runmat_parser/
lib.rs

1use runmat_lexer::Token;
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
5pub enum Expr {
6    Number(String),
7    String(String),
8    Ident(String),
9    EndKeyword, // 'end' used in indexing contexts
10    Unary(UnOp, Box<Expr>),
11    Binary(Box<Expr>, BinOp, Box<Expr>),
12    Tensor(Vec<Vec<Expr>>),
13    Cell(Vec<Vec<Expr>>),
14    Index(Box<Expr>, Vec<Expr>),
15    IndexCell(Box<Expr>, Vec<Expr>),
16    Range(Box<Expr>, Option<Box<Expr>>, Box<Expr>),
17    Colon,
18    FuncCall(String, Vec<Expr>),
19    Member(Box<Expr>, String),
20    // Dynamic field: s.(expr)
21    MemberDynamic(Box<Expr>, Box<Expr>),
22    MethodCall(Box<Expr>, String, Vec<Expr>),
23    AnonFunc {
24        params: Vec<String>,
25        body: Box<Expr>,
26    },
27    FuncHandle(String),
28    MetaClass(String),
29}
30
31#[derive(Debug, PartialEq, Copy, Clone, Serialize, Deserialize)]
32pub enum BinOp {
33    Add,
34    Sub,
35    Mul,
36    Div,
37    Pow,
38    LeftDiv,
39    Colon,
40    // Element-wise operations
41    ElemMul,     // .*
42    ElemDiv,     // ./
43    ElemPow,     // .^
44    ElemLeftDiv, // .\
45    // Logical operations
46    AndAnd, // && (short-circuit)
47    OrOr,   // || (short-circuit)
48    BitAnd, // &
49    BitOr,  // |
50    // Comparison operations
51    Equal,        // ==
52    NotEqual,     // ~=
53    Less,         // <
54    LessEqual,    // <=
55    Greater,      // >
56    GreaterEqual, // >=
57}
58
59#[derive(Debug, PartialEq, Copy, Clone, Serialize, Deserialize)]
60pub enum UnOp {
61    Plus,
62    Minus,
63    Transpose,
64    NonConjugateTranspose,
65    Not, // ~
66}
67
68#[derive(Debug, PartialEq)]
69pub enum Stmt {
70    ExprStmt(Expr, bool), // Expression and whether it's semicolon-terminated (suppressed)
71    Assign(String, Expr, bool), // Variable, Expression, and whether it's semicolon-terminated (suppressed)
72    MultiAssign(Vec<String>, Expr, bool),
73    AssignLValue(LValue, Expr, bool),
74    If {
75        cond: Expr,
76        then_body: Vec<Stmt>,
77        elseif_blocks: Vec<(Expr, Vec<Stmt>)>,
78        else_body: Option<Vec<Stmt>>,
79    },
80    While {
81        cond: Expr,
82        body: Vec<Stmt>,
83    },
84    For {
85        var: String,
86        expr: Expr,
87        body: Vec<Stmt>,
88    },
89    Switch {
90        expr: Expr,
91        cases: Vec<(Expr, Vec<Stmt>)>,
92        otherwise: Option<Vec<Stmt>>,
93    },
94    TryCatch {
95        try_body: Vec<Stmt>,
96        catch_var: Option<String>,
97        catch_body: Vec<Stmt>,
98    },
99    Global(Vec<String>),
100    Persistent(Vec<String>),
101    Break,
102    Continue,
103    Return,
104    Function {
105        name: String,
106        params: Vec<String>,
107        outputs: Vec<String>,
108        body: Vec<Stmt>,
109    },
110    Import {
111        path: Vec<String>,
112        wildcard: bool,
113    },
114    ClassDef {
115        name: String,
116        super_class: Option<String>,
117        members: Vec<ClassMember>,
118    },
119}
120
121#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
122pub enum LValue {
123    Var(String),
124    Member(Box<Expr>, String),
125    MemberDynamic(Box<Expr>, Box<Expr>),
126    Index(Box<Expr>, Vec<Expr>),
127    IndexCell(Box<Expr>, Vec<Expr>),
128}
129
130#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
131pub struct Attr {
132    pub name: String,
133    pub value: Option<String>,
134}
135
136#[derive(Debug, PartialEq)]
137pub enum ClassMember {
138    Properties {
139        attributes: Vec<Attr>,
140        names: Vec<String>,
141    },
142    Methods {
143        attributes: Vec<Attr>,
144        body: Vec<Stmt>,
145    },
146    Events {
147        attributes: Vec<Attr>,
148        names: Vec<String>,
149    },
150    Enumeration {
151        attributes: Vec<Attr>,
152        names: Vec<String>,
153    },
154    Arguments {
155        attributes: Vec<Attr>,
156        names: Vec<String>,
157    },
158}
159
160#[derive(Debug, PartialEq)]
161pub struct Program {
162    pub body: Vec<Stmt>,
163}
164
165#[derive(Clone)]
166struct TokenInfo {
167    token: Token,
168    lexeme: String,
169    position: usize,
170}
171
172#[derive(Debug)]
173pub struct ParseError {
174    pub message: String,
175    pub position: usize,
176    pub found_token: Option<String>,
177    pub expected: Option<String>,
178}
179
180pub fn parse(input: &str) -> Result<Program, ParseError> {
181    use runmat_lexer::tokenize_detailed;
182
183    let toks = tokenize_detailed(input);
184    let mut tokens = Vec::new();
185
186    for t in toks {
187        if matches!(t.token, Token::Error) {
188            return Err(ParseError {
189                message: format!("Invalid token: '{}'", t.lexeme),
190                position: t.start,
191                found_token: Some(t.lexeme),
192                expected: None,
193            });
194        }
195        // Skip layout-only tokens from lexing
196        if matches!(t.token, Token::Ellipsis | Token::Section) {
197            continue;
198        }
199        tokens.push(TokenInfo {
200            token: t.token,
201            lexeme: t.lexeme,
202            position: t.start,
203        });
204    }
205
206    let mut parser = Parser {
207        tokens,
208        pos: 0,
209        input: input.to_string(),
210    };
211    parser.parse_program()
212}
213
214// For backward compatibility
215pub fn parse_simple(input: &str) -> Result<Program, String> {
216    parse(input).map_err(|e| format!("{e}"))
217}
218
219impl std::fmt::Display for ParseError {
220    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
221        write!(
222            f,
223            "Parse error at position {}: {}",
224            self.position, self.message
225        )?;
226        if let Some(found) = &self.found_token {
227            write!(f, " (found: '{found}')")?;
228        }
229        if let Some(expected) = &self.expected {
230            write!(f, " (expected: {expected})")?;
231        }
232        Ok(())
233    }
234}
235
236impl std::error::Error for ParseError {}
237
238impl From<String> for ParseError {
239    fn from(message: String) -> Self {
240        ParseError {
241            message,
242            position: 0,
243            found_token: None,
244            expected: None,
245        }
246    }
247}
248
249impl From<ParseError> for String {
250    fn from(error: ParseError) -> Self {
251        format!("{error}")
252    }
253}
254
255struct Parser {
256    tokens: Vec<TokenInfo>,
257    pos: usize,
258    input: String,
259}
260
261impl Parser {
262    fn skip_newlines(&mut self) {
263        while self.consume(&Token::Newline) {}
264    }
265
266    fn is_simple_assignment_ahead(&self) -> bool {
267        // Heuristic: at statement start, if we see Ident ... '=' before a terminator, treat as assignment
268        self.peek_token() == Some(&Token::Ident) && self.peek_token_at(1) == Some(&Token::Assign)
269    }
270    fn parse_program(&mut self) -> Result<Program, ParseError> {
271        let mut body = Vec::new();
272        while self.pos < self.tokens.len() {
273            if self.consume(&Token::Semicolon)
274                || self.consume(&Token::Comma)
275                || self.consume(&Token::Newline)
276            {
277                continue;
278            }
279            body.push(self.parse_stmt_with_semicolon()?);
280        }
281        Ok(Program { body })
282    }
283
284    fn error(&self, message: &str) -> ParseError {
285        let (position, found_token) = if let Some(token_info) = self.tokens.get(self.pos) {
286            (token_info.position, Some(token_info.lexeme.clone()))
287        } else {
288            (self.input.len(), None)
289        };
290
291        ParseError {
292            message: message.to_string(),
293            position,
294            found_token,
295            expected: None,
296        }
297    }
298
299    fn error_with_expected(&self, message: &str, expected: &str) -> ParseError {
300        let (position, found_token) = if let Some(token_info) = self.tokens.get(self.pos) {
301            (token_info.position, Some(token_info.lexeme.clone()))
302        } else {
303            (self.input.len(), None)
304        };
305
306        ParseError {
307            message: message.to_string(),
308            position,
309            found_token,
310            expected: Some(expected.to_string()),
311        }
312    }
313
314    fn parse_stmt_with_semicolon(&mut self) -> Result<Stmt, ParseError> {
315        let stmt = self.parse_stmt()?;
316        let is_semicolon_terminated = self.consume(&Token::Semicolon);
317
318        // Expression statements: semicolon indicates output suppression
319        // Top-level assignments: set true only if a following top-level statement exists
320        // (i.e., not the final trailing semicolon at EOF).
321        match stmt {
322            Stmt::ExprStmt(expr, _) => Ok(Stmt::ExprStmt(expr, is_semicolon_terminated)),
323            Stmt::Assign(name, expr, _) => {
324                let has_more_toplevel_tokens = self.pos < self.tokens.len();
325                Ok(Stmt::Assign(
326                    name,
327                    expr,
328                    is_semicolon_terminated && has_more_toplevel_tokens,
329                ))
330            }
331            Stmt::MultiAssign(names, expr, _) => {
332                let has_more_toplevel_tokens = self.pos < self.tokens.len();
333                Ok(Stmt::MultiAssign(
334                    names,
335                    expr,
336                    is_semicolon_terminated && has_more_toplevel_tokens,
337                ))
338            }
339            Stmt::AssignLValue(lv, expr, _) => {
340                let has_more_toplevel_tokens = self.pos < self.tokens.len();
341                Ok(Stmt::AssignLValue(
342                    lv,
343                    expr,
344                    is_semicolon_terminated && has_more_toplevel_tokens,
345                ))
346            }
347            other => Ok(other),
348        }
349    }
350
351    fn parse_stmt(&mut self) -> Result<Stmt, ParseError> {
352        match self.peek_token() {
353            Some(Token::If) => self.parse_if().map_err(|e| e.into()),
354            Some(Token::For) => self.parse_for().map_err(|e| e.into()),
355            Some(Token::While) => self.parse_while().map_err(|e| e.into()),
356            Some(Token::Switch) => self.parse_switch().map_err(|e| e.into()),
357            Some(Token::Try) => self.parse_try_catch().map_err(|e| e.into()),
358            Some(Token::Import) => self.parse_import().map_err(|e| e.into()),
359            Some(Token::ClassDef) => self.parse_classdef().map_err(|e| e.into()),
360            Some(Token::Global) => self.parse_global().map_err(|e| e.into()),
361            Some(Token::Persistent) => self.parse_persistent().map_err(|e| e.into()),
362            Some(Token::Break) => {
363                self.pos += 1;
364                Ok(Stmt::Break)
365            }
366            Some(Token::Continue) => {
367                self.pos += 1;
368                Ok(Stmt::Continue)
369            }
370            Some(Token::Return) => {
371                self.pos += 1;
372                Ok(Stmt::Return)
373            }
374            Some(Token::Function) => self.parse_function().map_err(|e| e.into()),
375            // Multi-assign like [a,b] = f()
376            Some(Token::LBracket) => {
377                if matches!(self.peek_token_at(1), Some(Token::Ident | Token::Tilde)) {
378                    match self.try_parse_multi_assign() {
379                        Ok(stmt) => Ok(stmt),
380                        Err(msg) => Err(self.error(&msg)),
381                    }
382                } else {
383                    let expr = self.parse_expr()?;
384                    Ok(Stmt::ExprStmt(expr, false))
385                }
386            }
387            _ => {
388                if self.peek_token() == Some(&Token::Ident)
389                    && self.peek_token_at(1) == Some(&Token::Assign)
390                {
391                    let name = self
392                        .next()
393                        .ok_or_else(|| self.error("expected identifier"))?
394                        .lexeme;
395                    if !self.consume(&Token::Assign) {
396                        return Err(self.error_with_expected("expected assignment operator", "'='"));
397                    }
398                    let expr = self.parse_expr()?;
399                    Ok(Stmt::Assign(name, expr, false)) // Will be updated by parse_stmt_with_semicolon
400                } else if self.is_simple_assignment_ahead() {
401                    // Fallback: treat as simple assignment if '=' appears before terminator
402                    let name = self.expect_ident().map_err(|e| self.error(&e))?;
403                    if !self.consume(&Token::Assign) {
404                        return Err(self.error_with_expected("expected assignment operator", "'='"));
405                    }
406                    let expr = self.parse_expr()?;
407                    Ok(Stmt::Assign(name, expr, false))
408                } else if self.peek_token() == Some(&Token::Ident) {
409                    // First, try complex lvalue assignment starting from an identifier: A(1)=x, A{1}=x, s.f=x, s.(n)=x
410                    if let Some(lv) = self.try_parse_lvalue_assign()? {
411                        return Ok(lv);
412                    }
413                    // Command-form at statement start if it looks like a sequence of simple arguments
414                    // and is not immediately followed by indexing/member syntax.
415                    if self.can_start_command_form() {
416                        let name = self.next().unwrap().lexeme;
417                        let args = self.parse_command_args();
418                        Ok(Stmt::ExprStmt(Expr::FuncCall(name, args), false))
419                    } else {
420                        // If we see Ident <space> Ident immediately followed by postfix opener,
421                        // this is an ambiguous adjacency (e.g., "foo b(1)"). Emit a targeted error.
422                        if matches!(self.peek_token_at(1), Some(Token::Ident))
423                            && matches!(
424                                self.peek_token_at(2),
425                                Some(
426                                    Token::LParen
427                                        | Token::Dot
428                                        | Token::LBracket
429                                        | Token::LBrace
430                                        | Token::Transpose
431                                )
432                            )
433                        {
434                            return Err(self.error(
435                                "ambiguous command-form near identifier; use function syntax foo(b(...)) or quote argument",
436                            ));
437                        }
438                        // Fall back to full expression parse (e.g., foo(1), foo.bar, etc.)
439                        let expr = self.parse_expr()?;
440                        Ok(Stmt::ExprStmt(expr, false))
441                    }
442                } else if let Some(lv) = self.try_parse_lvalue_assign()? {
443                    Ok(lv)
444                } else {
445                    let expr = self.parse_expr()?;
446                    // Require statement terminator or EOF after a bare expression at statement level
447                    // Be permissive: allow subsequent tokens; ambiguity has been handled above by
448                    // can_start_command_form()/adjacency guard. Treat this as a normal expression statement.
449                    Ok(Stmt::ExprStmt(expr, false))
450                }
451            }
452        }
453    }
454
455    fn can_start_command_form(&self) -> bool {
456        // At entry, peek_token() is Some(Ident) for callee
457        let mut i = 1;
458        while matches!(
459            self.peek_token_at(i),
460            Some(Token::Newline | Token::Ellipsis)
461        ) {
462            i += 1;
463        }
464        // At least one simple arg must follow
465        if !matches!(
466            self.peek_token_at(i),
467            Some(Token::Ident | Token::Integer | Token::Float | Token::Str | Token::End)
468        ) {
469            return false;
470        }
471        // Consume all contiguous simple args
472        loop {
473            match self.peek_token_at(i) {
474                Some(Token::Ident | Token::Integer | Token::Float | Token::Str | Token::End) => {
475                    i += 1;
476                }
477                Some(Token::Newline | Token::Ellipsis) => {
478                    i += 1;
479                }
480                _ => break,
481            }
482        }
483        // If the next token begins indexing/member or other expression syntax, do not use command-form
484        match self.peek_token_at(i) {
485            Some(Token::LParen)
486            | Some(Token::Dot)
487            | Some(Token::LBracket)
488            | Some(Token::LBrace)
489            | Some(Token::Transpose) => false,
490            // If next token is assignment, also not a command-form (would be ambiguous)
491            Some(Token::Assign) => false,
492            // End of statement is okay for command-form
493            None | Some(Token::Semicolon) | Some(Token::Comma) | Some(Token::Newline) => true,
494            // Otherwise conservatively allow
495            _ => true,
496        }
497    }
498
499    fn parse_command_args(&mut self) -> Vec<Expr> {
500        let mut args = Vec::new();
501        loop {
502            if self.consume(&Token::Newline) {
503                continue;
504            }
505            if self.consume(&Token::Ellipsis) {
506                continue;
507            }
508            match self.peek_token() {
509                Some(Token::Ident) => {
510                    let ident = self.next().unwrap().lexeme;
511                    args.push(Expr::Ident(ident));
512                }
513                // In command-form, accept 'end' as a literal identifier token for compatibility
514                Some(Token::End) => {
515                    self.pos += 1;
516                    args.push(Expr::Ident("end".to_string()));
517                }
518                Some(Token::Integer) | Some(Token::Float) => {
519                    let num = self.next().unwrap().lexeme;
520                    args.push(Expr::Number(num));
521                }
522                Some(Token::Str) => {
523                    let s = self.next().unwrap().lexeme;
524                    args.push(Expr::String(s));
525                }
526                // Stop on tokens that would start normal expression syntax
527                Some(Token::Slash)
528                | Some(Token::Star)
529                | Some(Token::Backslash)
530                | Some(Token::Plus)
531                | Some(Token::Minus)
532                | Some(Token::LParen)
533                | Some(Token::Dot)
534                | Some(Token::LBracket)
535                | Some(Token::LBrace)
536                | Some(Token::Transpose) => break,
537                _ => break,
538            }
539        }
540        args
541    }
542
543    fn try_parse_lvalue_assign(&mut self) -> Result<Option<Stmt>, ParseError> {
544        let save = self.pos;
545        // Parse potential LValue: Member/Index/IndexCell
546        let lvalue = if self.peek_token() == Some(&Token::Ident) {
547            // Start with primary
548            let base_ident = self.next().unwrap().lexeme;
549            let mut base = Expr::Ident(base_ident);
550            loop {
551                if self.consume(&Token::LParen) {
552                    let mut args = Vec::new();
553                    if !self.consume(&Token::RParen) {
554                        args.push(self.parse_expr()?);
555                        while self.consume(&Token::Comma) {
556                            args.push(self.parse_expr()?);
557                        }
558                        if !self.consume(&Token::RParen) {
559                            return Err(self.error_with_expected("expected ')' after indices", ")"));
560                        }
561                    }
562                    base = Expr::Index(Box::new(base), args);
563                } else if self.consume(&Token::LBracket) {
564                    let mut idxs = Vec::new();
565                    idxs.push(self.parse_expr()?);
566                    while self.consume(&Token::Comma) {
567                        idxs.push(self.parse_expr()?);
568                    }
569                    if !self.consume(&Token::RBracket) {
570                        return Err(self.error_with_expected("expected ']'", "]"));
571                    }
572                    base = Expr::Index(Box::new(base), idxs);
573                } else if self.consume(&Token::LBrace) {
574                    let mut idxs = Vec::new();
575                    idxs.push(self.parse_expr()?);
576                    while self.consume(&Token::Comma) {
577                        idxs.push(self.parse_expr()?);
578                    }
579                    if !self.consume(&Token::RBrace) {
580                        return Err(self.error_with_expected("expected '}'", "}"));
581                    }
582                    base = Expr::IndexCell(Box::new(base), idxs);
583                } else if self.peek_token() == Some(&Token::Dot) {
584                    // If this is .', it's a non-conjugate transpose, not a member
585                    if self.peek_token_at(1) == Some(&Token::Transpose) {
586                        break;
587                    }
588                    // If this is .+ or .-, it's an additive operator, not a member
589                    if self.peek_token_at(1) == Some(&Token::Plus)
590                        || self.peek_token_at(1) == Some(&Token::Minus)
591                    {
592                        break;
593                    }
594                    // Otherwise, member access
595                    self.pos += 1; // consume '.'
596                                   // Support dynamic field: .(expr) or static .ident
597                    if self.consume(&Token::LParen) {
598                        let name_expr = self.parse_expr()?;
599                        if !self.consume(&Token::RParen) {
600                            return Err(self.error_with_expected(
601                                "expected ')' after dynamic field expression",
602                                ")",
603                            ));
604                        }
605                        base = Expr::MemberDynamic(Box::new(base), Box::new(name_expr));
606                    } else {
607                        let name = self.expect_ident()?;
608                        base = Expr::Member(Box::new(base), name);
609                    }
610                } else {
611                    break;
612                }
613            }
614            base
615        } else {
616            self.pos = save;
617            return Ok(None);
618        };
619        if !self.consume(&Token::Assign) {
620            self.pos = save;
621            return Ok(None);
622        }
623        let rhs = self.parse_expr()?;
624        let stmt = match lvalue {
625            Expr::Member(b, name) => Stmt::AssignLValue(LValue::Member(b, name), rhs, false),
626            Expr::MemberDynamic(b, n) => {
627                Stmt::AssignLValue(LValue::MemberDynamic(b, n), rhs, false)
628            }
629            Expr::Index(b, idxs) => Stmt::AssignLValue(LValue::Index(b, idxs), rhs, false),
630            Expr::IndexCell(b, idxs) => Stmt::AssignLValue(LValue::IndexCell(b, idxs), rhs, false),
631            Expr::Ident(v) => Stmt::Assign(v, rhs, false),
632            _ => {
633                self.pos = save;
634                return Ok(None);
635            }
636        };
637        Ok(Some(stmt))
638    }
639
640    fn parse_expr(&mut self) -> Result<Expr, ParseError> {
641        self.parse_logical_or()
642    }
643
644    fn parse_logical_or(&mut self) -> Result<Expr, ParseError> {
645        let mut node = self.parse_logical_and()?;
646        while self.consume(&Token::OrOr) {
647            let rhs = self.parse_logical_and()?;
648            node = Expr::Binary(Box::new(node), BinOp::OrOr, Box::new(rhs));
649        }
650        Ok(node)
651    }
652
653    fn parse_logical_and(&mut self) -> Result<Expr, ParseError> {
654        let mut node = self.parse_bitwise_or()?;
655        while self.consume(&Token::AndAnd) {
656            let rhs = self.parse_bitwise_or()?;
657            node = Expr::Binary(Box::new(node), BinOp::AndAnd, Box::new(rhs));
658        }
659        Ok(node)
660    }
661
662    fn parse_bitwise_or(&mut self) -> Result<Expr, ParseError> {
663        let mut node = self.parse_bitwise_and()?;
664        while self.consume(&Token::Or) {
665            let rhs = self.parse_bitwise_and()?;
666            node = Expr::Binary(Box::new(node), BinOp::BitOr, Box::new(rhs));
667        }
668        Ok(node)
669    }
670
671    fn parse_bitwise_and(&mut self) -> Result<Expr, ParseError> {
672        let mut node = self.parse_range()?;
673        while self.consume(&Token::And) {
674            let rhs = self.parse_range()?;
675            node = Expr::Binary(Box::new(node), BinOp::BitAnd, Box::new(rhs));
676        }
677        Ok(node)
678    }
679
680    fn parse_range(&mut self) -> Result<Expr, ParseError> {
681        let mut node = self.parse_comparison()?;
682        if self.consume(&Token::Colon) {
683            let mid = self.parse_comparison()?;
684            if self.consume(&Token::Colon) {
685                let end = self.parse_comparison()?;
686                node = Expr::Range(Box::new(node), Some(Box::new(mid)), Box::new(end));
687            } else {
688                node = Expr::Range(Box::new(node), None, Box::new(mid));
689            }
690        }
691        Ok(node)
692    }
693
694    fn parse_comparison(&mut self) -> Result<Expr, ParseError> {
695        let mut node = self.parse_add_sub()?;
696        loop {
697            let op = match self.peek_token() {
698                Some(Token::Equal) => BinOp::Equal,
699                Some(Token::NotEqual) => BinOp::NotEqual,
700                Some(Token::Less) => BinOp::Less,
701                Some(Token::LessEqual) => BinOp::LessEqual,
702                Some(Token::Greater) => BinOp::Greater,
703                Some(Token::GreaterEqual) => BinOp::GreaterEqual,
704                _ => break,
705            };
706            self.pos += 1; // consume op
707            let rhs = self.parse_add_sub()?;
708            node = Expr::Binary(Box::new(node), op, Box::new(rhs));
709        }
710        Ok(node)
711    }
712
713    fn parse_add_sub(&mut self) -> Result<Expr, String> {
714        let mut node = self.parse_mul_div()?;
715        loop {
716            let op = if self.consume(&Token::Plus) {
717                Some(BinOp::Add)
718            } else if self.consume(&Token::Minus) {
719                Some(BinOp::Sub)
720            } else if self.peek_token() == Some(&Token::Dot)
721                && (self.peek_token_at(1) == Some(&Token::Plus)
722                    || self.peek_token_at(1) == Some(&Token::Minus))
723            {
724                // '.+' or '.-' tokenized as Dot then Plus/Minus; treat like Add/Sub
725                // consume two tokens
726                self.pos += 2;
727                if self.tokens[self.pos - 1].token == Token::Plus {
728                    Some(BinOp::Add)
729                } else {
730                    Some(BinOp::Sub)
731                }
732            } else {
733                None
734            };
735            let Some(op) = op else { break };
736            let rhs = self.parse_mul_div()?;
737            node = Expr::Binary(Box::new(node), op, Box::new(rhs));
738        }
739        Ok(node)
740    }
741
742    fn parse_mul_div(&mut self) -> Result<Expr, String> {
743        let mut node = self.parse_unary()?;
744        loop {
745            let op = match self.peek_token() {
746                Some(Token::Star) => BinOp::Mul,
747                Some(Token::DotStar) => BinOp::ElemMul,
748                Some(Token::Slash) => BinOp::Div,
749                Some(Token::DotSlash) => BinOp::ElemDiv,
750                Some(Token::Backslash) => BinOp::LeftDiv,
751                Some(Token::DotBackslash) => BinOp::ElemLeftDiv,
752                _ => break,
753            };
754            self.pos += 1; // consume op
755            let rhs = self.parse_unary()?;
756            node = Expr::Binary(Box::new(node), op, Box::new(rhs));
757        }
758        Ok(node)
759    }
760
761    fn parse_pow(&mut self) -> Result<Expr, String> {
762        let node = self.parse_postfix()?;
763        if let Some(token) = self.peek_token() {
764            let op = match token {
765                Token::Caret => BinOp::Pow,
766                Token::DotCaret => BinOp::ElemPow,
767                _ => return Ok(node),
768            };
769            self.pos += 1; // consume
770            let rhs = self.parse_pow()?; // right associative
771            Ok(Expr::Binary(Box::new(node), op, Box::new(rhs)))
772        } else {
773            Ok(node)
774        }
775    }
776
777    fn parse_postfix_with_base(&mut self, mut expr: Expr) -> Result<Expr, String> {
778        loop {
779            if self.consume(&Token::LParen) {
780                let mut args = Vec::new();
781                if !self.consume(&Token::RParen) {
782                    args.push(self.parse_expr()?);
783                    while self.consume(&Token::Comma) {
784                        args.push(self.parse_expr()?);
785                    }
786                    if !self.consume(&Token::RParen) {
787                        return Err("expected ')' after arguments".into());
788                    }
789                }
790                // Binder-based disambiguation:
791                // If the callee is an identifier, defer call vs. index to HIR binding.
792                // Parse as a function call now; HIR will rewrite to Index if a variable shadows the function.
793                if let Expr::Ident(ref name) = expr {
794                    expr = Expr::FuncCall(name.clone(), args);
795                } else {
796                    // For non-ident bases (e.g., X(1), (A+B)(1)), this is indexing.
797                    expr = Expr::Index(Box::new(expr), args);
798                }
799            } else if self.consume(&Token::LBracket) {
800                // Array indexing
801                let mut indices = Vec::new();
802                indices.push(self.parse_expr()?);
803                while self.consume(&Token::Comma) {
804                    indices.push(self.parse_expr()?);
805                }
806                if !self.consume(&Token::RBracket) {
807                    return Err("expected ']'".into());
808                }
809                expr = Expr::Index(Box::new(expr), indices);
810            } else if self.consume(&Token::LBrace) {
811                // Cell content indexing
812                let mut indices = Vec::new();
813                indices.push(self.parse_expr()?);
814                while self.consume(&Token::Comma) {
815                    indices.push(self.parse_expr()?);
816                }
817                if !self.consume(&Token::RBrace) {
818                    return Err("expected '}'".into());
819                }
820                expr = Expr::IndexCell(Box::new(expr), indices);
821            } else if self.peek_token() == Some(&Token::Dot) {
822                // Could be .', .+ , .- or member access
823                if self.peek_token_at(1) == Some(&Token::Transpose) {
824                    self.pos += 2; // '.' and '''
825                    expr = Expr::Unary(UnOp::NonConjugateTranspose, Box::new(expr));
826                    continue;
827                }
828                if self.peek_token_at(1) == Some(&Token::Plus)
829                    || self.peek_token_at(1) == Some(&Token::Minus)
830                {
831                    // '.+' or '.-' belong to additive level; stop postfix loop
832                    break;
833                }
834                // Otherwise, member access
835                self.pos += 1; // consume '.'
836                let name = match self.next() {
837                    Some(TokenInfo {
838                        token: Token::Ident,
839                        lexeme,
840                        ..
841                    }) => lexeme,
842                    _ => return Err("expected member name after '.'".into()),
843                };
844                if self.consume(&Token::LParen) {
845                    let mut args = Vec::new();
846                    if !self.consume(&Token::RParen) {
847                        args.push(self.parse_expr()?);
848                        while self.consume(&Token::Comma) {
849                            args.push(self.parse_expr()?);
850                        }
851                        if !self.consume(&Token::RParen) {
852                            return Err("expected ')' after method arguments".into());
853                        }
854                    }
855                    expr = Expr::MethodCall(Box::new(expr), name, args);
856                } else {
857                    expr = Expr::Member(Box::new(expr), name);
858                }
859            } else if self.consume(&Token::Transpose) {
860                // Matrix transpose (postfix operator)
861                expr = Expr::Unary(UnOp::Transpose, Box::new(expr));
862            } else {
863                break;
864            }
865        }
866        Ok(expr)
867    }
868
869    fn parse_postfix(&mut self) -> Result<Expr, String> {
870        let expr = self.parse_primary()?;
871        self.parse_postfix_with_base(expr)
872    }
873
874    fn parse_unary(&mut self) -> Result<Expr, String> {
875        if self.consume(&Token::Plus) {
876            Ok(Expr::Unary(UnOp::Plus, Box::new(self.parse_unary()?)))
877        } else if self.consume(&Token::Minus) {
878            Ok(Expr::Unary(UnOp::Minus, Box::new(self.parse_unary()?)))
879        } else if self.consume(&Token::Tilde) {
880            Ok(Expr::Unary(UnOp::Not, Box::new(self.parse_unary()?)))
881        } else if self.consume(&Token::Question) {
882            // Meta-class query with controlled qualified name consumption to allow postfix chaining
883            // Consume packages (lowercase-leading) and exactly one Class segment (uppercase-leading), then stop.
884            let mut parts: Vec<String> = Vec::new();
885            let first = self.expect_ident()?;
886            let class_consumed = first
887                .chars()
888                .next()
889                .map(|c| c.is_uppercase())
890                .unwrap_or(false);
891            parts.push(first);
892            while self.peek_token() == Some(&Token::Dot)
893                && matches!(self.peek_token_at(1), Some(Token::Ident))
894            {
895                // Lookahead at the next identifier lexeme
896                let next_lex = if let Some(ti) = self.tokens.get(self.pos + 1) {
897                    ti.lexeme.clone()
898                } else {
899                    String::new()
900                };
901                let is_upper = next_lex
902                    .chars()
903                    .next()
904                    .map(|c| c.is_uppercase())
905                    .unwrap_or(false);
906                if class_consumed {
907                    break;
908                }
909                // Consume dot and ident
910                self.pos += 1; // consume '.'
911                let seg = self.expect_ident()?;
912                parts.push(seg);
913                if is_upper {
914                    break;
915                }
916            }
917            let base = Expr::MetaClass(parts.join("."));
918            self.parse_postfix_with_base(base)
919        } else {
920            self.parse_pow()
921        }
922    }
923
924    fn parse_primary(&mut self) -> Result<Expr, String> {
925        match self.next() {
926            Some(info) => match info.token {
927                Token::Integer | Token::Float => Ok(Expr::Number(info.lexeme)),
928                Token::Str => Ok(Expr::String(info.lexeme)),
929                Token::True => Ok(Expr::Ident("true".into())),
930                Token::False => Ok(Expr::Ident("false".into())),
931                Token::Ident => Ok(Expr::Ident(info.lexeme)),
932                // Treat 'end' as EndKeyword in expression contexts; in command-form we allow 'end' to be consumed as an identifier via command-args path.
933                Token::End => Ok(Expr::EndKeyword),
934                Token::At => {
935                    // Anonymous function or function handle
936                    if self.consume(&Token::LParen) {
937                        let mut params = Vec::new();
938                        if !self.consume(&Token::RParen) {
939                            params.push(self.expect_ident()?);
940                            while self.consume(&Token::Comma) {
941                                params.push(self.expect_ident()?);
942                            }
943                            if !self.consume(&Token::RParen) {
944                                return Err(
945                                    "expected ')' after anonymous function parameters".into()
946                                );
947                            }
948                        }
949                        let body = self.parse_expr().map_err(|e| e.message)?;
950                        Ok(Expr::AnonFunc {
951                            params,
952                            body: Box::new(body),
953                        })
954                    } else {
955                        // function handle @name
956                        let name = self.expect_ident()?;
957                        Ok(Expr::FuncHandle(name))
958                    }
959                }
960                Token::LParen => {
961                    let expr = self.parse_expr()?;
962                    if !self.consume(&Token::RParen) {
963                        return Err("expected ')' to close parentheses".into());
964                    }
965                    Ok(expr)
966                }
967                Token::LBracket => {
968                    let matrix = self.parse_matrix()?;
969                    if !self.consume(&Token::RBracket) {
970                        return Err("expected ']' to close matrix literal".into());
971                    }
972                    Ok(matrix)
973                }
974                Token::LBrace => {
975                    let cell = self.parse_cell()?;
976                    if !self.consume(&Token::RBrace) {
977                        return Err("expected '}' to close cell literal".into());
978                    }
979                    Ok(cell)
980                }
981                Token::Colon => Ok(Expr::Colon),
982                Token::ClassDef => {
983                    // Rewind one token and defer to statement parser for classdef blocks
984                    self.pos -= 1;
985                    Err("classdef in expression context".into())
986                }
987                _ => {
988                    // Provide detailed error message about what token was unexpected
989                    let token_desc = match info.token {
990                        Token::Semicolon => "semicolon ';' (statement separator)",
991                        Token::Comma => "comma ',' (list separator)",
992                        Token::RParen => {
993                            "closing parenthesis ')' (no matching opening parenthesis)"
994                        }
995                        Token::RBracket => "closing bracket ']' (no matching opening bracket)",
996                        Token::If => "keyword 'if' (expected in statement context)",
997                        Token::For => "keyword 'for' (expected in statement context)",
998                        Token::While => "keyword 'while' (expected in statement context)",
999                        Token::Function => "keyword 'function' (expected in statement context)",
1000                        Token::End => "keyword 'end' (no matching control structure)",
1001                        Token::Equal => "equality operator '==' (expected in comparison context)",
1002                        Token::Assign => "assignment operator '=' (expected in assignment context)",
1003                        Token::Error => "invalid character or symbol",
1004                        _ => {
1005                            return Err(format!(
1006                                "unexpected token '{}' in expression context",
1007                                info.lexeme
1008                            ))
1009                        }
1010                    };
1011                    Err(format!("unexpected {token_desc} in expression context"))
1012                }
1013            },
1014            None => Err("unexpected end of input, expected expression".into()),
1015        }
1016    }
1017
1018    fn parse_matrix(&mut self) -> Result<Expr, String> {
1019        self.skip_newlines();
1020        let mut rows = Vec::new();
1021        if self.peek_token() == Some(&Token::RBracket) {
1022            return Ok(Expr::Tensor(rows));
1023        }
1024        loop {
1025            self.skip_newlines();
1026            if self.peek_token() == Some(&Token::RBracket) {
1027                break;
1028            }
1029            let mut row = Vec::new();
1030            // First element in the row
1031            row.push(self.parse_expr()?);
1032            // Accept either comma-separated or whitespace-separated elements until ';' or ']'
1033            loop {
1034                if self.consume(&Token::Newline) {
1035                    continue;
1036                }
1037                if self.consume(&Token::Comma) {
1038                    row.push(self.parse_expr()?);
1039                    continue;
1040                }
1041                // If next token ends the row/matrix, stop
1042                if matches!(
1043                    self.peek_token(),
1044                    Some(Token::Semicolon) | Some(Token::RBracket)
1045                ) {
1046                    break;
1047                }
1048                // Otherwise, treat whitespace as a separator and parse the next element
1049                // Only proceed if the next token can start an expression
1050                match self.peek_token() {
1051                    Some(
1052                        Token::Ident
1053                        | Token::Integer
1054                        | Token::Float
1055                        | Token::Str
1056                        | Token::LParen
1057                        | Token::LBracket
1058                        | Token::LBrace
1059                        | Token::At
1060                        | Token::Plus
1061                        | Token::Minus
1062                        | Token::Colon
1063                        | Token::True
1064                        | Token::False,
1065                    ) => {
1066                        row.push(self.parse_expr()?);
1067                    }
1068                    _ => {
1069                        break;
1070                    }
1071                }
1072            }
1073            rows.push(row);
1074            if self.consume(&Token::Semicolon) {
1075                self.skip_newlines();
1076                continue;
1077            } else {
1078                break;
1079            }
1080        }
1081        self.skip_newlines();
1082        Ok(Expr::Tensor(rows))
1083    }
1084
1085    fn parse_if(&mut self) -> Result<Stmt, String> {
1086        self.consume(&Token::If);
1087        let cond = self.parse_expr()?;
1088        let then_body =
1089            self.parse_block(|t| matches!(t, Token::Else | Token::ElseIf | Token::End))?;
1090        let mut elseif_blocks = Vec::new();
1091        while self.consume(&Token::ElseIf) {
1092            let c = self.parse_expr()?;
1093            let body =
1094                self.parse_block(|t| matches!(t, Token::Else | Token::ElseIf | Token::End))?;
1095            elseif_blocks.push((c, body));
1096        }
1097        let else_body = if self.consume(&Token::Else) {
1098            Some(self.parse_block(|t| matches!(t, Token::End))?)
1099        } else {
1100            None
1101        };
1102        if !self.consume(&Token::End) {
1103            return Err("expected 'end'".into());
1104        }
1105        Ok(Stmt::If {
1106            cond,
1107            then_body,
1108            elseif_blocks,
1109            else_body,
1110        })
1111    }
1112
1113    fn parse_while(&mut self) -> Result<Stmt, String> {
1114        self.consume(&Token::While);
1115        let cond = self.parse_expr()?;
1116        let body = self.parse_block(|t| matches!(t, Token::End))?;
1117        if !self.consume(&Token::End) {
1118            return Err("expected 'end'".into());
1119        }
1120        Ok(Stmt::While { cond, body })
1121    }
1122
1123    fn parse_for(&mut self) -> Result<Stmt, String> {
1124        self.consume(&Token::For);
1125        let var = self.expect_ident()?;
1126        if !self.consume(&Token::Assign) {
1127            return Err("expected '='".into());
1128        }
1129        let expr = self.parse_expr()?;
1130        let body = self.parse_block(|t| matches!(t, Token::End))?;
1131        if !self.consume(&Token::End) {
1132            return Err("expected 'end'".into());
1133        }
1134        Ok(Stmt::For { var, expr, body })
1135    }
1136
1137    fn parse_function(&mut self) -> Result<Stmt, String> {
1138        self.consume(&Token::Function);
1139        let mut outputs = Vec::new();
1140        if self.consume(&Token::LBracket) {
1141            outputs.push(self.expect_ident_or_tilde()?);
1142            while self.consume(&Token::Comma) {
1143                outputs.push(self.expect_ident_or_tilde()?);
1144            }
1145            if !self.consume(&Token::RBracket) {
1146                return Err("expected ']'".into());
1147            }
1148            if !self.consume(&Token::Assign) {
1149                return Err("expected '='".into());
1150            }
1151        } else if self.peek_token() == Some(&Token::Ident)
1152            && self.peek_token_at(1) == Some(&Token::Assign)
1153        {
1154            outputs.push(self.next().unwrap().lexeme);
1155            self.consume(&Token::Assign);
1156        }
1157        let name = self.expect_ident()?;
1158        if !self.consume(&Token::LParen) {
1159            return Err("expected '('".into());
1160        }
1161        let mut params = Vec::new();
1162        if !self.consume(&Token::RParen) {
1163            params.push(self.expect_ident()?);
1164            while self.consume(&Token::Comma) {
1165                params.push(self.expect_ident()?);
1166            }
1167            if !self.consume(&Token::RParen) {
1168                return Err("expected ')'".into());
1169            }
1170        }
1171
1172        // Enforce varargs placement constraints at parse time
1173        // varargin: at most once, must be last in params if present
1174        if let Some(idx) = params.iter().position(|p| p == "varargin") {
1175            if idx != params.len() - 1 {
1176                return Err("'varargin' must be the last input parameter".into());
1177            }
1178            if params.iter().filter(|p| p.as_str() == "varargin").count() > 1 {
1179                return Err("'varargin' cannot appear more than once".into());
1180            }
1181        }
1182        // varargout: at most once, must be last in outputs if present
1183        if let Some(idx) = outputs.iter().position(|o| o == "varargout") {
1184            if idx != outputs.len() - 1 {
1185                return Err("'varargout' must be the last output parameter".into());
1186            }
1187            if outputs.iter().filter(|o| o.as_str() == "varargout").count() > 1 {
1188                return Err("'varargout' cannot appear more than once".into());
1189            }
1190        }
1191
1192        // Optional function-level arguments block
1193        // arguments ... end  (we accept a sequence of identifiers, validation semantics handled in HIR/runtime)
1194        if self.peek_token() == Some(&Token::Arguments) {
1195            self.pos += 1; // consume 'arguments'
1196                           // Accept a flat list of identifiers optionally separated by commas/semicolons
1197            loop {
1198                if self.consume(&Token::End) {
1199                    break;
1200                }
1201                if self.consume(&Token::Semicolon) || self.consume(&Token::Comma) {
1202                    continue;
1203                }
1204                if matches!(self.peek_token(), Some(Token::Ident)) {
1205                    let _ = self.expect_ident()?;
1206                    continue;
1207                }
1208                // Tolerate newlines/whitespace-only between entries
1209                if self.peek_token().is_none() {
1210                    break;
1211                }
1212                break;
1213            }
1214        }
1215
1216        let body = self.parse_block(|t| matches!(t, Token::End))?;
1217        if !self.consume(&Token::End) {
1218            return Err("expected 'end'".into());
1219        }
1220        Ok(Stmt::Function {
1221            name,
1222            params,
1223            outputs,
1224            body,
1225        })
1226    }
1227
1228    fn parse_block<F>(&mut self, term: F) -> Result<Vec<Stmt>, String>
1229    where
1230        F: Fn(&Token) -> bool,
1231    {
1232        let mut body = Vec::new();
1233        while let Some(tok) = self.peek_token() {
1234            if term(tok) {
1235                break;
1236            }
1237            if self.consume(&Token::Semicolon)
1238                || self.consume(&Token::Comma)
1239                || self.consume(&Token::Newline)
1240            {
1241                continue;
1242            }
1243            // Fast-path: handle multi-assign LHS at statement start inside blocks reliably
1244            let stmt = if self.peek_token() == Some(&Token::LBracket) {
1245                self.try_parse_multi_assign()?
1246            } else {
1247                self.parse_stmt().map_err(|e| e.message)?
1248            };
1249            let is_semicolon_terminated = self.consume(&Token::Semicolon);
1250
1251            // Only expression statements are display-suppressed by semicolon.
1252            let final_stmt = match stmt {
1253                Stmt::ExprStmt(expr, _) => Stmt::ExprStmt(expr, is_semicolon_terminated),
1254                Stmt::Assign(name, expr, _) => Stmt::Assign(name, expr, false),
1255                Stmt::MultiAssign(names, expr, _) => Stmt::MultiAssign(names, expr, false),
1256                Stmt::AssignLValue(lv, expr, _) => Stmt::AssignLValue(lv, expr, false),
1257                other => other,
1258            };
1259            body.push(final_stmt);
1260        }
1261        Ok(body)
1262    }
1263
1264    fn parse_cell(&mut self) -> Result<Expr, String> {
1265        let mut rows = Vec::new();
1266        self.skip_newlines();
1267        if self.peek_token() == Some(&Token::RBrace) {
1268            return Ok(Expr::Cell(rows));
1269        }
1270        loop {
1271            self.skip_newlines();
1272            if self.peek_token() == Some(&Token::RBrace) {
1273                break;
1274            }
1275            let mut row = Vec::new();
1276            row.push(self.parse_expr()?);
1277            while self.consume(&Token::Comma) {
1278                row.push(self.parse_expr()?);
1279            }
1280            rows.push(row);
1281            if self.consume(&Token::Semicolon) {
1282                self.skip_newlines();
1283                continue;
1284            } else {
1285                break;
1286            }
1287        }
1288        self.skip_newlines();
1289        Ok(Expr::Cell(rows))
1290    }
1291
1292    fn parse_switch(&mut self) -> Result<Stmt, String> {
1293        self.consume(&Token::Switch);
1294        let control = self.parse_expr()?;
1295        let mut cases = Vec::new();
1296        let mut otherwise: Option<Vec<Stmt>> = None;
1297        loop {
1298            if self.consume(&Token::Newline) || self.consume(&Token::Semicolon) {
1299                continue;
1300            }
1301            if self.consume(&Token::Case) {
1302                let val = self.parse_expr()?;
1303                let body =
1304                    self.parse_block(|t| matches!(t, Token::Case | Token::Otherwise | Token::End))?;
1305                cases.push((val, body));
1306            } else if self.consume(&Token::Otherwise) {
1307                let body = self.parse_block(|t| matches!(t, Token::End))?;
1308                otherwise = Some(body);
1309            } else if self.consume(&Token::Comma) {
1310                continue;
1311            } else {
1312                break;
1313            }
1314        }
1315        if !self.consume(&Token::End) {
1316            return Err("expected 'end' for switch".into());
1317        }
1318        Ok(Stmt::Switch {
1319            expr: control,
1320            cases,
1321            otherwise,
1322        })
1323    }
1324
1325    fn parse_try_catch(&mut self) -> Result<Stmt, String> {
1326        self.consume(&Token::Try);
1327        let try_body = self.parse_block(|t| matches!(t, Token::Catch | Token::End))?;
1328        let (catch_var, catch_body) = if self.consume(&Token::Catch) {
1329            let maybe_ident = if let Some(Token::Ident) = self.peek_token() {
1330                Some(self.expect_ident()?)
1331            } else {
1332                None
1333            };
1334            let body = self.parse_block(|t| matches!(t, Token::End))?;
1335            (maybe_ident, body)
1336        } else {
1337            (None, Vec::new())
1338        };
1339        if !self.consume(&Token::End) {
1340            return Err("expected 'end' after try/catch".into());
1341        }
1342        Ok(Stmt::TryCatch {
1343            try_body,
1344            catch_var,
1345            catch_body,
1346        })
1347    }
1348
1349    fn parse_import(&mut self) -> Result<Stmt, String> {
1350        self.consume(&Token::Import);
1351        // import pkg.sub.Class or import pkg.*
1352        let mut path = Vec::new();
1353        path.push(self.expect_ident()?);
1354        let mut wildcard = false;
1355        loop {
1356            if self.consume(&Token::DotStar) {
1357                wildcard = true;
1358                break;
1359            }
1360            if self.consume(&Token::Dot) {
1361                if self.consume(&Token::Star) {
1362                    wildcard = true;
1363                    break;
1364                } else {
1365                    path.push(self.expect_ident()?);
1366                    continue;
1367                }
1368            }
1369            break;
1370        }
1371        Ok(Stmt::Import { path, wildcard })
1372    }
1373
1374    fn parse_classdef(&mut self) -> Result<Stmt, String> {
1375        self.consume(&Token::ClassDef);
1376        let name = self.parse_qualified_name()?;
1377        let mut super_class = None;
1378        if self.consume(&Token::Less) {
1379            super_class = Some(self.parse_qualified_name()?);
1380        }
1381        let mut members: Vec<ClassMember> = Vec::new();
1382        loop {
1383            // Skip layout separators between member blocks
1384            if self.consume(&Token::Semicolon)
1385                || self.consume(&Token::Comma)
1386                || self.consume(&Token::Newline)
1387            {
1388                continue;
1389            }
1390            match self.peek_token() {
1391                Some(Token::Properties) => {
1392                    self.pos += 1;
1393                    let attrs = self.parse_optional_attr_list();
1394                    let props = self.parse_properties_names_block()?;
1395                    if !self.consume(&Token::End) {
1396                        return Err("expected 'end' after properties".into());
1397                    }
1398                    members.push(ClassMember::Properties {
1399                        attributes: attrs,
1400                        names: props,
1401                    });
1402                }
1403                Some(Token::Methods) => {
1404                    self.pos += 1;
1405                    let attrs = self.parse_optional_attr_list();
1406                    let body = self.parse_block(|t| matches!(t, Token::End))?;
1407                    if !self.consume(&Token::End) {
1408                        return Err("expected 'end' after methods".into());
1409                    }
1410                    members.push(ClassMember::Methods {
1411                        attributes: attrs,
1412                        body,
1413                    });
1414                }
1415                Some(Token::Events) => {
1416                    self.pos += 1;
1417                    let attrs = self.parse_optional_attr_list();
1418                    let names = self.parse_name_block()?;
1419                    if !self.consume(&Token::End) {
1420                        return Err("expected 'end' after events".into());
1421                    }
1422                    members.push(ClassMember::Events {
1423                        attributes: attrs,
1424                        names,
1425                    });
1426                }
1427                Some(Token::Enumeration) => {
1428                    self.pos += 1;
1429                    let attrs = self.parse_optional_attr_list();
1430                    let names = self.parse_name_block()?;
1431                    if !self.consume(&Token::End) {
1432                        return Err("expected 'end' after enumeration".into());
1433                    }
1434                    members.push(ClassMember::Enumeration {
1435                        attributes: attrs,
1436                        names,
1437                    });
1438                }
1439                Some(Token::Arguments) => {
1440                    self.pos += 1;
1441                    let attrs = self.parse_optional_attr_list();
1442                    let names = self.parse_name_block()?;
1443                    if !self.consume(&Token::End) {
1444                        return Err("expected 'end' after arguments".into());
1445                    }
1446                    members.push(ClassMember::Arguments {
1447                        attributes: attrs,
1448                        names,
1449                    });
1450                }
1451                Some(Token::End) => {
1452                    self.pos += 1;
1453                    break;
1454                }
1455                _ => break,
1456            }
1457        }
1458        Ok(Stmt::ClassDef {
1459            name,
1460            super_class,
1461            members,
1462        })
1463    }
1464
1465    fn parse_name_block(&mut self) -> Result<Vec<String>, String> {
1466        let mut names = Vec::new();
1467        while let Some(tok) = self.peek_token() {
1468            if matches!(tok, Token::End) {
1469                break;
1470            }
1471            if self.consume(&Token::Semicolon)
1472                || self.consume(&Token::Comma)
1473                || self.consume(&Token::Newline)
1474            {
1475                continue;
1476            }
1477            if let Some(Token::Ident) = self.peek_token() {
1478                names.push(self.expect_ident()?);
1479            } else {
1480                break;
1481            }
1482        }
1483        Ok(names)
1484    }
1485
1486    fn parse_properties_names_block(&mut self) -> Result<Vec<String>, String> {
1487        // Accept identifiers with optional default assignment: name, name = expr
1488        let mut names = Vec::new();
1489        while let Some(tok) = self.peek_token() {
1490            if matches!(tok, Token::End) {
1491                break;
1492            }
1493            if self.consume(&Token::Semicolon)
1494                || self.consume(&Token::Comma)
1495                || self.consume(&Token::Newline)
1496            {
1497                continue;
1498            }
1499            if let Some(Token::Ident) = self.peek_token() {
1500                names.push(self.expect_ident()?);
1501                // Optional default initializer: skip over `= expr` syntactically
1502                if self.consume(&Token::Assign) {
1503                    // Parse and discard expression to keep the grammar permissive; initializer is HIR/semantics concern
1504                    let _ = self.parse_expr().map_err(|e| e.message)?;
1505                }
1506            } else {
1507                break;
1508            }
1509        }
1510        Ok(names)
1511    }
1512
1513    fn parse_optional_attr_list(&mut self) -> Vec<Attr> {
1514        // Minimal parsing of attribute lists: (Attr, Attr=Value, ...)
1515        let mut attrs: Vec<Attr> = Vec::new();
1516        if !self.consume(&Token::LParen) {
1517            return attrs;
1518        }
1519        loop {
1520            if self.consume(&Token::RParen) {
1521                break;
1522            }
1523            match self.peek_token() {
1524                Some(Token::Ident) => {
1525                    let name = self.expect_ident().unwrap_or_else(|_| "".to_string());
1526                    let mut value: Option<String> = None;
1527                    if self.consume(&Token::Assign) {
1528                        // Value could be ident, string or number; capture raw lexeme
1529                        if let Some(tok) = self.next() {
1530                            value = Some(tok.lexeme);
1531                        }
1532                    }
1533                    attrs.push(Attr { name, value });
1534                    let _ = self.consume(&Token::Comma);
1535                }
1536                Some(Token::Comma) => {
1537                    self.pos += 1;
1538                }
1539                Some(Token::RParen) => {
1540                    self.pos += 1;
1541                    break;
1542                }
1543                Some(_) => {
1544                    self.pos += 1;
1545                }
1546                None => {
1547                    break;
1548                }
1549            }
1550        }
1551        attrs
1552    }
1553
1554    fn parse_global(&mut self) -> Result<Stmt, String> {
1555        self.consume(&Token::Global);
1556        let mut names = Vec::new();
1557        names.push(self.expect_ident()?);
1558        loop {
1559            if self.consume(&Token::Comma) {
1560                names.push(self.expect_ident()?);
1561                continue;
1562            }
1563            if self.peek_token() == Some(&Token::Ident) {
1564                names.push(self.expect_ident()?);
1565                continue;
1566            }
1567            break;
1568        }
1569        Ok(Stmt::Global(names))
1570    }
1571
1572    fn parse_persistent(&mut self) -> Result<Stmt, String> {
1573        self.consume(&Token::Persistent);
1574        let mut names = Vec::new();
1575        names.push(self.expect_ident()?);
1576        loop {
1577            if self.consume(&Token::Comma) {
1578                names.push(self.expect_ident()?);
1579                continue;
1580            }
1581            if self.peek_token() == Some(&Token::Ident) {
1582                names.push(self.expect_ident()?);
1583                continue;
1584            }
1585            break;
1586        }
1587        Ok(Stmt::Persistent(names))
1588    }
1589
1590    fn try_parse_multi_assign(&mut self) -> Result<Stmt, String> {
1591        if !self.consume(&Token::LBracket) {
1592            return Err("not a multi-assign".into());
1593        }
1594        let mut names = Vec::new();
1595        names.push(self.expect_ident_or_tilde()?);
1596        while self.consume(&Token::Comma) {
1597            names.push(self.expect_ident_or_tilde()?);
1598        }
1599        if !self.consume(&Token::RBracket) {
1600            return Err("expected ']'".into());
1601        }
1602        if !self.consume(&Token::Assign) {
1603            return Err("expected '='".into());
1604        }
1605        let rhs = self.parse_expr().map_err(|e| e.message)?;
1606        Ok(Stmt::MultiAssign(names, rhs, false))
1607    }
1608
1609    fn parse_qualified_name(&mut self) -> Result<String, String> {
1610        let mut parts = Vec::new();
1611        parts.push(self.expect_ident()?);
1612        while self.consume(&Token::Dot) {
1613            parts.push(self.expect_ident()?);
1614        }
1615        Ok(parts.join("."))
1616    }
1617
1618    fn expect_ident(&mut self) -> Result<String, String> {
1619        match self.next() {
1620            Some(TokenInfo {
1621                token: Token::Ident,
1622                lexeme,
1623                ..
1624            }) => Ok(lexeme),
1625            _ => Err("expected identifier".into()),
1626        }
1627    }
1628
1629    fn expect_ident_or_tilde(&mut self) -> Result<String, String> {
1630        match self.next() {
1631            Some(TokenInfo {
1632                token: Token::Ident,
1633                lexeme,
1634                ..
1635            }) => Ok(lexeme),
1636            Some(TokenInfo {
1637                token: Token::Tilde,
1638                ..
1639            }) => Ok("~".to_string()),
1640            _ => Err("expected identifier or '~'".into()),
1641        }
1642    }
1643
1644    fn peek_token(&self) -> Option<&Token> {
1645        self.tokens.get(self.pos).map(|t| &t.token)
1646    }
1647
1648    fn peek_token_at(&self, offset: usize) -> Option<&Token> {
1649        self.tokens.get(self.pos + offset).map(|t| &t.token)
1650    }
1651
1652    fn next(&mut self) -> Option<TokenInfo> {
1653        if self.pos < self.tokens.len() {
1654            let info = self.tokens[self.pos].clone();
1655            self.pos += 1;
1656            Some(info)
1657        } else {
1658            None
1659        }
1660    }
1661
1662    fn consume(&mut self, t: &Token) -> bool {
1663        if self.peek_token() == Some(t) {
1664            self.pos += 1;
1665            true
1666        } else {
1667            false
1668        }
1669    }
1670}