///|
fn scalar_type(tpe : @ir.Type, context : String) -> Scalar raise {
match tpe {
Unit(attrs) => {
if !attrs.constraints.is_empty() {
raise Invalid(
"moonbit.unsupported_constraint", context, "Scalar constraints are not implemented",
)
}
Unit
}
Reference(attrs, name, args) => {
validate_fqname(name)
if !attrs.constraints.is_empty() || !args.is_empty() {
raise Invalid(
"moonbit.unsupported_type", context, "Constrained or parameterized scalar type",
)
}
match name.to_canonical() {
"morphir/SDK:basics#int" => Integer
"morphir/SDK:int#int-32" => SignedInt32
"morphir/SDK:int#int-16" => BoundedInteger("int-16")
"morphir/SDK:int#int-64" => BoundedInteger("int-64")
"morphir/SDK:int#byte" => BoundedInteger("byte")
"morphir/SDK:int#uint-16" => BoundedInteger("uint-16")
"morphir/SDK:int#uint-32" => BoundedInteger("uint-32")
"morphir/SDK:int#uint-64" => BoundedInteger("uint-64")
"morphir/SDK:basics#float" => Float
"morphir/moonbit-source:float32#float32" => Float32
"morphir/SDK:basics#bool" => Boolean
"morphir/SDK:string#string" => Text
"morphir/SDK:char#char" => Character
"morphir/SDK:decimal#decimal" => Decimal
_ =>
raise Invalid(
"moonbit.unsupported_type",
context,
name.to_canonical(),
)
}
}
Variable(_, _) =>
raise Invalid(
"moonbit.ambiguous_type", context, "A concrete scalar type is required",
)
_ =>
raise Invalid(
"moonbit.unsupported_type", context, "This slice supports scalar types only",
)
}
}
///|
fn moon_type(scalar : Scalar, imports : Map[String, Bool]) -> String {
match scalar {
SignedInt32 => {
imports["int"] = true
"@int.Int32Value"
}
BoundedInteger(name) => {
imports["int"] = true
match name {
"int-16" => "@int.Int16Value"
"int-64" => "@int.Int64Value"
"byte" => "@int.ByteValue"
"uint-16" => "@int.UInt16Value"
"uint-32" => "@int.UInt32Value"
"uint-64" => "@int.UInt64Value"
_ => abort("admitted integer boundary")
}
}
Integer => {
imports["integer"] = true
"@integer.Integer"
}
Decimal => {
imports["decimal"] = true
"@decimal.Decimal"
}
Text => "@sdk.Text"
Character => "@sdk.Character"
Float | Float32 => "Double"
Boolean => "Bool"
Unit => "Unit"
}
}
///|
fn units(text : String) -> String {
"[" +
text
.code_units()
.iter()
.map(unit => "(" + unit.to_int().to_string() + ").to_uint16()")
.collect()
.join(", ") +
"]"
}
///|
/// Normalize only at numerical SDK construction; the source lexeme stays in Ion metadata.
fn decimal_constant(text : String, context : String) -> String raise {
ignore(@wire.decimal(Json::string(text)))
let scientific = text
.to_lower()
.split("e")
.map(StringView::to_owned)
.collect()
let exponent = if scientific.length() == 2 {
@bigint.BigInt::from_string(scientific[1].replace(old="+", new=""))
} else {
@bigint.BigInt::from_int(0)
}
if exponent.compare_int(-10000) < 0 || exponent.compare_int(10000) > 0 {
raise Invalid(
"moonbit.literal_out_of_range", context, "Decimal exponent exceeds construction budget",
)
}
let components = scientific[0].split(".").map(StringView::to_owned).collect()
let fraction = if components.length() == 2 { components[1] } else { "" }
let coefficient = @bigint.BigInt::from_string(
(components[0] + fraction).replace(old="+", new=""),
)
if coefficient.is_zero() {
return "0"
}
let negative = coefficient.compare_int(0) < 0
let digits = (if negative { -coefficient } else { coefficient }).to_string()
let mut end = digits.length()
let mut scale = exponent.to_int() - fraction.length()
while end > 1 && digits.code_unit_at(end - 1).to_int() == 48 {
end -= 1
scale += 1
}
if scale < -32 {
raise Invalid(
"moonbit.literal_precision_loss", context, "SDK Decimal would truncate digits below exponent -32",
)
}
let significant = digits[:end].to_owned()
let canonical = (if negative { "-" } else { "" }) +
significant[:1].to_owned() +
(if end > 1 { "." + significant[1:].to_owned() } else { "" }) +
"e" +
(scale + end - 1).to_string()
let number = match @decimal.from_string(canonical) {
Some(value) => value
None =>
raise Invalid(
"moonbit.literal_out_of_range", context, "SDK Decimal rejected normalized construction",
)
}
let normalized_coefficient = @bigint.BigInt::from_string(
(if negative { "-" } else { "" }) + significant,
)
if number.coefficient != normalized_coefficient || number.exponent != scale {
raise Invalid(
"moonbit.literal_precision_loss", context, "SDK Decimal changed the exact coefficient or exponent",
)
}
canonical
}
///|
fn check_annotation(
attrs : @ir.ValueAttributes,
expected : Scalar,
context : String,
) -> Unit raise {
if attrs.inferred_type is Some(tpe) {
if scalar_type(tpe, context) != expected {
raise Invalid(
"moonbit.type_mismatch", context, "Inferred type contradicts the declared type",
)
}
}
}
///|
fn scalar_literal(
body : @ir.Value,
expected : Scalar,
imports : Map[String, Bool],
context : String,
) -> String raise {
match body {
Literal(attrs, literal) => {
check_annotation(attrs, expected, context)
let (actual, source) = match literal {
BoolLiteral(value) => (Scalar::Boolean, value.to_string())
IntegerLiteral(text) => {
ignore(@integer.parse(text))
imports["integer"] = true
(Scalar::Integer, "@integer.parse(" + quote(text) + ")")
}
DecimalLiteral(text) => {
imports["decimal"] = true
let construction = quote(decimal_constant(text, context))
(
Scalar::Decimal,
"match @decimal.from_string(" +
construction +
") { Some(value) => value; None => raise @sdk.InvalidNumber(" +
construction +
") }",
)
}
StringLiteral(text) =>
(Scalar::Text, "@sdk.Text::from_units(" + units(text) + ")")
CharLiteral(character) =>
(
Scalar::Character,
"@sdk.Character::from_units(" + units(character.to_string()) + ")",
)
FloatLiteral(value) => {
if value.is_nan() || value.is_inf() {
raise Invalid("moonbit.invalid_literal", context, "Nonfinite Float")
}
let literal = "(" +
value.reinterpret_as_uint64().to_string() +
"UL).reinterpret_as_double()"
if expected == Scalar::Float32 {
imports["source_float"] = true
(Scalar::Float32, "@source_float.round32(" + literal + ")")
} else {
(Scalar::Float, literal)
}
}
DocumentLiteral(_) =>
raise Invalid(
"moonbit.unsupported_literal", context, "Document literals are not implemented",
)
}
if actual != expected {
raise Invalid(
"moonbit.type_mismatch", context, "Literal and declared output types differ",
)
}
source
}
Unit(attrs) => {
check_annotation(attrs, expected, context)
if expected != Scalar::Unit {
raise Invalid(
"moonbit.type_mismatch", context, "Unit body has a non-Unit declaration",
)
}
"()"
}
Hole(_, _, _) =>
raise Invalid("moonbit.incomplete_body", context, "Hole expression")
_ =>
raise Invalid(
"moonbit.unsupported_expression", context, "Expected a scalar literal or Unit",
)
}
}