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