///|
/// An IR type in Elm notation, for messages and tests. A reference shows its local name, so
/// `morphir/SDK:list#list` is `List`. A variable shows in camel case, so `comparable-a` is `comparableA`.
fn elm_text(t : @ir.Type) -> String {
match t {
Function(_, input, output) => {
let left = match input {
Function(_, _, _) => "(" + elm_text(input) + ")"
_ => elm_text(input)
}
left + " -> " + elm_text(output)
}
Reference(_, name, args) =>
[upper_camel(name.local_name), ..args.map(elm_argument)].join(" ")
_ => elm_argument(t)
}
}
///|
/// The Elm text of two types that differ. When the texts are the same, the types differ only in classes, so
/// each text also names the classes of its variables, as `a where a is comparable`.
fn distinct_texts(a : @ir.Type, b : @ir.Type) -> (String, String) {
let (ta, tb) = (elm_text(a), elm_text(b))
if ta != tb {
return (ta, tb)
}
(ta + class_clause(a), tb + class_clause(b))
}
///|
/// ` where a is comparable, b is number` for the variables of `t` that have a `typeclass`, in first-seen order.
/// Empty when no variable has one.
fn class_clause(t : @ir.Type) -> String {
let seen : Map[String, String] = Map([])
to_ty(t, (name, attrs) => {
if !seen.contains(name) &&
attrs.constraints.get(TYPECLASS) is Some(Text(class)) {
seen[name] = lower_camel(parse_name(name)) + " is " + class
}
UnitTy
})
|> ignore
if seen.is_empty() {
""
} else {
" where " + seen.values().collect().join(", ")
}
}
///|
/// A type in an argument position: a function or an applied reference takes parentheses.
fn elm_argument(t : @ir.Type) -> String {
match t {
Variable(_, name) => lower_camel(name)
Reference(_, name, []) => upper_camel(name.local_name)
Reference(_, _, _) | Function(_, _, _) => "(" + elm_text(t) + ")"
Tuple(_, items) => "( " + items.map(elm_text).join(", ") + " )"
Union(_, items) => "(" + items.map(elm_text).join(" | ") + ")"
Unit(_) => "()"
Record(_, fields) if fields.is_empty() => "{}"
Record(_, fields) => "{ " + elm_fields(fields) + " }"
ExtensibleRecord(_, row, fields) =>
"{ " + lower_camel(row) + " | " + elm_fields(fields) + " }"
}
}
///|
fn elm_fields(fields : Map[@ir.Name, @ir.Type]) -> String {
fields
.iter()
.map(field => lower_camel(field.0) + " : " + elm_text(field.1))
.collect()
.join(", ")
}
///|
/// `comparable-a` is `comparableA`.
fn lower_camel(name : @ir.Name) -> String {
let parts = name
.to_segments()
.map(segment => {
match segment {
Word(text) | Initialism(text) => text
}
})
let out = StringBuilder()
for i, part in parts {
out.write_string(if i == 0 { part } else { capitalize(part) })
}
out.to_string()
}
///|
/// `int-8` is `Int8`.
fn upper_camel(name : @ir.Name) -> String {
name
.to_segments()
.map(segment => {
match segment {
Word(text) | Initialism(text) => capitalize(text)
}
})
.join("")
}
///|
fn capitalize(text : String) -> String {
let chars = text.to_array()
if chars.length() > 0 && chars[0] >= 'a' && chars[0] <= 'z' {
chars[0] = (chars[0].to_int() - 32).unsafe_to_char()
}
String::from_array(chars)
}