Skip to main content

brep_render/engine_state/
expressions.rs

1use super::*;
2
3// Expression edits rerun the current history prefix so parameters that reference
4// variables rebuild against the updated environment.
5impl EngineState {
6    /// The history's `expressions` source string (the variable sheet the panel's
7    /// editor binds to). Raw source text — despite the `_json` suffix it mirrors
8    /// the other engine readouts' naming; the verifier reads it verbatim.
9    pub fn expressions_json(&self) -> String {
10        self.history.expressions()
11    }
12
13    /// Replace the `expressions` source and re-run the rolled-to prefix so every
14    /// feature param referencing a variable (e.g. `sizeX = "boxW"`) rebuilds with
15    /// the new value — the panel's live-update path. Returns the build-report JSON.
16    pub fn set_expressions(&mut self, expressions: &str) -> String {
17        self.history.set_expressions(expressions);
18        self.rerun_history()
19    }
20
21    /// The history's `configurator` object (typed named inputs) as JSON — a
22    /// read/display surface for the panel; deeper configurator editing is deferred.
23    pub fn configurator_json(&self) -> String {
24        self.history.configurator().to_string()
25    }
26
27    /// The parsed variable list for the sheet's name/value view:
28    /// `[{ "name": "...", "expr": "..." }, …]` — one entry per `name = rhs;`
29    /// assignment in the expressions source, in source order. The RHS is shown
30    /// verbatim (its DEFINING expression); evaluating it to a live scalar needs the
31    /// kernel's private `Env`, so a computed value column is deferred — the text
32    /// editor + re-run is the source of truth for applied values.
33    pub fn expression_variables_json(&self) -> String {
34        let vars = parse_expression_variables(&self.history.expressions());
35        serde_json::to_string(&vars).unwrap_or_else(|_| "[]".to_string())
36    }
37}
38
39/// Parse the `name = rhs;` assignments from an expressions source into an ordered
40/// `[{name, expr}]` list for the sheet's variable view. Mirrors the kernel
41/// evaluator's statement grammar (`IDENT '=' expr ';'`) without reaching into its
42/// private `Env`: `//` line comments are stripped, statements split on `;`, and
43/// each `IDENT = rhs` yields one entry (a non-identifier LHS or empty RHS is
44/// skipped, matching what the evaluator would reject).
45fn parse_expression_variables(source: &str) -> Vec<serde_json::Value> {
46    // Strip `//` line comments line-by-line (the DSL has no strings, so a bare
47    // `//` always starts a comment), then split statements on ';'.
48    let mut cleaned = String::with_capacity(source.len());
49    for line in source.lines() {
50        let code = match line.find("//") {
51            Some(idx) => &line[..idx],
52            None => line,
53        };
54        cleaned.push_str(code);
55        cleaned.push('\n');
56    }
57    let mut out = Vec::new();
58    for stmt in cleaned.split(';') {
59        let stmt = stmt.trim();
60        if stmt.is_empty() {
61            continue;
62        }
63        let Some(eq) = stmt.find('=') else {
64            continue;
65        };
66        let name = stmt[..eq].trim();
67        let expr = stmt[eq + 1..].trim();
68        if name.is_empty() || expr.is_empty() || !is_identifier(name) {
69            continue;
70        }
71        out.push(serde_json::json!({ "name": name, "expr": expr }));
72    }
73    out
74}
75
76/// Whether `s` is a single expression-DSL identifier (`[A-Za-z_$][A-Za-z0-9_$]*`)
77/// — the valid LHS of an assignment statement.
78fn is_identifier(s: &str) -> bool {
79    let mut chars = s.chars();
80    match chars.next() {
81        Some(c) if c.is_ascii_alphabetic() || c == '_' || c == '$' => {}
82        _ => return false,
83    }
84    chars.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '$')
85}
86
87// BREP private tests: b1558d84d885bf5c
88