///|
fn sdk_constructor(
ctx : Lowering,
name : @ir.FQName,
expected : Lowered,
context : String,
) -> String raise {
match (name.to_canonical(), expected) {
("morphir/SDK:maybe#nothing", Optional(_)) => "None"
("morphir/SDK:maybe#just", Arrow(a, Optional(b))) if a == b =>
curry_source(ctx, [("value", a)], Optional(b), "Some(value)")
("morphir/SDK:result#ok", Arrow(a, Outcome(b, e))) if a == b =>
curry_source(ctx, [("value", a)], Outcome(b, e), "Ok(value)")
("morphir/SDK:result#err", Arrow(e, Outcome(a, b))) if e == b =>
curry_source(ctx, [("value", e)], Outcome(a, b), "Err(value)")
_ =>
raise Invalid(
"moonbit.unresolved_constructor",
context,
name.to_canonical(),
)
}
}
///|
fn sdk_reference(
ctx : Lowering,
name : @ir.FQName,
expected : Lowered,
context : String,
) -> String raise {
let binding_name = name.local_name.to_canonical()
let arity = match (name.module_name.to_canonical(), binding_name) {
("decimal", "zero" | "one" | "minus-one") => 0
("decimal-checked-v1", "from-string-exact") => 1
("decimal-checked-v1", "divide") => 2
("decimal-checked-v1", "divide-with-default") => 3
("list", "foldl" | "foldr") => 3
("string", _) => sdk_string_arity(binding_name, context)
(
"basics",
"add"
| "subtract"
| "multiply"
| "integer-divide"
| "power"
| "mod-by"
| "remainder-by"
| "divide"
| "and"
| "or"
| "xor"
| "always"
| "equal"
| "not-equal"
| "less-than"
| "greater-than"
| "less-than-or-equal"
| "greater-than-or-equal",
)
| (
"decimal",
"add"
| "sub"
| "mul"
| "div"
| "eq"
| "neq"
| "gt"
| "gte"
| "lt"
| "lte",
)
| ("list", "map" | "filter" | "append")
| ("maybe", "map" | "with-default") => 2
(
"basics",
"negate"
| "abs"
| "not"
| "identity"
| "to-float"
| "round"
| "floor"
| "ceiling"
| "truncate"
| "sqrt"
| "sin"
| "cos",
)
| ("decimal", "negate" | "abs" | "truncate" | "round" | "from-int")
| ("list", "sum" | "product" | "length" | "reverse")
| ("char", "to-upper")
| (
"int",
"to-int32"
| "from-int32"
| "to-int-32"
| "from-int-32"
| "to-int-16"
| "from-int-16"
| "to-int-64"
| "from-int-64"
| "to-byte"
| "from-byte"
| "to-uint-16"
| "from-uint-16"
| "to-uint-32"
| "from-uint-32"
| "to-uint-64"
| "from-uint-64"
| "from-float64-exact"
| "from-float64-truncated"
| "from-float64-floor"
| "from-float64-ceiling"
| "from-float64-rounded",
) => 1
(
"int",
"from-float32-exact"
| "from-float32-truncated"
| "from-float32-floor"
| "from-float32-ceiling"
| "from-float32-rounded"
| "to-float64-exact"
| "to-float64-rounded"
| "to-float32-exact"
| "to-float32-rounded",
) => 1
_ => raise Invalid("moonbit.unsupported_sdk", context, name.to_canonical())
}
let inputs = []
let mut result = expected
for _ in 0.. ("a" + i.to_string(), inputs[i]))
let canonical = name.to_canonical()
let int = Atom(Scalar::Integer)
let dec = Atom(Scalar::Decimal)
let float = Atom(Scalar::Float)
let float32 = Atom(Scalar::Float32)
let bool = Atom(Scalar::Boolean)
let mut call = ""
let module_name = name.module_name.to_canonical()
let binding = name.local_name.to_canonical()
let native_binding = binding
.split("-")
.map(StringView::to_owned)
.collect()
.join("_")
fn require(signature : Array[Lowered], output : Lowered) -> Unit raise {
if inputs != signature || result != output {
raise Invalid("moonbit.sdk_signature", context, canonical)
}
}
fn native(module_name : String, function : String) -> String {
ctx.imports[module_name] = true
"@" +
module_name +
"." +
function +
"(" +
args.map(p => p.0).join(", ") +
")"
}
match (module_name, binding) {
("string", _) =>
call = sdk_string_call(ctx, binding, inputs, result, context)
("int", "to-int32" | "to-int-32") => {
require([int], Optional(Atom(Scalar::SignedInt32)))
call = native("int", "to_int32")
}
("int", "from-int32" | "from-int-32") => {
require([Atom(Scalar::SignedInt32)], int)
call = native("int", "from_int32")
}
(
"int",
"from-float64-exact"
| "from-float64-truncated"
| "from-float64-floor"
| "from-float64-ceiling"
| "from-float64-rounded",
) => {
require([float], Optional(int))
call = native("int", native_binding)
}
(
"int",
"from-float32-exact"
| "from-float32-truncated"
| "from-float32-floor"
| "from-float32-ceiling"
| "from-float32-rounded",
) => {
require([float32], Optional(int))
call = native("int", native_binding)
}
("int", "to-float64-exact") => {
require([int], Optional(float))
call = native("int", native_binding)
}
("int", "to-float32-exact") => {
require([int], Optional(float32))
call = native("int", native_binding)
}
("int", "to-float64-rounded") => {
require([int], float)
call = native("int", native_binding)
}
("int", "to-float32-rounded") => {
require([int], float32)
call = native("int", native_binding)
}
(
"int",
"to-int-16"
| "from-int-16"
| "to-int-64"
| "from-int-64"
| "to-byte"
| "from-byte"
| "to-uint-16"
| "from-uint-16"
| "to-uint-32"
| "from-uint-32"
| "to-uint-64"
| "from-uint-64",
) => {
let to = binding.has_prefix("to-")
let width = binding[if to { 3 } else { 5 }:].to_owned()
let bounded = Atom(Scalar::BoundedInteger(width))
if to {
require([int], Optional(bounded))
} else {
require([bounded], int)
}
call = native(
"int",
(if to { "to_" } else { "from_" }) + width.replace(old="-", new=""),
)
}
("basics", "add" | "subtract" | "multiply") => {
let t = if inputs == [int, int] { int } else { float }
require([t, t], t)
call = native(
"basics",
binding + (if t == int { "_int" } else { "_float" }),
)
}
("basics", "power") => {
let t = if inputs == [int, int] { int } else { float }
require([t, t], t)
call = native(
"basics",
if t == int {
"power_int"
} else {
"power_float"
},
)
}
("basics", "integer-divide" | "mod-by" | "remainder-by") => {
require([int, int], int)
call = native(
"basics",
match binding {
"integer-divide" => "integer_divide"
"mod-by" => "mod_by"
_ => "remainder_by"
},
)
}
("basics", "divide") => {
require([float, float], float)
call = native("basics", "divide")
}
("basics", "negate" | "abs") => {
let t = if inputs == [int] { int } else { float }
require([t], t)
call = native(
"basics",
binding + (if t == int { "_int" } else { "_float" }),
)
}
("basics", "not") => {
require([bool], bool)
call = "!a0"
}
("basics", "and" | "or" | "xor") => {
require([bool, bool], bool)
call = native(
"basics",
if binding == "xor" {
"xor"
} else {
binding + "_"
},
)
}
("basics", "identity") if inputs.length() == 1 && inputs[0] == result =>
call = "a0"
("basics", "always") if inputs.length() == 2 && inputs[0] == result =>
call = "a0"
("basics", "to-float") => {
require([int], float)
call = native("basics", "to_float")
}
("basics", "round" | "floor" | "ceiling" | "truncate") => {
require([float], int)
call = native("basics", binding)
}
("basics", "sqrt" | "sin" | "cos") => {
require([float], float)
call = native("basics", binding)
}
(
"basics",
"equal"
| "not-equal"
| "less-than"
| "greater-than"
| "less-than-or-equal"
| "greater-than-or-equal",
) if inputs.length() == 2 && inputs[0] == inputs[1] => {
let t = inputs[0]
if ![int, float, bool, Atom(Scalar::Text), Atom(Scalar::Character)].contains(
t,
) {
raise Invalid(
"moonbit.unsupported_sdk", context, "Comparison requires an audited scalar instance",
)
}
require([t, t], bool)
let equality = ["equal", "not-equal"].contains(binding)
if !equality && t == bool {
raise Invalid(
"moonbit.sdk_signature", context, "Boolean is not an ordered SDK scalar",
)
}
if !equality && (t == Atom(Scalar::Text) || t == Atom(Scalar::Character)) {
let left = if t == Atom(Scalar::Character) {
"a0.to_text()"
} else {
"a0"
}
let right = if t == Atom(Scalar::Character) {
"a1.to_text()"
} else {
"a1"
}
call = "(@sdk.text_order(" +
left +
", " +
right +
") " +
(match binding {
"less-than" => "== @sdk.LT"
"greater-than" => "== @sdk.GT"
"less-than-or-equal" => "!= @sdk.GT"
_ => "!= @sdk.LT"
}) +
")"
} else if !equality && t == float {
call = "if a0.is_nan() || a1.is_nan() { false } else { a0 " +
(match binding {
"less-than" => "<"
"greater-than" => ">"
"less-than-or-equal" => "<="
_ => ">="
}) +
" a1 }"
} else {
call = native(
"basics",
binding.split("-").map(StringView::to_owned).collect().join("_"),
)
}
}
("char", "to-upper") => {
require([Atom(Scalar::Character)], Atom(Scalar::Character))
call = native("char", "to_upper")
}
("decimal-checked-v1", "from-string-exact") => {
require([Atom(Scalar::Text)], Atom(Scalar::Decimal))
call = "@decimal.from_string_exact(a0.to_string() catch { _ => raise @decimal.InvalidLiteral })"
ctx.imports["decimal"] = true
}
("decimal-checked-v1", "divide") => {
require(
[Atom(Scalar::Decimal), Atom(Scalar::Decimal)],
Atom(Scalar::Decimal),
)
call = native("decimal", "divide")
}
("decimal-checked-v1", "divide-with-default") => {
require(
[Atom(Scalar::Decimal), Atom(Scalar::Decimal), Atom(Scalar::Decimal)],
Atom(Scalar::Decimal),
)
call = native("decimal", "divide_with_default")
}
("decimal", "add" | "sub" | "mul") => {
require([dec, dec], dec)
call = native("decimal", binding)
}
("decimal", "div") => {
require([dec, dec], Optional(dec))
call = native("decimal", "div")
}
("decimal", "negate" | "abs" | "truncate" | "round") => {
require([dec], dec)
call = native("decimal", binding)
}
("decimal", "eq" | "neq" | "gt" | "gte" | "lt" | "lte") => {
require([dec, dec], bool)
call = native("decimal", binding)
}
("decimal", "from-int") => {
require([int], dec)
call = native("decimal", "from_int")
}
("decimal", "zero" | "one" | "minus-one") => {
require([], dec)
call = native(
"decimal",
binding.split("-").map(StringView::to_owned).collect().join("_"),
)
}
("list", "map") => {
guard inputs is [Arrow(a, b), Sequence(c)] &&
result is Sequence(d) &&
a == c &&
b == d else {
raise Invalid("moonbit.sdk_signature", context, canonical)
}
call = native("list", "map")
}
("list", "filter") => {
guard inputs is [Arrow(a, b), Sequence(c)] &&
result is Sequence(d) &&
a == c &&
b == bool &&
c == d else {
raise Invalid("moonbit.sdk_signature", context, canonical)
}
call = native("list", "filter")
}
("list", "sum" | "product") => {
require([Sequence(int)], int)
call = native("list", binding)
}
("list", "length") if inputs.length() == 1 && inputs[0] is Sequence(_) => {
require(inputs, int)
ctx.imports["integer"] = true
call = "@integer.from_int(" + native("list", "length") + ")"
}
("list", "reverse") if inputs.length() == 1 &&
inputs[0] is Sequence(_) &&
result == inputs[0] => call = native("list", "reverse")
("list", "append") if inputs.length() == 2 &&
inputs[0] is Sequence(_) &&
inputs[0] == inputs[1] &&
result == inputs[0] => call = native("list", "append")
("list", "foldl" | "foldr") => {
guard inputs is [Arrow(a, Arrow(b, c)), d, Sequence(e)] &&
a == e &&
b == c &&
c == d &&
result == d else {
raise Invalid("moonbit.sdk_signature", context, canonical)
}
ctx.imports["list"] = true
call = "@list." +
binding +
"(fn(value, state) raise @sdk.SdkError { a0(value)(state) }, a1, a2)"
}
("maybe", "with-default") => {
guard inputs is [a, Optional(b)] && a == b && result == a else {
raise Invalid("moonbit.sdk_signature", context, canonical)
}
call = native("maybe", "with_default")
}
("maybe", "map") => {
guard inputs is [Arrow(a, b), Optional(c)] &&
result is Optional(d) &&
a == c &&
b == d else {
raise Invalid("moonbit.sdk_signature", context, canonical)
}
call = native("maybe", "map")
}
_ => raise Invalid("moonbit.unsupported_sdk", context, canonical)
}
curry_source(ctx, args, result, call)
}