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}