Skip to main content

newter_compiler/
value.rs

1//! Runtime values for the Newt interpreter (used during layout).
2
3use crate::ast::{ComponentDecl, Expr, Program, ProgramItem};
4use crate::error::NewtError;
5use std::collections::HashMap;
6
7/// Maximum number of elements `range()` can produce to prevent runaway loops.
8const MAX_RANGE_SIZE: i64 = 10_000;
9
10#[derive(Clone, Debug)]
11pub enum Value {
12    Number(f64),
13    String(String),
14    Bool(bool),
15    Color { r: u8, g: u8, b: u8, a: u8 },
16    Array(Vec<Value>),
17}
18
19impl Value {
20    pub fn as_number(&self) -> Result<f64, NewtError> {
21        match self {
22            Value::Number(n) => Ok(*n),
23            _ => Err(NewtError::Other("expected number".into())),
24        }
25    }
26
27    pub fn as_string(&self) -> Result<&str, NewtError> {
28        match self {
29            Value::String(s) => Ok(s),
30            _ => Err(NewtError::Other("expected string".into())),
31        }
32    }
33
34    pub fn as_bool(&self) -> Result<bool, NewtError> {
35        match self {
36            Value::Bool(b) => Ok(*b),
37            _ => Err(NewtError::Other("expected bool".into())),
38        }
39    }
40
41    pub fn as_color(&self) -> Result<(u8, u8, u8, u8), NewtError> {
42        match self {
43            Value::Color { r, g, b, a } => Ok((*r, *g, *b, *a)),
44            _ => Err(NewtError::Other("expected color".into())),
45        }
46    }
47
48    pub fn as_array(&self) -> Result<&[Value], NewtError> {
49        match self {
50            Value::Array(a) => Ok(a.as_slice()),
51            _ => Err(NewtError::Other("expected array".into())),
52        }
53    }
54}
55
56pub struct EvalContext {
57    pub variables: HashMap<String, Value>,
58    pub components: HashMap<String, ComponentDecl>,
59}
60
61impl EvalContext {
62    pub fn from_program(program: &Program) -> Self {
63        let mut components = HashMap::new();
64        let mut themes: HashMap<String, HashMap<String, Value>> = HashMap::new();
65        for item in &program.items {
66            if let ProgramItem::Component(c) = item {
67                components.insert(c.name.clone(), c.clone());
68            }
69        }
70        let mut ctx = Self {
71            variables: HashMap::new(),
72            components,
73        };
74        for item in &program.items {
75            match item {
76                ProgramItem::Variable(v) => {
77                    if let Ok(val) = eval_expr(&ctx, &v.value) {
78                        ctx.variables.insert(v.name.clone(), val);
79                    }
80                }
81                ProgramItem::Theme(t) => {
82                    let mut theme_vars: HashMap<String, Value> = HashMap::new();
83                    for v in &t.vars {
84                        let mut theme_ctx = EvalContext {
85                            variables: ctx.variables.clone(),
86                            components: ctx.components.clone(),
87                        };
88                        for (k, val) in &theme_vars {
89                            theme_ctx.variables.insert(k.clone(), val.clone());
90                        }
91                        if let Ok(val) = eval_expr(&theme_ctx, &v.value) {
92                            theme_vars.insert(v.name.clone(), val);
93                        }
94                    }
95                    themes.insert(t.name.clone(), theme_vars);
96                }
97                ProgramItem::UseTheme(name) => {
98                    if let Some(theme_vars) = themes.get(name) {
99                        for (k, v) in theme_vars {
100                            ctx.variables.insert(k.clone(), v.clone());
101                        }
102                    }
103                }
104                ProgramItem::StateDecl(sd) => {
105                    if let Ok(val) = eval_expr(&ctx, &sd.initial_value) {
106                        ctx.variables.insert(sd.name.clone(), val);
107                    }
108                }
109                _ => {}
110            }
111        }
112        ctx
113    }
114}
115
116/// Coerce any Value to a display string.
117pub fn value_to_string(v: &Value) -> String {
118    match v {
119        Value::String(s) => s.clone(),
120        Value::Number(n) => {
121            if *n == (*n as i64) as f64 {
122                format!("{}", *n as i64)
123            } else {
124                format!("{}", n)
125            }
126        }
127        Value::Bool(b) => format!("{}", b),
128        Value::Color { r, g, b, a } => {
129            if *a == 255 {
130                format!("#{:02x}{:02x}{:02x}", r, g, b)
131            } else {
132                format!("#{:02x}{:02x}{:02x}{:02x}", r, g, b, a)
133            }
134        }
135        Value::Array(arr) => {
136            let items: Vec<String> = arr.iter().map(value_to_string).collect();
137            format!("[{}]", items.join(", "))
138        }
139    }
140}
141
142pub fn value_to_json(v: &Value) -> serde_json::Value {
143    match v {
144        Value::Number(n) => {
145            if *n == (*n as i64) as f64 {
146                serde_json::Value::Number(serde_json::Number::from(*n as i64))
147            } else {
148                serde_json::json!(*n)
149            }
150        }
151        Value::String(s) => serde_json::Value::String(s.clone()),
152        Value::Bool(b) => serde_json::Value::Bool(*b),
153        Value::Color { r, g, b, a } => {
154            if *a == 255 {
155                serde_json::json!(format!("#{:02x}{:02x}{:02x}", r, g, b))
156            } else {
157                serde_json::json!(format!("#{:02x}{:02x}{:02x}{:02x}", r, g, b, a))
158            }
159        }
160        Value::Array(arr) => serde_json::Value::Array(arr.iter().map(value_to_json).collect()),
161    }
162}
163
164pub fn json_to_value(v: &serde_json::Value) -> Option<Value> {
165    match v {
166        serde_json::Value::Number(n) => Some(Value::Number(n.as_f64()?)),
167        serde_json::Value::String(s) => Some(Value::String(s.clone())),
168        serde_json::Value::Bool(b) => Some(Value::Bool(*b)),
169        _ => None,
170    }
171}
172
173pub fn eval_expr(ctx: &EvalContext, expr: &Expr) -> Result<Value, NewtError> {
174    use crate::ast::{BinaryOp, Expr, Literal, UnaryOp};
175    match expr {
176        Expr::Literal(lit) => match lit {
177            Literal::Number(n) => Ok(Value::Number(*n)),
178            Literal::String(s) => Ok(Value::String(s.clone())),
179            Literal::Bool(b) => Ok(Value::Bool(*b)),
180            Literal::Color { r, g, b, a } => Ok(Value::Color {
181                r: *r,
182                g: *g,
183                b: *b,
184                a: *a,
185            }),
186            Literal::Array(elems) => {
187                let mut arr = Vec::with_capacity(elems.len());
188                for e in elems {
189                    arr.push(eval_expr(ctx, e)?);
190                }
191                Ok(Value::Array(arr))
192            }
193        },
194        Expr::Ident(name, span) => {
195            ctx.variables
196                .get(name)
197                .cloned()
198                .ok_or_else(|| NewtError::semantic(*span, format!("undefined variable '{}'", name)))
199        }
200        Expr::Binary { left, op, right, .. } => {
201            let l = eval_expr(ctx, left)?;
202            let r = eval_expr(ctx, right)?;
203            match op {
204                BinaryOp::Add => Ok(Value::Number(l.as_number()? + r.as_number()?)),
205                BinaryOp::Sub => Ok(Value::Number(l.as_number()? - r.as_number()?)),
206                BinaryOp::Mul => Ok(Value::Number(l.as_number()? * r.as_number()?)),
207                BinaryOp::Div => Ok(Value::Number(l.as_number()? / r.as_number()?)),
208                BinaryOp::Mod => Ok(Value::Number(l.as_number()? % r.as_number()?)),
209                BinaryOp::Eq => Ok(Value::Bool(match (&l, &r) {
210                    (Value::Number(a), Value::Number(b)) => a == b,
211                    (Value::Bool(a), Value::Bool(b)) => a == b,
212                    (Value::String(a), Value::String(b)) => a == b,
213                    _ => false,
214                })),
215                BinaryOp::Ne => Ok(Value::Bool(match (&l, &r) {
216                    (Value::Number(a), Value::Number(b)) => a != b,
217                    (Value::Bool(a), Value::Bool(b)) => a != b,
218                    (Value::String(a), Value::String(b)) => a != b,
219                    _ => true,
220                })),
221                BinaryOp::Lt => Ok(Value::Bool(l.as_number()? < r.as_number()?)),
222                BinaryOp::Le => Ok(Value::Bool(l.as_number()? <= r.as_number()?)),
223                BinaryOp::Gt => Ok(Value::Bool(l.as_number()? > r.as_number()?)),
224                BinaryOp::Ge => Ok(Value::Bool(l.as_number()? >= r.as_number()?)),
225                BinaryOp::And => Ok(Value::Bool(l.as_bool()? && r.as_bool()?)),
226                BinaryOp::Or => Ok(Value::Bool(l.as_bool()? || r.as_bool()?)),
227            }
228        }
229        Expr::Unary { op, inner, .. } => {
230            let v = eval_expr(ctx, inner)?;
231            match op {
232                UnaryOp::Not => Ok(Value::Bool(!v.as_bool()?)),
233                UnaryOp::Neg => Ok(Value::Number(-v.as_number()?)),
234            }
235        }
236        Expr::Block { stmts, .. } => {
237            let mut block_ctx = EvalContext {
238                variables: ctx.variables.clone(),
239                components: ctx.components.clone(),
240            };
241            let mut last = Value::Bool(false);
242            for s in stmts {
243                match s {
244                    crate::ast::Stmt::Expr(e) => last = eval_expr(&block_ctx, e)?,
245                    crate::ast::Stmt::Let { name, value, .. } => {
246                        let val = eval_expr(&block_ctx, value)?;
247                        block_ctx.variables.insert(name.clone(), val.clone());
248                        last = val;
249                    }
250                    crate::ast::Stmt::StateDecl(sd) => {
251                        let val = eval_expr(&block_ctx, &sd.initial_value)?;
252                        block_ctx.variables.insert(sd.name.clone(), val.clone());
253                        last = val;
254                    }
255                }
256            }
257            Ok(last)
258        }
259        Expr::If { cond, then_branch, else_branch, .. } => {
260            if eval_expr(ctx, cond)?.as_bool()? {
261                eval_expr(ctx, then_branch)
262            } else if let Some(eb) = else_branch {
263                eval_expr(ctx, eb)
264            } else {
265                Ok(Value::Bool(false))
266            }
267        }
268        Expr::Call {
269            callee,
270            args,
271            slot_args,
272            span,
273            ..
274        } => {
275            if callee == "range" && args.len() == 1 && slot_args.is_none() {
276                let n = eval_expr(ctx, &args[0])?.as_number()? as i64;
277                let n = n.max(0).min(MAX_RANGE_SIZE) as usize;
278                return Ok(Value::Array(
279                    (0..n).map(|i| Value::Number(i as f64)).collect(),
280                ));
281            }
282            let comp = ctx
283                .components
284                .get(callee)
285                .ok_or_else(|| NewtError::semantic(*span, format!("unknown component '{}'", callee)))?;
286            let body = if let Some(ref slots) = slot_args {
287                crate::ast::substitute_slots(&comp.body, slots)
288            } else {
289                comp.body.clone()
290            };
291            let mut new_ctx = EvalContext {
292                variables: ctx.variables.clone(),
293                components: ctx.components.clone(),
294            };
295            if slot_args.is_none() {
296                for (i, param) in comp.params.iter().enumerate() {
297                    if let Some(arg) = args.get(i) {
298                        if let Ok(v) = eval_expr(ctx, arg) {
299                            new_ctx.variables.insert(param.clone(), v);
300                        }
301                    }
302                }
303            }
304            eval_expr(&new_ctx, &body)
305        }
306        Expr::For { var, iter, body, span, .. } => {
307            let iter_val = eval_expr(ctx, iter)?;
308            let arr = iter_val.as_array().map_err(|_| {
309                NewtError::semantic(*span, "for loop expects an array or range(n)")
310            })?;
311            let results: Result<Vec<Value>, NewtError> = arr.iter().map(|val| {
312                let mut loop_ctx = EvalContext {
313                    variables: ctx.variables.clone(),
314                    components: ctx.components.clone(),
315                };
316                loop_ctx.variables.insert(var.clone(), val.clone());
317                eval_expr(&loop_ctx, body)
318            }).collect();
319            Ok(Value::Array(results?))
320        }
321        Expr::InterpolatedString { parts, .. } => {
322            let mut result = String::new();
323            for seg in parts {
324                match seg {
325                    crate::ast::InterpSegment::Literal(s) => result.push_str(s),
326                    crate::ast::InterpSegment::Expr(e) => {
327                        let val = eval_expr(ctx, e)?;
328                        result.push_str(&value_to_string(&val));
329                    }
330                }
331            }
332            Ok(Value::String(result))
333        }
334        Expr::Assignment { value, .. } => {
335            // At compile time, evaluate RHS only (actual mutation is runtime)
336            eval_expr(ctx, value)
337        }
338        _ => Err(NewtError::Other("expression not evaluable to value".into())),
339    }
340}