///|
/// Why an operation on values failed. The interpreter adds the span and
/// applies the mode rule (spec M4a §4): a `TypeFailure` is MISSING in
/// permissive mode and a type error in strict mode; a `DataFailure` (a bad
/// value of the right type, such as a division by zero) is an error in both.
/// `Unsupported` marks a valid operation that this version does not evaluate
/// (spec M4d2 §4.1).
pub(all) suberror OpFailure {
TypeFailure(String)
DataFailure(String)
Unsupported(String)
} derive(Debug, Eq)
///|
fn boxed(v : @value.Value) -> @value.Value raise OpFailure {
v.lower() catch {
_ => raise TypeFailure("a symbol without text")
}
}
///|
/// Matches in tuple declaration order; repeated fields never shadow.
pub(all) enum FieldMatch {
NoField
One(@value.Value)
Many(@value.Value, count~ : Int32)
} derive(Debug, Eq)
///|
/// All fields matching a name, ignoring ASCII case for regular identifiers.
pub fn field_match(
t : @value.Tuple,
name : String,
delimited~ : Bool,
) -> FieldMatch {
let wanted = if delimited { name } else { name.to_lower() }
let mut found = NoField
for (key, value) in t.fields() {
let key = if delimited { key } else { key.to_lower() }
if key == wanted {
found = match found {
NoField => One(value)
One(first) => Many(first, count=2)
Many(first, count~) => Many(first, count=count + 1)
}
}
}
found
}
///|
/// The field `name` of a tuple. Without `delimited` the match ignores ASCII
/// case. With `unique`, repeated matches are a type failure.
pub fn field(
v : @value.Value,
name : String,
delimited~ : Bool,
unique~ : Bool,
) -> @value.Value raise OpFailure {
match boxed(v) {
Null => Missing
Tuple(t) =>
match field_match(t, name, delimited~) {
NoField => raise TypeFailure("no field \{name}")
One(v) => v
Many(v, count~) =>
if unique {
raise TypeFailure(
"field \{name} is ambiguous: the tuple has \{count} matching fields",
)
} else {
v
}
}
_ => raise TypeFailure("field \{name} of a value that is not a tuple")
}
}
///|
/// The element `i` of a list or the value for the key `i` of a map. A NULL or
/// MISSING map key gives NULL or MISSING. A tuple is indexed by a string only
/// when `named` is true: `t[s]` is the path `t.s` for a string literal `s`
/// (or a CAST to a string), not for any expression.
pub fn index(
v : @value.Value,
i : @value.Value,
named~ : Bool,
unique~ : Bool,
) -> @value.Value raise OpFailure {
match (boxed(v), boxed(i)) {
(Null, _) => Missing
(List(items), Int(n)) =>
if n >= 0L && n < items.length().to_int64() {
items[n.to_int()]
} else {
raise TypeFailure("list index \{n} out of range")
}
(Tuple(_) as t, String(name)) if named =>
field(t, name, delimited=true, unique~)
(Map(_), Null) => Null
(Map(_), Missing) => Missing
(Map(m), key) =>
match map_lookup(m, key) {
Some(v) => v
None => raise TypeFailure("no such key in the map")
}
_ => raise TypeFailure("a value that cannot be indexed that way")
}
}
///|
/// `[*]`: the items of a list or bag.
pub fn all_elements(v : @value.Value) -> Array[@value.Value] raise OpFailure {
match boxed(v) {
List(items) => items
Bag(b) => Array::from_iter(b.items().iter())
_ => raise TypeFailure("[*] of a value that is not a list or bag")
}
}
///|
/// `.*`: the values of a tuple.
pub fn all_fields(v : @value.Value) -> Array[@value.Value] raise OpFailure {
match boxed(v) {
Tuple(t) => t.fields().iter().map(f => f.1).collect()
_ => raise TypeFailure(".* of a value that is not a tuple")
}
}