1use crate::eval::{Expr, Op, expr_to_string};
2
3#[derive(Debug)]
5pub enum Stmt {
6 Assign(String, Expr),
8 Expr(Expr),
10 If {
12 cond: Expr,
14 body: Vec<(Stmt, bool)>,
16 elseif_branches: Vec<(Expr, Vec<(Stmt, bool)>)>,
18 else_body: Option<Vec<(Stmt, bool)>>,
20 },
21 For {
23 var: String,
25 range_expr: Expr,
27 body: Vec<(Stmt, bool)>,
29 },
30 While {
32 cond: Expr,
34 body: Vec<(Stmt, bool)>,
36 },
37 Break,
39 Continue,
41 #[allow(clippy::type_complexity)]
47 Switch {
48 expr: Expr,
50 cases: Vec<(Vec<Expr>, Vec<(Stmt, bool)>)>,
52 otherwise_body: Option<Vec<(Stmt, bool)>>,
54 },
55 DoUntil {
59 body: Vec<(Stmt, bool)>,
61 cond: Expr,
63 },
64 FunctionDef {
69 name: String,
71 outputs: Vec<String>,
73 params: Vec<String>,
75 body_source: String,
77 doc: Option<String>,
79 },
80 Return,
85 MultiAssign {
91 targets: Vec<String>,
93 expr: Expr,
95 },
96 TryCatch {
101 try_body: Vec<(Stmt, bool)>,
103 catch_var: Option<String>,
105 catch_body: Vec<(Stmt, bool)>,
107 },
108 CellSet(String, Expr, Expr),
112 FieldSet(String, Vec<String>, Expr),
123 StructArrayFieldSet(String, Expr, Vec<String>, Expr),
134 IndexSet {
141 name: String,
143 indices: Vec<Expr>,
145 value: Expr,
147 },
148 Global(Vec<String>),
154 Persistent(Vec<String>),
160}
161
162#[derive(Debug, Clone)]
163enum Token {
164 Number(f64),
165 Ident(String),
166 Str(String), StringObj(String), Plus,
169 Minus,
170 Star,
171 Slash,
172 Caret,
173 DotStar,
174 DotSlash,
175 DotCaret,
176 Apostrophe,
177 LParen,
178 RParen,
179 Comma,
180 LBracket,
181 RBracket,
182 Semicolon,
183 Colon,
184 PlusEq, MinusEq, StarEq, SlashEq, PlusPlus, MinusMinus, EqEq, NotEq, Lt, Gt, LtEq, GtEq, AmpAmp, PipePipe, Amp, Pipe, Tilde, At, LBrace, RBrace, StarStar, DotApostrophe, Backslash, Dot, }
214
215fn parse_integer_literal(
216 chars: &mut std::iter::Peekable<std::str::Chars<'_>>,
217 radix: u32,
218 prefix: &str,
219) -> Result<f64, String> {
220 let mut digit_str = String::new();
221 while let Some(&d) = chars.peek() {
222 let valid = match radix {
223 16 => d.is_ascii_hexdigit(),
224 2 => d == '0' || d == '1',
225 8 => ('0'..='7').contains(&d),
226 _ => false,
227 };
228 if valid {
229 digit_str.push(d);
230 chars.next();
231 } else {
232 break;
233 }
234 }
235 if digit_str.is_empty() {
236 return Err(format!("Expected digits after '{prefix}'"));
237 }
238 i64::from_str_radix(&digit_str, radix)
239 .map(|i| i as f64)
240 .map_err(|_| format!("Invalid {prefix} literal: '{prefix}{digit_str}'"))
241}
242
243fn try_consume_sci_exponent(
246 chars: &mut std::iter::Peekable<std::str::Chars<'_>>,
247 num_str: &mut String,
248) {
249 if !matches!(chars.peek(), Some('e') | Some('E')) {
250 return;
251 }
252 let mut lookahead = chars.clone();
253 let e_char = lookahead.next().unwrap();
254 match lookahead.peek().copied() {
255 Some('+') | Some('-') => {
256 let sign = lookahead.next().unwrap();
257 if lookahead.peek().is_some_and(|d| d.is_ascii_digit()) {
258 chars.next();
259 chars.next();
260 num_str.push(e_char);
261 num_str.push(sign);
262 while let Some(&d) = chars.peek() {
263 if d.is_ascii_digit() {
264 num_str.push(d);
265 chars.next();
266 } else {
267 break;
268 }
269 }
270 }
271 }
272 Some(d) if d.is_ascii_digit() => {
273 chars.next();
274 num_str.push(e_char);
275 while let Some(&d) = chars.peek() {
276 if d.is_ascii_digit() {
277 num_str.push(d);
278 chars.next();
279 } else {
280 break;
281 }
282 }
283 }
284 _ => {}
285 }
286}
287
288#[inline]
292fn push_imag_suffix(chars: &mut std::iter::Peekable<std::str::Chars<'_>>, tokens: &mut Vec<Token>) {
293 if matches!(chars.peek(), Some('i') | Some('j')) {
294 let mut la = chars.clone();
295 la.next();
296 if !la.peek().is_some_and(|c| c.is_alphanumeric() || *c == '_') {
297 chars.next(); tokens.push(Token::Star);
299 tokens.push(Token::Ident("i".to_string()));
300 }
301 }
302}
303
304fn tokenize(input: &str) -> Result<Vec<Token>, String> {
305 let mut tokens = Vec::new();
306 let mut chars = input.chars().peekable();
307
308 while let Some(&c) = chars.peek() {
309 match c {
310 ' ' | '\t' => {
311 chars.next();
312 }
313 '+' => {
314 chars.next();
315 match chars.peek() {
316 Some('=') => {
317 chars.next();
318 tokens.push(Token::PlusEq);
319 }
320 Some('+') => {
321 chars.next();
322 tokens.push(Token::PlusPlus);
323 }
324 _ => tokens.push(Token::Plus),
325 }
326 }
327 '-' => {
328 chars.next();
329 match chars.peek() {
330 Some('=') => {
331 chars.next();
332 tokens.push(Token::MinusEq);
333 }
334 Some('-') => {
335 chars.next();
336 tokens.push(Token::MinusMinus);
337 }
338 _ => tokens.push(Token::Minus),
339 }
340 }
341 '*' => {
342 chars.next();
343 match chars.peek() {
344 Some('=') => {
345 chars.next();
346 tokens.push(Token::StarEq);
347 }
348 Some('*') => {
349 chars.next();
350 tokens.push(Token::StarStar);
351 }
352 _ => tokens.push(Token::Star),
353 }
354 }
355 '/' => {
356 chars.next();
357 if chars.peek() == Some(&'=') {
358 chars.next();
359 tokens.push(Token::SlashEq);
360 } else {
361 tokens.push(Token::Slash);
362 }
363 }
364 '^' => {
365 tokens.push(Token::Caret);
366 chars.next();
367 }
368 '\'' => {
369 let is_transpose = matches!(
372 tokens.last(),
373 Some(
374 Token::Number(_)
375 | Token::Ident(_)
376 | Token::RParen
377 | Token::RBracket
378 | Token::Apostrophe
379 | Token::Str(_)
380 )
381 );
382 chars.next(); if is_transpose {
384 tokens.push(Token::Apostrophe);
385 } else {
386 let mut content = String::new();
388 loop {
389 match chars.next() {
390 None => return Err("Unterminated string literal".to_string()),
391 Some('\'') => {
392 if chars.peek().copied() == Some('\'') {
394 chars.next();
395 content.push('\'');
396 } else {
397 break;
398 }
399 }
400 Some(c) => content.push(c),
401 }
402 }
403 tokens.push(Token::Str(content));
404 }
405 }
406 '"' => {
407 chars.next(); let mut content = String::new();
409 loop {
410 match chars.next() {
411 None => return Err("Unterminated string literal".to_string()),
412 Some('"') => {
413 if chars.peek().copied() == Some('"') {
415 chars.next();
416 content.push('"');
417 } else {
418 break;
419 }
420 }
421 Some('\\') => match chars.next() {
422 Some('n') => content.push('\n'),
423 Some('t') => content.push('\t'),
424 Some('\\') => content.push('\\'),
425 Some('\'') => content.push('\''),
426 Some('"') => content.push('"'),
427 Some(other) => {
428 content.push('\\');
429 content.push(other);
430 }
431 None => return Err("Unterminated string literal".to_string()),
432 },
433 Some(c) => content.push(c),
434 }
435 }
436 tokens.push(Token::StringObj(content));
437 }
438 '.' => {
439 chars.next();
440 match chars.peek().copied() {
441 Some('.') => {
442 chars.next(); if chars.peek() == Some(&'.') {
445 chars.next(); while chars.next().is_some() {}
448 } else {
449 return Err("Unexpected '..'".to_string());
450 }
451 }
452 Some('\'') => {
453 chars.next();
454 tokens.push(Token::DotApostrophe);
455 }
456 Some('*') => {
457 chars.next();
458 tokens.push(Token::DotStar);
459 }
460 Some('/') => {
461 chars.next();
462 tokens.push(Token::DotSlash);
463 }
464 Some('^') => {
465 chars.next();
466 tokens.push(Token::DotCaret);
467 }
468 Some(d) if d.is_ascii_digit() => {
469 let mut num_str = String::from(".");
470 while let Some(&d) = chars.peek() {
471 if d.is_ascii_digit() {
472 num_str.push(d);
473 chars.next();
474 } else {
475 break;
476 }
477 }
478 try_consume_sci_exponent(&mut chars, &mut num_str);
479 let n: f64 = num_str
480 .parse()
481 .map_err(|_| format!("Invalid number: '{num_str}'"))?;
482 tokens.push(Token::Number(n));
483 }
484 Some(c) if c.is_ascii_alphabetic() || c == '_' => {
487 tokens.push(Token::Dot);
488 }
489 _ => return Err("Unexpected '.'".to_string()),
490 }
491 }
492 '%' | '#' => {
493 break;
495 }
496 '!' => {
497 chars.next();
498 if chars.peek().copied() == Some('=') {
499 chars.next();
500 tokens.push(Token::NotEq);
501 } else {
502 tokens.push(Token::Tilde);
503 }
504 }
505 '(' => {
506 tokens.push(Token::LParen);
507 chars.next();
508 }
509 ')' => {
510 tokens.push(Token::RParen);
511 chars.next();
512 }
513 ',' => {
514 tokens.push(Token::Comma);
515 chars.next();
516 }
517 '[' => {
518 tokens.push(Token::LBracket);
519 chars.next();
520 }
521 ']' => {
522 tokens.push(Token::RBracket);
523 chars.next();
524 }
525 '{' => {
526 tokens.push(Token::LBrace);
527 chars.next();
528 }
529 '}' => {
530 tokens.push(Token::RBrace);
531 chars.next();
532 }
533 ';' => {
534 tokens.push(Token::Semicolon);
535 chars.next();
536 }
537 ':' => {
538 tokens.push(Token::Colon);
539 chars.next();
540 }
541 '=' => {
542 chars.next();
543 if chars.peek().copied() == Some('=') {
544 chars.next();
545 tokens.push(Token::EqEq);
546 } else {
547 return Err("Unexpected '=': use '==' for comparison".to_string());
548 }
549 }
550 '~' => {
551 chars.next();
552 if chars.peek().copied() == Some('=') {
553 chars.next();
554 tokens.push(Token::NotEq);
555 } else {
556 tokens.push(Token::Tilde);
557 }
558 }
559 '<' => {
560 chars.next();
561 if chars.peek().copied() == Some('=') {
562 chars.next();
563 tokens.push(Token::LtEq);
564 } else {
565 tokens.push(Token::Lt);
566 }
567 }
568 '>' => {
569 chars.next();
570 if chars.peek().copied() == Some('=') {
571 chars.next();
572 tokens.push(Token::GtEq);
573 } else {
574 tokens.push(Token::Gt);
575 }
576 }
577 '&' => {
578 chars.next();
579 if chars.peek().copied() == Some('&') {
580 chars.next();
581 tokens.push(Token::AmpAmp);
582 } else {
583 tokens.push(Token::Amp);
584 }
585 }
586 '|' => {
587 chars.next();
588 if chars.peek().copied() == Some('|') {
589 chars.next();
590 tokens.push(Token::PipePipe);
591 } else {
592 tokens.push(Token::Pipe);
593 }
594 }
595 '0'..='9' => {
596 if c == '0' {
597 chars.next();
598 match chars.peek().copied() {
599 Some('x') | Some('X') => {
600 chars.next();
601 let n = parse_integer_literal(&mut chars, 16, "0x")?;
602 tokens.push(Token::Number(n));
603 }
604 Some('b') | Some('B') => {
605 chars.next();
606 let n = parse_integer_literal(&mut chars, 2, "0b")?;
607 tokens.push(Token::Number(n));
608 }
609 Some('o') | Some('O') => {
610 chars.next();
611 let n = parse_integer_literal(&mut chars, 8, "0o")?;
612 tokens.push(Token::Number(n));
613 }
614 _ => {
615 let mut num_str = String::from("0");
616 while let Some(&d) = chars.peek() {
617 if d.is_ascii_digit() {
618 num_str.push(d);
619 chars.next();
620 } else if d == '.' {
621 let mut la = chars.clone();
623 la.next();
624 if matches!(la.peek(), Some('*') | Some('/') | Some('^')) {
625 break;
626 }
627 num_str.push('.');
628 chars.next();
629 } else {
630 break;
631 }
632 }
633 try_consume_sci_exponent(&mut chars, &mut num_str);
634 let n: f64 = num_str
635 .parse()
636 .map_err(|_| format!("Invalid number: '{num_str}'"))?;
637 tokens.push(Token::Number(n));
638 push_imag_suffix(&mut chars, &mut tokens);
639 }
640 }
641 } else {
642 let mut num_str = String::new();
643 while let Some(&d) = chars.peek() {
644 if d.is_ascii_digit() {
645 num_str.push(d);
646 chars.next();
647 } else if d == '.' {
648 let mut la = chars.clone();
650 la.next();
651 if matches!(la.peek(), Some('*') | Some('/') | Some('^')) {
652 break;
653 }
654 num_str.push('.');
655 chars.next();
656 } else {
657 break;
658 }
659 }
660 try_consume_sci_exponent(&mut chars, &mut num_str);
661 let n: f64 = num_str
662 .parse()
663 .map_err(|_| format!("Invalid number: '{num_str}'"))?;
664 tokens.push(Token::Number(n));
665 push_imag_suffix(&mut chars, &mut tokens);
666 }
667 }
668 '@' => {
669 tokens.push(Token::At);
670 chars.next();
671 }
672 '\\' => {
673 tokens.push(Token::Backslash);
674 chars.next();
675 }
676 'a'..='z' | 'A'..='Z' | '_' => {
677 let mut ident = String::new();
678 while let Some(&c) = chars.peek() {
679 if c.is_alphanumeric() || c == '_' {
680 ident.push(c);
681 chars.next();
682 } else {
683 break;
684 }
685 }
686 tokens.push(Token::Ident(ident));
687 }
688 _ => return Err(format!("Unexpected character: '{c}'")),
689 }
690 }
691
692 Ok(tokens)
693}
694
695fn try_split_struct_array_field_assign(input: &str) -> Option<(String, &str, Vec<String>, &str)> {
699 let trimmed = input.trim();
700 let bytes = trimmed.as_bytes();
701 let mut i = 0;
702
703 if i >= bytes.len() || !(bytes[i].is_ascii_alphabetic() || bytes[i] == b'_') {
705 return None;
706 }
707 let base_start = i;
708 while i < bytes.len() && (bytes[i].is_ascii_alphanumeric() || bytes[i] == b'_') {
709 i += 1;
710 }
711 let base_var = trimmed[base_start..i].to_string();
712
713 if i >= bytes.len() || bytes[i] != b'(' {
715 return None;
716 }
717 i += 1;
718
719 let idx_start = i;
721 let mut depth = 1usize;
722 while i < bytes.len() && depth > 0 {
723 match bytes[i] {
724 b'(' | b'[' | b'{' => depth += 1,
725 b')' | b']' | b'}' => depth -= 1,
726 _ => {}
727 }
728 i += 1;
729 }
730 if depth != 0 {
731 return None;
732 }
733 let idx_str = &trimmed[idx_start..i - 1]; if i >= bytes.len() || bytes[i] != b'.' {
737 return None;
738 }
739 let mut fields = Vec::new();
740 while i < bytes.len() && bytes[i] == b'.' {
741 i += 1;
742 if i >= bytes.len() || !(bytes[i].is_ascii_alphabetic() || bytes[i] == b'_') {
743 return None;
744 }
745 let field_start = i;
746 while i < bytes.len() && (bytes[i].is_ascii_alphanumeric() || bytes[i] == b'_') {
747 i += 1;
748 }
749 fields.push(trimmed[field_start..i].to_string());
750 }
751 if fields.is_empty() {
752 return None;
753 }
754
755 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
757 i += 1;
758 }
759 if i >= bytes.len() || bytes[i] != b'=' {
760 return None;
761 }
762 i += 1;
763 if i < bytes.len() && bytes[i] == b'=' {
764 return None; }
766
767 let rhs = trimmed[i..].trim();
768 if rhs.is_empty() {
769 return None;
770 }
771 Some((base_var, idx_str, fields, rhs))
772}
773
774fn try_split_field_assign(input: &str) -> Option<(String, Vec<String>, &str)> {
779 let trimmed = input.trim();
780 let bytes = trimmed.as_bytes();
781 let mut i = 0;
782
783 if i >= bytes.len() || !(bytes[i].is_ascii_alphabetic() || bytes[i] == b'_') {
785 return None;
786 }
787 let base_start = i;
788 while i < bytes.len() && (bytes[i].is_ascii_alphanumeric() || bytes[i] == b'_') {
789 i += 1;
790 }
791 let base_var = trimmed[base_start..i].to_string();
792
793 let mut fields = Vec::new();
795 while i < bytes.len() && bytes[i] == b'.' {
796 i += 1;
797 if i >= bytes.len() || !(bytes[i].is_ascii_alphabetic() || bytes[i] == b'_') {
798 return None;
799 }
800 let field_start = i;
801 while i < bytes.len() && (bytes[i].is_ascii_alphanumeric() || bytes[i] == b'_') {
802 i += 1;
803 }
804 fields.push(trimmed[field_start..i].to_string());
805 }
806 if fields.is_empty() {
807 return None;
808 }
809
810 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
812 i += 1;
813 }
814 if i >= bytes.len() || bytes[i] != b'=' {
815 return None;
816 }
817 i += 1;
818 if i < bytes.len() && bytes[i] == b'=' {
819 return None; }
821
822 let rhs = trimmed[i..].trim();
823 if rhs.is_empty() {
824 return None;
825 }
826 Some((base_var, fields, rhs))
827}
828
829fn parse_name_list(rest: &str) -> Result<Vec<String>, String> {
833 let names: Vec<String> = rest
834 .split(|c: char| c.is_whitespace() || c == ',')
835 .filter(|s| !s.is_empty())
836 .map(String::from)
837 .collect();
838 if names.is_empty() {
839 return Err("Expected at least one variable name".to_string());
840 }
841 for name in &names {
842 if !name.starts_with(|c: char| c.is_alphabetic() || c == '_')
843 || name.chars().any(|c| !c.is_alphanumeric() && c != '_')
844 {
845 return Err(format!("Invalid variable name: '{name}'"));
846 }
847 }
848 Ok(names)
849}
850
851pub fn parse(input: &str) -> Result<Stmt, String> {
856 let trimmed = input.trim();
857
858 if let Some(rest) = trimmed
860 .strip_prefix("global")
861 .filter(|r| r.is_empty() || r.starts_with(|c: char| c.is_whitespace() || c == ','))
862 {
863 return Ok(Stmt::Global(parse_name_list(rest)?));
864 }
865
866 if let Some(rest) = trimmed
868 .strip_prefix("persistent")
869 .filter(|r| r.is_empty() || r.starts_with(|c: char| c.is_whitespace() || c == ','))
870 {
871 return Ok(Stmt::Persistent(parse_name_list(rest)?));
872 }
873
874 if trimmed == "return" {
876 return Ok(Stmt::Return);
877 }
878
879 if let Some((base_var, idx_str, fields, rhs)) = try_split_struct_array_field_assign(trimmed) {
881 let idx_tokens = tokenize(idx_str)?;
882 if idx_tokens.is_empty() {
883 return Err("Expected index expression inside '()'".to_string());
884 }
885 let mut idx_pos = 0;
886 let idx_expr = parse_logical_or(&idx_tokens, &mut idx_pos)?;
887 if idx_pos != idx_tokens.len() {
888 return Err("Unexpected token in struct array index expression".to_string());
889 }
890 let rhs_tokens = tokenize(rhs)?;
891 if rhs_tokens.is_empty() {
892 return Err("Expected expression after '='".to_string());
893 }
894 let mut rhs_pos = 0;
895 let rhs_expr = parse_logical_or(&rhs_tokens, &mut rhs_pos)?;
896 if rhs_pos != rhs_tokens.len() {
897 return Err("Unexpected token after expression".to_string());
898 }
899 return Ok(Stmt::StructArrayFieldSet(
900 base_var, idx_expr, fields, rhs_expr,
901 ));
902 }
903
904 if let Some((name, idx_str, rhs)) = try_split_index_assign(trimmed) {
906 let idx_tokens = tokenize(idx_str)?;
907 let indices = parse_index_args(&idx_tokens)?;
908 if indices.len() > 2 {
909 return Err("Indexed assignment supports at most 2 indices".to_string());
910 }
911 let rhs_tokens = tokenize(rhs)?;
912 if rhs_tokens.is_empty() {
913 return Err("Expected expression after '='".to_string());
914 }
915 let mut rhs_pos = 0;
916 let value = parse_logical_or(&rhs_tokens, &mut rhs_pos)?;
917 if rhs_pos != rhs_tokens.len() {
918 return Err("Unexpected token after expression".to_string());
919 }
920 return Ok(Stmt::IndexSet {
921 name,
922 indices,
923 value,
924 });
925 }
926
927 if let Some((base_var, fields, rhs)) = try_split_field_assign(trimmed) {
929 let tokens = tokenize(rhs)?;
930 if tokens.is_empty() {
931 return Err("Expected expression after '='".to_string());
932 }
933 let mut pos = 0;
934 let rhs_expr = parse_logical_or(&tokens, &mut pos)?;
935 if pos != tokens.len() {
936 return Err("Unexpected token after expression".to_string());
937 }
938 return Ok(Stmt::FieldSet(base_var, fields, rhs_expr));
939 }
940
941 if let Some((name, idx_str, rhs)) = try_split_cell_assign(trimmed) {
943 let idx_tokens = tokenize(idx_str)?;
944 if idx_tokens.is_empty() {
945 return Err("Expected index expression inside '{}'".to_string());
946 }
947 let mut idx_pos = 0;
948 let idx_expr = parse_logical_or(&idx_tokens, &mut idx_pos)?;
949 if idx_pos != idx_tokens.len() {
950 return Err("Unexpected token in cell index expression".to_string());
951 }
952 let rhs_tokens = tokenize(rhs)?;
953 if rhs_tokens.is_empty() {
954 return Err("Expected expression after '='".to_string());
955 }
956 let mut rhs_pos = 0;
957 let rhs_expr = parse_logical_or(&rhs_tokens, &mut rhs_pos)?;
958 if rhs_pos != rhs_tokens.len() {
959 return Err("Unexpected token after expression".to_string());
960 }
961 return Ok(Stmt::CellSet(name.to_string(), idx_expr, rhs_expr));
962 }
963
964 if let Some((targets, rhs)) = try_split_multi_assign(trimmed) {
966 let tokens = tokenize(rhs)?;
967 if tokens.is_empty() {
968 return Err("Expected expression after '='".to_string());
969 }
970 let mut pos = 0;
971 let expr = parse_logical_or(&tokens, &mut pos)?;
972 if pos != tokens.len() {
973 return Err("Unexpected token after expression".to_string());
974 }
975 return Ok(Stmt::MultiAssign { targets, expr });
976 }
977
978 if let Some((name, rhs)) = try_split_assignment(trimmed) {
979 let tokens = tokenize(rhs)?;
980 if tokens.is_empty() {
981 return Err("Expected expression after '='".to_string());
982 }
983 let mut pos = 0;
984 let expr = parse_logical_or(&tokens, &mut pos)?;
985 if pos != tokens.len() {
986 return Err("Unexpected token after expression".to_string());
987 }
988 return Ok(Stmt::Assign(name.to_string(), expr));
989 }
990
991 let tokens = tokenize(trimmed)?;
992 if tokens.is_empty() {
993 return Err("Empty expression".to_string());
994 }
995
996 if let Some(stmt) = try_parse_compound(&tokens)? {
999 return Ok(stmt);
1000 }
1001
1002 let mut pos = 0;
1003 let expr = parse_logical_or(&tokens, &mut pos)?;
1004 if pos != tokens.len() {
1005 return Err("Unexpected token after expression".to_string());
1006 }
1007 Ok(Stmt::Expr(expr))
1008}
1009
1010fn try_parse_compound(tokens: &[Token]) -> Result<Option<Stmt>, String> {
1023 if tokens.len() == 2
1025 && let Token::Ident(name) = &tokens[1]
1026 {
1027 let op = match &tokens[0] {
1028 Token::PlusPlus => Some(Op::Add),
1029 Token::MinusMinus => Some(Op::Sub),
1030 _ => None,
1031 };
1032 if let Some(op) = op {
1033 let expr = Expr::BinOp(
1034 Box::new(Expr::Var(name.clone())),
1035 op,
1036 Box::new(Expr::Number(1.0)),
1037 );
1038 return Ok(Some(Stmt::Assign(name.clone(), expr)));
1039 }
1040 }
1041
1042 let name = match tokens.first() {
1044 Some(Token::Ident(n)) => n.clone(),
1045 _ => return Ok(None),
1046 };
1047
1048 if tokens.len() < 2 {
1049 return Ok(None);
1050 }
1051
1052 match &tokens[1] {
1053 Token::PlusPlus | Token::MinusMinus if tokens.len() == 2 => {
1055 let op = if matches!(&tokens[1], Token::PlusPlus) {
1056 Op::Add
1057 } else {
1058 Op::Sub
1059 };
1060 let expr = Expr::BinOp(
1061 Box::new(Expr::Var(name.clone())),
1062 op,
1063 Box::new(Expr::Number(1.0)),
1064 );
1065 Ok(Some(Stmt::Assign(name, expr)))
1066 }
1067
1068 Token::PlusEq | Token::MinusEq | Token::StarEq | Token::SlashEq => {
1070 let op = match &tokens[1] {
1071 Token::PlusEq => Op::Add,
1072 Token::MinusEq => Op::Sub,
1073 Token::StarEq => Op::Mul,
1074 Token::SlashEq => Op::Div,
1075 _ => unreachable!(),
1076 };
1077 let rhs_tokens = &tokens[2..];
1078 if rhs_tokens.is_empty() {
1079 let op_str = match op {
1080 Op::Add => "+=",
1081 Op::Sub => "-=",
1082 Op::Mul => "*=",
1083 Op::Div => "/=",
1084 _ => "op=",
1085 };
1086 return Err(format!("Expected expression after '{op_str}'"));
1087 }
1088 let mut pos = 0;
1089 let rhs = parse_logical_or(rhs_tokens, &mut pos)?;
1090 if pos != rhs_tokens.len() {
1091 return Err("Unexpected token after expression".to_string());
1092 }
1093 let expr = Expr::BinOp(Box::new(Expr::Var(name.clone())), op, Box::new(rhs));
1094 Ok(Some(Stmt::Assign(name, expr)))
1095 }
1096
1097 _ => Ok(None),
1098 }
1099}
1100
1101pub fn is_partial(input: &str) -> bool {
1104 let mut chars = input.trim_start().chars();
1105 match chars.next() {
1106 Some('+') => !matches!(chars.next(), Some('+')),
1108 Some('-') => !matches!(chars.next(), Some('-')),
1109 Some('*' | '/' | '^' | '<' | '>') => true,
1110 Some('.') => matches!(chars.next(), Some('*' | '/' | '^')),
1112 Some('=') => chars.next() == Some('='),
1114 Some('~') => chars.next() == Some('='),
1115 Some('&') => chars.next() == Some('&'),
1117 Some('|') => chars.next() == Some('|'),
1118 _ => false,
1119 }
1120}
1121
1122pub fn split_stmts(input: &str) -> Vec<(&str, bool)> {
1132 let mut separators: Vec<(usize, bool)> = Vec::new();
1134 let mut comment_at = input.len();
1135 let mut in_sq = false;
1136 let mut in_dq = false;
1137 let mut paren_depth: i32 = 0;
1138 let mut bracket_depth: i32 = 0;
1139 let mut brace_depth: i32 = 0;
1140
1141 let chars: Vec<(usize, char)> = input.char_indices().collect();
1142 let mut ci = 0;
1143 while ci < chars.len() {
1144 let (i, c) = chars[ci];
1145 let at_depth0 =
1146 !in_sq && !in_dq && paren_depth == 0 && bracket_depth == 0 && brace_depth == 0;
1147 match c {
1148 '\'' if !in_dq => {
1149 if in_sq {
1150 let next = chars.get(ci + 1).map(|&(_, c)| c);
1152 if next == Some('\'') {
1153 ci += 1; } else {
1155 in_sq = false;
1156 }
1157 } else {
1158 let before = input[..i].trim_end_matches([' ', '\t']);
1159 let is_transpose = before.ends_with(|c: char| {
1160 c.is_alphanumeric()
1161 || c == '_'
1162 || c == ')'
1163 || c == ']'
1164 || c == '\''
1165 || c == '.'
1166 });
1167 if !is_transpose {
1168 in_sq = true;
1169 }
1170 }
1171 }
1172 '"' if !in_sq => in_dq = !in_dq,
1173 '(' if !in_sq && !in_dq => paren_depth += 1,
1174 ')' if !in_sq && !in_dq && paren_depth > 0 => {
1175 paren_depth -= 1;
1176 }
1177 '[' if !in_sq && !in_dq => bracket_depth += 1,
1178 ']' if !in_sq && !in_dq && bracket_depth > 0 => {
1179 bracket_depth -= 1;
1180 }
1181 '{' if !in_sq && !in_dq => brace_depth += 1,
1182 '}' if !in_sq && !in_dq && brace_depth > 0 => {
1183 brace_depth -= 1;
1184 }
1185 '%' | '#' if at_depth0 => {
1186 comment_at = i;
1187 break;
1188 }
1189 ';' if at_depth0 => separators.push((i, true)),
1190 ',' if at_depth0 => separators.push((i, false)),
1191 _ => {}
1192 }
1193 ci += 1;
1194 }
1195
1196 let content = input[..comment_at].trim_end();
1197 if content.is_empty() {
1198 return Vec::new();
1199 }
1200
1201 let mut result = Vec::new();
1202 let mut start = 0;
1203 for &(sc, silent) in &separators {
1204 if sc >= content.len() {
1205 break;
1206 }
1207 let part = content[start..sc].trim();
1208 if !part.is_empty() {
1209 result.push((part, silent));
1210 }
1211 start = sc + 1;
1212 }
1213 if start <= content.len() {
1214 let last = content[start..].trim();
1215 if !last.is_empty() {
1216 result.push((last, false));
1217 }
1218 }
1219 result
1220}
1221
1222pub fn block_depth_delta(line: &str) -> i32 {
1227 let trimmed = line.trim();
1228 if trimmed.starts_with("%{") || trimmed.starts_with("#{") {
1231 let rest = &trimmed[2..];
1232 return if rest.contains("%}") || rest.contains("#}") {
1234 0
1235 } else {
1236 1
1237 };
1238 }
1239 if trimmed.starts_with("%}") || trimmed.starts_with("#}") {
1240 return -1;
1241 }
1242 let stripped = strip_line_comment(line).trim();
1243 match leading_keyword(stripped) {
1244 Some("if") | Some("for") | Some("while") | Some("switch") | Some("do")
1245 | Some("function") | Some("try") => 1,
1246 Some("end") | Some("until") => -1,
1247 _ => 0,
1248 }
1249}
1250
1251pub fn is_single_line_block(line: &str) -> bool {
1256 let stripped = strip_line_comment(line).trim();
1257 if !matches!(
1258 leading_keyword(stripped),
1259 Some("if" | "for" | "while" | "switch" | "do")
1260 ) {
1261 return false;
1262 }
1263 let parts = split_block_line(stripped);
1264 matches!(
1265 parts.last().map(|s| leading_keyword(s.trim())),
1266 Some(Some("end" | "until"))
1267 )
1268}
1269
1270fn strip_block_comments(lines: &[&str]) -> Result<Vec<String>, String> {
1280 let mut result = Vec::with_capacity(lines.len());
1281 let mut in_block = false;
1282
1283 for &line in lines {
1284 let trimmed = line.trim();
1285
1286 if !in_block {
1287 if trimmed.starts_with("%{") || trimmed.starts_with("#{") {
1288 let rest = &trimmed[2..];
1289 if rest.contains("%}") || rest.contains("#}") {
1290 result.push(String::new());
1292 } else {
1293 in_block = true;
1294 result.push(String::new());
1295 }
1296 } else {
1297 result.push(line.to_string());
1298 }
1299 } else {
1300 if trimmed.starts_with("%}") || trimmed.starts_with("#}") {
1301 in_block = false;
1302 }
1303 result.push(String::new());
1304 }
1305 }
1306
1307 if in_block {
1308 Err("Unterminated block comment: missing closing '%}'".to_string())
1309 } else {
1310 Ok(result)
1311 }
1312}
1313
1314fn join_line_continuations(input: &str) -> String {
1319 let mut result = String::new();
1320 let mut pending = String::new();
1321
1322 for line in input.lines() {
1323 let stripped = strip_line_comment(line);
1324 let trimmed = stripped.trim_end();
1325 if let Some(before_dots) = trimmed.strip_suffix("...") {
1326 pending.push_str(before_dots);
1328 pending.push(' ');
1329 } else if pending.is_empty() {
1330 result.push_str(line);
1331 result.push('\n');
1332 } else {
1333 pending.push_str(line.trim_start());
1335 result.push_str(&pending);
1336 result.push('\n');
1337 pending.clear();
1338 }
1339 }
1340 if !pending.is_empty() {
1342 result.push_str(pending.trim_end());
1343 }
1344 result
1345}
1346
1347fn split_block_line(line: &str) -> Vec<String> {
1350 let mut parts = Vec::new();
1351 let mut current = String::new();
1352 let mut in_sq = false;
1353 let mut in_dq = false;
1354 let mut paren: i32 = 0;
1355 let mut bracket: i32 = 0;
1356 let mut brace: i32 = 0;
1357
1358 for c in line.chars() {
1359 let at_depth0 = !in_sq && !in_dq && paren == 0 && bracket == 0 && brace == 0;
1360 match c {
1361 '\'' if !in_dq => {
1362 in_sq = !in_sq;
1363 current.push(c);
1364 }
1365 '"' if !in_sq => {
1366 in_dq = !in_dq;
1367 current.push(c);
1368 }
1369 '(' if !in_sq && !in_dq => {
1370 paren += 1;
1371 current.push(c);
1372 }
1373 ')' if !in_sq && !in_dq => {
1374 if paren > 0 {
1375 paren -= 1;
1376 }
1377 current.push(c);
1378 }
1379 '[' if !in_sq && !in_dq => {
1380 bracket += 1;
1381 current.push(c);
1382 }
1383 ']' if !in_sq && !in_dq => {
1384 if bracket > 0 {
1385 bracket -= 1;
1386 }
1387 current.push(c);
1388 }
1389 '{' if !in_sq && !in_dq => {
1390 brace += 1;
1391 current.push(c);
1392 }
1393 '}' if !in_sq && !in_dq => {
1394 if brace > 0 {
1395 brace -= 1;
1396 }
1397 current.push(c);
1398 }
1399 ';' if at_depth0 => {
1400 let trimmed = current.trim().to_string();
1401 if !trimmed.is_empty() {
1402 parts.push(trimmed);
1403 }
1404 current.clear();
1405 }
1406 _ => current.push(c),
1407 }
1408 }
1409 let last = current.trim().to_string();
1410 if !last.is_empty() {
1411 parts.push(last);
1412 }
1413 parts
1414}
1415
1416pub fn parse_stmts(input: &str) -> Result<Vec<(Stmt, bool)>, String> {
1422 let raw_lines: Vec<&str> = input.lines().collect();
1423 let stripped = strip_block_comments(&raw_lines)?;
1424 let stripped_str = stripped.join("\n");
1425 let joined = join_line_continuations(&stripped_str);
1426 let lines: Vec<&str> = joined.lines().collect();
1427 let mut pos = 0;
1428 parse_stmts_from_lines(&lines, &mut pos, &[])
1429}
1430
1431fn parse_stmts_from_lines(
1434 lines: &[&str],
1435 pos: &mut usize,
1436 stop_at: &[&str],
1437) -> Result<Vec<(Stmt, bool)>, String> {
1438 let mut stmts = Vec::new();
1439
1440 while *pos < lines.len() {
1441 let raw = lines[*pos];
1442 let line = strip_line_comment(raw).trim();
1443
1444 if line.is_empty() {
1445 *pos += 1;
1446 continue;
1447 }
1448
1449 if let Some(kw) = leading_keyword(line)
1451 && stop_at.contains(&kw)
1452 {
1453 return Ok(stmts);
1454 }
1455
1456 if matches!(
1460 leading_keyword(line),
1461 Some("if" | "for" | "while" | "switch" | "do")
1462 ) {
1463 let virtual_parts = split_block_line(line);
1464 let last_kw = virtual_parts
1465 .last()
1466 .map(|s| leading_keyword(s.trim()))
1467 .unwrap_or(None);
1468 if matches!(last_kw, Some("end") | Some("until")) {
1469 let virtual_refs: Vec<&str> = virtual_parts.iter().map(|s| s.as_str()).collect();
1470 let mut vpos = 0;
1471 let inner = parse_stmts_from_lines(&virtual_refs, &mut vpos, stop_at)?;
1472 stmts.extend(inner);
1473 *pos += 1;
1474 continue;
1475 }
1476 }
1477
1478 match leading_keyword(line) {
1479 Some("if") => {
1481 let cond_str = line["if".len()..].trim();
1482 if cond_str.is_empty() {
1483 return Err("Expected condition after 'if'".to_string());
1484 }
1485 let cond = parse_condition(cond_str)?;
1486 *pos += 1;
1487
1488 let body = parse_stmts_from_lines(lines, pos, &["elseif", "else", "end"])?;
1489
1490 let mut elseif_branches = Vec::new();
1491 loop {
1492 if *pos >= lines.len() {
1493 return Err(
1494 "Unexpected end of input inside 'if': expected 'end'".to_string()
1495 );
1496 }
1497 let kw_line = strip_line_comment(lines[*pos]).trim();
1498 if leading_keyword(kw_line) == Some("elseif") {
1499 let ei_str = kw_line["elseif".len()..].trim();
1500 if ei_str.is_empty() {
1501 return Err("Expected condition after 'elseif'".to_string());
1502 }
1503 let ei_cond = parse_condition(ei_str)?;
1504 *pos += 1;
1505 let ei_body =
1506 parse_stmts_from_lines(lines, pos, &["elseif", "else", "end"])?;
1507 elseif_branches.push((ei_cond, ei_body));
1508 } else {
1509 break;
1510 }
1511 }
1512
1513 let else_body = if *pos < lines.len()
1514 && leading_keyword(strip_line_comment(lines[*pos]).trim()) == Some("else")
1515 {
1516 *pos += 1; Some(parse_stmts_from_lines(lines, pos, &["end"])?)
1518 } else {
1519 None
1520 };
1521
1522 expect_end(lines, pos, "if")?;
1523
1524 stmts.push((
1525 Stmt::If {
1526 cond,
1527 body,
1528 elseif_branches,
1529 else_body,
1530 },
1531 false,
1532 ));
1533 }
1534
1535 Some("for") => {
1537 let rest = line["for".len()..].trim();
1538 if rest.is_empty() {
1539 return Err("Expected 'var = expr' after 'for'".to_string());
1540 }
1541 let (var, range_expr) = parse_for_header(rest)?;
1542 *pos += 1;
1543 let body = parse_stmts_from_lines(lines, pos, &["end"])?;
1544 expect_end(lines, pos, "for")?;
1545 stmts.push((
1546 Stmt::For {
1547 var,
1548 range_expr,
1549 body,
1550 },
1551 false,
1552 ));
1553 }
1554
1555 Some("while") => {
1557 let cond_str = line["while".len()..].trim();
1558 if cond_str.is_empty() {
1559 return Err("Expected condition after 'while'".to_string());
1560 }
1561 let cond = parse_condition(cond_str)?;
1562 *pos += 1;
1563 let body = parse_stmts_from_lines(lines, pos, &["end"])?;
1564 expect_end(lines, pos, "while")?;
1565 stmts.push((Stmt::While { cond, body }, false));
1566 }
1567
1568 Some("break") => {
1570 stmts.push((Stmt::Break, false));
1571 *pos += 1;
1572 }
1573 Some("continue") => {
1574 stmts.push((Stmt::Continue, false));
1575 *pos += 1;
1576 }
1577
1578 Some("switch") => {
1580 let expr_str = line["switch".len()..].trim();
1581 if expr_str.is_empty() {
1582 return Err("Expected expression after 'switch'".to_string());
1583 }
1584 let expr = parse_condition(expr_str)?;
1585 *pos += 1;
1586
1587 #[allow(clippy::type_complexity)]
1588 let mut cases: Vec<(Vec<Expr>, Vec<(Stmt, bool)>)> = Vec::new();
1589 let mut otherwise_body: Option<Vec<(Stmt, bool)>> = None;
1590
1591 loop {
1592 if *pos >= lines.len() {
1593 return Err(
1594 "Unexpected end of input inside 'switch': expected 'end'".to_string()
1595 );
1596 }
1597 let kw_line = strip_line_comment(lines[*pos]).trim();
1598 match leading_keyword(kw_line) {
1599 Some("case") => {
1600 let case_str = kw_line["case".len()..].trim();
1601 if case_str.is_empty() {
1602 return Err("Expected value after 'case'".to_string());
1603 }
1604 let case_expr = parse_condition(case_str)?;
1605 *pos += 1;
1606 let case_body =
1607 parse_stmts_from_lines(lines, pos, &["case", "otherwise", "end"])?;
1608 cases.push((vec![case_expr], case_body));
1609 }
1610 Some("otherwise") => {
1611 *pos += 1;
1612 let ob = parse_stmts_from_lines(lines, pos, &["end"])?;
1613 otherwise_body = Some(ob);
1614 break;
1615 }
1616 Some("end") => break,
1617 _ => {
1618 return Err(format!(
1619 "Expected 'case', 'otherwise', or 'end' in switch block, found: '{kw_line}'"
1620 ));
1621 }
1622 }
1623 }
1624
1625 expect_end(lines, pos, "switch")?;
1626 stmts.push((
1627 Stmt::Switch {
1628 expr,
1629 cases,
1630 otherwise_body,
1631 },
1632 false,
1633 ));
1634 }
1635
1636 Some("do") => {
1638 *pos += 1;
1639 let body = parse_stmts_from_lines(lines, pos, &["until"])?;
1640 if *pos >= lines.len() {
1641 return Err("Unexpected end of input inside 'do': expected 'until'".to_string());
1642 }
1643 let until_line = strip_line_comment(lines[*pos]).trim();
1644 if leading_keyword(until_line) != Some("until") {
1645 return Err(format!("Expected 'until', found: '{until_line}'"));
1646 }
1647 let cond_str = until_line["until".len()..].trim();
1648 if cond_str.is_empty() {
1649 return Err("Expected condition after 'until'".to_string());
1650 }
1651 let cond = parse_condition(cond_str)?;
1652 *pos += 1;
1653 stmts.push((Stmt::DoUntil { body, cond }, false));
1654 }
1655
1656 Some("function") => {
1658 let header = line["function".len()..].trim();
1659 if header.is_empty() {
1660 return Err("Expected function header after 'function'".to_string());
1661 }
1662 let (name, outputs, params) = parse_function_header(header)?;
1663
1664 *pos += 1;
1665 let body_start = *pos;
1668
1669 let doc = {
1672 let mut doc_lines: Vec<String> = Vec::new();
1673 let mut scan = body_start;
1674 while scan < lines.len() {
1675 let raw = lines[scan].trim();
1676 if raw.starts_with('%') || raw.starts_with('#') {
1677 let stripped = raw.trim_start_matches(['%', '#']);
1678 let text = stripped
1679 .strip_prefix(' ')
1680 .unwrap_or(stripped)
1681 .trim_end()
1682 .to_string();
1683 doc_lines.push(text);
1684 scan += 1;
1685 } else {
1686 break;
1687 }
1688 }
1689 if doc_lines.is_empty() {
1690 None
1691 } else {
1692 Some(doc_lines.join("\n"))
1693 }
1694 };
1695
1696 let mut depth: i32 = 1;
1697 while *pos < lines.len() && depth > 0 {
1698 let l = strip_line_comment(lines[*pos]).trim();
1699 let delta = if is_single_line_block(l) {
1700 0
1701 } else {
1702 block_depth_delta(l)
1703 };
1704 depth += delta;
1705 if depth == 0 {
1706 break;
1707 }
1708 *pos += 1;
1709 }
1710 if depth != 0 {
1711 return Err(format!(
1712 "Unexpected end of input: expected 'end' to close 'function {name}'"
1713 ));
1714 }
1715 let body_source = lines[body_start..*pos].join("\n");
1716 *pos += 1; stmts.push((
1718 Stmt::FunctionDef {
1719 name,
1720 outputs,
1721 params,
1722 body_source,
1723 doc,
1724 },
1725 false,
1726 ));
1727 }
1728
1729 Some("return") => {
1731 stmts.push((Stmt::Return, false));
1732 *pos += 1;
1733 }
1734
1735 Some("try") => {
1737 *pos += 1;
1738 let try_body = parse_stmts_from_lines(lines, pos, &["catch", "end"])?;
1739
1740 if *pos >= lines.len() {
1741 return Err(
1742 "Unexpected end of input inside 'try': expected 'catch' or 'end'"
1743 .to_string(),
1744 );
1745 }
1746 let kw_line = strip_line_comment(lines[*pos]).trim();
1747 let (catch_var, catch_body) = if leading_keyword(kw_line) == Some("catch") {
1748 let catch_rest = kw_line["catch".len()..].trim();
1749 let catch_var = if catch_rest.is_empty() {
1750 None
1751 } else if is_valid_ident(catch_rest) {
1752 Some(catch_rest.to_string())
1753 } else {
1754 return Err(format!(
1755 "Expected identifier after 'catch', got '{catch_rest}'"
1756 ));
1757 };
1758 *pos += 1;
1759 let catch_body = parse_stmts_from_lines(lines, pos, &["end"])?;
1760 (catch_var, catch_body)
1761 } else {
1762 (None, vec![])
1764 };
1765
1766 expect_end(lines, pos, "try")?;
1767 stmts.push((
1768 Stmt::TryCatch {
1769 try_body,
1770 catch_var,
1771 catch_body,
1772 },
1773 false,
1774 ));
1775 }
1776
1777 Some(kw @ ("end" | "else" | "elseif" | "case" | "otherwise" | "until" | "catch")) => {
1779 return Err(format!("Unexpected '{kw}' without matching block opener"));
1780 }
1781
1782 _ => {
1784 if line == "clear" {
1788 stmts.push((Stmt::Expr(Expr::Call("clear".to_string(), vec![])), false));
1789 *pos += 1;
1790 continue;
1791 }
1792 if let Some(rest) = line
1793 .strip_prefix("clear")
1794 .filter(|r| r.starts_with(|c: char| c.is_whitespace()))
1795 {
1796 let names: Vec<Expr> = rest
1797 .split_whitespace()
1798 .map(|n| Expr::StrLiteral(n.to_string()))
1799 .collect();
1800 stmts.push((Stmt::Expr(Expr::Call("clear".to_string(), names)), false));
1801 *pos += 1;
1802 continue;
1803 }
1804
1805 if line == "format"
1807 || line
1808 .strip_prefix("format")
1809 .is_some_and(|r| r.starts_with(|c: char| c.is_whitespace()))
1810 {
1811 let arg = line
1812 .strip_prefix("format")
1813 .map(str::trim)
1814 .unwrap_or("")
1815 .to_string();
1816 let args = if arg.is_empty() {
1817 vec![]
1818 } else {
1819 vec![Expr::StrLiteral(arg)]
1820 };
1821 stmts.push((Stmt::Expr(Expr::Call("format".to_string(), args)), true));
1822 *pos += 1;
1823 continue;
1824 }
1825
1826 for (stmt_str, silent) in split_stmts(raw) {
1827 stmts.push((parse(stmt_str)?, silent));
1828 }
1829 *pos += 1;
1830 }
1831 }
1832 }
1833
1834 Ok(stmts)
1835}
1836
1837fn expect_end(lines: &[&str], pos: &mut usize, opener: &str) -> Result<(), String> {
1839 if *pos >= lines.len() {
1840 return Err(format!(
1841 "Unexpected end of input: expected 'end' to close '{opener}'"
1842 ));
1843 }
1844 let kw_line = strip_line_comment(lines[*pos]).trim();
1845 if leading_keyword(kw_line) != Some("end") {
1846 return Err(format!(
1847 "Expected 'end' to close '{opener}', found '{kw_line}'"
1848 ));
1849 }
1850 *pos += 1;
1851 Ok(())
1852}
1853
1854fn strip_line_comment(line: &str) -> &str {
1856 let mut in_sq = false;
1857 let mut in_dq = false;
1858 for (i, c) in line.char_indices() {
1859 match c {
1860 '\'' if !in_dq => in_sq = !in_sq,
1861 '"' if !in_sq => in_dq = !in_dq,
1862 '%' | '#' if !in_sq && !in_dq => return &line[..i],
1863 _ => {}
1864 }
1865 }
1866 line
1867}
1868
1869fn leading_keyword(line: &str) -> Option<&str> {
1873 let end = line
1874 .find(|c: char| !c.is_alphanumeric() && c != '_')
1875 .unwrap_or(line.len());
1876 let word = &line[..end];
1877 match word {
1878 "if" | "elseif" | "else" | "end" | "for" | "while" | "break" | "continue" | "switch"
1879 | "case" | "otherwise" | "do" | "until" | "function" | "return" | "try" | "catch" => {
1880 Some(word)
1881 }
1882 _ => None,
1883 }
1884}
1885
1886fn parse_function_header(header: &str) -> Result<(String, Vec<String>, Vec<String>), String> {
1893 if let Some(eq_pos) = header.find('=')
1895 && !header[eq_pos + 1..].starts_with('=')
1896 {
1897 let lhs = header[..eq_pos].trim();
1898 let rhs = header[eq_pos + 1..].trim();
1899 let outputs = parse_output_list(lhs)?;
1900 let (name, params) = parse_func_name_params(rhs)?;
1901 return Ok((name, outputs, params));
1902 }
1903 let (name, params) = parse_func_name_params(header.trim())?;
1905 Ok((name, vec![], params))
1906}
1907
1908fn parse_output_list(lhs: &str) -> Result<Vec<String>, String> {
1910 let lhs = lhs.trim();
1911 if lhs.starts_with('[') && lhs.ends_with(']') {
1912 let inner = &lhs[1..lhs.len() - 1];
1913 inner
1914 .split(',')
1915 .map(|s| {
1916 let s = s.trim();
1917 if is_valid_ident(s) {
1918 Ok(s.to_string())
1919 } else {
1920 Err(format!("Invalid output variable name: '{s}'"))
1921 }
1922 })
1923 .collect()
1924 } else if is_valid_ident(lhs) {
1925 Ok(vec![lhs.to_string()])
1926 } else {
1927 Err(format!("Invalid function output list: '{lhs}'"))
1928 }
1929}
1930
1931fn parse_func_name_params(s: &str) -> Result<(String, Vec<String>), String> {
1933 let s = s.trim();
1934 if let Some(paren_pos) = s.find('(') {
1935 let name = s[..paren_pos].trim();
1936 if !is_valid_ident(name) {
1937 return Err(format!("Invalid function name: '{name}'"));
1938 }
1939 let rest = s[paren_pos + 1..].trim();
1940 if !rest.ends_with(')') {
1941 return Err(format!("Expected ')' in function header: '{s}'"));
1942 }
1943 let params_str = rest[..rest.len() - 1].trim();
1944 let params = if params_str.is_empty() {
1945 vec![]
1946 } else {
1947 params_str
1948 .split(',')
1949 .map(|p| {
1950 let p = p.trim();
1951 if is_valid_ident(p) {
1952 Ok(p.to_string())
1953 } else {
1954 Err(format!("Invalid parameter name: '{p}'"))
1955 }
1956 })
1957 .collect::<Result<Vec<_>, _>>()?
1958 };
1959 Ok((name.to_string(), params))
1960 } else {
1961 if !is_valid_ident(s) {
1962 return Err(format!("Invalid function name: '{s}'"));
1963 }
1964 Ok((s.to_string(), vec![]))
1965 }
1966}
1967
1968fn parse_condition(cond_str: &str) -> Result<Expr, String> {
1970 match parse(cond_str)? {
1971 Stmt::Expr(e) => Ok(e),
1972 Stmt::Assign(_, _) => Err("Expected condition expression, found assignment".to_string()),
1973 _ => Err("Expected condition expression".to_string()),
1974 }
1975}
1976
1977fn parse_for_header(rest: &str) -> Result<(String, Expr), String> {
1979 match parse(rest)? {
1980 Stmt::Assign(var, expr) => Ok((var, expr)),
1981 _ => Err(format!(
1982 "Expected 'variable = expression' in 'for' header, found: '{rest}'"
1983 )),
1984 }
1985}
1986
1987fn try_split_multi_assign(input: &str) -> Option<(Vec<String>, &str)> {
1992 let trimmed = input.trim();
1993 if !trimmed.starts_with('[') {
1994 return None;
1995 }
1996 let close = trimmed.find(']')?;
1997 let rest = trimmed[close + 1..].trim();
1998 if !rest.starts_with('=') || rest.starts_with("==") {
1999 return None;
2000 }
2001 let rhs = rest[1..].trim();
2002 let inner = trimmed[1..close].trim();
2003 if inner.is_empty() {
2004 return None;
2005 }
2006 let targets: Vec<String> = inner.split(',').map(|s| s.trim().to_string()).collect();
2007 for t in &targets {
2008 if t != "~" && !is_valid_ident(t) {
2009 return None;
2010 }
2011 }
2012 Some((targets, rhs))
2013}
2014
2015fn try_split_cell_assign(input: &str) -> Option<(&str, &str, &str)> {
2018 let trimmed = input.trim();
2019 let brace_pos = trimmed.find('{')?;
2021 let name = trimmed[..brace_pos].trim();
2022 if !is_valid_ident(name) {
2023 return None;
2024 }
2025 let after_open = &trimmed[brace_pos + 1..];
2027 let close_pos = after_open.find('}')?;
2028 let idx_str = after_open[..close_pos].trim();
2029 let after_close = after_open[close_pos + 1..].trim();
2031 if !after_close.starts_with('=') || after_close.starts_with("==") {
2032 return None;
2033 }
2034 let rhs = after_close[1..].trim();
2035 Some((name, idx_str, rhs))
2036}
2037
2038fn try_split_index_assign(input: &str) -> Option<(String, &str, &str)> {
2043 let trimmed = input.trim();
2044 let bytes = trimmed.as_bytes();
2045 let mut i = 0;
2046
2047 if i >= bytes.len() || !(bytes[i].is_ascii_alphabetic() || bytes[i] == b'_') {
2049 return None;
2050 }
2051 let name_start = i;
2052 while i < bytes.len() && (bytes[i].is_ascii_alphanumeric() || bytes[i] == b'_') {
2053 i += 1;
2054 }
2055 let name = trimmed[name_start..i].to_string();
2056
2057 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
2059 i += 1;
2060 }
2061 if i >= bytes.len() || bytes[i] != b'(' {
2062 return None;
2063 }
2064 i += 1;
2065
2066 let idx_start = i;
2068 let mut depth = 1usize;
2069 while i < bytes.len() && depth > 0 {
2070 match bytes[i] {
2071 b'(' | b'[' | b'{' => depth += 1,
2072 b')' | b']' | b'}' => depth -= 1,
2073 _ => {}
2074 }
2075 i += 1;
2076 }
2077 if depth != 0 {
2078 return None;
2079 }
2080 let idx_str = trimmed[idx_start..i - 1].trim();
2081
2082 let rest = trimmed[i..].trim_start();
2084 if rest.starts_with('.') {
2085 return None;
2086 }
2087 if !rest.starts_with('=') || rest.starts_with("==") {
2089 return None;
2090 }
2091 let rhs = rest[1..].trim();
2092 if rhs.is_empty() {
2093 return None;
2094 }
2095 Some((name, idx_str, rhs))
2096}
2097
2098fn parse_index_args(tokens: &[Token]) -> Result<Vec<Expr>, String> {
2100 if tokens.is_empty() {
2101 return Err("Expected index expression inside '()'".to_string());
2102 }
2103 let mut pos = 0;
2104 let mut args = Vec::new();
2105 loop {
2106 args.push(parse_call_arg(tokens, &mut pos)?);
2107 match tokens.get(pos) {
2108 Some(Token::Comma) => {
2109 pos += 1;
2110 }
2111 None => break,
2112 Some(_) => return Err("Unexpected token in index expression".to_string()),
2113 }
2114 }
2115 Ok(args)
2116}
2117
2118fn try_split_assignment(input: &str) -> Option<(&str, &str)> {
2121 let trimmed = input.trim();
2122 let eq_pos = trimmed.find('=')?;
2123 if trimmed[eq_pos + 1..].starts_with('=') {
2125 return None;
2126 }
2127 let lhs = trimmed[..eq_pos].trim();
2128 let rhs = trimmed[eq_pos + 1..].trim();
2129 if is_valid_ident(lhs) {
2130 Some((lhs, rhs))
2131 } else {
2132 None
2133 }
2134}
2135
2136fn is_valid_ident(s: &str) -> bool {
2137 let mut chars = s.chars();
2138 match chars.next() {
2139 Some(c) if c.is_alphabetic() || c == '_' => chars.all(|c| c.is_alphanumeric() || c == '_'),
2140 _ => false,
2141 }
2142}
2143
2144fn parse_call_arg(tokens: &[Token], pos: &mut usize) -> Result<Expr, String> {
2148 if matches!(tokens.get(*pos), Some(Token::Colon)) {
2149 *pos += 1;
2150 return Ok(Expr::Colon);
2151 }
2152 parse_logical_or(tokens, pos)
2153}
2154
2155fn parse_logical_or(tokens: &[Token], pos: &mut usize) -> Result<Expr, String> {
2157 let mut left = parse_logical_and(tokens, pos)?;
2158 while matches!(tokens.get(*pos), Some(Token::PipePipe)) {
2159 *pos += 1;
2160 let right = parse_logical_and(tokens, pos)?;
2161 left = Expr::BinOp(Box::new(left), Op::Or, Box::new(right));
2162 }
2163 Ok(left)
2164}
2165
2166fn parse_logical_and(tokens: &[Token], pos: &mut usize) -> Result<Expr, String> {
2168 let mut left = parse_elem_or(tokens, pos)?;
2169 while matches!(tokens.get(*pos), Some(Token::AmpAmp)) {
2170 *pos += 1;
2171 let right = parse_elem_or(tokens, pos)?;
2172 left = Expr::BinOp(Box::new(left), Op::And, Box::new(right));
2173 }
2174 Ok(left)
2175}
2176
2177fn parse_elem_or(tokens: &[Token], pos: &mut usize) -> Result<Expr, String> {
2179 let mut left = parse_elem_and(tokens, pos)?;
2180 while matches!(tokens.get(*pos), Some(Token::Pipe)) {
2181 *pos += 1;
2182 let right = parse_elem_and(tokens, pos)?;
2183 left = Expr::BinOp(Box::new(left), Op::ElemOr, Box::new(right));
2184 }
2185 Ok(left)
2186}
2187
2188fn parse_elem_and(tokens: &[Token], pos: &mut usize) -> Result<Expr, String> {
2190 let mut left = parse_comparison(tokens, pos)?;
2191 while matches!(tokens.get(*pos), Some(Token::Amp)) {
2192 *pos += 1;
2193 let right = parse_comparison(tokens, pos)?;
2194 left = Expr::BinOp(Box::new(left), Op::ElemAnd, Box::new(right));
2195 }
2196 Ok(left)
2197}
2198
2199fn parse_comparison(tokens: &[Token], pos: &mut usize) -> Result<Expr, String> {
2202 let left = parse_range(tokens, pos)?;
2203 let op = match tokens.get(*pos) {
2204 Some(Token::EqEq) => Op::Eq,
2205 Some(Token::NotEq) => Op::NotEq,
2206 Some(Token::Lt) => Op::Lt,
2207 Some(Token::Gt) => Op::Gt,
2208 Some(Token::LtEq) => Op::LtEq,
2209 Some(Token::GtEq) => Op::GtEq,
2210 _ => return Ok(left),
2211 };
2212 *pos += 1;
2213 let right = parse_range(tokens, pos)?;
2214 Ok(Expr::BinOp(Box::new(left), op, Box::new(right)))
2215}
2216
2217fn parse_range(tokens: &[Token], pos: &mut usize) -> Result<Expr, String> {
2221 let start = parse_expr(tokens, pos)?;
2222 if !matches!(tokens.get(*pos), Some(Token::Colon)) {
2223 return Ok(start);
2224 }
2225 *pos += 1;
2226 let second = parse_expr(tokens, pos)?;
2227 if !matches!(tokens.get(*pos), Some(Token::Colon)) {
2228 return Ok(Expr::Range(Box::new(start), None, Box::new(second)));
2230 }
2231 *pos += 1;
2232 let third = parse_expr(tokens, pos)?;
2233 Ok(Expr::Range(
2235 Box::new(start),
2236 Some(Box::new(second)),
2237 Box::new(third),
2238 ))
2239}
2240
2241fn parse_expr(tokens: &[Token], pos: &mut usize) -> Result<Expr, String> {
2243 let mut left = parse_term(tokens, pos)?;
2244
2245 while *pos < tokens.len() {
2246 match &tokens[*pos] {
2247 Token::Plus => {
2248 *pos += 1;
2249 let right = parse_term(tokens, pos)?;
2250 left = Expr::BinOp(Box::new(left), Op::Add, Box::new(right));
2251 }
2252 Token::Minus => {
2253 *pos += 1;
2254 let right = parse_term(tokens, pos)?;
2255 left = Expr::BinOp(Box::new(left), Op::Sub, Box::new(right));
2256 }
2257 _ => break,
2258 }
2259 }
2260
2261 Ok(left)
2262}
2263
2264fn parse_term(tokens: &[Token], pos: &mut usize) -> Result<Expr, String> {
2267 let mut left = parse_power(tokens, pos)?;
2268
2269 while *pos < tokens.len() {
2270 match &tokens[*pos] {
2271 Token::Star => {
2272 *pos += 1;
2273 let right = parse_power(tokens, pos)?;
2274 left = Expr::BinOp(Box::new(left), Op::Mul, Box::new(right));
2275 }
2276 Token::Slash => {
2277 *pos += 1;
2278 let right = parse_power(tokens, pos)?;
2279 left = Expr::BinOp(Box::new(left), Op::Div, Box::new(right));
2280 }
2281 Token::DotStar => {
2282 *pos += 1;
2283 let right = parse_power(tokens, pos)?;
2284 left = Expr::BinOp(Box::new(left), Op::ElemMul, Box::new(right));
2285 }
2286 Token::DotSlash => {
2287 *pos += 1;
2288 let right = parse_power(tokens, pos)?;
2289 left = Expr::BinOp(Box::new(left), Op::ElemDiv, Box::new(right));
2290 }
2291 Token::Backslash => {
2292 *pos += 1;
2293 let right = parse_power(tokens, pos)?;
2294 left = Expr::BinOp(Box::new(left), Op::LDiv, Box::new(right));
2295 }
2296 Token::LParen => {
2297 let right = parse_power(tokens, pos)?;
2299 left = Expr::BinOp(Box::new(left), Op::Mul, Box::new(right));
2300 }
2301 _ => break,
2302 }
2303 }
2304
2305 Ok(left)
2306}
2307
2308fn parse_power(tokens: &[Token], pos: &mut usize) -> Result<Expr, String> {
2311 let base = parse_unary(tokens, pos)?;
2312 if *pos < tokens.len() {
2313 match &tokens[*pos] {
2314 Token::Caret | Token::StarStar => {
2315 *pos += 1;
2316 let exp = parse_power(tokens, pos)?;
2317 return Ok(Expr::BinOp(Box::new(base), Op::Pow, Box::new(exp)));
2318 }
2319 Token::DotCaret => {
2320 *pos += 1;
2321 let exp = parse_power(tokens, pos)?;
2322 return Ok(Expr::BinOp(Box::new(base), Op::ElemPow, Box::new(exp)));
2323 }
2324 _ => {}
2325 }
2326 }
2327 Ok(base)
2328}
2329
2330fn parse_unary(tokens: &[Token], pos: &mut usize) -> Result<Expr, String> {
2333 if *pos < tokens.len() {
2334 match &tokens[*pos] {
2335 Token::Plus => {
2336 *pos += 1;
2337 return parse_unary(tokens, pos); }
2339 Token::Minus => {
2340 *pos += 1;
2341 let expr = parse_unary(tokens, pos)?;
2342 return Ok(Expr::UnaryMinus(Box::new(expr)));
2343 }
2344 Token::Tilde => {
2345 *pos += 1;
2346 let expr = parse_unary(tokens, pos)?;
2347 return Ok(Expr::UnaryNot(Box::new(expr)));
2348 }
2349 _ => {}
2350 }
2351 }
2352 parse_primary(tokens, pos)
2353}
2354
2355fn parse_primary(tokens: &[Token], pos: &mut usize) -> Result<Expr, String> {
2358 if *pos >= tokens.len() {
2359 return Err("Unexpected end of expression".to_string());
2360 }
2361
2362 let mut expr = match &tokens[*pos] {
2363 Token::Number(n) => {
2364 let n = *n;
2365 *pos += 1;
2366 Expr::Number(n)
2367 }
2368 Token::LBrace => {
2369 *pos += 1;
2370 let mut elems = Vec::new();
2372 loop {
2373 match tokens.get(*pos) {
2374 None => return Err("Expected '}'".to_string()),
2375 Some(Token::RBrace) => {
2376 *pos += 1;
2377 break;
2378 }
2379 Some(Token::Comma) => {
2380 *pos += 1;
2381 }
2382 _ => {
2383 elems.push(parse_logical_or(tokens, pos)?);
2384 }
2385 }
2386 }
2387 Expr::CellLiteral(elems)
2388 }
2389 Token::Ident(name) => {
2390 let name = name.clone();
2391 *pos += 1;
2392 if *pos < tokens.len()
2394 && let Token::LBrace = &tokens[*pos]
2395 {
2396 *pos += 1;
2397 let idx = parse_logical_or(tokens, pos)?;
2398 if *pos >= tokens.len() {
2399 return Err("Expected '}'".to_string());
2400 }
2401 match &tokens[*pos] {
2402 Token::RBrace => {
2403 *pos += 1;
2404 Expr::CellIndex(Box::new(Expr::Var(name)), Box::new(idx))
2405 }
2406 _ => return Err("Expected '}'".to_string()),
2407 }
2408 } else if *pos < tokens.len()
2410 && let Token::LParen = &tokens[*pos]
2411 {
2412 *pos += 1;
2413 let args = if *pos < tokens.len() {
2414 if let Token::RParen = &tokens[*pos] {
2415 vec![]
2418 } else {
2419 let mut list = vec![parse_call_arg(tokens, pos)?];
2420 while *pos < tokens.len() {
2421 if let Token::Comma = &tokens[*pos] {
2422 *pos += 1;
2423 list.push(parse_call_arg(tokens, pos)?);
2424 } else {
2425 break;
2426 }
2427 }
2428 list
2429 }
2430 } else {
2431 return Err("Expected closing ')'".to_string());
2432 };
2433 if *pos >= tokens.len() {
2434 return Err("Expected closing ')'".to_string());
2435 }
2436 match &tokens[*pos] {
2437 Token::RParen => {
2438 *pos += 1;
2439 Expr::Call(name, args)
2440 }
2441 _ => return Err("Expected closing ')'".to_string()),
2442 }
2443 } else {
2444 match name.as_str() {
2446 "pi" => Expr::Number(std::f64::consts::PI),
2447 "e" => Expr::Var("e".to_string()),
2449 "nan" | "NaN" => Expr::Number(f64::NAN),
2450 "inf" | "Inf" => Expr::Number(f64::INFINITY),
2451 _ => Expr::Var(name),
2453 }
2454 }
2455 }
2456 Token::LParen => {
2457 *pos += 1;
2458 let inner = parse_logical_or(tokens, pos)?;
2459 if *pos >= tokens.len() {
2460 return Err("Expected closing ')'".to_string());
2461 }
2462 match &tokens[*pos] {
2463 Token::RParen => {
2464 *pos += 1;
2465 inner
2466 }
2467 _ => return Err("Expected closing ')'".to_string()),
2468 }
2469 }
2470 Token::LBracket => {
2471 *pos += 1;
2472 parse_matrix(tokens, pos)?
2473 }
2474 Token::Str(s) => {
2475 let s = s.clone();
2476 *pos += 1;
2477 Expr::StrLiteral(s)
2478 }
2479 Token::StringObj(s) => {
2480 let s = s.clone();
2481 *pos += 1;
2482 Expr::StringObjLiteral(s)
2483 }
2484 Token::At => {
2485 *pos += 1;
2486 if let Some(Token::Ident(name)) = tokens.get(*pos) {
2488 let name = name.clone();
2489 *pos += 1;
2490 return Ok(Expr::FuncHandle(name));
2491 }
2492 if !matches!(tokens.get(*pos), Some(Token::LParen)) {
2494 return Err("Expected '(' or identifier after '@'".to_string());
2495 }
2496 *pos += 1;
2497 let mut params = Vec::new();
2498 loop {
2499 match tokens.get(*pos) {
2500 Some(Token::RParen) => {
2501 *pos += 1;
2502 break;
2503 }
2504 Some(Token::Ident(name)) => {
2505 params.push(name.clone());
2506 *pos += 1;
2507 if matches!(tokens.get(*pos), Some(Token::Comma)) {
2508 *pos += 1;
2509 }
2510 }
2511 None => return Err("Expected ')' in lambda parameter list".to_string()),
2512 _ => return Err("Expected parameter name in lambda".to_string()),
2513 }
2514 }
2515 let body = parse_logical_or(tokens, pos)?;
2516 let source = format!("@({}) {}", params.join(", "), expr_to_string(&body));
2517 Expr::Lambda {
2518 params,
2519 body: Box::new(body),
2520 source,
2521 }
2522 }
2523 _ => {
2524 return Err(
2525 "Expected number, function, variable, string, '-', '[', '@', or '('".to_string(),
2526 );
2527 }
2528 };
2529
2530 loop {
2533 match tokens.get(*pos) {
2534 Some(Token::Dot) => {
2535 *pos += 1;
2536 match tokens.get(*pos) {
2537 Some(Token::Ident(field)) => {
2538 let field = field.clone();
2539 *pos += 1;
2540 expr = Expr::FieldGet(Box::new(expr), field);
2541 }
2542 _ => return Err("Expected field name after '.'".to_string()),
2543 }
2544 }
2545 Some(Token::LParen) => {
2546 if let Some(segs) = field_chain_segments(&expr)
2549 && segs.len() >= 2
2550 {
2551 *pos += 1;
2552 let args = if matches!(tokens.get(*pos), Some(Token::RParen)) {
2553 vec![]
2554 } else {
2555 let mut list = vec![parse_call_arg(tokens, pos)?];
2556 while matches!(tokens.get(*pos), Some(Token::Comma)) {
2557 *pos += 1;
2558 list.push(parse_call_arg(tokens, pos)?);
2559 }
2560 list
2561 };
2562 if !matches!(tokens.get(*pos), Some(Token::RParen)) {
2563 return Err("Expected closing ')'".to_string());
2564 }
2565 *pos += 1;
2566 expr = Expr::DotCall(segs, args);
2567 } else {
2568 break;
2569 }
2570 }
2571 Some(Token::Apostrophe) => {
2572 *pos += 1;
2573 expr = Expr::Transpose(Box::new(expr));
2574 }
2575 Some(Token::DotApostrophe) => {
2576 *pos += 1;
2577 expr = Expr::PlainTranspose(Box::new(expr));
2578 }
2579 _ => break,
2580 }
2581 }
2582
2583 Ok(expr)
2584}
2585
2586fn field_chain_segments(e: &Expr) -> Option<Vec<String>> {
2591 match e {
2592 Expr::Var(name) => Some(vec![name.clone()]),
2593 Expr::FieldGet(inner, field) => {
2594 let mut segs = field_chain_segments(inner)?;
2595 segs.push(field.clone());
2596 Some(segs)
2597 }
2598 _ => None,
2599 }
2600}
2601
2602fn parse_matrix(tokens: &[Token], pos: &mut usize) -> Result<Expr, String> {
2604 if matches!(tokens.get(*pos), Some(Token::RBracket)) {
2606 *pos += 1;
2607 return Ok(Expr::Matrix(vec![]));
2608 }
2609 let mut rows: Vec<Vec<Expr>> = Vec::new();
2610 let mut current_row: Vec<Expr> = Vec::new();
2611 loop {
2612 match tokens.get(*pos) {
2613 None => return Err("Expected ']'".to_string()),
2614 Some(Token::RBracket) => {
2615 *pos += 1;
2616 if !current_row.is_empty() {
2617 rows.push(current_row);
2618 }
2619 break;
2620 }
2621 Some(Token::Semicolon) => {
2622 *pos += 1;
2623 if !current_row.is_empty() {
2624 rows.push(std::mem::take(&mut current_row));
2625 }
2626 }
2627 Some(Token::Comma) => {
2628 *pos += 1;
2629 }
2630 _ => {
2631 current_row.push(parse_logical_or(tokens, pos)?);
2632 }
2633 }
2634 }
2635 Ok(Expr::Matrix(rows))
2636}
2637
2638#[cfg(test)]
2639#[path = "parser_tests.rs"]
2640mod tests;