/* Load an ID node. * IDs are indicated by the keyword id followed by the ID name, all contained * within parentheses. For example: * (id foo) * * The ( and id keyword must have been parsed before this is called. */ static ast_t* get_id(build_parser_t* builder, ast_t* existing_ast) { assert(builder != NULL); if(existing_ast != NULL) { ast_free(existing_ast); build_error(builder, "Seen ID not first in node"); return NULL; } ast_token_id id = get_token(builder); if(id != AT_ID && id != AT_STRING) { build_error(builder, "ID name expected"); return NULL; } ast_t* ast = ast_token(builder->token); ast_setid(ast, TK_ID); save_token(builder); if(get_token(builder) != AT_RPAREN) { build_error(builder, "Close paren expected for ID"); ast_free(ast); return NULL; } return ast; }
/* Load a type description. * A type description is the type AST node contained in square brackets. * The leading [ must have been loaded before this is called. */ static ast_t* get_type(build_parser_t* builder, ast_t* parent) { if(parent == NULL) { build_error(builder, "Type with no containing node"); return NULL; } if(ast_type(parent) != NULL) { build_error(builder, "Node has multiple types"); return NULL; } return get_nodes(builder, AT_RSQUARE); }
/* Load node attributes, if any. * Attributes are a list of keywords, each prefixed with a colon, following a * node name. For example: * seq:scope * * The node name must have been parsed before calling this, but not the colon. */ static ast_t* get_attributes(build_parser_t* builder, ast_t* node) { assert(builder != NULL); while(peek_token(builder) == AT_LBRACE) { get_token(builder); if(get_token(builder) != AT_ID) { build_error(builder, "Expected attribute in {}"); return NULL; } const char* attr = token_string(builder->token); if(strcmp("scope", attr)==0) { if(!scope_attribute(builder, node)) return NULL; } else if(strcmp("def", attr) == 0) { if(!def_attribute(builder, node)) return NULL; } else if(strcmp("dataref", attr) == 0) { if(!dataref_attribute(builder, node)) return NULL; } else { build_error(builder, "Unrecognised attribute \"%s\"", attr); return NULL; } if(get_token(builder) != AT_RBRACE) { build_error(builder, "Expected } after attribute %s", attr); return NULL; } } return node; }
/** * An element it the list has a bad type. * Used by i18n_message. */ ERL_NIF_TERM list_element_error(ErlNifEnv* env, const ERL_NIF_TERM list, int32_t num) { return build_error(env, enif_make_tuple3(env, enif_make_atom(env, "bad_element"), enif_make_tuple2(env, enif_make_atom(env, "list"), list), enif_make_tuple2(env, enif_make_atom(env, "index"), enif_make_int(env, (int) num)) )); }
/** * Convert an UErrorCode to an atom. */ ERL_NIF_TERM parse_error(ErlNifEnv* env, UErrorCode status, UParseError* e) { return build_error(env, enif_make_tuple3(env, enif_make_atom(env, u_errorName(status)), enif_make_tuple2(env, enif_make_atom(env, "line"), enif_make_int(env, (int) e->line)), enif_make_tuple2(env, enif_make_atom(env, "offset"), enif_make_int(env, (int) e->offset)) )); }
// Process all our sub-tree references static bool process_refs(build_parser_t* builder) { assert(builder != NULL); assert(builder->defs != NULL); for(builder_ref_t* p = builder->refs; p != NULL; p = p->next) { assert(p->name != NULL); assert(p->node != NULL); ast_t* subtree = (ast_t*)symtab_find(builder->defs, p->name, NULL); if(subtree == NULL) { build_error(builder, "Attribute {def %s} not found", p->name); return false; } if(p->symtab == NULL) { // Set node data ast_setdata(p->node, subtree); } else { // Add subtree to node's symtab symtab_t* symtab = ast_get_symtab(p->node); assert(symtab != NULL); if(!symtab_add(symtab, p->symtab, subtree, SYM_NONE)) { build_error(builder, "Duplicate name %s in symbol table", p->name); return false; } } } return true; }
/* Process a dataref attribute. * The dataref keyword should be found before calling this, but nothing else. */ static bool dataref_attribute(build_parser_t* builder, ast_t* node) { assert(builder != NULL); if(get_token(builder) != AT_ID) { build_error(builder, "Expected {dataref name}"); return false; } const char* ref_name = stringtab(token_string(builder->token)); add_subtree_ref(builder, node, ref_name, NULL); return true; }
// Add a sub-tree name definition static bool add_subtree_name(build_parser_t* builder, const char* name, ast_t* subtree) { assert(builder != NULL); assert(builder->defs != NULL); if(!symtab_add(builder->defs, name, subtree, SYM_NONE)) { build_error(builder, "Multiple {def %s} attributes", name); return false; } return true; }
/** * An element it the list has a bad type. * Used by i18n_message. */ ERL_NIF_TERM list_element_error(ErlNifEnv* env, UErrorCode status, const ERL_NIF_TERM list, int32_t num, const char *pszFile, long lLine) { return build_error(env, enif_make_tuple4(env, enif_make_atom(env, "bad_element"), enif_make_atom(env, u_errorName(status)), enif_make_tuple2(env, enif_make_atom(env, "list"), list), enif_make_tuple2(env, enif_make_atom(env, "index"), enif_make_int(env, (int) num)) ), pszFile, lLine); }
/** * Pass an error to Erlang code. * Error as a string will be converted to an atom. */ ERL_NIF_TERM make_error(ErlNifEnv* env, const char* code, const char *pszFile, long lLine) { return build_error(env, enif_make_atom(env, code), pszFile, lLine); }
// Load a sequence of nodes until the specified terminator is found static ast_t* get_nodes(build_parser_t* builder, ast_token_id terminator) { assert(builder != NULL); ast_t* ast = NULL; ast_t* last_child = NULL; while(true) { ast_token_id id = get_token(builder); ast_t* child = NULL; bool is_type = false; if(id == terminator) { if(ast == NULL) build_error(builder, "Syntax error"); if(ast_id(ast) == TK_MINUS && ast_childcount(ast) == 1) ast_setid(ast, TK_UNARY_MINUS); return ast; } if(id == AT_ID) id = keyword_replace(builder); switch(id) { case AT_LPAREN: child = get_nodes(builder, AT_RPAREN); break; case AT_LSQUARE: child = get_type(builder, ast); is_type = true; break; case AT_ERROR: // Propogate break; case AT_STRING: case AT_TOKEN: child = ast_token(builder->token); save_token(builder); get_attributes(builder, child); break; case AT_ID: if(strcmp("id", token_string(builder->token)) == 0) return get_id(builder, ast); build_error(builder, "Unrecognised identifier \"%s\"", token_string(builder->token)); break; default: build_error(builder, "Syntax error"); break; } if(child == NULL) { // An error occurred and should already have been reported ast_free(ast); return NULL; } if(ast == NULL) { ast = child; last_child = NULL; } else if(is_type) { ast_settype(ast, child); } else { if(last_child == NULL) ast_add(ast, child); else ast_add_sibling(last_child, child); last_child = child; } } }
/** * Pass an error to Erlang code. * Error as a string will be converted to an atom. */ ERL_NIF_TERM make_error(ErlNifEnv* env, const char* code) { return build_error(env, enif_make_atom(env, code)); }