Skip to main content

tancore/
seq.rs

1use tan::{
2    context::Context,
3    error::Error,
4    eval::{invoke, invoke_func},
5    expr::{expr_clone, format_value, Expr},
6    util::{
7        args::{unpack_arg, unpack_array_arg, unpack_array_mut_arg, unpack_int_arg},
8        module_util::require_module,
9    },
10};
11
12use super::cmp::rust_ordering_from_tan_ordering;
13
14// #insight Iterable is more general than Sequence. For example you could consider
15// a Map as an Iterable it's more of a stretch to think of it as a Sequence.
16
17// #todo Rename to `iter.rs`.
18
19// #todo Find a better name
20pub fn list_cons(args: &[Expr]) -> Result<Expr, Error> {
21    let [head, tail] = args else {
22        return Err(Error::invalid_arguments(
23            "requires `head` and `tail` arguments",
24            None,
25        ));
26    };
27
28    let Some(tail) = tail.as_list() else {
29        return Err(Error::invalid_arguments(
30            "`tail` argument should be a List",
31            tail.range(),
32        ));
33    };
34
35    // #todo this is slow!
36    let mut cons_items = vec![expr_clone(head.unpack())];
37    for expr in tail {
38        cons_items.push(expr_clone(expr));
39    }
40
41    Ok(Expr::List(cons_items))
42}
43
44// #todo find better name, match Array and String.
45pub fn list_count(args: &[Expr]) -> Result<Expr, Error> {
46    let [list, ..] = args else {
47        return Err(Error::invalid_arguments("requires `list` argument", None));
48    };
49
50    let Some(list) = list.as_list() else {
51        return Err(Error::invalid_arguments(
52            "`list` argument should be a List",
53            list.range(),
54        ));
55    };
56
57    Ok(Expr::Int(list.len() as i64))
58}
59
60// #todo implement slice _and_ takes
61
62// #todo implement sort! and sort (or sort, to-sorted)
63// #todo add put/insert at index
64
65// #todo implement generically for all iterables/countables, etc.
66
67pub fn array_eq(args: &[Expr]) -> Result<Expr, Error> {
68    // Use macros to monomorphise functions? or can we leverage Rust's generics? per viariant? maybe with cost generics?
69    // #todo support overloading,
70    // #todo support multiple arguments.
71
72    let a = unpack_array_arg(args, 0, "a")?;
73    let b = unpack_array_arg(args, 1, "b")?;
74
75    Ok(Expr::Bool(*a == *b))
76}
77
78// #todo version that returns a new sequence
79// #todo also consider insert, insert-back, append names
80// #todo item or element?
81pub fn array_push(args: &[Expr]) -> Result<Expr, Error> {
82    let [array, element] = args else {
83        return Err(Error::invalid_arguments(
84            "requires `this` and `element` argument",
85            None,
86        ));
87    };
88
89    let Some(mut elements) = array.as_array_mut() else {
90        return Err(Error::invalid_arguments(
91            "`array` argument should be a Array",
92            array.range(),
93        ));
94    };
95
96    elements.push(element.unpack().clone()); // #todo hmm this clone!
97
98    // #todo what to return?
99    Ok(Expr::None)
100}
101
102// #todo Add unit-test.
103// (put arr index value)
104pub fn array_put(args: &[Expr]) -> Result<Expr, Error> {
105    let mut array = unpack_array_mut_arg(args, 0, "array")?;
106    let index = unpack_int_arg(args, 1, "index")?;
107    let element = unpack_arg(args, 2, "element")?;
108
109    // #todo Remove this clone.
110    array[index as usize] = element.clone();
111
112    // #todo What should we return?
113    Ok(Expr::None)
114}
115
116// #todo generic Seq/extend, append on arrays, prepends on linked-lists.
117// #todo support concatenation of more than two arrays.
118// #todo find a good name
119// #todo consider the `++` operator
120pub fn array_concat_mut(args: &[Expr]) -> Result<Expr, Error> {
121    let [array1, array2] = args else {
122        return Err(Error::invalid_arguments("requires two arguments", None));
123    };
124
125    let Some(mut array1) = array1.as_array_mut() else {
126        return Err(Error::invalid_arguments(
127            "`array1` argument should be a Array",
128            array1.range(),
129        ));
130    };
131
132    let Some(mut array2) = array2.as_array_mut() else {
133        return Err(Error::invalid_arguments(
134            "`array2` argument should be a Array",
135            array2.range(),
136        ));
137    };
138
139    array1.append(&mut array2);
140
141    // #todo what to return?
142    Ok(Expr::None)
143}
144
145pub fn array_concat(args: &[Expr]) -> Result<Expr, Error> {
146    let a = unpack_array_arg(args, 0, "a")?;
147    let b = unpack_array_arg(args, 1, "b")?;
148
149    // #todo Check if a or b is empty and optimize!
150
151    let c: Vec<Expr> = a.iter().chain(b.iter()).cloned().collect();
152
153    Ok(Expr::array(c))
154}
155
156// #todo consider the name intercalate from haskell?
157// #todo can we find a more specific name?
158// #todo hm, it joins as strings, not very general, should move to string?
159/// (join names "\n")
160pub fn array_join(args: &[Expr]) -> Result<Expr, Error> {
161    let Some(array) = args.first() else {
162        return Err(Error::invalid_arguments("requires `array` argument", None));
163    };
164
165    let separator = args.get(1);
166    let separator = if separator.is_some() {
167        let Some(str) = separator.unwrap().as_stringable() else {
168            return Err(Error::invalid_arguments(
169                "the `separator` should be a Stringable",
170                None,
171            ));
172        };
173        str
174    } else {
175        ""
176    };
177
178    let Some(array) = array.as_array() else {
179        return Err(Error::invalid_arguments(
180            "`array` argument should be a Array",
181            array.range(),
182        ));
183    };
184
185    let elements: Vec<String> = array.iter().map(format_value).collect();
186
187    Ok(Expr::String(elements.join(separator)))
188}
189
190// #todo do we really want to support the no-argument case?
191/// (skip items 5) ; skips the first 5 elements
192/// (skip items) ; skips the first element
193pub fn array_skip(args: &[Expr]) -> Result<Expr, Error> {
194    // #insight
195    // An alternative name could be `drop` but for the moment we reserve this for
196    // the memory operation. Additionally, skip is a bit more descriptive.
197    let Some(array) = args.first() else {
198        return Err(Error::invalid_arguments("requires `array` argument", None));
199    };
200
201    let n = args.get(1);
202    let n = if n.is_some() {
203        let Some(n) = n.unwrap().as_int() else {
204            return Err(Error::invalid_arguments("`n` should be an Int", None));
205        };
206        n
207    } else {
208        1
209    };
210
211    let Some(array) = array.as_array() else {
212        return Err(Error::invalid_arguments(
213            "`array` argument should be a Array",
214            array.range(),
215        ));
216    };
217
218    let elements: Vec<Expr> = array.iter().skip(n as usize).cloned().collect();
219
220    Ok(Expr::array(elements))
221}
222
223// #insight use the word Iterable instead of Sequence/Seq, more generic (can handle non-sequences, e.g. maps)
224// #insight could also use Countable
225
226// #todo match the corresponding function in String.
227// #todo rename to `get-length`?
228// #todo implement generically for iterables.
229pub fn array_count(args: &[Expr]) -> Result<Expr, Error> {
230    let [array, ..] = args else {
231        return Err(Error::invalid_arguments("requires `array` argument", None));
232    };
233
234    let Some(array) = array.as_array() else {
235        return Err(Error::invalid_arguments(
236            "`array` argument should be a Array",
237            array.range(),
238        ));
239    };
240
241    Ok(Expr::Int(array.len() as i64))
242}
243
244// #todo implement with tan code!
245pub fn array_is_empty(args: &[Expr]) -> Result<Expr, Error> {
246    let [array, ..] = args else {
247        return Err(Error::invalid_arguments("requires `array` argument", None));
248    };
249
250    let Some(array) = array.as_array() else {
251        return Err(Error::invalid_arguments(
252            "`array` argument should be a Array",
253            array.range(),
254        ));
255    };
256
257    Ok(Expr::Bool(array.len() == 0))
258}
259
260pub fn array_contains(args: &[Expr]) -> Result<Expr, Error> {
261    let [array, element] = args else {
262        return Err(Error::invalid_arguments(
263            "requires `this` and `element` argument",
264            None,
265        ));
266    };
267
268    let Some(elements) = array.as_array_mut() else {
269        return Err(Error::invalid_arguments(
270            "`array` argument should be a Array",
271            array.range(),
272        ));
273    };
274
275    Ok(Expr::Bool(elements.contains(element.unpack())))
276}
277
278// #todo add unit tests.
279pub fn array_map(args: &[Expr], context: &mut Context) -> Result<Expr, Error> {
280    let [seq, func] = args else {
281        return Err(Error::invalid_arguments(
282            "requires `array` and `func` arguments",
283            None,
284        ));
285    };
286
287    // #todo should relax to allow for Iterable.
288    let Some(input_values) = seq.as_array() else {
289        return Err(Error::invalid_arguments(
290            "`seq` must be an `Array`",
291            seq.range(),
292        ));
293    };
294
295    // #insight cannot use map, because of the `?` operator.
296
297    let mut output_values: Vec<Expr> = Vec::new();
298
299    // #todo make sure that errors in the mapping function are propagated.
300
301    for x in input_values.iter() {
302        // #todo can we remove this clone somehow?
303        let args = vec![expr_clone(x)];
304        // let args = vec![eval(x, context)?];
305        // #todo #hack need to rething invoke_func/invoke_func_inner!!
306        output_values.push(invoke(func, args, context)?);
307    }
308
309    Ok(Expr::array(output_values))
310}
311
312// #todo this can actually be implemented with invoke_func.
313// #todo how to implement this? -> implement with tan code!
314pub fn array_filter(_args: &[Expr]) -> Result<Expr, Error> {
315    todo!();
316
317    // // #todo
318    // let [seq, predicate_fn] = args else {
319    //     return Err(Error::invalid_arguments(
320    //         "requires `this` and `predicate-fn` arguments",
321    //         None,
322    //     ));
323    // };
324
325    // let Some(arr) = seq.as_array() else {
326    //     return Err(Error::invalid_arguments(
327    //         "`filter` requires a `Seq` as the first argument",
328    //         seq.range(),
329    //     ));
330    // };
331
332    // let prev_scope = context.scope.clone();
333    // // context.scope = Rc::new(Scope::new(prev_scope.clone()));
334
335    // let mut results: Vec<Expr> = Vec::new();
336
337    // for x in arr.iter() {
338    //     // #todo how to call a closure?
339
340    //     // // #todo array should have Ann<Expr> use Ann<Expr> everywhere, avoid the clones!
341    //     // context.scope.insert(sym, x.clone());
342    //     // let result = eval(body, context)?;
343    //     // // #todo replace the clone with custom expr::ref/copy?
344    //     // results.push(result.unpack().clone());
345    // }
346
347    // // context.scope = prev_scope.clone();
348
349    // // #todo intentionally don't return a value, reconsider this?
350    // Ok(Expr::array(results).into())
351}
352
353// #todo implement first, last
354
355#[inline]
356fn sort_array_items(array_items: &mut [Expr], func: &Expr, context: &mut Context) {
357    // #todo validate func is a comparator.
358    // #todo validate that params has the correct structure.
359
360    // #todo #IMPORTANT, only sorts Int arrays!
361    // #hint Use e.g. (sort a (Func [a b] (Int (- a b))))
362
363    array_items.sort_by(|x, y| {
364        // #todo how to handle errors here?
365        // #todo should we evaluate array items?
366        // #insight args are already evaluated!
367        // let args = vec![eval(x, context).unwrap(), eval(y, context).unwrap()];
368        let args = vec![x.clone(), y.clone()];
369        let tan_ordering = invoke_func(func, args, context).unwrap();
370        rust_ordering_from_tan_ordering(&tan_ordering).unwrap()
371    });
372}
373
374pub fn array_sort(args: &[Expr], context: &mut Context) -> Result<Expr, Error> {
375    // #todo Find a good name for the array argument.
376    let xs = unpack_array_arg(args, 0, "xs")?;
377    let func = unpack_arg(args, 1, "func")?;
378
379    let mut xs_sorted = xs.clone();
380
381    sort_array_items(&mut xs_sorted, func, context);
382
383    Ok(Expr::array(xs_sorted))
384}
385
386// #todo Use (sort #mut arr (Func [a b] (- a b)))
387// #todo implement sort!, sort, sort-by!, sort-by
388// #todo need to introduce Comparable trait and (cmp ...) or (compare ...)
389// #todo need to introduce Ordering trait
390// (sort! [9 2 7] (Func [a b] (- a b)))
391// (sort! [9 2 7] (-> [a b] (- a b)))
392pub fn array_sort_mut(args: &[Expr], context: &mut Context) -> Result<Expr, Error> {
393    let [array, func] = args else {
394        return Err(Error::invalid_arguments(
395            "requires `array` and `func` arguments",
396            None,
397        ));
398    };
399
400    let Some(mut array_items) = array.as_array_mut() else {
401        return Err(Error::invalid_arguments(
402            "`array` argument should be a Array",
403            array.range(),
404        ));
405    };
406
407    sort_array_items(&mut array_items, func, context);
408
409    // #todo Don't return the array, skip the clone!!
410
411    // #insight interesting that we are also returning the input.
412
413    // Ok(Expr::array(array_items.clone()))
414    Ok(array.clone())
415}
416
417// #todo enforce range within string length
418// #todo rename to `cut`? (as in 'cut a slice')
419// #todo relation with range?
420// #todo pass range as argument?
421// #todo support negative index: -1 => length - 1
422// #insight negative index _may_ be problematic if the index is computed and returns negative by mistake.
423/// (slice arr 2 5)
424/// (slice arr 2)
425/// (slice arr 2 -2) ; -2 is length - 2
426pub fn array_slice(args: &[Expr]) -> Result<Expr, Error> {
427    let [this, start, ..] = args else {
428        return Err(Error::invalid_arguments(
429            "requires `this` and start arguments",
430            None,
431        ));
432    };
433
434    let Some(elements) = this.as_array() else {
435        return Err(Error::invalid_arguments(
436            "`this` argument should be an Array",
437            this.range(),
438        ));
439    };
440
441    let Some(start) = start.as_int() else {
442        return Err(Error::invalid_arguments(
443            "`start` argument should be an Int",
444            this.range(),
445        ));
446    };
447
448    let end = if let Some(end) = args.get(2) {
449        let Some(end) = end.as_int() else {
450            return Err(Error::invalid_arguments(
451                "`end` argument should be an Int",
452                this.range(),
453            ));
454        };
455        end
456    } else {
457        elements.len() as i64
458    };
459
460    let start = start as usize;
461    let end = if end < 0 {
462        // #todo supporting negative index may hide errors if the index is computed
463        // #todo offer a link to only support negative values for constant index
464        // If the end argument is negative it indexes from the end of the string.
465        (elements.len() as i64 + end) as usize
466    } else {
467        end as usize
468    };
469
470    let slice = &elements[start..end];
471
472    Ok(Expr::array(slice))
473}
474
475// #todo Consider different names: roll, rolled, rolling
476// #todo Consider eager (roll) and lazy (rolling/rolled) versions.
477// #todo Is this generic?
478pub fn array_roll(args: &[Expr]) -> Result<Expr, Error> {
479    let window_size = unpack_int_arg(args, 0, "window-size")?;
480    let items = unpack_array_arg(args, 1, "items")?;
481
482    let windows = items.windows(window_size as usize);
483
484    let mut rolled_items = Vec::new();
485    for window in windows {
486        rolled_items.push(Expr::array(window));
487    }
488
489    Ok(Expr::array(rolled_items))
490}
491
492pub fn setup_lib_seq(context: &mut Context) {
493    // #todo should put in `seq` module and then into `prelude`.
494    let module = require_module("prelude", context);
495
496    module.insert_invocable("=$$Array$$Array", Expr::foreign_func(&array_eq));
497
498    // #todo introduce `++` overload?
499    module.insert_invocable("cons", Expr::foreign_func(&list_cons));
500    module.insert_invocable("count", Expr::foreign_func(&list_count));
501    module.insert_invocable("count$$List", Expr::foreign_func(&list_count));
502
503    // #todo add type qualifiers!
504    module.insert_invocable("push", Expr::foreign_func(&array_push));
505    // #todo Add more signatures, or make generic somehow.
506    module.insert_invocable("put$$Array$$Int$$Int", Expr::foreign_func(&array_put));
507    module.insert_invocable("put$$Array$$Int$$Float", Expr::foreign_func(&array_put));
508    // #todo Reconsider the `!` suffix, reconsider the name.
509    // #todo Consider concat-mut
510    // #todo also introduce `++`, `++=`, versions
511    module.insert_invocable("concat!", Expr::foreign_func(&array_concat_mut));
512
513    module.insert_invocable("concat", Expr::foreign_func(&array_concat));
514    module.insert_invocable("++", Expr::foreign_func(&array_concat));
515
516    // (map (Func [x] (+ x 1)) [1 2 3]) ; => [2 3 4]
517    // (map (Fn x (+ x 1)) [1 2 3]) ; => [2 3 4]
518    // (map (-> x (+ x 1)) [1 2 3]) ; => [2 3 4]
519    // (map \(+ % 1) [1 2 3])
520    module.insert_invocable("map", Expr::foreign_func_mut_context(&array_map));
521
522    module.insert_invocable("join", Expr::foreign_func(&array_join));
523    module.insert_invocable("skip", Expr::foreign_func(&array_skip));
524    // #todo rename to (get-length) or something, match with String and other collection types.
525    module.insert_invocable("count", Expr::foreign_func(&array_count));
526    module.insert_invocable("count$$Array", Expr::foreign_func(&array_count));
527    // #todo make contains? generic!
528    module.insert_invocable("contains?", Expr::foreign_func(&array_contains));
529    module.insert_invocable("contains?$$Array$$Int", Expr::foreign_func(&array_contains));
530    module.insert_invocable(
531        "contains?$$Array$$String",
532        Expr::foreign_func(&array_contains),
533    );
534    module.insert_invocable("is-empty?", Expr::foreign_func(&array_is_empty));
535    module.insert_invocable("sort", Expr::foreign_func_mut_context(&array_sort));
536    module.insert_invocable("sort!", Expr::foreign_func_mut_context(&array_sort_mut));
537
538    // #todo slice is to general works both as noun and verb, try to find an explicit verb? e.g. `cut` or `carve`
539    // #todo alternatively use something like `get-slice` or `cut-slice` or `carve-slice`.
540    // module.insert_invocable("slice", Expr::foreign_func(&array_slice)));
541    module.insert_invocable("slice$$Array$$Int", Expr::foreign_func(&array_slice));
542    module.insert_invocable("slice$$Array$$Int$$Int", Expr::foreign_func(&array_slice));
543
544    module.insert_invocable("roll", Expr::foreign_func(&array_roll));
545
546    // let module = require_module("seq", context);
547
548    // println!("--- YO!");
549    // // #insight eval additional code implemented in Tan.
550    // if let Err(errors) = eval_module("seq", context, true) {
551    //     // #todo improve formatting here.
552    //     eprintln!("{errors:?}");
553    // }
554}
555
556// #todo introduce FuncMut?
557// #todo maybe (map ...) should emit warning for FuncMut/ForeignFuncMut