///|
priv struct ValueField {
name : String
c_type : String
moon_type : String
ffi_c_type : String
abi_kind : String
}
///|
priv struct ValueRecord {
name : String
fields : Array[ValueField]
reason : String
}
///|
fn value_field(
node : Json,
aliases : Map[String, String],
enums : Map[String, Bool],
) -> ValueField? {
guard text_field(node, "name") is Some(name) &&
type_spelling(node) is Some(c_type) else {
return None
}
if field(node, "isBitfield") == Some(Json::boolean(true)) ||
!valid_c_identifier(name) ||
moonbit_value_name(name) != name {
return None
}
let canonical = canonical_type_spelling(node).unwrap_or(c_type)
let shape = c_type_model(canonical)
if shape.pointer_depth != 0 ||
shape.array ||
shape.function_pointer ||
shape.record ||
c_type.contains("[") ||
canonical.contains("[") {
return None
}
let kind = v2_abi_kind(c_type, canonical)
let moon_type = if kind != "" {
v2_abi_moon_type(kind)
} else {
match resolve_type(canonical, aliases, Map([]), enums, result=false) {
Some(value) if value != "Unit" && !value.has_prefix("C_") => value
_ => return None
}
}
let ffi_c_type = if kind != "" {
v2_abi_c_type(kind)
} else {
match moon_type {
"Byte" => "uint8_t"
"Int" => "int32_t"
"UInt" => "uint32_t"
"Int64" => "int64_t"
"UInt64" => "uint64_t"
"Float" => "float"
"Double" => "double"
_ => return None
}
}
Some({ name, c_type, moon_type, ffi_c_type, abi_kind: kind, })
}
///|
fn value_records(
nodes : Array[LocatedNode],
aliases : Map[String, String],
enums : Map[String, Bool],
) -> Map[String, ValueRecord] {
let records : Map[String, ValueRecord] = Map([])
for entry in nodes {
let node = entry.node
if text_field(node, "kind") != Some("RecordDecl") ||
text_field(node, "tagUsed") != Some("struct") ||
field(node, "completeDefinition") != Some(Json::boolean(true)) {
continue
}
guard text_field(node, "name") is Some(name) && valid_c_identifier(name) else {
continue
}
let fields : Array[ValueField] = []
let mut reason = ""
for child in array_field(node, "inner") {
if text_field(child, "kind") == Some("FieldDecl") {
match value_field(child, aliases, enums) {
Some(value) => fields.push(value)
None => reason = "non-scalar, bit-field, array, or anonymous field"
}
}
}
if fields.length() == 0 {
reason = "record has no scalar fields"
}
records[name] = { name, fields, reason, }
}
records
}
///|
fn value_record_for(
raw : String,
aliases : Map[String, String],
records : Map[String, ValueRecord],
) -> ValueRecord? {
let mut source = normalized_type(raw)
for _ in 0..<16 {
if source.has_prefix("struct ") {
return records.get(source[7:].to_owned())
}
match records.get(source) {
Some(record) => return Some(record)
None => ()
}
match aliases.get(source) {
Some(next) => source = normalized_type(next)
None => return None
}
}
None
}
///|
fn uses_v2_value_record(
function : CFunction,
config : GenerateConfig,
aliases : Map[String, String],
records : Map[String, ValueRecord],
) -> Bool {
if config.schema_version != 2 {
return false
}
if value_record_for(function.return_type, aliases, records) is Some(_) {
return true
}
for parameter in function.parameters {
if value_record_for(parameter.c_type, aliases, records) is Some(_) {
return true
}
}
false
}
///|
fn value_record_decl(record : ValueRecord) -> String {
let fields : Array[String] = []
for field in record.fields {
fields.push(" " + field.name + " : " + field.moon_type)
}
"///|\npub(all) struct MbgValue_" +
record.name +
" {\n" +
fields.join("\n") +
"\n}\n"
}
///|
fn value_field_size_check(
c_record : String,
field : ValueField,
suffix : String,
) -> String {
if field.abi_kind != "" {
return ""
}
" typedef char mbg_check_" +
suffix +
"_" +
field.name +
"[(sizeof(( (" +
c_record +
" *)0)->" +
field.name +
") == sizeof(" +
field.ffi_c_type +
")) ? 1 : -1];"
}
///|
fn render_v2_value_function(
function : CFunction,
aliases : Map[String, String],
opaque_types : Map[String, Bool],
enums : Map[String, Bool],
records : Map[String, ValueRecord],
config : GenerateConfig,
used : Map[String, Bool],
) -> (String?, String?, String) {
if function.c_signature.contains("...") {
return (None, None, "variadic function")
}
if config.buffer_for(function.name) is Some(_) ||
config.return_buffer_for(function.name) is Some(_) ||
config.multi_output_for(function.name) is Some(_) ||
config.resource_create_for(function.name) is Some(_) ||
config.resource_view_for(function.name) is Some(_) {
return (None, None, "value record conflicts with another bridge policy")
}
let return_record = value_record_for(function.return_type, aliases, records)
let return_kind = v2_abi_kind(
function.return_type,
function.canonical_return_type,
)
let moon_return = match return_record {
Some(record) => {
if record.reason != "" {
return (
None,
None,
"unsupported value record " + record.name + ": " + record.reason,
)
}
"MbgValue_" + record.name
}
None =>
if return_kind != "" {
v2_abi_moon_type(return_kind)
} else {
match
resolve_configured_type(
function.return_type,
function.canonical_return_type,
aliases,
opaque_types,
enums,
config,
utf8_input=false,
result=true,
) {
Some(value) => value.moon_type
None =>
return (
None,
None,
"unsupported return type: " + function.return_type,
)
}
}
}
let bridge = "mbg_value_" + function.moon_name
let raw_type = "MbgRawValue_" + function.moon_name
let moon_params : Array[String] = []
let ffi_params : Array[String] = []
let ffi_args : Array[String] = []
let c_params : Array[String] = []
let c_args : Array[String] = []
let local_lines : Array[String] = []
let abi_checks : Array[String] = []
let guards : Array[String] = []
let borrowed : Array[String] = []
let pending_used : Array[String] = []
for index, parameter in function.parameters {
let arg = "arg" + index.to_string()
let c_arg = "p" + index.to_string()
match value_record_for(parameter.c_type, aliases, records) {
Some(record) => {
if record.reason != "" {
return (
None,
None,
"unsupported value record " + record.name + ": " + record.reason,
)
}
pending_used.push(record.name)
moon_params.push(arg + " : MbgValue_" + record.name)
local_lines.push(
" " + parameter.c_type + " value" + index.to_string() + ";",
)
local_lines.push(
" memset(&value" +
index.to_string() +
", 0, sizeof(value" +
index.to_string() +
"));",
)
c_args.push("value" + index.to_string())
for field in record.fields {
let size_check = value_field_size_check(
parameter.c_type,
field,
index.to_string(),
)
if size_check != "" {
abi_checks.push(size_check)
}
let flat = c_arg + "_" + field.name
ffi_params.push(flat + " : " + field.moon_type)
ffi_args.push(arg + "." + field.name)
c_params.push(field.ffi_c_type + " " + flat)
let check = v2_abi_range_check(field.abi_kind, flat)
if check != "" {
guards.push(check)
}
local_lines.push(
" value" +
index.to_string() +
"." +
field.name +
" = (" +
field.c_type +
")" +
flat +
";",
)
}
}
None => {
let kind = v2_abi_kind(parameter.c_type, parameter.canonical_type)
let moon_type = if kind != "" {
v2_abi_moon_type(kind)
} else {
match
resolve_configured_type(
parameter.c_type,
parameter.canonical_type,
aliases,
opaque_types,
enums,
config,
utf8_input=config.is_utf8_input(function.name, parameter.name),
result=false,
) {
Some(value) => value.moon_type
None =>
return (
None,
None,
"unsupported parameter type: " + parameter.c_type,
)
}
}
moon_params.push(arg + " : " + moon_type)
ffi_params.push(arg + " : " + moon_type)
ffi_args.push(arg)
if kind != "" {
c_params.push(v2_abi_c_type(kind) + " " + c_arg)
c_args.push("(" + parameter.c_type + ")" + c_arg)
let check = v2_abi_range_check(kind, c_arg)
if check != "" {
guards.push(check)
}
} else if moon_type == "Bytes" {
borrowed.push(arg)
c_params.push("moonbit_bytes_t " + c_arg)
c_args.push("(" + parameter.c_type + ")" + c_arg)
} else {
c_params.push(parameter.c_type + " " + c_arg)
c_args.push(c_arg)
}
}
}
}
let mut c_value_type = function.return_type
if return_kind != "" {
c_value_type = function.return_type
}
let moon_getters : Array[String] = []
let c_getters : Array[String] = []
let mut moon_value = "()"
match return_record {
Some(record) => {
pending_used.push(record.name)
let values : Array[String] = []
for field in record.fields {
let size_check = value_field_size_check(
function.return_type,
field,
"return",
)
if size_check != "" {
abi_checks.push(size_check)
}
let getter = bridge + "_" + field.name
moon_getters.push(
"///|\n#borrow(raw)\n" +
v2_extern(getter, ["raw : " + raw_type], field.moon_type),
)
c_getters.push(
"MOONBIT_FFI_EXPORT " +
field.ffi_c_type +
" " +
getter +
"(" +
bridge +
"_result *raw) { return (" +
field.ffi_c_type +
")raw->value." +
field.name +
"; }\n",
)
values.push(" " + field.name + ": " + getter + "(raw),")
}
moon_value = "{\n" + values.join("\n") + "\n }"
}
None =>
if moon_return != "Unit" {
let getter = bridge + "_value"
let c_getter_type = if return_kind != "" {
v2_abi_c_type(return_kind)
} else {
c_value_type
}
moon_getters.push(
"///|\n#borrow(raw)\n" +
v2_extern(getter, ["raw : " + raw_type], moon_return),
)
c_getters.push(
"MOONBIT_FFI_EXPORT " +
c_getter_type +
" " +
getter +
"(" +
bridge +
"_result *raw) { return (" +
c_getter_type +
")raw->value; }\n",
)
moon_value = getter + "(raw)"
}
}
let call = function.name + "(" + c_args.join(", ") + ")"
let c_call = if moon_return == "Unit" {
call + ";"
} else {
"raw->value = " + call + ";"
}
let moon = "///|\ntype " +
raw_type +
"\n\n" +
"///|\n" +
(if borrowed.length() == 0 {
""
} else {
"#borrow(" + borrowed.join(", ") + ")\n"
}) +
v2_extern(bridge, ffi_params, raw_type) +
"\n" +
"///|\n#borrow(raw)\n" +
v2_extern(bridge + "_error", ["raw : " + raw_type], "Int") +
"\n" +
moon_getters.join("\n") +
"\n///|\n" +
v2_public_head(function.moon_name, moon_params) +
" -> " +
moon_return +
" raise {\n" +
" let raw = " +
bridge +
"(" +
ffi_args.join(", ") +
")\n" +
" if " +
bridge +
"_error(raw) != 0 {\n fail(\"C scalar argument is out of range\")\n }\n" +
" " +
moon_value +
"\n}\n"
let c = "typedef struct {\n int32_t error;\n" +
(if moon_return == "Unit" { "" } else { " " + c_value_type + " value;\n" }) +
"} " +
bridge +
"_result;\n" +
"static void " +
bridge +
"_finalize(void *raw) { (void)raw; }\n" +
"MOONBIT_FFI_EXPORT " +
bridge +
"_result *" +
bridge +
"(" +
c_params.join(", ") +
") {\n" +
abi_checks.join("\n") +
"\n" +
" " +
bridge +
"_result *raw = (" +
bridge +
"_result *)moonbit_make_external_object(" +
bridge +
"_finalize, sizeof(" +
bridge +
"_result));\n" +
" memset(raw, 0, sizeof(*raw));\n" +
(if guards.length() == 0 {
""
} else {
" if (" + guards.join(" || ") + ") { raw->error = 1; return raw; }\n"
}) +
local_lines.join("\n") +
"\n " +
c_call +
"\n return raw;\n}\n" +
"MOONBIT_FFI_EXPORT int32_t " +
bridge +
"_error(" +
bridge +
"_result *raw) { return raw->error; }\n" +
c_getters.join("")
for name in pending_used {
used[name] = true
}
(Some(moon), Some(c), "")
}