///|
/// The class table Σ: every class declared so far, by qualified name.
///
/// A port of the reference's `classes.py`. A class has type parameters, and
/// its base is a class type applied to arguments in terms of them.
pub type ClassTable = @sorted_map.SortedMap[String, ClassTableEntry]
///|
pub(all) struct ClassTableEntry {
type_params : Array[String]
own_fields : Array[(String, Type)]
/// The base class, as a class name and its type arguments.
base : (String, Array[Type])?
} derive(Eq, Debug)
///|
/// `builtins.range`, the one class the specification predefines.
pub let range_class : String = "builtins.range"
///|
/// The class table every program starts with: `range`, with its `start` and
/// its `stop`.
pub fn predefined_classes() -> ClassTable {
@sorted_map.SortedMap::new().add(range_class, {
type_params: [],
own_fields: [("start", t_int), ("stop", t_int)],
base: None,
})
}
///|
pub fn empty_class_table() -> ClassTable {
@sorted_map.SortedMap::new()
}
///|
/// The last segment of a class's qualified name: what a message prints.
pub fn short_name(c : String) -> String {
match c.rev_split_once(".") {
Some((_, last)) => last.to_owned()
None => c
}
}
///|
fn entry(sigma : ClassTable, c : String) -> ClassTableEntry {
match sigma.get(c) {
Some(e) => e
None => abort("class \{c} is not in the class table")
}
}
///|
pub fn type_params_of(sigma : ClassTable, c : String) -> Array[String] {
entry(sigma, c).type_params
}
///|
pub fn base_of(sigma : ClassTable, c : String) -> (String, Array[Type])? {
entry(sigma, c).base
}
///|
/// The class and every class it inherits from, nearest first.
pub fn ancestors(sigma : ClassTable, c : String) -> Array[String] {
match entry(sigma, c).base {
None => [c]
Some((b, _)) => [c, ..ancestors(sigma, b)]
}
}
///|
/// Every field, inherited first, generic in the class's type parameters.
pub fn fields(sigma : ClassTable, c : String) -> FieldScheme {
let e = entry(sigma, c)
match e.base {
None => { type_params: e.type_params, fields: e.own_fields, }
Some((b, args)) => {
let inherited = instantiate(fields(sigma, b), args)
{ type_params: e.type_params, fields: [..inherited, ..e.own_fields], }
}
}
}
///|
pub fn field_names(sigma : ClassTable, c : String) -> Array[String] {
fields(sigma, c).fields.map(fn(f) { f.0 })
}
///|
/// A field's type at a class type's arguments.
pub fn field_type(
sigma : ClassTable,
c : String,
args : Array[Type],
x : String,
) -> Type? {
for f in instantiate(fields(sigma, c), args) {
if f.0 == x {
return Some(f.1)
}
}
None
}
///|
pub fn declared_type(
sigma : ClassTable,
c : String,
args : Array[Type],
x : String,
) -> Type {
match field_type(sigma, c, args, x) {
Some(t) => t
None => abort("\{c} has no field \{x}")
}
}
///|
/// Match a constructor's or a pattern's arguments to the class's fields, as
/// pairs in the order the dict the reference builds iterates: the positional
/// ones first, then the keywords as written.
///
/// `None` when the count is wrong, or the keywords are not
/// exactly the fields the positional arguments left over.
pub fn[T] field_map(
sigma : ClassTable,
c : String,
positional : Array[T],
kwd_names : Array[String],
kwd_values : Array[T],
) -> Array[(String, T)]? {
let xs = field_names(sigma, c)
let n = positional.length()
if n + kwd_names.length() != xs.length() {
return None
}
for k in kwd_names {
if !xs[n:].contains(k) {
return None
}
}
let out : Array[(String, T)] = []
for i in 0.. FieldMapFailure {
let xs = field_names(sigma, c)
if n + kwd_names.length() != xs.length() {
return ArityMismatch(expected=xs.length(), given=n + kwd_names.length())
}
let rest : Array[String] = []
for x in xs[n:] {
if !rest.contains(x) {
rest.push(x)
}
}
@basic.sort_names(rest)
UnknownKeywordArgs(rest)
}
///|
/// The value `field_map` paired with field `x`.
pub fn[T] lookup(pairs : Array[(String, T)], x : String) -> T {
for p in pairs {
if p.0 == x {
return p.1
}
}
abort("no argument for field \{x}")
}