Skip to main content

swan_core/
builtins.rs

1//! The built-in library and the root scope.
2//!
3//! [`root_scope`] creates the scope in which Swan code normally runs. It
4//! contains these bindings:
5//!
6//! | Name                  | Kind      | Description                                   |
7//! |-----------------------|-----------|-----------------------------------------------|
8//! | `TRUE`, `FALSE`       | constant  | The boolean values                            |
9//! | `type`                | function  | The type name of a value                      |
10//! | `parse`               | function  | Compiles Swan source text into a function     |
11//! | `bind`, `label`       | function  | Bind names in the global scope                |
12//! | `bool`                | namespace | Boolean conversion and reduction              |
13//! | `numb`                | namespace | Number conversion                             |
14//! | `text`                | namespace | Text inspection and manipulation              |
15//! | `list`                | namespace | List inspection and manipulation              |
16//! | `nspace`              | namespace | Namespace inspection                          |
17//! | `math`                | namespace | Mathematical constants and functions          |
18//! | `time`                | namespace | Timestamps and calendar dates                 |
19//! | `promise`             | namespace | Creating and resolving promises               |
20//! | `undef`               | namespace | Creating and inspecting undefined values      |
21//!
22//! Every function is documented, with examples, in the
23//! [built-in library reference](https://onlabsorg.github.io/texl/book/swan/builtins/index.html)
24//! of the Swan language manual.
25
26// Implementation notes
27//
28// Purpose: this module builds the root scope and defines the built-ins that
29// live directly in it (`type`, `parse`, `TRUE`, `FALSE`).
30// `bind` and `label` are also defined here: they take a name (or tuple of names) and return
31// a function that binds the names globally. `bind` returns Nothing, `label` returns the values.
32// Every other built-in lives in a submodule that exposes `create_namespace(&Scope) -> Value`.
33//
34// Adding a root-level function:
35//   1. Write `fn my_fn(arg: Value) -> Value`.
36//   2. Register it in `root_scope()` with `scope.set("my_fn", map_1("my_fn", my_fn))`.
37//      `map_1` makes it map over tuples and pass `Nothing` through.
38//
39// Adding a function to a namespace: put it in the matching submodule (`numb`,
40// `text`, `time`, …) and register it in that module's `create_namespace`.
41//
42// Multi-argument functions: these are curried. Use `map_curried` from `helpers`,
43// so `math.pow 10` returns a function that takes the second argument.
44//
45
46use crate::value::{BuiltinFn, Scope, Undef, Value};
47use crate::lexer::Lexer;
48use crate::parser::Parser;
49use crate::evaluator::eval;
50use crate::evaluator::assignment_operation;
51
52// Shared helpers and macros (map_1, map_curried, ...).
53mod helpers;
54use helpers::*;
55
56// One submodule per built-in namespace; each exposes create_namespace().
57mod bool_;
58mod numb;
59mod text;
60mod list;
61mod nspace;
62mod promise;
63mod undef;
64mod math;
65mod time;
66
67
68
69// ----------------------------------------------------------------------------
70//  Root scope factory
71// ----------------------------------------------------------------------------
72
73/// Builds a fresh scope in which every built-in name is available.
74///
75/// The built-ins live in a parent scope, and the returned scope is an empty
76/// child of it, so user code can shadow a built-in name without changing it.
77/// The global names created by the `bind` and `label` built-ins go into the
78/// returned scope.
79///
80/// Every evaluation session should start from a scope created here (or from a
81/// child of one).
82///
83/// # Examples
84///
85/// ```
86/// use swan_core::builtins::root_scope;
87/// use swan_core::evaluator::eval;
88/// use swan_core::lexer::Lexer;
89/// use swan_core::parser::Parser;
90///
91/// let scope = root_scope();
92/// let mut parser = Parser::new(Lexer::new("1+2"));
93/// let ast = parser.parse();
94/// assert_eq!(eval(&ast, &scope), swan_core::value::Value::Numb(3.0));
95/// ```
96pub fn root_scope() -> Scope {
97    // Built-ins are installed in this base scope.
98    let scope = Scope::new();
99
100    // Each namespace is registered under its own name, e.g. `math.sqrt`.
101    scope.set("bool"   , bool_::create_namespace(&scope));
102    scope.set("numb"   , numb::create_namespace(&scope));
103    scope.set("text"   , text::create_namespace(&scope));
104    scope.set("list"   , list::create_namespace(&scope));
105    scope.set("nspace" , nspace::create_namespace(&scope));
106    scope.set("promise", promise::create_namespace(&scope));
107    scope.set("undef"  , undef::create_namespace(&scope));
108    scope.set("math"   , math::create_namespace(&scope));
109    scope.set("time"   , time::create_namespace(&scope));
110
111    scope.set("TRUE" , Value::Bool(true));
112    scope.set("FALSE", Value::Bool(false));
113
114    scope.set("type" , map_1("type" , type_function));
115    scope.set("parse", map_1("parse", parse_function));
116
117    // Fresh empty child scope where user code runs. User code defines and shadows
118    // names here and cannot overwrite the built-ins stored in the parent.
119    let user_scope = scope.new_child();
120
121    // `bind` and `label` write into `user_scope`, so they are global from the point
122    // of view of user code, even when called from inside a function body.
123    // Each `move` closure needs its own clone.
124    //
125    // NOTE: this creates an Rc cycle (scope -> bind closure -> user_scope -> scope),
126    // so a root scope is never freed. `Scope` has no weak reference, so this is accepted.
127    let bind_target = user_scope.clone();
128    scope.set("bind", Value::Builtin(BuiltinFn::new(
129        "bind",
130        move |arg| set_global("bind", arg, bind_target.clone(), false)
131    )));
132
133    let label_target = user_scope.clone();
134    scope.set("label", Value::Builtin(BuiltinFn::new(
135        "label",
136        move |arg| set_global("label", arg, label_target.clone(), true)
137    )));
138
139    // Return the scope in which user code is evaluated
140    user_scope
141}
142
143
144
145// ----------------------------------------------------------------------------
146//  built-in functions
147// ----------------------------------------------------------------------------
148
149/// Builtin `type`: returns the type name of its argument as a `Text`.
150///
151/// Wrapped with `map_1`, so `type()` is `Nothing` and `type(a, b)` gives a
152/// tuple of type names. Both `Func` and `Builtin` values report `"Func"`.
153fn type_function(val: Value) -> Value {
154    let n = match val {
155        Value::Nothing => "Nothing",
156        Value::Bool(_) => "Bool",
157        Value::Numb(_) => "Numb",
158        Value::Text(_) => "Text",
159        Value::List(_) => "List",
160        Value::Namespace(_) => "Namespace",
161        Value::Func { .. } | Value::Builtin(_) => "Func",
162        Value::Promise(_)   => "Promise",
163        Value::Undefined(_) => "Undefined",
164        Value::Tuple(_)     => "Tuple",
165        Value::Name(_)      => "Name",
166    };
167    Value::Text(n.into())
168}
169
170
171/// Builtin `parse` function:
172///   - takes a `Text` containing source code and parses it immediately.
173///   - returns a builtin function named `"eval"`.
174///   - that function takes a `Namespace` and evaluates the captured AST in
175///     that namespace as its scope.
176///   - a non-`Text` argument to `parse` gives `Undefined`, labelled `"parse"`.
177///   - a non-`Namespace` argument to the returned function gives `Undefined`,
178///     labelled `"eval"`.
179fn parse_function(arg: Value) -> Value {
180    match arg {
181        Value::Text(source) => {
182            let mut parser = Parser::new(Lexer::new(&source));
183            let ast = parser.parse();
184
185            // The closure takes ownership of the AST.
186            Value::Builtin(BuiltinFn::new("eval", move |ns_arg| {
187                match ns_arg {
188                    Value::Namespace(scope) => eval(&ast, &scope),
189                    other => Value::undef_unary("eval", other, 0),
190                }
191            }))
192        }
193        other => Value::undef_unary("parse", other, 0),
194    }
195}
196
197
198/// Shared implementation of the builtins `bind` and `label`.
199///
200/// - `op`: `"bind"` or `"label"`, used as the label of the `Undefined` result.
201/// - `name_arg`: a `Text`, a tuple of `Text`s, or `Nothing`. These are the names.
202/// - `target_scope`: the scope where the names are bound.
203/// - `retval`: `false` for `bind` (like `=`, returns `Nothing`),
204///   `true` for `label` (like `:`, returns the bound values).
205///
206/// Returns a builtin function that takes the values and binds the names to them
207/// using `assignment_operation::eval`. That is the same function behind `=` and `:`,
208/// so mismatched numbers of names and values follow the same rules as those operators.
209/// If `name_arg` is not valid names, returns `Undefined` (unary operation `op`).
210fn set_global(op: &'static str, name_arg: Value, target_scope: Scope, retval: bool) -> Value {
211
212    // Convert Text / tuple of Text into Name / tuple of Name
213    let names = wrap_names(&name_arg);
214    if matches!(names, Value::Undefined(_)) {
215        return Value::undef_unary(op, name_arg, 0);
216    }
217
218    Value::Builtin(BuiltinFn::new(op, move |val| {
219        assignment_operation::eval(&target_scope, &names, &val);
220        if retval { val } else { Value::Nothing }
221    }))
222}
223
224/// Converts a runtime value into a validated name or tuple of names.
225/// `Text` gives a `Name`, `Nothing` stays `Nothing`, and a tuple of `Text`s gives a
226/// tuple of `Name`s. Anything else gives `Undefined(Undef::Name)`.
227fn wrap_names(val: &Value) -> Value {
228    match val {
229        Value::Nothing => Value::Nothing,
230        Value::Text(s) => Value::Name(s.clone()),
231        Value::Tuple(items) => {
232            let mapped: Vec<Value> = items.iter().map(wrap_names).collect();
233            if mapped.iter().any(|v| matches!(v, Value::Undefined(_))) {
234                Value::Undefined(Undef::Name { pos: 0 })
235            } else {
236                Value::tuple(mapped)
237            }
238        }
239        _ => Value::Undefined(Undef::Name { pos: 0 }),
240    }
241}
242
243
244
245// ----------------------------------------------------------------------------
246//  Tests
247// ----------------------------------------------------------------------------
248
249#[cfg(test)]
250mod tests {
251    use super::*;
252
253    /// Evaluates `src` in a fresh root scope.
254    fn run(src: &str) -> Value {
255        let scope = root_scope();
256        let mut parser = Parser::new(Lexer::new(src));
257        let ast = parser.parse();
258        eval(&ast, &scope)
259    }
260
261    #[test]
262    fn test_type() {
263        // Item
264        assert_eq!(run("type(1 == 1)"), Value::Text("Bool".into()));
265        assert_eq!(run("type 10"), Value::Text("Numb".into()));
266        assert_eq!(run(r#"type "abc""#), Value::Text("Text".into()));
267        assert_eq!(run("type [1, 2]"), Value::Text("List".into()));
268        assert_eq!(run("type {a: 1}"), Value::Text("Namespace".into()));
269        assert_eq!(run("type(x -> x)"), Value::Text("Func".into()));
270        assert_eq!(run("type type"), Value::Text("Func".into()));
271
272        // Nothing
273        assert_eq!(run("type()"), Value::Nothing);
274
275        // Tuple
276        assert_eq!(
277            run(r#"type(10, "abc", [1])"#),
278            Value::tuple(vec![
279                Value::Text("Numb".into()),
280                Value::Text("Text".into()),
281                Value::Text("List".into()),
282            ])
283        );
284    }
285
286    #[test]
287    fn test_parse() {
288        // Evaluation with a scope argument
289        assert_eq!(
290            run(r#"eval_fn = parse "a + b", eval_fn { a = 10, b = 20 }"#),
291            Value::Numb(30.0)
292        );
293
294        // Returns a function/closure
295        assert_eq!(
296            run(r#"double = (parse "x -> x * 2") {}, double 5"#),
297            Value::Numb(10.0)
298        );
299
300        // Nothing
301        assert_eq!(run("parse()"), Value::Nothing);
302
303        // Tuple
304        assert_eq!(
305            run(r#"(f,g) = parse("2*x", "3*x"), f{x:10}, g{x:20}"#),
306            Value::tuple(vec![
307                Value::Numb(20.0),
308                Value::Numb(60.0),
309            ]),
310        );
311
312        // Invalid argument type returns Undefined
313        assert!(matches!(run("parse 123"), Value::Undefined(_)));
314    }
315
316    #[test]
317    fn test_bind() {
318        // bind returns Nothing, but the name is bound
319        assert_eq!(run(r#"(bind "x") 5"#), Value::Nothing);
320        assert_eq!(run(r#"(bind "x") 5, x"#), Value::Numb(5.0));
321
322        // Tuple of names
323        assert_eq!(run(r#"(bind ("a","b")) (1,2), a + b"#), Value::Numb(3.0));
324
325        // Nothing as names: values are discarded
326        assert_eq!(run(r#"(bind ()) 5"#), Value::Nothing);
327
328        // Binding is global, even when called inside a function body
329        assert_eq!(run(r#"f = v -> (bind "y") v, f 7, y"#), Value::Numb(7.0));
330
331        // Mismatched counts behave exactly like `=`
332        assert_eq!(
333            run(r#"(bind ("a","b")) (1,2,3), (a,b)"#),
334            run("(a,b) = (1,2,3), (a,b)")
335        );
336        assert_eq!(
337            run(r#"(bind ("a","b")) 1, (a,b)"#),
338            run("(a,b) = 1, (a,b)")
339        );
340        assert_eq!(
341            run(r#"(bind "a") (1,2), a"#),
342            run("a = (1,2), a")
343        );
344
345        // Invalid names give Undefined labelled "bind"
346        assert!(matches!(run("bind 5"), Value::Undefined(Undef::UnaryOperation { op: "bind", .. })));
347        assert!(matches!(run(r#"bind ("a", 5)"#), Value::Undefined(Undef::UnaryOperation { op: "bind", .. })));
348    }
349
350    #[test]
351    fn test_label() {
352        // label returns the value and binds the name
353        assert_eq!(run(r#"(label "x") 5"#), Value::Numb(5.0));
354        assert_eq!(
355            run(r#"(label "x") 5, x"#),
356            Value::tuple(vec![Value::Numb(5.0), Value::Numb(5.0)])
357        );
358
359        // Tuple of names returns the tuple of values
360        assert_eq!(
361            run(r#"(label ("a","b")) (1,2)"#),
362            Value::tuple(vec![Value::Numb(1.0), Value::Numb(2.0)])
363        );
364
365        // Binding is global, even when called inside a function body
366        assert_eq!(
367            run(r#"f = v -> (label "y") v, f 7, y"#),
368            Value::tuple(vec![Value::Numb(7.0), Value::Numb(7.0)])
369        );
370
371        // Mismatched counts behave exactly like `:`
372        assert_eq!(
373            run(r#"(label ("a","b")) (1,2,3)"#),
374            run("(a,b) : (1,2,3)")
375        );
376        assert_eq!(
377            run(r#"(label ("a","b")) 1"#),
378            run("(a,b) : 1")
379        );
380        assert_eq!(
381            run(r#"(label "a") (1,2)"#),
382            run("a = (1,2), a")
383        );
384
385        // Invalid names give Undefined labelled "label"
386        assert!(matches!(run("label 5"), Value::Undefined(Undef::UnaryOperation { op: "label", .. })));
387        assert!(matches!(run(r#"label ("a", 5)"#), Value::Undefined(Undef::UnaryOperation { op: "label", .. })));
388    }
389}