text: #include <string.h>
  #include <assert.h>
  
  #include "semantics.h"
  #include "parser.h"
  #include "coerce.h"
  #include "stdio.h"
  
  /* -DFLATCC_PORTABLE may help if inttypes.h is missing. */
  #ifndef PRId64
  #include <inttypes.h>
  #endif
  
  /* Same order as enum! */
  static const char *fb_known_attribute_names[] = {
      "",
      "id",
      "deprecated",
      "original_order",
      "force_align",
      "bit_flags",
      "nested_flatbuffer",
      "key",
      "required",
      "hash",
      "base64",
      "base64url",
      "primary_key",
      "sorted",
  };
  
  static const int fb_known_attribute_types[] = {
      vt_invalid, /* Unknowns have arbitrary types. */
      vt_uint,
      vt_missing,
      vt_missing,
      vt_uint,
      vt_missing,
      vt_string,
      vt_missing,
      vt_missing,
      vt_string,
      vt_missing,
      vt_missing,
      vt_missing,
      vt_missing,
  };
  
  static fb_scalar_type_t map_scalar_token_type(fb_token_t *t)
  {
      switch (t->id) {
      case tok_kw_uint64:
      case tok_kw_ulong:
          return fb_ulong;
      case tok_kw_uint32:
      case tok_kw_uint:
          return fb_uint;
      case tok_kw_uint16:
      case tok_kw_ushort:
          return fb_ushort;
      case tok_kw_uint8:
      case tok_kw_ubyte:
          return fb_ubyte;
      case tok_kw_char:
          return fb_char;
      case tok_kw_bool:
          return fb_bool;
      case tok_kw_int64:
      case tok_kw_long:
          return fb_long;
      case tok_kw_int32:
      case tok_kw_int:
          return fb_int;
      case tok_kw_int16:
      case tok_kw_short:
          return fb_short;
      case tok_kw_int8:
      case tok_kw_byte:
          return fb_byte;
      case tok_kw_float64:
      case tok_kw_double:
          return fb_double;
      case tok_kw_float32:
      case tok_kw_float:
          return fb_float;
      default:
          return fb_missing_type;
      }
  }
  
  /*
   * The flatc compiler currently has a 256 limit.
   *
   * Some target C compilers might respect anything above
   * 16 and may reguire that PAD option of the C code generator.
   */
  static inline int is_valid_align(uint64_t align)
  {
      uint64_t n = 1;
      if (align == 0 || align > FLATCC_FORCE_ALIGN_MAX) {
          return 0;
      }
      while (n <= align) {
          if (n == align) {
              return 1;
          }
          n *= 2;
      }
      return 0;
  }
  
  static inline uint64_t fb_align(uint64_t size, uint64_t align)
  {
      assert(is_valid_align(align));
  
      return (size + align - 1) & ~(align - 1);
  }
  
  /*
   * The FNV-1a 32-bit little endian hash is a FlatBuffers standard for
   * transmission of type identifiers in a compact form, in particular as
   * alternative file identifiers. Note that if hash becomes 0, we map it
   * to hash("").
   */
  static inline void set_type_hash(fb_compound_type_t *ct)
  {
      fb_ref_t *name;
      fb_symbol_t *sym;
      uint32_t hash;
  
      hash = fb_hash_fnv1a_32_init();
      if (ct->scope)
      for (name = ct->scope->name; name; name = name->link) {
          hash = fb_hash_fnv1a_32_append(hash, name->ident->text, name->ident->len);
          hash = fb_hash_fnv1a_32_append(hash, ".", 1);
      }
      sym = &ct->symbol;
      hash = fb_hash_fnv1a_32_append(hash, sym->ident->text, sym->ident->len);
      if (hash == 0) {
          hash = fb_hash_fnv1a_32_init();
      }
      ct->type_hash = hash;
  }
  
  static inline fb_scope_t *fb_find_scope_by_string(fb_schema_t *S, const char *name, int len)
  {
      if (!S || !S->root_schema) {
          return 0;
      }
      if (len == 0) {
          /* Global scope. */
          name = 0;
      }
      return fb_scope_table_find(&S->root_schema->scope_index, name, (size_t)len);
  }
  
  /* count = 0 indicates zero-terminated ref list, name = 0 indicates global scope. */
  static inline fb_scope_t *fb_find_scope_by_ref(fb_schema_t *S, const fb_ref_t *name, int count)
  {
      if (!S || !S->root_schema) {
          return 0;
      }
      return fb_scope_table_find(&S->root_schema->scope_index, name, (size_t)(-count));
  }
  
  static inline fb_symbol_t *define_fb_symbol(fb_symbol_table_t *si, fb_symbol_t *sym)
  {
      return fb_symbol_table_insert_item(si, sym, ht_keep);
  }
  
  static inline fb_symbol_t *find_fb_symbol_by_token(fb_symbol_table_t *si, fb_token_t *token)
  {
      return fb_symbol_table_find(si, token->text, token->len);
  }
  
  static inline fb_name_t *define_fb_name(fb_name_table_t *ni, fb_name_t *name)
  {
      return fb_name_table_insert_item(ni, name, ht_keep);
  }
  
  static inline fb_name_t * find_fb_name_by_token(fb_name_table_t *ni, fb_token_t *token)
  {
      return fb_name_table_find(ni, token->text, token->len);
  }
  
  /* Returns 1 if value exists, 0 otherwise, */
  static inline int add_to_value_set(fb_value_set_t *vs, fb_value_t *value)
  {
      return fb_value_set_insert_item(vs, value, ht_keep) != 0;
  }
  
  static inline int is_in_value_set(fb_value_set_t *vs, fb_value_t *value)
  {
      return 0 != fb_value_set_find_item(vs, value);
  }
  
  /*
   * An immediate parent scope does not necessarily exist and it might
   * appear in a later search, so we return the nearest existing parent
   * and do not cache the parent.
   */
  static inline fb_scope_t *find_parent_scope(fb_parser_t *P, fb_scope_t *scope) 
  {
      fb_ref_t *p;
      int count;
      fb_scope_t *parent;
  
      parent = 0;
      count = 0;
      if (scope == 0) {
          return 0;
      }
      p = scope->name;
      while (p) {
          ++count;
          p = p->link;
      }
      if (count == 0) {
          return 0;
      }
      while (count-- > 1) {
          if ((parent = fb_find_scope_by_ref(&P->schema, scope->name, count))) {
              return parent;
          }
      }
      /* Root scope. */
      return fb_find_scope_by_ref(&P->schema, 0, 0);
  }
  
  static inline fb_symbol_t *lookup_string_reference(fb_parser_t *P, fb_scope_t *local, const char *s, int len)
  {
      fb_symbol_t *sym;
      fb_scope_t *scope;
      const char *name, *basename;
      int k;
  
      name = s;
      basename = s;
      k = len;
      while (k > 0) {
          if (s[--k] == '.') {
              basename = s + k + 1;
              --len;
              break;
          }
      }
      len -= k;
      if (local && k == 0) {
          do {
              if ((sym = fb_symbol_table_find(&local->symbol_index, basename, len))) {
                  if (get_compound_if_visible(&P->schema, sym)) {
                      return sym;
                  }
              }
              local = find_parent_scope(P, local);
          } while (local);
          return 0;
      }
      if (!(scope = fb_find_scope_by_string(&P->schema, name, k))) {
          return 0;
      }
      return fb_symbol_table_find(&scope->symbol_index, basename, len);
  }
  
  /*
   * First search the optional local scope, then the scope of the namespace prefix if any.
   * If `enumval` is non-zero, the last namepart is stored in that
   * pointer and the lookup stops before that part.
   *
   * If the reference is prefixed with a namespace then the scope is
   * looked up relative to root then the basename is searched in that
   * scope.
   *
   * If the refernce is not prefixed with a namespace then the name is
   * search in the local symbol table (which may be the root if null) and
   * if that fails, the nearest existing parent scope is used as the new
   * local scope and the process is repeated until local is root.
   *
   * This means that namespace prefixes cannot be relative to a parent
   * namespace or to the current scope, but simple names can be found in a
   * parent namespace.
   */
  static inline fb_symbol_t *lookup_reference(fb_parser_t *P, fb_scope_t *local, fb_ref_t *name, fb_ref_t **enumval)
  {
      fb_ref_t *basename, *last, *p;
      fb_scope_t *scope;
      fb_symbol_t *sym;
      int count;
  
      count = 0;
      scope = 0;
      p = name;
      last = 0;
      basename = 0;
      while (p) {
          basename = last;
          last = p;
          p = p->link;
          ++count;
      }
      if (enumval) {
          --count;
          *enumval = last;
      } else {
          basename = last;
      }
      if (!basename) {
          return 0;
      }
      if (local && count == 1) {
          do {
              if ((sym = find_fb_symbol_by_token(&local->symbol_index, basename->ident))) {
                  if (get_compound_if_visible(&P->schema, sym)) {
                      return sym;
                  }
              }
              local = find_parent_scope(P, local);
          } while (local);
          return 0;
      }
      /* Null name is valid in scope lookup, indicating global scope. */
      if (count == 1) {
          name = 0;
      }
      if (!(scope = fb_find_scope_by_ref(&P->schema, name, count - 1))) {
          return 0;
      }
      sym = find_fb_symbol_by_token(&scope->symbol_index, basename->ident);
      if (sym && get_compound_if_visible(&P->schema, sym)) {
          return sym;
      }
      return 0;
  }
  
  static inline fb_symbol_t *lookup_type_reference(fb_parser_t *P, fb_scope_t *local, fb_ref_t *name)
  {
      return lookup_reference(P, local, name, 0);
  }
  
  /*
   * `ct` is null when looking up names for scalar types and otherwise it is
   * the enum type being assigned. The provided reference may reference
   * an enum value in the `ct` type, or another enum if a scope/type is
   * given.
   */
  static inline int lookup_enum_name(fb_parser_t *P, fb_scope_t *local, fb_compound_type_t *ct, fb_ref_t *ref, fb_value_t *value)
  {
      fb_symbol_t *sym;
      fb_ref_t *enumval;
      fb_member_t *member;
  
      enumval = 0;
      assert(ref);
      assert(ct == 0 || ct->symbol.kind == fb_is_enum);
      sym = lookup_reference(P, local, ref, &enumval);
      if (sym && sym->kind == fb_is_enum) {
          ct = (fb_compound_type_t *)sym;
      } else if (ref->link) {
          /* If there was a scope / type prefix, it was not found, or it was not an enum type. */
          return -1;
      }
      if (!ct) {
          return -1;
      }
      sym = find_fb_symbol_by_token(&ct->index, enumval->ident);
      if (!sym) {
          return -1;
      }
      member = (fb_member_t *)sym;
      *value = member->value;
      return 0;
  }
  
  /* This is repeated for every include file, but this pose no problem. */
  static void install_known_attributes(fb_parser_t *P)
  {
      int i;
      fb_attribute_t *a;
  
      for (i = 0; i < KNOWN_ATTR_COUNT; ++i) {
          /* Don't put it in the parsed list, just the index. */
          a = new_elem(P, sizeof(*a));
          a->known = i;
          a->name.name.s.s = (char *)fb_known_attribute_names[i];
          a->name.name.s.len = (long)strlen(fb_known_attribute_names[i]);
          a->name.name.type = vt_string;
          a->name.link = 0;
          if ((a = (fb_attribute_t *)define_fb_name(&P->schema.root_schema->attribute_index, &a->name))) {
              /*
               * If the user alredy defined the attribute, keep that instead.
               * (Memory leak is ok here.)
               */
              a->known = i;
          }
      }
  }
  
  static void revert_order(fb_compound_type_t **list) {
      fb_compound_type_t *next, *prev = 0, *link = *list;
  
      while (link) {
          next = link->order;
          link->order = prev;
          prev = link;
          link = next;
      }
      *list = prev;
  }
  
  static inline unsigned short process_metadata(fb_parser_t *P, fb_metadata_t *m,
          unsigned short expect, fb_metadata_t *out[KNOWN_ATTR_COUNT])
  {
      unsigned short flags;
      int n = FLATCC_ATTR_MAX;
      int i;
      int type;
      fb_attribute_t *a;
  
      memset(out, 0, sizeof(out[0]) * KNOWN_ATTR_COUNT);
      for (flags = 0; m && n; --n, m = m->link) {
          a = (fb_attribute_t *)find_fb_name_by_token(&P->schema.root_schema->attribute_index, m->ident);
          if (!a) {
              error_tok(P, m->ident, "unknown attribute not declared");
              continue;
          }
          if (!(i = a->known)) {
              continue;
          }
          if (!((1 << i) & expect)) {
              error_tok(P, m->ident, "known attribute not expected in this context");
              continue;
          }
          flags |= 1 << i;
          if (out[i]) {
              error_tok(P, m->ident, "known attribute listed multiple times");
              continue;
          }
          out[i] = m;
          type = fb_known_attribute_types[i];
          if (type == vt_missing && m->value.type != vt_missing) {
              error_tok(P, m->ident, "known attribute does not expect a value");
              continue;
          }
          if (type == vt_string && m->value.type != vt_string) {
              error_tok(P, m->ident, "known attribute expects a string");
              continue;
          }
          if (type == vt_uint && m->value.type != vt_uint) {
              error_tok(P, m->ident, "known attribute expects an unsigned integer");
              continue;
          }
          if (type == vt_int && m->value.type != vt_uint && m->value.type != vt_int) {
              error_tok(P, m->ident, "known attribute expects an integer");
              continue;
          }
          if (type == vt_bool && m->value.type != vt_bool) {
              error_tok(P, m->ident, "known attribute expects 'true' or 'false'");
              continue;
          }
      }
      if (m) {
          error_tok(P, m->ident, "too many attributes");
      }
      return flags;
  }
  
  /*
   * Recursive types are allowed, according to FlatBuffers Internals doc.,
   * but this cannot be possible for structs because they have no default
   * value or null option, and can only hold scalars and other structs, so
   * recursion would never terminate. Enums are simple types and cannot be
   * recursive either. Unions reference tables which may reference unions,
   * and recursion works well here. Tables allow any other table, union,
   * or scalar value to be optional or default, so recursion is possible.
   * In conclusion, Unions and Table members may reference all other
   * types, and self. Enums are trivially checked because the only allow
   * scalars, which leaves structs that can build illegal forms.
   *
   * Object instances cannot be recursive meaning the object graph is
   * always a tree, but this isn't really a concern for the schema
   * compiler, and for the builder it happens naturally as it only adds to
   * the buffer (though a compressor might reuse old data without
   * violating the tree?).
   *
   * Conclusion: check structs for circular references and allow
   * everything else to unfold, provided they otherwise pass type checks.
   *
   * Algorithm:
   *
   * Depth first search of the struct reference tree. We maintain flags to
   * find back-links. We prune sub-trees already fully analyzed by using
   * the closed flag. This operation is O(N) since each struct member is
   * visited once.
   *
   * Recursion is limited to prevent blowing the stack and to protect
   * against abuse.
   */
  static int analyze_struct(fb_parser_t *P, fb_compound_type_t *ct)
  {
      fb_symbol_t *sym;
      fb_compound_type_t *type;
      fb_member_t *member;
      int ret = 0;
      uint64_t size;
      uint16_t align;
      fb_token_t *t;
  
      assert(ct->symbol.kind == fb_is_struct);
  
      assert(!(ct->symbol.flags & fb_circular_open));
      if (ct->symbol.flags & fb_circular_closed) {
          return 0;
      }
      assert(!ct->order);
      ct->symbol.flags |= fb_circular_open;
      align = 1;
      for (sym = ct->members; sym; sym = sym->link) {
          type = 0;
          if (P->nesting_level >= FLATCC_NESTING_MAX) {
              error(P, "maximum allowed nesting level exceeded while processing struct hierarchy");
              return -1;
          }
          member = (fb_member_t *)sym;
          switch (member->type.type) {
          case vt_fixed_array_type:
              /* fall through */
          case vt_scalar_type:
              t = member->type.t;
              member->type.st = map_scalar_token_type(t);
              size = sizeof_scalar_type(member->type.st);
              if (size < 1) {
                  error_sym_tok(P, sym, "unexpected type", t);
                  return -1;
              }
              member->align = (uint16_t)size;
              member->size = size * member->type.len;
              break;
          case vt_fixed_array_compound_type_ref:
              /* fall through */
          case vt_compound_type_ref:
              /* Enums might not be valid, but then it would be detected earlier. */
              if (member->type.ct->symbol.kind == fb_is_enum) {
                  type = member->type.ct;
                  size = type->size;
                  member->align = (uint16_t)size;
                  member->size = member->type.len * type->size;
                  break;
              } else if (member->type.ct->symbol.kind == fb_is_struct) {
                  type = member->type.ct;
                  if (type->symbol.flags & fb_circular_open) {
                      error_sym_2(P, sym, "circular reference to struct at", &type->symbol);
                      return -1;
                  }
                  if (!(type->symbol.flags & fb_circular_closed)) {
                      if (P->opts.hide_later_struct) {
                          error_sym_2(P, sym, "dependency on later defined struct not permitted with current settings", &type->symbol);
                      }
                      ++P->nesting_level;
                      ret = analyze_struct(P, type);
                      --P->nesting_level;
                      if (ret) {
                          return ret;
                      }
                  }
                  member->align = type->align;
                  member->size = member->type.len * type->size;
                  break;
              } else {
                  error_sym(P, sym, "unexpected compound type for field");
                  return -1;
              }
              break;
          case vt_invalid:
              /* Old error. */
              return -1;
          default:
              error_sym(P, sym, "unexpected type");
              return -1;
          }
          member->offset = fb_align(ct->size, member->align);
          if (member->offset < ct->size || member->offset + member->size < member->offset) {
              error_sym(P, sym, "struct size overflow");
              return -1;
          }
          size = member->offset + member->size;
          if (size < ct->size || size > FLATCC_STRUCT_MAX_SIZE) {
              error_sym(P, sym, "struct field overflows maximum allowed struct size");
          };
          ct->size = size;
          /*
           * FB spec is not very clear on this - but experimentally a
           * force aligned member struct will force that alignment upon a
           * containing struct if the alignment would otherwise be
           * smaller. In otherwise, a struct is aligned to the alignment
           * of the largest member, not just the largest scalar member
           * (directly or indirectly).
           */
          if (align < member->align) {
              align = member->align;
          }
      }
      if (ct->align > 0) {
          if (align > ct->align) {
              error_sym(P, &ct->symbol, "'force_align' cannot be smaller than natural alignment for");
              ct->align = align;
          }
      } else {
          ct->align = align;
      }
      /* Add trailing padding if necessary. */
      ct->size = fb_align(ct->size, ct->align);
  
      if (ct->size == 0) {
          error_sym(P, &ct->symbol, "struct cannot be empty");
          return -1;
      }
  
      ct->symbol.flags |= fb_circular_closed;
      ct->symbol.flags &= ~fb_circular_open;
      ct->order = P->schema.ordered_structs;
      P->schema.ordered_structs = ct;
      return ret;
  }
  
  static int define_nested_table(fb_parser_t *P, fb_scope_t *local, fb_member_t *member, fb_metadata_t *m)
  {
      fb_symbol_t *type_sym;
  
      if (member->type.type != vt_vector_type || member->type.st != fb_ubyte) {
          error_tok(P, m->ident, "'nested_flatbuffer' attribute requires a [ubyte] vector type");
          return -1;
      }
      if (m->value.type != vt_string) {
          /* All known attributes get automatically type checked, so just ignore. */
          return -1;
      }
      type_sym = lookup_string_reference(P, local, m->value.s.s, m->value.s.len);
      if (!type_sym) {
          error_tok_as_string(P, m->ident, "nested reference not found", m->value.s.s, m->value.s.len);
          return -1;
      }
      if (type_sym->kind != fb_is_table) {
          if (!P->opts.allow_struct_root) {
              error_tok_2(P, m->ident, "nested reference does not refer to a table", type_sym->ident);
              return -1;
          }
          if (type_sym->kind != fb_is_struct) {
              error_tok_2(P, m->ident, "nested reference does not refer to a table or a struct", type_sym->ident);
              return -1;
          }
      }
      member->nest = (fb_compound_type_t *)type_sym;
      return 0;
  }
  
  static int process_struct(fb_parser_t *P, fb_compound_type_t *ct)
  {
      fb_symbol_t *sym, *old, *type_sym;
      fb_member_t *member;
      fb_metadata_t *knowns[KNOWN_ATTR_COUNT], *m;
      uint16_t allow_flags;
      int key_count = 0, primary_count = 0, key_ok = 0;
  
      if (ct->type.type) {
          error_sym(P, &ct->symbol, "internal error: struct cannot have a type");
          return -1;
      }
      ct->metadata_flags = process_metadata(P, ct->metadata, fb_f_force_align, knowns);
      if ((m = knowns[fb_attr_force_align])) {
          if (!is_valid_align(m->value.u)) {
              error_sym(P, &ct->symbol, "'force_align' exceeds maximum permitted alignment or is not a power of 2");
          } else {
              /* This may still fail on natural alignment size. */
              ct->align = (uint16_t)m->value.u;
          }
      }
      for (sym = ct->members; sym; sym = sym->link) {
          if ((old = define_fb_symbol(&ct->index, sym))) {
              error_sym_2(P, sym, "struct field already defined here", old);
              continue;
          }
          if (sym->kind != fb_is_member) {
              error_sym(P, sym, "internal error: field type expected");
              return -1;
          }
          key_ok = 1;
          member = (fb_member_t *)sym;
          allow_flags = 0;
          /* Notice the difference between fb_f_ and fb_attr_ (flag vs index). */
          if (P->opts.allow_struct_field_key) {
              allow_flags |= fb_f_key;
              if (P->opts.allow_primary_key) {
                  allow_flags |= fb_f_primary_key;
              }
          }
          if (P->opts.allow_struct_field_deprecate) {
              allow_flags |= fb_f_deprecated;
          }
          member->metadata_flags = process_metadata(P, member->metadata, allow_flags, knowns);
          switch (member->type.type) {
          case vt_fixed_array_type_ref:
              key_ok = 0;
              /* fall through */
          case vt_type_ref:
              type_sym = lookup_type_reference(P, ct->scope, member->type.ref);
              if (!type_sym) {
                  error_ref_sym(P, member->type.ref, "unknown type reference used with struct field", sym);
                  member->type.type = vt_invalid;
                  continue;
              }
              member->type.ct = (fb_compound_type_t*)type_sym;
              member->type.type = member->type.type == vt_fixed_array_type_ref ?
                  vt_fixed_array_compound_type_ref : vt_compound_type_ref;
              if (type_sym->kind != fb_is_struct) {
                  if (P->opts.allow_enum_struct_field) {
                      if (type_sym->kind != fb_is_enum) {
                          error_sym_2(P, sym, "struct fields can only be scalars, structs, and enums, or arrays of these, but has type", type_sym);
                          member->type.type = vt_invalid;
                          return -1;
                      }
                      if (!P->opts.allow_enum_key) {
                          key_ok = 0;
                          break;
                      }
                  } else {
                      error_sym_2(P, sym, "struct fields can only be scalars and structs, or arrays of these, but has type", type_sym);
                      member->type.type = vt_invalid;
                      return -1;
                  }
              } else {
                  key_ok = 0;
              }
              break;
          case vt_fixed_array_string_type:
              error_sym(P, sym, "fixed size arrays cannot have string elements");
              member->type.type = vt_invalid;
              return -1;
          case vt_fixed_array_type:
              key_ok = 0;
              break;
          case vt_scalar_type:
              break;
          default:
              error_sym(P, sym, "struct member member can only be of struct scalar, or fixed size scalar array type");
              return -1;
          }
          if (!key_ok) {
              if (member->metadata_flags & fb_f_key) {
                  member->type.type = vt_invalid;
                  error_sym(P, sym, "key attribute now allowed for this kind of field");
                  return -1;
              }
              if (member->metadata_flags & fb_f_primary_key) {
                  member->type.type = vt_invalid;
                  error_sym(P, sym, "primary_key attribute now allowed for this kind of field");
                  return -1;
              }
          }
          if (member->metadata_flags & fb_f_deprecated) {
              if (member->metadata_flags & fb_f_key) {
                  error_sym(P, sym, "key attribute not allowed for deprecated struct member");
                  return -1;
              } else if (member->metadata_flags & fb_f_primary_key) {
                  error_sym(P, sym, "primary_key attribute not allowed for deprecated struct member");
                  return -1;
              }
          }
          if (member->metadata_flags & fb_f_key) {
              if (member->metadata_flags & fb_f_primary_key) {
                  error_sym(P, sym, "primary_key attribute conflicts with key attribute");
                  member->type.type = vt_invalid;
                  return -1;
              }
              key_count++;
              if (!ct->primary_key) {
                  /* First key is primary key if no primary key is given explicitly. */
                  ct->primary_key = member;
              }
          } else if (member->metadata_flags & fb_f_primary_key) {
              if (primary_count++) {
                  error_sym(P, sym, "at most one struct member can have a primary_key attribute");
                  member->type.type = vt_invalid;
                  return -1;
              } 
              key_count++;
              /* Allow backends to treat primary key as an ordinary key. */
              member->metadata_flags |= fb_f_key;
              ct->primary_key = member;
          }
          if (member->value.type) {
              error_sym(P, sym, "struct member member cannot have a default value");
              continue;
          }
      }
      if (key_count) {
          ct->symbol.flags |= fb_indexed;
      }
      /* Set primary key flag for backends even if chosen by default. */
      if (ct->primary_key) {
          ct->primary_key->metadata_flags |= fb_f_primary_key;
      }
      if (key_count > 1 && !P->opts.allow_multiple_key_fields) {
          error_sym(P, &ct->symbol, "table has multiple key fields, but at most one is permitted");
          return -1;
      }
      return 0;
  }
  
  static fb_member_t *original_order_members(fb_parser_t *P, fb_member_t *next)
  {
      fb_member_t *head = 0;
      fb_member_t **tail = &head;
  
      /* Not used for now, but in case we need error messages etc. */
      (void)P;
  
      while (next) {
          *tail = next;
          tail = &next->order;
          next = (fb_member_t *)(((fb_symbol_t *)next)->link);
      }
      *tail = 0;
      return head;
  }
  
  /*
   * Alignment of table offset fields are generally not stored, and
   * vectors store the element alignment for scalar types, so we
   * detect alignment based on type also. Unions are tricky since they
   * use a single byte type followed by an offset, but it is impractical
   * to store these separately so we sort by the type field.
   */
  static fb_member_t *align_order_members(fb_parser_t *P, fb_member_t *members)
  {
      uint16_t i, j, k;
      fb_member_t *heads[9] = {0};
      fb_member_t **tails[9] = {0};
      fb_member_t *next = members;
  
      while (next) {
          k = next->align;
          switch (next->type.type) {
          case vt_compound_type_ref:
              switch (next->type.ct->symbol.kind) {
              case fb_is_struct:
              case fb_is_enum:
                  k = next->type.ct->align;
                  break;
              case fb_is_union:
                  /*
                   * Unions align to their offsets because the type can
                   * always be added last in a second pass since it is 1
                   * byte.
                   */
                  k = P->opts.offset_size;
                  break;
              default:
                  k = P->opts.offset_size;
                  break;
              }
              break;
          case vt_vector_compound_type_ref:
          case vt_string_type:
          case vt_vector_type:
          case vt_vector_string_type:
              k = P->opts.offset_size;
              break;
          case vt_invalid:
              /* Just to have some sane behavior. */
              return original_order_members(P, members);
          default:
              k = next->align;
              break;
          }
          assert(k > 0);
          i = 0;
          while (k >>= 1) {
              ++i;
          }
          /* Normally the largest alignment is 256, but otherwise we group them together. */
          if (i > 7) {
              i = 7;
          }
          if (!heads[i]) {
              heads[i] = next;
          } else {
              *tails[i] = next;
          }
          tails[i] = &next->order;
          next = (fb_member_t *)(((fb_symbol_t *)next)->link);
      }
      i = j = 8;
      tails[8] = &heads[8];
      while (j) {
          while (i && !heads[--i]) {
          }
          *tails[j] = heads[i];
          j = i;
      }
      return heads[8];
  }
  
  /* Temporary markers only used during assignment of field identifiers. */
  enum { unused_field = 0, normal_field, type_field };
  
  static int process_table(fb_parser_t *P, fb_compound_type_t *ct)
  {
      char msg_buf [100];
      fb_symbol_t *sym, *old, *type_sym;
      fb_member_t *member;
      fb_metadata_t *knowns[KNOWN_ATTR_COUNT], *m;
      int ret = 0;
      uint64_t count = 0;
      int need_id = 0, id_failed = 0;
      uint64_t max_id = 0;
      int key_ok, key_count = 0, primary_count = 0;
      int is_union_vector, is_vector;
      uint64_t i, j;
      int max_id_errors = 10;
      int allow_flags = 0;
  
      /*
       * This just tracks the presence of a `normal_field` or a hidden
       * `type_field`.  The size is litmited to 16-bit unsigned offsets.
       * It is only of relevance for ithe optional `id` table field
       * attribute.
       */
      uint8_t *field_marker = 0;
      fb_symbol_t **field_index = 0;
  
      assert(ct->symbol.kind == fb_is_table);
      assert(!ct->type.type);
  
      ct->metadata_flags = process_metadata(P, ct->metadata, fb_f_original_order, knowns);
      /*
       * `original_order` now lives as a flag, we need not consider it
       * further until code generation.
       */
      for (sym = ct->members; sym; sym = sym->link) {
          key_ok = 0;
          type_sym = 0;
          is_vector = 0;
          is_union_vector = 0;
          if ((old = define_fb_symbol(&ct->index, sym))) {
              error_sym_2(P, sym, "table member already defined here", old);
              continue;
          }
          if (sym->kind != fb_is_member) {
              error_sym(P, sym, "internal error: member type expected");
              return -1;
          }
          member = (fb_member_t *)sym;
          if (member->type.type == vt_invalid) {
              continue;
          }
          if (member->type.type == vt_scalar_type || member->type.type == vt_vector_type) {
              member->type.st = map_scalar_token_type(member->type.t);
          }
          allow_flags = 
                  fb_f_id | fb_f_nested_flatbuffer | fb_f_deprecated | fb_f_key |
                  fb_f_required | fb_f_hash | fb_f_base64 | fb_f_base64url | fb_f_sorted;
  
          if (P->opts.allow_primary_key) {
              allow_flags |= fb_f_primary_key;
          }
          member->metadata_flags = process_metadata(P, member->metadata, allow_flags, knowns);
          if ((m = knowns[fb_attr_nested_flatbuffer])) {
              define_nested_table(P, ct->scope, member, m);
          }
          if ((member->metadata_flags & fb_f_required) && member->type.type == vt_scalar_type) {
              error_sym(P, sym, "'required' attribute is redundant on scalar table field");
          }
          /* Note: we allow base64 and base64url with nested attribute. */
          if ((member->metadata_flags & fb_f_base64) &&
                  (member->type.type != vt_vector_type || member->type.st != fb_ubyte)) {
              error_sym(P, sym, "'base64' attribute is only allowed on vectors of type ubyte");
          }
          if ((member->metadata_flags & fb_f_base64url) &&
                  (member->type.type != vt_vector_type || member->type.st != fb_ubyte)) {
              error_sym(P, sym, "'base64url' attribute is only allowed on vectors of type ubyte");
          }
          if ((member->metadata_flags & (fb_f_base64 | fb_f_base64url)) ==
                  (fb_f_base64 | fb_f_base64url)) {
              error_sym(P, sym, "'base64' and 'base64url' attributes cannot both be set");
          }
          m = knowns[fb_attr_id];
          if (m && count == 0) {
              need_id = 1;
              field_marker = P->tmp_field_marker;
              memset(field_marker, 0, (size_t)P->opts.vt_max_count);
          }
          if (!id_failed) {
              if (count >= P->opts.vt_max_count) {
                  error_sym(P, sym, "too many fields for vtable size");
                  id_failed = 1;
              } else if (!need_id) {
                  member->id = (unsigned short)count;
              }
              ++count;
          } 
          switch (member->type.type) {
          case vt_scalar_type:
              key_ok = 1;
              if (member->value.type == vt_name_ref) {
                  if (lookup_enum_name(P, ct->scope, 0, member->value.ref, &member->value)) {
                      error_ref_sym(P, member->value.ref, "unknown name used as initializer for scalar field", sym);
                      member->type.type = vt_invalid;
                      continue;
                  }
              }
              if (!member->value.type) {
                  /*
                   * Simplifying by ensuring we always have a default
                   * value where an initializer is possible (also goes for enum
                   * above).
                   */
                  member->value.type = vt_uint;
                  member->value.u = 0;
              }
              if (fb_coerce_scalar_type(P, sym, member->type.st, &member->value)) {
                  member->type.type = vt_invalid;
                  continue;
              }
              member->size = sizeof_scalar_type(member->type.st);
              member->align = (uint16_t)member->size;
              break;
          case vt_vector_type:
              is_vector = 1;
              member->size = sizeof_scalar_type(member->type.st);
              member->align =(uint16_t) member->size;
              if (member->value.type) {
                  error_sym(P, sym, "scalar vectors cannot have an initializer");
                  member->type.type = vt_invalid;
                  continue;
              }
              break;
          case vt_fixed_array_type_ref:
          case vt_fixed_array_string_type:
          case vt_fixed_array_type:
              error_sym(P, sym, "fixed size arrays can only be used with structs");
              member->type.type = vt_invalid;
              return -1;
          case vt_string_type:
              /* `size` or `align` not defined - these are implicit uoffset types. */
              key_ok = P->opts.allow_string_key;
              if (member->value.type) {
                  error_sym(P, sym, "strings cannot have an initializer");
                  member->type.type = vt_invalid;
                  continue;
              }
              break;
          case vt_vector_string_type:
              is_vector = 1;
              /* `size` or `align` not defined - these are implicit uoffset types. */
              if (member->value.type) {
                  error_sym(P, sym, "string vectors cannot have an initializer");
                  member->type.type = vt_invalid;
                  continue;
              }
              break;
          case vt_type_ref:
              type_sym = lookup_type_reference(P, ct->scope, member->type.ref);
              if (!type_sym) {
                  error_ref_sym(P, member->type.ref, "unknown type reference used with table field", sym);
                  member->type.type = vt_invalid;
                  /* We cannot count id's without knowing if it is a union. */
                  id_failed = 1;
                  continue;
              }
              switch (type_sym->kind) {
              case fb_is_enum:
                  if (member->metadata_flags & fb_f_required) {
                      error_sym(P, sym, "'required' attribute is redundant on enum table field");
                  }
                  key_ok = P->opts.allow_enum_key;
                  break;
              case fb_is_table:
              case fb_is_struct:
              case fb_is_union:
                  break;
              case fb_is_rpc_service:
                  error_sym_2(P, sym, "rpc service is not a valid table field type", type_sym);
                  member->type.type = vt_invalid;
                  continue;
              default:
                  error_sym_2(P, sym, "internal error: unexpected field type", type_sym);
                  member->type.type = vt_invalid;
                  continue;
              }
              member->type.type = vt_compound_type_ref;
              member->type.ct = (fb_compound_type_t*)type_sym;
              /*
               * Note: this information transfer won't always work because
               * structs do not know their full size at this point so
               * codegen must use the member->type.ct values.
               */
              member->size = member->type.ct->size;
              member->align = member->type.ct->align;
  
              if (type_sym->kind == fb_is_union && !id_failed) {
                  /* Hidden union type field. */
                  if (!need_id) {
                      member->id = (unsigned short)count;
                  }
                  ++count;
              }
              if (member->value.type) {
                  if (type_sym->kind != fb_is_enum) {
                      error_sym(P, sym, "non-scalar field cannot have an initializer");
                      member->type.type = vt_invalid;
                      continue;
                  }
                  if (member->value.type == vt_name_ref) {
                      if (lookup_enum_name(P, ct->scope, member->type.ct, member->value.ref, &member->value)) {
                          error_ref_sym(P, member->value.ref, "unknown name used as initializer for enum field", sym);
                          member->type.type = vt_invalid;
                          continue;
                      }
                  } else {
                      if (fb_coerce_scalar_type(P, sym, ((fb_compound_type_t *)type_sym)->type.st, &member->value)) {
                          member->type.type = vt_invalid;
                          continue;
                      }
                      if (P->opts.strict_enum_init) {
                          if (!is_in_value_set(&member->type.ct->value_set, &member->value)) {
                              error_sym(P, sym, "initializer does not match a defined enum value");
                              member->type.type = vt_invalid;
                              continue;
                          }
                      }
                  }
              } else {
                  /* Enum is the only type that cannot always default to 0. */
                  if (type_sym->kind == fb_is_enum) {
                      member->value.type = vt_uint;
                      member->value.u = 0;
                      if (fb_coerce_scalar_type(P, type_sym, ((fb_compound_type_t *)type_sym)->type.st, &member->value)) {
                          member->type.type = vt_invalid;
                          continue;
                      }
                      if (P->opts.strict_enum_init) {
                          if (!is_in_value_set(&member->type.ct->value_set, &member->value)) {
                              error_sym_2(P, sym,
                                      "enum type requires an explicit initializer because it has no 0 value", type_sym);
                              member->type.type = vt_invalid;
                              continue;
                          }
                      }
                  }
              }
              break;
          case vt_vector_type_ref:
              type_sym = lookup_type_reference(P, ct->scope, member->type.ref);
              if (!type_sym) {
                  error_ref_sym(P, member->type.ref, "unknown vector type reference used with table field", sym);
                  member->type.type = vt_invalid;
                  continue;
              }
              switch (type_sym->kind) {
              case fb_is_enum:
              case fb_is_table:
              case fb_is_struct:
              case fb_is_union:
                  break;
              default:
                  /* Vectors of strings are handled separately but this is irrelevant to the user. */
                  error_sym_tok(P, sym, "vectors can only hold scalars, structs, enums, strings, tables, and unions", member->type.t);
                  member->type.type = vt_invalid;
                  continue;
              }
              is_vector = 1;
              is_union_vector = type_sym->kind == fb_is_union;
              if (member->value.type) {
                  error_sym(P, sym, "non-scalar field cannot have an initializer");
                  member->type.type = vt_invalid;
                  continue;
              }
              /* Size of the vector element, not of the vector itself. */
              member->type.type = vt_vector_compound_type_ref;
              member->type.ct = (fb_compound_type_t*)type_sym;
              member->size = member->type.ct->size;
              member->align = member->type.ct->align;
              if (type_sym->kind == fb_is_union && !id_failed) {
                  /* Hidden union type field. */
                  if (!need_id) {
                      member->id = (unsigned short)count;
                  }
                  ++count;
              }
              break;
          default:
              error_sym(P, sym, "unexpected table field type encountered");
              member->type.type = vt_invalid;
              continue;
          }
          if (!id_failed) {
              if (m && !need_id && !id_failed) {
                  error_tok(P, m->ident, "unexpected id attribute, must be used on all fields, or none");
                  id_failed = 1;
              } else if (!m && need_id && !id_failed) {
                  error_sym(P, sym, "id attribute missing, must be used on all fields, or none");
                  id_failed = 1;
              } else if (m) {
                  if (m->value.type == vt_uint) {
                      if (m->value.u >= P->opts.vt_max_count) {
                          error_sym(P, sym, "id too large to fit in vtable");
                          id_failed = 1;
                      } else {
                          member->id = (unsigned short)m->value.u;
                          if (member->id > max_id) {
                              max_id = member->id;
                          }
                      }
                  } else if (m->value.type == vt_int) {
                      error_tok(P, m->ident, "id attribute cannot be negative");
                      id_failed = 1;
                  } else {
                      error_tok(P, m->ident, "unexpecte id attribute type");
                      id_failed = 1;
                  }
              }
          }
          if (need_id && !id_failed) {
              if (field_marker[member->id] == type_field) {
                  error_tok(P, m->ident, "id attribute value conflicts with a hidden type field");
                  id_failed = normal_field;
              } else if (field_marker[member->id]) {
                  error_tok(P, m->ident, "id attribute value conflicts with another field");
              } else {
                  field_marker[member->id] = normal_field;
              }
              if (!id_failed && type_sym && type_sym->kind == fb_is_union) {
                  if (member->id <= 1) {
                      error_tok(P, m->ident, is_union_vector ?
                              "id attribute value should be larger to accomdate hidden union vector type field" :
                              "id attribute value should be larger to accomdate hidden union type field");
                      id_failed = 1;
                  } else if (field_marker[member->id - 1] == type_field) {
                      error_tok(P, m->ident, is_union_vector ?
                              "hidden union vector type field below attribute id value conflicts with another hidden type field" :
                              "hidden union type field below attribute id value conflicts with another hidden type field");
                      id_failed = 1;
                  } else if (field_marker[member->id - 1]) {
                      error_tok(P, m->ident, is_union_vector ?
                              "hidden union vector type field below attribute id value conflicts with another field" :
                              "hidden union type field below attribute id value conflicts with another field");
                      id_failed = 1;
                  } else {
                      field_marker[member->id - 1] = type_field;
                  }
              }
          }
          if (member->metadata_flags & fb_f_deprecated) {
              if (member->metadata_flags & fb_f_key) {
                  error_sym(P, sym, "key attribute not allowed for deprecated field");
                  return -1;
              } else if (member->metadata_flags & fb_f_primary_key) {
                  error_sym(P, sym, "primary_key attribute not allowed for deprecated field");
                  return -1;
              }
          }
          if (member->metadata_flags & fb_f_key) {
              ++key_count;
              if (!key_ok) {
                  error_sym(P, sym, "key attribute not allowed for this kind of field");
                  member->type.type = vt_invalid;
              } else if (member->metadata_flags & fb_f_primary_key) {
                  error_sym(P, sym, "primary_key attribute conflicts with key attribute");
                  member->type.type = vt_invalid;
              } else if (!ct->primary_key || 
                      (primary_count == 0 && ct->primary_key->id > member->id)) {
                  /*
                   * Set key field with lowest id as default primary key
                   * unless a key field also has a primary attribute.
                   */
                  ct->primary_key = member;
              }
          } else if (member->metadata_flags & fb_f_primary_key) {
              if (member->metadata_flags & fb_f_primary_key) {
                  if (primary_count++) {
                      error_sym(P, sym, "at most one field can have a primary_key attribute");
                      member->type.type = vt_invalid;
                      continue;
                  } else {
                      ct->primary_key = member;
                  }
              }
              key_count++;
              /* Allow backends to treat primary key as an ordinary key. */
              member->metadata_flags |= fb_f_key;
          }
          if (member->metadata_flags & fb_f_sorted) {
              if (is_union_vector) {
                  error_sym(P, sym, "sorted attribute not allowed for union vectors");
                  member->type.type = vt_invalid;
                  return -1;
              } else if (!is_vector) {
                  error_sym(P, sym, "sorted attribute only allowed for vectors");
                  member->type.type = vt_invalid;
                  return -1;
              }
              /*
               * A subsequent call to validate_table_attr will verify that a
               * sorted vector of tables or structs have a defined field
               * key. This cannot be done before all types have been
               * processed.
               */
          }
      }
      if (!id_failed) {
          ct->count = count;
      }
      if (!id_failed && need_id) {
          if (count && max_id >= count) {
              for (i = 0; i < max_id; ++i) {
                  if (field_marker[i] == 0) {
                      if (!max_id_errors--) {
                          error_sym(P, &ct->symbol, "... more id's missing");
                          break;
                      }
                      sprintf(msg_buf,  "id range not consequtive from 0, missing id: %"PRIu64"", i);
                      error_sym(P, &ct->symbol, msg_buf);
                  }
              }
              id_failed = 1;
          }
      }
      /* Order in which data is ordered in binary buffer. */
      if (ct->metadata_flags & fb_f_original_order) {
          ct->ordered_members = original_order_members(P, (fb_member_t *)ct->members);
      } else {
          /* Size efficient ordering. */ 
          ct->ordered_members = align_order_members(P, (fb_member_t *)ct->members);
      }
      if (!id_failed && need_id && count > 0) {
          field_index = P->tmp_field_index;
          memset(field_index, 0, sizeof(field_index[0]) * (size_t)P->opts.vt_max_count);
          /*
           * Reorder by id so table constructor arguments in code
           * generators always use same ordering across versions.
           */
          for (sym = ct->members; sym; sym = sym->link) {
              member = (fb_member_t *)sym;
              field_index[member->id] = sym;
          }
          j = 0;
          if (field_index[0] == 0) {
              j = 1; /* Adjust for union type. */
          }
          ct->members = field_index[j];
          for (i = j + 1; i <= max_id; ++i) {
              if (field_index[i] == 0) ++i; /* Adjust for union type. */
              field_index[j]->link = field_index[i];
              j = i;
          }
          field_index[max_id]->link = 0;
      }
      if (key_count) {
          ct->symbol.flags |= fb_indexed;
      }
      /* Set primary key flag for backends even if chosen by default. */
      if (ct->primary_key) {
          ct->primary_key->metadata_flags |= fb_f_primary_key;
      }
      if (key_count > 1 && !P->opts.allow_multiple_key_fields) {
          error_sym(P, &ct->symbol, "table has multiple key fields, but at most one is permitted");
          ret = -1;
      }
      if (id_failed) {
          ret = -1;
      }
      return ret;
  }
  
  /*
   * Post processing of process_table because some information is only
   * available when all types have been processed.
   */
  static int validate_table_attr(fb_parser_t *P, fb_compound_type_t *ct)
  {
      fb_symbol_t *sym;
      fb_member_t *member;
  
      for (sym = ct->members; sym; sym = sym->link) {
          member = (fb_member_t *)sym;
          if (member->metadata_flags & fb_f_deprecated) {
              continue;
          }
  
          if (member->type.type == vt_vector_compound_type_ref &&
                  member->metadata_flags & fb_f_sorted) {
              switch (member->type.ct->symbol.kind) {
              case fb_is_table:
                  if (!member->type.ct->primary_key) {
                      error_sym(P, sym, "sorted table vector only valid when table has a key field");
                      return -1;
                  }
                  break;
              case fb_is_struct:
                  if (!member->type.ct->primary_key) {
                      error_sym(P, sym, "sorted struct vector only valid when struct has a key field");
                      return -1;
                  }
                  break;
              /* Other cases already handled in process_table. */
              default:
                  continue;
              }
          }
      }
      return 0;
  }
  
  /*
   * The parser already makes sure we have exactly one request type,
   * one response type, and no initializer.
   *
   * We are a bit heavy on flagging attributes because their behavior
   * isn't really specified at this point.
   */
  static int process_rpc_service(fb_parser_t *P, fb_compound_type_t *ct)
  {
      fb_symbol_t *sym, *old, *type_sym;
      fb_member_t *member;
  #if FLATCC_ALLOW_RPC_SERVICE_ATTRIBUTES || FLATCC_ALLOW_RPC_METHOD_ATTRIBUTES
      fb_metadata_t *knowns[KNOWN_ATTR_COUNT];
  #endif
  
      assert(ct->symbol.kind == fb_is_rpc_service);
      assert(!ct->type.type);
  
      /*
       * Deprecated is defined for fields - but it is unclear if this also
       * covers rpc services. Anyway, we accept it since it may be useful,
       * and does not harm.
       */
  #if FLATCC_ALLOW_RPC_SERVICE_ATTRIBUTES
      /* But we have no known attributes to support. */
      ct->metadata_flags = process_metadata(P, ct->metadata, 0, knowns);
  #else
      if (ct->metadata) {
          error_sym(P, &ct->symbol, "rpc services cannot have attributes");
          /* Non-fatal. */
      }
  #endif
  
      /*
       * `original_order` now lives as a flag, we need not consider it
       * further until code generation.
       */
      for (sym = ct->members; sym; sym = sym->link) {
          type_sym = 0;
          if ((old = define_fb_symbol(&ct->index, sym))) {
              error_sym_2(P, sym, "rpc method already defined here", old);
              continue;
          }
          if (sym->kind != fb_is_member) {
              error_sym(P, sym, "internal error: member type expected");
              return -1;
          }
          member = (fb_member_t *)sym;
          if (member->value.type) {
              error_sym(P, sym, "internal error: initializer should have been rejected by parser");
          }
          if (member->type.type == vt_invalid) {
              continue;
          }
          if (member->type.type != vt_type_ref) {
              error_sym(P, sym, "internal error: request type expected to be a type reference");
          }
          type_sym = lookup_type_reference(P, ct->scope, member->req_type.ref);
          if (!type_sym) {
              error_ref_sym(P, member->req_type.ref, "unknown type reference used with rpc request type", sym);
              member->type.type = vt_invalid;
              continue;
          } else {
              if (type_sym->kind != fb_is_table) {
                  error_sym_2(P, sym, "rpc request type must reference a table, defined here", type_sym);
                  member->type.type = vt_invalid;
                  continue;
              }
              member->req_type.type = vt_compound_type_ref;
              member->req_type.ct = (fb_compound_type_t*)type_sym;
          }
          type_sym = lookup_type_reference(P, ct->scope, member->type.ref);
          if (!type_sym) {
              error_ref_sym(P, member->type.ref, "unknown type reference used with rpc response type", sym);
              member->type.type = vt_invalid;
              continue;
          } else {
              if (type_sym->kind != fb_is_table) {
                  error_sym_2(P, sym, "rpc response type must reference a table, defined here", type_sym);
                  member->type.type = vt_invalid;
                  continue;
              }
              member->type.type = vt_compound_type_ref;
              member->type.ct = (fb_compound_type_t*)type_sym;
              /* Symbols have no size. */
              member->size = 0;
          }
  #if FLATCC_ALLOW_RPC_METHOD_ATTRIBUTES
  #if FLATCC_ALLOW_DEPRECATED_RPC_METHOD
          member->metadata_flags = process_metadata(P, member->metadata, fb_f_deprecated, knowns);
  #else
          member->metadata_flags = process_metadata(P, member->metadata, 0, knowns);
  #endif
  #else
          if (member->metadata) {
              error_sym(P, sym, "rpc methods cannot have attributes");
              /* Non-fatal. */
              continue;
          }
  #endif
      }
      return 0;
  }
  
  static int process_enum(fb_parser_t *P, fb_compound_type_t *ct)
  {
      fb_symbol_t *sym, *old, *type_sym;
      fb_member_t *member;
      fb_metadata_t *knowns[KNOWN_ATTR_COUNT];
      fb_value_t index;
      fb_value_t old_index;
      int first = 1;
      int bit_flags = 0;
      int is_union = ct->symbol.kind == fb_is_union;
  
      if (!is_union) {
          assert(ct->symbol.kind == fb_is_enum);
          if (!ct->type.type) {
              ct->type.type = vt_invalid;
              error_sym(P, &ct->symbol, "enum must have a type");
              return -1;
          }
          if (ct->type.type == vt_missing) {
              /*
               * Enums normally require a type, but the parser may have an
               * option to allow missing type, and then we provide a
               * sensible default.
               */
              ct->type.st = fb_int;
              ct->type.type = vt_scalar_type;
          } else if (ct->type.type == vt_scalar_type) {
              ct->type.st = map_scalar_token_type(ct->type.t);
          } else {
              /* Spec does not mention boolean type in enum, but we allow it. */
              error_sym(P, &ct->symbol, "enum type must be a scalar integral type or bool");
              return -1;
          }
          ct->size = sizeof_scalar_type(ct->type.st);
          ct->align = (uint16_t)ct->size;
      } else {
          if (ct->type.type) {
              error_sym(P, &ct->symbol, "unions cannot have a type, they are always enumerated as ubyte");
              return -1;
          }
          /*
           * We preprocess unions as enums to get the value assignments.
           * The type field is not documented, but generated output from
           * flatc suggests ubyte. We use a an injected token to represent
           * the ubyte type so we do not have to hardcode elsewhere.
           */
          ct->type.type = vt_scalar_type;
          ct->type.st = fb_ubyte;
          /*
           * The union field use the type field and not the offset field
           * to define its size because type.type is scalar.
           */
          ct->size = sizeof_scalar_type(fb_ubyte);
          ct->align = (uint16_t)ct->size;
      }
  
      ct->metadata_flags = process_metadata(P, ct->metadata, fb_f_bit_flags, knowns);
      if (ct->metadata_flags & fb_f_bit_flags) {
          bit_flags = 1;
          index.type = vt_uint;
          index.u = 0;
      }
  
      if (ct->type.st == fb_bool) {
          index.b = 0;
          index.type = vt_bool;
      } else {
          index.i = 0;
          index.type = vt_int;
          if (fb_coerce_scalar_type(P, (fb_symbol_t *)ct, ct->type.st, &index)) {
              error(P, "internal error: unexpected conversion failure on enum 0 index");
              return -1;
          }
      }
      old_index = index;
  
      for (sym = ct->members; sym; sym = sym->link, first = 0) {
          member = (fb_member_t *)sym;
          if ((old = define_fb_symbol(&ct->index, sym))) {
              if (old->ident == &P->t_none) {
                  /*
                   * Parser injects `NONE` as the first union member and
                   * it therefore gets index 0. Additional use of NONE
                   * will fail.
                   */
                  error_sym(P, sym, "'NONE' is a predefined value");
                  member->type.type = vt_invalid;
                  continue;
              }
              error_sym_2(P, sym, "value already defined here", old);
              member->type.type = vt_invalid;
              continue;
          }
          if (sym->kind != fb_is_member) {
              error_sym(P, sym, "internal error: enum value type expected");
              return -1;
          }
          /* Enum / union values cannot have metadata. */
          assert(member->metadata == 0);
          if (is_union) {
              if (member->symbol.ident == &P->t_none) {
                  /* Handle implicit NONE specially. */
                  member->type.type = vt_missing;
              } else if (member->type.type == vt_string_type) {
                  member->size = 0;
              } else if (member->type.type != vt_type_ref) {
                  if (member->type.type != vt_invalid) {
                      error_sym(P, sym, "union member type must be string or a reference to a table or a struct");
                      member->type.type = vt_invalid;
                  }
                  continue;
              } else {
                  type_sym = lookup_type_reference(P, ct->scope, member->type.ref);
                  if (!type_sym) {
                      error_ref_sym(P, member->type.ref, "unknown type reference used with union member", sym);
                      member->type.type = vt_invalid;
                      continue;
                  } else {
                      if (type_sym->kind != fb_is_table && type_sym->kind != fb_is_struct) {
                          error_sym_2(P, sym, "union member type reference must be a table or a struct, defined here", type_sym);
                          member->type.type = vt_invalid;
                          continue;
                      }
                      member->type.type = vt_compound_type_ref;
                      member->type.ct = (fb_compound_type_t*)type_sym;
                      /* Symbols have no size. */
                      member->size = 0;
                  }
              }
          }
          if (!member->value.type && !first) {
              if (index.type == vt_uint) {
                  if (ct->type.st == fb_long && index.u == UINT64_MAX) {
                      /* Not captured by range check. */
                      error_sym(P, sym, "64-bit unsigned int overflow");
                  }
                  index.u = index.u + 1;
              } else if (index.type == vt_int && !first) {
                  if (ct->type.st == fb_long && index.i == INT64_MAX) {
                      /* Not captured by range check. */
                      error_sym(P, sym, "64-bit signed int overflow");
                  }
                  index.i = index.i + 1;
              } else if (index.type == vt_bool && !first) {
                  if (index.b == 1) {
                      error_sym(P, sym, "boolean overflow: cannot enumerate past true");
                  }
                  index.b = 1;
              }
          }
          if (bit_flags) {
              if (member->value.type) {
                  if (member->value.type != vt_uint) {
                      error_sym(P, sym, "enum value must be unsigned int when used with 'bit_flags'");
                      return -1;
                  } else {
                      index = member->value;
                  }
              }
              if (index.u >= sizeof_scalar_type(ct->type.st) * 8) {
                  error_sym(P, sym, "enum value out of range when used with 'bit_flags'");
                  return -1;
              }
              member->value.u = 1LL << index.u;
              member->value.type = vt_uint;
              if (fb_coerce_scalar_type(P, sym, ct->type.st, &member->value)) {
                   /* E.g. enumval = 15 causes signed overflow with short. */
                  error_sym(P, sym, "enum value out of range when used with 'bit_flags'");
                  return -1;
              }
          } else {
              if (member->value.type) {
                  index = member->value;
                  /*
                   * Captures errors in user assigned values. Also captures
                   * overflow on auto-increment on all types except maximum
                   * representation size, i.e. long or ulong which we handled
                   * above.
                   */
                  if (fb_coerce_scalar_type(P, sym, ct->type.st, &index)) {
                      return -1;
                  }
              }
              member->value = index;
          }
          if (!first && P->opts.ascending_enum) {
              /* Without ascending enum we also allow duplicate values (but not names). */
              if ((index.type == vt_uint && index.u <= old_index.u) ||
                      (index.type == vt_int && index.i <= old_index.i)) {
                  if (is_union && old_index.u == 0) {
                      /*
                       * The user explicitly assigned zero, or less, to the first
                       * element (here second element after parser injecting
                       * the NONE element).
                       */
                      error_sym(P, sym, "union values must be positive, 0 is reserved for implicit 'NONE'");
                      member->value.type = vt_invalid;
                      return -1;
                  }
                  error_sym(P, sym, "enum values must be in ascending order");
                  member->value.type = vt_invalid;
                  return -1;
              }
              if (index.type == vt_bool && index.b <= old_index.b) {
                  error_sym(P, sym, "enum of type bool can only enumerate from false (0) to true (1)");
                  member->value.type = vt_invalid;
                  return -1;
              }
          }
          old_index = index;
          if (add_to_value_set(&ct->value_set, &member->value)) {
              if (is_union) {
                  error_sym(P, sym, "union members require unique positive values (0 being reserved for 'NONE'");
                  member->value.type = vt_invalid;
                  return -1;
              } else {
                  /*
                   * With ascending enums this won't happen, but
                   * otherwise flag secondary values so we can remove them
                   * from inverse name mappings in code gen.
                   */
                  member->symbol.flags |= fb_duplicate;
              }
          }
          if (member->metadata) {
              error_sym(P, sym, "enum values cannot have attributes");
              /* Non-fatal. */
              continue;
          }
      }
      return 0;
  }
  
  static inline int process_union(fb_parser_t *P, fb_compound_type_t *ct)
  {
      return process_enum(P, ct);
  }
  
  int fb_build_schema(fb_parser_t *P)
  {
      fb_schema_t *S = &P->schema;
      fb_symbol_t *sym, *old_sym;
      fb_name_t *old_name;
      fb_compound_type_t *ct;
      fb_attribute_t *a;
  
      /* Make sure self is visible at this point in time. */
      assert(ptr_set_exists(&P->schema.visible_schema, &P->schema));
      for (sym = S->symbols; sym; sym = sym->link) {
          switch (sym->kind) {
          case fb_is_table:
          case fb_is_enum:
          case fb_is_union:
          case fb_is_struct:
          case fb_is_rpc_service:
              ct = (fb_compound_type_t *)sym;
              set_type_hash(ct);
              ct->schema = &P->schema;
              if (ct->scope && (old_sym = define_fb_symbol(&ct->scope->symbol_index, sym))) {
                  error_sym_2(P, sym, "symbol already defined here", old_sym);
              }
          }
      }
  
      /*
       * Known attributes will be pre-defined if not provided by the
       * user. After that point, all attribute references must be
       * defined.
       */
      for (a = (fb_attribute_t *)S->attributes; a; a = (fb_attribute_t *)a->name.link) {
          if ((old_name = define_fb_name(&S->root_schema->attribute_index, &a->name))) {
              /*
               * Allow attributes to be defined multiple times, including
               * known attributes.
               */
  #if 0
              error_name(P, &a->name, "attribute already defined");
  #endif
          }
      }
      install_known_attributes(P);
  
      if (!P->opts.hide_later_enum) {
          for (sym = S->symbols; sym; sym = sym->link) {
              switch (sym->kind) {
              case fb_is_enum:
                  ct = (fb_compound_type_t *)sym;
                  if (process_enum(P, ct)) {
                      ct->type.type = vt_invalid;
                      continue;
                  }
                  break;
          default:
              continue;
              }
          }
      }
  
      /*
       * Resolve type references both earlier and later than point of
       * reference. This also supports recursion for tables and unions.
       */
      for (sym = S->symbols; sym; sym = sym->link) {
          switch (sym->kind) {
          case fb_is_struct:
              ct = (fb_compound_type_t *)sym;
              if (process_struct(P, ct)) {
                  ct->type.type = vt_invalid;
                  continue;
              }
              break;
          case fb_is_table:
              /* Handle after structs and enums. */
              continue;
          case fb_is_rpc_service:
              /*
               * Also handle rpc_service later like tables, just in case
               * we allow non-table types in request/response type.
               */
              continue;
          case fb_is_enum:
              if (P->opts.hide_later_enum) {
                  ct = (fb_compound_type_t *)sym;
                  if (process_enum(P, ct)) {
                      ct->type.type = vt_invalid;
                      continue;
                  }
              }
              break;
          case fb_is_union:
              ct = (fb_compound_type_t *)sym;
              if (process_union(P, ct)) {
                  ct->type.type = vt_invalid;
                  continue;
              }
              break;
          default:
              error_sym(P, sym, "internal error: unexpected symbol at schema level");
              return -1;
          }
      }
      for (sym = P->schema.symbols; sym; sym = sym->link) {
          switch (sym->kind) {
          case fb_is_struct:
              /*
               * Structs need two stages, first process symbols, then
               * analyze for size, alignment, and circular references.
               */
              ct = (fb_compound_type_t *)sym;
              if (ct->type.type != vt_invalid && analyze_struct(P, ct)) {
                  ct->type.type = vt_invalid;
                  continue;
              }
              break;
          default:
              continue;
          }
      }
      for (sym = P->schema.symbols; sym; sym = sym->link) {
          switch (sym->kind) {
          case fb_is_table:
              ct = (fb_compound_type_t *)sym;
              /* Only now is the full struct size available. */
              if (ct->type.type != vt_invalid && process_table(P, ct)) {
                  ct->type.type = vt_invalid;
                  continue;
              }
              break;
          case fb_is_rpc_service:
              ct = (fb_compound_type_t *)sym;
              /* Only now is the full struct size available. */
              if (ct->type.type != vt_invalid && process_rpc_service(P, ct)) {
                  ct->type.type = vt_invalid;
                  continue;
              }
          }
      }
      for (sym = P->schema.symbols; sym; sym = sym->link) {
          switch (sym->kind) {
          case fb_is_table:
              ct = (fb_compound_type_t *)sym;
              /*
               * Some table attributes depend on attributes on members and
               * therefore can only be validated after procesing.
               */
              if (ct->type.type != vt_invalid && validate_table_attr(P, ct)) {
                  ct->type.type = vt_invalid;
                  continue;
              }
          }
      }
      revert_order(&P->schema.ordered_structs);
      if (!S->root_type.name) {
          if (P->opts.require_root_type) {
              error(P, "root type not declared");
          }
      } else {
          sym = S->root_type.type = lookup_type_reference(P,
                  S->root_type.scope, S->root_type.name);
          if (!sym) {
              error_ref(P, S->root_type.name, "root type not found");
          } else if (P->opts.allow_struct_root) {
              if (sym->kind != fb_is_struct && sym->kind != fb_is_table) {
                  error_ref(P, S->root_type.name, "root type must be a struct or a table");
              } else {
                  S->root_type.type = sym;
              }
          } else {
              if (sym->kind != fb_is_table) {
                  error_ref(P, S->root_type.name, "root type must be a table");
              } else {
                  S->root_type.type = sym;
              }
          }
          S->root_type.name = 0;
      }
      P->has_schema = !P->failed;
      return P->failed;
  }
  