dash_lang/
eval.rs

1use crate::ast::{Expr, Stmt, Context, LoopControl, Op};
2
3/// Evaluates an expression within the given context and returns its result as a string.
4///
5/// Supports literals, variables, binary operations, and function calls.
6/// Binary operations are evaluated as integer arithmetic or comparisons.
7/// Function calls are executed with a new local context.
8///
9/// # Arguments
10/// * `expr` - The expression to evaluate.
11/// * `ctx` - The current execution context containing variables and functions.
12///
13/// # Returns
14/// A string representing the result of the evaluated expression.
15pub fn eval_expr(expr: &Expr, ctx: &Context) -> String {
16    match expr {
17        Expr::Int(i) => i.to_string(),
18        Expr::Str(s) => s.clone(),
19        Expr::Var(name) => ctx
20            .variables
21            .get(name)
22            .cloned()
23            .unwrap_or_else(|| panic!("Undefined variable: {}", name)),
24        Expr::Binary(left, op, right) => {
25            let l = eval_expr(left, ctx).parse::<i64>().unwrap();
26            let r = eval_expr(right, ctx).parse::<i64>().unwrap();
27            let result = match op {
28                Op::Add => l + r,
29                Op::Sub => l - r,
30                Op::Mul => l * r,
31                Op::Div => l / r,
32                Op::Greater => (l > r) as i64,
33                Op::Less => (l < r) as i64,
34                Op::GreaterEq => (l >= r) as i64,
35                Op::LessEq => (l <= r) as i64,
36                Op::Equal => (l == r) as i64,
37                Op::NotEqual => (l != r) as i64,
38            };
39            result.to_string()
40        }
41        Expr::Call(name, args) => {
42            let (params, body) = ctx
43                .functions
44                .get(name)
45                .unwrap_or_else(|| panic!("Undefined function: {}", name))
46                .clone();
47
48            if params.len() != args.len() {
49                panic!(
50                    "Function '{}' expected {} args, got {}",
51                    name,
52                    params.len(),
53                    args.len()
54                );
55            }
56
57            let mut local_ctx = Context::default();
58            for (param, arg) in params.iter().zip(args.iter()) {
59                let value = eval_expr(arg, ctx);
60                local_ctx.variables.insert(param.clone(), value);
61            }
62
63            for stmt in body {
64                match exec_stmt(&stmt, &mut local_ctx) {
65                    LoopControl::Return(val) => return val,
66                    LoopControl::None => continue,
67                    _ => panic!("Unexpected control flow in function"),
68                }
69            }
70            "".to_string()
71        }
72    }
73}
74
75/// Executes a single statement within the given mutable context.
76///
77/// Handles all statement types including variable assignment, control flow,
78/// function definitions, function calls, and return statements.
79///
80/// # Arguments
81/// * `stmt` - The statement to execute.
82/// * `ctx` - The mutable execution context.
83///
84/// # Returns
85/// A `LoopControl` value indicating control flow status (e.g., break, continue, return).
86pub fn exec_stmt(stmt: &Stmt, ctx: &mut Context) -> LoopControl {
87    match stmt {
88        Stmt::Print(expr) => {
89            println!("{}", eval_expr(expr, ctx));
90            LoopControl::None
91        }
92        Stmt::Let(name, expr) => {
93            let value = eval_expr(expr, ctx);
94            ctx.variables.insert(name.clone(), value);
95            LoopControl::None
96        }
97        Stmt::Break => LoopControl::Break,
98        Stmt::Continue => LoopControl::Continue,
99        Stmt::If {
100            condition,
101            then_branch,
102            else_branch,
103        } => {
104            let cond_value = eval_expr(condition, ctx);
105            let is_true = cond_value != "0" && cond_value != "" && cond_value != "false";
106            let fallback = Vec::new();
107            let branch = if is_true {
108                then_branch
109            } else {
110                else_branch.as_ref().unwrap_or(&fallback)
111            };
112            for stmt in branch {
113                match exec_stmt(stmt, ctx) {
114                    LoopControl::None => continue,
115                    control => return control,
116                }
117            }
118            LoopControl::None
119        }
120        Stmt::While { condition, body } => {
121            while eval_expr(condition, ctx) != "0" {
122                for stmt in body {
123                    match exec_stmt(stmt, ctx) {
124                        LoopControl::None => continue,
125                        LoopControl::Break => return LoopControl::None,
126                        LoopControl::Continue => break,
127                        LoopControl::Return(val) => return LoopControl::Return(val),
128                    }
129                }
130            }
131            LoopControl::None
132        }
133        Stmt::Fn { name, params, body } => {
134            ctx.functions
135                .insert(name.clone(), (params.clone(), body.to_vec()));
136            LoopControl::None
137        }
138        Stmt::Call(name, args) => {
139            let (params, body) = ctx.functions.get(name).unwrap().clone();
140            let mut local_ctx = Context::default();
141            for (param, arg) in params.iter().zip(args.iter()) {
142                let value = eval_expr(arg, ctx);
143                local_ctx.variables.insert(param.clone(), value);
144            }
145            for stmt in body {
146                exec_stmt(&stmt, &mut local_ctx);
147            }
148            LoopControl::None
149        }
150        Stmt::Return(expr) => {
151            let value = eval_expr(expr, ctx);
152            LoopControl::Return(value)
153        }
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160    use crate::ast::{Expr, Op, Context};
161
162    #[test]
163    fn test_addition_expr() {
164        let expr = Expr::Binary(Box::new(Expr::Int(2)), Op::Add, Box::new(Expr::Int(3)));
165        let ctx = Context::default();
166        let result = eval_expr(&expr, &ctx);
167        assert_eq!(result, "5");
168    }
169
170    #[test]
171    fn test_variable_lookup() {
172        let mut ctx = Context::default();
173        ctx.variables.insert("x".to_string(), "42".to_string());
174        let expr = Expr::Var("x".to_string());
175        let result = eval_expr(&expr, &ctx);
176        assert_eq!(result, "42");
177    }
178}