///|
/// mbt_gen.mbt - Generate MoonBit FFI bindings from MbtBinding
///
/// This is the MoonBit implementation of dts-to-mbt code generation.
/// TypeScript parsing is still done in JS, code generation is done here.
// ============================================================
// Types (mirror of dts-to-mbt.ts types)
// ============================================================
///|
pub struct MbtBinding {
package_name : String
types : Array[MbtType]
functions : Array[MbtFunction]
extern_types : Array[String]
classes : Array[MbtClass]
}
///|
pub struct MbtClass {
name : String
type_params : Array[String]
methods : Array[MbtMethod]
has_constructor : Bool
}
///|
pub struct MbtMethod {
name : String
js_name : String
}
///|
pub enum MbtTypeKind {
Struct
Type
Enum
}
///|
pub struct MbtType {
name : String
kind : MbtTypeKind
fields : Array[MbtField]
variants : Array[MbtVariant]
type_params : Array[String]
js_name : String
}
///|
pub struct MbtField {
name : String
type_ : String
mutable : Bool
}
///|
pub struct MbtVariant {
name : String
payload : Array[String]
}
///|
pub struct MbtFunction {
name : String
params : Array[MbtParam]
return_type : String
is_async : Bool
js_name : String
type_params : Array[String]
is_method : Bool
class_name : String
}
///|
pub struct MbtParam {
name : String
type_ : String
optional : Bool
}
// ============================================================
// Helper Functions (public for testing)
// ============================================================
///|
/// Format type parameters as "[T, U, V]" or "" if empty
pub fn format_type_params(params : Array[String]) -> String {
if params.length() == 0 {
return ""
}
let buf = StringBuilder::new()
buf.write_string("[")
for i, p in params {
if i > 0 {
buf.write_string(", ")
}
buf.write_string(p)
}
buf.write_string("]")
buf.to_string()
}
///|
/// Format struct fields
pub fn format_fields(fields : Array[MbtField]) -> String {
let buf = StringBuilder::new()
for f in fields {
buf.write_string(" ")
if f.mutable {
buf.write_string("mut ")
}
buf.write_string(f.name)
buf.write_string(" : ")
buf.write_string(f.type_)
buf.write_string("\n")
}
buf.to_string()
}
///|
/// Format enum variants
pub fn format_variants(variants : Array[MbtVariant]) -> String {
let buf = StringBuilder::new()
for v in variants {
buf.write_string(" ")
buf.write_string(v.name)
if v.payload.length() > 0 {
buf.write_string("(")
for i, p in v.payload {
if i > 0 {
buf.write_string(", ")
}
buf.write_string(p)
}
buf.write_string(")")
}
buf.write_string("\n")
}
buf.to_string()
}
///|
/// Format function parameters as "name : Type, name2 : Type2"
pub fn format_fn_params(params : Array[MbtParam]) -> String {
let buf = StringBuilder::new()
for i, p in params {
if i > 0 {
buf.write_string(", ")
}
let name = if p.name.length() > 0 { p.name } else { "arg\{i}" }
buf.write_string(name)
buf.write_string(" : ")
buf.write_string(p.type_)
}
buf.to_string()
}
// ============================================================
// Code Generation
// ============================================================
///|
pub fn generate_mbt(binding : MbtBinding) -> String {
let buf = StringBuilder::new()
// Package declaration (commented out)
if binding.package_name.length() > 0 {
buf.write_string("// package \"")
buf.write_string(binding.package_name)
buf.write_string("\"\n\n")
}
// Extern type declarations
if binding.extern_types.length() > 0 {
buf.write_string("// Extern types\n")
for t in binding.extern_types {
buf.write_string("#external\n")
buf.write_string(t)
buf.write_string("\n\n")
}
}
// Type definitions
if binding.types.length() > 0 {
buf.write_string("// Types\n")
for t in binding.types {
buf.write_string(gen_mbt_type(t))
buf.write_string("\n\n")
}
}
// Function bindings
if binding.functions.length() > 0 {
buf.write_string("// Functions\n")
for f in binding.functions {
buf.write_string(gen_mbt_function(f))
buf.write_string("\n")
}
}
buf.to_string()
}
///|
fn gen_mbt_type(t : MbtType) -> String {
let type_params = format_type_params(t.type_params)
match t.kind {
Struct => gen_struct(t.name, type_params, t.fields)
Enum => gen_enum(t.name, type_params, t.variants)
Type => "pub type \{t.name}\{type_params}"
}
}
///|
/// Generate struct definition
fn gen_struct(
name : String,
type_params : String,
fields : Array[MbtField],
) -> String {
if fields.length() == 0 {
"pub struct \{name}\{type_params} {}"
} else {
let fields_str = format_fields(fields)
"pub struct \{name}\{type_params} {\n\{fields_str}}"
}
}
///|
/// Generate enum definition
fn gen_enum(
name : String,
type_params : String,
variants : Array[MbtVariant],
) -> String {
let variants_str = format_variants(variants)
"pub enum \{name}\{type_params} {\n\{variants_str}}"
}
///|
fn gen_mbt_function(f : MbtFunction) -> String {
let type_params = format_type_params(f.type_params)
let params = format_fn_params(f.params)
// Handle async - wrap in Promise if not already
let return_type = if f.is_async && not(f.return_type.has_prefix("Promise[")) {
"Promise[\{f.return_type}]"
} else {
f.return_type
}
// Generate inline JS for methods/constructors (glue-code-free)
if f.is_method && f.class_name.length() > 0 {
// Check if it's a constructor (name ends with ::new)
if f.name.has_suffix("::new") {
// Constructor: new ClassName(args)
let arg_names = gen_param_names(f.params)
let buf = StringBuilder::new()
buf.write_string("extern \"js\" fn ")
buf.write_string(f.name)
buf.write_string(type_params)
buf.write_string("(")
buf.write_string(params)
buf.write_string(") -> ")
buf.write_string(return_type)
buf.write_string(" =\n #| (")
buf.write_string(arg_names)
buf.write_string(") => new ")
buf.write_string(f.class_name)
buf.write_string("(")
buf.write_string(arg_names)
buf.write_string(")")
buf.to_string()
} else {
// Method: self.methodName(args)
// Extract method name from "ClassName::method_name"
let method_name = extract_method_name(f.name)
let js_method_name = to_camel_case(method_name)
// Skip 'self' parameter for JS call
let arg_names_without_self = gen_param_names_skip_first(f.params)
let buf = StringBuilder::new()
buf.write_string("extern \"js\" fn ")
buf.write_string(f.name)
buf.write_string(type_params)
buf.write_string("(")
buf.write_string(params)
buf.write_string(") -> ")
buf.write_string(return_type)
buf.write_string(" =\n #| (self")
if arg_names_without_self.length() > 0 {
buf.write_string(", ")
buf.write_string(arg_names_without_self)
}
buf.write_string(") => self.")
buf.write_string(js_method_name)
buf.write_string("(")
buf.write_string(arg_names_without_self)
buf.write_string(")")
buf.to_string()
}
} else {
// Regular function - use simple string binding
"extern \"js\" fn \{f.name}\{type_params}(\{params}) -> \{return_type} = \"\{f.js_name}\""
}
}
///|
/// Generate comma-separated parameter names
fn gen_param_names(params : Array[MbtParam]) -> String {
let buf = StringBuilder::new()
for i, p in params {
if i > 0 {
buf.write_string(", ")
}
let name = if p.name.length() > 0 { p.name } else { "arg\{i}" }
buf.write_string(name)
}
buf.to_string()
}
///|
/// Generate comma-separated parameter names, skipping first (self)
fn gen_param_names_skip_first(params : Array[MbtParam]) -> String {
let buf = StringBuilder::new()
let mut first = true
for i, p in params {
if i == 0 {
continue // Skip self
}
if not(first) {
buf.write_string(", ")
}
first = false
let name = if p.name.length() > 0 { p.name } else { "arg\{i}" }
buf.write_string(name)
}
buf.to_string()
}
///|
/// Extract method name from "ClassName::method_name"
fn extract_method_name(full_name : String) -> String {
match full_name.find("::") {
Some(idx) => {
let chars : Array[Char] = full_name.to_array()
let buf = StringBuilder::new()
for i = idx + 2; i < chars.length(); i = i + 1 {
buf.write_char(chars[i])
}
buf.to_string()
}
None => full_name
}
}
// ============================================================
// Default constructors
// ============================================================
///|
pub fn MbtBinding::new() -> MbtBinding {
MbtBinding::{
package_name: "",
types: [],
functions: [],
extern_types: [],
classes: [],
}
}
///|
pub fn MbtType::new(name : String, kind : MbtTypeKind) -> MbtType {
MbtType::{
name,
kind,
fields: [],
variants: [],
type_params: [],
js_name: "",
}
}
///|
pub fn MbtFunction::new(name : String, js_name : String) -> MbtFunction {
MbtFunction::{
name,
params: [],
return_type: "Unit",
is_async: false,
js_name,
type_params: [],
is_method: false,
class_name: "",
}
}
///|
pub fn MbtParam::new(name : String, type_ : String) -> MbtParam {
MbtParam::{ name, type_, optional: false }
}
///|
pub fn MbtField::new(name : String, type_ : String) -> MbtField {
MbtField::{ name, type_, mutable: false }
}
///|
pub fn MbtVariant::new(name : String) -> MbtVariant {
MbtVariant::{ name, payload: [] }
}
// ============================================================
// JSON Parsing (for TypeScript integration)
// ============================================================
///|
/// Helper to get optional string from Json object
fn get_string(obj : Map[String, Json], key : String) -> String? {
match obj.get(key) {
Some(v) => v.as_string()
None => None
}
}
///|
/// Helper to get optional bool from Json object
fn get_bool(obj : Map[String, Json], key : String) -> Bool? {
match obj.get(key) {
Some(v) => v.as_bool()
None => None
}
}
///|
/// Helper to get optional array from Json object
fn get_array(obj : Map[String, Json], key : String) -> Array[Json]? {
match obj.get(key) {
Some(v) => v.as_array()
None => None
}
}
///|
/// Parse MbtBinding from JSON
/// JSON structure mirrors dts-to-mbt.ts MbtBinding interface
pub fn MbtBinding::from_json(json : Json) -> MbtBinding? {
match json.as_object() {
None => None
Some(obj) => {
let package_name = get_string(obj, "packageName").unwrap_or("")
let extern_types = parse_string_array(get_array(obj, "externTypes"))
let types = parse_mbt_types(get_array(obj, "types"))
let functions = parse_mbt_functions(get_array(obj, "functions"))
let classes = parse_mbt_classes(get_array(obj, "classes"))
Some(MbtBinding::{ package_name, types, functions, extern_types, classes })
}
}
}
///|
fn parse_string_array(arr : Array[Json]?) -> Array[String] {
let result : Array[String] = []
match arr {
Some(items) =>
for item in items {
match item.as_string() {
Some(s) => result.push(s)
None => ()
}
}
None => ()
}
result
}
///|
fn parse_mbt_types(arr : Array[Json]?) -> Array[MbtType] {
let result : Array[MbtType] = []
match arr {
Some(items) =>
for item in items {
match MbtType::from_json(item) {
Some(t) => result.push(t)
None => ()
}
}
None => ()
}
result
}
///|
fn parse_mbt_functions(arr : Array[Json]?) -> Array[MbtFunction] {
let result : Array[MbtFunction] = []
match arr {
Some(items) =>
for item in items {
match MbtFunction::from_json(item) {
Some(f) => result.push(f)
None => ()
}
}
None => ()
}
result
}
///|
fn parse_mbt_classes(arr : Array[Json]?) -> Array[MbtClass] {
let result : Array[MbtClass] = []
match arr {
Some(items) =>
for item in items {
match MbtClass::from_json(item) {
Some(c) => result.push(c)
None => ()
}
}
None => ()
}
result
}
///|
pub fn MbtType::from_json(json : Json) -> MbtType? {
match json.as_object() {
None => None
Some(obj) => {
let name = match get_string(obj, "name") {
Some(n) => n
None => return None
}
let kind_str = match get_string(obj, "kind") {
Some(k) => k
None => return None
}
let kind = match kind_str {
"struct" => MbtTypeKind::Struct
"type" => MbtTypeKind::Type
"enum" => MbtTypeKind::Enum
_ => return None
}
let fields = parse_mbt_fields(get_array(obj, "fields"))
let variants = parse_mbt_variants(get_array(obj, "variants"))
let type_params = parse_string_array(get_array(obj, "typeParams"))
let js_name = get_string(obj, "jsName").unwrap_or("")
Some(MbtType::{ name, kind, fields, variants, type_params, js_name })
}
}
}
///|
fn parse_mbt_fields(arr : Array[Json]?) -> Array[MbtField] {
let result : Array[MbtField] = []
match arr {
Some(items) =>
for item in items {
match MbtField::from_json(item) {
Some(f) => result.push(f)
None => ()
}
}
None => ()
}
result
}
///|
pub fn MbtField::from_json(json : Json) -> MbtField? {
match json.as_object() {
None => None
Some(obj) => {
let name = match get_string(obj, "name") {
Some(n) => n
None => return None
}
let type_ = match get_string(obj, "type") {
Some(t) => t
None => return None
}
let mutable = get_bool(obj, "mutable").unwrap_or(false)
Some(MbtField::{ name, type_, mutable })
}
}
}
///|
fn parse_mbt_variants(arr : Array[Json]?) -> Array[MbtVariant] {
let result : Array[MbtVariant] = []
match arr {
Some(items) =>
for item in items {
match MbtVariant::from_json(item) {
Some(v) => result.push(v)
None => ()
}
}
None => ()
}
result
}
///|
pub fn MbtVariant::from_json(json : Json) -> MbtVariant? {
match json.as_object() {
None => None
Some(obj) => {
let name = match get_string(obj, "name") {
Some(n) => n
None => return None
}
let payload = parse_string_array(get_array(obj, "payload"))
Some(MbtVariant::{ name, payload })
}
}
}
///|
pub fn MbtFunction::from_json(json : Json) -> MbtFunction? {
match json.as_object() {
None => None
Some(obj) => {
let name = match get_string(obj, "name") {
Some(n) => n
None => return None
}
let js_name = match get_string(obj, "jsName") {
Some(n) => n
None => return None
}
let return_type = get_string(obj, "returnType").unwrap_or("Unit")
let is_async = get_bool(obj, "isAsync").unwrap_or(false)
let type_params = parse_string_array(get_array(obj, "typeParams"))
let is_method = get_bool(obj, "isMethod").unwrap_or(false)
let class_name = get_string(obj, "className").unwrap_or("")
let params = parse_mbt_params(get_array(obj, "params"))
Some(MbtFunction::{
name,
params,
return_type,
is_async,
js_name,
type_params,
is_method,
class_name,
})
}
}
}
///|
fn parse_mbt_params(arr : Array[Json]?) -> Array[MbtParam] {
let result : Array[MbtParam] = []
match arr {
Some(items) =>
for item in items {
match MbtParam::from_json(item) {
Some(p) => result.push(p)
None => ()
}
}
None => ()
}
result
}
///|
pub fn MbtParam::from_json(json : Json) -> MbtParam? {
match json.as_object() {
None => None
Some(obj) => {
let name = match get_string(obj, "name") {
Some(n) => n
None => return None
}
let type_ = match get_string(obj, "type") {
Some(t) => t
None => return None
}
let optional = get_bool(obj, "optional").unwrap_or(false)
Some(MbtParam::{ name, type_, optional })
}
}
}
///|
pub fn MbtClass::from_json(json : Json) -> MbtClass? {
match json.as_object() {
None => None
Some(obj) => {
let name = match get_string(obj, "name") {
Some(n) => n
None => return None
}
let type_params = parse_string_array(get_array(obj, "typeParams"))
let has_constructor = get_bool(obj, "hasConstructor").unwrap_or(false)
let methods = parse_mbt_methods(get_array(obj, "methods"))
Some(MbtClass::{ name, type_params, methods, has_constructor })
}
}
}
///|
fn parse_mbt_methods(arr : Array[Json]?) -> Array[MbtMethod] {
let result : Array[MbtMethod] = []
match arr {
Some(items) =>
for item in items {
match MbtMethod::from_json(item) {
Some(m) => result.push(m)
None => ()
}
}
None => ()
}
result
}
///|
pub fn MbtMethod::from_json(json : Json) -> MbtMethod? {
match json.as_object() {
None => None
Some(obj) => {
let name = match get_string(obj, "name") {
Some(n) => n
None => return None
}
let js_name = match get_string(obj, "jsName") {
Some(n) => n
None => return None
}
Some(MbtMethod::{ name, js_name })
}
}
}