1#![allow(missing_docs)]
7
8use super::ast::{BinaryOp, Expr, Program, Statement, Type, UnaryOp};
9use crate::error::{AlgorithmError, Result};
10use crate::expr_depth::{check_source_nesting_depth, guard_depth};
11use pest::Parser;
12use pest_derive::Parser;
13
14#[cfg(not(feature = "std"))]
15use alloc::{boxed::Box, string::String, vec::Vec};
16
17#[derive(Parser)]
18#[grammar = "dsl/grammar.pest"]
19struct RasterParser;
20
21pub fn parse_program(input: &str) -> Result<Program> {
31 check_source_nesting_depth(input, "dsl")?;
32
33 let pairs = RasterParser::parse(Rule::program, input).map_err(|e| {
34 AlgorithmError::InvalidParameter {
35 parameter: "dsl",
36 message: format!("Parse error: {e}"),
37 }
38 })?;
39
40 let mut statements = Vec::new();
41
42 for pair in pairs {
43 match pair.as_rule() {
44 Rule::program => {
45 for inner in pair.into_inner() {
46 if inner.as_rule() == Rule::statement {
47 statements.push(parse_statement(inner, 0)?);
48 }
49 }
50 }
51 Rule::EOI => {}
52 _ => {
53 return Err(AlgorithmError::InvalidParameter {
54 parameter: "dsl",
55 message: format!("Unexpected rule: {:?}", pair.as_rule()),
56 });
57 }
58 }
59 }
60
61 Ok(Program { statements })
62}
63
64pub fn parse_expression(input: &str) -> Result<Expr> {
72 check_source_nesting_depth(input, "dsl")?;
73
74 let mut full_input = String::from(input);
75 if !full_input.ends_with(';') {
76 full_input.push(';');
77 }
78
79 let pairs = RasterParser::parse(Rule::program, &full_input).map_err(|e| {
80 AlgorithmError::InvalidParameter {
81 parameter: "dsl",
82 message: format!("Parse error: {e}"),
83 }
84 })?;
85
86 for pair in pairs {
87 if pair.as_rule() == Rule::program {
88 for inner in pair.into_inner() {
89 if inner.as_rule() == Rule::statement {
90 let stmt_inner = inner.into_inner().next().ok_or_else(|| {
92 AlgorithmError::InvalidParameter {
93 parameter: "dsl",
94 message: "Empty statement".to_string(),
95 }
96 })?;
97
98 if stmt_inner.as_rule() == Rule::expr_stmt {
100 return parse_expr_stmt(stmt_inner, 0);
101 }
102 }
103 }
104 }
105 }
106
107 Err(AlgorithmError::InvalidParameter {
108 parameter: "dsl",
109 message: "No expression found".to_string(),
110 })
111}
112
113fn parse_statement(pair: pest::iterators::Pair<Rule>, depth: usize) -> Result<Statement> {
114 guard_depth(depth, "dsl")?;
115
116 let inner = pair
117 .into_inner()
118 .next()
119 .ok_or_else(|| AlgorithmError::InvalidParameter {
120 parameter: "dsl",
121 message: "Empty statement".to_string(),
122 })?;
123
124 match inner.as_rule() {
125 Rule::variable_decl => {
126 let mut parts = inner.into_inner();
127 let name = parts
128 .next()
129 .ok_or_else(|| AlgorithmError::InvalidParameter {
130 parameter: "dsl",
131 message: "Missing variable name".to_string(),
132 })?
133 .as_str()
134 .to_string();
135
136 let value = parts
137 .next()
138 .ok_or_else(|| AlgorithmError::InvalidParameter {
139 parameter: "dsl",
140 message: "Missing variable value".to_string(),
141 })?;
142
143 Ok(Statement::VariableDecl {
144 name,
145 value: Box::new(parse_expr(value, depth)?),
146 })
147 }
148 Rule::function_decl => {
149 let mut parts = inner.into_inner();
150 let name = parts
151 .next()
152 .ok_or_else(|| AlgorithmError::InvalidParameter {
153 parameter: "dsl",
154 message: "Missing function name".to_string(),
155 })?
156 .as_str()
157 .to_string();
158
159 let mut params = Vec::new();
160 let mut body_pair = None;
161
162 for part in parts {
163 match part.as_rule() {
164 Rule::param_list => {
165 for param in part.into_inner() {
166 params.push(param.as_str().to_string());
167 }
168 }
169 Rule::expression => {
170 body_pair = Some(part);
171 }
172 _ => {}
173 }
174 }
175
176 let body = body_pair.ok_or_else(|| AlgorithmError::InvalidParameter {
177 parameter: "dsl",
178 message: "Missing function body".to_string(),
179 })?;
180
181 Ok(Statement::FunctionDecl {
182 name,
183 params,
184 body: Box::new(parse_expr(body, depth)?),
185 })
186 }
187 Rule::return_stmt => {
188 let expr =
189 inner
190 .into_inner()
191 .next()
192 .ok_or_else(|| AlgorithmError::InvalidParameter {
193 parameter: "dsl",
194 message: "Missing return expression".to_string(),
195 })?;
196
197 Ok(Statement::Return(Box::new(parse_expr(expr, depth)?)))
198 }
199 Rule::expr_stmt => parse_expr_stmt(inner, depth).map(|e| Statement::Expr(Box::new(e))),
200 _ => Err(AlgorithmError::InvalidParameter {
201 parameter: "dsl",
202 message: format!("Unexpected statement: {:?}", inner.as_rule()),
203 }),
204 }
205}
206
207fn parse_expr_stmt(pair: pest::iterators::Pair<Rule>, depth: usize) -> Result<Expr> {
208 guard_depth(depth, "dsl")?;
209
210 let expr = pair
211 .into_inner()
212 .next()
213 .ok_or_else(|| AlgorithmError::InvalidParameter {
214 parameter: "dsl",
215 message: "Empty expression statement".to_string(),
216 })?;
217
218 parse_expr(expr, depth)
219}
220
221fn parse_expr(pair: pest::iterators::Pair<Rule>, depth: usize) -> Result<Expr> {
229 guard_depth(depth, "dsl")?;
230
231 match pair.as_rule() {
232 Rule::expression => parse_expr(
233 pair.into_inner()
234 .next()
235 .ok_or_else(|| AlgorithmError::InvalidParameter {
236 parameter: "dsl",
237 message: "Empty expression".to_string(),
238 })?,
239 depth,
240 ),
241 Rule::logical_or => parse_binary_op(pair, BinaryOp::Or, depth),
242 Rule::logical_and => parse_binary_op(pair, BinaryOp::And, depth),
243 Rule::logical_not => {
244 let mut inner = pair.into_inner();
245 let mut not_count = 0;
246
247 while let Some(next) = inner.peek() {
249 if matches!(next.as_rule(), Rule::not_op) {
250 not_count += 1;
251 inner.next();
252 } else {
253 break;
254 }
255 }
256
257 let mut expr = parse_expr(
258 inner
259 .next()
260 .ok_or_else(|| AlgorithmError::InvalidParameter {
261 parameter: "dsl",
262 message: "Missing expression after not".to_string(),
263 })?,
264 depth,
265 )?;
266
267 for _ in 0..not_count {
269 expr = Expr::Unary {
270 op: UnaryOp::Not,
271 expr: Box::new(expr),
272 ty: Type::Unknown,
273 };
274 }
275
276 Ok(expr)
277 }
278 Rule::comparison => parse_comparison(pair, depth),
279 Rule::additive => parse_additive(pair, depth),
280 Rule::multiplicative => parse_multiplicative(pair, depth),
281 Rule::power => parse_power(pair, depth),
282 Rule::unary => parse_unary(pair, depth),
283 Rule::primary => parse_primary(pair, depth),
284 _ => Err(AlgorithmError::InvalidParameter {
285 parameter: "dsl",
286 message: format!("Unexpected expression rule: {:?}", pair.as_rule()),
287 }),
288 }
289}
290
291fn parse_binary_op(
292 pair: pest::iterators::Pair<Rule>,
293 default_op: BinaryOp,
294 depth: usize,
295) -> Result<Expr> {
296 guard_depth(depth, "dsl")?;
297
298 let mut inner = pair.into_inner();
299 let mut left = parse_expr(
300 inner
301 .next()
302 .ok_or_else(|| AlgorithmError::InvalidParameter {
303 parameter: "dsl",
304 message: "Missing left operand".to_string(),
305 })?,
306 depth,
307 )?;
308
309 while let Some(next) = inner.next() {
310 let op = match next.as_rule() {
311 Rule::or_op => BinaryOp::Or,
312 Rule::and_op => BinaryOp::And,
313 _ => {
314 let right = parse_expr(next, depth)?;
315 left = Expr::Binary {
316 left: Box::new(left),
317 op: default_op,
318 right: Box::new(right),
319 ty: Type::Unknown,
320 };
321 continue;
322 }
323 };
324
325 let right = parse_expr(
326 inner
327 .next()
328 .ok_or_else(|| AlgorithmError::InvalidParameter {
329 parameter: "dsl",
330 message: "Missing right operand".to_string(),
331 })?,
332 depth,
333 )?;
334
335 left = Expr::Binary {
336 left: Box::new(left),
337 op,
338 right: Box::new(right),
339 ty: Type::Unknown,
340 };
341 }
342
343 Ok(left)
344}
345
346fn parse_comparison(pair: pest::iterators::Pair<Rule>, depth: usize) -> Result<Expr> {
347 guard_depth(depth, "dsl")?;
348
349 let mut inner = pair.into_inner();
350 let left = parse_expr(
351 inner
352 .next()
353 .ok_or_else(|| AlgorithmError::InvalidParameter {
354 parameter: "dsl",
355 message: "Missing left operand".to_string(),
356 })?,
357 depth,
358 )?;
359
360 if let Some(op_pair) = inner.next() {
361 let op = match op_pair.as_rule() {
362 Rule::eq_op => BinaryOp::Equal,
363 Rule::ne_op => BinaryOp::NotEqual,
364 Rule::lt_op => BinaryOp::Less,
365 Rule::le_op => BinaryOp::LessEqual,
366 Rule::gt_op => BinaryOp::Greater,
367 Rule::ge_op => BinaryOp::GreaterEqual,
368 _ => {
369 return Err(AlgorithmError::InvalidParameter {
370 parameter: "dsl",
371 message: format!("Unknown comparison operator: {:?}", op_pair.as_rule()),
372 });
373 }
374 };
375
376 let right = parse_expr(
377 inner
378 .next()
379 .ok_or_else(|| AlgorithmError::InvalidParameter {
380 parameter: "dsl",
381 message: "Missing right operand".to_string(),
382 })?,
383 depth,
384 )?;
385
386 Ok(Expr::Binary {
387 left: Box::new(left),
388 op,
389 right: Box::new(right),
390 ty: Type::Unknown,
391 })
392 } else {
393 Ok(left)
394 }
395}
396
397fn parse_additive(pair: pest::iterators::Pair<Rule>, depth: usize) -> Result<Expr> {
398 guard_depth(depth, "dsl")?;
399
400 let mut inner = pair.into_inner();
401 let mut left = parse_expr(
402 inner
403 .next()
404 .ok_or_else(|| AlgorithmError::InvalidParameter {
405 parameter: "dsl",
406 message: "Missing left operand".to_string(),
407 })?,
408 depth,
409 )?;
410
411 while let Some(op_pair) = inner.next() {
412 let op = match op_pair.as_rule() {
413 Rule::add_op => BinaryOp::Add,
414 Rule::sub_op => BinaryOp::Subtract,
415 _ => {
416 let right = parse_expr(op_pair, depth)?;
417 left = Expr::Binary {
418 left: Box::new(left),
419 op: BinaryOp::Add,
420 right: Box::new(right),
421 ty: Type::Unknown,
422 };
423 continue;
424 }
425 };
426
427 let right = parse_expr(
428 inner
429 .next()
430 .ok_or_else(|| AlgorithmError::InvalidParameter {
431 parameter: "dsl",
432 message: "Missing right operand".to_string(),
433 })?,
434 depth,
435 )?;
436
437 left = Expr::Binary {
438 left: Box::new(left),
439 op,
440 right: Box::new(right),
441 ty: Type::Unknown,
442 };
443 }
444
445 Ok(left)
446}
447
448fn parse_multiplicative(pair: pest::iterators::Pair<Rule>, depth: usize) -> Result<Expr> {
449 guard_depth(depth, "dsl")?;
450
451 let mut inner = pair.into_inner();
452 let mut left = parse_expr(
453 inner
454 .next()
455 .ok_or_else(|| AlgorithmError::InvalidParameter {
456 parameter: "dsl",
457 message: "Missing left operand".to_string(),
458 })?,
459 depth,
460 )?;
461
462 while let Some(op_pair) = inner.next() {
463 let op = match op_pair.as_rule() {
464 Rule::mul_op => BinaryOp::Multiply,
465 Rule::div_op => BinaryOp::Divide,
466 Rule::mod_op => BinaryOp::Modulo,
467 _ => {
468 let right = parse_expr(op_pair, depth)?;
469 left = Expr::Binary {
470 left: Box::new(left),
471 op: BinaryOp::Multiply,
472 right: Box::new(right),
473 ty: Type::Unknown,
474 };
475 continue;
476 }
477 };
478
479 let right = parse_expr(
480 inner
481 .next()
482 .ok_or_else(|| AlgorithmError::InvalidParameter {
483 parameter: "dsl",
484 message: "Missing right operand".to_string(),
485 })?,
486 depth,
487 )?;
488
489 left = Expr::Binary {
490 left: Box::new(left),
491 op,
492 right: Box::new(right),
493 ty: Type::Unknown,
494 };
495 }
496
497 Ok(left)
498}
499
500fn parse_power(pair: pest::iterators::Pair<Rule>, depth: usize) -> Result<Expr> {
501 guard_depth(depth, "dsl")?;
502
503 let mut inner = pair.into_inner();
504 let mut left = parse_expr(
505 inner
506 .next()
507 .ok_or_else(|| AlgorithmError::InvalidParameter {
508 parameter: "dsl",
509 message: "Missing left operand".to_string(),
510 })?,
511 depth,
512 )?;
513
514 while let Some(op_pair) = inner.next() {
515 if matches!(op_pair.as_rule(), Rule::pow_op) {
516 let right = parse_expr(
517 inner
518 .next()
519 .ok_or_else(|| AlgorithmError::InvalidParameter {
520 parameter: "dsl",
521 message: "Missing right operand".to_string(),
522 })?,
523 depth,
524 )?;
525
526 left = Expr::Binary {
527 left: Box::new(left),
528 op: BinaryOp::Power,
529 right: Box::new(right),
530 ty: Type::Unknown,
531 };
532 } else {
533 let right = parse_expr(op_pair, depth)?;
534 left = Expr::Binary {
535 left: Box::new(left),
536 op: BinaryOp::Power,
537 right: Box::new(right),
538 ty: Type::Unknown,
539 };
540 }
541 }
542
543 Ok(left)
544}
545
546fn parse_unary(pair: pest::iterators::Pair<Rule>, depth: usize) -> Result<Expr> {
547 guard_depth(depth, "dsl")?;
548
549 let mut inner = pair.into_inner();
550 let first = inner
551 .next()
552 .ok_or_else(|| AlgorithmError::InvalidParameter {
553 parameter: "dsl",
554 message: "Empty unary expression".to_string(),
555 })?;
556
557 match first.as_rule() {
558 Rule::sub_op => {
559 let expr = inner
561 .next()
562 .ok_or_else(|| AlgorithmError::InvalidParameter {
563 parameter: "dsl",
564 message: "Missing expression after -".to_string(),
565 })?;
566
567 Ok(Expr::Unary {
568 op: UnaryOp::Negate,
569 expr: Box::new(parse_expr(expr, depth + 1)?),
570 ty: Type::Unknown,
571 })
572 }
573 Rule::add_op => {
574 let expr = inner
576 .next()
577 .ok_or_else(|| AlgorithmError::InvalidParameter {
578 parameter: "dsl",
579 message: "Missing expression after +".to_string(),
580 })?;
581
582 Ok(Expr::Unary {
583 op: UnaryOp::Plus,
584 expr: Box::new(parse_expr(expr, depth + 1)?),
585 ty: Type::Unknown,
586 })
587 }
588 _ => parse_expr(first, depth),
589 }
590}
591
592fn parse_primary(pair: pest::iterators::Pair<Rule>, depth: usize) -> Result<Expr> {
593 guard_depth(depth, "dsl")?;
594
595 let inner = pair
596 .into_inner()
597 .next()
598 .ok_or_else(|| AlgorithmError::InvalidParameter {
599 parameter: "dsl",
600 message: "Empty primary expression".to_string(),
601 })?;
602
603 match inner.as_rule() {
604 Rule::number => {
605 let num =
606 inner
607 .as_str()
608 .parse::<f64>()
609 .map_err(|_| AlgorithmError::InvalidParameter {
610 parameter: "dsl",
611 message: format!("Invalid number: {}", inner.as_str()),
612 })?;
613 Ok(Expr::Number(num))
614 }
615 Rule::band_ref => {
616 let band_str = inner.as_str();
617 let band_num =
618 band_str[1..]
619 .parse::<usize>()
620 .map_err(|_| AlgorithmError::InvalidParameter {
621 parameter: "dsl",
622 message: format!("Invalid band reference: {band_str}"),
623 })?;
624 Ok(Expr::Band(band_num))
625 }
626 Rule::function_call => {
627 let mut parts = inner.into_inner();
628 let name = parts
629 .next()
630 .ok_or_else(|| AlgorithmError::InvalidParameter {
631 parameter: "dsl",
632 message: "Missing function name".to_string(),
633 })?
634 .as_str()
635 .to_string();
636
637 let mut args = Vec::new();
638 if let Some(arg_list) = parts.next() {
639 for arg in arg_list.into_inner() {
640 args.push(parse_expr(arg, depth + 1)?);
641 }
642 }
643
644 Ok(Expr::Call {
645 name,
646 args,
647 ty: Type::Unknown,
648 })
649 }
650 Rule::variable_ref => Ok(Expr::Variable(inner.as_str().to_string())),
651 Rule::conditional => {
652 let mut parts = inner.into_inner();
653 let condition = parse_expr(
654 parts
655 .next()
656 .ok_or_else(|| AlgorithmError::InvalidParameter {
657 parameter: "dsl",
658 message: "Missing condition".to_string(),
659 })?,
660 depth + 1,
661 )?;
662
663 let then_expr = parse_expr(
664 parts
665 .next()
666 .ok_or_else(|| AlgorithmError::InvalidParameter {
667 parameter: "dsl",
668 message: "Missing then expression".to_string(),
669 })?,
670 depth + 1,
671 )?;
672
673 let else_expr = parse_expr(
674 parts
675 .next()
676 .ok_or_else(|| AlgorithmError::InvalidParameter {
677 parameter: "dsl",
678 message: "Missing else expression".to_string(),
679 })?,
680 depth + 1,
681 )?;
682
683 Ok(Expr::Conditional {
684 condition: Box::new(condition),
685 then_expr: Box::new(then_expr),
686 else_expr: Box::new(else_expr),
687 ty: Type::Unknown,
688 })
689 }
690 Rule::block => {
691 let mut statements = Vec::new();
692 let mut result = None;
693
694 for part in inner.into_inner() {
695 match part.as_rule() {
696 Rule::statement => statements.push(parse_statement(part, depth + 1)?),
697 Rule::expression => result = Some(Box::new(parse_expr(part, depth + 1)?)),
698 _ => {}
699 }
700 }
701
702 Ok(Expr::Block {
703 statements,
704 result,
705 ty: Type::Unknown,
706 })
707 }
708 Rule::expression => parse_expr(inner, depth + 1),
709 _ => Err(AlgorithmError::InvalidParameter {
710 parameter: "dsl",
711 message: format!("Unexpected primary: {:?}", inner.as_rule()),
712 }),
713 }
714}
715
716#[cfg(test)]
717mod tests {
718 use super::*;
719
720 #[test]
721 fn test_parse_number() {
722 let expr = parse_expression("42.5").expect("Should parse");
723 assert!(matches!(expr, Expr::Number(n) if (n - 42.5).abs() < 1e-10));
724 }
725
726 #[test]
727 fn test_parse_band() {
728 let expr = parse_expression("B1").expect("Should parse");
729 assert!(matches!(expr, Expr::Band(1)));
730 }
731
732 #[test]
733 fn test_parse_arithmetic() {
734 let result = parse_expression("1 + 2 * 3");
735 assert!(result.is_ok());
736 }
737
738 #[test]
739 fn test_parse_ndvi() {
740 let result = parse_expression("(B1 - B2) / (B1 + B2)");
741 assert!(result.is_ok());
742 }
743
744 #[test]
745 fn test_parse_conditional() {
746 let result = parse_expression("if B1 > 0.5 then 1 else 0");
747 if let Err(e) = &result {
748 eprintln!("Parse error: {:?}", e);
749 }
750 assert!(result.is_ok(), "Parse failed: {:?}", result);
751 }
752
753 #[test]
754 fn test_parse_function_call() {
755 let result = parse_expression("sqrt(B1 * B1 + B2 * B2)");
756 assert!(result.is_ok());
757 }
758
759 #[test]
760 fn test_parse_program() {
761 let program = r#"
762 let ndvi = (B8 - B4) / (B8 + B4);
763 let result = if ndvi > 0.5 then 1 else 0;
764 "#;
765 let result = parse_program(program);
766 assert!(result.is_ok());
767 }
768
769 use crate::expr_depth::MAX_EXPRESSION_DEPTH;
778
779 fn nested_parens(depth: usize) -> String {
780 let mut expr = String::from("B1");
781 for _ in 0..depth {
782 expr = format!("({expr} + 1)");
783 }
784 expr
785 }
786
787 #[test]
788 fn test_parse_accepts_expression_at_the_depth_limit() {
789 let expr = nested_parens(MAX_EXPRESSION_DEPTH);
790 assert!(
791 parse_expression(&expr).is_ok(),
792 "an expression exactly at the limit must still parse"
793 );
794 }
795
796 #[test]
797 fn test_parse_rejects_one_level_past_the_depth_limit() {
798 let expr = nested_parens(MAX_EXPRESSION_DEPTH + 1);
799 let err = parse_expression(&expr).expect_err("one level past the limit must be rejected");
800 assert!(
801 matches!(err, AlgorithmError::NestingTooDeep { max, .. } if max == MAX_EXPRESSION_DEPTH),
802 "expected NestingTooDeep, got {err:?}"
803 );
804 }
805
806 #[test]
807 fn test_parse_rejects_deeply_nested_parentheses_without_overflowing() {
808 let err =
809 parse_expression(&nested_parens(1000)).expect_err("deep nesting must be rejected");
810 assert!(matches!(err, AlgorithmError::NestingTooDeep { .. }));
811 assert!(err.to_string().contains(&MAX_EXPRESSION_DEPTH.to_string()));
813 }
814
815 #[test]
816 fn test_parse_rejects_deep_conditional_chain_without_overflowing() {
817 let mut expr = String::new();
820 for _ in 0..1000 {
821 expr.push_str("if B1 > 0 then 1 else ");
822 }
823 expr.push('0');
824
825 let err = parse_expression(&expr).expect_err("deep if-chain must be rejected");
826 assert!(matches!(err, AlgorithmError::NestingTooDeep { .. }));
827 }
828
829 #[test]
830 fn test_parse_rejects_deep_unary_chain_without_overflowing() {
831 let mut expr = "-".repeat(5000);
833 expr.push_str("B1");
834
835 let err = parse_expression(&expr).expect_err("deep unary chain must be rejected");
836 assert!(matches!(err, AlgorithmError::NestingTooDeep { .. }));
837 }
838
839 #[test]
840 fn test_parse_program_rejects_deep_nesting_without_overflowing() {
841 let program = format!("let x = {};", nested_parens(1000));
842 let err = parse_program(&program).expect_err("deep nesting must be rejected");
843 assert!(matches!(err, AlgorithmError::NestingTooDeep { .. }));
844 }
845
846 #[test]
847 fn test_depth_limit_does_not_reject_realistic_expressions() {
848 let cases = [
850 "(B1 - B2) / (B1 + B2)",
851 "sqrt(B1 * B1 + B2 * B2)",
852 "if B1 > 0.6 then 1.0 else if B1 > 0.3 then 0.5 else 0.0",
853 "2.5 * ((B8 - B4) / (B8 + 6 * B4 - 7.5 * B2 + 1))",
854 "clamp(pow(abs(B1 - B2), 2), 0, 1)",
855 "-B1 + -B2",
856 "1e-3 * B1 + 2.5e+4",
857 ];
858 for case in cases {
859 assert!(parse_expression(case).is_ok(), "should parse: {case}");
860 }
861
862 let flat = (0..500)
864 .map(|i| format!("B{}", i % 8 + 1))
865 .collect::<Vec<_>>()
866 .join(" + ");
867 assert!(parse_expression(&flat).is_ok(), "flat sums must parse");
868
869 let program = "let a = if B1 > 0 then 1 else 0;\n".repeat(200);
871 assert!(parse_program(&program).is_ok(), "sibling statements");
872 }
873}