text: #include <stdio.h>
  #include <stdlib.h>
  #include "flatcc/reflection/reflection_builder.h"
  #include "symbols.h"
  #include "parser.h"
  #include "codegen.h"
  #include "fileio.h"
  /* Needed to store length prefix. */
  #include "catalog.h"
  
  #define BaseType(x) FLATBUFFERS_WRAP_NAMESPACE(reflection_BaseType, x)
  
  static flatbuffers_bool_t is_optional_type(fb_value_t type, int optional, int required)
  {
      if (required) return 0;
      if (optional) return 1;
      if (type.type == vt_scalar_type) return 0;
      if (type.type == vt_compound_type_ref && type.ct->symbol.kind == fb_is_enum) return 0;
      return 1;
  }
  
  static reflection_Type_ref_t export_type(flatcc_builder_t *B, fb_value_t type)
  {
      fb_scalar_type_t st = fb_missing_type;
      int32_t index = -1;
      reflection_BaseType_enum_t base_type = BaseType(None);
      reflection_BaseType_enum_t element = BaseType(None);
      reflection_BaseType_enum_t primitive = BaseType(None);
      uint16_t fixed_length = 0;
  
      switch (type.type) {
      case vt_scalar_type:
          st = type.st;
          break;
      case vt_vector_type:
          st = type.st;
          base_type = BaseType(Vector);
          break;
      case vt_vector_string_type:
          element = BaseType(String);
          base_type = BaseType(Vector);
          break;
      case vt_vector_compound_type_ref:
          index = (int32_t)type.ct->export_index;
          switch (type.ct->symbol.kind) {
          case fb_is_enum:
              st = type.ct->type.st;
              base_type = BaseType(Vector);
              break;
          case fb_is_struct:
          case fb_is_table:
              base_type = BaseType(Vector);
              element = BaseType(Obj);
              break;
          case fb_is_union:
              base_type = BaseType(Vector);
              element = BaseType(Union);
              break;
          default:
              break;
          }
          break;
      case vt_string_type:
          base_type = BaseType(String);
          break;
      case vt_compound_type_ref:
          index = (int32_t)type.ct->export_index;
          switch (type.ct->symbol.kind) {
          case fb_is_enum:
              st = type.ct->type.st;
              break;
          case fb_is_struct:
          case fb_is_table:
              base_type = BaseType(Obj);
              break;
          case fb_is_union:
              base_type = BaseType(Union);
              break;
          default:
              index = -1;
              break;
          }
          break;
      case vt_fixed_array_type:
          st = type.st;
          base_type = BaseType(Array);
          fixed_length = (uint16_t)type.len;
          break;
      case vt_fixed_array_string_type:
          break;
          element = BaseType(Byte);
          base_type = BaseType(Array);
          fixed_length = (uint16_t)type.len;
          break;
      case vt_fixed_array_compound_type_ref:
          index = (int32_t)type.ct->export_index;
          switch (type.ct->symbol.kind) {
          case fb_is_enum:
              st = type.ct->type.st;
              break;
          case fb_is_struct:
          case fb_is_table:
              element = BaseType(Obj);
              break;
          case fb_is_union:
              element = BaseType(Union);
              break;
          default:
              break;
          }
          base_type = BaseType(Array);
          fixed_length = (uint16_t)type.len;
          break;
      default:
          break;
      }
      /* If st is set, resolve scalar type and set it to base_type or element. */
      switch (st) {
      case fb_missing_type: break;
      case fb_ulong: primitive = BaseType(ULong); break;
      case fb_uint: primitive = BaseType(UInt); break;
      case fb_ushort: primitive = BaseType(UShort); break;
      case fb_ubyte: primitive = BaseType(UByte); break;
      case fb_bool: primitive = BaseType(Bool); break;
      case fb_long: primitive = BaseType(Long); break;
      case fb_int: primitive = BaseType(Int); break;
      case fb_short: primitive = BaseType(Short); break;
      case fb_byte: primitive = BaseType(Byte); break;
      case fb_double: primitive = BaseType(Double); break;
      case fb_float: primitive = BaseType(Float); break;
      /* TODO: Googles flatc tool does not have char arrays so we use Byte as element type */
      case fb_char: primitive = BaseType(Byte); break;
      default: break;
      }
  
      if (base_type == BaseType(None)) {
          base_type = primitive;
      } else if (base_type == BaseType(Vector) || base_type == BaseType(Array)) {
          if (element == BaseType(None)) {
              element = primitive;
          }
      }
      return reflection_Type_create(B, base_type, element, index, fixed_length);
  }
  
  static void export_attributes(flatcc_builder_t *B, fb_metadata_t *m)
  {
      for (; m; m = m->link) {
          reflection_KeyValue_vec_push_start(B);
          reflection_KeyValue_key_create_strn(B, m->ident->text, (size_t)m->ident->len);
          if (m->value.type == vt_string) {
              reflection_KeyValue_value_create_strn(B, m->value.s.s, (size_t)m->value.s.len);
          }
          reflection_KeyValue_vec_push_end(B);
      }
  }
  
  static void export_fields(flatcc_builder_t *B, fb_compound_type_t *ct)
  {
      fb_symbol_t *sym;
      fb_member_t *member;
      flatbuffers_bool_t has_key, deprecated, required, optional, key_processed = 0;
      int64_t default_integer;
      double default_real;
  
      for (sym = ct->members; sym; sym = sym->link) {
          member = (fb_member_t *)sym;
          /*
           * Unlike `flatc` we allow multiple keys in the parser, but
           * there is no way to tell which key is default in the
           * reflection schema because the fields are sorted, so we only
           * export the default (first) key.
           */
          has_key = !key_processed && (member->metadata_flags & fb_f_key) != 0;
          required = (member->metadata_flags & fb_f_required) != 0;
          default_integer = 0;
          default_real = 0.0;
          deprecated = (member->metadata_flags & fb_f_deprecated) != 0;
          /*
           * Flag is only set when `= null` is used in the schema, but
           * non-scalar types are optional by default and therfore also
           * true in the binary schema.
           */
          optional = is_optional_type(member->type, !!(member->flags & fb_fm_optional), required);
  
          if ((member->type.type == vt_compound_type_ref || member->type.type == vt_vector_compound_type_ref)
                  && member->type.ct->symbol.kind == fb_is_union) {
              reflection_Field_vec_push_start(B);
              reflection_Field_name_start(B);
              reflection_Field_name_append(B, member->symbol.ident->text, (size_t)member->symbol.ident->len);
              reflection_Field_name_append(B, "_type", 5);
              reflection_Field_name_end(B);
              switch(member->type.type) {
              case vt_compound_type_ref:
                  reflection_Field_type_create(B, BaseType(UType), BaseType(None), -1, 0);
                  break;
              case vt_vector_compound_type_ref:
                  reflection_Field_type_create(B, BaseType(Vector), BaseType(UType), -1, 0);
                  break;
              }
              reflection_Field_offset_add(B, (uint16_t)(member->id - 1 + 2) * sizeof(flatbuffers_voffset_t));
              reflection_Field_id_add(B, (uint16_t)(member->id - 1));
              reflection_Field_deprecated_add(B, deprecated);
              reflection_Field_vec_push_end(B);
          }
          reflection_Field_vec_push_start(B);
          reflection_Field_name_create(B, member->symbol.ident->text, (size_t)member->symbol.ident->len);
          reflection_Field_type_add(B, export_type(B, member->type));
          switch (ct->symbol.kind) {
          case fb_is_table:
              switch (member->value.type) {
              case vt_uint:
                  default_integer = (int64_t)member->value.u;
                  break;
              case vt_int:
                  default_integer = (int64_t)member->value.i;
                  break;
              case vt_bool:
                  default_integer = (int64_t)member->value.b;
                  break;
              case vt_float:
                  default_real = member->value.f;
                  break;
              }
              reflection_Field_default_integer_add(B, default_integer);
              reflection_Field_default_real_add(B, default_real);
              reflection_Field_id_add(B, (uint16_t)member->id);
              reflection_Field_offset_add(B, (uint16_t)(member->id + 2) * sizeof(flatbuffers_voffset_t));
              reflection_Field_key_add(B, has_key);
              reflection_Field_required_add(B, required);
              reflection_Field_optional_add(B, optional);
              break;
          case fb_is_struct:
              reflection_Field_offset_add(B, (uint16_t)member->offset);
              break;
          default: break;
          }
          /* Deprecated struct fields not supported by `flatc` but is here as an option. */
          reflection_Field_deprecated_add(B, deprecated);
          if (member->metadata) {
              reflection_Field_attributes_start(B);
              export_attributes(B, member->metadata);
              reflection_Field_attributes_end(B);
          }
          reflection_Field_vec_push_end(B);
          key_processed |= has_key;
      }
  }
  
  /* `vec` is filled with references to the constructed objects. */
  static void export_objects(flatcc_builder_t *B, object_entry_t *objects, int nobjects,
          reflection_Object_ref_t *object_map)
  {
      int i, is_struct;
      fb_compound_type_t *ct;
  
      for (i = 0; i < nobjects; ++i) {
          ct = objects[i].ct;
          reflection_Object_start(B);
          reflection_Object_name_create_str(B, objects[i].name);
          /*
           * We can post sort-fields because the index is not used, unlike
           * objects and enums.
           */
          reflection_Object_fields_start(B);
          export_fields(B, ct);
          reflection_Object_fields_end(B);
          is_struct = ct->symbol.kind == fb_is_struct;
          if (is_struct) {
              reflection_Object_bytesize_add(B, (int32_t)ct->size);
          }
          reflection_Object_is_struct_add(B, (flatbuffers_bool_t)is_struct);
          reflection_Object_minalign_add(B, ct->align);
          if (ct->metadata) {
              reflection_Object_attributes_start(B);
              export_attributes(B, ct->metadata);
              reflection_Object_attributes_end(B);
          }
          object_map[i] = reflection_Object_end(B);
      }
      reflection_Schema_objects_create(B, object_map, (size_t)nobjects);
  }
  
  static void export_enumval(flatcc_builder_t *B, fb_member_t *member, reflection_Object_ref_t *object_map)
  {
      int is_union = object_map != 0;
  
      reflection_EnumVal_vec_push_start(B);
      reflection_EnumVal_name_create(B, member->symbol.ident->text, (size_t)member->symbol.ident->len);
      if (is_union) {
          if (member->type.type == vt_compound_type_ref) {
              /* object is deprecated in favor of union_type to support mixed union types. */
              reflection_EnumVal_object_add(B, object_map[member->type.ct->export_index]);
          }
          reflection_EnumVal_union_type_add(B, export_type(B, member->type));
      }
      reflection_EnumVal_value_add(B, (int64_t)member->value.u);
      reflection_EnumVal_vec_push_end(B);
  }
  
  static void export_enums(flatcc_builder_t *B, enum_entry_t *enums, int nenums,
          reflection_Object_ref_t *object_map)
  {
      int i, is_union;
      fb_compound_type_t *ct;
      fb_symbol_t *sym;
  
      reflection_Schema_enums_start(B);
      for (i = 0; i < nenums; ++i) {
          ct = enums[i].ct;
          is_union = ct->symbol.kind == fb_is_union;
          reflection_Enum_vec_push_start(B);
          reflection_Enum_name_create_str(B, enums[i].name);
          reflection_Enum_values_start(B);
          for (sym = ct->members; sym; sym = sym->link) {
              export_enumval(B, (fb_member_t *)sym, is_union ? object_map : 0);
          }
          reflection_Enum_values_end(B);
          reflection_Enum_is_union_add(B, (flatbuffers_bool_t)is_union);
          reflection_Enum_underlying_type_add(B, export_type(B, ct->type));
          if (ct->metadata) {
              reflection_Enum_attributes_start(B);
              export_attributes(B, ct->metadata);
              reflection_Enum_attributes_end(B);
          }
          reflection_Enum_vec_push_end(B);
      }
      reflection_Schema_enums_end(B);
  }
  
  static void export_root_type(flatcc_builder_t *B, fb_symbol_t * root_type,
          reflection_Object_ref_t *object_map)
  {
      fb_compound_type_t *ct;
      if (root_type) {
          /*
           * We could also store a struct object here, but since the
           * binrary schema says root_table, not root_type as in the text
           * schema, it would be misleading.
           */
          if (root_type->kind == fb_is_table) {
              ct = (fb_compound_type_t *)root_type;
              reflection_Schema_root_table_add(B, object_map[ct->export_index]);
          }
      }
  }
  
  static int export_schema(flatcc_builder_t *B, fb_options_t *opts, fb_schema_t *S)
  {
      catalog_t catalog;
      reflection_Object_ref_t *object_map = 0;
  
      if (build_catalog(&catalog, S, opts->bgen_qualify_names, &S->root_schema->scope_index)) {
          return -1;
      }
  
      if (catalog.nobjects > 0 && !(object_map = malloc((size_t)catalog.nobjects * sizeof(object_map[0])))) {
          clear_catalog(&catalog);
          return -1;
      }
  
      /* Build the schema. */
  
      if (opts->bgen_length_prefix) {
          reflection_Schema_start_as_root_with_size(B);
      } else {
          reflection_Schema_start_as_root(B);
      }
      if (S->file_identifier.type == vt_string) {
          reflection_Schema_file_ident_create(B,
                  S->file_identifier.s.s, (size_t)S->file_identifier.s.len);
      }
      if (S->file_extension.type == vt_string) {
          reflection_Schema_file_ext_create(B,
                  S->file_extension.s.s, (size_t)S->file_extension.s.len);
      }
      export_objects(B, catalog.objects, catalog.nobjects, object_map);
      export_enums(B, catalog.enums, catalog.nenums, object_map);
      export_root_type(B, S->root_type.type, object_map);
  
      reflection_Schema_end_as_root(B);
  
      /* Clean up support datastructures. */
  
      clear_catalog(&catalog);
      if (object_map) {
          free(object_map);
      }
      return 0;
  }
  
  /*
   * We do not not sort attributes because we would loose ordering
   * information between different attributes, and between same named
   * attributes because the sort is not stable.
   *
   * The C bindings has a scan interface that can find attributes
   * in order of appearance.
   *
   * Field sorting is done on the finished buffer.
   */
  static void sort_objects(void *buffer)
  {
      size_t i;
      reflection_Schema_table_t schema;
      reflection_Object_vec_t objects;
      reflection_Object_table_t object;
      reflection_Field_vec_t fields;
      reflection_Field_mutable_vec_t mfields;
  
      schema = reflection_Schema_as_root(buffer);
      objects = reflection_Schema_objects(schema);
      for (i = 0; i < reflection_Object_vec_len(objects); ++i) {
          object = reflection_Object_vec_at(objects, i);
          fields = reflection_Object_fields(object);
          if (fields) {
              mfields = (reflection_Field_mutable_vec_t)fields;
              reflection_Field_vec_sort(mfields);
          }
      }
  }
  
  static FILE *open_file(fb_options_t *opts, fb_schema_t *S)
  {
      FILE *fp = 0;
      char *path;
      const char *prefix = opts->outpath ? opts->outpath : "";
      size_t len, prefix_len = strlen(prefix);
      const char *name;
      const char *ext;
  
      name = S->basename;
      len = strlen(name);
  
      ext = flatbuffers_extension;
  
      /* We generally should not use cgen options here, but in this case it makes sense. */
      if (opts->gen_stdout) {
          return stdout;
      }
      checkmem((path = fb_create_join_path_n(prefix, prefix_len, name, len, ext, 1)));
      fp = fopen(path, "wb");
      if (!fp) {
          fprintf(stderr, "error opening file for writing binary schema: %s\n", path);
      }
      free(path);
      return fp;
  }
  
  static void close_file(FILE *fp)
  {
      if (fp && fp != stdout) {
          fclose(fp);
      }
  }
  
  /*
   * Normally enums are required to be ascending in the schema and
   * therefore there is no need to sort enums. If not, we export them in
   * the order defined anyway becuase there is no well-defined ordering
   * and blindly sorting the content would just loose more information.
   *
   * In conclusion: find by enum value is only supported when enums are
   * defined in consequtive order.
   *
   * refers to: `opts->ascending_enum`
   *
   * `size` must hold the maximum buffer size.
   * Returns intput buffer if successful and updates size argument.
   */
  void *fb_codegen_bfbs_to_buffer(fb_options_t *opts, fb_schema_t *S, void *buffer, size_t *size)
  {
      flatcc_builder_t builder, *B;
  
      B = &builder;
      flatcc_builder_init(B);
      export_schema(B, opts, S);
      if (!flatcc_builder_copy_buffer(B, buffer, *size)) {
          goto done;
      }
      sort_objects(buffer);
  done:
      *size = flatcc_builder_get_buffer_size(B);
      flatcc_builder_clear(B);
      return buffer;
  }
  
  /*
   * Like to_buffer, but returns allocated buffer.
   * Updates size argument with buffer size if not null.
   * Returned buffer must be deallocatd with `free`.
   * The buffer is malloc aligned which should suffice for reflection buffers.
   */
  void *fb_codegen_bfbs_alloc_buffer(fb_options_t *opts, fb_schema_t *S, size_t *size)
  {
      flatcc_builder_t builder, *B;
      void *buffer = 0;
  
      B = &builder;
      flatcc_builder_init(B);
      if (export_schema(B, opts, S)) {
          goto done;
      }
      if (!(buffer = flatcc_builder_finalize_buffer(B, size))) {
          goto done;
      }
      sort_objects(buffer);
  done:
      flatcc_builder_clear(B);
      return buffer;
  }
  
  int fb_codegen_bfbs_to_file(fb_options_t *opts, fb_schema_t *S)
  {
      void *buffer;
      size_t size;
      FILE *fp;
      int ret = -1;
  
      fp = open_file(opts, S);
      if (!fp) {
          return -1;
      }
      buffer = fb_codegen_bfbs_alloc_buffer(opts, S, &size);
      if (!buffer) {
          fprintf(stderr, "failed to generate binary schema\n");
          goto done;
      }
      if (size != fwrite(buffer, 1, size, fp)) {
          fprintf(stderr, "could not write binary schema to file\n");
          goto done;
      }
      ret = 0;
  done:
      if (buffer) {
          free(buffer);
      }
      close_file(fp);
      return ret;
  }
  