Skip to main content

elly_core/
builtins.rs

1//! Native builtins of the reserved `__` namespace: the [`Builtin`] enum, its
2//! names and arities, and the [`MODULES`] table grouping them into modules.
3//! Running one is the evaluator's (`eval/builtin.rs`).
4
5use crate::sym::{self, Sym};
6
7/// A native builtin operation in the reserved `__` namespace. All are curried.
8#[allow(clippy::enum_variant_names)]
9#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
10pub enum Builtin {
11    IntAdd,
12    IntSub,
13    IntMul,
14    IntPow,
15    IntDivrem,
16    IntFor,
17    /// Relational tests on two integers, each returning a [`Bool`](Builtin::Bool)
18    /// iota. These feed `if` through receiver dispatch (`n.lt 60`).
19    IntEq,
20    IntLt,
21    IntLe,
22    IntGt,
23    IntGe,
24    ErrRaise,
25    ErrCatch,
26    Match,
27    ListSize,
28    ListGet,
29    ListAppend,
30    ListWith,
31    ListSplit,
32    Eq,
33    MapNil,
34    MapWith,
35    MapGet,
36    MapGets,
37    MapFor,
38    MapMerge,
39    MapCat,
40    MapWithout,
41    MapSize,
42    IntStr,
43    IntParse,
44    StrPack,
45    StrUnpack,
46    SymFrom,
47    SymStr,
48    ModName,
49    /// `__Mod.exports <Mod>`: returns the module's exported item names as a
50    /// list of symbols sorted by text. A local, private iota, or non-re-exported import
51    /// is not an export. Reads the frozen table without evaluating item bodies.
52    /// Non-modules raise `.not_a_module`.
53    ModExports,
54    /// `__proto v`: the prototype of any value: an object's own, or the
55    /// builtin module for other kinds. A bare builtin that cuts across kinds.
56    /// Total, it serves as the classifier `as <Proto>` and the basis for
57    /// object design. It grants no authority to construct or unwrap.
58    Proto,
59    /// `__repr v`: the display string of any value, matching
60    /// [`to_display`](crate::Value::to_display). A bare builtin that cuts across kinds.
61    Repr,
62    /// Object constructor `(prototype, payload)`. Not reachable by name;
63    /// only accessible via the [`__new`](crate::HomeLeaf::New) leaf, which
64    /// supplies the prototype from the current module.
65    ObjNew,
66    /// Object payload reader `(prototype, object)`. Accessible only via the
67    /// [`__value`](crate::HomeLeaf::Value) leaf. Refuses objects whose prototype
68    /// does not match the supplied one (`.foreign_object`), preventing methods
69    /// from unwrapping foreign objects.
70    ObjValue,
71    // The **builtin modules** — a 0-arity builtin each, naming the module that
72    // gathers the operations above as its items (see [`MODULES`]).
73    Int,
74    Str,
75    Sym,
76    List,
77    Map,
78    Err,
79    Mod,
80    /// `__Bool`, the two-iota boolean module. Its `const` declarations are
81    /// [`true`](Builtin::BoolTrue) and [`false`](Builtin::BoolFalse), name-sorted
82    /// so `false` is iota index 0 and `true` is index 1 (see `bool_value`); it
83    /// re-exports each as an item forwarding to it — so `Bool.true` resolves like a
84    /// user module's re-exported iota, while `__true` / `__false` name them
85    /// directly (see [`from_name`](Builtin::from_name)). Its logic operations
86    /// [`and`](Builtin::BoolAnd) / [`or`](Builtin::BoolOr) / [`not`](Builtin::BoolNot)
87    /// compose them; the relational `__Int.*` tests produce them.
88    Bool,
89    /// `__Bool.true` / `__true`: the true iota, an arity-0 value builtin that fires
90    /// on reference to `bool_value(ctx, true)`. Both spellings fold to this one op,
91    /// so they are the *same* iota of the same cached `__Bool` instance.
92    BoolTrue,
93    /// `__Bool.false` / `__false`: the false iota — [`BoolTrue`](Builtin::BoolTrue)'s
94    /// counterpart.
95    BoolFalse,
96    /// `__Bool.and` / `or` / `not` — strict logic over two [`Bool`](Builtin::Bool)
97    /// iotas (one, for `not`); a non-`Bool` argument raises `.not_a_bool`.
98    BoolAnd,
99    BoolOr,
100    BoolNot,
101    /// `__Fun`, the prototype of every callable kind — a closure, a builtin, and
102    /// a host function. It has no items yet: `docs/todo/elly-function-arity.md`
103    /// is what gives it `nargs` / `nfree` / `nrets`. It exists for the totality of
104    /// [`Proto`](Builtin::Proto), which is enough on its own to make it a module.
105    Fun,
106    /// `__Unit`, the prototype of the [unit](crate::Value::Unit) value `()`. It has no
107    /// items — a nominal type only, exactly like [`Fun`](Builtin::Fun) — and
108    /// exists so every value, unit included, answers [`Proto`](Builtin::Proto).
109    Unit,
110}
111
112/// A builtin module's item table: `(item name, the builtin it names)`, **sorted
113/// by name** — and so by [`Sym`], since fixed ids follow text order: the order a
114/// module's members take in a chunk, and what [`member`](Builtin::member)
115/// binary-searches.
116pub(crate) type Members = &'static [(Sym, Builtin)];
117
118/// A builtin module's `const` declarations — its **iotas** — as name-sorted fixed
119/// symbols. Only [`__Bool`](Builtin::Bool) has any today; every other module's is
120/// empty. The names are the module's iotas in the `Elly` chunk, which is what an iota's
121/// `<const .name>` rendering reads (see [`Value::write_display`]).
122pub(crate) type Iotas = &'static [Sym];
123
124/// The **builtin modules**, in cache-slot order (see [`Ctx::builtin_module`]):
125/// the 0-arity builtin naming each module, its shadowable bare alias, and its
126/// items. The `__<Ns>_<op>` naming convention the flat builtins used to spell out
127/// lives here instead — `__Int.add` is the module `__Int`'s item `add` — so this
128/// table is the whole of the `__` namespace's structure.
129///
130/// `__match`, `__eq` and `__proto` are not in it: they cut across kinds and
131/// belong to no module.
132pub(crate) const MODULES: &[(Builtin, Sym, Members, Iotas)] = &[
133    (
134        Builtin::Int,
135        Sym::MOD_INT,
136        &[
137            (Sym::ADD, Builtin::IntAdd),
138            (Sym::DIVREM, Builtin::IntDivrem),
139            (Sym::EQ, Builtin::IntEq),
140            (Sym::FOR, Builtin::IntFor),
141            (Sym::GE, Builtin::IntGe),
142            (Sym::GT, Builtin::IntGt),
143            (Sym::LE, Builtin::IntLe),
144            (Sym::LT, Builtin::IntLt),
145            (Sym::MUL, Builtin::IntMul),
146            (Sym::PARSE, Builtin::IntParse),
147            (Sym::POW, Builtin::IntPow),
148            (Sym::STR, Builtin::IntStr),
149            (Sym::SUB, Builtin::IntSub),
150        ],
151        &[],
152    ),
153    (
154        Builtin::Str,
155        Sym::MOD_STR,
156        &[
157            (Sym::PACK, Builtin::StrPack),
158            (Sym::UNPACK, Builtin::StrUnpack),
159        ],
160        &[],
161    ),
162    (
163        Builtin::Sym,
164        Sym::MOD_SYM,
165        &[(Sym::FROM, Builtin::SymFrom), (Sym::STR, Builtin::SymStr)],
166        &[],
167    ),
168    (
169        Builtin::List,
170        Sym::MOD_LIST,
171        &[
172            (Sym::APPEND, Builtin::ListAppend),
173            (Sym::GET, Builtin::ListGet),
174            (Sym::SIZE, Builtin::ListSize),
175            (Sym::SPLIT, Builtin::ListSplit),
176            (Sym::WITH, Builtin::ListWith),
177        ],
178        &[],
179    ),
180    (
181        Builtin::Map,
182        Sym::MOD_MAP,
183        &[
184            (Sym::CAT, Builtin::MapCat),
185            (Sym::FOR, Builtin::MapFor),
186            (Sym::GET, Builtin::MapGet),
187            (Sym::GETS, Builtin::MapGets),
188            (Sym::MERGE, Builtin::MapMerge),
189            (Sym::NIL, Builtin::MapNil),
190            (Sym::SIZE, Builtin::MapSize),
191            (Sym::WITH, Builtin::MapWith),
192            (Sym::WITHOUT, Builtin::MapWithout),
193        ],
194        &[],
195    ),
196    (
197        Builtin::Err,
198        Sym::MOD_ERR,
199        &[
200            (Sym::CATCH, Builtin::ErrCatch),
201            (Sym::RAISE, Builtin::ErrRaise),
202        ],
203        &[],
204    ),
205    (
206        Builtin::Mod,
207        Sym::MOD_MOD,
208        &[
209            (Sym::EXPORTS, Builtin::ModExports),
210            (Sym::NAME, Builtin::ModName),
211        ],
212        &[],
213    ),
214    // The two-iota boolean module. `true` / `false` are both iotas (`const`
215    // declarations, populating the iota table for rendering) and items that
216    // re-export them — the same double declaration a user's `false = const` makes
217    // — so `Bool.true` resolves through the ordinary member path while `__true`
218    // names the iota directly. The iotas are name-sorted: `false` is index 0,
219    // `true` index 1 (see [`bool_value`]).
220    (
221        Builtin::Bool,
222        Sym::MOD_BOOL,
223        &[
224            (Sym::AND, Builtin::BoolAnd),
225            (Sym::FALSE, Builtin::BoolFalse),
226            (Sym::NOT, Builtin::BoolNot),
227            (Sym::OR, Builtin::BoolOr),
228            (Sym::TRUE, Builtin::BoolTrue),
229        ],
230        &[Sym::FALSE, Sym::TRUE],
231    ),
232    // No items yet, and a module all the same: it is the prototype the three
233    // callable kinds answer `__proto` with (see [`Builtin::Fun`]).
234    (Builtin::Fun, Sym::MOD_FUN, &[], &[]),
235    // Likewise item-less: the nominal type of the unit value `()`.
236    (Builtin::Unit, Sym::MOD_UNIT, &[], &[]),
237];
238
239/// How many builtin modules there are — the width of the [`Ctx`] cache.
240pub(crate) const NMODULES: usize = MODULES.len();
241
242/// Value kinds with builtin module prototypes, allowing `as <Proto>` to
243/// compile to a discriminant test (see [`Pat::TypeKind`]).
244#[derive(Debug, Clone, Copy, PartialEq, Eq)]
245pub enum TyKind {
246    Int,
247    Sym,
248    Str,
249    List,
250    Map,
251    Fun,
252    /// Module value (prototype `__Mod`).
253    Mod,
254    /// Unit value (prototype `__Unit`).
255    Unit,
256    /// Boolean (iota of `__Bool`).
257    Bool,
258}
259
260impl TyKind {
261    /// Spelling used in `as <Proto>` surface and dumps.
262    pub fn name(self) -> &'static str {
263        match self {
264            TyKind::Int => "Int",
265            TyKind::Sym => "Sym",
266            TyKind::Str => "Str",
267            TyKind::List => "List",
268            TyKind::Map => "Map",
269            TyKind::Fun => "Fun",
270            TyKind::Mod => "Mod",
271            TyKind::Unit => "Unit",
272            TyKind::Bool => "Bool",
273        }
274    }
275}
276
277impl Builtin {
278    /// Every nameable builtin in declaration order; the source of truth for
279    /// tooling enumerating the `__` namespace. Keep in sync with the enum.
280    ///
281    /// [`ObjNew`](Builtin::ObjNew) and [`ObjValue`](Builtin::ObjValue) are
282    /// omitted as no name resolves to them; they only render via [`name`](Builtin::name).
283    pub const ALL: &'static [Builtin] = &[
284        Builtin::IntAdd,
285        Builtin::IntSub,
286        Builtin::IntMul,
287        Builtin::IntPow,
288        Builtin::IntDivrem,
289        Builtin::IntFor,
290        Builtin::IntEq,
291        Builtin::IntLt,
292        Builtin::IntLe,
293        Builtin::IntGt,
294        Builtin::IntGe,
295        Builtin::ErrRaise,
296        Builtin::ErrCatch,
297        Builtin::Match,
298        Builtin::ListSize,
299        Builtin::ListGet,
300        Builtin::ListAppend,
301        Builtin::ListWith,
302        Builtin::ListSplit,
303        Builtin::Eq,
304        Builtin::MapNil,
305        Builtin::MapWith,
306        Builtin::MapGet,
307        Builtin::MapGets,
308        Builtin::MapFor,
309        Builtin::MapMerge,
310        Builtin::MapCat,
311        Builtin::MapWithout,
312        Builtin::MapSize,
313        Builtin::IntStr,
314        Builtin::IntParse,
315        Builtin::StrPack,
316        Builtin::StrUnpack,
317        Builtin::SymFrom,
318        Builtin::SymStr,
319        Builtin::ModName,
320        Builtin::ModExports,
321        Builtin::Proto,
322        Builtin::Repr,
323        Builtin::Int,
324        Builtin::Str,
325        Builtin::Sym,
326        Builtin::List,
327        Builtin::Map,
328        Builtin::Err,
329        Builtin::Mod,
330        Builtin::Bool,
331        Builtin::BoolTrue,
332        Builtin::BoolFalse,
333        Builtin::BoolAnd,
334        Builtin::BoolOr,
335        Builtin::BoolNot,
336        Builtin::Fun,
337        Builtin::Unit,
338    ];
339
340    /// Resolves a `__`-name to its builtin: a bare name (`__eq`), a builtin
341    /// module (`__Int`), or a module member in dotted form (`__Int.add`).
342    ///
343    /// Since Muon symbols cannot contain dots, the parser reaches members via
344    /// [`member`](Self::member) after folding. This method is used by tooling and
345    /// the `Builtin::ALL` round-trip.
346    pub fn from_name(name: &str) -> Option<Builtin> {
347        if let Some((ns, item)) = name.split_once('.') {
348            return Builtin::from_name(ns)?.member(sym::fixed(item)?);
349        }
350        Some(match name {
351            "__match" => Builtin::Match,
352            "__eq" => Builtin::Eq,
353            "__proto" => Builtin::Proto,
354            "__repr" => Builtin::Repr,
355            // The two boolean iotas name themselves in the closed `__` namespace,
356            // beside `Bool.true` / `Bool.false`: one Muon `<sym>` each, so a
357            // comparand (`&(= __true)`) can name one where the dotted member is
358            // two items. Both spellings fold to the same op, hence the same iota.
359            "__true" => Builtin::BoolTrue,
360            "__false" => Builtin::BoolFalse,
361            "__Int" => Builtin::Int,
362            "__Str" => Builtin::Str,
363            "__Sym" => Builtin::Sym,
364            "__List" => Builtin::List,
365            "__Map" => Builtin::Map,
366            "__Err" => Builtin::Err,
367            "__Mod" => Builtin::Mod,
368            "__Bool" => Builtin::Bool,
369            "__Fun" => Builtin::Fun,
370            "__Unit" => Builtin::Unit,
371            _ => return None,
372        })
373    }
374
375    /// The builtin module this bare alias names (e.g. `Int` → `__Int`).
376    /// Aliases are the resolver's bottom tier (the prelude), consulted last to
377    /// ensure they are shadowed by lexical bindings.
378    pub fn from_alias(name: Sym) -> Option<Builtin> {
379        MODULES
380            .iter()
381            .find(|(_, alias, _, _)| *alias == name)
382            .map(|(op, _, _, _)| *op)
383    }
384
385    /// The items of this builtin module, or `None` if not a module.
386    pub(crate) fn items(self) -> Option<Members> {
387        MODULES
388            .iter()
389            .find(|(op, _, _, _)| *op == self)
390            .map(|(_, _, items, _)| *items)
391    }
392
393    /// The cache slot for this builtin module in a [`Ctx`], or `None`.
394    /// Positional and stable for the life of the process.
395    pub(crate) fn module_slot(self) -> Option<usize> {
396        MODULES.iter().position(|(op, _, _, _)| *op == self)
397    }
398
399    /// The item of this builtin module matching `name`, if any. Returns `None`
400    /// if the name is not held or the builtin is not a module. Safe to call on
401    /// any callee of a call spine.
402    pub fn member(self, name: Sym) -> Option<Builtin> {
403        let items = self.items()?;
404        items
405            .binary_search_by_key(&name, |(item, _)| *item)
406            .ok()
407            .map(|i| items[i].1)
408    }
409
410    /// The value kind this builtin module is the prototype of, enabling `as <Proto>`
411    /// patterns to compile to discriminant tests. Returns `None` if not a module
412    /// or for `__Err` (no value has `__Err` as its prototype).
413    pub fn proto_kind(self) -> Option<TyKind> {
414        Some(match self {
415            Builtin::Int => TyKind::Int,
416            Builtin::Str => TyKind::Str,
417            Builtin::Sym => TyKind::Sym,
418            Builtin::List => TyKind::List,
419            Builtin::Map => TyKind::Map,
420            Builtin::Mod => TyKind::Mod,
421            Builtin::Fun => TyKind::Fun,
422            Builtin::Unit => TyKind::Unit,
423            Builtin::Bool => TyKind::Bool,
424            _ => return None,
425        })
426    }
427
428    /// Number of arguments the op consumes before firing. Arity 0 ops are bare
429    /// values that fire on reference (e.g. `__Map.nil` or builtin modules).
430    pub(crate) fn arity(self) -> usize {
431        match self {
432            Builtin::MapNil
433            | Builtin::Int
434            | Builtin::Str
435            | Builtin::Sym
436            | Builtin::List
437            | Builtin::Map
438            | Builtin::Err
439            | Builtin::Mod
440            | Builtin::Bool
441            // The two boolean iotas are values: they fire on reference.
442            | Builtin::BoolTrue
443            | Builtin::BoolFalse
444            | Builtin::Fun
445            | Builtin::Unit => 0,
446            Builtin::Proto
447            | Builtin::Repr
448            | Builtin::ErrRaise
449            | Builtin::ListSize
450            | Builtin::MapSize
451            | Builtin::IntStr
452            | Builtin::IntParse
453            | Builtin::StrPack
454            | Builtin::StrUnpack
455            | Builtin::SymFrom
456            | Builtin::SymStr
457            | Builtin::BoolNot
458            | Builtin::ModName
459            | Builtin::ModExports => 1,
460            Builtin::IntAdd
461            | Builtin::IntSub
462            | Builtin::IntMul
463            | Builtin::IntPow
464            | Builtin::IntDivrem
465            | Builtin::IntEq
466            | Builtin::IntLt
467            | Builtin::IntLe
468            | Builtin::IntGt
469            | Builtin::IntGe
470            | Builtin::BoolAnd
471            | Builtin::BoolOr => 2,
472            // `__match clauses val`: strict in the clause list and the subject.
473            // `__new proto data` / `__value proto obj` — the prototype is the
474            // first argument, and the home-module leaf has already supplied it.
475            Builtin::ObjNew
476            | Builtin::ObjValue
477            | Builtin::Match
478            | Builtin::ErrCatch
479            | Builtin::ListGet
480            | Builtin::ListAppend
481            | Builtin::ListSplit
482            | Builtin::MapGet
483            | Builtin::MapGets
484            | Builtin::MapCat
485            | Builtin::MapWithout => 2,
486            Builtin::ListWith | Builtin::MapWith | Builtin::MapFor | Builtin::MapMerge => 3,
487            Builtin::IntFor | Builtin::Eq => 4,
488        }
489    }
490
491    /// Whether argument `i` is a callback that is only ever applied, never stored.
492    /// Literals in such positions use [`Plan::Chain`](crate::code::Plan::Chain) to share the defining
493    /// environment instead of copying a frame.
494    ///
495    /// Must stay in sync with [`invoke`]: arguments listed here must only be
496    /// reached via `apply1`/`apply_n`.
497    pub(crate) fn applies_arg(self, i: usize) -> bool {
498        match self {
499            // `__Err.catch handler thunk` — both are run by the catch.
500            Builtin::ErrCatch => i == 0 || i == 1,
501            // `__eq a b then else` — one of the two branches is forced.
502            Builtin::Eq => i == 2 || i == 3,
503            // `__Int.for from to init f` — the fold callback.
504            Builtin::IntFor => i == 3,
505            // `__Map.for m init f` / `__Map.merge m1 m2 f` — the fold callbacks.
506            Builtin::MapFor | Builtin::MapMerge => i == 2,
507            _ => false,
508        }
509    }
510
511    /// Whether argument `i` is a list of callbacks applied in order (e.g.
512    /// `__match` clauses). Literal abstractions in such lists use [`Plan::Chain`](crate::code::Plan::Chain).
513    pub(crate) fn applies_elements_of(self, i: usize) -> bool {
514        matches!(self, Builtin::Match) && i == 0
515    }
516
517    /// The reserved `__`-name for this builtin. Module members are dotted
518    /// (e.g. `__Int.add`).
519    pub fn name(self) -> &'static str {
520        match self {
521            Builtin::IntAdd => "__Int.add",
522            Builtin::IntSub => "__Int.sub",
523            Builtin::IntMul => "__Int.mul",
524            Builtin::IntPow => "__Int.pow",
525            Builtin::IntDivrem => "__Int.divrem",
526            Builtin::IntFor => "__Int.for",
527            Builtin::IntEq => "__Int.eq",
528            Builtin::IntLt => "__Int.lt",
529            Builtin::IntLe => "__Int.le",
530            Builtin::IntGt => "__Int.gt",
531            Builtin::IntGe => "__Int.ge",
532            Builtin::ErrRaise => "__Err.raise",
533            Builtin::ErrCatch => "__Err.catch",
534            Builtin::Match => "__match",
535            Builtin::ListSize => "__List.size",
536            Builtin::ListGet => "__List.get",
537            Builtin::ListAppend => "__List.append",
538            Builtin::ListWith => "__List.with",
539            Builtin::ListSplit => "__List.split",
540            Builtin::Eq => "__eq",
541            Builtin::MapNil => "__Map.nil",
542            Builtin::MapWith => "__Map.with",
543            Builtin::MapGet => "__Map.get",
544            Builtin::MapGets => "__Map.gets",
545            Builtin::MapFor => "__Map.for",
546            Builtin::MapMerge => "__Map.merge",
547            Builtin::MapCat => "__Map.cat",
548            Builtin::MapWithout => "__Map.without",
549            Builtin::MapSize => "__Map.size",
550            Builtin::IntStr => "__Int.str",
551            Builtin::IntParse => "__Int.parse",
552            Builtin::StrPack => "__Str.pack",
553            Builtin::StrUnpack => "__Str.unpack",
554            Builtin::SymFrom => "__Sym.from",
555            Builtin::SymStr => "__Sym.str",
556            Builtin::ModName => "__Mod.name",
557            Builtin::ModExports => "__Mod.exports",
558            Builtin::Proto => "__proto",
559            Builtin::Repr => "__repr",
560            // The two capabilities render under the leaf that produced them —
561            // there is no other way to hold one, so this is the name a reader
562            // wrote (see [`ALL`](Builtin::ALL) for why they are not in it).
563            Builtin::ObjNew => "__new",
564            Builtin::ObjValue => "__value",
565            Builtin::Int => "__Int",
566            Builtin::Str => "__Str",
567            Builtin::Sym => "__Sym",
568            Builtin::List => "__List",
569            Builtin::Map => "__Map",
570            Builtin::Err => "__Err",
571            Builtin::Mod => "__Mod",
572            Builtin::Bool => "__Bool",
573            // The members render dotted, as every module member does; `__true` /
574            // `__false` are the extra bare spellings [`from_name`](Self::from_name)
575            // also accepts, the way `Int` aliases `__Int`.
576            Builtin::BoolTrue => "__Bool.true",
577            Builtin::BoolFalse => "__Bool.false",
578            Builtin::BoolAnd => "__Bool.and",
579            Builtin::BoolOr => "__Bool.or",
580            Builtin::BoolNot => "__Bool.not",
581            Builtin::Fun => "__Fun",
582            Builtin::Unit => "__Unit",
583        }
584    }
585}