///|
/// MoonBit binding code generator for the WIT core subset.
///
/// Maps WIT types and definitions to idiomatic MoonBit:
/// record→struct, enum→unit enum, variant→payload enum, flags→Bool struct,
/// resource→`#external` opaque type, type alias→`type`,
/// func→a `pub fn` stub that aborts. Functions whose bodies are not yet bound
/// are emitted as stubs so the generated package always compiles.
/// Design inspired by bytecodealliance/wit-bindgen.
///|
/// Generate the contents of a `bindings.mbt` file for `pkg`.
pub fn generate(pkg : WitPackage) -> String {
let mut out = "// Generated by moon-wit. DO NOT EDIT.\n"
out = out + "// Design inspired by bytecodealliance/wit-bindgen.\n"
out = out + "\n"
if needs_future(pkg) {
out = out + "///|\n#external\ntype WitFuture[T]\n\n"
}
if needs_stream(pkg) {
out = out + "///|\n#external\ntype WitStream[T]\n\n"
}
for i in 0.. Bool {
for item in pkg.items {
if item_uses_list(item) {
return true
}
}
false
}
///|
/// Contents of the `moon.pkg` for a generated package.
pub fn generate_moon_pkg(use_list : Bool) -> String {
if use_list {
"import {\n \"moonbitlang/core/list\",\n}\n"
} else {
"\n"
}
}
///|
fn generate_item(item : WitItem) -> String {
match item {
Interface(iface) => generate_interface(iface)
World(w) => generate_world(w)
Use(u) => "// \{u.to_string()}\n"
}
}
///|
fn generate_interface(iface : Interface) -> String {
let mut out = ""
for i in 0.. String {
match item {
Type(t) => generate_typedef(t)
Func(f) => generate_func(f)
Use(u) => "// \{u.to_string()}\n"
}
}
///|
fn generate_world(world : World) -> String {
let mut out = ""
for i in 0.. String {
match item {
Import(imp) => generate_impexp("import", imp)
Export(exp) => generate_impexp("export", exp)
ImportFunc(f) => "// import \{f.name}\n\n" + generate_func(f)
ExportFunc(f) => "// export \{f.name}\n\n" + generate_func(f)
Include(path, names) => "// \{Include(path, names).to_string()}\n"
Use(u) => "// \{u.to_string()}\n"
}
}
///|
fn generate_impexp(kw : String, imp : ImpExp) -> String {
let mut out = "// \{kw} \{imp.name}"
match imp.asName {
Some(a) => out = out + " as \{a}"
None => ()
}
out = out + "\n"
match imp.inline {
Some(iface) => out = out + "\n" + generate_interface(iface)
None => ()
}
out
}
///|
fn generate_typedef(def : TypeDef) -> String {
let tn = type_name(def.name)
match def.kind {
Record(fields) => {
let mut out = "///|\npub struct \{tn} {\n"
for f in fields {
out = out + " \{field_name(f.name)} : \{type_str(f.ty)}\n"
}
out + "}\n"
}
Enum(names) => {
let mut out = "///|\npub enum \{tn} {\n"
for n in names {
out = out + " \{case_name(n)}\n"
}
out + "}\n"
}
Variant(cases) => {
let mut out = "///|\npub enum \{tn} {\n"
for c in cases {
match c.ty {
Some(t) => out = out + " \{case_name(c.name)}(\{type_str(t)})\n"
None => out = out + " \{case_name(c.name)}\n"
}
}
out + "}\n"
}
Flags(names) => {
let mut out = "///|\npub type \{tn} = UInt\n"
for i in 0.. {
let mut out = "///|\n#external\ntype \{tn}\n"
for f in funcs {
let prefix = match f.kind {
Constructor => "new"
Static => field_name(f.sig.name)
Method => field_name(f.sig.name)
}
let params = f.sig.params
let results = match f.kind {
Constructor => [Unnamed(Name(def.name))]
_ => f.sig.results
}
let generated_name = match f.kind {
Method => "\{type_name(def.name)}::\{field_name(f.sig.name)}"
_ => "\{field_name(def.name)}_\{prefix}"
}
let sig : FuncSig = { name: generated_name, params, results }
out = out + "\n" + generate_func(sig)
}
out
}
Alias(t) => "///|\npub type \{tn} = \{type_str(t)}\n"
}
}
///|
fn generate_func(f : FuncSig) -> String {
let mut out = "///|\npub fn \{field_name(f.name)}(\{params_str(f.params)})"
if f.results.length() == 0 {
out = out + " -> Unit"
} else {
out = out + " -> \{results_str(f.results)}"
}
out = out + " {\n"
for p in f.params {
out = out + " ignore(\{field_name(p.name)})\n"
}
out = out + " abort(\"stub: \{f.name}\")\n}\n"
out
}
///|
fn params_str(params : Array[Field]) -> String {
join_strings(
params.map(fn(p) { "\{field_name(p.name)} : \{type_str(p.ty)}" }),
", ",
)
}
///|
fn results_str(results : Array[FuncResult]) -> String {
if results.length() == 1 {
match results[0] {
Unnamed(t) => return type_str(t)
Named(f) => return type_str(f.ty)
}
}
"(\{join_strings(results.map(fn(r) { type_str_of_result(r) }), ", ")})"
}
///|
fn type_str_of_result(r : FuncResult) -> String {
match r {
Unnamed(t) => type_str(t)
Named(f) => type_str(f.ty)
}
}
///|
/// WIT type → MoonBit type.
pub fn type_str(t : WitType) -> String {
match t {
Name(n) => type_name(n)
U8 => "Byte"
U16 => "UInt16"
U32 => "UInt"
U64 => "UInt64"
S8 => "Int"
S16 => "Int16"
S32 => "Int"
S64 => "Int64"
F32 => "Float"
F64 => "Double"
TChar => "Char"
TString => "String"
TBool => "Bool"
List(t) => "@list.List[\{type_str(t)}]"
Option(t) => "Option[\{type_str(t)}]"
Result(ok, err) => "Result[\{slot_str(ok)}, \{slot_str(err)}]"
Tuple(ts) => "(\{join_strings(ts.map(fn(t) { type_str(t) }), ", ")})"
Own(n) => type_name(n)
Borrow(n) => type_name(n)
Future(t) => "WitFuture[\{slot_str(t)}]"
Stream(t) => "WitStream[\{slot_str(t)}]"
}
}
///|
fn slot_str(slot : WitType?) -> String {
match slot {
Some(t) => type_str(t)
None => "Unit"
}
}
///|
/// `hello-world` → `HelloWorld` (MoonBit type/case naming).
pub fn type_name(name : String) -> String {
kebab_to_pascal(name)
}
///|
/// `first-name` → `FirstName` (MoonBit enum case naming).
pub fn case_name(name : String) -> String {
kebab_to_pascal(name)
}
///|
/// `first-name` → `first_name` (MoonBit field/function naming).
pub fn field_name(name : String) -> String {
let ident = kebab_to_snake(name)
if is_moonbit_keyword(ident) {
ident + "_"
} else {
ident
}
}
///|
fn is_moonbit_keyword(name : String) -> Bool {
match name {
"_"
| "as"
| "async"
| "break"
| "catch"
| "const"
| "continue"
| "derive"
| "else"
| "enum"
| "extern"
| "fn"
| "for"
| "guard"
| "if"
| "impl"
| "in"
| "is"
| "let"
| "loop"
| "match"
| "mut"
| "priv"
| "pub"
| "raise"
| "return"
| "struct"
| "test"
| "trait"
| "try"
| "type"
| "typealias"
| "while"
| "with"
| "yield" => true
_ => false
}
}
///|
fn kebab_to_pascal(s : String) -> String {
let mut out = ""
let mut cap_next = true
for ch in s {
if ch == '%' {
continue
} else if ch == '-' || ch == '_' {
cap_next = true
} else if cap_next {
out = out + upcase_char(ch).to_string()
cap_next = false
} else {
out = out + ch.to_string()
}
}
out
}
///|
fn kebab_to_snake(s : String) -> String {
let mut out = ""
for ch in s {
if ch == '%' {
continue
} else if ch == '-' {
out = out + "_"
} else {
out = out + ch.to_string()
}
}
out
}
///|
fn upcase_char(ch : Char) -> Char {
let code = ch.to_int()
if code >= 97 && code <= 122 {
(code - 32).unsafe_to_char()
} else {
ch
}
}
///|
fn item_uses_list(item : WitItem) -> Bool {
match item {
Interface(iface) => {
for i in iface.items {
if interface_item_uses_list(i) {
return true
}
}
false
}
World(w) => {
for wi in w.items {
if world_item_uses_list(wi) {
return true
}
}
false
}
Use(_) => false
}
}
///|
fn interface_item_uses_list(item : InterfaceItem) -> Bool {
match item {
Type(t) => typedef_uses_list(t)
Func(f) => func_uses_list(f)
Use(_) => false
}
}
///|
fn world_item_uses_list(item : WorldItem) -> Bool {
match item {
Import(imp) => impexp_uses_list(imp)
Export(imp) => impexp_uses_list(imp)
ImportFunc(f) | ExportFunc(f) => func_uses_list(f)
Include(_, _) => false
Use(_) => false
}
}
///|
fn impexp_uses_list(imp : ImpExp) -> Bool {
match imp.inline {
Some(iface) => interface_uses_list(iface)
None => false
}
}
///|
fn interface_uses_list(iface : Interface) -> Bool {
for item in iface.items {
if interface_item_uses_list(item) {
return true
}
}
false
}
///|
fn typedef_uses_list(def : TypeDef) -> Bool {
match def.kind {
Record(fields) => {
for f in fields {
if type_uses_list(f.ty) {
return true
}
}
false
}
Variant(cases) => {
for c in cases {
match c.ty {
Some(t) => if type_uses_list(t) { return true }
None => ()
}
}
false
}
Alias(t) => type_uses_list(t)
Resource(funcs) => {
for f in funcs {
if func_uses_list(f.sig) {
return true
}
}
false
}
Enum(_) | Flags(_) => false
}
}
///|
fn func_uses_list(f : FuncSig) -> Bool {
for p in f.params {
if type_uses_list(p.ty) {
return true
}
}
for r in f.results {
match r {
Unnamed(t) => if type_uses_list(t) { return true }
Named(field) => if type_uses_list(field.ty) { return true }
}
}
false
}
///|
fn type_uses_list(t : WitType) -> Bool {
match t {
List(_) => true
Option(inner) => type_uses_list(inner)
Result(ok, err) => slot_uses_list(ok) || slot_uses_list(err)
Future(inner) | Stream(inner) => slot_uses_list(inner)
Tuple(ts) => {
for inner in ts {
if type_uses_list(inner) {
return true
}
}
false
}
_ => false
}
}
///|
fn needs_future(pkg : WitPackage) -> Bool {
package_uses_type(pkg, fn(t) { t is Future(_) })
}
///|
fn needs_stream(pkg : WitPackage) -> Bool {
package_uses_type(pkg, fn(t) { t is Stream(_) })
}
///|
fn package_uses_type(pkg : WitPackage, predicate : (WitType) -> Bool) -> Bool {
for item in pkg.items {
if item_contains_type(item, predicate) {
return true
}
}
false
}
///|
fn item_contains_type(item : WitItem, predicate : (WitType) -> Bool) -> Bool {
match item {
Interface(iface) => interface_contains_type(iface, predicate)
World(world) => {
for item in world.items {
let found = match item {
Import(imp) | Export(imp) =>
match imp.inline {
Some(iface) => interface_contains_type(iface, predicate)
None => false
}
ImportFunc(f) | ExportFunc(f) => func_contains_type(f, predicate)
Include(_, _) | Use(_) => false
}
if found {
return true
}
}
false
}
Use(_) => false
}
}
///|
fn interface_contains_type(
iface : Interface,
predicate : (WitType) -> Bool,
) -> Bool {
for item in iface.items {
let found = match item {
Type(def) => typedef_contains_type(def, predicate)
Func(f) => func_contains_type(f, predicate)
Use(_) => false
}
if found {
return true
}
}
false
}
///|
fn typedef_contains_type(def : TypeDef, predicate : (WitType) -> Bool) -> Bool {
match def.kind {
Record(fields) => fields_contain_type(fields, predicate)
Variant(cases) => {
for c in cases {
match c.ty {
Some(t) => if wit_type_contains(t, predicate) { return true }
None => ()
}
}
false
}
Alias(t) => wit_type_contains(t, predicate)
Resource(funcs) => {
for f in funcs {
if func_contains_type(f.sig, predicate) {
return true
}
}
false
}
Enum(_) | Flags(_) => false
}
}
///|
fn func_contains_type(f : FuncSig, predicate : (WitType) -> Bool) -> Bool {
if fields_contain_type(f.params, predicate) {
return true
}
for result in f.results {
let t = match result {
Unnamed(t) => t
Named(field) => field.ty
}
if wit_type_contains(t, predicate) {
return true
}
}
false
}
///|
fn fields_contain_type(
fields : Array[Field],
predicate : (WitType) -> Bool,
) -> Bool {
for field in fields {
if wit_type_contains(field.ty, predicate) {
return true
}
}
false
}
///|
fn wit_type_contains(t : WitType, predicate : (WitType) -> Bool) -> Bool {
if predicate(t) {
return true
}
match t {
List(inner) | Option(inner) => wit_type_contains(inner, predicate)
Result(ok, err) =>
optional_type_contains(ok, predicate) ||
optional_type_contains(err, predicate)
Tuple(items) => {
for item in items {
if wit_type_contains(item, predicate) {
return true
}
}
false
}
Future(inner) | Stream(inner) => optional_type_contains(inner, predicate)
_ => false
}
}
///|
fn optional_type_contains(
value : WitType?,
predicate : (WitType) -> Bool,
) -> Bool {
match value {
Some(t) => wit_type_contains(t, predicate)
None => false
}
}
///|
fn slot_uses_list(slot : WitType?) -> Bool {
match slot {
Some(t) => type_uses_list(t)
None => false
}
}