/* `enum` must already be matched. */ static void parse_enum_decl(fb_parser_t *P, fb_compound_type_t *ct) { fb_token_t *t, *t0; fb_member_t *member; if (!(ct->symbol.ident = match(P, LEX_TOK_ID, "enum declaration expected identifier"))) { goto fail; } if (optional(P, ':')) { parse_type(P, &ct->type); if (ct->type.type != vt_scalar_type) { error_tok(P, ct->type.t, "integral type expected"); } else { switch (ct->type.t->id) { case tok_kw_float: case tok_kw_double: error_tok(P, ct->type.t, "integral type expected"); default: break; } } } ct->metadata = parse_metadata(P); if (!((t0 = match(P, '{', "enum declaration expected '{'")))) { goto fail; } for (;;) { if (!(t = match(P, LEX_TOK_ID, "member identifier expected"))) { goto fail; } if (P->failed >= FLATCC_MAX_ERRORS) { goto fail; } member = fb_add_member(P, &ct->members); member->symbol.ident = t; if (optional(P, '=')) { t = P->token; parse_value(P, &member->value, 0, "integral constant expected"); /* Leave detailed type (e.g. no floats) and range checking to a later stage. */ } /* * Trailing comma is optional in flatc but not in grammar, we * follow flatc. */ if (!optional(P, ',') || P->token->id == '}') { break; } P->doc = 0; } if (t0) { advance(P, '}', "enum missing closing '}' to match", t0); } revert_symbols(&ct->members); return; fail: recover(P, '}', 1); }
static void parse_value(fb_parser_t *P, fb_value_t *v, int flags, const char *error_msg) { fb_token_t *t; fb_token_t *sign; sign = optional(P, '-'); t = P->token; switch (t->id) { case LEX_TOK_INT: read_integer_value(P, t, v, sign != 0); break; case LEX_TOK_FLOAT: read_float_value(P, t, v, sign != 0); break; case tok_kw_true: v->b = 1; v->type = vt_bool; break; case tok_kw_false: v->b = 0; v->type = vt_bool; break; case LEX_TOK_STRING_BEGIN: next(P); parse_string_literal(P, v); if (!(flags & allow_string_value)) { v->type = vt_invalid; error_tok(P, t, error_msg); return; } if (sign) { v->type = vt_invalid; error_tok(P, t, "string constants cannot be signed"); return; } return; case LEX_TOK_ID: parse_ref(P, &v->ref); v->type = vt_name_ref; if (sign) { v->type = vt_invalid; /* Technically they could, but we do not allow it. */ error_tok(P, t, "named values cannot be signed"); } return; default: /* We might have consumed a sign, but never mind that. */ error_tok(P, t, error_msg); return; } if (sign && v->type == vt_bool) { v->type = vt_invalid; error_tok(P, t, "boolean constants cannot be signed"); } next(P); }
/* ':' must already be matched */ static void parse_type(fb_parser_t *P, fb_value_t *v) { fb_token_t *t = 0; fb_token_t *t0 = P->token; int vector = 0; v->type = vt_invalid; while ((t = optional(P, '['))) { ++vector; } if (vector > 1) { error_tok(P, t0, "vector type can only be one-dimensional"); } switch (P->token->id) { case tok_kw_int: case tok_kw_bool: case tok_kw_byte: case tok_kw_long: case tok_kw_uint: case tok_kw_float: case tok_kw_short: case tok_kw_ubyte: case tok_kw_ulong: case tok_kw_ushort: case tok_kw_double: v->t = P->token; v->type = vector ? vt_vector_type : vt_scalar_type; next(P); break; case tok_kw_string: v->t = P->token; v->type = vector ? vt_vector_string_type : vt_string_type; next(P); break; case LEX_TOK_ID: parse_ref(P, &v->ref); v->type = vector ? vt_vector_type_ref : vt_type_ref; break; case ']': error_tok(P, t, "vector type cannot be empty"); break; default: error_tok(P, t, "invalid type specifier"); break; } while (optional(P, ']') && vector--) { } if (vector) { error_tok_2(P, t, "vector type missing ']' to match", t0); } if ((t = optional(P, ']'))) { error_tok_2(P, t, "extra ']' not matching", t0); while (optional(P, ']')) { } } }
/* * We disallow escape characters, newlines and other control characters, * but especially escape characters because they would require us to * reallocate the string and convert the escaped characters. We also * disallow non-utf8 characters, but we do not check for it. The tab * character could meaningfully be accepted, but we don't. * * String literals are only used to name attributes, namespaces, * file identifiers and file externsions, so we really have no need * for these extra featuresescape . * * JSON strings should be handled separately, if or when supported - * either by converting escapes and reallocating the string, or * simply by ignoring the escape errors and use the string unmodified. */ static void parse_string_literal(fb_parser_t *P, fb_value_t *v) { fb_token_t *t; v->type = vt_string; v->s.s = 0; v->s.len = 0; for (;;) { t = P->token; switch (t->id) { case LEX_TOK_STRING_PART: if (v->s.s == 0) { v->s.s = (char *)t->text; } break; case LEX_TOK_STRING_ESCAPE: v->type = vt_invalid; error_tok(P, t, "escape not allowed in strings"); break; case LEX_TOK_STRING_CTRL: v->type = vt_invalid; error_tok_as_string(P, t, "control characters not allowed in strings", "?", 1); break; case LEX_TOK_STRING_NEWLINE: v->type = vt_invalid; error_tok(P, t, "newline not allowed in strings"); break; case LEX_TOK_STRING_UNTERMINATED: case LEX_TOK_STRING_END: goto done; default: error_tok(P, t, "internal error: unexpected token in string"); v->type = vt_invalid; goto done; } next(P); } done: /* * If we were to ignore all errors, we would get the full * string as is excluding delimiting quotes. */ if (v->s.s) { v->s.len = P->token->text - v->s.s; } if (!match(P, LEX_TOK_STRING_END, "unterminated string")) { v->type = vt_invalid; } }
static void parse_method(fb_parser_t *P, fb_member_t *fld) { fb_token_t *t; if (!(t = match(P, LEX_TOK_ID, "method expected identifier"))) { goto fail; } fld->symbol.ident = t; if (!match(P, '(', "method expected '(' after identifier")) { goto fail; } parse_type(P, &fld->req_type); if (!match(P, ')', "method expected ')' after request type")) { goto fail; } if (!match(P, ':', "method expected ':' before mandatory response type")) { goto fail; } parse_type(P, &fld->type); if ((t = optional(P, '='))) { error_tok(P, t, "method does not accept an initializer"); goto fail; } fld->metadata = parse_metadata(P); advance(P, ';', "method must be terminated with ';'", 0); return; fail: recover2(P, ';', 1, '}', 0); }
static int parse_schema(fb_parser_t *P) { fb_token_t *t, *t0; parse_include(P); t = P->token; for (;;) { if (is_end(t)) { break; } if (P->failed >= FLATCC_MAX_ERRORS) { return -1; } t0 = t; parse_schema_decl(P); t = P->token; if (t == t0) { if (P->failed) { return -1; } error_tok(P, t, "extra tokens in input"); return -1; } } revert_names(&P->schema.attributes); revert_symbols(&P->schema.symbols); return 0; }
/* `struct` or `table` must already be matched. */ static void parse_compound_type(fb_parser_t *P, fb_compound_type_t *ct) { fb_token_t *t = 0; if (!(t = match(P, LEX_TOK_ID, "Declaration expected an identifier"))) { goto fail; } ct->symbol.ident = t; ct->metadata = parse_metadata(P); if (!(match(P, '{', "Declaration expected '{'"))) { goto fail; } t = P->token; /* Allow empty tables and structs. */ #if 0 if (P->token->id == '}') { error_tok(P, t, "table / struct declaration cannot be empty"); } #endif while (P->token->id != '}') { parse_field(P, fb_add_member(P, &ct->members)); if (P->failed >= FLATCC_MAX_ERRORS) { goto fail; } } if (!optional(P, '}') && t) { error_tok_2(P, P->token, "Declaration missing closing '}' to match", t); } revert_symbols(&ct->members); return; fail: recover(P, '}', 1); }
static void parse_schema_decl(fb_parser_t *P) { switch(P->token->id) { case tok_kw_namespace: next(P); parse_namespace(P); break; case tok_kw_file_extension: next(P); parse_file_extension(P, &P->schema.file_extension); break; case tok_kw_file_identifier: next(P); parse_file_identifier(P, &P->schema.file_identifier); break; case tok_kw_root_type: next(P); parse_root_type(P, &P->schema.root_type); break; case tok_kw_attribute: next(P); parse_attribute(P, fb_add_attribute(P)); break; case tok_kw_struct: next(P); parse_compound_type(P, fb_add_struct(P)); break; case tok_kw_table: next(P); parse_compound_type(P, fb_add_table(P)); break; case tok_kw_enum: next(P); parse_enum_decl(P, fb_add_enum(P)); break; case tok_kw_union: next(P); parse_union_decl(P, fb_add_union(P)); break; case '{': error_tok(P, P->token, "JSON objects are not supported by this implementation"); break; default: error_tok(P, P->token, "unexpected token in schema definition"); break; } }
static void read_float_value(fb_parser_t *P, fb_token_t *t, fb_value_t *v, int sign) { char *end; v->type = vt_float; v->f = strtod(t->text, &end); if (end != t->text + t->len) { v->type = vt_invalid; error_tok(P, t, "invalid float format"); } else if (t->text[0] == '.') { v->type = vt_invalid; /* The FB spec requires this, in line with the JSON format. */ error_tok(P, t, "numeric values must start with a digit"); } else if (sign) { v->f = -v->f; } }
static inline fb_token_t *match(fb_parser_t *P, long id, char *msg) { fb_token_t *t = 0; if (P->token->id == id) { t = P->token; next(P); } else { error_tok(P, P->token, msg); } return t; }
static void parse_root_type(fb_parser_t *P, fb_root_type_t *rt) { fb_token_t *t = P->token; if (rt->name) { error_tok(P, P->token, "root_type already set"); } parse_ref(P, &rt->name); rt->scope = P->current_scope; advance(P, ';', "missing ';' expected by root_type at", t); }
static void read_integer_value(fb_parser_t *P, fb_token_t *t, fb_value_t *v, int sign) { char *end; v->type = vt_uint; v->u = strtoull(t->text, &end, 0); if (end && end != t->text + t->len) { v->type = vt_invalid; error_tok(P, t, "invalid integer format"); } if (sign) { v->i = -(int64_t)v->u; v->type = vt_int; #ifdef FLATCC_FAIL_ON_INT_SIGN_OVERFLOW /* Sometimes we might want this, so don't fail by default. */ if (v->i > 0) { v->type = vt_invalid; error_tok(P, t, "sign overflow in integer format"); } #endif } }
static void read_integer_value(fb_parser_t *P, fb_token_t *t, fb_value_t *v, int sign) { int status; v->type = vt_uint; /* The token does not store the sign internally. */ parse_integer(t->text, t->len, &v->u, &status); if (status != PARSE_INTEGER_UNSIGNED) { v->type = vt_invalid; error_tok(P, t, "invalid integer format"); } if (sign) { v->i = -(int64_t)v->u; v->type = vt_int; #ifdef FLATCC_FAIL_ON_INT_SIGN_OVERFLOW /* Sometimes we might want this, so don't fail by default. */ if (v->i > 0) { v->type = vt_invalid; error_tok(P, t, "sign overflow in integer format"); } #endif } }
/* Current token must be an identifier. */ static void parse_ref(fb_parser_t *P, fb_ref_t **ref) { *ref = fb_add_ref(P, P->token); next(P); ref = &((*ref)->link); while (optional(P, '.')) { if (P->token->id != LEX_TOK_ID) { error_tok(P, P->token, "namespace prefix expected identifier"); break; } *ref = fb_add_ref(P, P->token); ref = &((*ref)->link); next(P); } }
/* `union` must already be matched. */ static void parse_union_decl(fb_parser_t *P, fb_compound_type_t *ct) { fb_token_t *t0; fb_member_t *member; fb_ref_t *ref; if (!(ct->symbol.ident = match(P, LEX_TOK_ID, "union declaration expected identifier"))) { goto fail; } ct->metadata = parse_metadata(P); if (!((t0 = match(P, '{', "union declaration expected '{'")))) { goto fail; } for (;;) { if (P->token->id != LEX_TOK_ID) { error_tok(P, P->token, "union expects an identifier"); goto fail; } if (P->failed >= FLATCC_MAX_ERRORS) { goto fail; } member = fb_add_member(P, &ct->members); parse_ref(P, &ref); member->type.ref = ref; member->type.type = vt_type_ref; while (ref->link) { ref = ref->link; } /* The union member is the unqualified reference. */ member->symbol.ident = ref->ident; if (optional(P, '=')) { parse_value(P, &member->value, 0, "integral constant expected"); /* Leave detailed type (e.g. no floats) and range checking to a later stage. */ } if (!optional(P, ',') || P->token->id == '}') { break; } P->doc = 0; } advance(P, '}', "union missing closing '}' to match", t0); revert_symbols(&ct->members); /* Add implicit `NONE` member first in the list. */ member = fb_add_member(P, &ct->members); member->symbol.ident = &P->t_none; return; fail: recover2(P, ';', 1, '}', 0); }
static void parse_include(fb_parser_t *P) { fb_token_t *t = P->token; while (optional(P, tok_kw_include)) { if (P->opts.disable_includes) { error_tok(P, t, "include statements not supported by current environment"); } if (P->failed >= FLATCC_MAX_ERRORS) { return; } if (!match(P, LEX_TOK_STRING_BEGIN, "include expected a string literal as filename")) { recover(P, ';', 1); } parse_string_literal(P, &fb_add_include(P)->name); match(P, ';', "include statement expected ';'"); } }
static void parse_namespace(fb_parser_t *P) { fb_ref_t *ref = 0; fb_token_t *t = P->token; if (optional(P, ';') && t) { /* Revert to global namespace. */ P->current_scope = 0; return; } if (P->token->id != LEX_TOK_ID) { error_tok(P, P->token, "namespace expects an identifier"); recover(P, ';', 1); return; } parse_ref(P, &ref); advance(P, ';', "missing ';' expected by namespace at", t); P->current_scope = fb_add_scope(P, ref); }
static void parse_file_identifier(fb_parser_t *P, fb_value_t *v) { fb_token_t *t; if (v->type != vt_missing) { error_tok_as_string(P, P->token, "file identifier already set", v->s.s, v->s.len); } if (!match(P, LEX_TOK_STRING_BEGIN, "file_identifier expected string literal")) { goto fail; } t = P->token; parse_string_literal(P, v); if (v->s.s && v->s.len != 4) { v->type = vt_invalid; error_tok(P, t, "file_identifier must be 4 characters"); } match(P, ';', "file_identifier expected ';'"); return; fail: recover(P, ';', 1); }
static fb_token_t *next(fb_parser_t *P) { again: ++P->token; if (P->token == P->te) { /* We keep returning end of token to help binary operators etc., if any. */ --P->token; switch (P->token->id) { case LEX_TOK_EOS: case LEX_TOK_EOB: case LEX_TOK_EOF: P->token->id = LEX_TOK_EOF; return P->token; } error_tok(P, P->token, "Unexpected end of input"); } if (P->token->id == tok_kw_doc_comment) { fb_add_doc(P, P->token); goto again; } debug_token("next", P->token); return P->token; }
static fb_token_t *next(fb_parser_t *P) { again: ++P->token; if (P->token == P->te) { /* We keep returning end of token to help binary operators etc., if any. */ --P->token; assert(0); switch (P->token->id) { case LEX_TOK_EOS: case LEX_TOK_EOB: case LEX_TOK_EOF: P->token->id = LEX_TOK_EOF; return P->token; } error_tok(P, P->token, "unexpected end of input"); } if (P->token->id == tok_kw_doc_comment) { /* Note: we can have blanks that are control characters here, such as \t. */ fb_add_doc(P, P->token); goto again; } debug_token("next", P->token); return P->token; }
static void parse_schema_decl(fb_parser_t *P) { switch(P->token->id) { case tok_kw_namespace: next(P); parse_namespace(P); break; case tok_kw_file_extension: next(P); parse_file_extension(P, &P->schema.file_extension); break; case tok_kw_file_identifier: next(P); parse_file_identifier(P, &P->schema.file_identifier); break; case tok_kw_root_type: next(P); parse_root_type(P, &P->schema.root_type); break; case tok_kw_attribute: next(P); parse_attribute(P, fb_add_attribute(P)); break; case tok_kw_struct: next(P); parse_compound_type(P, fb_add_struct(P), tok_kw_struct); break; case tok_kw_table: next(P); parse_compound_type(P, fb_add_table(P), tok_kw_table); break; case tok_kw_rpc_service: next(P); parse_compound_type(P, fb_add_rpc_service(P), tok_kw_rpc_service); break; case tok_kw_enum: next(P); parse_enum_decl(P, fb_add_enum(P)); break; case tok_kw_union: next(P); parse_union_decl(P, fb_add_union(P)); break; case tok_kw_include: error_tok(P, P->token, "include statements must be placed first in the schema"); break; case '{': error_tok(P, P->token, "JSON objects in schema file is not supported - but a schema specific JSON parser can be generated"); break; case LEX_TOK_CTRL: error_tok_as_string(P, P->token, "unexpected control character in schema definition", "?", 1); break; case LEX_TOK_COMMENT_CTRL: if (lex_isblank(P->token->text[0])) { /* This also strips tabs in doc comments. */ next(P); break; } error_tok_as_string(P, P->token, "unexpected control character in comment", "?", 1); break; case LEX_TOK_COMMENT_UNTERMINATED: error_tok_as_string(P, P->token, "unterminated comment", "<eof>", 5); break; default: error_tok(P, P->token, "unexpected token in schema definition"); break; } }