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}