FlatBuffers 模式语言IDL文法全解Grammar.md 逐条解析与源码印证【免费下载链接】flatbuffersFlatBuffers: Memory Efficient Serialization Library项目地址: https://gitcode.com/gh_mirrors/flat/flatbuffers导读本文是 FlatBuffers 官方文档 docs/source/Grammar.md 的完整中文技术解析。该文档以 BNF巴科斯-瑙尔范式形式给出了 FlatBuffers 模式语言即 IDLInterface Definition Language的完整文法定义——从文件顶层结构include、namespace、root_type到类型声明table、struct、enum、union、字段声明、RPC 声明再到整数/浮点/布尔字面量的词法规则。读完本文你将能逐条读懂每一条文法产生式掌握.fbs模式文件如 tests/monster_test.fbs、samples/monster.fbs中每一个语法要素的准确含义并理解flatc编译器在 src/idl_parser.cpp 中是如何逐条实现这些规则的。一、文法的整体骨架一个 schema 由什么组成Grammar.md 的第一条产生式定义了整个 schema 文件的顶层结构schema include* ( namespace_decl | type_decl | enum_decl | root_decl | file_extension_decl | file_identifier_decl | attribute_decl | rpc_decl | object )*即一个.fbs模式文件由一个可选的include列表include*表示零个或多个以及零个或多个顶层声明组成。顶层声明可以任意交错出现包括产生式用途namespace_decl声明命名空间对应 C 命名空间 / Java 包名type_decl声明table或structenum_decl声明enum或unionroot_decl声明根类型root_typefile_extension_decl声明二进制文件扩展名file_identifier_decl声明 4 字符文件标识符attribute_decl声明自定义属性rpc_decl声明 RPC 服务objectJSON 对象字面量用于 JSON 解析场景注意文法中出现了type_decl与enum_decl两条产生式这意味着table/struct/enum/union属于类型声明而root_type等属于独立声明。tests/monster_test.fbs几乎展示了全部要素include、多个namespace、attribute priority、多个enum/union、table/struct、rpc_service、root_type、file_identifier、file_extension是研读本文法的最佳完整样例。源码印证Parser::Parse是 src/idl_parser.cpp 中实现这条顶层循环的入口它在 while 循环中依据 token 分派到ParseNamespace、ParseTypeDecl、ParseEnum、ParseDecl等各个子解析器如 ParseEnum。二、include、namespace 与 attribute文件级声明2.1 include —— 引入其他模式文件include include string_constant ;include后跟一个字符串常量如include_test1.fbs并以分号结尾。它的作用是让当前文件可以引用其他.fbs中定义的类型。tests/monster_test.fbs第 3 行即是一例include include_test1.fbs;关键行为参见 docs/source/Schemas.md 的 Includes 小节每个被包含文件只会被解析一次即使被多处引用flatc生成代码时只生成当前文件中的定义被 include 文件里的定义需要单独生成。源码印证include 解析时Parser会把目标文件的内容并入当前解析流ParseRoot内部会处理 include 的递归加载并在structs_/enums_符号表中建立跨文件可见性。2.2 namespace —— 声明命名空间namespace_decl namespace ident ( . ident )* ;namespace后跟一个或多个用.连接的标识符以分号结尾。它在生成的 C 代码中对应嵌套命名空间在 Java 中对应包名。tests/monster_test.fbs中出现了多次 namespace 切换namespace MyGame; namespace MyGame.Example2; namespace MyGame.Example;这展示了同一文件中可以多次切换命名空间且支持嵌套写法MyGame.Example2。后续的类型声明会归入当前namespace直到下一次切换。这也是MyGame.Example.Monster与MyGame.Example2.Monster可以在同一文件中重名共存的原因见该文件中第 7 行与第 11 行两个同名table Monster的定义。源码印证Namespace::GetFullyQualifiedName 实现了用.连接各层命名的全限定名解析Parser::ParseNamespace 内部通过GetPooledString复用字符串、维护namespaces_栈来支持嵌套。2.3 attribute —— 声明并注册自定义属性attribute_decl attribute ident | ident ;两种写法等价带不带引号均可用于声明用户自定义属性。只有声明过的属性才被编译器接受才能在运行时通过解析 schema 查询到。tests/monster_test.fbs第 15 行attribute priority;声明后即可在字段元数据中使用它friendly:bool false (deprecated, priority: 1, id: 4);见 tests/monster_test.fbs。内置属性如deprecated、id、key等无需声明。参见 docs/source/Schemas.md 的 Attributes 小节自定义属性适合为自研代码生成器、编辑器等工具附加额外信息如帮助文本。三、类型声明table、struct、enum 与 union3.1 table 与 struct ——type_decltype_decl ( table | struct ) ident metadata { field_decl }table与struct是 FlatBuffers 的两类主要对象文法结构相同关键字 名字 可选元数据metadata即括号中的属性列表{一个或多个字段声明}。两者的语义差异详见 docs/source/Schemas.mdtable字段可选、可设默认值、可增删演进新增字段只能加在末尾或通过id属性显式编号以 vtable 方式存储支持前向/后向兼容。struct字段全部内联、不可选、无默认值、不可增删或标记废弃只能包含标量或其他 struct内存占用更小、访问更快。samples/monster.fbs中的示例struct Vec3 { x:float; y:float; z:float; } table Monster { pos:Vec3; mana:short 150; hp:short 100; name:string; ... }注意文法中field_decl表示花括号内至少一个字段。不过实践中存在空 table 的用法——tests/monster_test.fbs中的table InParentNamespace {}、table Monster {}便是空 table它们用于测试同名类型在不同命名空间的解析。源码印证Parser::ParseTypeDecl 读取table/struct关键字后调用 ParseField 循环解析字段直到遇到}structfixed true的字段偏移会在 AddField 中按InlineSize/InlineAlignment静态计算这正是 struct 布局可预测的底层保证。3.2 enum ——enum_decl枚举分支enum_decl ( enum ident : type | union ident ) metadata { commasep( enumval_decl ) }enum的写法是enum 名字 : 底层类型后跟可选 metadata再跟{逗号分隔的枚举值列表}。底层类型必须是整数类型byte、ubyte、short、ushort、int、uint、long、ulong或它们的别名int8等。tests/monster_test.fbs中的两个典型例子enum Color:ubyte (bit_flags) { Red 0, // 显式赋值 Green, // 自动递增值为 1 Blue 3, // 显式赋值可跳跃 } enum Race:byte { None -1, // 允许负数 Human 0, Dwarf, // 值为 1 Elf, // 值为 2 }枚举值的产生式为enumval_decl ident [ integer_constant ] metadata名字后可以跟 整数常量也可省略此时自动在上一个值基础上 1首值默认为 0。Color同时展示了带bit_flags元数据时值表示位的用法以及LongEnum:ulong (bit_flags)这种超出 32 位整数的位标志枚举LongBig 40表示 140。参见 docs/source/Schemas.md 的 Enums 小节枚举值通常只增不删前向兼容需由代码自行处理未知值。源码层面Parser::ParseEnum 处理is_union参数来区分enum与union并调用ParseType解析:后的底层类型。3.3 union ——enum_decl联合分支union与enum共用enum_decl产生式但:后面的类型改为引用表名union Any { Monster, TestSimpleTableWithEnum, MyGame.Example2.Monster } union AnyUniqueAliases { M: Monster, TS: TestSimpleTableWithEnum, M2: MyGame.Example2.Monster }union 的成员是表类型名可带全限定名且允许使用别名M: Monster形式——别名使同一个表可以在联合中扮演不同角色。union 隐含一个NONE哨兵值表示未存储任何值因此NONE不能用作别名。使用 union 的字段在序列化时实际占据两个字段一个_type字段保存对应枚举值和一个值字段。正因为如此union 字段必须位于 table 中不能直接作为根类型且其id属性必须取第二个字段的编号参见 docs/source/Schemas.md 的 Unions 小节以及 Attributes 小节中关于id的说明——union 的 id 需比前一个字段多 2。tests/monster_test.fbs中Monster表的用法test:Any (id: 8);这里id: 8是值字段的 id而隐式的类型字段 id 为 7若前一个字段 id 为 6。此外vector_of_enums:[Color]展示了枚举向量C 中还实验性支持[Any]形式的 union 向量。四、字段声明 field_declfield_decl ident : type [ scalar ] metadata ;字段声明的文法要点ident字段名:type字段类型[ scalar ]可选默认值仅标量类型允许有默认值scalar boolean_constant | integer_constant | float_constantmetadata可选元数据属性列表;分号结尾。samples/monster.fbs中的字段示例mana:short 150; // 带默认值 friendly:bool false (deprecated); // 带默认值 属性 inventory:[ubyte]; // 无默认值缺省时返回 0 或 null默认值与可选性语义参见 docs/source/Schemas.md 的 Default, Optional and Required Values 小节有默认值的字段数据缺失时返回 schema 中定义的默认值标量缺省默认为0可选字段标量默认值为null或非标量字段返回语言相关的nullrequired字段缺失即错误验证器verifier会将整个缓冲区判为无效。只有标量字段能设默认值string/vector/table 字段缺失时一律为null。还需注意默认值不写入序列化数据——显式写入的值恰好等于默认值时也不会占用空间参见 docs/source/Schemas.md Gotchas 相关说明因此默认值一旦发布就不应随意修改。tests/monster_test.fbs还展示了默认值的丰富形态nan、inf、inf、infinity、-infinity等特殊浮点默认值第 145–152 行以及long_enum_normal_default:LongEnum LongOne这种用枚举名做默认值的写法第 143 行。源码印证Parser::ParseField 先解析类型再判断是否存在并调用ParseSingleValue解析默认值ParseSingleValue 是值解析的核心——它接受true/false、枚举名、null、nan/inf/infinity、十进制/十六进制数以及[]空向量默认值并对非标量字段给出 type mismatch or invalid value 错误。该函数注释明确说明解析.fbsschema 时check_now必须为 true以强制校验默认值解析 JSON 时为 false 以避免重复校验。五、类型系统type 产生式type bool | byte | ubyte | short | ushort | int | uint | float | long | ulong | double | int8 | uint8 | int16 | uint16 | int32 | uint32| int64 | uint64 | float32 | float64 | string | [ type ] | ident类型分为四类标量类型bool以及 8/16/32/64 位有符号与无符号整数、单双精度浮点别名类型括号中的int8/uint8/int16/uint16/int32/uint32/int64/uint64/float32/float64与byte/ubyte/short/ushort/int/uint/long/ulong/float/double完全等价不影响代码生成参见 docs/source/Schemas.md 的 Types 小节。tests/monster_test.fbs中的table TypeAliases就是专门验证这些别名的测试表table TypeAliases { i8:int8; u8:uint8; i16:int16; u16:uint16; i32:int32; u32:uint32; i64:int64; u64:uint64; f32:float32; f64:float64; v8:[int8]; vf64:[float64]; }string只能存放 UTF-8 或 7-bit ASCII其他编码或二进制数据应使用[byte]/[ubyte]向量[ type ]任意类型的向量。文法上[type]允许嵌套写法但嵌套向量不被支持IsSeries检测到嵌套时报 nested vector types not supported (wrap in table first)见 src/idl_parser.cpp需要嵌套时用 table 包装内层向量。此外[type:n]形式的定长数组如[float:3]在文法的ParseType实现中也被支持见下文定长数组。类型还有一个重要规则一旦类型投入使用便不可更改reinterpret_cast语义上安全的大小相同类型除外如uint→int参见 docs/source/Schemas.md 的 Types 小节。源码印证Parser::ParseType 完整实现了本产生式先匹配内置关键字IsIdent(bool)、IsIdent(byte) || IsIdent(int8)……不是内置关键字则回退到ParseTypeIdent查符号表LookupStruct/LookupEnum见 src/idl_parser.cpp遇到[则递归解析子类型若:后跟整数则构造BASE_TYPE_ARRAY定长数组否则构造BASE_TYPE_VECTOR。5.1 定长数组Array虽然 Grammar.md 的type产生式只列出[type]但flatc实际还支持[type:n]定长数组——docs/source/Schemas.md的 Arrays 小节给出了等价示例// 写法 A展开为三个字段 struct Vec3 { x:float; y:float; z:float; } // 写法 B定长数组与写法 A 二进制等价 struct Vec3 { v:[float:3]; }定长数组目前只能在struct中使用。在 Parser::ParseType 中[之后的:分支要求必须紧跟整数常量否则报错 length of fixed-length array must be an integer value长度必须为正且能装入uint16_t。六、元数据metadata与属性metadata [ ( commasep( ident [ : single_value ] ) ) ] single_value scalar | string_constantmetadata 是一组可选的、逗号分隔的属性整体放在(…)中。每个属性是一个标识符可带:后的单个值标量或字符串。它可出现在table/struct名字之后如struct Vec3 (force_align: 8)enum/union名字之后如enum Color:ubyte (bit_flags)字段声明末尾如name:string (id: 3, key)枚举值之后enumval_decl ident [ integer_constant ] metadataRPC 方法之后见下节。tests/monster_test.fbs中 metadata 的密集使用struct Vec3 (force_align: 8) { ... } // 强制 8 字节对齐 id:ulong(key, hash:fnv1a_64); // key hash 属性 pos:Vec3 (id: 0); // 手动分配字段 id friendly:bool false (deprecated, priority: 1, id: 4); testnestedflatbuffer:[ubyte] (id:13, nested_flatbuffer: Monster); flex:[ubyte] (id:30, flexbuffer);常见内置属性完整说明见 docs/source/Schemas.md 的 Attributes 小节属性作用对象说明id: ntable 字段手动指定字段编号使用后所有字段都必须编号且必须是从 0 开始的连续区间union 字段 id 取第二个值字段的编号deprecatedtable 字段不再生成访问器旧数据仍可读取required非标量字段字段必须存在缺失时验证失败force_align: sizestruct / 向量强制对齐struct 场景对向量仅 C 生成代码生效bit_flags无符号枚举值表示位N代表1N不写值时依次为 1、2、4、8…nested_flatbuffer: 表名[ubyte]字段内嵌另一个 FlatBuffer生成便捷访问器flexbuffer[ubyte]字段内嵌 FlexBuffer 数据生成便捷访问器keytable 字段作为向量排序/二分查找的键hash: 算法32/64 位整数字段JSON 解析时允许字符串按fnv1_32/fnv1_64/fnv1a_32/fnv1a_64哈希后存储original_ordertable禁止按字段大小重排以优化空间native_*系列各对象支持 C Object Based API 的属性前缀enum Color:ubyte (bit_flags)中Red 0表示10 1Blue 3表示13 8——这正是bit_flags的语义见该文件第 18–25 行的注释。七、根类型与文件标识7.1 root_type —— 根类型声明root_decl root_type ident ;声明序列化数据的根表类型。这对 JSON 解析尤其重要——JSON 本身不携带类型信息需要靠 root_type 确定解析目标参见 docs/source/Schemas.md 的 Root type 小节。两个示例文件均以它收尾root_type Monster;一个 schema 通常只有一个 root_type从源码看Parser::ParseDecl 在解析root_type时更新root_struct_def_供代码生成与 JSON 解析共用。7.2 file_identifier 与 file_extensionfile_identifier_decl file_identifier string_constant ; file_extension_decl file_extension string_constant ;file_identifier给二进制缓冲区加一个 4 字符的魔数标识占用极小空间位于缓冲区偏移 4–7 字节处。声明后flatc -b生成的二进制以及FinishMonsterBuffer之类的调用会自动写入该标识读取方可用MonsterBufferHasIdentifier检查参见 docs/source/Schemas.md 的 File identification and extension 小节。标识必须恰好 4 个字符。file_extension改变flatc输出二进制文件的默认扩展名默认是.bin。tests/monster_test.fbs的结尾root_type Monster; file_identifier MONS; file_extension mon;这也是测试目录中大量文件名为monsterdata_test.mon、*.mon的原因——它们由该 schema 生成扩展名即来自file_extension mon。八、RPC 服务声明rpc_decl rpc_service ident { rpc_method } rpc_method ident ( ident ) : ident metadata ;rpc_service定义一个 RPC 服务花括号内是一个或多个方法。每个方法的文法为方法名(请求表名):响应表名 可选 metadata;。请求与响应都必须是 table 类型。tests/monster_test.fbs第 170–175 行的完整示例rpc_service MonsterStorage { Store(Monster):Stat (streaming: none); Retrieve(Stat):Monster (streaming: server, idempotent); GetMaxHitPoint(Monster):Stat (streaming: client); GetMinMaxHitPoints(Monster):Stat (streaming: bidi); }这里展示了 metadata 在 RPC 方法上的用法streaming属性指定流式模式none/server/client/bidiidempotent标记幂等。生成的代码形态取决于语言与 RPC 框架——FlatBuffers 通过--grpc代码生成器提供 gRPC 支持仓库中的 grpc/examples 与 grpc/tests 提供了 go/python/swift/ts 等语言的完整示例。源码印证Parser::ParseService 解析rpc_service其中ParseTypeIdent用于校验请求/响应类型必须是已定义的表见 src/idl_parser.cpp方法上的属性解析复用 metadata 机制。九、字面量词法整数、浮点与布尔常量Grammar.md 后半部分给出字面量的词法定义string_constant .*? ident [a-zA-Z_][a-zA-Z0-9_]* [:digit:] [0-9] [:xdigit:] [0-9a-fA-F] dec_integer_constant [-]?[:digit:] hex_integer_constant [-]?0[xX][:xdigit:] integer_constant dec_integer_constant | hex_integer_constant dec_float_constant [-]?(([.][:digit:])|([:digit:][.][:digit:]*)|([:digit:]))([eE][-]?[:digit:])? hex_float_constant [-]?0xX|([:xdigit:][.][:xdigit:]*)|([:xdigit:]))([pP][-]?[:digit:]) special_float_constant [-]?(nan|inf|infinity) float_constant dec_float_constant | hex_float_constant | special_float_constant boolean_constant true | false逐项要点string_constant与之间的任意字符.不匹配换行用于include、file_identifier、file_extension、attribute的字符串形式以及 metadata 的字符串值。ident以字母或下划线开头后续为字母、数字或下划线。注意不能以数字开头这与绝大多数编程语言一致。整数常量支持十进制[-]?[:digit:]可带正负号与十六进制[-]?0[xX][0-9a-fA-F]可带正负号。例如Red 0、LongBig 40。浮点常量三类——十进制浮点可省略整数部分.5或小数部分5.并支持科学计数法e/E指数十六进制浮点0x前缀 p/P二进制指数C99 风格特殊浮点nan、inf、infinity均允许/-前缀。tests/monster_test.fbs第 145–152 行对特殊浮点默认值做了全覆盖测试nan_default:float nan; inf_default:float inf; positive_inf_default:float inf; infinity_default:float infinity; positive_infinity_default:float infinity; negative_inf_default:float -inf; negative_infinity_default:float -infinity; double_inf_default:double inf;布尔常量仅true与false两个关键字。源码印证Parser::ParseSingleValue 中token 为/-时会把符号并入attribute_再解析标识符用于nan/inf/infinity及数学函数布尔true/false被归一化为1/0标量字段允许null表示可选浮点/整数字面量通过TryTypedValue→atot完成字符串到数值的转换并做范围检查src/idl_parser.cpp。十六进制浮点0x...p...的词法由kTokenFloatConstant扫描器按hex_float_constant规则识别。十、object 与 valueJSON 字面量object { commasep( ident : value ) } single_value scalar | string_constant value single_value | object | [ commasep( value ) ] commasep(x) [ x ( , x )* ]这三条产生式说明了 schema 语法与JSON 解析的衔接object是键值对组成的 JSON 对象value可以是标量/字符串、嵌套对象或数组commasep定义逗号分隔列表可空。FlatBuffers 的解析器本身就是强类型 JSON 解析器——它复用同一套解析框架依据 schema 把 JSON 直接解析成 FlatBuffer 二进制参见 docs/source/Schemas.md 的 JSON Parsing 小节。仓库中的实际 JSON 样例可对照阅读tests/monsterdata_test.json、tests/monsterdata_extra.json、tests/optional_scalars.json以及flatc通过-b schema.fbs data.json生成二进制参见 src/flatc.cpp或-t将二进制转回 JSON 的用法。源码印证Parser::ParseTable 与 ParseTableDelimiters 实现了object的解析按 schema 中的字段名匹配 JSON 键遇到未知字段时报错这使 FlatBuffers JSON 解析比通用 JSON 解析器更严格ParseSingleValue的check_now false分支正是为 JSON 解析路径准备的见 src/idl_parser.cpp 注释。十一、把文法落地一份最小可编译的 .fbs综合以上全部产生式samples/monster.fbs共 33 行几乎用到了 Grammar.md 中的每一条规则是速查的最佳模板// Example IDL file for our monsters schema. namespace MyGame.Sample; // namespace_decl enum Color:byte { Red 0, Green, Blue 2 } // enum_decl union Equipment { Weapon } // union 分支enum_decl struct Vec3 { // type_decl (struct) x:float; y:float; z:float; } table Monster { // type_decl (table) pos:Vec3; mana:short 150; // 字段 默认值 hp:short 100; name:string; friendly:bool false (deprecated); // metadata inventory:[ubyte]; // 向量类型 color:Color Blue; // 枚举类型 默认值 weapons:[Weapon]; // 表向量 equipped:Equipment; // union 字段 path:[Vec3]; // struct 向量 } table Weapon { name:string; damage:short; } root_type Monster; // root_decl更复杂、覆盖全部高级特性的完整示例请阅读 tests/monster_test.fbsenum 位标志、union 别名、force_align、nested_flatbuffer、flexbuffer、hash、RPC 服务、file_identifier/file_extension一应俱全。结语Grammar.md 用 74 行 BNF 精确刻画了 FlatBuffers 模式语言的完整语法从schema顶层骨架、include/namespace/attribute等文件级声明到table/struct/enum/union类型体系、字段与默认值、metadata 属性机制、root_type/file_identifier/file_extension、RPC 服务以及整数/浮点/布尔/字符串字面量的词法规则还有支撑强类型 JSON 解析的object/value产生式。这些规则在 src/idl_parser.cpp 中被逐一实现ParseType、ParseField、ParseEnum、ParseTable、ParseSingleValue等函数与文法产生式一一对应并在 tests/monster_test.fbs 与 samples/monster.fbs 中得到完整实践验证。掌握这份文法你就掌握了阅读、编写与演进任何 FlatBuffers schema 的完整语法地图。【免费下载链接】flatbuffersFlatBuffers: Memory Efficient Serialization Library项目地址: https://gitcode.com/gh_mirrors/flat/flatbuffers创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考