tan/eval/
eval_for.rs

1use std::{cell::RefCell, rc::Rc, sync::Arc};
2
3use crate::{
4    context::Context,
5    error::{Error, ErrorVariant},
6    expr::Expr,
7    scope::Scope,
8};
9
10use super::{
11    eval, insert_binding,
12    iterator::{try_iterator_from_consuming, ExprIterator},
13};
14
15// #insight
16// - `while` is a generalization of `if`
17// - `for` is a generalization of `let`
18// - `for` is related with `do`
19// - `for` is monadic
20
21// #todo check racket.
22// #todo implement for->list, for->map, for->fold, etc.
23
24// #todo what should happen if variable source is nil?
25
26// Insert the bindings to the next interation. Returns false if the iteration
27// should be stopped.
28fn insert_next_bindings(
29    bindings: &[(&Expr, Rc<RefCell<dyn ExprIterator>>)],
30    context: &mut Context,
31) -> Result<bool, Error> {
32    for (var, iterator) in bindings {
33        // #todo borrow_mut/RC is not really needed here?
34        let mut iterator = iterator.borrow_mut();
35        if let Some(value) = iterator.next() {
36            insert_binding(var, value, context)?;
37        } else {
38            return Ok(false);
39        }
40    }
41    Ok(true)
42}
43
44// #todo Update for-list to match this implementation.
45// #todo Add unit tests!
46// (for [x 10] (writeln x))
47pub fn eval_for(args: &[Expr], context: &mut Context) -> Result<Expr, Error> {
48    // #todo reuse code from let
49    // #todo the resolver should handle this.
50    // #todo this code should be reused in while.
51
52    if args.len() < 2 {
53        // #todo add more structural checks.
54        // #todo proper error!
55        return Err(Error::invalid_arguments("missing for arguments", None));
56    }
57
58    let binding = args.first().unwrap();
59    let body = &args[1..];
60
61    // #todo #perf #important Optimize the standard case with one binding!
62
63    // #todo should check both for list and array (i.e. as_iterable)
64    let Some(binding_parts) = binding.as_array() else {
65        // #todo proper error!
66        return Err(Error::invalid_arguments(
67            "invalid for binding",
68            binding.range(),
69        ));
70    };
71
72    let mut i = 0;
73
74    let mut bindings = Vec::with_capacity(binding_parts.len() / 2);
75
76    while i < binding_parts.len() {
77        let var = binding_parts.get(i).unwrap();
78        let value = binding_parts.get(i + 1).unwrap();
79
80        let value = eval(value, context)?;
81
82        // #todo also handle (Range start end step)
83        // #todo maybe step should be external to Range, or use SteppedRange, or (Step-By (Range T))
84        let Some(iterator) = try_iterator_from_consuming(value) else {
85            // #todo proper error!
86            return Err(Error::invalid_arguments(
87                "invalid for binding, not iterable",
88                // value.range(),
89                None,
90            ));
91        };
92
93        bindings.push((var, iterator));
94
95        i += 2;
96    }
97
98    // #insight If the binding iterables have different length, iterate until
99    // the shortest is exhausted, similar to how Rust's zip and Python list
100    // comprehensions work.
101
102    // // #todo support _multiple_ bindings.
103    // let [var, value] = &binding_parts[..] else {
104    //     return Err(Error::invalid_arguments(
105    //         "invalid for binding",
106    //         binding.range(),
107    //     ));
108    // };
109
110    // // #insight for the ListIterator
111    // let value = eval(value, context)?;
112
113    // // #todo also handle (Range start end step)
114    // // #todo maybe step should be external to Range, or use SteppedRange, or (Step-By (Range T))
115    // let Some(iterator) = try_iterator_from(&value) else {
116    //     // #todo proper error!
117    //     return Err(Error::invalid_arguments(
118    //         &format!("invalid for binding, `{value}` is not iterable"),
119    //         value.range(),
120    //     ));
121    // };
122
123    let prev_scope = context.scope.clone();
124    context.scope = Arc::new(Scope::new(prev_scope.clone()));
125
126    // let mut iterator = iterator.borrow_mut();
127
128    // 'outer_loop: while let Some(value) = iterator.next() {
129    //     insert_binding(var, value, context)?;
130    'outer_loop: while insert_next_bindings(&bindings, context)? {
131        // insert_binding(var, value, context)?;
132        'inner_loop: for expr in body {
133            match eval(expr, context) {
134                Err(Error {
135                    variant: ErrorVariant::BreakCF(_value),
136                    ..
137                }) => {
138                    // #todo for the moment we ignore break with value, should think some more about it.
139                    break 'outer_loop;
140                }
141                Err(Error {
142                    variant: ErrorVariant::ContinueCF,
143                    ..
144                }) => {
145                    break 'inner_loop;
146                }
147                Err(error) => {
148                    // #todo add unit test to catch for-error regression.
149                    // Propagate all other errors. This is very ..error-prone code, think how
150                    // to refactor.
151                    return Err(error);
152                }
153                _ => {
154                    // #insight plain `for` is useful only for the side-effects, ignore the value.
155                    // #todo maybe it should return the last value?
156                }
157            }
158        }
159    }
160
161    // #todo what happens to this if an error is thrown?
162    context.scope = prev_scope;
163
164    Ok(Expr::None)
165}