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