rimu_eval/
block.rs

1// with help from
2// - https://github.com/DennisPrediger/SLAC/blob/main/src/interpreter.rs
3
4use std::{cell::RefCell, ops::Deref, rc::Rc};
5
6use rimu_ast::{Block, Expression, SpannedBlock, SpannedExpression};
7use rimu_meta::{Span, Spanned};
8use rimu_value::{
9    convert_value_object_to_serde_value_object, Environment, Function, FunctionBody, SpannedValue,
10    Value, ValueList, ValueObject,
11};
12
13use crate::{common, expression::evaluate as evaluate_expression, EvalError, Result};
14
15pub fn evaluate(expression: &SpannedBlock, env: Rc<RefCell<Environment>>) -> Result<SpannedValue> {
16    Evaluator::new(env).block(expression)
17}
18
19/// A tree walking interpreter which given an [`Environment`] and an [`Block`]
20/// recursivly walks the tree and computes a single [`Value`].
21struct Evaluator {
22    env: Rc<RefCell<Environment>>,
23}
24
25impl Evaluator {
26    fn new(env: Rc<RefCell<Environment>>) -> Self {
27        Self { env }
28    }
29
30    fn block(&self, block: &SpannedBlock) -> Result<SpannedValue> {
31        let span = block.span();
32        match block.inner() {
33            Block::Expression(expr) => self.expression(span, expr),
34            Block::Object(object) => self.object(span, object),
35            Block::List(list) => self.list(span, list),
36            Block::Function { args, body } => self.function(span, args, body),
37            Block::Call { function, args } => self.call(span, function, args),
38            Block::If {
39                condition,
40                consequent,
41                alternative,
42            } => self.if_(
43                span,
44                condition,
45                consequent.as_ref().map(|c| c.deref()),
46                alternative.as_ref().map(|a| a.deref()),
47            ),
48            Block::Let { variables, body } => self.let_(span, variables, body.deref()),
49        }
50    }
51
52    fn expression(&self, span: Span, expr: &Expression) -> Result<SpannedValue> {
53        evaluate_expression(&Spanned::new(expr.clone(), span), self.env.clone())
54    }
55
56    fn object(
57        &self,
58        span: Span,
59        entries: &[(Spanned<String>, SpannedBlock)],
60    ) -> Result<SpannedValue> {
61        let mut object = ValueObject::new();
62        for (key, value) in entries.iter() {
63            let key = key.clone().into_inner();
64            let value = self.block(value)?;
65            if value.inner() == &Value::Null {
66                continue;
67            };
68            object.insert(key, value);
69        }
70        let value = Value::Object(object);
71        Ok(Spanned::new(value, span))
72    }
73
74    fn list(&self, span: Span, items: &[SpannedBlock]) -> Result<SpannedValue> {
75        let mut list = ValueList::with_capacity(items.len());
76        for item in items.iter() {
77            let item = self.block(item)?;
78            if item.inner() == &Value::Null {
79                continue;
80            };
81            list.push(item);
82        }
83        let value = Value::List(list);
84        Ok(Spanned::new(value, span))
85    }
86
87    fn function(
88        &self,
89        span: Span,
90        args: &[Spanned<String>],
91        body: &SpannedBlock,
92    ) -> Result<SpannedValue> {
93        let args: Vec<String> = args.iter().map(|a| a.inner()).cloned().collect();
94        let body = FunctionBody::Block(body.clone());
95        let env = self.env.clone();
96        let value = Value::Function(Function { args, body, env });
97        Ok(Spanned::new(value, span))
98    }
99
100    fn call(
101        &self,
102        span: Span,
103        function: &SpannedExpression,
104        args: &SpannedBlock,
105    ) -> Result<SpannedValue> {
106        let Value::Function(function) =
107            evaluate_expression(function, self.env.clone())?.into_inner()
108        else {
109            return Err(EvalError::CallNonFunction {
110                span: function.span(),
111                expr: function.clone().into_inner(),
112            });
113        };
114
115        let arg = self.block(args)?;
116
117        let args = match arg.inner() {
118            Value::List(list) => list.clone(),
119            _ => vec![arg],
120        };
121
122        common::call(span, function, &args)
123    }
124
125    fn if_(
126        &self,
127        span: Span,
128        condition: &SpannedBlock,
129        consequent: Option<&SpannedBlock>,
130        alternative: Option<&SpannedBlock>,
131    ) -> Result<SpannedValue> {
132        let condition = self.block(condition)?.into_inner();
133
134        let value = if Into::<bool>::into(condition) {
135            if let Some(consequent) = &consequent {
136                self.block(consequent)?.into_inner()
137            } else {
138                Value::Null
139            }
140        } else {
141            #[allow(clippy::collapsible_else_if)]
142            if let Some(alternative) = &alternative {
143                self.block(alternative)?.into_inner()
144            } else {
145                Value::Null
146            }
147        };
148
149        Ok(Spanned::new(value, span))
150    }
151
152    fn let_(
153        &self,
154        span: Span,
155        entries: &[(Spanned<String>, SpannedBlock)],
156        body: &SpannedBlock,
157    ) -> Result<SpannedValue> {
158        let mut variables = ValueObject::new();
159        for (key, value) in entries.iter() {
160            let key = key.clone().into_inner();
161            let value = self.block(value)?;
162            if value.inner() == &Value::Null {
163                continue;
164            };
165            variables.insert(key, value);
166        }
167
168        let parent_env = self.env.clone();
169        let variables = convert_value_object_to_serde_value_object(variables);
170        let let_env = Environment::from_object(&variables, Some(parent_env)).map_err(|error| {
171            EvalError::Environment {
172                span: span.clone(),
173                source: error,
174            }
175        })?;
176        let let_env = Rc::new(RefCell::new(let_env));
177
178        let value = evaluate(body, let_env)?.into_inner();
179
180        Ok(Spanned::new(value, span))
181    }
182}
183
184#[cfg(test)]
185mod tests {
186    use std::cell::RefCell;
187    use std::rc::Rc;
188
189    use indexmap::IndexMap;
190
191    use indexmap::indexmap;
192    use pretty_assertions::assert_eq;
193    use rimu_ast::SpannedBlock;
194    use rimu_meta::SourceId;
195    use rimu_parse::parse_block;
196    use rimu_value::SerdeValue;
197    use rimu_value::{Environment, Value};
198    use rust_decimal_macros::dec;
199
200    use super::{evaluate, EvalError};
201
202    fn test_block(
203        expr: SpannedBlock,
204        env_object: Option<IndexMap<String, SerdeValue>>,
205    ) -> Result<Value, EvalError> {
206        let mut env = Environment::new();
207        if let Some(env_object) = env_object {
208            for (key, value) in env_object.into_iter() {
209                env.insert(key, value);
210            }
211        }
212        let env = Rc::new(RefCell::new(env));
213
214        let value = evaluate(&expr, env)?.into_inner();
215        Ok(value)
216    }
217
218    fn test_code(
219        code: &str,
220        env_object: Option<IndexMap<String, SerdeValue>>,
221    ) -> Result<SerdeValue, EvalError> {
222        let (Some(expr), errors) = parse_block(code, SourceId::empty()) else {
223            panic!()
224        };
225        assert_eq!(errors.len(), 0);
226        Ok(test_block(expr, env_object)?.into())
227    }
228
229    #[test]
230    fn op_if() {
231        let code = "
232zero:
233  if ten > five
234  then five
235  else ten
236";
237
238        let env = indexmap! {
239            "five".into() => SerdeValue::Number(dec!(5).into()),
240            "ten".into() => SerdeValue::Number(dec!(10).into()),
241        };
242        let actual = test_code(code, Some(env));
243
244        let expected = Ok(SerdeValue::Object(indexmap! {
245            "zero".into() => SerdeValue::Number(dec!(5).into())
246        }));
247
248        assert_eq!(expected, actual);
249    }
250
251    #[test]
252    fn op_let() {
253        let code = "
254zero:
255  let
256    one: ten
257    two: 2
258  in
259    three: one + two
260";
261
262        let env = indexmap! {
263            "ten".into() => SerdeValue::Number(dec!(10).into()),
264        };
265        let actual = test_code(code, Some(env));
266
267        let expected = Ok(SerdeValue::Object(indexmap! {
268            "zero".into() => SerdeValue::Object(indexmap! {
269                "three".into() => SerdeValue::Number(dec!(12).into())
270            })
271        }));
272
273        assert_eq!(expected, actual);
274    }
275}