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