///|
/// Compiles one supported Kaitai Struct description to self-contained MoonBit source.
pub fn generate(source : String) -> String raise SchemaError {
let schema = load_schema(source)
let output = StringBuilder()
let id = schema.root.name
let root_type = pascal_case(id)
let root_record_type = root_type + "Record"
let records = [schema.root, ..schema.records]
let mut has_numeric_fields = false
let mut has_uint64 = false
let mut has_repeated_fields = false
let mut has_contents = false
let mut has_validation = false
for record in records {
for field in record.fields {
has_numeric_fields = has_numeric_fields ||
field.kind is Unsigned(_, _) ||
field.kind is Signed(_, _) ||
field.kind is Float32(_)
has_uint64 = has_uint64 || field.kind is Unsigned64(_)
has_repeated_fields = has_repeated_fields || field.count is Some(_)
has_contents = has_contents || field.kind is Contents(_)
has_validation = has_validation || field.validation is Some(_)
}
}
output.write_string("// Generated by KsyMoon from \{id}.ksy.\n\n")
for record in schema.records {
emit_record_type(
output,
nested_type(root_type, record.name),
record,
root_type,
)
}
emit_record_type(output, root_record_type, schema.root, root_type)
emit_reader(
output,
root_type,
has_numeric_fields~,
has_uint64~,
has_repeated_fields~,
has_contents~,
has_validation~,
)
for record in schema.records {
let name = nested_reader(id, record.name)
emit_record_reader(
output,
name,
nested_type(root_type, record.name),
record,
id,
root_type,
)
}
emit_record_reader(
output,
"read_\{id}_root",
root_record_type,
schema.root,
id,
root_type,
)
output.write_string(
"///|\npub fn parse_\{id}(input : Bytes) -> \{root_record_type} raise \{root_type}ParseError {\n",
)
let repeat_budget = if has_repeated_fields {
", repeats_left: 1_000_000"
} else {
""
}
output.write_string(
" let reader : \{root_type}Reader = { input, offset: 0\{repeat_budget} }\n",
)
output.write_string(" read_\{id}_root(reader, \"\{id}\")\n}\n")
output.to_string()
}
///|
fn pascal_case(name : String) -> String {
let output = StringBuilder()
for segment in name.split("_") {
let segment = segment.to_owned()
output.write_string(segment[0:1].to_owned().to_upper())
output.write_stringview(segment[1:])
}
output.to_string()
}
///|
fn nested_type(root_type : String, name : String) -> String {
"\{root_type}_\{name}"
}
///|
fn nested_reader(id : String, name : String) -> String {
"read_\{id}_type_\{name}"
}
///|
fn field_type(kind : FieldKind, root_type : String) -> String {
match kind {
Unsigned(_, _) => "UInt"
Unsigned64(_) => "UInt64"
Signed(_, _) => "Int"
Float32(_) => "Float"
RawBytes(_) | Contents(_) => "BytesView"
Nested(name) => nested_type(root_type, name)
}
}
///|
fn emit_record_type(
output : StringBuilder,
type_name : String,
record : RecordDef,
root_type : String,
) -> Unit {
output.write_string("///|\npub struct \{type_name} {\n")
for field in record.fields {
let element_type = field_type(field.kind, root_type)
let type_name = if field.count is Some(_) {
"Array[\{element_type}]"
} else {
element_type
}
output.write_string(" \{field.name} : \{type_name}\n")
}
output.write_string("}\n\n")
}
///|
fn length_code(expression : LengthExpr) -> String {
match expression {
Constant(value) => "\{value}L"
EarlierField(name, 0L) => "v_\{name}.to_int64()"
EarlierField(name, subtract) => "v_\{name}.to_int64() - \{subtract}L"
}
}
///|
fn endian_flag(endian : Endian) -> String {
match endian {
Little => "true"
Big => "false"
}
}
///|
fn read_code(kind : FieldKind, id : String, path : String) -> String {
match kind {
Unsigned(width, endian) =>
"reader.read_bits(\{width.to_int()}, \{endian_flag(endian)}, \{path})"
Unsigned64(endian) =>
"reader.read_uint64_bits(8, \{endian_flag(endian)}, \{path})"
Signed(width, endian) => {
let bits = "reader.read_bits(\{width.to_int()}, \{endian_flag(endian)}, \{path})"
let shift = (4 - width.to_int()) * 8
if shift == 0 {
"\{bits}.reinterpret_as_int()"
} else {
"((\{bits} << \{shift}).reinterpret_as_int() >> \{shift})"
}
}
Float32(endian) =>
"Float::reinterpret_from_uint(reader.read_bits(4, \{endian_flag(endian)}, \{path}))"
RawBytes(length) => "reader.take(\{length_code(length)}, \{path})"
Contents(bytes) => {
let values = bytes
.iter()
.map(fn(byte) { byte.to_int().to_string() })
.collect()
"reader.read_contents(Bytes::from_array([\{values.join(", ")}]), \{path})"
}
Nested(name) => "\{nested_reader(id, name)}(reader, \{path})"
}
}
///|
fn emit_record_reader(
output : StringBuilder,
reader_name : String,
type_name : String,
record : RecordDef,
id : String,
root_type : String,
) -> Unit {
output.write_string(
"///|\nfn \{reader_name}(reader : \{root_type}Reader, path : String) -> \{type_name} raise \{root_type}ParseError {\n",
)
for field in record.fields {
let name = field.name
let field_path = "path_\{name}"
output.write_string(" let \{field_path} = path + \".\{name}\"\n")
match field.count {
None => {
output.write_string(
" let v_\{name} = \{read_code(field.kind, id, field_path)}\n",
)
if field.validation is Some(check) {
output.write_string(
" reader.check_scalar(v_\{name}.to_int64(), \{check.width.to_int()}, \{check.minimum}L, \{check.maximum}L, \{field_path})\n",
)
}
}
Some(count) => {
output.write_string(
" let count_\{name} = reader.reserve_repeats(\{length_code(count)}, \{field_path})\n",
)
output.write_string(" let v_\{name} = []\n")
output.write_string(" for index in 0..