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}