///|
fn boundary_type(t : Lowered) -> @execution.ValueType? {
fn required(t : Lowered) -> @execution.ValueType raise {
guard boundary_type(t) is Some(result) else {
raise Failure("boundary function")
}
result
}
Some(
match t {
Atom(SignedInt32) => @execution.Custom("morphir/SDK:int#int-32")
Atom(BoundedInteger(name)) => @execution.Custom("morphir/SDK:int#" + name)
Atom(Integer) => @execution.Int
Atom(Boolean) => @execution.Bool
Atom(Unit) => @execution.Unit
Atom(Decimal) => @execution.Decimal
Atom(Float) | Atom(Float32) => @execution.Float64
Atom(Text) => @execution.Text
Atom(Character) => @execution.Character
Product(items) => @execution.Product(items.map(required))
Shape(_, fields) =>
@execution.Record(fields.map(p => (p.0.to_canonical(), required(p.1))))
Sequence(item) => @execution.Sequence(required(item))
Optional(item) => @execution.Optional(required(item))
Outcome(ok, err) => @execution.Outcome(required(ok), required(err))
Nominal(name) => @execution.Custom(name.to_canonical())
Arrow(_, _) => raise Failure("boundary function")
},
) catch {
_ => None
}
}
///|
fn decoder(t : Lowered) -> String {
"decode_" + hex(lower_key(t))
}
///|
fn encoder(t : Lowered) -> String {
"encode_" + hex(lower_key(t))
}
///|
fn boundary_moon_type(ctx : Lowering, t : Lowered) -> String {
match t {
Shape(name, _) => "@model." + name
Nominal(name) => "@model." + ctx.names.names["type:" + name.to_canonical()]
Product(items) =>
"(" + items.map(t => boundary_moon_type(ctx, t)).join(", ") + ")"
Sequence(item) => "@list.List[" + boundary_moon_type(ctx, item) + "]"
Optional(item) => "Option[" + boundary_moon_type(ctx, item) + "]"
Outcome(ok, err) =>
"Result[" +
boundary_moon_type(ctx, ok) +
", " +
boundary_moon_type(ctx, err) +
"]"
_ => type_source(ctx, t)
}
}
///|
fn invocation_codecs(
ctx : Lowering,
entries : Array[InvocationEntry],
) -> (String, Array[@execution.CustomType]) raise {
let visited = Map([])
let source = []
let definitions = []
let invalid = "raise @execution.Invalid(\"execution.wrong_type\")"
fn emit(t : Lowered) -> Unit raise {
let key = lower_key(t)
if visited.contains(key) {
return
}
visited[key] = true
let (decode, encode) = match t {
Atom(SignedInt32) => {
definitions.push(@execution.CustomType::{
name: "morphir/SDK:int#int-32",
constructors: [
{ name: "morphir/SDK:int#int32", inputs: [@execution.Int], },
],
})
(
"match v { Constructor(\"morphir/SDK:int#int-32\", \"morphir/SDK:int#int32\", [Integer(n)]) => { guard @int.to_int32(@integer.from_bigint(n)) is Some(value) else { raise @execution.Invalid(\"execution.int32_range\") }; value }; _ => " +
invalid +
" }",
"@execution.Constructor(\"morphir/SDK:int#int-32\", \"morphir/SDK:int#int32\", [@execution.Integer(@integer.to_bigint(@int.from_int32(v)))])",
)
}
Atom(BoundedInteger(name)) => {
let compact = name.replace(old="-", new="")
let tag = "morphir/SDK:int#" +
(if name.has_prefix("int-") { compact } else { name })
let owner = "morphir/SDK:int#" + name
let function = if name == "byte" { "byte" } else { compact }
definitions.push(@execution.CustomType::{
name: owner,
constructors: [{ name: tag, inputs: [@execution.Int], }],
})
(
"match v { Constructor(" +
quote(owner) +
", " +
quote(tag) +
", [Integer(n)]) => { guard @int.to_" +
function +
"(@integer.from_bigint(n)) is Some(value) else { raise @execution.Invalid(\"execution." +
compact +
"_range\") }; value }; _ => " +
invalid +
" }",
"@execution.Constructor(" +
quote(owner) +
", " +
quote(tag) +
", [@execution.Integer(@integer.to_bigint(@int.from_" +
function +
"(v)))])",
)
}
Atom(Integer) =>
(
"match v { Integer(n) => @integer.from_bigint(n); _ => " +
invalid +
" }",
"@execution.Integer(@integer.to_bigint(v))",
)
Atom(Boolean) =>
(
"match v { Boolean(b) => b; _ => " + invalid + " }",
"@execution.Boolean(v)",
)
Atom(Unit) =>
("match v { Unit => (); _ => " + invalid + " }", "@execution.Unit")
Atom(Float) =>
(
"match v { Float64(bits) => bits.reinterpret_as_double(); _ => " +
invalid +
" }",
"@execution.Float64(v.reinterpret_as_uint64())",
)
Atom(Float32) => {
ctx.imports["source_float"] = true
(
"match v { Float64(bits) => @source_float.round32(bits.reinterpret_as_double()); _ => " +
invalid +
" }",
"@execution.Float64(@source_float.round32(v).reinterpret_as_uint64())",
)
}
Atom(Decimal) =>
(
"match v { Decimal(n, e) => { let (n, e) = @execution.normalize_decimal(n,e); guard @decimal.from_string(@execution.decimal_source(n,e)) is Some(d) else { " +
invalid +
" }; if d.coefficient != n || d.exponent != e { raise @execution.Invalid(\"execution.decimal_precision\") }; d }; _ => " +
invalid +
" }",
"@execution.Decimal(v.coefficient, v.exponent)",
)
Atom(Text) =>
(
"match v { Text(units) => @sdk.Text::from_units(units.map(n => n.to_uint16())); _ => " +
invalid +
" }",
"@execution.Text(v.to_units().map(n => n.to_int()))",
)
Atom(Character) =>
(
"match v { Character(units) => @sdk.Character::from_units(units.map(n => n.to_uint16())); _ => " +
invalid +
" }",
"@execution.Character(v.to_units().map(n => n.to_int()))",
)
Sequence(item) => {
emit(item)
(
"match v { Sequence(items) => @list.from_array(items.map(" +
decoder(item) +
")); _ => " +
invalid +
" }",
"@execution.Sequence(@list.to_array(v).map(" + encoder(item) + "))",
)
}
Optional(item) => {
emit(item)
(
"match v { Optional(item) => item.map(" +
decoder(item) +
"); _ => " +
invalid +
" }",
"@execution.Optional(v.map(" + encoder(item) + "))",
)
}
Outcome(ok, err) => {
emit(ok)
emit(err)
(
"match v { Outcome(Ok(item)) => Ok(" +
decoder(ok) +
"(item)); Outcome(Err(item)) => Err(" +
decoder(err) +
"(item)); _ => " +
invalid +
" }",
"@execution.Outcome(match v { Ok(item) => Ok(" +
encoder(ok) +
"(item)); Err(item) => Err(" +
encoder(err) +
"(item)) })",
)
}
Product(items) => {
for item in items {
emit(item)
}
let inputs = []
let outputs = []
for i, item in items {
inputs.push(decoder(item) + "(items[" + i.to_string() + "])")
outputs.push(encoder(item) + "(v." + i.to_string() + ")")
}
(
"match v { Product(items) => { if items.length() != " +
items.length().to_string() +
" { " +
invalid +
" }; (" +
inputs.join(", ") +
") }; _ => " +
invalid +
" }",
"@execution.Product([" + outputs.join(", ") + "])",
)
}
Shape(name, fields) => {
let inputs = []
let outputs = []
for (field, item) in fields {
emit(item)
inputs.push(
field_name(ctx, name, field) +
": " +
decoder(item) +
"(record_field(items, " +
quote(field.to_canonical()) +
"))",
)
outputs.push(
"(" +
quote(field.to_canonical()) +
", " +
encoder(item) +
"(v." +
field_name(ctx, name, field) +
"))",
)
}
(
"match v { Record(items) => { if items.length() != " +
fields.length().to_string() +
" { " +
invalid +
" }; @model." +
name +
"::{" +
inputs.join(", ") +
",} }; _ => " +
invalid +
" }",
"@execution.Record([" + outputs.join(", ") + "])",
)
}
Nominal(name) => {
let constructors = ctx.constructors
.keys()
.collect()
.filter(n => ctx.constructors[n].output == t)
constructors.sort_by(fn(a, b) {
a.to_canonical().compare(b.to_canonical())
})
let inputs = []
let outputs = []
let shapes = []
for ctor in constructors {
let entry = ctx.constructors[ctor]
if !entry.public {
raise Invalid(
"moonbit.inaccessible_constructor",
name.to_canonical(),
"Invocation requires public constructors",
)
}
let arguments = []
let patterns = []
let values = []
for i, item in entry.inputs {
emit(item)
arguments.push(decoder(item) + "(items[" + i.to_string() + "])")
patterns.push("x" + i.to_string())
values.push(encoder(item) + "(x" + i.to_string() + ")")
}
let generated = "@model." +
ctx.names.names["constructor:" + ctor.to_canonical()]
inputs.push(
quote(ctor.to_canonical()) +
" => { if items.length() != " +
entry.inputs.length().to_string() +
" { " +
invalid +
" }; " +
generated +
(if arguments.is_empty() {
""
} else {
"(" + arguments.join(", ") + ")"
}) +
" }",
)
outputs.push(
generated +
(if patterns.is_empty() {
""
} else {
"(" + patterns.join(", ") + ")"
}) +
" => @execution.Constructor(" +
quote(name.to_canonical()) +
", " +
quote(ctor.to_canonical()) +
", [" +
values.join(", ") +
"])",
)
shapes.push(@execution.ConstructorType::{
name: ctor.to_canonical(),
inputs: entry.inputs.map(item => boundary_type(item).unwrap()),
})
}
definitions.push(@execution.CustomType::{
name: name.to_canonical(),
constructors: shapes,
})
(
"match v { Constructor(owner, tag, items) => { if owner != " +
quote(name.to_canonical()) +
" { " +
invalid +
" }; match tag { " +
inputs.join("; ") +
"; _ => " +
invalid +
" } }; _ => " +
invalid +
" }",
"match v { " + outputs.join("; ") + " }",
)
}
Arrow(_, _) =>
raise Invalid(
"moonbit.boundary_function", key, "Functions cannot cross the invocation boundary",
)
}
let moon_type = boundary_moon_type(ctx, t)
source.push(
"///|\nfn " +
decoder(t) +
"(v : @execution.Value) -> " +
moon_type +
" raise { " +
decode +
" }\n///|\nfn " +
encoder(t) +
"(v : " +
moon_type +
") -> @execution.Value { " +
encode +
" }\n",
)
}
for entry in entries {
let d = ctx.values[@ir.FQName::parse(entry.fqname)]
for (_, t) in d.inputs {
emit(t)
}
emit(d.output)
}
definitions.sort_by(fn(a, b) { a.name.compare(b.name) })
(source.join("\n"), definitions)
}
///|
fn boundary_supported(
ctx : Lowering,
t : Lowered,
seen : Map[String, Bool],
) -> Bool {
let key = lower_key(t)
if seen.contains(key) {
return true
}
seen[key] = true
match t {
Arrow(_, _) => false
Atom(_) => true
Product(items) => items.all(t => boundary_supported(ctx, t, seen))
Shape(_, fields) => fields.all(p => boundary_supported(ctx, p.1, seen))
Sequence(t) | Optional(t) => boundary_supported(ctx, t, seen)
Outcome(ok, err) =>
boundary_supported(ctx, ok, seen) && boundary_supported(ctx, err, seen)
Nominal(_) =>
ctx.constructors
.values()
.all(c => {
c.output != t ||
(c.public && c.inputs.all(t => boundary_supported(ctx, t, seen)))
})
}
}