Skip to main content

meow_meow_script/
transform.rs

1/// AstTransform passes applied between parsing and evaluation.
2use crate::ast::{
3    BinOpKind, BlockStatement, CallExpression, ElseBranch, Expression, Ident, IfStatement,
4    Statement,
5};
6
7// ---------------------------------------------------------------------------
8// EmitLiftTransform
9// ---------------------------------------------------------------------------
10
11/// Rewrites free-standing `ComponentExpression` statements into `emit(ce)` calls.
12///
13/// ```text
14/// Statement::Expression(Expression::Component(ce))
15///     →  Statement::Expression(Expression::Call { callee: "emit", args: [Component(ce)] })
16/// ```
17///
18/// Applied recursively to all blocks (top-level, function bodies, if-branches, etc.).
19/// No syntax or tokenizer changes — purely a structural AST rewrite.
20pub struct EmitLiftTransform;
21
22impl EmitLiftTransform {
23    pub fn apply(stmts: &mut Vec<Statement>) {
24        for stmt in stmts.iter_mut() {
25            lift_stmt(stmt);
26        }
27    }
28}
29
30fn lift_stmt(stmt: &mut Statement) {
31    match stmt {
32        Statement::Expression(expr) => {
33            if let Expression::Component(_) = expr {
34                // Move the expression out, wrap in emit() call.
35                let inner = std::mem::replace(expr, Expression::Null);
36                *expr = Expression::Call(CallExpression {
37                    callee: Box::new(Expression::Identifier(Ident("emit".into()))),
38                    args: vec![inner],
39                });
40            } else if let Expression::Function { body, .. } = expr {
41                lift_block(body);
42            }
43        }
44        Statement::Assignment(a) => {
45            if let Expression::Function { body, .. } = &mut a.value {
46                lift_block(body);
47            }
48        }
49        Statement::Block(block) => lift_block(block),
50        Statement::If(if_stmt) => lift_if(if_stmt),
51        Statement::Return(_) => {}
52        Statement::ForIn { body, .. } => lift_block(body),
53        Statement::While { body, .. } => lift_block(body),
54        Statement::Break | Statement::Continue => {}
55        Statement::Import { .. } => {}
56        Statement::Reassign { .. } => {}
57    }
58}
59
60fn lift_block(block: &mut BlockStatement) {
61    for stmt in block.statements.iter_mut() {
62        lift_stmt(stmt);
63    }
64}
65
66fn lift_if(if_stmt: &mut IfStatement) {
67    lift_block(&mut if_stmt.then_branch);
68    if let Some(else_branch) = &mut if_stmt.else_branch {
69        lift_else_branch(else_branch);
70    }
71}
72
73fn lift_else_branch(else_branch: &mut ElseBranch) {
74    match else_branch {
75        ElseBranch::Block(block) => lift_block(block),
76        ElseBranch::If(if_stmt) => lift_if(if_stmt),
77    }
78}
79
80// ---------------------------------------------------------------------------
81// QueryDesugarTransform
82// ---------------------------------------------------------------------------
83
84/// Rewrites `->` query/dispatch sugar into explicit `query()`/`query_all()` calls.
85///
86/// The parser produces `BinOp(Query, lhs, rhs)` for all `->` expressions without
87/// interpreting selector/query semantics. This pass rewrites query/dispatch forms
88/// before the evaluator runs, leaving only plain `expr |> fn_value` pipes for eval.
89///
90/// ## Rewrite rules (implemented)
91///
92/// ```text
93/// "selector" -> handler
94///     →  query("selector", handler)        // if selector matches #id pattern
95///     →  query_all("selector", handler)    // otherwise
96/// ```
97///
98/// ## Deferred (pending Query HostCall + small extra rewrite rules)
99///
100/// ```text
101/// "selector" -> method(args)
102///     →  query("selector", fn(r) { r.method(args) })
103///
104/// scope -> "selector" -> handler
105///     →  scope.query("selector", handler)
106/// ```
107///
108/// These do **not** need a dedicated `Expression::MethodCall` AST node — methods
109/// are already represented as `CallExpression` with `callee = BinOp(Dot, ..)`.
110/// They need: (1) a Query HostCall on the host, (2) detection of `Call`-shaped
111/// rhs in the existing `BinOp(Query, ..)` rewrite. See
112/// `docs/meow_meow/task/query-engine-and-mms-query-host-calls.md`.
113///
114/// Selector heuristic: starts with `#` and contains no spaces or combinators → single
115/// (`query`); otherwise → multiple (`query_all`). Callers can override with the explicit
116/// `query()`/`query_all()` function forms.
117pub struct QueryDesugarTransform;
118
119impl QueryDesugarTransform {
120    pub fn apply(stmts: &mut Vec<Statement>) {
121        for stmt in stmts.iter_mut() {
122            qd_stmt(stmt);
123        }
124    }
125}
126
127fn selector_is_single(sel: &str) -> bool {
128    sel.starts_with('#') && !sel.contains(' ') && !sel.contains('>')
129}
130
131fn qd_stmt(stmt: &mut Statement) {
132    match stmt {
133        Statement::Expression(expr) => qd_expr(expr),
134        Statement::Assignment(a) => qd_expr(&mut a.value),
135        Statement::Reassign { value, .. } => qd_expr(value),
136        Statement::Return(ret) => {
137            if let Some(expr) = &mut ret.value {
138                qd_expr(expr);
139            }
140        }
141        Statement::If(if_stmt) => qd_if(if_stmt),
142        Statement::Block(block) => qd_block(block),
143        Statement::ForIn { iterable, body, .. } => {
144            qd_expr(iterable);
145            qd_block(body);
146        }
147        Statement::While { condition, body } => {
148            qd_expr(condition);
149            qd_block(body);
150        }
151        Statement::Break | Statement::Continue | Statement::Import { .. } => {}
152    }
153}
154
155fn qd_block(block: &mut BlockStatement) {
156    for stmt in block.statements.iter_mut() {
157        qd_stmt(stmt);
158    }
159}
160
161fn qd_if(if_stmt: &mut IfStatement) {
162    qd_expr(&mut if_stmt.condition);
163    qd_block(&mut if_stmt.then_branch);
164    if let Some(else_branch) = &mut if_stmt.else_branch {
165        qd_else_branch(else_branch);
166    }
167}
168
169fn qd_else_branch(else_branch: &mut ElseBranch) {
170    match else_branch {
171        ElseBranch::Block(block) => qd_block(block),
172        ElseBranch::If(if_stmt) => qd_if(if_stmt),
173    }
174}
175
176fn qd_expr(expr: &mut Expression) {
177    // Bottom-up: recurse into children first, then rewrite this node.
178    match expr {
179        Expression::BinaryOp { lhs, rhs, .. } => {
180            qd_expr(lhs);
181            qd_expr(rhs);
182        }
183        Expression::UnaryOp { operand, .. } => qd_expr(operand),
184        Expression::Index { base, index } => {
185            qd_expr(base);
186            qd_expr(index);
187        }
188        Expression::Call(call) => {
189            for arg in call.args.iter_mut() {
190                qd_expr(arg);
191            }
192        }
193        Expression::Array(items) => {
194            for item in items.iter_mut() {
195                qd_expr(item);
196            }
197        }
198        Expression::Table(fields) => {
199            for field in fields.iter_mut() {
200                qd_expr(&mut field.value);
201            }
202        }
203        Expression::Function { body, .. } => qd_block(body),
204        _ => {}
205    }
206
207    // Now rewrite this node if it's a query/dispatch expression.
208    if let Expression::BinaryOp {
209        op: BinOpKind::Query,
210        lhs,
211        rhs,
212    } = expr
213    {
214        let callee = if let Expression::String(sel) = lhs.as_ref() {
215            if selector_is_single(sel) {
216                "query"
217            } else {
218                "query_all"
219            }
220        } else {
221            "query_all" // computed selector — conservative fallback
222        };
223        let sel_expr = std::mem::replace(lhs.as_mut(), Expression::Null);
224        let handler_or_call = std::mem::replace(rhs.as_mut(), Expression::Null);
225        // If the rhs is a method-shorthand call (e.g. `set_color(0,1,0,1)`),
226        // wrap it as `fn(__qr) { __qr.method(args) }` so the receiver is the
227        // implicit query result. We only do this when the call's callee is a
228        // bare identifier — anything more complex is treated as a regular
229        // expression handler.
230        let handler = match handler_or_call {
231            Expression::Call(CallExpression { callee, args })
232                if matches!(callee.as_ref(), Expression::Identifier(_)) =>
233            {
234                let method = match *callee {
235                    Expression::Identifier(id) => id,
236                    _ => unreachable!(),
237                };
238                wrap_method_shorthand(method, args)
239            }
240            other => other,
241        };
242        *expr = Expression::Call(CallExpression {
243            callee: Box::new(Expression::Identifier(Ident(callee.into()))),
244            args: vec![sel_expr, handler],
245        });
246    }
247}
248
249/// Build `fn(__qr) { __qr.method(args) }` for `"sel" -> method(args)` sugar.
250fn wrap_method_shorthand(method: Ident, args: Vec<Expression>) -> Expression {
251    let receiver = Ident("__qr".into());
252    let receiver_ref = Expression::Identifier(receiver.clone());
253    let dot = Expression::BinaryOp {
254        op: BinOpKind::Dot,
255        lhs: Box::new(receiver_ref),
256        rhs: Box::new(Expression::Identifier(method)),
257    };
258    let call = Expression::Call(CallExpression {
259        callee: Box::new(dot),
260        args,
261    });
262    Expression::Function {
263        params: vec![receiver],
264        body: BlockStatement {
265            statements: vec![Statement::Expression(call)],
266        },
267    }
268}