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), 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 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 ElemMul, ElemDiv, ElemPow, ElemLeftDiv, AndAnd, OrOr, BitAnd, BitOr, Equal, NotEqual, Less, LessEqual, Greater, GreaterEqual, }
143
144#[derive(Debug, PartialEq, Copy, Clone, Serialize, Deserialize)]
145pub enum UnOp {
146 Plus,
147 Minus,
148 Transpose,
149 NonConjugateTranspose,
150 Not, }
152
153#[derive(Debug, PartialEq)]
154pub enum Stmt {
155 ExprStmt(Expr, bool, Span), Assign(String, Expr, bool, Span), 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 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 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 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 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 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 if let Some(lv) = self.try_parse_lvalue_assign()? {
641 return Ok(lv);
642 }
643 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 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 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 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 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 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 Some(Token::Assign) => false,
760 None | Some(Token::Semicolon) | Some(Token::Comma) | Some(Token::Newline) => true,
762 _ => 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 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 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 }
866 }
867 Ok(())
868 }
869
870 fn try_parse_lvalue_assign(&mut self) -> Result<Option<Stmt>, SyntaxError> {
871 let save = self.pos;
872 let lvalue = if self.peek_token() == Some(&Token::Ident) {
874 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 self.peek_token_at(1) == Some(&Token::Transpose) {
920 break;
921 }
922 if self.peek_token_at(1) == Some(&Token::Plus)
924 || self.peek_token_at(1) == Some(&Token::Minus)
925 {
926 break;
927 }
928 self.pos += 1; 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; 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 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 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; 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; let rhs = self.parse_pow()?; 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 if let Expr::Ident(ref name, _) = expr {
1187 expr = Expr::FuncCall(name.clone(), args, span);
1188 } else {
1189 expr = Expr::Index(Box::new(expr), args, span);
1191 }
1192 } else if self.consume(&Token::LBracket) {
1193 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 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 if self.peek_token_at(1) == Some(&Token::Transpose) {
1223 self.pos += 2; 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 break;
1234 }
1235 self.pos += 1; 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 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 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 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 self.pos += 1; 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 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 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 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 row.push(self.parse_matrix_expr()?);
1457 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 matches!(
1468 self.peek_token(),
1469 Some(Token::Semicolon) | Some(Token::RBracket)
1470 ) {
1471 break;
1472 }
1473 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 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 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 if self.peek_token() == Some(&Token::Arguments) {
1645 self.pos += 1; 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 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 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 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 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 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 if self.consume(&Token::Assign) {
1975 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 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 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}