Skip to main content

pepl_eval/
space.rs

1//! SpaceInstance — runtime representation of a PEPL space.
2//!
3//! Manages state, derived fields, invariants, action dispatch,
4//! view rendering, and atomic transactions with rollback.
5
6use crate::error::{EvalError, EvalResult};
7use crate::evaluator::Evaluator;
8use crate::test_runner::MockResponse;
9use pepl_stdlib::{ResultValue, Value};
10use pepl_types::ast::*;
11use std::collections::BTreeMap;
12
13/// A snapshot of the view surface tree.
14#[derive(Debug, Clone, PartialEq)]
15pub struct SurfaceNode {
16    /// Component name (e.g., "Text", "Column").
17    pub component: String,
18    /// Component props as key-value pairs.
19    pub props: BTreeMap<String, Value>,
20    /// Child surface nodes.
21    pub children: Vec<SurfaceNode>,
22}
23
24/// The result of dispatching an action.
25#[derive(Debug)]
26pub struct ActionResult {
27    /// Whether the action committed (true) or rolled back (false).
28    pub committed: bool,
29    /// Invariant violation message, if rollback occurred.
30    pub invariant_error: Option<String>,
31}
32
33/// Runtime instance of a PEPL space.
34///
35/// Holds the current state, derived fields, and references to the AST
36/// for actions, views, and invariants. Supports atomic action dispatch
37/// with invariant checking and rollback.
38pub struct SpaceInstance {
39    /// The evaluator engine.
40    eval: Evaluator,
41    /// State field names (for identifying which env bindings are state).
42    state_fields: Vec<String>,
43    /// Derived field definitions (name + expression, computed in order).
44    derived_fields: Vec<(String, Expr)>,
45    /// Invariant definitions (name + condition expression).
46    invariants: Vec<(String, Expr)>,
47    /// Action declarations.
48    actions: Vec<ActionDecl>,
49    /// View declarations.
50    views: Vec<ViewDecl>,
51    /// Credential values (set by host before dispatch).
52    credentials: BTreeMap<String, Value>,
53    /// Update declaration (optional game loop).
54    update_decl: Option<UpdateDecl>,
55    /// HandleEvent declaration (optional game loop).
56    handle_event_decl: Option<HandleEventDecl>,
57    /// Mock capability responses for test runner.
58    mock_responses: Vec<MockResponse>,
59}
60
61impl SpaceInstance {
62    /// Create a new SpaceInstance from a parsed+validated Program.
63    ///
64    /// Initializes state fields with their default values, computes
65    /// derived fields, and registers actions/views.
66    pub fn new(program: &Program) -> EvalResult<Self> {
67        Self::with_gas_limit(program, 1_000_000)
68    }
69
70    /// Create with a custom gas limit.
71    pub fn with_gas_limit(program: &Program, gas_limit: u64) -> EvalResult<Self> {
72        let body = &program.space.body;
73        let mut eval = Evaluator::new(gas_limit);
74
75        // Register action names for reference resolution
76        eval.action_names = body.actions.iter().map(|a| a.name.name.clone()).collect();
77
78        // Initialize state fields with default values
79        let mut state_fields = Vec::new();
80        for field in &body.state.fields {
81            let default = eval.eval_expr(&field.default)?;
82            eval.env.define(&field.name.name, default);
83            state_fields.push(field.name.name.clone());
84        }
85
86        // Register credentials (as nil initially — host sets them before use)
87        let mut credentials = BTreeMap::new();
88        if let Some(creds) = &body.credentials {
89            for field in &creds.fields {
90                eval.env.define(&field.name.name, Value::Nil);
91                credentials.insert(field.name.name.clone(), Value::Nil);
92            }
93        }
94
95        // Collect derived field definitions
96        let derived_fields: Vec<(String, Expr)> = body
97            .derived
98            .as_ref()
99            .map(|d| {
100                d.fields
101                    .iter()
102                    .map(|f| (f.name.name.clone(), f.value.clone()))
103                    .collect()
104            })
105            .unwrap_or_default();
106
107        // Collect invariants
108        let invariants: Vec<(String, Expr)> = body
109            .invariants
110            .iter()
111            .map(|inv| (inv.name.name.clone(), inv.condition.clone()))
112            .collect();
113
114        let mut instance = Self {
115            eval,
116            state_fields,
117            derived_fields,
118            invariants,
119            actions: body.actions.clone(),
120            views: body.views.clone(),
121            credentials,
122            update_decl: body.update.clone(),
123            handle_event_decl: body.handle_event.clone(),
124            mock_responses: Vec::new(),
125        };
126
127        // Compute initial derived fields
128        instance.recompute_derived()?;
129
130        Ok(instance)
131    }
132
133    // ══════════════════════════════════════════════════════════════════════
134    // State access
135    // ══════════════════════════════════════════════════════════════════════
136
137    /// Get the current value of a state field.
138    pub fn get_state(&self, name: &str) -> Option<&Value> {
139        self.eval.env.get(name)
140    }
141
142    /// Get all state as a snapshot.
143    pub fn state_snapshot(&self) -> BTreeMap<String, Value> {
144        let mut snap = BTreeMap::new();
145        for name in &self.state_fields {
146            if let Some(val) = self.eval.env.get(name) {
147                snap.insert(name.clone(), val.clone());
148            }
149        }
150        snap
151    }
152
153    /// Get captured log output.
154    pub fn log_output(&self) -> &[String] {
155        &self.eval.log_output
156    }
157
158    /// Clear log output.
159    pub fn clear_log(&mut self) {
160        self.eval.log_output.clear();
161    }
162
163    /// Set a credential value (called by host before actions).
164    pub fn set_credential(&mut self, name: &str, value: Value) {
165        self.credentials.insert(name.to_string(), value.clone());
166        self.eval.env.define(name, value);
167    }
168
169    // ══════════════════════════════════════════════════════════════════════
170    // Action dispatch
171    // ══════════════════════════════════════════════════════════════════════
172
173    /// Dispatch an action by name with arguments.
174    ///
175    /// Implements atomic transactions:
176    /// 1. Snapshot pre-action state
177    /// 2. Execute action body
178    /// 3. Check invariants
179    /// 4. Commit or rollback
180    pub fn dispatch(&mut self, action_name: &str, args: Vec<Value>) -> EvalResult<ActionResult> {
181        // Find the action
182        let action = self
183            .actions
184            .iter()
185            .find(|a| a.name.name == action_name)
186            .cloned()
187            .ok_or_else(|| EvalError::UndefinedAction(action_name.to_string()))?;
188
189        // Snapshot pre-action state
190        let snapshot = self.eval.env.global_bindings().clone();
191
192        // Push action scope, bind parameters
193        self.eval.env.push_scope();
194        for (param, arg) in action.params.iter().zip(args.into_iter()) {
195            self.eval.env.define(&param.name.name, arg);
196        }
197
198        // Execute action body
199        let exec_result = self.eval.eval_block(&action.body);
200        self.eval.env.pop_scope();
201
202        // Handle return (early exit — prior set statements applied)
203        match exec_result {
204            Ok(_) => {}
205            Err(EvalError::Return(_)) => {} // Return is expected — prior sets are kept
206            Err(e) => return Err(e),
207        }
208
209        // Recompute derived fields before invariant check
210        self.recompute_derived()?;
211
212        // Check invariants
213        match self.check_invariants() {
214            Ok(()) => Ok(ActionResult {
215                committed: true,
216                invariant_error: None,
217            }),
218            Err(msg) => {
219                // Rollback to pre-action state
220                self.eval.env.restore_global(snapshot);
221                // Recompute derived with rolled-back state
222                self.recompute_derived()?;
223                Ok(ActionResult {
224                    committed: false,
225                    invariant_error: Some(msg),
226                })
227            }
228        }
229    }
230
231    // ══════════════════════════════════════════════════════════════════════
232    // Derived fields
233    // ══════════════════════════════════════════════════════════════════════
234
235    /// Recompute all derived fields in declaration order.
236    fn recompute_derived(&mut self) -> EvalResult<()> {
237        for (name, expr) in &self.derived_fields.clone() {
238            let val = self.eval.eval_expr(expr)?;
239            // Define/update derived field in global scope
240            if !self.eval.env.set(name, val.clone()) {
241                self.eval.env.define(name, val);
242            }
243        }
244        Ok(())
245    }
246
247    // ══════════════════════════════════════════════════════════════════════
248    // Invariant checking
249    // ══════════════════════════════════════════════════════════════════════
250
251    /// Check all invariants. Returns Ok(()) if all pass, Err(message) if one fails.
252    fn check_invariants(&mut self) -> Result<(), String> {
253        for (name, condition) in &self.invariants.clone() {
254            match self.eval.eval_expr(condition) {
255                Ok(val) => {
256                    if !val.is_truthy() {
257                        return Err(format!("invariant '{name}' violated"));
258                    }
259                }
260                Err(e) => {
261                    return Err(format!("invariant '{name}' evaluation error: {e}"));
262                }
263            }
264        }
265        Ok(())
266    }
267
268    // ══════════════════════════════════════════════════════════════════════
269    // View rendering
270    // ══════════════════════════════════════════════════════════════════════
271
272    /// Render the main view (or a named view) to a Surface tree.
273    pub fn render_view(&mut self, view_name: &str) -> EvalResult<Vec<SurfaceNode>> {
274        let view = self
275            .views
276            .iter()
277            .find(|v| v.name.name == view_name)
278            .cloned()
279            .ok_or_else(|| EvalError::Runtime(format!("unknown view '{view_name}'")))?;
280
281        self.eval_ui_block(&view.body)
282    }
283
284    /// Render the default "main" view.
285    pub fn render(&mut self) -> EvalResult<Vec<SurfaceNode>> {
286        self.render_view("main")
287    }
288
289    fn eval_ui_block(&mut self, block: &UIBlock) -> EvalResult<Vec<SurfaceNode>> {
290        let mut nodes = Vec::new();
291        for elem in &block.elements {
292            self.eval_ui_element(elem, &mut nodes)?;
293        }
294        Ok(nodes)
295    }
296
297    fn eval_ui_element(&mut self, elem: &UIElement, out: &mut Vec<SurfaceNode>) -> EvalResult<()> {
298        match elem {
299            UIElement::Component(comp) => {
300                let node = self.eval_component(comp)?;
301                out.push(node);
302            }
303            UIElement::Let(binding) => {
304                let value = self.eval.eval_expr(&binding.value)?;
305                if let Some(name) = &binding.name {
306                    self.eval.env.define(&name.name, value);
307                }
308            }
309            UIElement::If(ui_if) => {
310                self.eval_ui_if(ui_if, out)?;
311            }
312            UIElement::For(ui_for) => {
313                self.eval_ui_for(ui_for, out)?;
314            }
315        }
316        Ok(())
317    }
318
319    fn eval_component(&mut self, comp: &ComponentExpr) -> EvalResult<SurfaceNode> {
320        let mut props = BTreeMap::new();
321        for prop in &comp.props {
322            let name = &prop.name.name;
323            // Handle action references: on_tap, on_change, etc.
324            if name.starts_with("on_") {
325                match &prop.value.kind {
326                    ExprKind::Identifier(action_name) => {
327                        // Direct action reference: on_tap: increment
328                        let mut action_props = BTreeMap::new();
329                        action_props
330                            .insert("__action".to_string(), Value::String(action_name.clone()));
331                        props.insert(
332                            name.clone(),
333                            Value::Record {
334                                type_name: None,
335                                fields: action_props,
336                            },
337                        );
338                        continue;
339                    }
340                    ExprKind::Call {
341                        name: fn_name,
342                        args,
343                    } => {
344                        // Action call with args: on_tap: toggle(index)
345                        let mut action_props = BTreeMap::new();
346                        action_props
347                            .insert("__action".to_string(), Value::String(fn_name.name.clone()));
348                        let mut arg_vals = Vec::new();
349                        for arg in args {
350                            arg_vals.push(self.eval.eval_expr(arg)?);
351                        }
352                        action_props.insert("__args".to_string(), Value::List(arg_vals));
353                        props.insert(
354                            name.clone(),
355                            Value::Record {
356                                type_name: None,
357                                fields: action_props,
358                            },
359                        );
360                        continue;
361                    }
362                    ExprKind::Lambda(lambda) => {
363                        // Lambda callback: on_change: fn(v) { ... }
364                        // or on_change: update_input (which is an action name)
365                        let closure = self.eval.eval_lambda(lambda)?;
366                        let mut lambda_props = BTreeMap::new();
367                        lambda_props.insert("__lambda".to_string(), closure);
368                        props.insert(
369                            name.clone(),
370                            Value::Record {
371                                type_name: None,
372                                fields: lambda_props,
373                            },
374                        );
375                        continue;
376                    }
377                    _ => {}
378                }
379            }
380
381            // Normal prop evaluation
382            let val = self.eval.eval_expr(&prop.value)?;
383            props.insert(name.clone(), val);
384        }
385
386        let children = if let Some(child_block) = &comp.children {
387            self.eval_ui_block(child_block)?
388        } else {
389            Vec::new()
390        };
391
392        Ok(SurfaceNode {
393            component: comp.name.name.clone(),
394            props,
395            children,
396        })
397    }
398
399    fn eval_ui_if(&mut self, ui_if: &UIIf, out: &mut Vec<SurfaceNode>) -> EvalResult<()> {
400        let cond = self.eval.eval_expr(&ui_if.condition)?;
401        if cond.is_truthy() {
402            let nodes = self.eval_ui_block(&ui_if.then_block)?;
403            out.extend(nodes);
404        } else if let Some(else_block) = &ui_if.else_block {
405            match else_block {
406                UIElse::ElseIf(elif) => self.eval_ui_if(elif, out)?,
407                UIElse::Block(block) => {
408                    let nodes = self.eval_ui_block(block)?;
409                    out.extend(nodes);
410                }
411            }
412        }
413        Ok(())
414    }
415
416    fn eval_ui_for(&mut self, ui_for: &UIFor, out: &mut Vec<SurfaceNode>) -> EvalResult<()> {
417        let iterable = self.eval.eval_expr(&ui_for.iterable)?;
418        let items = match iterable {
419            Value::List(items) => items,
420            _ => {
421                return Err(EvalError::TypeMismatch(format!(
422                    "for loop requires list, got {}",
423                    iterable.type_name()
424                )));
425            }
426        };
427
428        self.eval.env.push_scope();
429        for (i, item) in items.iter().enumerate() {
430            self.eval.env.define(&ui_for.item.name, item.clone());
431            if let Some(idx) = &ui_for.index {
432                self.eval.env.define(&idx.name, Value::Number(i as f64));
433            }
434            let nodes = self.eval_ui_block(&ui_for.body)?;
435            out.extend(nodes);
436        }
437        self.eval.env.pop_scope();
438        Ok(())
439    }
440
441    // ══════════════════════════════════════════════════════════════════════
442    // Test runner helpers
443    // ══════════════════════════════════════════════════════════════════════
444
445    /// Install mock capability responses (used by test runner).
446    pub fn set_mock_responses(&mut self, mocks: Vec<MockResponse>) {
447        // Propagate to evaluator for stdlib dispatch
448        self.eval.mock_responses = mocks
449            .iter()
450            .map(|m| (m.module.clone(), m.function.clone(), m.response.clone()))
451            .collect();
452        self.mock_responses = mocks;
453    }
454
455    /// Evaluate an expression via the internal evaluator (public for test runner).
456    pub fn eval_expr_public(&mut self, expr: &Expr) -> EvalResult<Value> {
457        self.eval.eval_expr(expr)
458    }
459
460    /// Execute a statement via the internal evaluator (public for test runner).
461    pub fn eval_stmt_public(&mut self, stmt: &Stmt) -> EvalResult<Value> {
462        self.eval.eval_stmt(stmt)
463    }
464
465    /// Define a variable in the current environment scope (public for test runner).
466    pub fn define_in_env(&mut self, name: &str, value: Value) {
467        self.eval.env.define(name, value);
468    }
469
470    /// Push a new scope in the environment (public for test runner).
471    pub fn push_scope(&mut self) {
472        self.eval.env.push_scope();
473    }
474
475    /// Pop the innermost scope (public for test runner).
476    pub fn pop_scope(&mut self) {
477        self.eval.env.pop_scope();
478    }
479
480    // ══════════════════════════════════════════════════════════════════════
481    // Game loop
482    // ══════════════════════════════════════════════════════════════════════
483
484    /// Call `update(dt)` — game loop tick.
485    ///
486    /// Like an action dispatch: atomic, with invariant checking and rollback.
487    pub fn call_update(&mut self, dt: f64) -> EvalResult<ActionResult> {
488        let update = self
489            .update_decl
490            .clone()
491            .ok_or_else(|| EvalError::Runtime("space has no update() declaration".into()))?;
492
493        let snapshot = self.eval.env.global_bindings().clone();
494
495        self.eval.env.push_scope();
496        self.eval
497            .env
498            .define(&update.param.name.name, Value::Number(dt));
499
500        let exec_result = self.eval.eval_block(&update.body);
501        self.eval.env.pop_scope();
502
503        match exec_result {
504            Ok(_) => {}
505            Err(EvalError::Return(_)) => {}
506            Err(e) => return Err(e),
507        }
508
509        self.recompute_derived()?;
510
511        match self.check_invariants() {
512            Ok(()) => Ok(ActionResult {
513                committed: true,
514                invariant_error: None,
515            }),
516            Err(msg) => {
517                self.eval.env.restore_global(snapshot);
518                self.recompute_derived()?;
519                Ok(ActionResult {
520                    committed: false,
521                    invariant_error: Some(msg),
522                })
523            }
524        }
525    }
526
527    /// Call `handleEvent(event)` — game loop event handler.
528    ///
529    /// Like an action dispatch: atomic, with invariant checking and rollback.
530    pub fn call_handle_event(&mut self, event: Value) -> EvalResult<ActionResult> {
531        let handler = self
532            .handle_event_decl
533            .clone()
534            .ok_or_else(|| EvalError::Runtime("space has no handleEvent() declaration".into()))?;
535
536        let snapshot = self.eval.env.global_bindings().clone();
537
538        self.eval.env.push_scope();
539        self.eval.env.define(&handler.param.name.name, event);
540
541        let exec_result = self.eval.eval_block(&handler.body);
542        self.eval.env.pop_scope();
543
544        match exec_result {
545            Ok(_) => {}
546            Err(EvalError::Return(_)) => {}
547            Err(e) => return Err(e),
548        }
549
550        self.recompute_derived()?;
551
552        match self.check_invariants() {
553            Ok(()) => Ok(ActionResult {
554                committed: true,
555                invariant_error: None,
556            }),
557            Err(msg) => {
558                self.eval.env.restore_global(snapshot);
559                self.recompute_derived()?;
560                Ok(ActionResult {
561                    committed: false,
562                    invariant_error: Some(msg),
563                })
564            }
565        }
566    }
567
568    // ══════════════════════════════════════════════════════════════════════
569    // Surface serialization
570    // ══════════════════════════════════════════════════════════════════════
571
572    /// Serialize a surface tree to JSON.
573    pub fn surface_to_json(nodes: &[SurfaceNode]) -> serde_json::Value {
574        serde_json::Value::Array(nodes.iter().map(Self::node_to_json).collect())
575    }
576
577    fn node_to_json(node: &SurfaceNode) -> serde_json::Value {
578        let mut map = serde_json::Map::new();
579        map.insert(
580            "component".to_string(),
581            serde_json::Value::String(node.component.clone()),
582        );
583
584        let mut props_map = serde_json::Map::new();
585        for (k, v) in &node.props {
586            props_map.insert(k.clone(), Self::value_to_json(v));
587        }
588        map.insert("props".to_string(), serde_json::Value::Object(props_map));
589
590        if !node.children.is_empty() {
591            map.insert(
592                "children".to_string(),
593                serde_json::Value::Array(node.children.iter().map(Self::node_to_json).collect()),
594            );
595        }
596
597        serde_json::Value::Object(map)
598    }
599
600    fn value_to_json(val: &Value) -> serde_json::Value {
601        Self::value_to_json_public(val)
602    }
603
604    /// Convert a Value to JSON (public for golden reference generation).
605    pub fn value_to_json_public(val: &Value) -> serde_json::Value {
606        match val {
607            Value::Number(n) => {
608                if n.fract() == 0.0
609                    && n.is_finite()
610                    && *n >= i64::MIN as f64
611                    && *n <= i64::MAX as f64
612                {
613                    serde_json::Value::Number(serde_json::Number::from(*n as i64))
614                } else {
615                    serde_json::json!(*n)
616                }
617            }
618            Value::String(s) => serde_json::Value::String(s.clone()),
619            Value::Bool(b) => serde_json::Value::Bool(*b),
620            Value::Nil => serde_json::Value::Null,
621            Value::List(items) => {
622                serde_json::Value::Array(items.iter().map(Self::value_to_json).collect())
623            }
624            Value::Record { fields, .. } => {
625                let mut map = serde_json::Map::new();
626                for (k, v) in fields {
627                    map.insert(k.clone(), Self::value_to_json(v));
628                }
629                serde_json::Value::Object(map)
630            }
631            Value::Result(r) => match r.as_ref() {
632                ResultValue::Ok(v) => serde_json::json!({"Ok": Self::value_to_json(v)}),
633                ResultValue::Err(v) => serde_json::json!({"Err": Self::value_to_json(v)}),
634            },
635            Value::SumVariant {
636                variant, fields, ..
637            } => {
638                if fields.is_empty() {
639                    serde_json::Value::String(variant.clone())
640                } else {
641                    serde_json::json!({
642                        variant: fields.iter().map(Self::value_to_json).collect::<Vec<_>>()
643                    })
644                }
645            }
646            Value::Function(_) => serde_json::Value::String("<function>".to_string()),
647            Value::Color { r, g, b, a } => {
648                serde_json::json!({"r": r, "g": g, "b": b, "a": a})
649            }
650        }
651    }
652}