Skip to main content

zapcode_core/parser/
mod.rs

1pub mod ir;
2
3use ir::*;
4use oxc_allocator::Allocator;
5use oxc_ast::ast;
6use oxc_parser::Parser;
7use oxc_span::SourceType;
8
9use crate::error::{Result, ZapcodeError};
10
11pub fn parse(source: &str) -> Result<Program> {
12    // Auto-wrap trailing object literals: `{ key: value }` → `({ key: value })`
13    // This avoids the JS ambiguity where `{` at statement start is a block.
14    let source = wrap_trailing_object(source);
15
16    let allocator = Allocator::default();
17    let source_type = SourceType::tsx();
18    let ret = Parser::new(&allocator, &source, source_type).parse();
19
20    if !ret.errors.is_empty() {
21        let msgs: Vec<String> = ret.errors.iter().map(|e| e.to_string()).collect();
22        return Err(ZapcodeError::ParseError(msgs.join("\n")));
23    }
24
25    let mut lowerer = AstLowerer::new(&source);
26    lowerer.lower_program(&ret.program)?;
27
28    Ok(Program {
29        body: lowerer.body,
30        functions: lowerer.functions,
31    })
32}
33
34/// If the source ends with a `{ ... }` block that looks like an object literal
35/// (contains `key: value` or `key,` patterns), wrap it in `(...)` so oxc
36/// parses it as an expression instead of a block statement.
37fn wrap_trailing_object(source: &str) -> String {
38    let trimmed = source.trim_end();
39
40    // Must end with `}`
41    if !trimmed.ends_with('}') {
42        return source.to_string();
43    }
44
45    // Find the matching `{`
46    //
47    // Limitation: this brace scanner is a simple depth-counting heuristic that
48    // does not account for braces inside string literals, comments, or template
49    // literals. This is acceptable for preprocessing because:
50    //   1. The input is AI-generated tool output (simple expressions), not
51    //      arbitrary user code likely to contain embedded brace characters.
52    //   2. This is only a heuristic for trailing-object extraction — if the
53    //      heuristic produces malformed output, oxc will catch the parse error
54    //      downstream, so correctness is never silently lost.
55    //
56    // TODO: revisit this heuristic if real-world failures are reported (e.g.
57    // strings containing unbalanced braces causing incorrect extraction).
58    let mut depth = 0;
59    let mut open_pos = None;
60    for (i, ch) in trimmed.char_indices().rev() {
61        match ch {
62            '}' => depth += 1,
63            '{' => {
64                depth -= 1;
65                if depth == 0 {
66                    open_pos = Some(i);
67                    break;
68                }
69            }
70            _ => {}
71        }
72    }
73
74    let open_pos = match open_pos {
75        Some(pos) => pos,
76        None => return source.to_string(),
77    };
78
79    // The `{` must be at the start of a statement (preceded by newline, semicolon, or start)
80    let before = trimmed[..open_pos].trim_end();
81    if !before.is_empty() {
82        let last_char = before.chars().last().unwrap();
83        // If preceded by =, (, return, =>, etc. — it's already in expression context
84        if matches!(last_char, '=' | '(' | ',' | ':' | '>' | '[') {
85            return source.to_string();
86        }
87        // If preceded by a keyword that takes a block, don't wrap
88        let last_word = before
89            .rsplit(|c: char| !c.is_alphanumeric() && c != '_')
90            .next()
91            .unwrap_or("");
92        if matches!(
93            last_word,
94            "if" | "else"
95                | "for"
96                | "while"
97                | "do"
98                | "try"
99                | "catch"
100                | "finally"
101                | "class"
102                | "function"
103                | "switch"
104        ) {
105            return source.to_string();
106        }
107    }
108
109    // Check the content between braces looks like object literal syntax
110    let inner = &trimmed[open_pos + 1..trimmed.len() - 1].trim();
111    if inner.is_empty() {
112        return source.to_string();
113    }
114
115    // Heuristic: contains `identifier:` pattern (key-value) or commas between identifiers
116    let looks_like_object = inner.contains(':') || {
117        // Check for shorthand properties: `{ a, b }` pattern
118        inner.split(',').all(|part| {
119            let p = part.trim();
120            !p.is_empty()
121                && p.chars()
122                    .all(|c| c.is_alphanumeric() || c == '_' || c == ' ')
123        })
124    };
125
126    if !looks_like_object {
127        return source.to_string();
128    }
129
130    // Wrap in parentheses with a semicolon to prevent it being parsed
131    // as a function call on the preceding expression (e.g. `1({a})`)
132    let close_pos = source.rfind('}').unwrap();
133    let mut result = String::with_capacity(source.len() + 3);
134    result.push_str(&source[..open_pos]);
135    result.push_str(";(");
136    result.push_str(&source[open_pos..=close_pos]);
137    result.push(')');
138    if close_pos + 1 < source.len() {
139        result.push_str(&source[close_pos + 1..]);
140    }
141    result
142}
143
144struct AstLowerer<'a> {
145    #[allow(dead_code)]
146    source: &'a str,
147    body: Vec<Statement>,
148    functions: Vec<FunctionDef>,
149}
150
151impl<'a> AstLowerer<'a> {
152    fn new(source: &'a str) -> Self {
153        Self {
154            source,
155            body: Vec::new(),
156            functions: Vec::new(),
157        }
158    }
159
160    fn span(&self, s: oxc_span::Span) -> Span {
161        s.into()
162    }
163
164    fn unsupported(&self, span: oxc_span::Span, desc: &str) -> ZapcodeError {
165        ZapcodeError::UnsupportedSyntax {
166            span: format!("{}..{}", span.start, span.end),
167            description: desc.to_string(),
168        }
169    }
170
171    fn lower_program(&mut self, program: &ast::Program<'_>) -> Result<()> {
172        // Handle directives (e.g., "use strict", but also bare string literals)
173        for directive in &program.directives {
174            let span = self.span(directive.span);
175            let expr = Expr::StringLit(directive.directive.to_string());
176            self.body.push(Statement::Expression { expr, span });
177        }
178        for stmt in &program.body {
179            let s = self.lower_statement(stmt)?;
180            self.body.push(s);
181        }
182        Ok(())
183    }
184
185    fn lower_statement(&mut self, stmt: &ast::Statement<'_>) -> Result<Statement> {
186        match stmt {
187            ast::Statement::VariableDeclaration(decl) => self.lower_var_decl(decl),
188            ast::Statement::ExpressionStatement(expr_stmt) => {
189                let span = self.span(expr_stmt.span);
190                let expr = self.lower_expr(&expr_stmt.expression)?;
191                Ok(Statement::Expression { expr, span })
192            }
193            ast::Statement::ReturnStatement(ret) => {
194                let span = self.span(ret.span);
195                let value = match &ret.argument {
196                    Some(arg) => Some(self.lower_expr(arg)?),
197                    None => None,
198                };
199                Ok(Statement::Return { value, span })
200            }
201            ast::Statement::IfStatement(if_stmt) => self.lower_if(if_stmt),
202            ast::Statement::WhileStatement(while_stmt) => {
203                let span = self.span(while_stmt.span);
204                let test = self.lower_expr(&while_stmt.test)?;
205                let body = self.lower_statement_as_block(&while_stmt.body)?;
206                Ok(Statement::While { test, body, span })
207            }
208            ast::Statement::DoWhileStatement(do_while) => {
209                let span = self.span(do_while.span);
210                let body = self.lower_statement_as_block(&do_while.body)?;
211                let test = self.lower_expr(&do_while.test)?;
212                Ok(Statement::DoWhile { body, test, span })
213            }
214            ast::Statement::ForStatement(for_stmt) => self.lower_for(for_stmt),
215            ast::Statement::ForInStatement(s) => {
216                Err(self.unsupported(s.span, "for...in loops are not supported, use for...of"))
217            }
218            ast::Statement::ForOfStatement(for_of) => self.lower_for_of(for_of),
219            ast::Statement::BlockStatement(block) => {
220                let span = self.span(block.span);
221                let body = self.lower_statements(&block.body)?;
222                Ok(Statement::Block { body, span })
223            }
224            ast::Statement::ThrowStatement(throw) => {
225                let span = self.span(throw.span);
226                let value = self.lower_expr(&throw.argument)?;
227                Ok(Statement::Throw { value, span })
228            }
229            ast::Statement::TryStatement(try_stmt) => self.lower_try(try_stmt),
230            ast::Statement::BreakStatement(s) => Ok(Statement::Break {
231                span: self.span(s.span),
232            }),
233            ast::Statement::ContinueStatement(s) => Ok(Statement::Continue {
234                span: self.span(s.span),
235            }),
236            ast::Statement::FunctionDeclaration(func) => self.lower_func_decl(func),
237            ast::Statement::ClassDeclaration(class) => self.lower_class_decl(class),
238            ast::Statement::SwitchStatement(switch) => self.lower_switch(switch),
239            ast::Statement::EmptyStatement(_) => Ok(Statement::Expression {
240                expr: Expr::UndefinedLit,
241                span: Span { start: 0, end: 0 },
242            }),
243            ast::Statement::LabeledStatement(labeled) => self.lower_statement(&labeled.body),
244            ast::Statement::TSTypeAliasDeclaration(s) => Ok(Statement::Expression {
245                expr: Expr::UndefinedLit,
246                span: self.span(s.span),
247            }),
248            ast::Statement::TSInterfaceDeclaration(s) => Ok(Statement::Expression {
249                expr: Expr::UndefinedLit,
250                span: self.span(s.span),
251            }),
252            ast::Statement::TSEnumDeclaration(s) => {
253                Err(self.unsupported(s.span, "TypeScript enums are not supported"))
254            }
255            ast::Statement::ImportDeclaration(s) => Err(ZapcodeError::SandboxViolation(format!(
256                "import declarations are forbidden in the sandbox (at {}..{})",
257                s.span.start, s.span.end
258            ))),
259            ast::Statement::ExportDefaultDeclaration(s) => {
260                Err(ZapcodeError::SandboxViolation(format!(
261                    "export declarations are forbidden in the sandbox (at {}..{})",
262                    s.span.start, s.span.end
263                )))
264            }
265            ast::Statement::ExportNamedDeclaration(s) => {
266                Err(ZapcodeError::SandboxViolation(format!(
267                    "export declarations are forbidden in the sandbox (at {}..{})",
268                    s.span.start, s.span.end
269                )))
270            }
271            ast::Statement::ExportAllDeclaration(s) => {
272                Err(ZapcodeError::SandboxViolation(format!(
273                    "export declarations are forbidden in the sandbox (at {}..{})",
274                    s.span.start, s.span.end
275                )))
276            }
277            ast::Statement::DebuggerStatement(s) => {
278                Err(self.unsupported(s.span, "unsupported statement type"))
279            }
280            ast::Statement::WithStatement(s) => {
281                Err(self.unsupported(s.span, "unsupported statement type"))
282            }
283            ast::Statement::TSModuleDeclaration(s) => {
284                Err(self.unsupported(s.span, "unsupported statement type"))
285            }
286            ast::Statement::TSGlobalDeclaration(s) => {
287                Err(self.unsupported(s.span, "unsupported statement type"))
288            }
289            ast::Statement::TSImportEqualsDeclaration(s) => {
290                Err(self.unsupported(s.span, "unsupported statement type"))
291            }
292            ast::Statement::TSExportAssignment(s) => {
293                Err(self.unsupported(s.span, "unsupported statement type"))
294            }
295            ast::Statement::TSNamespaceExportDeclaration(s) => {
296                Err(self.unsupported(s.span, "unsupported statement type"))
297            }
298        }
299    }
300
301    fn lower_statements(&mut self, stmts: &[ast::Statement<'_>]) -> Result<Vec<Statement>> {
302        stmts.iter().map(|s| self.lower_statement(s)).collect()
303    }
304
305    fn lower_statement_as_block(&mut self, stmt: &ast::Statement<'_>) -> Result<Vec<Statement>> {
306        match stmt {
307            ast::Statement::BlockStatement(block) => self.lower_statements(&block.body),
308            other => Ok(vec![self.lower_statement(other)?]),
309        }
310    }
311
312    fn lower_var_decl(&mut self, decl: &ast::VariableDeclaration<'_>) -> Result<Statement> {
313        let span = self.span(decl.span);
314        let kind = match decl.kind {
315            ast::VariableDeclarationKind::Const => VarKind::Const,
316            ast::VariableDeclarationKind::Let => VarKind::Let,
317            ast::VariableDeclarationKind::Var => VarKind::Var,
318            ast::VariableDeclarationKind::Using | ast::VariableDeclarationKind::AwaitUsing => {
319                return Err(self.unsupported(decl.span, "using declarations are not supported"));
320            }
321        };
322        let mut declarations = Vec::new();
323        for declarator in &decl.declarations {
324            let pattern = self.lower_binding_pattern(&declarator.id)?;
325            let init = match &declarator.init {
326                Some(expr) => Some(self.lower_expr(expr)?),
327                None => None,
328            };
329            declarations.push(VarDeclarator { pattern, init });
330        }
331        Ok(Statement::VariableDecl {
332            kind,
333            declarations,
334            span,
335        })
336    }
337
338    fn lower_binding_pattern(&mut self, pat: &ast::BindingPattern<'_>) -> Result<AssignTarget> {
339        match pat {
340            ast::BindingPattern::BindingIdentifier(id) => {
341                Ok(AssignTarget::Ident(id.name.to_string()))
342            }
343            ast::BindingPattern::ObjectPattern(obj) => {
344                let mut fields = Vec::new();
345                for prop in &obj.properties {
346                    let key = property_key_to_string(&prop.key);
347                    let alias = match &prop.value {
348                        ast::BindingPattern::BindingIdentifier(id) => {
349                            let name = id.name.to_string();
350                            if name != key {
351                                Some(name)
352                            } else {
353                                None
354                            }
355                        }
356                        _ => None,
357                    };
358                    let default = match &prop.value {
359                        ast::BindingPattern::AssignmentPattern(assign) => {
360                            Some(self.lower_expr(&assign.right)?)
361                        }
362                        _ => None,
363                    };
364                    fields.push(DestructureField {
365                        key,
366                        alias,
367                        default,
368                    });
369                }
370                Ok(AssignTarget::ObjectDestructure(fields))
371            }
372            ast::BindingPattern::ArrayPattern(arr) => {
373                let mut elements = Vec::new();
374                for elem in &arr.elements {
375                    match elem {
376                        Some(pat) => elements.push(Some(self.lower_binding_pattern(pat)?)),
377                        None => elements.push(None),
378                    }
379                }
380                Ok(AssignTarget::ArrayDestructure(elements))
381            }
382            ast::BindingPattern::AssignmentPattern(assign) => {
383                self.lower_binding_pattern(&assign.left)
384            }
385        }
386    }
387
388    fn lower_binding_pattern_to_param(
389        &mut self,
390        pat: &ast::BindingPattern<'_>,
391    ) -> Result<ParamPattern> {
392        match pat {
393            ast::BindingPattern::BindingIdentifier(id) => {
394                Ok(ParamPattern::Ident(id.name.to_string()))
395            }
396            ast::BindingPattern::ObjectPattern(obj) => {
397                let mut fields = Vec::new();
398                for prop in &obj.properties {
399                    let key = property_key_to_string(&prop.key);
400                    let alias = match &prop.value {
401                        ast::BindingPattern::BindingIdentifier(id) => {
402                            let name = id.name.to_string();
403                            if name != key {
404                                Some(name)
405                            } else {
406                                None
407                            }
408                        }
409                        _ => None,
410                    };
411                    fields.push(DestructureField {
412                        key,
413                        alias,
414                        default: None,
415                    });
416                }
417                Ok(ParamPattern::ObjectDestructure(fields))
418            }
419            ast::BindingPattern::ArrayPattern(arr) => {
420                let mut elems = Vec::new();
421                for elem in &arr.elements {
422                    match elem {
423                        Some(p) => elems.push(Some(self.lower_binding_pattern_to_param(p)?)),
424                        None => elems.push(None),
425                    }
426                }
427                Ok(ParamPattern::ArrayDestructure(elems))
428            }
429            ast::BindingPattern::AssignmentPattern(assign) => {
430                let inner = self.lower_binding_pattern_to_param(&assign.left)?;
431                let default = self.lower_expr(&assign.right)?;
432                Ok(ParamPattern::DefaultValue {
433                    pattern: Box::new(inner),
434                    default,
435                })
436            }
437        }
438    }
439
440    fn lower_if(&mut self, if_stmt: &ast::IfStatement<'_>) -> Result<Statement> {
441        let span = self.span(if_stmt.span);
442        let test = self.lower_expr(&if_stmt.test)?;
443        let consequent = self.lower_statement_as_block(&if_stmt.consequent)?;
444        let alternate = match &if_stmt.alternate {
445            Some(alt) => Some(self.lower_statement_as_block(alt)?),
446            None => None,
447        };
448        Ok(Statement::If {
449            test,
450            consequent,
451            alternate,
452            span,
453        })
454    }
455
456    fn lower_for(&mut self, for_stmt: &ast::ForStatement<'_>) -> Result<Statement> {
457        let span = self.span(for_stmt.span);
458        let init = match &for_stmt.init {
459            Some(init) => match init {
460                ast::ForStatementInit::VariableDeclaration(decl) => {
461                    Some(Box::new(self.lower_var_decl(decl)?))
462                }
463                other => {
464                    if let Some(expr_ref) = other.as_expression() {
465                        let expr = self.lower_expr(expr_ref)?;
466                        Some(Box::new(Statement::Expression { expr, span }))
467                    } else {
468                        return Err(ZapcodeError::CompileError(
469                            "unsupported for-loop initializer".to_string(),
470                        ));
471                    }
472                }
473            },
474            None => None,
475        };
476        let test = match &for_stmt.test {
477            Some(t) => Some(self.lower_expr(t)?),
478            None => None,
479        };
480        let update = match &for_stmt.update {
481            Some(u) => Some(self.lower_expr(u)?),
482            None => None,
483        };
484        let body = self.lower_statement_as_block(&for_stmt.body)?;
485        Ok(Statement::For {
486            init,
487            test,
488            update,
489            body,
490            span,
491        })
492    }
493
494    fn lower_for_of(&mut self, for_of: &ast::ForOfStatement<'_>) -> Result<Statement> {
495        let span = self.span(for_of.span);
496        let binding = match &for_of.left {
497            ast::ForStatementLeft::VariableDeclaration(decl) => {
498                if let Some(declarator) = decl.declarations.first() {
499                    match &declarator.id {
500                        ast::BindingPattern::BindingIdentifier(id) => {
501                            ForBinding::Ident(id.name.to_string())
502                        }
503                        _ => {
504                            let pat = self.lower_binding_pattern_to_param(&declarator.id)?;
505                            ForBinding::Destructure(pat)
506                        }
507                    }
508                } else {
509                    return Err(self.unsupported(for_of.span, "empty for-of binding"));
510                }
511            }
512            _ => return Err(self.unsupported(for_of.span, "unsupported for-of left-hand side")),
513        };
514        let iterable = self.lower_expr(&for_of.right)?;
515        let body = self.lower_statement_as_block(&for_of.body)?;
516        Ok(Statement::ForOf {
517            binding,
518            iterable,
519            body,
520            span,
521        })
522    }
523
524    fn lower_try(&mut self, try_stmt: &ast::TryStatement<'_>) -> Result<Statement> {
525        let span = self.span(try_stmt.span);
526        let try_body = self.lower_statements(&try_stmt.block.body)?;
527        let (catch_param, catch_body) = match &try_stmt.handler {
528            Some(handler) => {
529                let param = handler.param.as_ref().and_then(|p| match &p.pattern {
530                    ast::BindingPattern::BindingIdentifier(id) => Some(id.name.to_string()),
531                    _ => None,
532                });
533                let body = self.lower_statements(&handler.body.body)?;
534                (param, body)
535            }
536            None => (None, Vec::new()),
537        };
538        let finally_body = match &try_stmt.finalizer {
539            Some(block) => Some(self.lower_statements(&block.body)?),
540            None => None,
541        };
542        Ok(Statement::TryCatch {
543            try_body,
544            catch_param,
545            catch_body,
546            finally_body,
547            span,
548        })
549    }
550
551    fn lower_func_decl(&mut self, func: &ast::Function<'_>) -> Result<Statement> {
552        let span = self.span(func.span);
553        let func_def = self.lower_function(func)?;
554        let func_index = self.functions.len();
555        self.functions.push(func_def);
556        Ok(Statement::FunctionDecl { func_index, span })
557    }
558
559    fn lower_function(&mut self, func: &ast::Function<'_>) -> Result<FunctionDef> {
560        let name = func.id.as_ref().map(|id| id.name.to_string());
561        let params = self.lower_formal_params(&func.params)?;
562        let body = match &func.body {
563            Some(body) => self.lower_statements(&body.statements)?,
564            None => Vec::new(),
565        };
566        Ok(FunctionDef {
567            name,
568            params,
569            body,
570            is_async: func.r#async,
571            is_generator: func.generator,
572            is_arrow: false,
573            span: self.span(func.span),
574        })
575    }
576
577    fn lower_formal_params(
578        &mut self,
579        params: &ast::FormalParameters<'_>,
580    ) -> Result<Vec<ParamPattern>> {
581        let mut result = Vec::new();
582        for param in &params.items {
583            let pat = self.lower_binding_pattern_to_param(&param.pattern)?;
584            result.push(pat);
585        }
586        if let Some(rest) = &params.rest {
587            match &rest.rest.argument {
588                ast::BindingPattern::BindingIdentifier(id) => {
589                    result.push(ParamPattern::Rest(id.name.to_string()));
590                }
591                _ => {
592                    return Err(self.unsupported(
593                        rest.span,
594                        "complex rest parameter patterns are not supported",
595                    ));
596                }
597            }
598        }
599        Ok(result)
600    }
601
602    fn lower_class_decl(&mut self, class: &ast::Class<'_>) -> Result<Statement> {
603        let span = self.span(class.span);
604        let name = class
605            .id
606            .as_ref()
607            .map(|id| id.name.to_string())
608            .unwrap_or_else(|| "AnonymousClass".to_string());
609
610        let super_class = match &class.super_class {
611            Some(expr) => {
612                if let ast::Expression::Identifier(id) = expr {
613                    Some(id.name.to_string())
614                } else {
615                    return Err(self.unsupported(
616                        class.span,
617                        "computed super class expressions are not supported",
618                    ));
619                }
620            }
621            None => None,
622        };
623
624        let (constructor, methods, static_methods) = self.lower_class_body(&class.body)?;
625
626        Ok(Statement::ClassDecl {
627            name,
628            super_class,
629            constructor,
630            methods,
631            static_methods,
632            span,
633        })
634    }
635
636    fn lower_class_expr(&mut self, class: &ast::Class<'_>) -> Result<Expr> {
637        let name = class.id.as_ref().map(|id| id.name.to_string());
638
639        let super_class = match &class.super_class {
640            Some(expr) => {
641                if let ast::Expression::Identifier(id) = expr {
642                    Some(id.name.to_string())
643                } else {
644                    return Err(self.unsupported(
645                        class.span,
646                        "computed super class expressions are not supported",
647                    ));
648                }
649            }
650            None => None,
651        };
652
653        let (constructor, methods, static_methods) = self.lower_class_body(&class.body)?;
654
655        Ok(Expr::ClassExpr {
656            name,
657            super_class,
658            constructor,
659            methods,
660            static_methods,
661        })
662    }
663
664    fn lower_class_body(&mut self, body: &ast::ClassBody<'_>) -> Result<ClassBodyParts> {
665        let mut constructor = None;
666        let mut methods = Vec::new();
667        let mut static_methods = Vec::new();
668
669        for element in &body.body {
670            match element {
671                ast::ClassElement::MethodDefinition(method) => {
672                    let method_name = match &method.key {
673                        ast::PropertyKey::StaticIdentifier(id) => id.name.to_string(),
674                        ast::PropertyKey::StringLiteral(s) => s.value.to_string(),
675                        _ => continue, // skip computed method names
676                    };
677
678                    let func = &method.value;
679                    let params = self.lower_formal_params(&func.params)?;
680                    let body_stmts = match &func.body {
681                        Some(body) => self.lower_statements(&body.statements)?,
682                        None => Vec::new(),
683                    };
684
685                    let func_def = FunctionDef {
686                        name: Some(method_name.clone()),
687                        params,
688                        body: body_stmts,
689                        is_async: func.r#async,
690                        is_generator: false,
691                        is_arrow: false,
692                        span: self.span(func.span),
693                    };
694
695                    match method.kind {
696                        ast::MethodDefinitionKind::Constructor => {
697                            constructor = Some(Box::new(func_def));
698                        }
699                        ast::MethodDefinitionKind::Method => {
700                            if method.r#static {
701                                static_methods.push(ClassMethod {
702                                    name: method_name,
703                                    func: func_def,
704                                });
705                            } else {
706                                methods.push(ClassMethod {
707                                    name: method_name,
708                                    func: func_def,
709                                });
710                            }
711                        }
712                        ast::MethodDefinitionKind::Get | ast::MethodDefinitionKind::Set => {
713                            // Getters/setters: treat as regular methods for now
714                            if method.r#static {
715                                static_methods.push(ClassMethod {
716                                    name: method_name,
717                                    func: func_def,
718                                });
719                            } else {
720                                methods.push(ClassMethod {
721                                    name: method_name,
722                                    func: func_def,
723                                });
724                            }
725                        }
726                    }
727                }
728                ast::ClassElement::PropertyDefinition(_) => {
729                    // Class property declarations (e.g., `name: string;`) are type-level
730                    // and are handled at runtime through constructor assignments.
731                    // Skip them in the IR.
732                }
733                ast::ClassElement::AccessorProperty(s) => {
734                    return Err(self
735                        .unsupported(s.span, "accessor properties in classes are not supported"));
736                }
737                ast::ClassElement::TSIndexSignature(_) => {
738                    // TypeScript-only, skip
739                }
740                ast::ClassElement::StaticBlock(s) => {
741                    return Err(self.unsupported(s.span, "static blocks are not supported"));
742                }
743            }
744        }
745
746        Ok((constructor, methods, static_methods))
747    }
748
749    fn lower_switch(&mut self, switch: &ast::SwitchStatement<'_>) -> Result<Statement> {
750        let span = self.span(switch.span);
751        let discriminant = self.lower_expr(&switch.discriminant)?;
752        let mut cases = Vec::new();
753        for case in &switch.cases {
754            let test = match &case.test {
755                Some(t) => Some(self.lower_expr(t)?),
756                None => None,
757            };
758            let consequent = self.lower_statements(&case.consequent)?;
759            cases.push(SwitchCase { test, consequent });
760        }
761        Ok(Statement::Switch {
762            discriminant,
763            cases,
764            span,
765        })
766    }
767
768    fn lower_expr(&mut self, expr: &ast::Expression<'_>) -> Result<Expr> {
769        match expr {
770            ast::Expression::NumericLiteral(lit) => Ok(Expr::NumberLit(lit.value)),
771            ast::Expression::StringLiteral(lit) => Ok(Expr::StringLit(lit.value.to_string())),
772            ast::Expression::BooleanLiteral(lit) => Ok(Expr::BoolLit(lit.value)),
773            ast::Expression::NullLiteral(_) => Ok(Expr::NullLit),
774            ast::Expression::TemplateLiteral(tpl) => {
775                let quasis: Vec<String> =
776                    tpl.quasis.iter().map(|q| q.value.raw.to_string()).collect();
777                let exprs: Result<Vec<Expr>> =
778                    tpl.expressions.iter().map(|e| self.lower_expr(e)).collect();
779                Ok(Expr::TemplateLit {
780                    quasis,
781                    exprs: exprs?,
782                })
783            }
784            ast::Expression::RegExpLiteral(re) => Ok(Expr::RegExpLit {
785                pattern: format!("{:?}", re.regex.pattern),
786                flags: re.regex.flags.to_string(),
787            }),
788            ast::Expression::Identifier(id) => {
789                let name = id.name.to_string();
790                match name.as_str() {
791                    "undefined" => Ok(Expr::UndefinedLit),
792                    "NaN" => Ok(Expr::NumberLit(f64::NAN)),
793                    "Infinity" => Ok(Expr::NumberLit(f64::INFINITY)),
794                    "eval" => Err(ZapcodeError::SandboxViolation(
795                        "eval is forbidden in the sandbox".to_string(),
796                    )),
797                    "Function" => Err(ZapcodeError::SandboxViolation(
798                        "Function constructor is forbidden in the sandbox".to_string(),
799                    )),
800                    "process" => Err(ZapcodeError::SandboxViolation(
801                        "process is forbidden in the sandbox".to_string(),
802                    )),
803                    "globalThis" | "global" => Err(ZapcodeError::SandboxViolation(
804                        "globalThis/global is forbidden in the sandbox".to_string(),
805                    )),
806                    "require" => Err(ZapcodeError::SandboxViolation(
807                        "require is forbidden in the sandbox".to_string(),
808                    )),
809                    _ => Ok(Expr::Ident(name)),
810                }
811            }
812            ast::Expression::ArrayExpression(arr) => {
813                let mut elements = Vec::new();
814                for elem in &arr.elements {
815                    match elem {
816                        ast::ArrayExpressionElement::SpreadElement(spread) => {
817                            let expr = self.lower_expr(&spread.argument)?;
818                            elements.push(Some(Expr::Spread(Box::new(expr))));
819                        }
820                        ast::ArrayExpressionElement::Elision(_) => {
821                            elements.push(None);
822                        }
823                        other => {
824                            let expr_ref = other.to_expression();
825                            let expr = self.lower_expr(expr_ref)?;
826                            elements.push(Some(expr));
827                        }
828                    }
829                }
830                Ok(Expr::Array(elements))
831            }
832            ast::Expression::ObjectExpression(obj) => {
833                let mut props = Vec::new();
834                for prop in &obj.properties {
835                    match prop {
836                        ast::ObjectPropertyKind::ObjectProperty(p) => {
837                            let key = self.lower_property_key(&p.key)?;
838                            let computed = p.computed;
839
840                            if p.shorthand {
841                                props.push(ObjProperty {
842                                    kind: PropKind::Shorthand,
843                                    key: key.clone(),
844                                    value: Expr::Ident(key),
845                                    computed: false,
846                                });
847                            } else if p.method {
848                                let value = self.lower_expr(&p.value)?;
849                                props.push(ObjProperty {
850                                    kind: PropKind::Method,
851                                    key,
852                                    value,
853                                    computed,
854                                });
855                            } else {
856                                let value = self.lower_expr(&p.value)?;
857                                props.push(ObjProperty {
858                                    kind: PropKind::Init,
859                                    key,
860                                    value,
861                                    computed,
862                                });
863                            }
864                        }
865                        ast::ObjectPropertyKind::SpreadProperty(spread) => {
866                            let expr = self.lower_expr(&spread.argument)?;
867                            props.push(ObjProperty {
868                                kind: PropKind::Spread,
869                                key: String::new(),
870                                value: expr,
871                                computed: false,
872                            });
873                        }
874                    }
875                }
876                Ok(Expr::Object(props))
877            }
878            ast::Expression::BinaryExpression(bin) => {
879                let op = lower_binary_op(bin.operator)?;
880                let left = self.lower_expr(&bin.left)?;
881                let right = self.lower_expr(&bin.right)?;
882                Ok(Expr::Binary {
883                    op,
884                    left: Box::new(left),
885                    right: Box::new(right),
886                })
887            }
888            ast::Expression::UnaryExpression(unary) => {
889                if matches!(unary.operator, ast::UnaryOperator::Typeof) {
890                    let operand = self.lower_expr(&unary.argument)?;
891                    return Ok(Expr::TypeOf(Box::new(operand)));
892                }
893                if matches!(unary.operator, ast::UnaryOperator::Delete) {
894                    return Err(self.unsupported(unary.span, "delete operator is not supported"));
895                }
896                let op = match unary.operator {
897                    ast::UnaryOperator::UnaryNegation => UnaryOp::Neg,
898                    ast::UnaryOperator::LogicalNot => UnaryOp::Not,
899                    ast::UnaryOperator::BitwiseNot => UnaryOp::BitNot,
900                    ast::UnaryOperator::Void => UnaryOp::Void,
901                    ast::UnaryOperator::UnaryPlus => {
902                        let operand = self.lower_expr(&unary.argument)?;
903                        return Ok(Expr::Binary {
904                            op: BinOp::Mul,
905                            left: Box::new(operand),
906                            right: Box::new(Expr::NumberLit(1.0)),
907                        });
908                    }
909                    _ => {
910                        return Err(self.unsupported(unary.span, "unsupported unary operator"));
911                    }
912                };
913                let operand = self.lower_expr(&unary.argument)?;
914                Ok(Expr::Unary {
915                    op,
916                    operand: Box::new(operand),
917                })
918            }
919            ast::Expression::UpdateExpression(update) => {
920                let op = match update.operator {
921                    ast::UpdateOperator::Increment => UpdateOp::Increment,
922                    ast::UpdateOperator::Decrement => UpdateOp::Decrement,
923                };
924                let operand = self.lower_simple_assign_target(&update.argument)?;
925                Ok(Expr::Update {
926                    op,
927                    prefix: update.prefix,
928                    operand: Box::new(operand),
929                })
930            }
931            ast::Expression::LogicalExpression(logical) => {
932                let op = match logical.operator {
933                    ast::LogicalOperator::And => LogicalOp::And,
934                    ast::LogicalOperator::Or => LogicalOp::Or,
935                    ast::LogicalOperator::Coalesce => LogicalOp::NullishCoalescing,
936                };
937                let left = self.lower_expr(&logical.left)?;
938                let right = self.lower_expr(&logical.right)?;
939                Ok(Expr::Logical {
940                    op,
941                    left: Box::new(left),
942                    right: Box::new(right),
943                })
944            }
945            ast::Expression::ConditionalExpression(cond) => {
946                let test = self.lower_expr(&cond.test)?;
947                let consequent = self.lower_expr(&cond.consequent)?;
948                let alternate = self.lower_expr(&cond.alternate)?;
949                Ok(Expr::Conditional {
950                    test: Box::new(test),
951                    consequent: Box::new(consequent),
952                    alternate: Box::new(alternate),
953                })
954            }
955            ast::Expression::AssignmentExpression(assign) => {
956                let op = lower_assign_op(assign.operator);
957                let target = self.lower_assignment_target(&assign.left)?;
958                let value = self.lower_expr(&assign.right)?;
959                Ok(Expr::Assignment {
960                    op,
961                    target: Box::new(target),
962                    value: Box::new(value),
963                })
964            }
965            ast::Expression::SequenceExpression(seq) => {
966                let exprs: Result<Vec<Expr>> =
967                    seq.expressions.iter().map(|e| self.lower_expr(e)).collect();
968                Ok(Expr::Sequence(exprs?))
969            }
970            ast::Expression::CallExpression(call) => {
971                let callee = self.lower_expr(&call.callee)?;
972                let args = self.lower_args(&call.arguments)?;
973                Ok(Expr::Call {
974                    callee: Box::new(callee),
975                    args,
976                    optional: call.optional,
977                })
978            }
979            ast::Expression::NewExpression(new_expr) => {
980                let callee = self.lower_expr(&new_expr.callee)?;
981                let args = self.lower_args(&new_expr.arguments)?;
982                Ok(Expr::New {
983                    callee: Box::new(callee),
984                    args,
985                })
986            }
987            ast::Expression::StaticMemberExpression(member) => {
988                let object = self.lower_expr(&member.object)?;
989                let property = member.property.name.to_string();
990                Ok(Expr::Member {
991                    object: Box::new(object),
992                    property,
993                    optional: member.optional,
994                })
995            }
996            ast::Expression::ComputedMemberExpression(member) => {
997                let object = self.lower_expr(&member.object)?;
998                let property = self.lower_expr(&member.expression)?;
999                Ok(Expr::ComputedMember {
1000                    object: Box::new(object),
1001                    property: Box::new(property),
1002                    optional: member.optional,
1003                })
1004            }
1005            ast::Expression::PrivateFieldExpression(s) => {
1006                Err(self.unsupported(s.span, "private fields are not supported"))
1007            }
1008            ast::Expression::ArrowFunctionExpression(arrow) => {
1009                let params = self.lower_formal_params(&arrow.params)?;
1010                let body = if arrow.expression {
1011                    match arrow.body.statements.first() {
1012                        Some(ast::Statement::ExpressionStatement(expr)) => {
1013                            let ret_expr = self.lower_expr(&expr.expression)?;
1014                            vec![Statement::Return {
1015                                value: Some(ret_expr),
1016                                span: self.span(arrow.span),
1017                            }]
1018                        }
1019                        _ => self.lower_statements(&arrow.body.statements)?,
1020                    }
1021                } else {
1022                    self.lower_statements(&arrow.body.statements)?
1023                };
1024                let func_index = self.functions.len();
1025                self.functions.push(FunctionDef {
1026                    name: None,
1027                    params,
1028                    body,
1029                    is_async: arrow.r#async,
1030                    is_generator: false,
1031                    is_arrow: true,
1032                    span: self.span(arrow.span),
1033                });
1034                Ok(Expr::ArrowFunction { func_index })
1035            }
1036            ast::Expression::FunctionExpression(func) => {
1037                let func_def = self.lower_function(func)?;
1038                let func_index = self.functions.len();
1039                self.functions.push(func_def);
1040                Ok(Expr::FunctionExpr { func_index })
1041            }
1042            ast::Expression::AwaitExpression(await_expr) => {
1043                let expr = self.lower_expr(&await_expr.argument)?;
1044                Ok(Expr::Await(Box::new(expr)))
1045            }
1046            ast::Expression::ParenthesizedExpression(paren) => self.lower_expr(&paren.expression),
1047            ast::Expression::ChainExpression(chain) => self.lower_chain_expr(&chain.expression),
1048            ast::Expression::TaggedTemplateExpression(s) => {
1049                Err(self.unsupported(s.span, "tagged template expressions are not supported"))
1050            }
1051            ast::Expression::ThisExpression(_) => Ok(Expr::Ident("this".to_string())),
1052            ast::Expression::Super(_) => Ok(Expr::Ident("super".to_string())),
1053            ast::Expression::YieldExpression(yield_expr) => {
1054                let value = match &yield_expr.argument {
1055                    Some(arg) => Some(Box::new(self.lower_expr(arg)?)),
1056                    None => None,
1057                };
1058                Ok(Expr::Yield {
1059                    value,
1060                    delegate: yield_expr.delegate,
1061                })
1062            }
1063            ast::Expression::ClassExpression(class) => self.lower_class_expr(class),
1064            ast::Expression::MetaProperty(s) => {
1065                Err(self.unsupported(s.span, "meta properties are not supported"))
1066            }
1067            ast::Expression::ImportExpression(s) => Err(ZapcodeError::SandboxViolation(format!(
1068                "dynamic import() is forbidden in the sandbox (at {}..{})",
1069                s.span.start, s.span.end
1070            ))),
1071            ast::Expression::TSAsExpression(ts) => self.lower_expr(&ts.expression),
1072            ast::Expression::TSSatisfiesExpression(ts) => self.lower_expr(&ts.expression),
1073            ast::Expression::TSNonNullExpression(ts) => self.lower_expr(&ts.expression),
1074            ast::Expression::TSTypeAssertion(ts) => self.lower_expr(&ts.expression),
1075            ast::Expression::TSInstantiationExpression(ts) => self.lower_expr(&ts.expression),
1076            _ => Err(ZapcodeError::UnsupportedSyntax {
1077                span: "unknown".to_string(),
1078                description: "unsupported expression type".to_string(),
1079            }),
1080        }
1081    }
1082
1083    fn lower_chain_expr(&mut self, expr: &ast::ChainElement<'_>) -> Result<Expr> {
1084        match expr {
1085            ast::ChainElement::CallExpression(call) => {
1086                let callee = self.lower_expr(&call.callee)?;
1087                let args = self.lower_args(&call.arguments)?;
1088                Ok(Expr::Call {
1089                    callee: Box::new(callee),
1090                    args,
1091                    optional: call.optional,
1092                })
1093            }
1094            ast::ChainElement::StaticMemberExpression(member) => {
1095                let object = self.lower_expr(&member.object)?;
1096                Ok(Expr::Member {
1097                    object: Box::new(object),
1098                    property: member.property.name.to_string(),
1099                    optional: member.optional,
1100                })
1101            }
1102            ast::ChainElement::ComputedMemberExpression(member) => {
1103                let object = self.lower_expr(&member.object)?;
1104                let property = self.lower_expr(&member.expression)?;
1105                Ok(Expr::ComputedMember {
1106                    object: Box::new(object),
1107                    property: Box::new(property),
1108                    optional: member.optional,
1109                })
1110            }
1111            ast::ChainElement::PrivateFieldExpression(s) => {
1112                Err(self.unsupported(s.span, "private fields are not supported"))
1113            }
1114            ast::ChainElement::TSNonNullExpression(ts) => self.lower_expr(&ts.expression),
1115        }
1116    }
1117
1118    fn lower_args(&mut self, args: &[ast::Argument<'_>]) -> Result<Vec<Expr>> {
1119        let mut result = Vec::new();
1120        for arg in args {
1121            match arg {
1122                ast::Argument::SpreadElement(spread) => {
1123                    let expr = self.lower_expr(&spread.argument)?;
1124                    result.push(Expr::Spread(Box::new(expr)));
1125                }
1126                other => {
1127                    let expr_ref = other.to_expression();
1128                    let expr = self.lower_expr(expr_ref)?;
1129                    result.push(expr);
1130                }
1131            }
1132        }
1133        Ok(result)
1134    }
1135
1136    fn lower_property_key(&mut self, key: &ast::PropertyKey<'_>) -> Result<String> {
1137        Ok(property_key_to_string_from_key(key))
1138    }
1139
1140    fn lower_assignment_target(&mut self, target: &ast::AssignmentTarget<'_>) -> Result<Expr> {
1141        match target {
1142            ast::AssignmentTarget::AssignmentTargetIdentifier(id) => {
1143                Ok(Expr::Ident(id.name.to_string()))
1144            }
1145            ast::AssignmentTarget::StaticMemberExpression(member) => {
1146                let object = self.lower_expr(&member.object)?;
1147                Ok(Expr::Member {
1148                    object: Box::new(object),
1149                    property: member.property.name.to_string(),
1150                    optional: false,
1151                })
1152            }
1153            ast::AssignmentTarget::ComputedMemberExpression(member) => {
1154                let object = self.lower_expr(&member.object)?;
1155                let property = self.lower_expr(&member.expression)?;
1156                Ok(Expr::ComputedMember {
1157                    object: Box::new(object),
1158                    property: Box::new(property),
1159                    optional: false,
1160                })
1161            }
1162            _ => Err(ZapcodeError::CompileError(
1163                "unsupported assignment target".to_string(),
1164            )),
1165        }
1166    }
1167
1168    fn lower_simple_assign_target(
1169        &mut self,
1170        target: &ast::SimpleAssignmentTarget<'_>,
1171    ) -> Result<Expr> {
1172        match target {
1173            ast::SimpleAssignmentTarget::AssignmentTargetIdentifier(id) => {
1174                Ok(Expr::Ident(id.name.to_string()))
1175            }
1176            ast::SimpleAssignmentTarget::StaticMemberExpression(member) => {
1177                let object = self.lower_expr(&member.object)?;
1178                Ok(Expr::Member {
1179                    object: Box::new(object),
1180                    property: member.property.name.to_string(),
1181                    optional: false,
1182                })
1183            }
1184            ast::SimpleAssignmentTarget::ComputedMemberExpression(member) => {
1185                let object = self.lower_expr(&member.object)?;
1186                let property = self.lower_expr(&member.expression)?;
1187                Ok(Expr::ComputedMember {
1188                    object: Box::new(object),
1189                    property: Box::new(property),
1190                    optional: false,
1191                })
1192            }
1193            _ => Err(ZapcodeError::CompileError(
1194                "unsupported update target".to_string(),
1195            )),
1196        }
1197    }
1198}
1199
1200fn property_key_to_string(key: &ast::PropertyKey<'_>) -> String {
1201    property_key_to_string_from_key(key)
1202}
1203
1204fn property_key_to_string_from_key(key: &ast::PropertyKey<'_>) -> String {
1205    match key {
1206        ast::PropertyKey::StaticIdentifier(id) => id.name.to_string(),
1207        ast::PropertyKey::StringLiteral(s) => s.value.to_string(),
1208        ast::PropertyKey::NumericLiteral(n) => n.value.to_string(),
1209        _ => "<computed>".to_string(),
1210    }
1211}
1212
1213fn lower_binary_op(op: ast::BinaryOperator) -> Result<BinOp> {
1214    match op {
1215        ast::BinaryOperator::Addition => Ok(BinOp::Add),
1216        ast::BinaryOperator::Subtraction => Ok(BinOp::Sub),
1217        ast::BinaryOperator::Multiplication => Ok(BinOp::Mul),
1218        ast::BinaryOperator::Division => Ok(BinOp::Div),
1219        ast::BinaryOperator::Remainder => Ok(BinOp::Rem),
1220        ast::BinaryOperator::Exponential => Ok(BinOp::Pow),
1221        ast::BinaryOperator::Equality => Ok(BinOp::Eq),
1222        ast::BinaryOperator::Inequality => Ok(BinOp::Neq),
1223        ast::BinaryOperator::StrictEquality => Ok(BinOp::StrictEq),
1224        ast::BinaryOperator::StrictInequality => Ok(BinOp::StrictNeq),
1225        ast::BinaryOperator::LessThan => Ok(BinOp::Lt),
1226        ast::BinaryOperator::LessEqualThan => Ok(BinOp::Lte),
1227        ast::BinaryOperator::GreaterThan => Ok(BinOp::Gt),
1228        ast::BinaryOperator::GreaterEqualThan => Ok(BinOp::Gte),
1229        ast::BinaryOperator::BitwiseAnd => Ok(BinOp::BitAnd),
1230        ast::BinaryOperator::BitwiseOR => Ok(BinOp::BitOr),
1231        ast::BinaryOperator::BitwiseXOR => Ok(BinOp::BitXor),
1232        ast::BinaryOperator::ShiftLeft => Ok(BinOp::Shl),
1233        ast::BinaryOperator::ShiftRight => Ok(BinOp::Shr),
1234        ast::BinaryOperator::ShiftRightZeroFill => Ok(BinOp::Ushr),
1235        ast::BinaryOperator::In => Ok(BinOp::In),
1236        ast::BinaryOperator::Instanceof => Ok(BinOp::InstanceOf),
1237    }
1238}
1239
1240fn lower_assign_op(op: ast::AssignmentOperator) -> AssignOp {
1241    match op {
1242        ast::AssignmentOperator::Assign => AssignOp::Assign,
1243        ast::AssignmentOperator::Addition => AssignOp::AddAssign,
1244        ast::AssignmentOperator::Subtraction => AssignOp::SubAssign,
1245        ast::AssignmentOperator::Multiplication => AssignOp::MulAssign,
1246        ast::AssignmentOperator::Division => AssignOp::DivAssign,
1247        ast::AssignmentOperator::Remainder => AssignOp::RemAssign,
1248        ast::AssignmentOperator::Exponential => AssignOp::PowAssign,
1249        ast::AssignmentOperator::BitwiseAnd => AssignOp::BitAndAssign,
1250        ast::AssignmentOperator::BitwiseOR => AssignOp::BitOrAssign,
1251        ast::AssignmentOperator::BitwiseXOR => AssignOp::BitXorAssign,
1252        ast::AssignmentOperator::ShiftLeft => AssignOp::ShlAssign,
1253        ast::AssignmentOperator::ShiftRight => AssignOp::ShrAssign,
1254        ast::AssignmentOperator::ShiftRightZeroFill => AssignOp::UshrAssign,
1255        ast::AssignmentOperator::LogicalNullish => AssignOp::NullishAssign,
1256        ast::AssignmentOperator::LogicalAnd => AssignOp::AndAssign,
1257        ast::AssignmentOperator::LogicalOr => AssignOp::OrAssign,
1258    }
1259}