///|
/// FlatBuffers Schema Parser and MoonBit Code Generator
/// Parses .fbs schema files and generates MoonBit code.
// =============================================================================
// Schema AST Types
// =============================================================================
///|
/// Scalar types in FlatBuffers
pub enum ScalarType {
Bool
Byte
UByte
Short
UShort
Int
UInt
Long
ULong
Float
Double
}
///|
pub fn ScalarType::from_string(s : String) -> ScalarType? {
match s {
"bool" => Some(Bool)
"byte" | "int8" => Some(Byte)
"ubyte" | "uint8" => Some(UByte)
"short" | "int16" => Some(Short)
"ushort" | "uint16" => Some(UShort)
"int" | "int32" => Some(Int)
"uint" | "uint32" => Some(UInt)
"long" | "int64" => Some(Long)
"ulong" | "uint64" => Some(ULong)
"float" | "float32" => Some(Float)
"double" | "float64" => Some(Double)
_ => None
}
}
///|
pub fn ScalarType::to_moonbit_type(self : ScalarType) -> String {
match self {
Bool => "Bool"
Byte => "Int"
UByte => "Int"
Short => "Int"
UShort => "Int"
Int => "Int"
UInt => "UInt"
Long => "Int64"
ULong => "UInt64"
Float => "Float"
Double => "Double"
}
}
///|
pub fn ScalarType::to_builder_method(self : ScalarType) -> String {
match self {
Bool => "add_bool"
Byte => "add_int8"
UByte => "add_uint8"
Short => "add_int16"
UShort => "add_uint16"
Int => "add_int32"
UInt => "add_uint32"
Long => "add_int64"
ULong => "add_uint64"
Float => "add_float32"
Double => "add_float64"
}
}
///|
pub fn ScalarType::to_getter_method(self : ScalarType) -> String {
match self {
Bool => "get_bool"
Byte => "get_int8"
UByte => "get_uint8"
Short => "get_int16"
UShort => "get_uint16"
Int => "get_int32"
UInt => "get_uint32"
Long => "get_int64"
ULong => "get_uint64"
Float => "get_float32"
Double => "get_float64"
}
}
///|
pub fn ScalarType::default_value(self : ScalarType) -> String {
match self {
Bool => "false"
Float | Double => "0.0"
_ => "0"
}
}
///|
/// Field type in schema
pub enum FieldTypeAst {
Scalar(ScalarType)
String
Vector(FieldTypeAst)
Reference(String) // Reference to another type (enum, struct, table)
}
///|
/// Field definition in schema
pub struct FieldAst {
name : String
field_type : FieldTypeAst
default_value : String?
deprecated : Bool
id : Int? // explicit field id
}
///|
/// Enum value definition
pub struct EnumValueAst {
name : String
value : Int?
}
///|
/// Enum definition in schema
pub struct EnumAst {
name : String
base_type : ScalarType
bit_flags : Bool
values : Array[EnumValueAst]
}
///|
/// Struct definition in schema (fixed-size)
pub struct StructAst {
name : String
fields : Array[FieldAst]
}
///|
/// Table definition in schema (variable-size)
pub struct TableAst {
name : String
fields : Array[FieldAst]
}
///|
/// Union definition in schema
pub struct UnionAst {
name : String
types : Array[String]
}
///|
/// Schema definition
pub struct SchemaAst {
mut ns : String // namespace (reserved word)
enums : Array[EnumAst]
structs : Array[StructAst]
tables : Array[TableAst]
unions : Array[UnionAst]
mut root_type : String?
}
///|
pub fn SchemaAst::new() -> SchemaAst {
{ ns: "", enums: [], structs: [], tables: [], unions: [], root_type: None }
}
// =============================================================================
// Simple FBS Parser
// =============================================================================
///|
/// Get char from string at index
fn string_char_at(s : String, i : Int) -> Char {
s[i].to_int().unsafe_to_char()
}
///|
/// Parser state
priv struct Parser {
input : String
mut pos : Int
mut line : Int
mut col : Int
}
///|
fn Parser::new(input : String) -> Parser {
{ input, pos: 0, line: 1, col: 1 }
}
///|
fn Parser::peek(self : Parser) -> Char? {
if self.pos < self.input.length() {
Some(string_char_at(self.input, self.pos))
} else {
None
}
}
///|
fn Parser::advance(self : Parser) -> Char? {
if self.pos < self.input.length() {
let c = string_char_at(self.input, self.pos)
self.pos = self.pos + 1
if c == '\n' {
self.line = self.line + 1
self.col = 1
} else {
self.col = self.col + 1
}
Some(c)
} else {
None
}
}
///|
fn Parser::skip_whitespace(self : Parser) -> Unit {
while true {
match self.peek() {
Some(' ') | Some('\t') | Some('\n') | Some('\r') => ignore(self.advance())
Some('/') =>
// Check for comments
if self.pos + 1 < self.input.length() &&
string_char_at(self.input, self.pos + 1) == '/' {
// Line comment
while true {
match self.peek() {
Some('\n') | None => break
_ => ignore(self.advance())
}
}
} else if self.pos + 1 < self.input.length() &&
string_char_at(self.input, self.pos + 1) == '*' {
// Block comment
ignore(self.advance()) // /
ignore(self.advance()) // *
while true {
match self.peek() {
None => break
Some('*') => {
ignore(self.advance())
match self.peek() {
Some('/') => {
ignore(self.advance())
break
}
_ => ()
}
}
_ => ignore(self.advance())
}
}
} else {
break
}
_ => break
}
}
}
///|
fn Parser::parse_identifier(self : Parser) -> String {
self.skip_whitespace()
let result : Array[Char] = []
while true {
match self.peek() {
Some(c) =>
if is_identifier_char(c) {
result.push(c)
ignore(self.advance())
} else {
break
}
None => break
}
}
String::from_array(result)
}
///|
fn is_identifier_char(c : Char) -> Bool {
(c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') ||
c == '_' ||
c == '.'
}
///|
fn Parser::expect(self : Parser, expected : Char) -> Bool {
self.skip_whitespace()
match self.peek() {
Some(c) if c == expected => {
ignore(self.advance())
true
}
_ => false
}
}
///|
fn Parser::parse_number(self : Parser) -> Int {
self.skip_whitespace()
let result : Array[Char] = []
let mut negative = false
match self.peek() {
Some('-') => {
negative = true
ignore(self.advance())
}
_ => ()
}
while true {
match self.peek() {
Some(c) if c >= '0' && c <= '9' => {
result.push(c)
ignore(self.advance())
}
_ => break
}
}
let num = if result.is_empty() { 0 } else { parse_int_from_chars(result) }
if negative {
-num
} else {
num
}
}
///|
fn parse_int_from_chars(chars : Array[Char]) -> Int {
let mut result = 0
for c in chars {
result = result * 10 + (c.to_int() - '0'.to_int())
}
result
}
///|
fn Parser::parse_string_literal(self : Parser) -> String {
self.skip_whitespace()
if not(self.expect('\u{22}')) {
return ""
}
let result : Array[Char] = []
while true {
match self.peek() {
Some('\u{22}') => {
ignore(self.advance())
break
}
Some('\\') => {
ignore(self.advance())
match self.peek() {
Some('n') => {
result.push('\n')
ignore(self.advance())
}
Some('t') => {
result.push('\t')
ignore(self.advance())
}
Some(c) => {
result.push(c)
ignore(self.advance())
}
None => break
}
}
Some(c) => {
result.push(c)
ignore(self.advance())
}
None => break
}
}
String::from_array(result)
}
///|
fn Parser::parse_field_type(self : Parser) -> FieldTypeAst {
self.skip_whitespace()
// Check for vector type [type]
if self.expect('[') {
let inner = self.parse_field_type()
ignore(self.expect(']'))
return Vector(inner)
}
let type_name = self.parse_identifier()
match type_name {
"string" => String
_ =>
match ScalarType::from_string(type_name) {
Some(scalar) => Scalar(scalar)
None => Reference(type_name)
}
}
}
///|
fn Parser::skip_attributes(self : Parser) -> Unit {
self.skip_whitespace()
if self.expect('(') {
let mut depth = 1
while depth > 0 {
match self.peek() {
Some('(') => {
depth = depth + 1
ignore(self.advance())
}
Some(')') => {
depth = depth - 1
ignore(self.advance())
}
Some(_) => ignore(self.advance())
None => break
}
}
}
}
///|
fn Parser::parse_field(self : Parser) -> FieldAst? {
self.skip_whitespace()
let name = self.parse_identifier()
if name.is_empty() {
return None
}
if not(self.expect(':')) {
return None
}
let field_type = self.parse_field_type()
self.skip_whitespace()
// Check for default value
let default_value = if self.expect('=') {
self.skip_whitespace()
// Could be a number, identifier, or string
match self.peek() {
Some(c) if (c >= '0' && c <= '9') || c == '-' =>
Some(self.parse_number().to_string())
Some('\u{22}') => Some(self.parse_string_literal())
_ => {
let id = self.parse_identifier()
if id.is_empty() {
None
} else {
Some(id)
}
}
}
} else {
None
}
// Skip attributes like (deprecated, id: 1)
self.skip_attributes()
ignore(self.expect(';'))
Some({ name, field_type, default_value, deprecated: false, id: None })
}
///|
fn Parser::parse_enum(self : Parser) -> EnumAst? {
let name = self.parse_identifier()
if name.is_empty() {
return None
}
self.skip_whitespace()
// Parse base type
let base_type = if self.expect(':') {
let type_name = self.parse_identifier()
match ScalarType::from_string(type_name) {
Some(t) => t
None => UByte
}
} else {
Int
}
// Skip attributes
self.skip_attributes()
if not(self.expect('{')) {
return None
}
// Parse values
let values : Array[EnumValueAst] = []
while true {
self.skip_whitespace()
if self.expect('}') {
break
}
let value_name = self.parse_identifier()
if value_name.is_empty() {
break
}
let value = if self.expect('=') { Some(self.parse_number()) } else { None }
values.push({ name: value_name, value })
ignore(self.expect(','))
}
Some({ name, base_type, bit_flags: false, values })
}
///|
fn Parser::parse_struct(self : Parser) -> StructAst? {
let name = self.parse_identifier()
if name.is_empty() {
return None
}
self.skip_attributes()
if not(self.expect('{')) {
return None
}
let fields : Array[FieldAst] = []
while true {
self.skip_whitespace()
if self.expect('}') {
break
}
match self.parse_field() {
Some(field) => fields.push(field)
None => break
}
}
Some({ name, fields })
}
///|
fn Parser::parse_table(self : Parser) -> TableAst? {
let name = self.parse_identifier()
if name.is_empty() {
return None
}
self.skip_attributes()
if not(self.expect('{')) {
return None
}
let fields : Array[FieldAst] = []
while true {
self.skip_whitespace()
if self.expect('}') {
break
}
match self.parse_field() {
Some(field) => fields.push(field)
None => break
}
}
Some({ name, fields })
}
///|
fn Parser::parse_union(self : Parser) -> UnionAst? {
let name = self.parse_identifier()
if name.is_empty() {
return None
}
if not(self.expect('{')) {
return None
}
let types : Array[String] = []
while true {
self.skip_whitespace()
if self.expect('}') {
break
}
let type_name = self.parse_identifier()
if type_name.is_empty() {
break
}
// Skip alias (e.g., M: Monster -> Monster)
if self.expect(':') {
let actual = self.parse_identifier()
types.push(actual)
} else {
types.push(type_name)
}
ignore(self.expect(','))
}
Some({ name, types })
}
///|
/// Parse a FlatBuffers schema
pub fn parse_schema(input : String) -> SchemaAst {
let parser = Parser::new(input)
let schema = SchemaAst::new()
while true {
parser.skip_whitespace()
if parser.pos >= parser.input.length() {
break
}
let keyword = parser.parse_identifier()
match keyword {
"namespace" => {
let ns = parser.parse_identifier()
schema.ns = ns
ignore(parser.expect(';'))
}
"enum" =>
match parser.parse_enum() {
Some(e) => schema.enums.push(e)
None => ()
}
"struct" =>
match parser.parse_struct() {
Some(s) => schema.structs.push(s)
None => ()
}
"table" =>
match parser.parse_table() {
Some(t) => schema.tables.push(t)
None => ()
}
"union" =>
match parser.parse_union() {
Some(u) => schema.unions.push(u)
None => ()
}
"root_type" => {
let rt = parser.parse_identifier()
schema.root_type = Some(rt)
ignore(parser.expect(';'))
}
"include" | "attribute" | "file_identifier" | "file_extension" =>
// Skip these directives
while true {
match parser.peek() {
Some(';') => {
ignore(parser.advance())
break
}
Some(_) => ignore(parser.advance())
None => break
}
}
"" => break
_ =>
// Skip unknown tokens
()
}
}
schema
}
// =============================================================================
// Code Generator
// =============================================================================
///|
/// Generate MoonBit code from schema
pub fn generate_moonbit(schema : SchemaAst) -> String {
let code : Array[String] = []
// Header
code.push("///|")
code.push("/// Auto-generated FlatBuffers code")
code.push("/// DO NOT EDIT")
code.push("")
// Generate enums
for enum_def in schema.enums {
code.push(generate_enum(enum_def))
code.push("")
}
// Generate structs
for struct_def in schema.structs {
code.push(generate_struct(struct_def))
code.push("")
}
// Generate unions
for union_def in schema.unions {
code.push(generate_union(union_def))
code.push("")
}
// Generate tables
for table_def in schema.tables {
code.push(generate_table(table_def, schema))
code.push("")
}
code.join("\n")
}
///|
fn generate_enum(enum_def : EnumAst) -> String {
let lines : Array[String] = []
lines.push("///|")
lines.push("pub enum " + enum_def.name + " {")
let mut next_value = 0
for v in enum_def.values {
let value = match v.value {
Some(n) => {
next_value = n + 1
n
}
None => {
let n = next_value
next_value = next_value + 1
n
}
}
lines.push(" " + v.name + " // = " + value.to_string())
}
lines.push("}")
lines.push("")
// Add to_int method
lines.push("///|")
lines.push(
"pub fn " +
enum_def.name +
"::to_int(self : " +
enum_def.name +
") -> Int {",
)
lines.push(" match self {")
next_value = 0
for v in enum_def.values {
let value = match v.value {
Some(n) => {
next_value = n + 1
n
}
None => {
let n = next_value
next_value = next_value + 1
n
}
}
lines.push(" " + v.name + " => " + value.to_string())
}
lines.push(" }")
lines.push("}")
lines.push("")
// Add from_int method
lines.push("///|")
lines.push(
"pub fn " +
enum_def.name +
"::from_int(value : Int) -> " +
enum_def.name +
"? {",
)
lines.push(" match value {")
next_value = 0
for v in enum_def.values {
let value = match v.value {
Some(n) => {
next_value = n + 1
n
}
None => {
let n = next_value
next_value = next_value + 1
n
}
}
lines.push(" " + value.to_string() + " => Some(" + v.name + ")")
}
lines.push(" _ => None")
lines.push(" }")
lines.push("}")
lines.join("\n")
}
///|
/// Calculate struct size in bytes
fn calc_struct_size(struct_def : StructAst) -> Int {
let mut size = 0
for field in struct_def.fields {
match field.field_type {
Scalar(scalar_type) =>
match scalar_type {
Bool | Byte | UByte => size = size + 1
Short | UShort => size = size + 2
Int | UInt | Float => size = size + 4
Long | ULong | Double => size = size + 8
}
_ => size = size + 4 // Assume 4 bytes for references
}
}
size
}
///|
/// Generate struct code (fixed-size inline data)
fn generate_struct(struct_def : StructAst) -> String {
let lines : Array[String] = []
let name = struct_def.name
let size = calc_struct_size(struct_def)
// Generate struct reader
lines.push("///|")
lines.push(
"/// " + name + " struct reader (size: " + size.to_string() + " bytes)",
)
lines.push("pub struct " + name + " {")
lines.push(" buf : @flatbuffers.ByteBuffer")
lines.push(" pos : Int")
lines.push("}")
lines.push("")
// Generate constructor
lines.push("///|")
lines.push(
"pub fn " +
name +
"::new(buf : @flatbuffers.ByteBuffer, pos : Int) -> " +
name +
" {",
)
lines.push(" { buf, pos }")
lines.push("}")
lines.push("")
// Generate field getters
let mut offset = 0
for field in struct_def.fields {
let field_size = match field.field_type {
Scalar(scalar_type) =>
match scalar_type {
Bool | Byte | UByte => 1
Short | UShort => 2
Int | UInt | Float => 4
Long | ULong | Double => 8
}
_ => 4
}
match field.field_type {
Scalar(scalar_type) => {
let moonbit_type = scalar_type.to_moonbit_type()
let read_method = match scalar_type {
Bool => "read_bool"
Byte => "read_int8"
UByte => "read_uint8"
Short => "read_int16"
UShort => "read_uint16"
Int => "read_int32"
UInt => "read_uint32"
Long => "read_int64"
ULong => "read_uint64"
Float => "read_float32"
Double => "read_float64"
}
lines.push("///|")
lines.push(
"pub fn " +
name +
"::" +
field.name +
"(self : " +
name +
") -> " +
moonbit_type +
" {",
)
lines.push(
" self.buf." +
read_method +
"(self.pos + " +
offset.to_string() +
")",
)
lines.push("}")
lines.push("")
}
_ => ()
}
offset = offset + field_size
}
// Generate struct builder
lines.push("///|")
lines.push("/// " + name + " struct builder")
lines.push("pub struct " + name + "Builder {")
lines.push(" builder : @flatbuffers.Builder")
lines.push("}")
lines.push("")
lines.push("///|")
lines.push(
"pub fn " +
name +
"Builder::new(builder : @flatbuffers.Builder) -> " +
name +
"Builder {",
)
lines.push(" { builder }")
lines.push("}")
lines.push("")
// Generate create method that takes all fields
lines.push("///|")
let params : Array[String] = []
for field in struct_def.fields {
match field.field_type {
Scalar(scalar_type) => {
let moonbit_type = scalar_type.to_moonbit_type()
params.push(field.name + " : " + moonbit_type)
}
_ => ()
}
}
lines.push(
"pub fn " +
name +
"Builder::create(self : " +
name +
"Builder, " +
params.join(", ") +
") -> Int {",
)
lines.push(" self.builder.prep(" + size.to_string() + ", 0)")
// Write fields in reverse order (FlatBuffers are built backwards)
let mut rev_offset = size
let fields_rev = struct_def.fields.copy()
fields_rev.rev_in_place()
for field in fields_rev {
match field.field_type {
Scalar(scalar_type) => {
let field_size = match scalar_type {
Bool | Byte | UByte => 1
Short | UShort => 2
Int | UInt | Float => 4
Long | ULong | Double => 8
}
rev_offset = rev_offset - field_size
let write_method = match scalar_type {
Bool => "place_bool"
Byte => "place_int8"
UByte => "place_uint8"
Short => "place_int16"
UShort => "place_uint16"
Int => "place_int32"
UInt => "place_uint32"
Long => "place_int64"
ULong => "place_uint64"
Float => "place_float32"
Double => "place_float64"
}
lines.push(" self.builder." + write_method + "(" + field.name + ")")
}
_ => ()
}
}
lines.push(" self.builder.offset()")
lines.push("}")
lines.join("\n")
}
///|
/// Generate union code
fn generate_union(union_def : UnionAst) -> String {
let lines : Array[String] = []
let name = union_def.name
// Generate union type enum
lines.push("///|")
lines.push("/// " + name + " union type")
lines.push("pub enum " + name + "Type {")
lines.push(" None // = 0")
for i, type_name in union_def.types {
lines.push(" " + type_name + " // = " + (i + 1).to_string())
}
lines.push("}")
lines.push("")
// Generate to_int method
lines.push("///|")
lines.push(
"pub fn " + name + "Type::to_int(self : " + name + "Type) -> Int {",
)
lines.push(" match self {")
lines.push(" None => 0")
for i, type_name in union_def.types {
lines.push(" " + type_name + " => " + (i + 1).to_string())
}
lines.push(" }")
lines.push("}")
lines.push("")
// Generate from_int method
lines.push("///|")
lines.push(
"pub fn " + name + "Type::from_int(value : Int) -> " + name + "Type {",
)
lines.push(" match value {")
for i, type_name in union_def.types {
lines.push(" " + (i + 1).to_string() + " => " + type_name)
}
lines.push(" _ => None")
lines.push(" }")
lines.push("}")
lines.push("")
// Generate union reader struct
lines.push("///|")
lines.push("/// " + name + " union reader")
lines.push("pub struct " + name + " {")
lines.push(" union_type : " + name + "Type")
lines.push(" table : @flatbuffers.Table?")
lines.push("}")
lines.push("")
lines.push("///|")
lines.push(
"pub fn " +
name +
"::new(union_type : " +
name +
"Type, table : @flatbuffers.Table?) -> " +
name +
" {",
)
lines.push(" { union_type, table }")
lines.push("}")
lines.push("")
lines.push("///|")
lines.push(
"pub fn " + name + "::get_type(self : " + name + ") -> " + name + "Type {",
)
lines.push(" self.union_type")
lines.push("}")
lines.push("")
lines.push("///|")
lines.push(
"pub fn " +
name +
"::get_table(self : " +
name +
") -> @flatbuffers.Table? {",
)
lines.push(" self.table")
lines.push("}")
lines.join("\n")
}
///|
fn generate_table(table_def : TableAst, schema : SchemaAst) -> String {
let lines : Array[String] = []
let name = table_def.name
// Generate reader struct
lines.push("///|")
lines.push("/// " + name + " reader")
lines.push("pub struct " + name + " {")
lines.push(" table : @flatbuffers.Table")
lines.push("}")
lines.push("")
// Generate constructor from buffer
lines.push("///|")
lines.push(
"pub fn " +
name +
"::from_buffer(buf : @flatbuffers.ByteBuffer) -> " +
name +
" {",
)
lines.push(" { table: @flatbuffers.Table::get_root(buf) }")
lines.push("}")
lines.push("")
// Generate getters for each field
for i, field in table_def.fields {
let slot = i
let getter = generate_field_getter(name, field, slot, schema)
lines.push(getter)
lines.push("")
}
// Generate builder struct
lines.push("///|")
lines.push("/// " + name + " builder")
lines.push("pub struct " + name + "Builder {")
lines.push(" builder : @flatbuffers.Builder")
lines.push(" string_offsets : Map[String, Int]")
lines.push(" vector_offsets : Map[String, Int]")
lines.push("}")
lines.push("")
// Generate builder constructor
lines.push("///|")
lines.push(
"pub fn " +
name +
"Builder::new(builder : @flatbuffers.Builder) -> " +
name +
"Builder {",
)
lines.push(" { builder, string_offsets: {}, vector_offsets: {} }")
lines.push("}")
lines.push("")
// Generate setters for offset types (must be called before start)
for field in table_def.fields {
match field.field_type {
String => {
lines.push("///|")
lines.push(
"pub fn " +
name +
"Builder::set_" +
field.name +
"(self : " +
name +
"Builder, value : String) -> " +
name +
"Builder {",
)
lines.push(" let offset = self.builder.create_string(value)")
lines.push(" self.string_offsets.set(\"" + field.name + "\", offset)")
lines.push(" self")
lines.push("}")
lines.push("")
}
Vector(_) => {
lines.push("///|")
lines.push(
"pub fn " +
name +
"Builder::set_" +
field.name +
"_offset(self : " +
name +
"Builder, offset : Int) -> " +
name +
"Builder {",
)
lines.push(" self.vector_offsets.set(\"" + field.name + "\", offset)")
lines.push(" self")
lines.push("}")
lines.push("")
}
_ => ()
}
}
// Generate start method
lines.push("///|")
lines.push(
"pub fn " + name + "Builder::start(self : " + name + "Builder) -> Unit {",
)
lines.push(
" self.builder.start_object(" + table_def.fields.length().to_string() + ")",
)
lines.push("}")
lines.push("")
// Generate add methods for scalar fields
for i, field in table_def.fields {
let slot = i
match field.field_type {
Scalar(scalar_type) => {
let moonbit_type = scalar_type.to_moonbit_type()
let builder_method = scalar_type.to_builder_method()
let default = match field.default_value {
Some(d) => d
None => scalar_type.default_value()
}
lines.push("///|")
lines.push(
"pub fn " +
name +
"Builder::add_" +
field.name +
"(self : " +
name +
"Builder, value : " +
moonbit_type +
") -> " +
name +
"Builder {",
)
lines.push(
" self.builder." +
builder_method +
"(" +
slot.to_string() +
", value, " +
default +
")",
)
lines.push(" self")
lines.push("}")
lines.push("")
}
_ => ()
}
}
// Generate finish_fields method
lines.push("///|")
lines.push(
"pub fn " +
name +
"Builder::finish_fields(self : " +
name +
"Builder) -> Unit {",
)
for i, field in table_def.fields {
let slot = i
match field.field_type {
String => {
lines.push(" match self.string_offsets.get(\"" + field.name + "\") {")
lines.push(
" Some(offset) => self.builder.add_offset(" +
slot.to_string() +
", offset)",
)
lines.push(" None => ()")
lines.push(" }")
}
Vector(_) => {
lines.push(" match self.vector_offsets.get(\"" + field.name + "\") {")
lines.push(
" Some(offset) => self.builder.add_offset(" +
slot.to_string() +
", offset)",
)
lines.push(" None => ()")
lines.push(" }")
}
_ => ()
}
}
lines.push("}")
lines.push("")
// Generate end method
lines.push("///|")
lines.push(
"pub fn " + name + "Builder::end(self : " + name + "Builder) -> Int {",
)
lines.push(" self.finish_fields()")
lines.push(" self.builder.end_object()")
lines.push("}")
lines.push("")
// Generate finish method
lines.push("///|")
lines.push(
"pub fn " + name + "Builder::finish(self : " + name + "Builder) -> Unit {",
)
lines.push(" let offset = self.end()")
lines.push(" self.builder.finish(offset)")
lines.push("}")
lines.join("\n")
}
///|
/// Resolve enum default value name to integer
fn resolve_enum_default(
schema : SchemaAst,
enum_name : String,
default_name : String,
) -> String {
// First check if it's already a number
let is_numeric = {
let mut all_digits = true
for c in default_name {
if not((c >= '0' && c <= '9') || c == '-') {
all_digits = false
break
}
}
all_digits && not(default_name.is_empty())
}
if is_numeric {
return default_name
}
// Look up the enum value by name
for e in schema.enums {
if e.name == enum_name {
let mut next_value = 0
for v in e.values {
let value = match v.value {
Some(n) => {
next_value = n + 1
n
}
None => {
let n = next_value
next_value = next_value + 1
n
}
}
if v.name == default_name {
return value.to_string()
}
}
}
}
"0"
}
///|
fn is_union_type(schema : SchemaAst, type_name : String) -> Bool {
for u in schema.unions {
if u.name == type_name {
return true
}
}
false
}
///|
fn is_struct_type(schema : SchemaAst, type_name : String) -> Bool {
for s in schema.structs {
if s.name == type_name {
return true
}
}
false
}
///|
fn is_enum_type(schema : SchemaAst, type_name : String) -> Bool {
for e in schema.enums {
if e.name == type_name {
return true
}
}
false
}
///|
fn generate_field_getter(
type_name : String,
field : FieldAst,
slot : Int,
schema : SchemaAst,
) -> String {
let lines : Array[String] = []
match field.field_type {
Scalar(scalar_type) => {
let moonbit_type = scalar_type.to_moonbit_type()
let getter_method = scalar_type.to_getter_method()
let default = match field.default_value {
Some(d) => d
None => scalar_type.default_value()
}
lines.push("///|")
lines.push(
"pub fn " +
type_name +
"::" +
field.name +
"(self : " +
type_name +
") -> " +
moonbit_type +
" {",
)
lines.push(
" self.table." +
getter_method +
"(" +
slot.to_string() +
", " +
default +
")",
)
lines.push("}")
}
String => {
lines.push("///|")
lines.push(
"pub fn " +
type_name +
"::" +
field.name +
"(self : " +
type_name +
") -> String {",
)
lines.push(" self.table.get_string(" + slot.to_string() + ", \"\")")
lines.push("}")
}
Vector(elem_type) => {
lines.push("///|")
lines.push(
"pub fn " +
type_name +
"::" +
field.name +
"_length(self : " +
type_name +
") -> Int {",
)
lines.push(" self.table.get_vector_length(" + slot.to_string() + ")")
lines.push("}")
match elem_type {
Scalar(UByte) => {
lines.push("")
lines.push("///|")
lines.push(
"pub fn " +
type_name +
"::" +
field.name +
"(self : " +
type_name +
", index : Int) -> Byte {",
)
lines.push(
" self.table.get_vector_byte(" + slot.to_string() + ", index)",
)
lines.push("}")
}
Scalar(Int) => {
lines.push("")
lines.push("///|")
lines.push(
"pub fn " +
type_name +
"::" +
field.name +
"(self : " +
type_name +
", index : Int) -> Int {",
)
lines.push(
" self.table.get_vector_int32(" + slot.to_string() + ", index)",
)
lines.push("}")
}
Reference(ref_name) => {
// Vector of tables
lines.push("")
lines.push("///|")
lines.push(
"pub fn " +
type_name +
"::" +
field.name +
"_table(self : " +
type_name +
", index : Int) -> @flatbuffers.Table? {",
)
lines.push(
" self.table.get_vector_table(" + slot.to_string() + ", index)",
)
lines.push("}")
ignore(ref_name)
}
_ => ()
}
}
Reference(ref_type) =>
// Check if it's a union, struct, enum, or table
if is_union_type(schema, ref_type) {
// Union: generates both type getter and value getter
// Union fields actually use TWO slots: type_slot and value_slot
// The type is stored at slot, the value at slot+1 (or we use slot-1 for type)
// In FlatBuffers convention, union type is at field index N, union value at N+1
lines.push("///|")
lines.push("/// Get " + field.name + " union type")
lines.push(
"pub fn " +
type_name +
"::" +
field.name +
"_type(self : " +
type_name +
") -> " +
ref_type +
"Type {",
)
lines.push(
" " +
ref_type +
"Type::from_int(self.table.get_uint8(" +
slot.to_string() +
", 0))",
)
lines.push("}")
lines.push("")
lines.push("///|")
lines.push("/// Get " + field.name + " union value as table")
lines.push(
"pub fn " +
type_name +
"::" +
field.name +
"(self : " +
type_name +
") -> " +
ref_type +
" {",
)
// Note: the actual value slot is typically the next slot
let value_slot = slot + 1
lines.push(" let union_type = self." + field.name + "_type()")
lines.push(
" let table = self.table.get_table(" + value_slot.to_string() + ")",
)
lines.push(" " + ref_type + "::new(union_type, table)")
lines.push("}")
} else if is_struct_type(schema, ref_type) {
// Struct: read inline data
lines.push("///|")
lines.push(
"pub fn " +
type_name +
"::" +
field.name +
"(self : " +
type_name +
") -> " +
ref_type +
"? {",
)
lines.push(" match self.table.get_struct(" + slot.to_string() + ") {")
lines.push(
" Some(pos) => Some(" + ref_type + "::new(self.table.buf, pos))",
)
lines.push(" None => None")
lines.push(" }")
lines.push("}")
} else if is_enum_type(schema, ref_type) {
// Enum: read as integer and convert
lines.push("///|")
lines.push(
"pub fn " +
type_name +
"::" +
field.name +
"(self : " +
type_name +
") -> " +
ref_type +
"? {",
)
// Resolve enum default value to integer
let default = match field.default_value {
Some(d) => resolve_enum_default(schema, ref_type, d)
None => "0"
}
lines.push(
" " +
ref_type +
"::from_int(self.table.get_uint8(" +
slot.to_string() +
", " +
default +
"))",
)
lines.push("}")
} else {
// Regular table reference
lines.push("///|")
lines.push(
"pub fn " +
type_name +
"::" +
field.name +
"(self : " +
type_name +
") -> @flatbuffers.Table? {",
)
lines.push(" self.table.get_table(" + slot.to_string() + ")")
lines.push("}")
}
}
lines.join("\n")
}