⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/browser/
js_parser.rs

1//! JavaScript Parser
2//!
3//! Parses a token stream from the lexer into an abstract syntax tree (AST).
4//! Uses arena allocation (Vec + index) for AST nodes, Pratt parsing for
5//! expressions (precedence climbing), and recursive descent for statements.
6
7#![allow(dead_code)]
8
9use alloc::{
10    string::{String, ToString},
11    vec,
12    vec::Vec,
13};
14
15use super::js_lexer::{JsLexer, JsNumber, JsToken};
16
17// ---------------------------------------------------------------------------
18// AST node types
19// ---------------------------------------------------------------------------
20
21/// Arena index for AST nodes
22pub type AstNodeId = usize;
23
24/// Sentinel value for "no node"
25pub const AST_NONE: AstNodeId = usize::MAX;
26
27/// Binary operator
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum BinOp {
30    Add,
31    Sub,
32    Mul,
33    Div,
34    Mod,
35    Eq,
36    StrictEq,
37    NotEq,
38    StrictNotEq,
39    Lt,
40    Gt,
41    LtEq,
42    GtEq,
43    And,
44    Or,
45    BitAnd,
46    BitOr,
47    BitXor,
48    ShiftLeft,
49    ShiftRight,
50    Instanceof,
51    In,
52}
53
54/// Unary operator
55#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub enum UnaryOp {
57    Neg,
58    Not,
59    Typeof,
60    Void,
61    Delete,
62    BitNot,
63}
64
65/// AST node
66#[derive(Debug, Clone)]
67pub enum AstNode {
68    /// Program root: list of top-level statements
69    Program(Vec<AstNodeId>),
70
71    /// Variable declaration: (name, optional initializer)
72    VarDecl {
73        name: String,
74        init: Option<AstNodeId>,
75        kind: VarKind,
76    },
77
78    /// Function declaration
79    FuncDecl {
80        name: String,
81        params: Vec<String>,
82        body: AstNodeId,
83    },
84
85    /// Return statement
86    Return(Option<AstNodeId>),
87
88    /// If statement
89    If {
90        condition: AstNodeId,
91        then_branch: AstNodeId,
92        else_branch: Option<AstNodeId>,
93    },
94
95    /// While loop
96    While {
97        condition: AstNodeId,
98        body: AstNodeId,
99    },
100
101    /// For loop
102    For {
103        init: Option<AstNodeId>,
104        condition: Option<AstNodeId>,
105        update: Option<AstNodeId>,
106        body: AstNodeId,
107    },
108
109    /// Block statement { ... }
110    Block(Vec<AstNodeId>),
111
112    /// Expression statement
113    ExprStatement(AstNodeId),
114
115    /// Binary expression
116    BinaryExpr {
117        op: BinOp,
118        left: AstNodeId,
119        right: AstNodeId,
120    },
121
122    /// Unary expression
123    UnaryExpr { op: UnaryOp, operand: AstNodeId },
124
125    /// Assignment expression
126    AssignExpr { target: AstNodeId, value: AstNodeId },
127
128    /// Compound assignment (+=, -=, etc.)
129    CompoundAssign {
130        op: BinOp,
131        target: AstNodeId,
132        value: AstNodeId,
133    },
134
135    /// Function call
136    CallExpr {
137        callee: AstNodeId,
138        args: Vec<AstNodeId>,
139    },
140
141    /// Member access (obj.prop)
142    MemberExpr { object: AstNodeId, property: String },
143
144    /// Index access (obj[expr])
145    IndexExpr { object: AstNodeId, index: AstNodeId },
146
147    /// Object literal { key: value, ... }
148    ObjectLiteral(Vec<(String, AstNodeId)>),
149
150    /// Array literal [expr, ...]
151    ArrayLiteral(Vec<AstNodeId>),
152
153    /// String literal
154    StringLit(String),
155
156    /// Number literal
157    NumberLit(JsNumber),
158
159    /// Boolean literal
160    BoolLit(bool),
161
162    /// Null literal
163    NullLit,
164
165    /// Undefined
166    UndefinedLit,
167
168    /// Identifier reference
169    Identifier(String),
170
171    /// `this` keyword
172    This,
173
174    /// Function expression
175    FuncExpr {
176        params: Vec<String>,
177        body: AstNodeId,
178    },
179
180    /// Arrow function (params) => body
181    ArrowFunc {
182        params: Vec<String>,
183        body: AstNodeId,
184    },
185
186    /// new Foo(args)
187    NewExpr {
188        callee: AstNodeId,
189        args: Vec<AstNodeId>,
190    },
191
192    /// typeof expr
193    TypeofExpr(AstNodeId),
194
195    /// throw expr
196    Throw(AstNodeId),
197
198    /// try/catch/finally
199    TryCatch {
200        try_body: AstNodeId,
201        catch_param: Option<String>,
202        catch_body: Option<AstNodeId>,
203        finally_body: Option<AstNodeId>,
204    },
205
206    /// break
207    Break,
208
209    /// continue
210    Continue,
211
212    /// Empty statement
213    Empty,
214}
215
216/// Variable declaration kind
217#[derive(Debug, Clone, Copy, PartialEq, Eq)]
218pub enum VarKind {
219    Var,
220    Let,
221    Const,
222}
223
224// ---------------------------------------------------------------------------
225// AST Arena
226// ---------------------------------------------------------------------------
227
228/// Arena allocator for AST nodes
229pub struct AstArena {
230    nodes: Vec<AstNode>,
231}
232
233impl Default for AstArena {
234    fn default() -> Self {
235        Self::new()
236    }
237}
238
239impl AstArena {
240    pub fn new() -> Self {
241        Self {
242            nodes: Vec::with_capacity(256),
243        }
244    }
245
246    /// Allocate a new AST node, returning its ID
247    pub fn alloc(&mut self, node: AstNode) -> AstNodeId {
248        let id = self.nodes.len();
249        self.nodes.push(node);
250        id
251    }
252
253    /// Get a reference to a node by ID
254    pub fn get(&self, id: AstNodeId) -> Option<&AstNode> {
255        self.nodes.get(id)
256    }
257
258    /// Get a mutable reference to a node
259    pub fn get_mut(&mut self, id: AstNodeId) -> Option<&mut AstNode> {
260        self.nodes.get_mut(id)
261    }
262
263    /// Number of allocated nodes
264    pub fn len(&self) -> usize {
265        self.nodes.len()
266    }
267
268    /// Whether arena is empty
269    pub fn is_empty(&self) -> bool {
270        self.nodes.is_empty()
271    }
272}
273
274// ---------------------------------------------------------------------------
275// Parser
276// ---------------------------------------------------------------------------
277
278/// JavaScript parser
279pub struct JsParser {
280    /// Token stream
281    tokens: Vec<JsToken>,
282    /// Current position in token stream
283    pos: usize,
284    /// AST arena
285    pub arena: AstArena,
286    /// Parse errors
287    pub errors: Vec<String>,
288}
289
290impl JsParser {
291    /// Create a parser from source code
292    pub fn from_source(source: &str) -> Self {
293        let tokens = JsLexer::new(source).tokenize_all();
294        Self {
295            tokens,
296            pos: 0,
297            arena: AstArena::new(),
298            errors: Vec::new(),
299        }
300    }
301
302    /// Create a parser from pre-lexed tokens
303    pub fn from_tokens(tokens: Vec<JsToken>) -> Self {
304        Self {
305            tokens,
306            pos: 0,
307            arena: AstArena::new(),
308            errors: Vec::new(),
309        }
310    }
311
312    /// Parse the entire program
313    pub fn parse(&mut self) -> AstNodeId {
314        let mut stmts = Vec::new();
315        while !self.at_end() {
316            if self.check(&JsToken::Semicolon) {
317                self.advance_pos();
318                continue;
319            }
320            let stmt = self.parse_statement();
321            stmts.push(stmt);
322        }
323        self.arena.alloc(AstNode::Program(stmts))
324    }
325
326    // -- Token helpers --
327
328    fn current(&self) -> &JsToken {
329        self.tokens.get(self.pos).unwrap_or(&JsToken::Eof)
330    }
331
332    fn peek_token(&self) -> &JsToken {
333        self.tokens.get(self.pos + 1).unwrap_or(&JsToken::Eof)
334    }
335
336    fn at_end(&self) -> bool {
337        matches!(self.current(), JsToken::Eof)
338    }
339
340    fn check(&self, expected: &JsToken) -> bool {
341        core::mem::discriminant(self.current()) == core::mem::discriminant(expected)
342    }
343
344    fn advance_pos(&mut self) -> JsToken {
345        let tok = self.current().clone();
346        if self.pos < self.tokens.len() {
347            self.pos += 1;
348        }
349        tok
350    }
351
352    fn expect(&mut self, expected: &JsToken) -> bool {
353        if self.check(expected) {
354            self.advance_pos();
355            true
356        } else {
357            self.errors.push(alloc::format!(
358                "Expected {:?}, got {:?}",
359                expected,
360                self.current()
361            ));
362            false
363        }
364    }
365
366    fn eat_semicolon(&mut self) {
367        if self.check(&JsToken::Semicolon) {
368            self.advance_pos();
369        }
370    }
371
372    // -- Statement parsing (recursive descent) --
373
374    fn parse_statement(&mut self) -> AstNodeId {
375        match self.current() {
376            JsToken::Let => self.parse_var_decl(VarKind::Let),
377            JsToken::Const => self.parse_var_decl(VarKind::Const),
378            JsToken::Var => self.parse_var_decl(VarKind::Var),
379            JsToken::Function => self.parse_function_decl(),
380            JsToken::Return => self.parse_return(),
381            JsToken::If => self.parse_if(),
382            JsToken::While => self.parse_while(),
383            JsToken::For => self.parse_for(),
384            JsToken::OpenBrace => self.parse_block(),
385            JsToken::Throw => self.parse_throw(),
386            JsToken::Try => self.parse_try(),
387            JsToken::Break => {
388                self.advance_pos();
389                self.eat_semicolon();
390                self.arena.alloc(AstNode::Break)
391            }
392            JsToken::Continue => {
393                self.advance_pos();
394                self.eat_semicolon();
395                self.arena.alloc(AstNode::Continue)
396            }
397            _ => self.parse_expression_statement(),
398        }
399    }
400
401    fn parse_var_decl(&mut self, kind: VarKind) -> AstNodeId {
402        self.advance_pos(); // let/const/var
403        let name = match self.advance_pos() {
404            JsToken::Identifier(n) => n,
405            _ => {
406                self.errors
407                    .push("Expected identifier in variable declaration".to_string());
408                String::from("_error")
409            }
410        };
411        let init = if self.check(&JsToken::Assign) {
412            self.advance_pos();
413            Some(self.parse_expression(0))
414        } else {
415            None
416        };
417        self.eat_semicolon();
418        self.arena.alloc(AstNode::VarDecl { name, init, kind })
419    }
420
421    fn parse_function_decl(&mut self) -> AstNodeId {
422        self.advance_pos(); // function
423        let name = match self.advance_pos() {
424            JsToken::Identifier(n) => n,
425            _ => {
426                self.errors.push("Expected function name".to_string());
427                String::from("_error")
428            }
429        };
430        let params = self.parse_param_list();
431        let body = self.parse_block();
432        self.arena.alloc(AstNode::FuncDecl { name, params, body })
433    }
434
435    fn parse_param_list(&mut self) -> Vec<String> {
436        let mut params = Vec::new();
437        self.expect(&JsToken::OpenParen);
438        while !self.check(&JsToken::CloseParen) && !self.at_end() {
439            if let JsToken::Identifier(name) = self.advance_pos() {
440                params.push(name);
441            }
442            if self.check(&JsToken::Comma) {
443                self.advance_pos();
444            }
445        }
446        self.expect(&JsToken::CloseParen);
447        params
448    }
449
450    fn parse_return(&mut self) -> AstNodeId {
451        self.advance_pos(); // return
452        let value =
453            if self.check(&JsToken::Semicolon) || self.check(&JsToken::CloseBrace) || self.at_end()
454            {
455                None
456            } else {
457                Some(self.parse_expression(0))
458            };
459        self.eat_semicolon();
460        self.arena.alloc(AstNode::Return(value))
461    }
462
463    fn parse_if(&mut self) -> AstNodeId {
464        self.advance_pos(); // if
465        self.expect(&JsToken::OpenParen);
466        let condition = self.parse_expression(0);
467        self.expect(&JsToken::CloseParen);
468        let then_branch = self.parse_statement();
469        let else_branch = if self.check(&JsToken::Else) {
470            self.advance_pos();
471            Some(self.parse_statement())
472        } else {
473            None
474        };
475        self.arena.alloc(AstNode::If {
476            condition,
477            then_branch,
478            else_branch,
479        })
480    }
481
482    fn parse_while(&mut self) -> AstNodeId {
483        self.advance_pos(); // while
484        self.expect(&JsToken::OpenParen);
485        let condition = self.parse_expression(0);
486        self.expect(&JsToken::CloseParen);
487        let body = self.parse_statement();
488        self.arena.alloc(AstNode::While { condition, body })
489    }
490
491    fn parse_for(&mut self) -> AstNodeId {
492        self.advance_pos(); // for
493        self.expect(&JsToken::OpenParen);
494        let init = if self.check(&JsToken::Semicolon) {
495            None
496        } else if self.check(&JsToken::Let)
497            || self.check(&JsToken::Var)
498            || self.check(&JsToken::Const)
499        {
500            let kind = match self.current() {
501                JsToken::Let => VarKind::Let,
502                JsToken::Const => VarKind::Const,
503                _ => VarKind::Var,
504            };
505            Some(self.parse_var_decl(kind))
506        } else {
507            let expr = self.parse_expression(0);
508            Some(self.arena.alloc(AstNode::ExprStatement(expr)))
509        };
510        // The var_decl parser may have eaten the semicolon
511        if self.check(&JsToken::Semicolon) {
512            self.advance_pos();
513        }
514        let condition = if self.check(&JsToken::Semicolon) {
515            None
516        } else {
517            Some(self.parse_expression(0))
518        };
519        self.expect(&JsToken::Semicolon);
520        let update = if self.check(&JsToken::CloseParen) {
521            None
522        } else {
523            Some(self.parse_assignment_expression())
524        };
525        self.expect(&JsToken::CloseParen);
526        let body = self.parse_statement();
527        self.arena.alloc(AstNode::For {
528            init,
529            condition,
530            update,
531            body,
532        })
533    }
534
535    fn parse_block(&mut self) -> AstNodeId {
536        self.expect(&JsToken::OpenBrace);
537        let mut stmts = Vec::new();
538        while !self.check(&JsToken::CloseBrace) && !self.at_end() {
539            if self.check(&JsToken::Semicolon) {
540                self.advance_pos();
541                continue;
542            }
543            stmts.push(self.parse_statement());
544        }
545        self.expect(&JsToken::CloseBrace);
546        self.arena.alloc(AstNode::Block(stmts))
547    }
548
549    fn parse_throw(&mut self) -> AstNodeId {
550        self.advance_pos(); // throw
551        let expr = self.parse_expression(0);
552        self.eat_semicolon();
553        self.arena.alloc(AstNode::Throw(expr))
554    }
555
556    fn parse_try(&mut self) -> AstNodeId {
557        self.advance_pos(); // try
558        let try_body = self.parse_block();
559
560        let (catch_param, catch_body) = if self.check(&JsToken::Catch) {
561            self.advance_pos();
562            let param = if self.check(&JsToken::OpenParen) {
563                self.advance_pos();
564                let name = match self.advance_pos() {
565                    JsToken::Identifier(n) => Some(n),
566                    _ => None,
567                };
568                self.expect(&JsToken::CloseParen);
569                name
570            } else {
571                None
572            };
573            let body = self.parse_block();
574            (param, Some(body))
575        } else {
576            (None, None)
577        };
578
579        let finally_body = if self.check(&JsToken::Finally) {
580            self.advance_pos();
581            Some(self.parse_block())
582        } else {
583            None
584        };
585
586        self.arena.alloc(AstNode::TryCatch {
587            try_body,
588            catch_param,
589            catch_body,
590            finally_body,
591        })
592    }
593
594    /// Parse an expression that may include assignment (used in for-loop
595    /// update, etc.)
596    fn parse_assignment_expression(&mut self) -> AstNodeId {
597        let expr = self.parse_expression(0);
598        let node = match self.current() {
599            JsToken::Assign => {
600                self.advance_pos();
601                let value = self.parse_expression(0);
602                AstNode::AssignExpr {
603                    target: expr,
604                    value,
605                }
606            }
607            JsToken::PlusAssign => {
608                self.advance_pos();
609                let value = self.parse_expression(0);
610                AstNode::CompoundAssign {
611                    op: BinOp::Add,
612                    target: expr,
613                    value,
614                }
615            }
616            JsToken::MinusAssign => {
617                self.advance_pos();
618                let value = self.parse_expression(0);
619                AstNode::CompoundAssign {
620                    op: BinOp::Sub,
621                    target: expr,
622                    value,
623                }
624            }
625            JsToken::StarAssign => {
626                self.advance_pos();
627                let value = self.parse_expression(0);
628                AstNode::CompoundAssign {
629                    op: BinOp::Mul,
630                    target: expr,
631                    value,
632                }
633            }
634            JsToken::SlashAssign => {
635                self.advance_pos();
636                let value = self.parse_expression(0);
637                AstNode::CompoundAssign {
638                    op: BinOp::Div,
639                    target: expr,
640                    value,
641                }
642            }
643            _ => return expr,
644        };
645        self.arena.alloc(node)
646    }
647
648    fn parse_expression_statement(&mut self) -> AstNodeId {
649        let expr = self.parse_expression(0);
650
651        // Check for assignment
652        let node = match self.current() {
653            JsToken::Assign => {
654                self.advance_pos();
655                let value = self.parse_expression(0);
656                AstNode::AssignExpr {
657                    target: expr,
658                    value,
659                }
660            }
661            JsToken::PlusAssign => {
662                self.advance_pos();
663                let value = self.parse_expression(0);
664                AstNode::CompoundAssign {
665                    op: BinOp::Add,
666                    target: expr,
667                    value,
668                }
669            }
670            JsToken::MinusAssign => {
671                self.advance_pos();
672                let value = self.parse_expression(0);
673                AstNode::CompoundAssign {
674                    op: BinOp::Sub,
675                    target: expr,
676                    value,
677                }
678            }
679            JsToken::StarAssign => {
680                self.advance_pos();
681                let value = self.parse_expression(0);
682                AstNode::CompoundAssign {
683                    op: BinOp::Mul,
684                    target: expr,
685                    value,
686                }
687            }
688            JsToken::SlashAssign => {
689                self.advance_pos();
690                let value = self.parse_expression(0);
691                AstNode::CompoundAssign {
692                    op: BinOp::Div,
693                    target: expr,
694                    value,
695                }
696            }
697            _ => AstNode::ExprStatement(expr),
698        };
699
700        let id = self.arena.alloc(node);
701        self.eat_semicolon();
702        id
703    }
704
705    // -- Expression parsing (Pratt / precedence climbing) --
706
707    fn parse_expression(&mut self, min_prec: u8) -> AstNodeId {
708        let mut left = self.parse_unary();
709
710        while let Some((op, prec)) = self.current_binop() {
711            if prec < min_prec {
712                break;
713            }
714            self.advance_pos();
715            let right = self.parse_expression(prec + 1);
716            left = self.arena.alloc(AstNode::BinaryExpr { op, left, right });
717        }
718
719        left
720    }
721
722    fn current_binop(&self) -> Option<(BinOp, u8)> {
723        match self.current() {
724            JsToken::Or => Some((BinOp::Or, 3)),
725            JsToken::And => Some((BinOp::And, 4)),
726            JsToken::BitOr => Some((BinOp::BitOr, 5)),
727            JsToken::BitXor => Some((BinOp::BitXor, 6)),
728            JsToken::BitAnd => Some((BinOp::BitAnd, 7)),
729            JsToken::EqEq => Some((BinOp::Eq, 8)),
730            JsToken::EqEqEq => Some((BinOp::StrictEq, 8)),
731            JsToken::NotEq => Some((BinOp::NotEq, 8)),
732            JsToken::NotEqEq => Some((BinOp::StrictNotEq, 8)),
733            JsToken::Lt => Some((BinOp::Lt, 9)),
734            JsToken::Gt => Some((BinOp::Gt, 9)),
735            JsToken::LtEq => Some((BinOp::LtEq, 9)),
736            JsToken::GtEq => Some((BinOp::GtEq, 9)),
737            JsToken::Instanceof => Some((BinOp::Instanceof, 9)),
738            JsToken::In => Some((BinOp::In, 9)),
739            JsToken::ShiftLeft => Some((BinOp::ShiftLeft, 10)),
740            JsToken::ShiftRight => Some((BinOp::ShiftRight, 10)),
741            JsToken::Plus => Some((BinOp::Add, 11)),
742            JsToken::Minus => Some((BinOp::Sub, 11)),
743            JsToken::Star => Some((BinOp::Mul, 12)),
744            JsToken::Slash => Some((BinOp::Div, 12)),
745            JsToken::Percent => Some((BinOp::Mod, 12)),
746            _ => None,
747        }
748    }
749
750    fn parse_unary(&mut self) -> AstNodeId {
751        match self.current() {
752            JsToken::Minus => {
753                self.advance_pos();
754                let operand = self.parse_unary();
755                self.arena.alloc(AstNode::UnaryExpr {
756                    op: UnaryOp::Neg,
757                    operand,
758                })
759            }
760            JsToken::Not => {
761                self.advance_pos();
762                let operand = self.parse_unary();
763                self.arena.alloc(AstNode::UnaryExpr {
764                    op: UnaryOp::Not,
765                    operand,
766                })
767            }
768            JsToken::Typeof => {
769                self.advance_pos();
770                let operand = self.parse_unary();
771                self.arena.alloc(AstNode::TypeofExpr(operand))
772            }
773            JsToken::Void => {
774                self.advance_pos();
775                let operand = self.parse_unary();
776                self.arena.alloc(AstNode::UnaryExpr {
777                    op: UnaryOp::Void,
778                    operand,
779                })
780            }
781            JsToken::Delete => {
782                self.advance_pos();
783                let operand = self.parse_unary();
784                self.arena.alloc(AstNode::UnaryExpr {
785                    op: UnaryOp::Delete,
786                    operand,
787                })
788            }
789            JsToken::New => self.parse_new_expr(),
790            _ => self.parse_call_member(),
791        }
792    }
793
794    fn parse_new_expr(&mut self) -> AstNodeId {
795        self.advance_pos(); // new
796        let callee = self.parse_primary();
797        let args = if self.check(&JsToken::OpenParen) {
798            self.parse_arguments()
799        } else {
800            Vec::new()
801        };
802        self.arena.alloc(AstNode::NewExpr { callee, args })
803    }
804
805    fn parse_call_member(&mut self) -> AstNodeId {
806        let mut node = self.parse_primary();
807
808        loop {
809            match self.current() {
810                JsToken::Dot => {
811                    self.advance_pos();
812                    let prop = match self.advance_pos() {
813                        JsToken::Identifier(n) => n,
814                        _ => {
815                            self.errors
816                                .push("Expected property name after '.'".to_string());
817                            String::from("_error")
818                        }
819                    };
820                    node = self.arena.alloc(AstNode::MemberExpr {
821                        object: node,
822                        property: prop,
823                    });
824                }
825                JsToken::OpenBracket => {
826                    self.advance_pos();
827                    let index = self.parse_expression(0);
828                    self.expect(&JsToken::CloseBracket);
829                    node = self.arena.alloc(AstNode::IndexExpr {
830                        object: node,
831                        index,
832                    });
833                }
834                JsToken::OpenParen => {
835                    let args = self.parse_arguments();
836                    node = self.arena.alloc(AstNode::CallExpr { callee: node, args });
837                }
838                _ => break,
839            }
840        }
841
842        node
843    }
844
845    fn parse_arguments(&mut self) -> Vec<AstNodeId> {
846        let mut args = Vec::new();
847        self.expect(&JsToken::OpenParen);
848        while !self.check(&JsToken::CloseParen) && !self.at_end() {
849            args.push(self.parse_expression(0));
850            if self.check(&JsToken::Comma) {
851                self.advance_pos();
852            }
853        }
854        self.expect(&JsToken::CloseParen);
855        args
856    }
857
858    fn parse_primary(&mut self) -> AstNodeId {
859        let tok = self.current().clone();
860        match tok {
861            JsToken::Number(n) => {
862                self.advance_pos();
863                self.arena.alloc(AstNode::NumberLit(n))
864            }
865            JsToken::StringLiteral(s) => {
866                self.advance_pos();
867                self.arena.alloc(AstNode::StringLit(s))
868            }
869            JsToken::True => {
870                self.advance_pos();
871                self.arena.alloc(AstNode::BoolLit(true))
872            }
873            JsToken::False => {
874                self.advance_pos();
875                self.arena.alloc(AstNode::BoolLit(false))
876            }
877            JsToken::Null => {
878                self.advance_pos();
879                self.arena.alloc(AstNode::NullLit)
880            }
881            JsToken::Undefined => {
882                self.advance_pos();
883                self.arena.alloc(AstNode::UndefinedLit)
884            }
885            JsToken::This => {
886                self.advance_pos();
887                self.arena.alloc(AstNode::This)
888            }
889            JsToken::Identifier(ref name) => {
890                let name = name.clone();
891                self.advance_pos();
892
893                // Check for arrow function: (ident) => ...
894                // Simple case: single-param arrow x => body
895                if self.check(&JsToken::Arrow) {
896                    self.advance_pos();
897                    let body = if self.check(&JsToken::OpenBrace) {
898                        self.parse_block()
899                    } else {
900                        let expr = self.parse_expression(0);
901                        self.arena.alloc(AstNode::Return(Some(expr)))
902                    };
903                    return self.arena.alloc(AstNode::ArrowFunc {
904                        params: vec![name],
905                        body,
906                    });
907                }
908
909                self.arena.alloc(AstNode::Identifier(name))
910            }
911            JsToken::OpenParen => {
912                self.advance_pos();
913
914                // Check for arrow function with params
915                // Heuristic: if we see identifiers separated by commas,
916                // followed by ) =>, it's an arrow function
917                if self.is_arrow_params() {
918                    let params = self.parse_arrow_params();
919                    self.expect(&JsToken::Arrow);
920                    let body = if self.check(&JsToken::OpenBrace) {
921                        self.parse_block()
922                    } else {
923                        let expr = self.parse_expression(0);
924                        self.arena.alloc(AstNode::Return(Some(expr)))
925                    };
926                    return self.arena.alloc(AstNode::ArrowFunc { params, body });
927                }
928
929                let expr = self.parse_expression(0);
930                self.expect(&JsToken::CloseParen);
931                expr
932            }
933            JsToken::OpenBrace => self.parse_object_literal(),
934            JsToken::OpenBracket => self.parse_array_literal(),
935            JsToken::Function => {
936                self.advance_pos();
937                // Optional name for function expression
938                if let JsToken::Identifier(_) = self.current() {
939                    self.advance_pos();
940                };
941                let params = self.parse_param_list();
942                let body = self.parse_block();
943                self.arena.alloc(AstNode::FuncExpr { params, body })
944            }
945            _ => {
946                self.errors
947                    .push(alloc::format!("Unexpected token in expression: {:?}", tok));
948                self.advance_pos();
949                self.arena.alloc(AstNode::Empty)
950            }
951        }
952    }
953
954    fn parse_object_literal(&mut self) -> AstNodeId {
955        self.advance_pos(); // {
956        let mut props = Vec::new();
957        while !self.check(&JsToken::CloseBrace) && !self.at_end() {
958            let key = match self.advance_pos() {
959                JsToken::Identifier(k) => k,
960                JsToken::StringLiteral(k) => k,
961                _ => {
962                    self.errors.push("Expected property key".to_string());
963                    String::from("_error")
964                }
965            };
966            self.expect(&JsToken::Colon);
967            let value = self.parse_expression(0);
968            props.push((key, value));
969            if self.check(&JsToken::Comma) {
970                self.advance_pos();
971            }
972        }
973        self.expect(&JsToken::CloseBrace);
974        self.arena.alloc(AstNode::ObjectLiteral(props))
975    }
976
977    fn parse_array_literal(&mut self) -> AstNodeId {
978        self.advance_pos(); // [
979        let mut elements = Vec::new();
980        while !self.check(&JsToken::CloseBracket) && !self.at_end() {
981            elements.push(self.parse_expression(0));
982            if self.check(&JsToken::Comma) {
983                self.advance_pos();
984            }
985        }
986        self.expect(&JsToken::CloseBracket);
987        self.arena.alloc(AstNode::ArrayLiteral(elements))
988    }
989
990    /// Heuristic: check if current position starts arrow function params
991    fn is_arrow_params(&self) -> bool {
992        // Look ahead for ) =>
993        let mut depth = 0;
994        let mut i = self.pos;
995        while i < self.tokens.len() {
996            match &self.tokens[i] {
997                JsToken::CloseParen if depth == 0 => {
998                    // Check for => after )
999                    return matches!(self.tokens.get(i + 1), Some(JsToken::Arrow));
1000                }
1001                JsToken::OpenParen => depth += 1,
1002                JsToken::CloseParen => depth -= 1,
1003                JsToken::Eof => return false,
1004                _ => {}
1005            }
1006            i += 1;
1007        }
1008        false
1009    }
1010
1011    /// Parse arrow function parameters (already past opening paren)
1012    fn parse_arrow_params(&mut self) -> Vec<String> {
1013        let mut params = Vec::new();
1014        while !self.check(&JsToken::CloseParen) && !self.at_end() {
1015            if let JsToken::Identifier(name) = self.advance_pos() {
1016                params.push(name);
1017            }
1018            if self.check(&JsToken::Comma) {
1019                self.advance_pos();
1020            }
1021        }
1022        self.expect(&JsToken::CloseParen);
1023        params
1024    }
1025}
1026
1027// ---------------------------------------------------------------------------
1028// Tests
1029// ---------------------------------------------------------------------------
1030
1031#[cfg(test)]
1032mod tests {
1033    use super::*;
1034
1035    fn parse(src: &str) -> JsParser {
1036        let mut parser = JsParser::from_source(src);
1037        parser.parse();
1038        parser
1039    }
1040
1041    #[test]
1042    fn test_empty_program() {
1043        let p = parse("");
1044        assert_eq!(p.arena.len(), 1);
1045        matches!(p.arena.get(0), Some(AstNode::Program(stmts)) if stmts.is_empty());
1046    }
1047
1048    #[test]
1049    fn test_number_literal() {
1050        let p = parse("42;");
1051        assert!(p.errors.is_empty());
1052        assert!(p.arena.len() >= 2); // Program + NumberLit + ExprStmt
1053    }
1054
1055    #[test]
1056    fn test_string_literal() {
1057        let p = parse("\"hello\";");
1058        assert!(p.errors.is_empty());
1059    }
1060
1061    #[test]
1062    fn test_var_decl_let() {
1063        let p = parse("let x = 10;");
1064        assert!(p.errors.is_empty());
1065        // Find VarDecl node
1066        let has_var = (0..p.arena.len()).any(|i| matches!(p.arena.get(i), Some(AstNode::VarDecl { name, kind: VarKind::Let, .. }) if name == "x"));
1067        assert!(has_var);
1068    }
1069
1070    #[test]
1071    fn test_var_decl_const() {
1072        let p = parse("const y = 'hello';");
1073        assert!(p.errors.is_empty());
1074        let has_const = (0..p.arena.len()).any(|i| {
1075            matches!(
1076                p.arena.get(i),
1077                Some(AstNode::VarDecl {
1078                    kind: VarKind::Const,
1079                    ..
1080                })
1081            )
1082        });
1083        assert!(has_const);
1084    }
1085
1086    #[test]
1087    fn test_function_decl() {
1088        let p = parse("function add(a, b) { return a + b; }");
1089        assert!(p.errors.is_empty());
1090        let has_func = (0..p.arena.len()).any(|i| matches!(p.arena.get(i), Some(AstNode::FuncDecl { name, params, .. }) if name == "add" && params.len() == 2));
1091        assert!(has_func);
1092    }
1093
1094    #[test]
1095    fn test_if_else() {
1096        let p = parse("if (x > 0) { y = 1; } else { y = 2; }");
1097        assert!(p.errors.is_empty());
1098        let has_if = (0..p.arena.len()).any(|i| {
1099            matches!(
1100                p.arena.get(i),
1101                Some(AstNode::If {
1102                    else_branch: Some(_),
1103                    ..
1104                })
1105            )
1106        });
1107        assert!(has_if);
1108    }
1109
1110    #[test]
1111    fn test_while_loop() {
1112        let p = parse("while (i < 10) { i = i + 1; }");
1113        assert!(p.errors.is_empty());
1114        let has_while =
1115            (0..p.arena.len()).any(|i| matches!(p.arena.get(i), Some(AstNode::While { .. })));
1116        assert!(has_while);
1117    }
1118
1119    #[test]
1120    fn test_for_loop() {
1121        let p = parse("for (let i = 0; i < 10; i = i + 1) { x = i; }");
1122        assert!(p.errors.is_empty());
1123        let has_for =
1124            (0..p.arena.len()).any(|i| matches!(p.arena.get(i), Some(AstNode::For { .. })));
1125        assert!(has_for);
1126    }
1127
1128    #[test]
1129    fn test_binary_expr() {
1130        let p = parse("1 + 2 * 3;");
1131        assert!(p.errors.is_empty());
1132        // Should have BinaryExpr nodes
1133        let bin_count = (0..p.arena.len())
1134            .filter(|&i| matches!(p.arena.get(i), Some(AstNode::BinaryExpr { .. })))
1135            .count();
1136        assert_eq!(bin_count, 2);
1137    }
1138
1139    #[test]
1140    fn test_precedence() {
1141        let p = parse("1 + 2 * 3;");
1142        // The Mul should be nested inside the Add
1143        // Find the Add node
1144        for i in 0..p.arena.len() {
1145            if let Some(AstNode::BinaryExpr {
1146                op: BinOp::Add,
1147                right,
1148                ..
1149            }) = p.arena.get(i)
1150            {
1151                // right should be a Mul
1152                assert!(matches!(
1153                    p.arena.get(*right),
1154                    Some(AstNode::BinaryExpr { op: BinOp::Mul, .. })
1155                ));
1156            }
1157        }
1158    }
1159
1160    #[test]
1161    fn test_unary_neg() {
1162        let p = parse("-x;");
1163        assert!(p.errors.is_empty());
1164        let has_unary = (0..p.arena.len()).any(|i| {
1165            matches!(
1166                p.arena.get(i),
1167                Some(AstNode::UnaryExpr {
1168                    op: UnaryOp::Neg,
1169                    ..
1170                })
1171            )
1172        });
1173        assert!(has_unary);
1174    }
1175
1176    #[test]
1177    fn test_call_expr() {
1178        let p = parse("foo(1, 2, 3);");
1179        assert!(p.errors.is_empty());
1180        let has_call = (0..p.arena.len()).any(
1181            |i| matches!(p.arena.get(i), Some(AstNode::CallExpr { args, .. }) if args.len() == 3),
1182        );
1183        assert!(has_call);
1184    }
1185
1186    #[test]
1187    fn test_member_expr() {
1188        let p = parse("obj.prop;");
1189        assert!(p.errors.is_empty());
1190        let has_member = (0..p.arena.len()).any(|i| matches!(p.arena.get(i), Some(AstNode::MemberExpr { property, .. }) if property == "prop"));
1191        assert!(has_member);
1192    }
1193
1194    #[test]
1195    fn test_index_expr() {
1196        let p = parse("arr[0];");
1197        assert!(p.errors.is_empty());
1198        let has_index =
1199            (0..p.arena.len()).any(|i| matches!(p.arena.get(i), Some(AstNode::IndexExpr { .. })));
1200        assert!(has_index);
1201    }
1202
1203    #[test]
1204    fn test_object_literal() {
1205        let p = parse("let o = {a: 1, b: 2};");
1206        assert!(p.errors.is_empty());
1207        let has_obj = (0..p.arena.len()).any(
1208            |i| matches!(p.arena.get(i), Some(AstNode::ObjectLiteral(props)) if props.len() == 2),
1209        );
1210        assert!(has_obj);
1211    }
1212
1213    #[test]
1214    fn test_array_literal() {
1215        let p = parse("let a = [1, 2, 3];");
1216        assert!(p.errors.is_empty());
1217        let has_arr = (0..p.arena.len()).any(
1218            |i| matches!(p.arena.get(i), Some(AstNode::ArrayLiteral(elems)) if elems.len() == 3),
1219        );
1220        assert!(has_arr);
1221    }
1222
1223    #[test]
1224    fn test_assignment() {
1225        let p = parse("x = 5;");
1226        assert!(p.errors.is_empty());
1227        let has_assign =
1228            (0..p.arena.len()).any(|i| matches!(p.arena.get(i), Some(AstNode::AssignExpr { .. })));
1229        assert!(has_assign);
1230    }
1231
1232    #[test]
1233    fn test_compound_assign() {
1234        let p = parse("x += 1;");
1235        assert!(p.errors.is_empty());
1236        let has_compound = (0..p.arena.len()).any(|i| {
1237            matches!(
1238                p.arena.get(i),
1239                Some(AstNode::CompoundAssign { op: BinOp::Add, .. })
1240            )
1241        });
1242        assert!(has_compound);
1243    }
1244
1245    #[test]
1246    fn test_try_catch() {
1247        let p = parse("try { x(); } catch (e) { log(e); }");
1248        assert!(p.errors.is_empty());
1249        let has_try = (0..p.arena.len()).any(|i| matches!(p.arena.get(i), Some(AstNode::TryCatch { catch_param: Some(ref name), .. }) if name == "e"));
1250        assert!(has_try);
1251    }
1252
1253    #[test]
1254    fn test_try_finally() {
1255        let p = parse("try { x(); } finally { cleanup(); }");
1256        assert!(p.errors.is_empty());
1257        let has_finally = (0..p.arena.len()).any(|i| {
1258            matches!(
1259                p.arena.get(i),
1260                Some(AstNode::TryCatch {
1261                    finally_body: Some(_),
1262                    ..
1263                })
1264            )
1265        });
1266        assert!(has_finally);
1267    }
1268
1269    #[test]
1270    fn test_throw() {
1271        let p = parse("throw new Error();");
1272        assert!(p.errors.is_empty());
1273        let has_throw =
1274            (0..p.arena.len()).any(|i| matches!(p.arena.get(i), Some(AstNode::Throw(_))));
1275        assert!(has_throw);
1276    }
1277
1278    #[test]
1279    fn test_new_expr() {
1280        let p = parse("new Foo(1);");
1281        assert!(p.errors.is_empty());
1282        let has_new =
1283            (0..p.arena.len()).any(|i| matches!(p.arena.get(i), Some(AstNode::NewExpr { .. })));
1284        assert!(has_new);
1285    }
1286
1287    #[test]
1288    fn test_arrow_function() {
1289        let p = parse("let f = x => x + 1;");
1290        assert!(p.errors.is_empty());
1291        let has_arrow = (0..p.arena.len()).any(|i| matches!(p.arena.get(i), Some(AstNode::ArrowFunc { params, .. }) if params.len() == 1));
1292        assert!(has_arrow);
1293    }
1294
1295    #[test]
1296    fn test_function_expr() {
1297        let p = parse("let f = function(a) { return a; };");
1298        assert!(p.errors.is_empty());
1299        let has_func_expr =
1300            (0..p.arena.len()).any(|i| matches!(p.arena.get(i), Some(AstNode::FuncExpr { .. })));
1301        assert!(has_func_expr);
1302    }
1303
1304    #[test]
1305    fn test_break_continue() {
1306        let p = parse("while (true) { break; continue; }");
1307        assert!(p.errors.is_empty());
1308        let has_break = (0..p.arena.len()).any(|i| matches!(p.arena.get(i), Some(AstNode::Break)));
1309        let has_continue =
1310            (0..p.arena.len()).any(|i| matches!(p.arena.get(i), Some(AstNode::Continue)));
1311        assert!(has_break);
1312        assert!(has_continue);
1313    }
1314
1315    #[test]
1316    fn test_chained_member() {
1317        let p = parse("a.b.c;");
1318        assert!(p.errors.is_empty());
1319        let member_count = (0..p.arena.len())
1320            .filter(|&i| matches!(p.arena.get(i), Some(AstNode::MemberExpr { .. })))
1321            .count();
1322        assert_eq!(member_count, 2);
1323    }
1324
1325    #[test]
1326    fn test_nested_calls() {
1327        let p = parse("a(b(c()));");
1328        assert!(p.errors.is_empty());
1329        let call_count = (0..p.arena.len())
1330            .filter(|&i| matches!(p.arena.get(i), Some(AstNode::CallExpr { .. })))
1331            .count();
1332        assert_eq!(call_count, 3);
1333    }
1334
1335    #[test]
1336    fn test_typeof() {
1337        let p = parse("typeof x;");
1338        assert!(p.errors.is_empty());
1339        let has_typeof =
1340            (0..p.arena.len()).any(|i| matches!(p.arena.get(i), Some(AstNode::TypeofExpr(_))));
1341        assert!(has_typeof);
1342    }
1343
1344    #[test]
1345    fn test_this() {
1346        let p = parse("this.x;");
1347        assert!(p.errors.is_empty());
1348        let has_this = (0..p.arena.len()).any(|i| matches!(p.arena.get(i), Some(AstNode::This)));
1349        assert!(has_this);
1350    }
1351
1352    #[test]
1353    fn test_boolean_literals() {
1354        let p = parse("true; false;");
1355        assert!(p.errors.is_empty());
1356        let has_true =
1357            (0..p.arena.len()).any(|i| matches!(p.arena.get(i), Some(AstNode::BoolLit(true))));
1358        let has_false =
1359            (0..p.arena.len()).any(|i| matches!(p.arena.get(i), Some(AstNode::BoolLit(false))));
1360        assert!(has_true);
1361        assert!(has_false);
1362    }
1363
1364    #[test]
1365    fn test_null_undefined() {
1366        let p = parse("null; undefined;");
1367        assert!(p.errors.is_empty());
1368    }
1369
1370    #[test]
1371    fn test_logical_and_or() {
1372        let p = parse("a && b || c;");
1373        assert!(p.errors.is_empty());
1374    }
1375
1376    #[test]
1377    fn test_comparison_ops() {
1378        let p = parse("a < b; c >= d; e === f; g !== h;");
1379        assert!(p.errors.is_empty());
1380    }
1381
1382    #[test]
1383    fn test_return_no_value() {
1384        let p = parse("function f() { return; }");
1385        assert!(p.errors.is_empty());
1386        let has_return =
1387            (0..p.arena.len()).any(|i| matches!(p.arena.get(i), Some(AstNode::Return(None))));
1388        assert!(has_return);
1389    }
1390
1391    #[test]
1392    fn test_if_no_else() {
1393        let p = parse("if (x) { y; }");
1394        assert!(p.errors.is_empty());
1395        let has_if = (0..p.arena.len()).any(|i| {
1396            matches!(
1397                p.arena.get(i),
1398                Some(AstNode::If {
1399                    else_branch: None,
1400                    ..
1401                })
1402            )
1403        });
1404        assert!(has_if);
1405    }
1406
1407    #[test]
1408    fn test_empty_block() {
1409        let p = parse("{}");
1410        assert!(p.errors.is_empty());
1411        let has_block = (0..p.arena.len())
1412            .any(|i| matches!(p.arena.get(i), Some(AstNode::Block(stmts)) if stmts.is_empty()));
1413        assert!(has_block);
1414    }
1415
1416    #[test]
1417    fn test_method_call() {
1418        let p = parse("console.log('hello');");
1419        assert!(p.errors.is_empty());
1420    }
1421
1422    #[test]
1423    fn test_ast_arena_basic() {
1424        let mut arena = AstArena::new();
1425        let id = arena.alloc(AstNode::NullLit);
1426        assert_eq!(id, 0);
1427        assert_eq!(arena.len(), 1);
1428        assert!(!arena.is_empty());
1429    }
1430
1431    #[test]
1432    fn test_not_operator() {
1433        let p = parse("!x;");
1434        assert!(p.errors.is_empty());
1435        let has_not = (0..p.arena.len()).any(|i| {
1436            matches!(
1437                p.arena.get(i),
1438                Some(AstNode::UnaryExpr {
1439                    op: UnaryOp::Not,
1440                    ..
1441                })
1442            )
1443        });
1444        assert!(has_not);
1445    }
1446
1447    #[test]
1448    fn test_multiple_statements() {
1449        let p = parse("let a = 1; let b = 2; let c = a + b;");
1450        assert!(p.errors.is_empty());
1451        let var_count = (0..p.arena.len())
1452            .filter(|&i| matches!(p.arena.get(i), Some(AstNode::VarDecl { .. })))
1453            .count();
1454        assert_eq!(var_count, 3);
1455    }
1456}