cao_lang/
stdlib.rs

1//! Cao-Lang standard library
2//!
3//! The standard library is injected into every `Module` at compilation time.
4//! Standard functions can be imported via the `std` module
5
6#[cfg(test)]
7mod tests;
8
9use crate::{
10    compiler::{Card, ForEach, Function, Module},
11    procedures::ExecutionErrorPayload,
12    value::Value,
13    vm::{runtime::cao_lang_object::CaoLangObjectBody, Vm},
14};
15
16/// Given a table and a callback that returns a bool create a new table whith the items that return
17/// true
18pub fn filter() -> Function {
19    Function::default()
20        .with_arg("iterable")
21        .with_arg("callback")
22        .with_cards(vec![
23            Card::set_var("res", Card::CreateTable),
24            Card::ForEach(Box::new(ForEach {
25                i: Some("i".to_string()),
26                k: Some("k".to_string()),
27                v: Some("v".to_string()),
28                iterable: Box::new(Card::read_var("iterable")),
29                body: Box::new(Card::composite_card(
30                    "_",
31                    vec![Card::IfTrue(Box::new([
32                        Card::dynamic_call(
33                            Card::read_var("callback"),
34                            vec![
35                                Card::read_var("i"),
36                                Card::read_var("v"),
37                                Card::read_var("k"),
38                            ],
39                        ),
40                        Card::set_property(
41                            Card::read_var("v"),
42                            Card::read_var("res"),
43                            Card::read_var("k"),
44                        ),
45                    ]))],
46                )),
47            })),
48            Card::return_card(Card::read_var("res")),
49        ])
50}
51
52/// Returns the key of the first row that returns True from the callback
53pub fn any() -> Function {
54    Function::default()
55        .with_arg("iterable")
56        .with_arg("callback")
57        .with_cards(vec![
58            Card::set_var("res", Card::CreateTable),
59            Card::ForEach(Box::new(ForEach {
60                i: Some("i".to_string()),
61                k: Some("k".to_string()),
62                v: Some("v".to_string()),
63                iterable: Box::new(Card::read_var("iterable")),
64                body: Box::new(Card::composite_card(
65                    "_",
66                    vec![Card::IfTrue(Box::new([
67                        Card::dynamic_call(
68                            Card::read_var("callback"),
69                            vec![
70                                Card::read_var("i"),
71                                Card::read_var("v"),
72                                Card::read_var("k"),
73                            ],
74                        ),
75                        Card::return_card(Card::read_var("k")),
76                    ]))],
77                )),
78            })),
79            Card::return_card(Card::ScalarNil),
80        ])
81}
82
83/// Iterate on a table calling the provided callback for each row.
84/// Build a new table from the callback return values, using the same keys
85pub fn map() -> Function {
86    Function::default()
87        .with_arg("iterable")
88        .with_arg("callback")
89        .with_cards(vec![
90            Card::set_var("res", Card::CreateTable),
91            Card::ForEach(Box::new(ForEach {
92                i: Some("i".to_string()),
93                k: Some("k".to_string()),
94                v: Some("v".to_string()),
95                iterable: Box::new(Card::read_var("iterable")),
96                body: Box::new(Card::composite_card(
97                    "_",
98                    vec![Card::set_property(
99                        Card::composite_card(
100                            "",
101                            vec![Card::dynamic_call(
102                                Card::read_var("callback"),
103                                vec![
104                                    Card::read_var("i"),
105                                    Card::read_var("v"),
106                                    Card::read_var("k"),
107                                ],
108                            )],
109                        ),
110                        Card::read_var("res"),
111                        Card::read_var("k"),
112                    )],
113                )),
114            })),
115            Card::return_card(Card::read_var("res")),
116        ])
117}
118
119fn minmax(minimax: &str) -> Function {
120    Function::default()
121        .with_arg("iterable")
122        .with_card(Card::return_card(Card::call_function(
123            minimax,
124            vec![
125                Card::function_value("row_to_value"),
126                Card::read_var("iterable"),
127            ],
128        )))
129}
130
131/// Return the smallest value in the table, or nil if the table is empty
132pub fn min() -> Function {
133    minmax("min_by_key")
134}
135
136/// Return the largest value in the table, or nil if the table is empty
137pub fn max() -> Function {
138    minmax("max_by_key")
139}
140
141pub fn sorted() -> Function {
142    Function::default()
143        .with_arg("iterable")
144        .with_card(Card::return_card(Card::call_function(
145            "sorted_by_key",
146            vec![
147                Card::function_value("row_to_value"),
148                Card::read_var("iterable"),
149            ],
150        )))
151}
152
153pub fn native_minmax<T, const LESS: bool>(
154    vm: &mut Vm<T>,
155    iterable: Value,
156    key_fn: Value,
157) -> Result<Value, ExecutionErrorPayload> {
158    match iterable {
159        Value::Nil | Value::Integer(_) | Value::Real(_) => Ok(iterable),
160        Value::Object(o) => unsafe {
161            match &o.as_ref().body {
162                CaoLangObjectBody::Table(t) => {
163                    let Some(first) = t.iter().next() else {
164                        return Ok(Value::Nil);
165                    };
166                    vm.stack_push(*first.1)?;
167                    vm.stack_push(*first.0)?;
168                    let mut max_key = vm.run_function(key_fn)?;
169                    let mut i = 0;
170
171                    for (j, (k, value)) in t.iter().enumerate().skip(1) {
172                        vm.stack_push(*value)?;
173                        vm.stack_push(*k)?;
174                        let key = vm.run_function(key_fn)?;
175                        if if LESS { key < max_key } else { key > max_key } {
176                            i = j;
177                            max_key = key;
178                        }
179                    }
180                    let k = t.nth_key(i);
181                    let v = *t.get(&k).unwrap();
182                    let mut result = vm.init_table()?;
183                    let t = result.0.as_mut().as_table_mut().unwrap();
184                    t.insert(vm.init_string("key")?, k)?;
185                    t.insert(vm.init_string("value")?, v)?;
186
187                    Ok(Value::Object(result.0))
188                }
189                CaoLangObjectBody::String(_)
190                | CaoLangObjectBody::Function(_)
191                | CaoLangObjectBody::Closure(_)
192                | CaoLangObjectBody::Upvalue(_)
193                | CaoLangObjectBody::NativeFunction(_) => Ok(iterable),
194            }
195        },
196    }
197}
198
199pub fn native_sorted<T>(
200    vm: &mut Vm<T>,
201    iterable: Value,
202    key_fn: Value,
203) -> Result<Value, ExecutionErrorPayload> {
204    match iterable {
205        Value::Nil | Value::Integer(_) | Value::Real(_) => Ok(iterable),
206        Value::Object(o) => unsafe {
207            match &o.as_ref().body {
208                CaoLangObjectBody::Table(t) => {
209                    // TODO:
210                    // sort in place?
211                    let mut result = Vec::with_capacity(t.len());
212                    for (k, v) in t.iter() {
213                        vm.stack_push(*v)?;
214                        vm.stack_push(*k)?;
215                        let key = vm.run_function(key_fn)?;
216                        result.push((key, k, v));
217                    }
218                    result.sort_by(|(a, _, _), (b, _, _)| {
219                        a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)
220                    });
221
222                    let mut out = vm.init_table()?;
223                    let t = out.as_table_mut().unwrap();
224                    for (_, k, v) in result {
225                        t.insert(*k, *v)?;
226                    }
227                    Ok(Value::Object(out.0))
228                }
229                CaoLangObjectBody::String(_) // TODO: define sort for strings?
230                | CaoLangObjectBody::Function(_)
231                | CaoLangObjectBody::Closure(_)
232                | CaoLangObjectBody::Upvalue(_)
233                | CaoLangObjectBody::NativeFunction(_) => Ok(iterable),
234            }
235        },
236    }
237}
238
239pub fn native_to_array<T>(vm: &mut Vm<T>, iterable: Value) -> Result<Value, ExecutionErrorPayload> {
240    match iterable {
241        Value::Nil | Value::Integer(_) | Value::Real(_) => Ok(iterable),
242        Value::Object(o) => unsafe {
243            match &o.as_ref().body {
244                CaoLangObjectBody::Table(t) => {
245                    let mut out = vm.init_table()?;
246                    let table = out.as_table_mut().unwrap();
247                    for (i, (_, val)) in t.iter().enumerate() {
248                        table.insert(i as i64, *val)?;
249                    }
250                    Ok(Value::Object(out.0))
251                }
252                CaoLangObjectBody::String(_)
253                | CaoLangObjectBody::Function(_)
254                | CaoLangObjectBody::Closure(_)
255                | CaoLangObjectBody::Upvalue(_)
256                | CaoLangObjectBody::NativeFunction(_) => Ok(iterable),
257            }
258        },
259    }
260}
261
262/// Return the smallest value in the table, or nil if the table is empty
263pub fn min_by_key() -> Function {
264    Function::default()
265        .with_arg("iterable")
266        .with_arg("key_function")
267        .with_card(Card::return_card(Card::call_native(
268            "__min",
269            vec![Card::read_var("iterable"), Card::read_var("key_function")],
270        )))
271}
272
273pub fn sorted_by_key() -> Function {
274    Function::default()
275        .with_arg("iterable")
276        .with_arg("key_function")
277        .with_card(Card::return_card(Card::call_native(
278            "__sort",
279            vec![Card::read_var("iterable"), Card::read_var("key_function")],
280        )))
281}
282
283pub fn max_by_key() -> Function {
284    Function::default()
285        .with_arg("iterable")
286        .with_arg("key_function")
287        .with_card(Card::return_card(Card::call_native(
288            "__max",
289            vec![Card::read_var("iterable"), Card::read_var("key_function")],
290        )))
291}
292
293/// A (key, value) function that returns the value given
294pub fn value_key_fn() -> Function {
295    Function::default()
296        .with_arg("_key")
297        .with_arg("val")
298        .with_card(Card::return_card(Card::read_var("val")))
299}
300
301pub fn to_array() -> Function {
302    Function::default()
303        .with_arg("iterable")
304        .with_card(Card::return_card(Card::call_native(
305            "__to_array",
306            vec![Card::read_var("iterable")],
307        )))
308}
309
310pub fn standard_library() -> Module {
311    let mut module = Module::default();
312    module.functions.push(("to_array".to_string(), to_array()));
313    module.functions.push(("filter".to_string(), filter()));
314    module.functions.push(("any".to_string(), any()));
315    module.functions.push(("map".to_string(), map()));
316    module.functions.push(("min".to_string(), min()));
317    module.functions.push(("max".to_string(), max()));
318    module
319        .functions
320        .push(("min_by_key".to_string(), min_by_key()));
321    module
322        .functions
323        .push(("max_by_key".to_string(), max_by_key()));
324    module
325        .functions
326        .push(("sorted_by_key".to_string(), sorted_by_key()));
327    module.functions.push(("sorted".to_string(), sorted()));
328    module
329        .functions
330        .push(("row_to_value".to_string(), value_key_fn()));
331    module
332}