diff options
author | Toni Uhlig <matzeton@googlemail.com> | 2023-07-16 02:03:33 +0200 |
---|---|---|
committer | Toni Uhlig <matzeton@googlemail.com> | 2023-07-16 02:03:33 +0200 |
commit | b31e4bc16d1df62b50c6f77a77041f9e7b6c906d (patch) | |
tree | 024c74c13d918aa6bde302aab6836fa33607613c /flatcc/samples | |
parent | ba6815ef8fb8ae472412b5af2837a7caba2799c2 (diff) | |
parent | 5a40295c4cf0af5ea8da9ced04a4ce7d3621a080 (diff) |
Merge commit '5a40295c4cf0af5ea8da9ced04a4ce7d3621a080' as 'flatcc'
Diffstat (limited to 'flatcc/samples')
-rw-r--r-- | flatcc/samples/CMakeLists.txt | 8 | ||||
-rw-r--r-- | flatcc/samples/bugreport/.gitignore | 1 | ||||
-rwxr-xr-x | flatcc/samples/bugreport/build.sh | 22 | ||||
-rw-r--r-- | flatcc/samples/bugreport/eclectic.fbs | 11 | ||||
-rw-r--r-- | flatcc/samples/bugreport/myissue.c | 35 | ||||
-rw-r--r-- | flatcc/samples/monster/CMakeLists.txt | 22 | ||||
-rwxr-xr-x | flatcc/samples/monster/build.sh | 27 | ||||
-rw-r--r-- | flatcc/samples/monster/monster.c | 353 | ||||
-rw-r--r-- | flatcc/samples/monster/monster.fbs | 32 | ||||
-rw-r--r-- | flatcc/samples/reflection/CMakeLists.txt | 31 | ||||
-rw-r--r-- | flatcc/samples/reflection/bfbs2json.c | 314 | ||||
-rwxr-xr-x | flatcc/samples/reflection/build.sh | 27 |
12 files changed, 883 insertions, 0 deletions
diff --git a/flatcc/samples/CMakeLists.txt b/flatcc/samples/CMakeLists.txt new file mode 100644 index 0000000..c05a450 --- /dev/null +++ b/flatcc/samples/CMakeLists.txt @@ -0,0 +1,8 @@ +if (FLATCC_NEED_C89_VAR_DECLS) + MESSAGE( STATUS "Disabling monster sample: needed C99 style variable declarations not supported by target compiler") +else() +add_subdirectory(monster) +endif() +if (FLATCC_REFLECTION) + add_subdirectory(reflection) +endif() diff --git a/flatcc/samples/bugreport/.gitignore b/flatcc/samples/bugreport/.gitignore new file mode 100644 index 0000000..378eac2 --- /dev/null +++ b/flatcc/samples/bugreport/.gitignore @@ -0,0 +1 @@ +build diff --git a/flatcc/samples/bugreport/build.sh b/flatcc/samples/bugreport/build.sh new file mode 100755 index 0000000..2de7528 --- /dev/null +++ b/flatcc/samples/bugreport/build.sh @@ -0,0 +1,22 @@ +#!/bin/sh +cd $(dirname $0) + +FLATBUFFERS_DIR=../.. +NAME=myissue +SCHEMA=eclectic.fbs +OUT=build + +FLATCC_EXE=$FLATBUFFERS_DIR/bin/flatcc +FLATCC_INCLUDE=$FLATBUFFERS_DIR/include +FLATCC_LIB=$FLATBUFFERS_DIR/lib + +mkdir -p $OUT +$FLATCC_EXE --outfile $OUT/${NAME}_generated.h -a $SCHEMA || exit 1 +cc -I$FLATCC_INCLUDE -g -o $OUT/$NAME $NAME.c -L$FLATCC_LIB -lflatccrt_d || exit 1 +echo "running $OUT/$NAME" +if $OUT/$NAME; then + echo "success" +else + echo "failed" + exit 1 +fi diff --git a/flatcc/samples/bugreport/eclectic.fbs b/flatcc/samples/bugreport/eclectic.fbs new file mode 100644 index 0000000..ad507e7 --- /dev/null +++ b/flatcc/samples/bugreport/eclectic.fbs @@ -0,0 +1,11 @@ +namespace Eclectic; + +enum Fruit : byte { Banana = -1, Orange = 42 } +table FooBar { + meal : Fruit = Banana; + density : long (deprecated); + say : string; + height : short; +} +file_identifier "NOOB"; +root_type FooBar; diff --git a/flatcc/samples/bugreport/myissue.c b/flatcc/samples/bugreport/myissue.c new file mode 100644 index 0000000..0098235 --- /dev/null +++ b/flatcc/samples/bugreport/myissue.c @@ -0,0 +1,35 @@ +/* Minimal test with all headers generated into a single file. */ +#include "build/myissue_generated.h" +#include "flatcc/support/hexdump.h" + +int main(int argc, char *argv[]) +{ + int ret; + void *buf; + size_t size; + flatcc_builder_t builder, *B; + + (void)argc; + (void)argv; + + B = &builder; + flatcc_builder_init(B); + + Eclectic_FooBar_start_as_root(B); + Eclectic_FooBar_say_create_str(B, "hello"); + Eclectic_FooBar_meal_add(B, Eclectic_Fruit_Orange); + Eclectic_FooBar_height_add(B, -8000); + Eclectic_FooBar_end_as_root(B); + buf = flatcc_builder_get_direct_buffer(B, &size); +#if defined(PROVOKE_ERROR) || 0 + /* Provoke error for testing. */ + ((char*)buf)[0] = 42; +#endif + ret = Eclectic_FooBar_verify_as_root(buf, size); + if (ret) { + hexdump("Eclectic.FooBar buffer for myissue", buf, size, stdout); + printf("could not verify Electic.FooBar table, got %s\n", flatcc_verify_error_string(ret)); + } + flatcc_builder_clear(B); + return ret; +} diff --git a/flatcc/samples/monster/CMakeLists.txt b/flatcc/samples/monster/CMakeLists.txt new file mode 100644 index 0000000..1c03455 --- /dev/null +++ b/flatcc/samples/monster/CMakeLists.txt @@ -0,0 +1,22 @@ +include(CTest) + +set(INC_DIR "${PROJECT_SOURCE_DIR}/include") +set(GEN_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated") +set(FBS_DIR "${CMAKE_CURRENT_SOURCE_DIR}") + +include_directories("${GEN_DIR}" "${INC_DIR}") + +add_custom_target(gen_monster_fbs ALL) +add_custom_command ( + TARGET gen_monster_fbs + COMMAND ${CMAKE_COMMAND} -E make_directory "${GEN_DIR}" + COMMAND flatcc_cli -a -o "${GEN_DIR}" "${FBS_DIR}/monster.fbs" + DEPENDS flatcc_cli "${FBS_DIR}/monster.fbs" +) +add_executable(monster monster.c) +add_dependencies(monster gen_monster_fbs) +target_link_libraries(monster flatccrt) + +if (FLATCC_TEST) + add_test(monster monster${CMAKE_EXECUTABLE_SUFFIX}) +endif() diff --git a/flatcc/samples/monster/build.sh b/flatcc/samples/monster/build.sh new file mode 100755 index 0000000..e090aed --- /dev/null +++ b/flatcc/samples/monster/build.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +set -e +cd `dirname $0`/../.. +ROOT=`pwd` +NAME=monster +TMP=${ROOT}/build/tmp/samples/${NAME} +EX=${ROOT}/samples/${NAME} + +CC=${CC:-cc} +CFLAGS_DEBUG="-g -I ${ROOT}/include" +CFLAGS_RELEASE="-O3 -DNDEBUG -I ${ROOT}/include" +${ROOT}/scripts/build.sh +mkdir -p ${TMP} +rm -rf ${TMP}/* +bin/flatcc -a -o ${TMP} ${EX}/${NAME}.fbs + +cp ${EX}/*.c ${TMP} +cd ${TMP} + +echo "building $NAME example (debug)" +$CC $CFLAGS_DEBUG ${NAME}.c ${ROOT}/lib/libflatccrt_d.a -o ${NAME}_d +echo "building $NAME example (release)" +$CC $CFLAGS_RELEASE ${NAME}.c ${ROOT}/lib/libflatccrt.a -o ${NAME} + +echo "running $NAME example (debug)" +./${NAME}_d diff --git a/flatcc/samples/monster/monster.c b/flatcc/samples/monster/monster.c new file mode 100644 index 0000000..32a39b0 --- /dev/null +++ b/flatcc/samples/monster/monster.c @@ -0,0 +1,353 @@ +// Example on how to build a Monster FlatBuffer. + + +// Note: while some older C89 compilers are supported when +// -DFLATCC_PORTABLE is defined, this particular sample is known not to +// not work with MSVC 2010 (MSVC 2013 is OK) due to inline variable +// declarations. These are easily move to the start of code blocks, but +// since we follow the step-wise tutorial, it isn't really practical +// in this case. The comment style is technically also in violation of C89. + + +#include "monster_builder.h" // Generated by `flatcc`. +// <string.h> and <assert.h> already included. + +// Convenient namespace macro to manage long namespace prefix. +// The ns macro makes it possible to write `ns(Monster_create(...))` +// instead of `MyGame_Sample_Monster_create(...)` +#undef ns +#define ns(x) FLATBUFFERS_WRAP_NAMESPACE(MyGame_Sample, x) // Specified in the schema. + +// A helper to simplify creating vectors from C-arrays. +#define c_vec_len(V) (sizeof(V)/sizeof((V)[0])) + +// This allows us to verify result in optimized builds. +#define test_assert(x) do { if (!(x)) { assert(0); return -1; }} while(0) + +// Bottom-up approach where we create child objects and store these +// in temporary references before a parent object is created with +// these references. +int create_monster_bottom_up(flatcc_builder_t *B, int flexible) +{ + flatbuffers_string_ref_t weapon_one_name = flatbuffers_string_create_str(B, "Sword"); + int16_t weapon_one_damage = 3; + + flatbuffers_string_ref_t weapon_two_name = flatbuffers_string_create_str(B, "Axe"); + int16_t weapon_two_damage = 5; + + // Use the `MyGame_Sample_Weapon_create` shortcut to create Weapons + // with all the fields set. + // + // In the C-API, verbs (here create) always follow the type name + // (here Weapon), prefixed by the namespace (here MyGame_Sample_): + // MyGame_Sample_Weapon_create(...), or ns(Weapone_create(...)). + ns(Weapon_ref_t) sword = ns(Weapon_create(B, weapon_one_name, weapon_one_damage)); + ns(Weapon_ref_t) axe = ns(Weapon_create(B, weapon_two_name, weapon_two_damage)); + + // Serialize a name for our monster, called "Orc". + // The _str suffix indicates the source is an ascii-z string. + flatbuffers_string_ref_t name = flatbuffers_string_create_str(B, "Orc"); + + // Create a `vector` representing the inventory of the Orc. Each number + // could correspond to an item that can be claimed after he is slain. + uint8_t treasure[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + flatbuffers_uint8_vec_ref_t inventory; + // `c_vec_len` is the convenience macro we defined earlier. + inventory = flatbuffers_uint8_vec_create(B, treasure, c_vec_len(treasure)); + + // Here we use a top-down approach locally to build a Weapons vector + // in-place instead of creating a temporary external vector to use + // as argument like we did with the `inventory` earlier on, but the + // overall approach is still bottom-up. + ns(Weapon_vec_start(B)); + ns(Weapon_vec_push(B, sword)); + ns(Weapon_vec_push(B, axe)); + ns(Weapon_vec_ref_t) weapons = ns(Weapon_vec_end(B)); + + + // Create a `Vec3`, representing the Orc's position in 3-D space. + ns(Vec3_t) pos = { 1.0f, 2.0f, 3.0f }; + + + // Set his hit points to 300 and his mana to 150. + int16_t hp = 300; + // The default value is 150, so we will never store this field. + int16_t mana = 150; + + // Create the equipment union. In the C++ language API this is given + // as two arguments to the create call, or as two separate add + // operations for the type and the table reference. Here we create + // a single union value that carries both the type and reference. + ns(Equipment_union_ref_t) equipped = ns(Equipment_as_Weapon(axe)); + + if (!flexible) { + // Finally, create the monster using the `Monster_create` helper function + // to set all fields. + // + // Note that the Equipment union only take up one argument in C, where + // C++ takes a type and an object argument. + ns(Monster_create_as_root(B, &pos, mana, hp, name, inventory, ns(Color_Red), + weapons, equipped)); + + // Unlike C++ we do not use a Finish call. Instead we use the + // `create_as_root` action which has better type safety and + // simplicity. + // + // However, we can also express this as: + // + // ns(Monster_ref_t) orc = ns(Monster_create(B, ...)); + // flatcc_builder_buffer_create(orc); + // + // In this approach the function should return the orc and + // let a calling function handle the flatcc_buffer_create call + // for a more composable setup that is also able to create child + // monsters. In general, `flatcc_builder` calls are best isolated + // in a containing driver function. + + } else { + + // A more flexible approach where we mix bottom-up and top-down + // style. We still create child objects first, but then create + // a top-down style monster object that we can manipulate in more + // detail. + + // It is important to pair `start_as_root` with `end_as_root`. + ns(Monster_start_as_root(B)); + ns(Monster_pos_create(B, 1.0f, 2.0f, 3.0f)); + // or alternatively + //ns(Monster_pos_add(&pos); + + ns(Monster_hp_add(B, hp)); + // Notice that `Monser_name_add` adds a string reference unlike the + // add_str and add_strn variants. + ns(Monster_name_add(B, name)); + ns(Monster_inventory_add(B, inventory)); + ns(Monster_color_add(B, ns(Color_Red))); + ns(Monster_weapons_add(B, weapons)); + ns(Monster_equipped_add(B, equipped)); + // Complete the monster object and make it the buffer root object. + ns(Monster_end_as_root(B)); + + // We could also drop the `as_root` suffix from Monster_start/end(B) + // and add the table as buffer root later: + // + // ns(Monster_ref_t) orc = ns(Monster_start(B)); + // ... + // ns(Monster_ref_t) orc = ns(Monster_end(B)); + // flatcc_builder_buffer_create(orc); + // + // It is best to keep the `flatcc_builder` calls in a containing + // driver function for modularity. + } + return 0; +} + +// Alternative top-down approach where parent objects are created before +// their children. We only need to save one reference because the `axe` +// object is used in two places effectively making the buffer object +// graph a DAG. +int create_monster_top_down(flatcc_builder_t *B) +{ + uint8_t treasure[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + size_t treasure_count = c_vec_len(treasure); + ns(Weapon_ref_t) axe; + + // NOTE: if we use end_as_root, we MUST also start as root. + ns(Monster_start_as_root(B)); + ns(Monster_pos_create(B, 1.0f, 2.0f, 3.0f)); + ns(Monster_hp_add(B, 300)); + //ns(Monster_mana_add(B, 150)); + // We use create_str instead of add because we have no existing string reference. + ns(Monster_name_create_str(B, "Orc")); + // Again we use create because we no existing vector object, only a C-array. + ns(Monster_inventory_create(B, treasure, treasure_count)); + ns(Monster_color_add(B, ns(Color_Red))); + if (1) { + ns(Monster_weapons_start(B)); + ns(Monster_weapons_push_create(B, flatbuffers_string_create_str(B, "Sword"), 3)); + // We reuse the axe object later. Note that we dereference a pointer + // because push always returns a short-term pointer to the stored element. + // We could also have created the axe object first and simply pushed it. + axe = *ns(Monster_weapons_push_create(B, flatbuffers_string_create_str(B, "Axe"), 5)); + ns(Monster_weapons_end(B)); + } else { + // We can have more control with the table elements added to a vector: + // + ns(Monster_weapons_start(B)); + ns(Monster_weapons_push_start(B)); + ns(Weapon_name_create_str(B, "Sword")); + ns(Weapon_damage_add(B, 3)); + ns(Monster_weapons_push_end(B)); + ns(Monster_weapons_push_start(B)); + ns(Weapon_name_create_str(B, "Axe")); + ns(Weapon_damage_add(B, 5)); + axe = *ns(Monster_weapons_push_end(B)); + ns(Monster_weapons_end(B)); + } + // Unions can get their type by using a type-specific add/create/start method. + ns(Monster_equipped_Weapon_add(B, axe)); + + ns(Monster_end_as_root(B)); + return 0; +} + +// This isn't strictly needed because the builder already included the reader, +// but we would need it if our reader were in a separate file. +#include "monster_reader.h" + +#undef ns +#define ns(x) FLATBUFFERS_WRAP_NAMESPACE(MyGame_Sample, x) // Specified in the schema. + +int access_monster_buffer(const void *buffer) +{ + // Note that we use the `table_t` suffix when reading a table object + // as opposed to the `ref_t` suffix used during the construction of + // the buffer. + ns(Monster_table_t) monster = ns(Monster_as_root(buffer)); + + // Note: root object pointers are NOT the same as the `buffer` pointer. + + // Make sure the buffer is accessible. + test_assert(monster != 0); + + int16_t hp = ns(Monster_hp(monster)); + int16_t mana = ns(Monster_mana(monster)); + // This is just a const char *, but it also supports a fast length operation. + flatbuffers_string_t name = ns(Monster_name(monster)); + size_t name_len = flatbuffers_string_len(name); + + test_assert(hp == 300); + // Since 150 is the default, we are reading a value that wasn't stored. + test_assert(mana == 150); + test_assert(0 == strcmp(name, "Orc")); + test_assert(name_len == strlen("Orc")); + + int hp_present = ns(Monster_hp_is_present(monster)); // 1 + int mana_present = ns(Monster_mana_is_present(monster)); // 0 + test_assert(hp_present); + test_assert(!mana_present); + + ns(Vec3_struct_t) pos = ns(Monster_pos(monster)); + // Make sure pos has been set. + test_assert(pos != 0); + float x = ns(Vec3_x(pos)); + float y = ns(Vec3_y(pos)); + float z = ns(Vec3_z(pos)); + + // The literal `f` suffix is important because double literals does + // not always map cleanly to 32-bit represention even with only a few digits: + // `1.0 == 1.0f`, but `3.2 != 3.2f`. + test_assert(x == 1.0f); + test_assert(y == 2.0f); + test_assert(z == 3.0f); + + // We can also read the position into a C-struct. We have to copy + // because we generally do not know if the native endian format + // matches the one stored in the buffer (pe: protocol endian). + ns(Vec3_t) pos_vec; + // `pe` indicates endian conversion from protocol to native. + ns(Vec3_copy_from_pe(&pos_vec, pos)); + test_assert(pos_vec.x == 1.0f); + test_assert(pos_vec.y == 2.0f); + test_assert(pos_vec.z == 3.0f); + + // This is a const uint8_t *, but it shouldn't be accessed directly + // to ensure proper endian conversion. However, uint8 (ubyte) are + // not sensitive endianness, so we *could* have accessed it directly. + // The compiler likely optimizes this so that it doesn't matter. + flatbuffers_uint8_vec_t inv = ns(Monster_inventory(monster)); + size_t inv_len = flatbuffers_uint8_vec_len(inv); + // Make sure the inventory has been set. + test_assert(inv != 0); + // If `inv` were absent, the length would 0, so the above test is redundant. + test_assert(inv_len == 10); + // Index 0 is the first, index 2 is the third. + // NOTE: C++ uses the `Get` terminology for vector elemetns, C use `at`. + uint8_t third_item = flatbuffers_uint8_vec_at(inv, 2); + test_assert(third_item == 2); + + ns(Weapon_vec_t) weapons = ns(Monster_weapons(monster)); + size_t weapons_len = ns(Weapon_vec_len(weapons)); + test_assert(weapons_len == 2); + // We can use `const char *` instead of `flatbuffers_string_t`. + const char *second_weapon_name = ns(Weapon_name(ns(Weapon_vec_at(weapons, 1)))); + int16_t second_weapon_damage = ns(Weapon_damage(ns(Weapon_vec_at(weapons, 1)))); + test_assert(second_weapon_name != 0 && strcmp(second_weapon_name, "Axe") == 0); + test_assert(second_weapon_damage == 5); + + // Access union type field. + if (ns(Monster_equipped_type(monster)) == ns(Equipment_Weapon)) { + // Cast to appropriate type: + // C does not require the cast to Weapon_table_t, but C++ does. + ns(Weapon_table_t) weapon = (ns(Weapon_table_t)) ns(Monster_equipped(monster)); + const char *weapon_name = ns(Weapon_name(weapon)); + int16_t weapon_damage = ns(Weapon_damage(weapon)); + + test_assert(0 == strcmp(weapon_name, "Axe")); + test_assert(weapon_damage == 5); + } + return 0; +} + +#include <stdio.h> + +int main(int argc, char *argv[]) +{ + // Create a `FlatBufferBuilder`, which will be used to create our + // monsters' FlatBuffers. + flatcc_builder_t builder; + void *buf; + size_t size; + + // Silence warnings. + (void)argc; + (void)argv; + + // Initialize the builder object. + flatcc_builder_init(&builder); + test_assert(0 == create_monster_bottom_up(&builder, 0)); + + // Allocate and extract a readable buffer from internal builder heap. + // NOTE: Finalizing the buffer does NOT change the builder, it + // just creates a snapshot of the builder content. + // NOTE2: finalize_buffer uses malloc while finalize_aligned_buffer + // uses a portable aligned allocation method. Often the malloc + // version is sufficient, but won't work for all schema on all + // systems. If the buffer is written to disk or network, but not + // accessed in memory, `finalize_buffer` is also sufficient. + // The flatcc_builder version of free or aligned_free should be used + // instead of `free` although free will often work on POSIX systems. + // This ensures portability and prevents issues when linking to + // allocation libraries other than malloc. + buf = flatcc_builder_finalize_aligned_buffer(&builder, &size); + //buf = flatcc_builder_finalize_buffer(&builder, &size); + + // We now have a FlatBuffer we can store on disk or send over a network. + // ** file/network code goes here :) ** + // Instead, we're going to access it right away (as if we just received it). + //access_monster_buffer(buf); + + // prior to v0.5.0, use `aligned_free` + flatcc_builder_aligned_free(buf); + //free(buf); + // + // The builder object can optionally be reused after a reset which + // is faster than creating a new builder. Subsequent use might + // entirely avoid temporary allocations until finalizing the buffer. + flatcc_builder_reset(&builder); + test_assert(0 == create_monster_bottom_up(&builder, 1)); + buf = flatcc_builder_finalize_aligned_buffer(&builder, &size); + access_monster_buffer(buf); + flatcc_builder_aligned_free(buf); + flatcc_builder_reset(&builder); + create_monster_top_down(&builder); + buf = flatcc_builder_finalize_buffer(&builder, &size); + test_assert(0 == access_monster_buffer(buf)); + flatcc_builder_free(buf); + // Eventually the builder must be cleaned up: + flatcc_builder_clear(&builder); + + printf("The FlatBuffer was successfully created and accessed!\n"); + + return 0; +} diff --git a/flatcc/samples/monster/monster.fbs b/flatcc/samples/monster/monster.fbs new file mode 100644 index 0000000..12859d2 --- /dev/null +++ b/flatcc/samples/monster/monster.fbs @@ -0,0 +1,32 @@ +// Example IDL file for our monster's schema. + +namespace MyGame.Sample; + +enum Color:byte { Red = 0, Green, Blue = 2 } + +union Equipment { Weapon } // Optionally add more tables. + +struct Vec3 { + x:float; + y:float; + z:float; +} + +table Monster { + pos:Vec3; // Struct. + mana:short = 150; + hp:short = 100; + name:string; + friendly:bool = false (deprecated); + inventory:[ubyte]; // Vector of scalars. + color:Color = Blue; // Enum. + weapons:[Weapon]; // Vector of tables. + equipped:Equipment; // Union. +} + +table Weapon { + name:string; + damage:short; +} + +root_type Monster; diff --git a/flatcc/samples/reflection/CMakeLists.txt b/flatcc/samples/reflection/CMakeLists.txt new file mode 100644 index 0000000..cfbef1c --- /dev/null +++ b/flatcc/samples/reflection/CMakeLists.txt @@ -0,0 +1,31 @@ +include(CTest) + +# +# This projects depends headers generated from reflection.fbs but these +# are pre-generated in `include/flatcc/reflection` so we don't need to +# build them here. +# +# What we do build is a binary schema `monster.bfbs` for the monster +# sample, and the actual C source of this project. +# + +set(INC_DIR "${PROJECT_SOURCE_DIR}/include") +set(GEN_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated") +set(FBS_DIR "${PROJECT_SOURCE_DIR}/samples/monster") + +include_directories("${GEN_DIR}" "${INC_DIR}") + +add_custom_target(gen_monster_bfbs ALL) +add_custom_command ( + TARGET gen_monster_bfbs + COMMAND ${CMAKE_COMMAND} -E make_directory "${GEN_DIR}" + COMMAND flatcc_cli --schema -o "${GEN_DIR}" "${FBS_DIR}/monster.fbs" + DEPENDS flatcc_cli "${FBS_DIR}/monster.fbs" +) +add_executable(bfbs2json bfbs2json.c) +add_dependencies(bfbs2json gen_monster_bfbs) +target_link_libraries(bfbs2json flatccrt) + +if (FLATCC_TEST) + add_test(bfbs2json bfbs2json${CMAKE_EXECUTABLE_SUFFIX} ${GEN_DIR}/monster.bfbs) +endif() diff --git a/flatcc/samples/reflection/bfbs2json.c b/flatcc/samples/reflection/bfbs2json.c new file mode 100644 index 0000000..03c31e7 --- /dev/null +++ b/flatcc/samples/reflection/bfbs2json.c @@ -0,0 +1,314 @@ +#include "flatcc/support/readfile.h" +#include "flatcc/reflection/reflection_reader.h" + +/* -DFLATCC_PORTABLE may help if inttypes.h is missing. */ +#ifndef PRId64 +#include <inttypes.h> +#endif + + +/* + * Reads a binary schema generated by `flatcc` or Googles `flatc` tool, + * then prints the content out in a custom JSON format. + * + * Note: This is completely unrelated to `flatcc's` JSON support - we + * just needed to do something tangible with the data we read from the + * binary schema and opted to print it as JSON. + * + * The JSON can be pretty printed with an external tool, for example: + * + * cat monster_test_schema.json | jq '.' + */ + +void print_type(reflection_Type_table_t T) +{ + int first = 1; + printf("{"); + if (reflection_Type_base_type_is_present(T)) { + if (!first) { + printf(","); + } + printf("\"base_type\":\"%s\"", reflection_BaseType_name(reflection_Type_base_type(T))); + first = 0; + } + if (reflection_Type_element_is_present(T)) { + if (!first) { + printf(","); + } + printf("\"element\":\"%s\"", reflection_BaseType_name(reflection_Type_element(T))); + first = 0; + } + if (reflection_Type_index_is_present(T)) { + if (!first) { + printf(","); + } + printf("\"index\":%d", reflection_Type_index(T)); + first = 0; + } + if (reflection_Type_fixed_length_is_present(T)) { + if (!first) { + printf(","); + } + printf("\"fixed_length\":%d", reflection_Type_fixed_length(T)); + first = 0; + } + printf("}"); +} + +void print_attributes(reflection_KeyValue_vec_t KV) +{ + size_t i; + reflection_KeyValue_table_t attribute; + const char *key, *value; + + printf("["); + for (i = 0; i < reflection_KeyValue_vec_len(KV); ++i) { + attribute = reflection_KeyValue_vec_at(KV, i); + key = reflection_KeyValue_key(attribute); + value = reflection_KeyValue_value(attribute); + if (i > 0) { + printf(","); + } + printf("{\"key\":\"%s\"", key); + if (value) { + /* TODO: we ought to escape '\"' and other non-string-able characters. */ + printf(",\"value\":\"%s\"", value); + } + printf("}"); + } + printf("]"); +} + +void print_object(reflection_Object_table_t O) +{ + reflection_Field_vec_t Flds; + reflection_Field_table_t F; + size_t i; + + Flds = reflection_Object_fields(O); + printf("{\"name\":\"%s\"", reflection_Object_name(O)); + printf(",\"fields\":["); + for (i = 0; i < reflection_Field_vec_len(Flds); ++i) { + if (i > 0) { + printf(","); + } + F = reflection_Field_vec_at(Flds, i); + printf("{\"name\":\"%s\",\"type\":", reflection_Field_name(F)); + print_type(reflection_Field_type(F)); + if (reflection_Field_id_is_present(F)) { + printf(",\"id\":%hu", reflection_Field_id(F)); + } + if (reflection_Field_default_integer_is_present(F)) { + printf(",\"default_integer\":%"PRId64"", (int64_t)reflection_Field_default_integer(F)); + } + if (reflection_Field_default_real_is_present(F)) { + printf(",\"default_integer\":%lf", reflection_Field_default_real(F)); + } + if (reflection_Field_required_is_present(F)) { + printf(",\"required\":%s", reflection_Field_required(F) ? "true" : "false"); + } + if (reflection_Field_key_is_present(F)) { + printf(",\"key\":%s", reflection_Field_key(F) ? "true" : "false"); + } + if (reflection_Field_attributes_is_present(F)) { + printf(",\"attributes\":"); + print_attributes(reflection_Field_attributes(F)); + } + printf("}"); + } + printf("]"); + if (reflection_Object_is_struct_is_present(O)) { + printf(",\"is_struct\":%s", reflection_Object_is_struct(O) ? "true" : "false"); + } + if (reflection_Object_minalign_is_present(O)) { + printf(",\"minalign\":%d", reflection_Object_minalign(O)); + } + if (reflection_Object_bytesize_is_present(O)) { + printf(",\"bytesize\":%d", reflection_Object_bytesize(O)); + } + if (reflection_Object_attributes_is_present(O)) { + printf(",\"attributes\":"); + print_attributes(reflection_Object_attributes(O)); + } + printf("}"); +} + +void print_enum(reflection_Enum_table_t E) +{ + reflection_EnumVal_vec_t EnumVals; + reflection_EnumVal_table_t EV; + size_t i; + + printf("{\"name\":\"%s\"", reflection_Enum_name(E)); + EnumVals = reflection_Enum_values(E); + printf(",\"values\":["); + for (i = 0; i < reflection_Enum_vec_len(EnumVals); ++i) { + EV = reflection_EnumVal_vec_at(EnumVals, i); + if (i > 0) { + printf(","); + } + printf("{\"name\":\"%s\"", reflection_EnumVal_name(EV)); + if (reflection_EnumVal_value_is_present(EV)) { + printf(",\"value\":%"PRId64"", (int64_t)reflection_EnumVal_value(EV)); + } + if (reflection_EnumVal_object_is_present(EV)) { + printf(",\"object\":"); + print_object(reflection_EnumVal_object(EV)); + } + if (reflection_EnumVal_union_type_is_present(EV)) { + printf(",\"union_type\":"); + print_type(reflection_EnumVal_union_type(EV)); + } + printf("}"); + } + printf("]"); + if (reflection_Enum_is_union_is_present(E)) { + printf(",\"is_union\":%s", reflection_Enum_is_union(E) ? "true" : "false"); + } + printf(",\"underlying_type\":"); + print_type(reflection_Enum_underlying_type(E)); + if (reflection_Enum_attributes_is_present(E)) { + printf(",\"attributes\":"); + print_attributes(reflection_Enum_attributes(E)); + } + printf("}"); +} + +void print_call(reflection_RPCCall_table_t C) +{ + printf("{\"name\":\"%s\"", reflection_RPCCall_name(C)); + printf(",\"request\":"); + print_object(reflection_RPCCall_request(C)); + printf(",\"response\":"); + print_object(reflection_RPCCall_response(C)); + + if (reflection_RPCCall_attributes_is_present(C)) { + printf(",\"attributes\":"); + print_attributes(reflection_RPCCall_attributes(C)); + } + printf("}"); +} + +void print_service(reflection_Service_table_t S) +{ + reflection_RPCCall_vec_t calls; + size_t i; + + printf("{\"name\":\"%s\"", reflection_Service_name(S)); + + printf(",\"calls\":["); + calls = reflection_Service_calls(S); + for (i = 0; i < reflection_RPCCall_vec_len(calls); ++i) { + if (i > 0) { + printf(","); + } + print_call(reflection_RPCCall_vec_at(calls, i)); + } + printf("]"); + + if (reflection_Service_attributes_is_present(S)) { + printf(",\"attributes\":"); + print_attributes(reflection_Service_attributes(S)); + } + printf("}"); +} + +void print_schema(reflection_Schema_table_t S) +{ + reflection_Object_vec_t Objs; + reflection_Enum_vec_t Enums; + reflection_Service_vec_t Services; + size_t i; + + Objs = reflection_Schema_objects(S); + printf("{"); + printf("\"objects\":["); + for (i = 0; i < reflection_Object_vec_len(Objs); ++i) { + if (i > 0) { + printf(","); + } + print_object(reflection_Object_vec_at(Objs, i)); + } + printf("]"); + Enums = reflection_Schema_enums(S); + printf(",\"enums\":["); + for (i = 0; i < reflection_Enum_vec_len(Enums); ++i) { + if (i > 0) { + printf(","); + } + print_enum(reflection_Enum_vec_at(Enums, i)); + } + printf("]"); + if (reflection_Schema_file_ident_is_present(S)) { + printf(",\"file_ident\":\"%s\"", reflection_Schema_file_ident(S)); + } + if (reflection_Schema_file_ext_is_present(S)) { + printf(",\"file_ext\":\"%s\"", reflection_Schema_file_ext(S)); + } + if (reflection_Schema_root_table_is_present(S)) { + printf(",\"root_table\":"); + print_object(reflection_Schema_root_table(S)); + } + if (reflection_Schema_services_is_present(S)) { + printf(",\"services\":["); + Services = reflection_Schema_services(S); + for (i = 0; i < reflection_Service_vec_len(Services); ++i) { + if (i > 0) { + printf(","); + } + print_service(reflection_Service_vec_at(Services, i)); + } + printf("]"); + } + printf("}\n"); +} + +int load_and_dump_schema(const char *filename) +{ + void *buffer; + size_t size; + int ret = -1; + reflection_Schema_table_t S; + + buffer = readfile(filename, 100000, &size); + if (!buffer) { + fprintf(stderr, "failed to load binary schema file: '%s'\n", filename); + goto done; + } + if (size < 12) { + fprintf(stderr, "file too small to access: '%s'\n", filename); + goto done; + } + S = reflection_Schema_as_root(buffer); + if (!S) { + S = reflection_Schema_as_root((char*)buffer + 4); + if (S) { + fprintf(stderr, "(skipping length field of input buffer)\n"); + } + } + if (!S) { + fprintf(stderr, "input is not a valid schema"); + goto done; + } + print_schema(S); + ret = 0; + +done: + if (buffer) { + free(buffer); + } + return ret; +} + +int main(int argc, char *argv[]) +{ + if (argc != 2) { + fprintf(stderr, "usage: bfbs2json <filename>\n"); + fprintf(stderr, "reads a binary flatbuffer schema and prints it to compact json on stdout\n\n"); + fprintf(stderr, "pretty print with exernal tool, for example:\n" + " bfbs2json myschema.bfbs | python -m json.tool > myschema.json\n" + "note: also understands binary schema files with a 4 byte length prefix\n"); + exit(-1); + } + return load_and_dump_schema(argv[1]); +} diff --git a/flatcc/samples/reflection/build.sh b/flatcc/samples/reflection/build.sh new file mode 100755 index 0000000..84ccf8a --- /dev/null +++ b/flatcc/samples/reflection/build.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +set -e +cd `dirname $0`/../.. +ROOT=`pwd` +TMP=${ROOT}/build/tmp/samples/reflection + +CC=${CC:-cc} +${ROOT}/scripts/build.sh +mkdir -p ${TMP} +rm -rf ${TMP}/* +#bin/flatcc --schema --schema-length=yes -o ${TMP} test/monster_test/monster_test.fbs +bin/flatcc --schema -o ${TMP} test/monster_test/monster_test.fbs + +cp samples/reflection/*.c ${TMP} +cd ${TMP} +# We don't need debug version, but it is always useful to have if we get +# assertions in the interface code. +$CC -g -I ${ROOT}/include bfbs2json.c -o bfbs2jsond +$CC -O3 -DNDEBUG -I ${ROOT}/include bfbs2json.c -o bfbs2json +cp bfbs2json ${ROOT}/bin/bfbs2json +echo "generating example json output from monster_test.fbs schema ..." +${ROOT}/bin/bfbs2json ${TMP}/monster_test.bfbs > monster_test_schema.json +cat monster_test_schema.json | python -m json.tool > pretty_monster_test_schema.json +echo "test json file located in ${TMP}/monster_test_schema.json" +echo "pretty printed file located in ${TMP}/pretty_monster_test_schema.json" +echo "bfbs2json tool placed in ${ROOT}/bin/bfbs2json" |