Skip to main content

runmat_parser/
lib.rs

1use runmat_lexer::Token;
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
5#[serde(rename_all = "snake_case")]
6pub enum CompatMode {
7    #[default]
8    Matlab,
9    Strict,
10}
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
13pub struct ParserOptions {
14    #[serde(default)]
15    pub compat_mode: CompatMode,
16}
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
19pub struct Span {
20    pub start: usize,
21    pub end: usize,
22}
23
24impl Default for ParserOptions {
25    fn default() -> Self {
26        Self {
27            compat_mode: CompatMode::Matlab,
28        }
29    }
30}
31
32impl ParserOptions {
33    pub fn new(compat_mode: CompatMode) -> Self {
34        Self { compat_mode }
35    }
36}
37
38#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
39pub enum Expr {
40    Number(String, Span),
41    String(String, Span),
42    Ident(String, Span),
43    EndKeyword(Span), // 'end' used in indexing contexts
44    Unary(UnOp, Box<Expr>, Span),
45    Binary(Box<Expr>, BinOp, Box<Expr>, Span),
46    Tensor(Vec<Vec<Expr>>, Span),
47    Cell(Vec<Vec<Expr>>, Span),
48    Index(Box<Expr>, Vec<Expr>, Span),
49    IndexCell(Box<Expr>, Vec<Expr>, Span),
50    Range(Box<Expr>, Option<Box<Expr>>, Box<Expr>, Span),
51    Colon(Span),
52    FuncCall(String, Vec<Expr>, Span),
53    Member(Box<Expr>, String, Span),
54    // Dynamic field: s.(expr)
55    MemberDynamic(Box<Expr>, Box<Expr>, Span),
56    MethodCall(Box<Expr>, String, Vec<Expr>, Span),
57    AnonFunc {
58        params: Vec<String>,
59        body: Box<Expr>,
60        span: Span,
61    },
62    FuncHandle(String, Span),
63    MetaClass(String, Span),
64}
65
66impl Expr {
67    pub fn span(&self) -> Span {
68        match self {
69            Expr::Number(_, span)
70            | Expr::String(_, span)
71            | Expr::Ident(_, span)
72            | Expr::EndKeyword(span)
73            | Expr::Unary(_, _, span)
74            | Expr::Binary(_, _, _, span)
75            | Expr::Tensor(_, span)
76            | Expr::Cell(_, span)
77            | Expr::Index(_, _, span)
78            | Expr::IndexCell(_, _, span)
79            | Expr::Range(_, _, _, span)
80            | Expr::Colon(span)
81            | Expr::FuncCall(_, _, span)
82            | Expr::Member(_, _, span)
83            | Expr::MemberDynamic(_, _, span)
84            | Expr::MethodCall(_, _, _, span)
85            | Expr::FuncHandle(_, span)
86            | Expr::MetaClass(_, span) => *span,
87            Expr::AnonFunc { span, .. } => *span,
88        }
89    }
90
91    pub fn with_span(self, span: Span) -> Expr {
92        match self {
93            Expr::Number(value, _) => Expr::Number(value, span),
94            Expr::String(value, _) => Expr::String(value, span),
95            Expr::Ident(value, _) => Expr::Ident(value, span),
96            Expr::EndKeyword(_) => Expr::EndKeyword(span),
97            Expr::Unary(op, expr, _) => Expr::Unary(op, expr, span),
98            Expr::Binary(lhs, op, rhs, _) => Expr::Binary(lhs, op, rhs, span),
99            Expr::Tensor(rows, _) => Expr::Tensor(rows, span),
100            Expr::Cell(rows, _) => Expr::Cell(rows, span),
101            Expr::Index(base, indices, _) => Expr::Index(base, indices, span),
102            Expr::IndexCell(base, indices, _) => Expr::IndexCell(base, indices, span),
103            Expr::Range(start, step, end, _) => Expr::Range(start, step, end, span),
104            Expr::Colon(_) => Expr::Colon(span),
105            Expr::FuncCall(name, args, _) => Expr::FuncCall(name, args, span),
106            Expr::Member(base, name, _) => Expr::Member(base, name, span),
107            Expr::MemberDynamic(base, name, _) => Expr::MemberDynamic(base, name, span),
108            Expr::MethodCall(base, name, args, _) => Expr::MethodCall(base, name, args, span),
109            Expr::AnonFunc { params, body, .. } => Expr::AnonFunc { params, body, span },
110            Expr::FuncHandle(name, _) => Expr::FuncHandle(name, span),
111            Expr::MetaClass(name, _) => Expr::MetaClass(name, span),
112        }
113    }
114}
115
116#[derive(Debug, PartialEq, Copy, Clone, Serialize, Deserialize)]
117pub enum BinOp {
118    Add,
119    Sub,
120    Mul,
121    Div,
122    Pow,
123    LeftDiv,
124    Colon,
125    // Element-wise operations
126    ElemMul,     // .*
127    ElemDiv,     // ./
128    ElemPow,     // .^
129    ElemLeftDiv, // .\
130    // Logical operations
131    AndAnd, // && (short-circuit)
132    OrOr,   // || (short-circuit)
133    BitAnd, // &
134    BitOr,  // |
135    // Comparison operations
136    Equal,        // ==
137    NotEqual,     // ~=
138    Less,         // <
139    LessEqual,    // <=
140    Greater,      // >
141    GreaterEqual, // >=
142}
143
144#[derive(Debug, PartialEq, Copy, Clone, Serialize, Deserialize)]
145pub enum UnOp {
146    Plus,
147    Minus,
148    Transpose,
149    NonConjugateTranspose,
150    Not, // ~
151}
152
153#[derive(Debug, PartialEq)]
154pub enum Stmt {
155    ExprStmt(Expr, bool, Span), // Expression and whether it's semicolon-terminated (suppressed)
156    Assign(String, Expr, bool, Span), // Variable, Expression, and whether it's semicolon-terminated (suppressed)
157    MultiAssign(Vec<String>, Expr, bool, Span),
158    AssignLValue(LValue, Expr, bool, Span),
159    If {
160        cond: Expr,
161        then_body: Vec<Stmt>,
162        elseif_blocks: Vec<(Expr, Vec<Stmt>)>,
163        else_body: Option<Vec<Stmt>>,
164        span: Span,
165    },
166    While {
167        cond: Expr,
168        body: Vec<Stmt>,
169        span: Span,
170    },
171    For {
172        var: String,
173        expr: Expr,
174        body: Vec<Stmt>,
175        span: Span,
176    },
177    Switch {
178        expr: Expr,
179        cases: Vec<(Expr, Vec<Stmt>)>,
180        otherwise: Option<Vec<Stmt>>,
181        span: Span,
182    },
183    TryCatch {
184        try_body: Vec<Stmt>,
185        catch_var: Option<String>,
186        catch_body: Vec<Stmt>,
187        span: Span,
188    },
189    Global(Vec<String>, Span),
190    Persistent(Vec<String>, Span),
191    Break(Span),
192    Continue(Span),
193    Return(Span),
194    Function {
195        name: String,
196        params: Vec<String>,
197        outputs: Vec<String>,
198        body: Vec<Stmt>,
199        span: Span,
200    },
201    Import {
202        path: Vec<String>,
203        wildcard: bool,
204        span: Span,
205    },
206    ClassDef {
207        name: String,
208        super_class: Option<String>,
209        members: Vec<ClassMember>,
210        span: Span,
211    },
212}
213
214impl Stmt {
215    pub fn span(&self) -> Span {
216        match self {
217            Stmt::ExprStmt(_, _, span)
218            | Stmt::Assign(_, _, _, span)
219            | Stmt::MultiAssign(_, _, _, span)
220            | Stmt::AssignLValue(_, _, _, span)
221            | Stmt::Global(_, span)
222            | Stmt::Persistent(_, span)
223            | Stmt::Break(span)
224            | Stmt::Continue(span)
225            | Stmt::Return(span) => *span,
226            Stmt::If { span, .. }
227            | Stmt::While { span, .. }
228            | Stmt::For { span, .. }
229            | Stmt::Switch { span, .. }
230            | Stmt::TryCatch { span, .. }
231            | Stmt::Function { span, .. }
232            | Stmt::Import { span, .. }
233            | Stmt::ClassDef { span, .. } => *span,
234        }
235    }
236}
237
238#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
239pub enum LValue {
240    Var(String),
241    Member(Box<Expr>, String),
242    MemberDynamic(Box<Expr>, Box<Expr>),
243    Index(Box<Expr>, Vec<Expr>),
244    IndexCell(Box<Expr>, Vec<Expr>),
245}
246
247#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
248pub struct Attr {
249    pub name: String,
250    pub value: Option<String>,
251}
252
253#[derive(Debug, PartialEq)]
254pub enum ClassMember {
255    Properties {
256        attributes: Vec<Attr>,
257        names: Vec<String>,
258    },
259    Methods {
260        attributes: Vec<Attr>,
261        body: Vec<Stmt>,
262    },
263    Events {
264        attributes: Vec<Attr>,
265        names: Vec<String>,
266    },
267    Enumeration {
268        attributes: Vec<Attr>,
269        names: Vec<String>,
270    },
271    Arguments {
272        attributes: Vec<Attr>,
273        names: Vec<String>,
274    },
275}
276
277#[derive(Debug, PartialEq)]
278pub struct Program {
279    pub body: Vec<Stmt>,
280}
281
282#[derive(Clone)]
283struct TokenInfo {
284    token: Token,
285    lexeme: String,
286    position: usize,
287    end: usize,
288}
289
290#[derive(Debug)]
291pub struct SyntaxError {
292    pub message: String,
293    pub position: usize,
294    pub found_token: Option<String>,
295    pub expected: Option<String>,
296}
297
298pub fn parse(input: &str) -> Result<Program, SyntaxError> {
299    parse_with_options(input, ParserOptions::default())
300}
301
302pub fn parse_with_options(input: &str, options: ParserOptions) -> Result<Program, SyntaxError> {
303    use runmat_lexer::tokenize_detailed;
304
305    let toks = tokenize_detailed(input);
306    let mut tokens = Vec::new();
307
308    for t in toks {
309        if matches!(t.token, Token::Error) {
310            return Err(SyntaxError {
311                message: format!("Invalid token: '{}'", t.lexeme),
312                position: t.start,
313                found_token: Some(t.lexeme),
314                expected: None,
315            });
316        }
317        // Skip layout-only tokens from lexing
318        if matches!(t.token, Token::Ellipsis | Token::Section) {
319            continue;
320        }
321        tokens.push(TokenInfo {
322            token: t.token,
323            lexeme: t.lexeme,
324            position: t.start,
325            end: t.end,
326        });
327    }
328
329    let mut parser = Parser {
330        tokens,
331        pos: 0,
332        input: input.to_string(),
333        options,
334        in_matrix_expr: false,
335    };
336    parser.parse_program()
337}
338
339impl std::fmt::Display for SyntaxError {
340    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
341        write!(
342            f,
343            "Syntax error at position {}: {}",
344            self.position, self.message
345        )?;
346        if let Some(found) = &self.found_token {
347            write!(f, " (found: '{found}')")?;
348        }
349        if let Some(expected) = &self.expected {
350            write!(f, " (expected: {expected})")?;
351        }
352        Ok(())
353    }
354}
355
356impl std::error::Error for SyntaxError {}
357
358impl From<String> for SyntaxError {
359    fn from(value: String) -> Self {
360        SyntaxError {
361            message: value,
362            position: 0,
363            found_token: None,
364            expected: None,
365        }
366    }
367}
368
369impl From<SyntaxError> for String {
370    fn from(error: SyntaxError) -> Self {
371        error.to_string()
372    }
373}
374
375struct Parser {
376    tokens: Vec<TokenInfo>,
377    pos: usize,
378    input: String,
379    options: ParserOptions,
380    in_matrix_expr: bool,
381}
382
383#[derive(Clone, Copy)]
384struct CommandVerb {
385    name: &'static str,
386    arg_kind: CommandArgKind,
387}
388
389#[derive(Clone, Copy)]
390enum CommandArgKind {
391    Keyword {
392        allowed: &'static [&'static str],
393        optional: bool,
394    },
395    Any,
396}
397
398const COMMAND_VERBS: &[CommandVerb] = &[
399    CommandVerb {
400        name: "hold",
401        arg_kind: CommandArgKind::Keyword {
402            allowed: &["on", "off", "all", "reset"],
403            optional: false,
404        },
405    },
406    CommandVerb {
407        name: "grid",
408        arg_kind: CommandArgKind::Keyword {
409            allowed: &["on", "off"],
410            optional: false,
411        },
412    },
413    CommandVerb {
414        name: "box",
415        arg_kind: CommandArgKind::Keyword {
416            allowed: &["on", "off"],
417            optional: false,
418        },
419    },
420    CommandVerb {
421        name: "axis",
422        arg_kind: CommandArgKind::Keyword {
423            allowed: &["auto", "manual", "tight", "equal", "ij", "xy"],
424            optional: false,
425        },
426    },
427    CommandVerb {
428        name: "shading",
429        arg_kind: CommandArgKind::Keyword {
430            allowed: &["flat", "interp", "faceted"],
431            optional: false,
432        },
433    },
434    CommandVerb {
435        name: "colormap",
436        arg_kind: CommandArgKind::Keyword {
437            allowed: &[
438                "parula", "jet", "hsv", "hot", "cool", "spring", "summer", "autumn", "winter",
439                "gray", "bone", "copper", "pink",
440            ],
441            optional: false,
442        },
443    },
444    CommandVerb {
445        name: "colorbar",
446        arg_kind: CommandArgKind::Keyword {
447            allowed: &["on", "off"],
448            optional: true,
449        },
450    },
451    CommandVerb {
452        name: "figure",
453        arg_kind: CommandArgKind::Any,
454    },
455    CommandVerb {
456        name: "subplot",
457        arg_kind: CommandArgKind::Any,
458    },
459    CommandVerb {
460        name: "clf",
461        arg_kind: CommandArgKind::Any,
462    },
463    CommandVerb {
464        name: "cla",
465        arg_kind: CommandArgKind::Any,
466    },
467    CommandVerb {
468        name: "close",
469        arg_kind: CommandArgKind::Any,
470    },
471];
472
473impl Parser {
474    fn skip_newlines(&mut self) {
475        while self.consume(&Token::Newline) {}
476    }
477
478    fn tokens_adjacent(&self, left: usize, right: usize) -> bool {
479        match (self.tokens.get(left), self.tokens.get(right)) {
480            (Some(a), Some(b)) => a.end == b.position,
481            _ => false,
482        }
483    }
484
485    fn span_from(&self, start: usize, end: usize) -> Span {
486        Span { start, end }
487    }
488
489    fn span_between(&self, start: Span, end: Span) -> Span {
490        Span {
491            start: start.start,
492            end: end.end,
493        }
494    }
495
496    fn last_token_end(&self) -> usize {
497        self.tokens
498            .get(self.pos.saturating_sub(1))
499            .map(|t| t.end)
500            .unwrap_or(self.input.len())
501    }
502
503    fn make_binary(&self, left: Expr, op: BinOp, right: Expr) -> Expr {
504        let span = self.span_between(left.span(), right.span());
505        Expr::Binary(Box::new(left), op, Box::new(right), span)
506    }
507
508    fn make_unary(&self, op: UnOp, operand: Expr, op_start: usize) -> Expr {
509        let span = self.span_from(op_start, operand.span().end);
510        Expr::Unary(op, Box::new(operand), span)
511    }
512
513    fn is_simple_assignment_ahead(&self) -> bool {
514        // Heuristic: at statement start, if we see Ident ... '=' before a terminator, treat as assignment
515        self.peek_token() == Some(&Token::Ident) && self.peek_token_at(1) == Some(&Token::Assign)
516    }
517    fn parse_program(&mut self) -> Result<Program, SyntaxError> {
518        let mut body = Vec::new();
519        while self.pos < self.tokens.len() {
520            if self.consume(&Token::Semicolon)
521                || self.consume(&Token::Comma)
522                || self.consume(&Token::Newline)
523            {
524                continue;
525            }
526            body.push(self.parse_stmt_with_semicolon()?);
527        }
528        Ok(Program { body })
529    }
530
531    fn error(&self, message: &str) -> SyntaxError {
532        SyntaxError {
533            message: message.to_string(),
534            position: self.current_position(),
535            found_token: self.peek().map(|t| t.lexeme.clone()),
536            expected: None,
537        }
538    }
539
540    fn error_with_expected(&self, message: &str, expected: &str) -> SyntaxError {
541        SyntaxError {
542            message: message.to_string(),
543            position: self.current_position(),
544            found_token: self.peek().map(|t| t.lexeme.clone()),
545            expected: Some(expected.to_string()),
546        }
547    }
548
549    fn parse_stmt_with_semicolon(&mut self) -> Result<Stmt, SyntaxError> {
550        let stmt = self.parse_stmt()?;
551        let is_semicolon_terminated = self.consume(&Token::Semicolon);
552
553        // Expression statements: semicolon indicates output suppression.
554        // Assignments/lvalues are now suppressed whenever a semicolon is present, even at EOF.
555        match stmt {
556            Stmt::ExprStmt(expr, _, span) => {
557                Ok(Stmt::ExprStmt(expr, is_semicolon_terminated, span))
558            }
559            Stmt::Assign(name, expr, _, span) => {
560                Ok(Stmt::Assign(name, expr, is_semicolon_terminated, span))
561            }
562            Stmt::MultiAssign(names, expr, _, span) => Ok(Stmt::MultiAssign(
563                names,
564                expr,
565                is_semicolon_terminated,
566                span,
567            )),
568            Stmt::AssignLValue(lv, expr, _, span) => {
569                Ok(Stmt::AssignLValue(lv, expr, is_semicolon_terminated, span))
570            }
571            other => Ok(other),
572        }
573    }
574
575    fn parse_stmt(&mut self) -> Result<Stmt, SyntaxError> {
576        match self.peek_token() {
577            Some(Token::If) => self.parse_if().map_err(|e| e.into()),
578            Some(Token::For) => self.parse_for().map_err(|e| e.into()),
579            Some(Token::While) => self.parse_while().map_err(|e| e.into()),
580            Some(Token::Switch) => self.parse_switch().map_err(|e| e.into()),
581            Some(Token::Try) => self.parse_try_catch().map_err(|e| e.into()),
582            Some(Token::Import) => self.parse_import().map_err(|e| e.into()),
583            Some(Token::ClassDef) => self.parse_classdef().map_err(|e| e.into()),
584            Some(Token::Global) => self.parse_global().map_err(|e| e.into()),
585            Some(Token::Persistent) => self.parse_persistent().map_err(|e| e.into()),
586            Some(Token::Break) => {
587                let token = &self.tokens[self.pos];
588                self.pos += 1;
589                Ok(Stmt::Break(self.span_from(token.position, token.end)))
590            }
591            Some(Token::Continue) => {
592                let token = &self.tokens[self.pos];
593                self.pos += 1;
594                Ok(Stmt::Continue(self.span_from(token.position, token.end)))
595            }
596            Some(Token::Return) => {
597                let token = &self.tokens[self.pos];
598                self.pos += 1;
599                Ok(Stmt::Return(self.span_from(token.position, token.end)))
600            }
601            Some(Token::Function) => self.parse_function().map_err(|e| e.into()),
602            // Multi-assign like [a,b] = f()
603            Some(Token::LBracket) => {
604                if matches!(self.peek_token_at(1), Some(Token::Ident | Token::Tilde)) {
605                    match self.try_parse_multi_assign() {
606                        Ok(stmt) => Ok(stmt),
607                        Err(msg) => Err(self.error(&msg)),
608                    }
609                } else {
610                    let expr = self.parse_expr()?;
611                    let span = expr.span();
612                    Ok(Stmt::ExprStmt(expr, false, span))
613                }
614            }
615            _ => {
616                if self.peek_token() == Some(&Token::Ident)
617                    && self.peek_token_at(1) == Some(&Token::Assign)
618                {
619                    let name_token = self
620                        .next()
621                        .ok_or_else(|| self.error("expected identifier"))?;
622                    if !self.consume(&Token::Assign) {
623                        return Err(self.error_with_expected("expected assignment operator", "'='"));
624                    }
625                    let expr = self.parse_expr()?;
626                    let span = self.span_from(name_token.position, expr.span().end);
627                    Ok(Stmt::Assign(name_token.lexeme, expr, false, span))
628                } else if self.is_simple_assignment_ahead() {
629                    // Fallback: treat as simple assignment if '=' appears before terminator
630                    let name = self.expect_ident().map_err(|e| self.error(&e))?;
631                    let start = self.tokens[self.pos.saturating_sub(1)].position;
632                    if !self.consume(&Token::Assign) {
633                        return Err(self.error_with_expected("expected assignment operator", "'='"));
634                    }
635                    let expr = self.parse_expr()?;
636                    let span = self.span_from(start, expr.span().end);
637                    Ok(Stmt::Assign(name, expr, false, span))
638                } else if self.peek_token() == Some(&Token::Ident) {
639                    // First, try complex lvalue assignment starting from an identifier: A(1)=x, A{1}=x, s.f=x, s.(n)=x
640                    if let Some(lv) = self.try_parse_lvalue_assign()? {
641                        return Ok(lv);
642                    }
643                    // Command-form at statement start if it looks like a sequence of simple arguments
644                    // and is not immediately followed by indexing/member syntax.
645                    if self.can_start_command_form() {
646                        if self.options.compat_mode == CompatMode::Strict {
647                            return Err(self.error(
648                                "Command syntax is disabled in strict compatibility mode; call functions with parentheses.",
649                            ));
650                        }
651                        let name_token = self.next().unwrap();
652                        let mut args = self.parse_command_args();
653                        if let Some(command) = self.lookup_command(&name_token.lexeme) {
654                            self.normalize_command_args(command, &mut args[..])?;
655                        }
656                        let end = self.last_token_end();
657                        let span = self.span_from(name_token.position, end);
658                        Ok(Stmt::ExprStmt(
659                            Expr::FuncCall(name_token.lexeme, args, span),
660                            false,
661                            span,
662                        ))
663                    } else {
664                        // If we see Ident <space> Ident immediately followed by postfix opener,
665                        // this is an ambiguous adjacency (e.g., "foo b(1)"). Emit a targeted error.
666                        if matches!(self.peek_token_at(1), Some(Token::Ident))
667                            && matches!(
668                                self.peek_token_at(2),
669                                Some(
670                                    Token::LParen
671                                        | Token::Dot
672                                        | Token::LBracket
673                                        | Token::LBrace
674                                        | Token::Transpose
675                                )
676                            )
677                        {
678                            return Err(self.error(
679                                "Unexpected adjacency: interpret as function call? Use parentheses (e.g., foo(b(1))).",
680                            ));
681                        }
682                        let expr = self.parse_expr()?;
683                        let span = expr.span();
684                        Ok(Stmt::ExprStmt(expr, false, span))
685                    }
686                } else {
687                    let expr = self.parse_expr()?;
688                    let span = expr.span();
689                    Ok(Stmt::ExprStmt(expr, false, span))
690                }
691            }
692        }
693    }
694
695    fn can_start_command_form(&self) -> bool {
696        // At entry, peek_token() is Some(Ident) for callee
697        let Some(current) = self.tokens.get(self.pos) else {
698            return false;
699        };
700        let verb = current.lexeme.as_str();
701        let command = self.lookup_command(verb);
702        let zero_arg_allowed = matches!(
703            command,
704            Some(CommandVerb {
705                arg_kind: CommandArgKind::Any,
706                ..
707            })
708        ) || matches!(
709            command,
710            Some(CommandVerb {
711                arg_kind: CommandArgKind::Keyword { optional: true, .. },
712                ..
713            })
714        );
715
716        let mut i = 1;
717        let mut saw_arg = false;
718        while matches!(
719            self.peek_token_at(i),
720            Some(Token::Newline | Token::Ellipsis)
721        ) {
722            i += 1;
723        }
724        // At least one simple arg must follow
725        if !matches!(
726            self.peek_token_at(i),
727            Some(Token::Ident | Token::Integer | Token::Float | Token::Str | Token::End)
728        ) {
729            if !zero_arg_allowed {
730                return false;
731            }
732        } else {
733            saw_arg = true;
734        }
735        // Consume all contiguous simple args
736        loop {
737            match self.peek_token_at(i) {
738                Some(Token::Ident | Token::Integer | Token::Float | Token::Str | Token::End) => {
739                    saw_arg = true;
740                    i += 1;
741                }
742                Some(Token::Newline | Token::Ellipsis) => {
743                    i += 1;
744                }
745                _ => break,
746            }
747        }
748        if !saw_arg && !zero_arg_allowed {
749            return false;
750        }
751        // If the next token begins indexing/member or other expression syntax, do not use command-form
752        match self.peek_token_at(i) {
753            Some(Token::LParen)
754            | Some(Token::Dot)
755            | Some(Token::LBracket)
756            | Some(Token::LBrace)
757            | Some(Token::Transpose) => false,
758            // If next token is assignment, also not a command-form (would be ambiguous)
759            Some(Token::Assign) => false,
760            // End of statement is okay for command-form
761            None | Some(Token::Semicolon) | Some(Token::Comma) | Some(Token::Newline) => true,
762            // Otherwise conservatively allow
763            _ => true,
764        }
765    }
766
767    fn parse_command_args(&mut self) -> Vec<Expr> {
768        let mut args = Vec::new();
769        loop {
770            if self.consume(&Token::Newline) {
771                continue;
772            }
773            if self.consume(&Token::Ellipsis) {
774                continue;
775            }
776            match self.peek_token() {
777                Some(Token::Ident) => {
778                    let token = self.next().unwrap();
779                    let span = self.span_from(token.position, token.end);
780                    args.push(Expr::Ident(token.lexeme, span));
781                }
782                // In command-form, accept 'end' as a literal identifier token for compatibility
783                Some(Token::End) => {
784                    let token = &self.tokens[self.pos];
785                    self.pos += 1;
786                    let span = self.span_from(token.position, token.end);
787                    args.push(Expr::Ident("end".to_string(), span));
788                }
789                Some(Token::Integer) | Some(Token::Float) => {
790                    let token = self.next().unwrap();
791                    let span = self.span_from(token.position, token.end);
792                    args.push(Expr::Number(token.lexeme, span));
793                }
794                Some(Token::Str) => {
795                    let token = self.next().unwrap();
796                    let span = self.span_from(token.position, token.end);
797                    args.push(Expr::String(token.lexeme, span));
798                }
799                // Stop on tokens that would start normal expression syntax
800                Some(Token::Slash)
801                | Some(Token::Star)
802                | Some(Token::Backslash)
803                | Some(Token::Plus)
804                | Some(Token::Minus)
805                | Some(Token::LParen)
806                | Some(Token::Dot)
807                | Some(Token::LBracket)
808                | Some(Token::LBrace)
809                | Some(Token::Transpose) => break,
810                _ => break,
811            }
812        }
813        args
814    }
815
816    fn lookup_command(&self, name: &str) -> Option<&'static CommandVerb> {
817        COMMAND_VERBS
818            .iter()
819            .find(|cmd| cmd.name.eq_ignore_ascii_case(name))
820    }
821
822    fn normalize_command_args(
823        &self,
824        command: &CommandVerb,
825        args: &mut [Expr],
826    ) -> Result<(), SyntaxError> {
827        match command.arg_kind {
828            CommandArgKind::Keyword { allowed, optional } => {
829                if args.is_empty() {
830                    if optional {
831                        return Ok(());
832                    }
833                    return Err(self.error(&format!(
834                        "'{}' command syntax requires an argument",
835                        command.name
836                    )));
837                }
838                if args.len() > 1 {
839                    return Err(self.error(&format!(
840                        "'{}' command syntax accepts only one argument",
841                        command.name
842                    )));
843                }
844                let keyword = extract_keyword(&args[0]).ok_or_else(|| {
845                    self.error(&format!(
846                        "'{}' command syntax expects a keyword argument",
847                        command.name
848                    ))
849                })?;
850                if allowed
851                    .iter()
852                    .any(|candidate| candidate.eq_ignore_ascii_case(&keyword))
853                {
854                    let span = args[0].span();
855                    args[0] = Expr::String(format!("\"{}\"", keyword), span);
856                } else {
857                    return Err(self.error(&format!(
858                        "'{}' command syntax does not support '{}'",
859                        command.name, keyword
860                    )));
861                }
862            }
863            CommandArgKind::Any => {
864                // Accept general expressions; no normalization needed.
865            }
866        }
867        Ok(())
868    }
869
870    fn try_parse_lvalue_assign(&mut self) -> Result<Option<Stmt>, SyntaxError> {
871        let save = self.pos;
872        // Parse potential LValue: Member/Index/IndexCell
873        let lvalue = if self.peek_token() == Some(&Token::Ident) {
874            // Start with primary
875            let base_token = self.next().unwrap();
876            let base_span = self.span_from(base_token.position, base_token.end);
877            let mut base = Expr::Ident(base_token.lexeme, base_span);
878            loop {
879                if self.consume(&Token::LParen) {
880                    let mut args = Vec::new();
881                    if !self.consume(&Token::RParen) {
882                        args.push(self.parse_expr()?);
883                        while self.consume(&Token::Comma) {
884                            args.push(self.parse_expr()?);
885                        }
886                        if !self.consume(&Token::RParen) {
887                            return Err(self.error_with_expected("expected ')' after indices", ")"));
888                        }
889                    }
890                    let end = self.last_token_end();
891                    let span = self.span_from(base.span().start, end);
892                    base = Expr::Index(Box::new(base), args, span);
893                } else if self.consume(&Token::LBracket) {
894                    let mut idxs = Vec::new();
895                    idxs.push(self.parse_expr()?);
896                    while self.consume(&Token::Comma) {
897                        idxs.push(self.parse_expr()?);
898                    }
899                    if !self.consume(&Token::RBracket) {
900                        return Err(self.error_with_expected("expected ']'", "]"));
901                    }
902                    let end = self.last_token_end();
903                    let span = self.span_from(base.span().start, end);
904                    base = Expr::Index(Box::new(base), idxs, span);
905                } else if self.consume(&Token::LBrace) {
906                    let mut idxs = Vec::new();
907                    idxs.push(self.parse_expr()?);
908                    while self.consume(&Token::Comma) {
909                        idxs.push(self.parse_expr()?);
910                    }
911                    if !self.consume(&Token::RBrace) {
912                        return Err(self.error_with_expected("expected '}'", "}"));
913                    }
914                    let end = self.last_token_end();
915                    let span = self.span_from(base.span().start, end);
916                    base = Expr::IndexCell(Box::new(base), idxs, span);
917                } else if self.peek_token() == Some(&Token::Dot) {
918                    // If this is .', it's a non-conjugate transpose, not a member
919                    if self.peek_token_at(1) == Some(&Token::Transpose) {
920                        break;
921                    }
922                    // If this is .+ or .-, it's an additive operator, not a member
923                    if self.peek_token_at(1) == Some(&Token::Plus)
924                        || self.peek_token_at(1) == Some(&Token::Minus)
925                    {
926                        break;
927                    }
928                    // Otherwise, member access
929                    self.pos += 1; // consume '.'
930                                   // Support dynamic field: .(expr) or static .ident
931                    if self.consume(&Token::LParen) {
932                        let name_expr = self.parse_expr()?;
933                        if !self.consume(&Token::RParen) {
934                            return Err(self.error_with_expected(
935                                "expected ')' after dynamic field expression",
936                                ")",
937                            ));
938                        }
939                        let end = self.last_token_end();
940                        let span = self.span_from(base.span().start, end);
941                        base = Expr::MemberDynamic(Box::new(base), Box::new(name_expr), span);
942                    } else {
943                        let name = self.expect_ident()?;
944                        let end = self.last_token_end();
945                        let span = self.span_from(base.span().start, end);
946                        base = Expr::Member(Box::new(base), name, span);
947                    }
948                } else {
949                    break;
950                }
951            }
952            base
953        } else {
954            self.pos = save;
955            return Ok(None);
956        };
957        if !self.consume(&Token::Assign) {
958            self.pos = save;
959            return Ok(None);
960        }
961        let rhs = self.parse_expr()?;
962        let stmt_span = self.span_between(lvalue.span(), rhs.span());
963        let stmt = match lvalue {
964            Expr::Member(b, name, _) => {
965                Stmt::AssignLValue(LValue::Member(b, name), rhs, false, stmt_span)
966            }
967            Expr::MemberDynamic(b, n, _) => {
968                Stmt::AssignLValue(LValue::MemberDynamic(b, n), rhs, false, stmt_span)
969            }
970            Expr::Index(b, idxs, _) => {
971                Stmt::AssignLValue(LValue::Index(b, idxs), rhs, false, stmt_span)
972            }
973            Expr::IndexCell(b, idxs, _) => {
974                Stmt::AssignLValue(LValue::IndexCell(b, idxs), rhs, false, stmt_span)
975            }
976            Expr::Ident(v, _) => Stmt::Assign(v, rhs, false, stmt_span),
977            _ => {
978                self.pos = save;
979                return Ok(None);
980            }
981        };
982        Ok(Some(stmt))
983    }
984
985    fn parse_expr(&mut self) -> Result<Expr, SyntaxError> {
986        self.parse_logical_or()
987    }
988
989    fn parse_logical_or(&mut self) -> Result<Expr, SyntaxError> {
990        let mut node = self.parse_logical_and()?;
991        while self.consume(&Token::OrOr) {
992            let rhs = self.parse_logical_and()?;
993            node = self.make_binary(node, BinOp::OrOr, rhs);
994        }
995        Ok(node)
996    }
997
998    fn parse_logical_and(&mut self) -> Result<Expr, SyntaxError> {
999        let mut node = self.parse_bitwise_or()?;
1000        while self.consume(&Token::AndAnd) {
1001            let rhs = self.parse_bitwise_or()?;
1002            node = self.make_binary(node, BinOp::AndAnd, rhs);
1003        }
1004        Ok(node)
1005    }
1006
1007    fn parse_bitwise_or(&mut self) -> Result<Expr, SyntaxError> {
1008        let mut node = self.parse_bitwise_and()?;
1009        while self.consume(&Token::Or) {
1010            let rhs = self.parse_bitwise_and()?;
1011            node = self.make_binary(node, BinOp::BitOr, rhs);
1012        }
1013        Ok(node)
1014    }
1015
1016    fn parse_bitwise_and(&mut self) -> Result<Expr, SyntaxError> {
1017        let mut node = self.parse_range()?;
1018        while self.consume(&Token::And) {
1019            let rhs = self.parse_range()?;
1020            node = self.make_binary(node, BinOp::BitAnd, rhs);
1021        }
1022        Ok(node)
1023    }
1024
1025    fn parse_range(&mut self) -> Result<Expr, SyntaxError> {
1026        let mut node = self.parse_comparison()?;
1027        if self.consume(&Token::Colon) {
1028            let mid = self.parse_comparison()?;
1029            if self.consume(&Token::Colon) {
1030                let end = self.parse_comparison()?;
1031                let span = self.span_between(node.span(), end.span());
1032                node = Expr::Range(Box::new(node), Some(Box::new(mid)), Box::new(end), span);
1033            } else {
1034                let span = self.span_between(node.span(), mid.span());
1035                node = Expr::Range(Box::new(node), None, Box::new(mid), span);
1036            }
1037        }
1038        Ok(node)
1039    }
1040
1041    fn parse_comparison(&mut self) -> Result<Expr, SyntaxError> {
1042        let mut node = self.parse_add_sub()?;
1043        loop {
1044            let op = match self.peek_token() {
1045                Some(Token::Equal) => BinOp::Equal,
1046                Some(Token::NotEqual) => BinOp::NotEqual,
1047                Some(Token::Less) => BinOp::Less,
1048                Some(Token::LessEqual) => BinOp::LessEqual,
1049                Some(Token::Greater) => BinOp::Greater,
1050                Some(Token::GreaterEqual) => BinOp::GreaterEqual,
1051                _ => break,
1052            };
1053            self.pos += 1; // consume op
1054            let rhs = self.parse_add_sub()?;
1055            node = self.make_binary(node, op, rhs);
1056        }
1057        Ok(node)
1058    }
1059
1060    fn parse_add_sub(&mut self) -> Result<Expr, String> {
1061        let mut node = self.parse_mul_div()?;
1062        loop {
1063            // In matrix expressions, `[1 +2]` (space before but not after) is element
1064            // separation yielding [1, 2], while `[1 + 2]` (space on both sides) is
1065            // addition yielding [3]. Break only when there's space before the operator
1066            // but the operator is adjacent to the next token.
1067            if self.in_matrix_expr
1068                && matches!(self.peek_token(), Some(Token::Plus | Token::Minus))
1069                && self.pos > 0
1070                && !self.tokens_adjacent(self.pos - 1, self.pos)
1071                && self.tokens_adjacent(self.pos, self.pos + 1)
1072            {
1073                let rhs_index = self.pos + 1;
1074                let rhs_is_imag_literal = self
1075                    .tokens
1076                    .get(rhs_index)
1077                    .map(|info| matches!(info.token, Token::Integer | Token::Float))
1078                    .unwrap_or(false)
1079                    && self
1080                        .tokens
1081                        .get(rhs_index + 1)
1082                        .map(|info| {
1083                            matches!(info.token, Token::Ident)
1084                                && (info.lexeme.eq_ignore_ascii_case("i")
1085                                    || info.lexeme.eq_ignore_ascii_case("j"))
1086                                && self.tokens_adjacent(rhs_index, rhs_index + 1)
1087                        })
1088                        .unwrap_or(false);
1089                if !rhs_is_imag_literal {
1090                    break;
1091                }
1092            }
1093            let op = if self.consume(&Token::Plus) {
1094                Some(BinOp::Add)
1095            } else if self.consume(&Token::Minus) {
1096                Some(BinOp::Sub)
1097            } else if self.peek_token() == Some(&Token::Dot)
1098                && (self.peek_token_at(1) == Some(&Token::Plus)
1099                    || self.peek_token_at(1) == Some(&Token::Minus))
1100            {
1101                // '.+' or '.-' tokenized as Dot then Plus/Minus; treat like Add/Sub
1102                // consume two tokens
1103                self.pos += 2;
1104                if self.tokens[self.pos - 1].token == Token::Plus {
1105                    Some(BinOp::Add)
1106                } else {
1107                    Some(BinOp::Sub)
1108                }
1109            } else {
1110                None
1111            };
1112            let Some(op) = op else { break };
1113            let rhs = self.parse_mul_div()?;
1114            node = self.make_binary(node, op, rhs);
1115        }
1116        Ok(node)
1117    }
1118
1119    fn parse_mul_div(&mut self) -> Result<Expr, String> {
1120        let mut node = self.parse_unary()?;
1121        loop {
1122            if self.peek_token() == Some(&Token::Ident) && self.pos > 0 {
1123                let prev = &self.tokens[self.pos - 1];
1124                let curr = &self.tokens[self.pos];
1125                let is_adjacent = self.tokens_adjacent(self.pos - 1, self.pos);
1126                let is_imag =
1127                    curr.lexeme.eq_ignore_ascii_case("i") || curr.lexeme.eq_ignore_ascii_case("j");
1128                if is_adjacent && is_imag && matches!(prev.token, Token::Integer | Token::Float) {
1129                    let token = self.next().unwrap();
1130                    let rhs = Expr::Ident(token.lexeme, self.span_from(token.position, token.end));
1131                    node = self.make_binary(node, BinOp::Mul, rhs);
1132                    continue;
1133                }
1134            }
1135            let op = match self.peek_token() {
1136                Some(Token::Star) => BinOp::Mul,
1137                Some(Token::DotStar) => BinOp::ElemMul,
1138                Some(Token::Slash) => BinOp::Div,
1139                Some(Token::DotSlash) => BinOp::ElemDiv,
1140                Some(Token::Backslash) => BinOp::LeftDiv,
1141                Some(Token::DotBackslash) => BinOp::ElemLeftDiv,
1142                _ => break,
1143            };
1144            self.pos += 1; // consume op
1145            let rhs = self.parse_unary()?;
1146            node = self.make_binary(node, op, rhs);
1147        }
1148        Ok(node)
1149    }
1150
1151    fn parse_pow(&mut self) -> Result<Expr, String> {
1152        let node = self.parse_postfix()?;
1153        if let Some(token) = self.peek_token() {
1154            let op = match token {
1155                Token::Caret => BinOp::Pow,
1156                Token::DotCaret => BinOp::ElemPow,
1157                _ => return Ok(node),
1158            };
1159            self.pos += 1; // consume
1160            let rhs = self.parse_pow()?; // right associative
1161            Ok(self.make_binary(node, op, rhs))
1162        } else {
1163            Ok(node)
1164        }
1165    }
1166
1167    fn parse_postfix_with_base(&mut self, mut expr: Expr) -> Result<Expr, String> {
1168        loop {
1169            if self.consume(&Token::LParen) {
1170                let start = expr.span().start;
1171                let mut args = Vec::new();
1172                if !self.consume(&Token::RParen) {
1173                    args.push(self.parse_expr()?);
1174                    while self.consume(&Token::Comma) {
1175                        args.push(self.parse_expr()?);
1176                    }
1177                    if !self.consume(&Token::RParen) {
1178                        return Err("expected ')' after arguments".into());
1179                    }
1180                }
1181                let end = self.last_token_end();
1182                let span = self.span_from(start, end);
1183                // Binder-based disambiguation:
1184                // If the callee is an identifier, defer call vs. index to HIR binding.
1185                // Parse as a function call now; HIR will rewrite to Index if a variable shadows the function.
1186                if let Expr::Ident(ref name, _) = expr {
1187                    expr = Expr::FuncCall(name.clone(), args, span);
1188                } else {
1189                    // For non-ident bases (e.g., X(1), (A+B)(1)), this is indexing.
1190                    expr = Expr::Index(Box::new(expr), args, span);
1191                }
1192            } else if self.consume(&Token::LBracket) {
1193                // Array indexing
1194                let start = expr.span().start;
1195                let mut indices = Vec::new();
1196                indices.push(self.parse_expr()?);
1197                while self.consume(&Token::Comma) {
1198                    indices.push(self.parse_expr()?);
1199                }
1200                if !self.consume(&Token::RBracket) {
1201                    return Err("expected ']'".into());
1202                }
1203                let end = self.last_token_end();
1204                let span = self.span_from(start, end);
1205                expr = Expr::Index(Box::new(expr), indices, span);
1206            } else if self.consume(&Token::LBrace) {
1207                // Cell content indexing
1208                let start = expr.span().start;
1209                let mut indices = Vec::new();
1210                indices.push(self.parse_expr()?);
1211                while self.consume(&Token::Comma) {
1212                    indices.push(self.parse_expr()?);
1213                }
1214                if !self.consume(&Token::RBrace) {
1215                    return Err("expected '}'".into());
1216                }
1217                let end = self.last_token_end();
1218                let span = self.span_from(start, end);
1219                expr = Expr::IndexCell(Box::new(expr), indices, span);
1220            } else if self.peek_token() == Some(&Token::Dot) {
1221                // Could be .', .+ , .- or member access
1222                if self.peek_token_at(1) == Some(&Token::Transpose) {
1223                    self.pos += 2; // '.' and '''
1224                    let end = self.last_token_end();
1225                    let span = self.span_from(expr.span().start, end);
1226                    expr = Expr::Unary(UnOp::NonConjugateTranspose, Box::new(expr), span);
1227                    continue;
1228                }
1229                if self.peek_token_at(1) == Some(&Token::Plus)
1230                    || self.peek_token_at(1) == Some(&Token::Minus)
1231                {
1232                    // '.+' or '.-' belong to additive level; stop postfix loop
1233                    break;
1234                }
1235                // Otherwise, member access
1236                self.pos += 1; // consume '.'
1237                let name_token = match self.next() {
1238                    Some(TokenInfo {
1239                        token: Token::Ident,
1240                        lexeme,
1241                        position,
1242                        end,
1243                    }) => (lexeme, position, end),
1244                    _ => return Err("expected member name after '.'".into()),
1245                };
1246                if self.consume(&Token::LParen) {
1247                    let mut args = Vec::new();
1248                    if !self.consume(&Token::RParen) {
1249                        args.push(self.parse_expr()?);
1250                        while self.consume(&Token::Comma) {
1251                            args.push(self.parse_expr()?);
1252                        }
1253                        if !self.consume(&Token::RParen) {
1254                            return Err("expected ')' after method arguments".into());
1255                        }
1256                    }
1257                    let end = self.last_token_end();
1258                    let span = self.span_from(expr.span().start, end);
1259                    expr = Expr::MethodCall(Box::new(expr), name_token.0, args, span);
1260                } else {
1261                    let span = self.span_from(expr.span().start, name_token.2);
1262                    expr = Expr::Member(Box::new(expr), name_token.0, span);
1263                }
1264            } else if self.consume(&Token::Transpose) {
1265                // Matrix transpose (postfix operator)
1266                let end = self.last_token_end();
1267                let span = self.span_from(expr.span().start, end);
1268                expr = Expr::Unary(UnOp::Transpose, Box::new(expr), span);
1269            } else {
1270                break;
1271            }
1272        }
1273        Ok(expr)
1274    }
1275
1276    fn parse_postfix(&mut self) -> Result<Expr, String> {
1277        let expr = self.parse_primary()?;
1278        self.parse_postfix_with_base(expr)
1279    }
1280
1281    fn parse_unary(&mut self) -> Result<Expr, String> {
1282        if self.peek_token() == Some(&Token::Plus) {
1283            let start = self.tokens[self.pos].position;
1284            self.pos += 1;
1285            let expr = self.parse_unary()?;
1286            Ok(self.make_unary(UnOp::Plus, expr, start))
1287        } else if self.peek_token() == Some(&Token::Minus) {
1288            let start = self.tokens[self.pos].position;
1289            self.pos += 1;
1290            let expr = self.parse_unary()?;
1291            Ok(self.make_unary(UnOp::Minus, expr, start))
1292        } else if self.peek_token() == Some(&Token::Tilde) {
1293            let start = self.tokens[self.pos].position;
1294            self.pos += 1;
1295            let expr = self.parse_unary()?;
1296            Ok(self.make_unary(UnOp::Not, expr, start))
1297        } else if self.peek_token() == Some(&Token::Question) {
1298            let start = self.tokens[self.pos].position;
1299            self.pos += 1;
1300            // Meta-class query with controlled qualified name consumption to allow postfix chaining
1301            // Consume packages (lowercase-leading) and exactly one Class segment (uppercase-leading), then stop.
1302            let mut parts: Vec<String> = Vec::new();
1303            let first = self.expect_ident()?;
1304            let class_consumed = first
1305                .chars()
1306                .next()
1307                .map(|c| c.is_uppercase())
1308                .unwrap_or(false);
1309            parts.push(first);
1310            while self.peek_token() == Some(&Token::Dot)
1311                && matches!(self.peek_token_at(1), Some(Token::Ident))
1312            {
1313                // Lookahead at the next identifier lexeme
1314                let next_lex = if let Some(ti) = self.tokens.get(self.pos + 1) {
1315                    ti.lexeme.clone()
1316                } else {
1317                    String::new()
1318                };
1319                let is_upper = next_lex
1320                    .chars()
1321                    .next()
1322                    .map(|c| c.is_uppercase())
1323                    .unwrap_or(false);
1324                if class_consumed {
1325                    break;
1326                }
1327                // Consume dot and ident
1328                self.pos += 1; // consume '.'
1329                let seg = self.expect_ident()?;
1330                parts.push(seg);
1331                if is_upper {
1332                    break;
1333                }
1334            }
1335            let end = self.last_token_end();
1336            let span = self.span_from(start, end);
1337            let base = Expr::MetaClass(parts.join("."), span);
1338            self.parse_postfix_with_base(base)
1339        } else {
1340            self.parse_pow()
1341        }
1342    }
1343
1344    fn parse_primary(&mut self) -> Result<Expr, String> {
1345        match self.next() {
1346            Some(info) => match info.token {
1347                Token::Integer | Token::Float => {
1348                    let span = self.span_from(info.position, info.end);
1349                    Ok(Expr::Number(info.lexeme, span))
1350                }
1351                Token::Str => {
1352                    let span = self.span_from(info.position, info.end);
1353                    Ok(Expr::String(info.lexeme, span))
1354                }
1355                Token::True => {
1356                    let span = self.span_from(info.position, info.end);
1357                    Ok(Expr::Ident("true".into(), span))
1358                }
1359                Token::False => {
1360                    let span = self.span_from(info.position, info.end);
1361                    Ok(Expr::Ident("false".into(), span))
1362                }
1363                Token::Ident => {
1364                    let span = self.span_from(info.position, info.end);
1365                    Ok(Expr::Ident(info.lexeme, span))
1366                }
1367                // Treat 'end' as EndKeyword in expression contexts; in command-form we allow 'end' to be consumed as an identifier via command-args path.
1368                Token::End => {
1369                    let span = self.span_from(info.position, info.end);
1370                    Ok(Expr::EndKeyword(span))
1371                }
1372                Token::At => {
1373                    let start = info.position;
1374                    // Anonymous function or function handle
1375                    if self.consume(&Token::LParen) {
1376                        let mut params = Vec::new();
1377                        if !self.consume(&Token::RParen) {
1378                            params.push(self.expect_ident()?);
1379                            while self.consume(&Token::Comma) {
1380                                params.push(self.expect_ident()?);
1381                            }
1382                            if !self.consume(&Token::RParen) {
1383                                return Err(
1384                                    "expected ')' after anonymous function parameters".into()
1385                                );
1386                            }
1387                        }
1388                        let body = self.parse_expr().map_err(|e| e.message)?;
1389                        let span = self.span_from(start, body.span().end);
1390                        Ok(Expr::AnonFunc {
1391                            params,
1392                            body: Box::new(body),
1393                            span,
1394                        })
1395                    } else {
1396                        // function handle @name
1397                        let name = self.expect_ident()?;
1398                        let end = self.last_token_end();
1399                        let span = self.span_from(start, end);
1400                        Ok(Expr::FuncHandle(name, span))
1401                    }
1402                }
1403                Token::LParen => {
1404                    let start = info.position;
1405                    let expr = self.parse_expr()?;
1406                    if !self.consume(&Token::RParen) {
1407                        return Err("expected ')' to close parentheses".into());
1408                    }
1409                    let end = self.last_token_end();
1410                    let span = self.span_from(start, end);
1411                    Ok(expr.with_span(span))
1412                }
1413                Token::LBracket => {
1414                    let start = info.position;
1415                    let matrix = self.parse_matrix()?;
1416                    if !self.consume(&Token::RBracket) {
1417                        return Err("expected ']' to close matrix literal".into());
1418                    }
1419                    let end = self.last_token_end();
1420                    let span = self.span_from(start, end);
1421                    Ok(matrix.with_span(span))
1422                }
1423                Token::LBrace => {
1424                    let start = info.position;
1425                    let cell = self.parse_cell()?;
1426                    if !self.consume(&Token::RBrace) {
1427                        return Err("expected '}' to close cell literal".into());
1428                    }
1429                    let end = self.last_token_end();
1430                    let span = self.span_from(start, end);
1431                    Ok(cell.with_span(span))
1432                }
1433                Token::Colon => {
1434                    let span = self.span_from(info.position, info.end);
1435                    Ok(Expr::Colon(span))
1436                }
1437                _ => Err(format!("unexpected token: {:?}", info.token)),
1438            },
1439            None => Err("unexpected end of input".into()),
1440        }
1441    }
1442
1443    fn parse_matrix(&mut self) -> Result<Expr, String> {
1444        self.skip_newlines();
1445        let mut rows = Vec::new();
1446        if self.peek_token() == Some(&Token::RBracket) {
1447            return Ok(Expr::Tensor(rows, Span::default()));
1448        }
1449        loop {
1450            self.skip_newlines();
1451            if self.peek_token() == Some(&Token::RBracket) {
1452                break;
1453            }
1454            let mut row = Vec::new();
1455            // First element in the row
1456            row.push(self.parse_matrix_expr()?);
1457            // Accept either comma-separated or whitespace-separated elements until ';' or ']'
1458            loop {
1459                if self.consume(&Token::Newline) {
1460                    continue;
1461                }
1462                if self.consume(&Token::Comma) {
1463                    row.push(self.parse_matrix_expr()?);
1464                    continue;
1465                }
1466                // If next token ends the row/matrix, stop
1467                if matches!(
1468                    self.peek_token(),
1469                    Some(Token::Semicolon) | Some(Token::RBracket)
1470                ) {
1471                    break;
1472                }
1473                // Otherwise, treat whitespace as a separator and parse the next element
1474                // Only proceed if the next token can start an expression
1475                match self.peek_token() {
1476                    Some(
1477                        Token::Ident
1478                        | Token::Integer
1479                        | Token::Float
1480                        | Token::Str
1481                        | Token::LParen
1482                        | Token::LBracket
1483                        | Token::LBrace
1484                        | Token::At
1485                        | Token::Plus
1486                        | Token::Minus
1487                        | Token::Colon
1488                        | Token::True
1489                        | Token::False,
1490                    ) => {
1491                        row.push(self.parse_matrix_expr()?);
1492                    }
1493                    _ => {
1494                        break;
1495                    }
1496                }
1497            }
1498            rows.push(row);
1499            if self.consume(&Token::Semicolon) {
1500                self.skip_newlines();
1501                continue;
1502            } else {
1503                break;
1504            }
1505        }
1506        self.skip_newlines();
1507        Ok(Expr::Tensor(rows, Span::default()))
1508    }
1509
1510    fn parse_matrix_expr(&mut self) -> Result<Expr, String> {
1511        let prior = self.in_matrix_expr;
1512        self.in_matrix_expr = true;
1513        let expr = self.parse_expr().map_err(|e| e.message);
1514        self.in_matrix_expr = prior;
1515        expr
1516    }
1517
1518    fn parse_if(&mut self) -> Result<Stmt, String> {
1519        let start = self.tokens[self.pos].position;
1520        self.consume(&Token::If);
1521        let cond = self.parse_expr()?;
1522        let then_body =
1523            self.parse_block(|t| matches!(t, Token::Else | Token::ElseIf | Token::End))?;
1524        let mut elseif_blocks = Vec::new();
1525        while self.consume(&Token::ElseIf) {
1526            let c = self.parse_expr()?;
1527            let body =
1528                self.parse_block(|t| matches!(t, Token::Else | Token::ElseIf | Token::End))?;
1529            elseif_blocks.push((c, body));
1530        }
1531        let else_body = if self.consume(&Token::Else) {
1532            Some(self.parse_block(|t| matches!(t, Token::End))?)
1533        } else {
1534            None
1535        };
1536        if !self.consume(&Token::End) {
1537            return Err("expected 'end'".into());
1538        }
1539        let end = self.last_token_end();
1540        Ok(Stmt::If {
1541            cond,
1542            then_body,
1543            elseif_blocks,
1544            else_body,
1545            span: self.span_from(start, end),
1546        })
1547    }
1548
1549    fn parse_while(&mut self) -> Result<Stmt, String> {
1550        let start = self.tokens[self.pos].position;
1551        self.consume(&Token::While);
1552        let cond = self.parse_expr()?;
1553        let body = self.parse_block(|t| matches!(t, Token::End))?;
1554        if !self.consume(&Token::End) {
1555            return Err("expected 'end'".into());
1556        }
1557        let end = self.last_token_end();
1558        Ok(Stmt::While {
1559            cond,
1560            body,
1561            span: self.span_from(start, end),
1562        })
1563    }
1564
1565    fn parse_for(&mut self) -> Result<Stmt, String> {
1566        let start = self.tokens[self.pos].position;
1567        self.consume(&Token::For);
1568        let var = self.expect_ident()?;
1569        if !self.consume(&Token::Assign) {
1570            return Err("expected '='".into());
1571        }
1572        let expr = self.parse_expr()?;
1573        let body = self.parse_block(|t| matches!(t, Token::End))?;
1574        if !self.consume(&Token::End) {
1575            return Err("expected 'end'".into());
1576        }
1577        let end = self.last_token_end();
1578        Ok(Stmt::For {
1579            var,
1580            expr,
1581            body,
1582            span: self.span_from(start, end),
1583        })
1584    }
1585
1586    fn parse_function(&mut self) -> Result<Stmt, String> {
1587        let start = self.tokens[self.pos].position;
1588        self.consume(&Token::Function);
1589        let mut outputs = Vec::new();
1590        if self.consume(&Token::LBracket) {
1591            outputs.push(self.expect_ident_or_tilde()?);
1592            while self.consume(&Token::Comma) {
1593                outputs.push(self.expect_ident_or_tilde()?);
1594            }
1595            if !self.consume(&Token::RBracket) {
1596                return Err("expected ']'".into());
1597            }
1598            if !self.consume(&Token::Assign) {
1599                return Err("expected '='".into());
1600            }
1601        } else if self.peek_token() == Some(&Token::Ident)
1602            && self.peek_token_at(1) == Some(&Token::Assign)
1603        {
1604            outputs.push(self.next().unwrap().lexeme);
1605            self.consume(&Token::Assign);
1606        }
1607        let name = self.expect_ident()?;
1608        if !self.consume(&Token::LParen) {
1609            return Err("expected '('".into());
1610        }
1611        let mut params = Vec::new();
1612        if !self.consume(&Token::RParen) {
1613            params.push(self.expect_ident()?);
1614            while self.consume(&Token::Comma) {
1615                params.push(self.expect_ident()?);
1616            }
1617            if !self.consume(&Token::RParen) {
1618                return Err("expected ')'".into());
1619            }
1620        }
1621
1622        // Enforce varargs placement constraints at parse time
1623        // varargin: at most once, must be last in params if present
1624        if let Some(idx) = params.iter().position(|p| p == "varargin") {
1625            if idx != params.len() - 1 {
1626                return Err("'varargin' must be the last input parameter".into());
1627            }
1628            if params.iter().filter(|p| p.as_str() == "varargin").count() > 1 {
1629                return Err("'varargin' cannot appear more than once".into());
1630            }
1631        }
1632        // varargout: at most once, must be last in outputs if present
1633        if let Some(idx) = outputs.iter().position(|o| o == "varargout") {
1634            if idx != outputs.len() - 1 {
1635                return Err("'varargout' must be the last output parameter".into());
1636            }
1637            if outputs.iter().filter(|o| o.as_str() == "varargout").count() > 1 {
1638                return Err("'varargout' cannot appear more than once".into());
1639            }
1640        }
1641
1642        // Optional function-level arguments block
1643        // arguments ... end  (we accept a sequence of identifiers, validation semantics handled in HIR/runtime)
1644        if self.peek_token() == Some(&Token::Arguments) {
1645            self.pos += 1; // consume 'arguments'
1646                           // Accept a flat list of identifiers optionally separated by commas/semicolons
1647            loop {
1648                if self.consume(&Token::End) {
1649                    break;
1650                }
1651                if self.consume(&Token::Semicolon) || self.consume(&Token::Comma) {
1652                    continue;
1653                }
1654                if matches!(self.peek_token(), Some(Token::Ident)) {
1655                    let _ = self.expect_ident()?;
1656                    continue;
1657                }
1658                // Tolerate newlines/whitespace-only between entries
1659                if self.peek_token().is_none() {
1660                    break;
1661                }
1662                break;
1663            }
1664        }
1665
1666        let body = self.parse_block(|t| matches!(t, Token::End))?;
1667        if !self.consume(&Token::End) {
1668            return Err("expected 'end'".into());
1669        }
1670        let end = self.last_token_end();
1671        Ok(Stmt::Function {
1672            name,
1673            params,
1674            outputs,
1675            body,
1676            span: self.span_from(start, end),
1677        })
1678    }
1679
1680    fn parse_block<F>(&mut self, term: F) -> Result<Vec<Stmt>, String>
1681    where
1682        F: Fn(&Token) -> bool,
1683    {
1684        let mut body = Vec::new();
1685        while let Some(tok) = self.peek_token() {
1686            if term(tok) {
1687                break;
1688            }
1689            if self.consume(&Token::Semicolon)
1690                || self.consume(&Token::Comma)
1691                || self.consume(&Token::Newline)
1692            {
1693                continue;
1694            }
1695            // Fast-path: handle multi-assign LHS at statement start inside blocks reliably
1696            let stmt = if self.peek_token() == Some(&Token::LBracket) {
1697                self.try_parse_multi_assign()?
1698            } else {
1699                self.parse_stmt().map_err(|e| e.message)?
1700            };
1701            let is_semicolon_terminated = self.consume(&Token::Semicolon);
1702
1703            let final_stmt = match stmt {
1704                Stmt::ExprStmt(expr, _, span) => {
1705                    Stmt::ExprStmt(expr, is_semicolon_terminated, span)
1706                }
1707                Stmt::Assign(name, expr, _, span) => {
1708                    Stmt::Assign(name, expr, is_semicolon_terminated, span)
1709                }
1710                Stmt::MultiAssign(names, expr, _, span) => {
1711                    Stmt::MultiAssign(names, expr, is_semicolon_terminated, span)
1712                }
1713                Stmt::AssignLValue(lv, expr, _, span) => {
1714                    Stmt::AssignLValue(lv, expr, is_semicolon_terminated, span)
1715                }
1716                other => other,
1717            };
1718            body.push(final_stmt);
1719        }
1720        Ok(body)
1721    }
1722
1723    fn parse_cell(&mut self) -> Result<Expr, String> {
1724        let mut rows = Vec::new();
1725        self.skip_newlines();
1726        if self.peek_token() == Some(&Token::RBrace) {
1727            return Ok(Expr::Cell(rows, Span::default()));
1728        }
1729        loop {
1730            self.skip_newlines();
1731            if self.peek_token() == Some(&Token::RBrace) {
1732                break;
1733            }
1734            let mut row = Vec::new();
1735            row.push(self.parse_expr()?);
1736            while self.consume(&Token::Comma) {
1737                row.push(self.parse_expr()?);
1738            }
1739            rows.push(row);
1740            if self.consume(&Token::Semicolon) {
1741                self.skip_newlines();
1742                continue;
1743            } else {
1744                break;
1745            }
1746        }
1747        self.skip_newlines();
1748        Ok(Expr::Cell(rows, Span::default()))
1749    }
1750
1751    fn parse_switch(&mut self) -> Result<Stmt, String> {
1752        let start = self.tokens[self.pos].position;
1753        self.consume(&Token::Switch);
1754        let control = self.parse_expr()?;
1755        let mut cases = Vec::new();
1756        let mut otherwise: Option<Vec<Stmt>> = None;
1757        loop {
1758            if self.consume(&Token::Newline) || self.consume(&Token::Semicolon) {
1759                continue;
1760            }
1761            if self.consume(&Token::Case) {
1762                let val = self.parse_expr()?;
1763                let body =
1764                    self.parse_block(|t| matches!(t, Token::Case | Token::Otherwise | Token::End))?;
1765                cases.push((val, body));
1766            } else if self.consume(&Token::Otherwise) {
1767                let body = self.parse_block(|t| matches!(t, Token::End))?;
1768                otherwise = Some(body);
1769            } else if self.consume(&Token::Comma) {
1770                continue;
1771            } else {
1772                break;
1773            }
1774        }
1775        if !self.consume(&Token::End) {
1776            return Err("expected 'end' for switch".into());
1777        }
1778        let end = self.last_token_end();
1779        Ok(Stmt::Switch {
1780            expr: control,
1781            cases,
1782            otherwise,
1783            span: self.span_from(start, end),
1784        })
1785    }
1786
1787    fn parse_try_catch(&mut self) -> Result<Stmt, String> {
1788        let start = self.tokens[self.pos].position;
1789        self.consume(&Token::Try);
1790        let try_body = self.parse_block(|t| matches!(t, Token::Catch | Token::End))?;
1791        if !self.consume(&Token::Catch) {
1792            return Err("expected 'catch' after try".into());
1793        }
1794        let catch_var = if self.peek_token() == Some(&Token::Ident) {
1795            Some(self.expect_ident()?)
1796        } else {
1797            None
1798        };
1799        let catch_body = self.parse_block(|t| matches!(t, Token::End))?;
1800        if !self.consume(&Token::End) {
1801            return Err("expected 'end' after catch".into());
1802        }
1803        let end = self.last_token_end();
1804        Ok(Stmt::TryCatch {
1805            try_body,
1806            catch_var,
1807            catch_body,
1808            span: self.span_from(start, end),
1809        })
1810    }
1811
1812    fn parse_import(&mut self) -> Result<Stmt, String> {
1813        let start = self.tokens[self.pos].position;
1814        self.consume(&Token::Import);
1815        // import pkg.sub.Class or import pkg.*
1816        let mut path = Vec::new();
1817        path.push(self.expect_ident()?);
1818        let mut wildcard = false;
1819        loop {
1820            if self.consume(&Token::DotStar) {
1821                wildcard = true;
1822                break;
1823            }
1824            if self.consume(&Token::Dot) {
1825                if self.consume(&Token::Star) {
1826                    wildcard = true;
1827                    break;
1828                } else {
1829                    path.push(self.expect_ident()?);
1830                    continue;
1831                }
1832            }
1833            break;
1834        }
1835        let end = self.last_token_end();
1836        Ok(Stmt::Import {
1837            path,
1838            wildcard,
1839            span: self.span_from(start, end),
1840        })
1841    }
1842
1843    fn parse_classdef(&mut self) -> Result<Stmt, String> {
1844        let start = self.tokens[self.pos].position;
1845        self.consume(&Token::ClassDef);
1846        let name = self.parse_qualified_name()?;
1847        let mut super_class = None;
1848        if self.consume(&Token::Less) {
1849            super_class = Some(self.parse_qualified_name()?);
1850        }
1851        let mut members: Vec<ClassMember> = Vec::new();
1852        loop {
1853            // Skip layout separators between member blocks
1854            if self.consume(&Token::Semicolon)
1855                || self.consume(&Token::Comma)
1856                || self.consume(&Token::Newline)
1857            {
1858                continue;
1859            }
1860            match self.peek_token() {
1861                Some(Token::Properties) => {
1862                    self.pos += 1;
1863                    let attrs = self.parse_optional_attr_list();
1864                    let props = self.parse_properties_names_block()?;
1865                    if !self.consume(&Token::End) {
1866                        return Err("expected 'end' after properties".into());
1867                    }
1868                    members.push(ClassMember::Properties {
1869                        attributes: attrs,
1870                        names: props,
1871                    });
1872                }
1873                Some(Token::Methods) => {
1874                    self.pos += 1;
1875                    let attrs = self.parse_optional_attr_list();
1876                    let body = self.parse_block(|t| matches!(t, Token::End))?;
1877                    if !self.consume(&Token::End) {
1878                        return Err("expected 'end' after methods".into());
1879                    }
1880                    members.push(ClassMember::Methods {
1881                        attributes: attrs,
1882                        body,
1883                    });
1884                }
1885                Some(Token::Events) => {
1886                    self.pos += 1;
1887                    let attrs = self.parse_optional_attr_list();
1888                    let names = self.parse_name_block()?;
1889                    if !self.consume(&Token::End) {
1890                        return Err("expected 'end' after events".into());
1891                    }
1892                    members.push(ClassMember::Events {
1893                        attributes: attrs,
1894                        names,
1895                    });
1896                }
1897                Some(Token::Enumeration) => {
1898                    self.pos += 1;
1899                    let attrs = self.parse_optional_attr_list();
1900                    let names = self.parse_name_block()?;
1901                    if !self.consume(&Token::End) {
1902                        return Err("expected 'end' after enumeration".into());
1903                    }
1904                    members.push(ClassMember::Enumeration {
1905                        attributes: attrs,
1906                        names,
1907                    });
1908                }
1909                Some(Token::Arguments) => {
1910                    self.pos += 1;
1911                    let attrs = self.parse_optional_attr_list();
1912                    let names = self.parse_name_block()?;
1913                    if !self.consume(&Token::End) {
1914                        return Err("expected 'end' after arguments".into());
1915                    }
1916                    members.push(ClassMember::Arguments {
1917                        attributes: attrs,
1918                        names,
1919                    });
1920                }
1921                Some(Token::End) => {
1922                    self.pos += 1;
1923                    break;
1924                }
1925                _ => break,
1926            }
1927        }
1928        let end = self.last_token_end();
1929        Ok(Stmt::ClassDef {
1930            name,
1931            super_class,
1932            members,
1933            span: self.span_from(start, end),
1934        })
1935    }
1936
1937    fn parse_name_block(&mut self) -> Result<Vec<String>, String> {
1938        let mut names = Vec::new();
1939        while let Some(tok) = self.peek_token() {
1940            if matches!(tok, Token::End) {
1941                break;
1942            }
1943            if self.consume(&Token::Semicolon)
1944                || self.consume(&Token::Comma)
1945                || self.consume(&Token::Newline)
1946            {
1947                continue;
1948            }
1949            if let Some(Token::Ident) = self.peek_token() {
1950                names.push(self.expect_ident()?);
1951            } else {
1952                break;
1953            }
1954        }
1955        Ok(names)
1956    }
1957
1958    fn parse_properties_names_block(&mut self) -> Result<Vec<String>, String> {
1959        // Accept identifiers with optional default assignment: name, name = expr
1960        let mut names = Vec::new();
1961        while let Some(tok) = self.peek_token() {
1962            if matches!(tok, Token::End) {
1963                break;
1964            }
1965            if self.consume(&Token::Semicolon)
1966                || self.consume(&Token::Comma)
1967                || self.consume(&Token::Newline)
1968            {
1969                continue;
1970            }
1971            if let Some(Token::Ident) = self.peek_token() {
1972                names.push(self.expect_ident()?);
1973                // Optional default initializer: skip over `= expr` syntactically
1974                if self.consume(&Token::Assign) {
1975                    // Parse and discard expression to keep the grammar permissive; initializer is HIR/semantics concern
1976                    let _ = self.parse_expr().map_err(|e| e.message)?;
1977                }
1978            } else {
1979                break;
1980            }
1981        }
1982        Ok(names)
1983    }
1984
1985    fn parse_optional_attr_list(&mut self) -> Vec<Attr> {
1986        // Minimal parsing of attribute lists: (Attr, Attr=Value, ...)
1987        let mut attrs: Vec<Attr> = Vec::new();
1988        if !self.consume(&Token::LParen) {
1989            return attrs;
1990        }
1991        loop {
1992            if self.consume(&Token::RParen) {
1993                break;
1994            }
1995            match self.peek_token() {
1996                Some(Token::Ident) => {
1997                    let name = self.expect_ident().unwrap_or_else(|_| "".to_string());
1998                    let mut value: Option<String> = None;
1999                    if self.consume(&Token::Assign) {
2000                        // Value could be ident, string or number; capture raw lexeme
2001                        if let Some(tok) = self.next() {
2002                            value = Some(tok.lexeme);
2003                        }
2004                    }
2005                    attrs.push(Attr { name, value });
2006                    let _ = self.consume(&Token::Comma);
2007                }
2008                Some(Token::Comma) => {
2009                    self.pos += 1;
2010                }
2011                Some(Token::RParen) => {
2012                    self.pos += 1;
2013                    break;
2014                }
2015                Some(_) => {
2016                    self.pos += 1;
2017                }
2018                None => {
2019                    break;
2020                }
2021            }
2022        }
2023        attrs
2024    }
2025
2026    fn parse_global(&mut self) -> Result<Stmt, String> {
2027        let start = self.tokens[self.pos].position;
2028        self.consume(&Token::Global);
2029        let mut names = Vec::new();
2030        names.push(self.expect_ident()?);
2031        loop {
2032            if self.consume(&Token::Comma) {
2033                names.push(self.expect_ident()?);
2034                continue;
2035            }
2036            if self.peek_token() == Some(&Token::Ident) {
2037                names.push(self.expect_ident()?);
2038                continue;
2039            }
2040            break;
2041        }
2042        let end = self.last_token_end();
2043        Ok(Stmt::Global(names, self.span_from(start, end)))
2044    }
2045
2046    fn parse_persistent(&mut self) -> Result<Stmt, String> {
2047        let start = self.tokens[self.pos].position;
2048        self.consume(&Token::Persistent);
2049        let mut names = Vec::new();
2050        names.push(self.expect_ident()?);
2051        loop {
2052            if self.consume(&Token::Comma) {
2053                names.push(self.expect_ident()?);
2054                continue;
2055            }
2056            if self.peek_token() == Some(&Token::Ident) {
2057                names.push(self.expect_ident()?);
2058                continue;
2059            }
2060            break;
2061        }
2062        let end = self.last_token_end();
2063        Ok(Stmt::Persistent(names, self.span_from(start, end)))
2064    }
2065
2066    fn try_parse_multi_assign(&mut self) -> Result<Stmt, String> {
2067        if !self.consume(&Token::LBracket) {
2068            return Err("not a multi-assign".into());
2069        }
2070        let start = self.tokens[self.pos.saturating_sub(1)].position;
2071        let mut names = Vec::new();
2072        names.push(self.expect_ident_or_tilde()?);
2073        while self.consume(&Token::Comma) {
2074            names.push(self.expect_ident_or_tilde()?);
2075        }
2076        if !self.consume(&Token::RBracket) {
2077            return Err("expected ']'".into());
2078        }
2079        if !self.consume(&Token::Assign) {
2080            return Err("expected '='".into());
2081        }
2082        let rhs = self.parse_expr().map_err(|e| e.message)?;
2083        let span = self.span_from(start, rhs.span().end);
2084        Ok(Stmt::MultiAssign(names, rhs, false, span))
2085    }
2086
2087    fn parse_qualified_name(&mut self) -> Result<String, String> {
2088        let mut parts = Vec::new();
2089        parts.push(self.expect_ident()?);
2090        while self.consume(&Token::Dot) {
2091            parts.push(self.expect_ident()?);
2092        }
2093        Ok(parts.join("."))
2094    }
2095
2096    fn expect_ident(&mut self) -> Result<String, String> {
2097        match self.next() {
2098            Some(TokenInfo {
2099                token: Token::Ident,
2100                lexeme,
2101                ..
2102            }) => Ok(lexeme),
2103            _ => Err("expected identifier".into()),
2104        }
2105    }
2106
2107    fn expect_ident_or_tilde(&mut self) -> Result<String, String> {
2108        match self.next() {
2109            Some(TokenInfo {
2110                token: Token::Ident,
2111                lexeme,
2112                ..
2113            }) => Ok(lexeme),
2114            Some(TokenInfo {
2115                token: Token::Tilde,
2116                ..
2117            }) => Ok("~".to_string()),
2118            _ => Err("expected identifier or '~'".into()),
2119        }
2120    }
2121
2122    fn peek(&self) -> Option<&TokenInfo> {
2123        self.tokens.get(self.pos)
2124    }
2125
2126    fn current_position(&self) -> usize {
2127        self.peek()
2128            .map(|t| t.position)
2129            .unwrap_or_else(|| self.input.len())
2130    }
2131
2132    fn peek_token(&self) -> Option<&Token> {
2133        self.tokens.get(self.pos).map(|t| &t.token)
2134    }
2135
2136    fn peek_token_at(&self, offset: usize) -> Option<&Token> {
2137        self.tokens.get(self.pos + offset).map(|t| &t.token)
2138    }
2139
2140    fn next(&mut self) -> Option<TokenInfo> {
2141        if self.pos < self.tokens.len() {
2142            let info = self.tokens[self.pos].clone();
2143            self.pos += 1;
2144            Some(info)
2145        } else {
2146            None
2147        }
2148    }
2149
2150    fn consume(&mut self, t: &Token) -> bool {
2151        if self.peek_token() == Some(t) {
2152            self.pos += 1;
2153            true
2154        } else {
2155            false
2156        }
2157    }
2158}
2159
2160fn extract_keyword(expr: &Expr) -> Option<String> {
2161    match expr {
2162        Expr::Ident(s, _) => Some(s.clone()),
2163        Expr::String(s, _) => Some(s.trim_matches(&['"', '\''][..]).to_string()),
2164        _ => None,
2165    }
2166}