1use crate::ast::*;
2use harn_lexer::{Span, TokenKind};
3
4use super::error::ParserError;
5use super::state::Parser;
6
7impl Parser {
8 pub fn parse_single_expression(&mut self) -> Result<SNode, ParserError> {
10 self.check_token_nesting_limit()?;
11 self.skip_newlines();
12 self.parse_expression()
13 }
14
15 pub(super) fn parse_nested_expression(
16 &mut self,
17 context: &'static str,
18 ) -> Result<SNode, ParserError> {
19 self.with_nesting(context, |parser| parser.parse_expression())
20 }
21
22 pub(super) fn parse_expression(&mut self) -> Result<SNode, ParserError> {
23 self.skip_newlines();
24 self.parse_pipe()
25 }
26
27 pub(super) fn parse_pipe(&mut self) -> Result<SNode, ParserError> {
28 let mut left = self.parse_range()?;
29 while self.check_skip_newlines(&TokenKind::Pipe) {
30 let start = left.span;
31 self.advance();
32 self.skip_newlines();
33 let right = self.parse_range()?;
34 left = spanned(
35 Node::BinaryOp {
36 op: "|>".into(),
37 left: Box::new(left),
38 right: Box::new(right),
39 },
40 Span::merge(start, self.prev_span()),
41 );
42 }
43 Ok(left)
44 }
45
46 pub(super) fn parse_range(&mut self) -> Result<SNode, ParserError> {
47 let left = self.parse_ternary()?;
48 if self.check(&TokenKind::To) {
49 let start = left.span;
50 self.advance();
51 let right = self.parse_ternary()?;
52 let inclusive = if self.check(&TokenKind::Exclusive) {
53 self.advance();
54 false
55 } else {
56 true
57 };
58 return Ok(spanned(
59 Node::RangeExpr {
60 start: Box::new(left),
61 end: Box::new(right),
62 inclusive,
63 },
64 Span::merge(start, self.prev_span()),
65 ));
66 }
67 Ok(left)
68 }
69
70 pub(super) fn parse_ternary(&mut self) -> Result<SNode, ParserError> {
71 let condition = self.parse_logical_or()?;
72 if !self.check_skip_newlines(&TokenKind::Question) {
77 return Ok(condition);
78 }
79 let start = condition.span;
80 self.advance(); self.skip_newlines();
82 let true_val = self.with_nesting("ternary expression", |parser| parser.parse_ternary())?;
83 self.consume(&TokenKind::Colon, ":")?;
85 self.skip_newlines();
86 let false_val = self.with_nesting("ternary expression", |parser| parser.parse_ternary())?;
87 Ok(spanned(
88 Node::Ternary {
89 condition: Box::new(condition),
90 true_expr: Box::new(true_val),
91 false_expr: Box::new(false_val),
92 },
93 Span::merge(start, self.prev_span()),
94 ))
95 }
96
97 pub(super) fn parse_nil_coalescing(&mut self) -> Result<SNode, ParserError> {
100 let mut left = self.parse_multiplicative()?;
101 while self.check_skip_newlines(&TokenKind::NilCoal) {
102 let start = left.span;
103 self.advance();
104 self.skip_newlines();
105 let right = self.parse_multiplicative()?;
106 left = spanned(
107 Node::BinaryOp {
108 op: "??".into(),
109 left: Box::new(left),
110 right: Box::new(right),
111 },
112 Span::merge(start, self.prev_span()),
113 );
114 }
115 Ok(left)
116 }
117
118 pub(super) fn parse_logical_or(&mut self) -> Result<SNode, ParserError> {
119 let mut left = self.parse_logical_and()?;
120 while self.check_skip_newlines(&TokenKind::Or) {
121 let start = left.span;
122 self.advance();
123 self.skip_newlines();
124 let right = self.parse_logical_and()?;
125 left = spanned(
126 Node::BinaryOp {
127 op: "||".into(),
128 left: Box::new(left),
129 right: Box::new(right),
130 },
131 Span::merge(start, self.prev_span()),
132 );
133 }
134 Ok(left)
135 }
136
137 pub(super) fn parse_logical_and(&mut self) -> Result<SNode, ParserError> {
138 let mut left = self.parse_equality()?;
139 while self.check_skip_newlines(&TokenKind::And) {
140 let start = left.span;
141 self.advance();
142 self.skip_newlines();
143 let right = self.parse_equality()?;
144 left = spanned(
145 Node::BinaryOp {
146 op: "&&".into(),
147 left: Box::new(left),
148 right: Box::new(right),
149 },
150 Span::merge(start, self.prev_span()),
151 );
152 }
153 Ok(left)
154 }
155
156 pub(super) fn parse_equality(&mut self) -> Result<SNode, ParserError> {
157 let mut left = self.parse_comparison()?;
158 while self.check_skip_newlines(&TokenKind::Eq) || self.check_skip_newlines(&TokenKind::Neq)
159 {
160 let start = left.span;
161 let op = if self.check(&TokenKind::Eq) {
162 "=="
163 } else {
164 "!="
165 };
166 self.advance();
167 self.skip_newlines();
168 let right = self.parse_comparison()?;
169 left = spanned(
170 Node::BinaryOp {
171 op: op.into(),
172 left: Box::new(left),
173 right: Box::new(right),
174 },
175 Span::merge(start, self.prev_span()),
176 );
177 }
178 Ok(left)
179 }
180
181 pub(super) fn parse_comparison(&mut self) -> Result<SNode, ParserError> {
182 let mut left = self.parse_additive()?;
183 loop {
184 if self.check_skip_newlines(&TokenKind::Lt)
185 || self.check_skip_newlines(&TokenKind::Gt)
186 || self.check_skip_newlines(&TokenKind::Lte)
187 || self.check_skip_newlines(&TokenKind::Gte)
188 {
189 let start = left.span;
190 let op = match self.current().map(|t| &t.kind) {
191 Some(TokenKind::Lt) => "<",
192 Some(TokenKind::Gt) => ">",
193 Some(TokenKind::Lte) => "<=",
194 Some(TokenKind::Gte) => ">=",
195 _ => "<",
196 };
197 self.advance();
198 self.skip_newlines();
199 let right = self.parse_additive()?;
200 left = spanned(
201 Node::BinaryOp {
202 op: op.into(),
203 left: Box::new(left),
204 right: Box::new(right),
205 },
206 Span::merge(start, self.prev_span()),
207 );
208 } else if self.check(&TokenKind::In) {
209 let start = left.span;
210 self.advance();
211 self.skip_newlines();
212 let right = self.parse_additive()?;
213 left = spanned(
214 Node::BinaryOp {
215 op: "in".into(),
216 left: Box::new(left),
217 right: Box::new(right),
218 },
219 Span::merge(start, self.prev_span()),
220 );
221 } else if self.check_identifier("not") {
222 let saved = self.pos;
223 self.advance();
224 if self.check(&TokenKind::In) {
225 let start = left.span;
226 self.advance();
227 self.skip_newlines();
228 let right = self.parse_additive()?;
229 left = spanned(
230 Node::BinaryOp {
231 op: "not_in".into(),
232 left: Box::new(left),
233 right: Box::new(right),
234 },
235 Span::merge(start, self.prev_span()),
236 );
237 } else {
238 self.pos = saved;
239 break;
240 }
241 } else {
242 break;
243 }
244 }
245 Ok(left)
246 }
247
248 pub(super) fn parse_additive(&mut self) -> Result<SNode, ParserError> {
249 let mut left = self.parse_nil_coalescing()?;
250 while self.check_skip_newlines(&TokenKind::Plus) || self.check(&TokenKind::Minus) {
251 let start = left.span;
252 let op = if self.check(&TokenKind::Plus) {
253 "+"
254 } else {
255 "-"
256 };
257 self.advance();
258 self.skip_newlines();
259 let right = self.parse_nil_coalescing()?;
260 left = spanned(
261 Node::BinaryOp {
262 op: op.into(),
263 left: Box::new(left),
264 right: Box::new(right),
265 },
266 Span::merge(start, self.prev_span()),
267 );
268 }
269 Ok(left)
270 }
271
272 pub(super) fn parse_multiplicative(&mut self) -> Result<SNode, ParserError> {
273 let mut left = self.parse_unary()?;
274 while self.check_skip_newlines(&TokenKind::Star)
275 || self.check_skip_newlines(&TokenKind::Slash)
276 || self.check_skip_newlines(&TokenKind::Percent)
277 {
278 let start = left.span;
279 let op = if self.check(&TokenKind::Star) {
280 "*"
281 } else if self.check(&TokenKind::Slash) {
282 "/"
283 } else {
284 "%"
285 };
286 self.advance();
287 self.skip_newlines();
288 let right = self.parse_unary()?;
289 left = spanned(
290 Node::BinaryOp {
291 op: op.into(),
292 left: Box::new(left),
293 right: Box::new(right),
294 },
295 Span::merge(start, self.prev_span()),
296 );
297 }
298 Ok(left)
299 }
300
301 pub(super) fn parse_exponent(&mut self) -> Result<SNode, ParserError> {
308 let left = self.parse_postfix()?;
309 if !self.check_skip_newlines(&TokenKind::Pow) {
310 return Ok(left);
311 }
312
313 let start = left.span;
314 self.advance();
315 self.skip_newlines();
316 let right = self.with_nesting("exponent expression", |parser| parser.parse_unary())?;
317 Ok(spanned(
318 Node::BinaryOp {
319 op: "**".into(),
320 left: Box::new(left),
321 right: Box::new(right),
322 },
323 Span::merge(start, self.prev_span()),
324 ))
325 }
326
327 pub(super) fn parse_unary(&mut self) -> Result<SNode, ParserError> {
328 if self.check(&TokenKind::Not) {
329 let start = self.current_span();
330 self.advance();
331 let operand = self.with_nesting("unary expression", |parser| parser.parse_unary())?;
332 return Ok(spanned(
333 Node::UnaryOp {
334 op: "!".into(),
335 operand: Box::new(operand),
336 },
337 Span::merge(start, self.prev_span()),
338 ));
339 }
340 if self.check(&TokenKind::Minus) {
341 let start = self.current_span();
342 self.advance();
343 let operand = self.with_nesting("unary expression", |parser| parser.parse_unary())?;
344 return Ok(spanned(
345 Node::UnaryOp {
346 op: "-".into(),
347 operand: Box::new(operand),
348 },
349 Span::merge(start, self.prev_span()),
350 ));
351 }
352 self.parse_exponent()
353 }
354
355 pub(super) fn parse_postfix(&mut self) -> Result<SNode, ParserError> {
356 let mut expr = self.parse_primary()?;
357
358 loop {
359 if self.check_skip_newlines(&TokenKind::Dot)
360 || self.check_skip_newlines(&TokenKind::QuestionDot)
361 {
362 let optional = self.check(&TokenKind::QuestionDot);
363 let start = expr.span;
364 self.advance();
365 let member = self.consume_identifier_or_keyword("member name")?;
366 if self.check(&TokenKind::LParen) {
367 self.advance();
368 let args = self.parse_arg_list()?;
369 self.consume(&TokenKind::RParen, ")")?;
370 if optional {
371 expr = spanned(
372 Node::OptionalMethodCall {
373 object: Box::new(expr),
374 method: member,
375 args,
376 },
377 Span::merge(start, self.prev_span()),
378 );
379 } else {
380 expr = spanned(
381 Node::MethodCall {
382 object: Box::new(expr),
383 method: member,
384 args,
385 },
386 Span::merge(start, self.prev_span()),
387 );
388 }
389 } else if optional {
390 expr = spanned(
391 Node::OptionalPropertyAccess {
392 object: Box::new(expr),
393 property: member,
394 },
395 Span::merge(start, self.prev_span()),
396 );
397 } else {
398 expr = spanned(
399 Node::PropertyAccess {
400 object: Box::new(expr),
401 property: member,
402 },
403 Span::merge(start, self.prev_span()),
404 );
405 }
406 } else if self.check(&TokenKind::LBracket) {
407 let start = expr.span;
408 self.advance();
409
410 if self.check(&TokenKind::Colon) {
413 self.advance();
414 let end_expr = if self.check(&TokenKind::RBracket) {
415 None
416 } else {
417 Some(Box::new(self.parse_nested_expression("slice bound")?))
418 };
419 self.consume(&TokenKind::RBracket, "]")?;
420 expr = spanned(
421 Node::SliceAccess {
422 object: Box::new(expr),
423 start: None,
424 end: end_expr,
425 },
426 Span::merge(start, self.prev_span()),
427 );
428 } else {
429 let index = self.parse_nested_expression("subscript index")?;
430 if self.check(&TokenKind::Colon) {
431 self.advance();
432 let end_expr = if self.check(&TokenKind::RBracket) {
433 None
434 } else {
435 Some(Box::new(self.parse_nested_expression("slice bound")?))
436 };
437 self.consume(&TokenKind::RBracket, "]")?;
438 expr = spanned(
439 Node::SliceAccess {
440 object: Box::new(expr),
441 start: Some(Box::new(index)),
442 end: end_expr,
443 },
444 Span::merge(start, self.prev_span()),
445 );
446 } else {
447 self.consume(&TokenKind::RBracket, "]")?;
448 expr = spanned(
449 Node::SubscriptAccess {
450 object: Box::new(expr),
451 index: Box::new(index),
452 },
453 Span::merge(start, self.prev_span()),
454 );
455 }
456 }
457 } else if self.check(&TokenKind::LBrace) {
458 let struct_name = match &expr.node {
459 Node::Identifier(name) if self.is_struct_construct_lookahead(name) => {
460 Some(name.clone())
461 }
462 _ => None,
463 };
464 let Some(struct_name) = struct_name else {
465 break;
466 };
467 let start = expr.span;
468 self.advance();
469 let dict = self.parse_dict_literal(start)?;
470 let fields = match dict.node {
471 Node::DictLiteral(fields) => fields,
472 _ => unreachable!("dict parser must return a dict literal"),
473 };
474 expr = spanned(
475 Node::StructConstruct {
476 struct_name,
477 fields,
478 },
479 dict.span,
480 );
481 } else if self.check(&TokenKind::Lt) && matches!(expr.node, Node::Identifier(_)) {
482 let saved_pos = self.pos;
483 let start = expr.span;
484 self.advance();
485 let parsed_type_args = self.parse_type_arg_list();
486 if let Ok(type_args) = parsed_type_args {
487 if self.check(&TokenKind::LParen) {
488 self.advance();
489 let args = self.parse_arg_list()?;
490 self.consume(&TokenKind::RParen, ")")?;
491 if let Node::Identifier(name) = expr.node {
492 expr = spanned(
493 Node::FunctionCall {
494 name,
495 type_args,
496 args,
497 },
498 Span::merge(start, self.prev_span()),
499 );
500 }
501 } else {
502 self.pos = saved_pos;
503 break;
504 }
505 } else {
506 self.pos = saved_pos;
507 break;
508 }
509 } else if self.check(&TokenKind::LParen) && matches!(expr.node, Node::Identifier(_)) {
510 let start = expr.span;
511 self.advance();
512 let args = self.parse_arg_list()?;
513 self.consume(&TokenKind::RParen, ")")?;
514 if let Node::Identifier(name) = expr.node {
515 expr = spanned(
516 Node::FunctionCall {
517 name,
518 type_args: Vec::new(),
519 args,
520 },
521 Span::merge(start, self.prev_span()),
522 );
523 }
524 } else if self.check(&TokenKind::Question) {
525 if self.question_starts_ternary_branch() {
528 break;
529 }
530 if matches!(self.peek_kind_at(1), Some(TokenKind::LBracket)) {
531 let start = expr.span;
532 self.advance(); self.advance(); let index = self.parse_nested_expression("optional subscript index")?;
535 self.consume(&TokenKind::RBracket, "]")?;
536 expr = spanned(
537 Node::OptionalSubscriptAccess {
538 object: Box::new(expr),
539 index: Box::new(index),
540 },
541 Span::merge(start, self.prev_span()),
542 );
543 continue;
544 }
545 let start = expr.span;
546 self.advance();
547 expr = spanned(
548 Node::TryOperator {
549 operand: Box::new(expr),
550 },
551 Span::merge(start, self.prev_span()),
552 );
553 } else {
554 break;
555 }
556 }
557
558 Ok(expr)
559 }
560
561 fn question_starts_ternary_branch(&self) -> bool {
562 let next = self
567 .tokens
568 .iter()
569 .skip(self.pos + 1)
570 .find(|t| t.kind != TokenKind::Newline)
571 .map(|t| &t.kind);
572 next.is_some_and(Self::token_starts_ternary_branch)
573 && self.question_has_top_level_ternary_colon()
574 }
575
576 fn token_starts_ternary_branch(kind: &TokenKind) -> bool {
577 matches!(
578 kind,
579 TokenKind::Identifier(_)
580 | TokenKind::IntLiteral(_)
581 | TokenKind::FloatLiteral(_)
582 | TokenKind::StringLiteral(_)
583 | TokenKind::RawStringLiteral(_)
584 | TokenKind::InterpolatedString(_)
585 | TokenKind::True
586 | TokenKind::False
587 | TokenKind::Nil
588 | TokenKind::LParen
589 | TokenKind::LBracket
590 | TokenKind::LBrace
591 | TokenKind::Not
592 | TokenKind::Minus
593 | TokenKind::Fn
594 | TokenKind::If
595 | TokenKind::Match
596 | TokenKind::Try
597 | TokenKind::Spawn
598 | TokenKind::Parallel
599 | TokenKind::Retry
600 | TokenKind::Deadline
601 | TokenKind::RequestApproval
602 | TokenKind::DualControl
603 | TokenKind::AskUser
604 | TokenKind::EscalateTo
605 | TokenKind::DurationLiteral(_)
606 )
607 }
608
609 fn question_has_top_level_ternary_colon(&self) -> bool {
610 let mut delimiter_depth = 0usize;
611 let mut at_branch_start = true;
615 for (pos, token) in self.tokens.iter().enumerate().skip(self.pos + 1) {
616 if delimiter_depth == 0 {
617 match token.kind {
618 TokenKind::Colon => return true,
619 TokenKind::Newline => {
620 if at_branch_start {
621 continue;
624 }
625 if self.next_non_newline_continues_ternary_branch(pos + 1) {
626 continue;
627 }
628 return false;
629 }
630 TokenKind::RParen
631 | TokenKind::RBracket
632 | TokenKind::RBrace
633 | TokenKind::Eof => {
634 return false;
635 }
636 _ => {
637 at_branch_start = false;
638 }
639 }
640 }
641
642 match token.kind {
643 TokenKind::LParen | TokenKind::LBracket | TokenKind::LBrace => {
644 delimiter_depth += 1;
645 }
646 TokenKind::RParen | TokenKind::RBracket | TokenKind::RBrace => {
647 delimiter_depth = delimiter_depth.saturating_sub(1);
648 }
649 TokenKind::Eof => return false,
650 _ => {}
651 }
652 }
653 false
654 }
655
656 fn next_non_newline_continues_ternary_branch(&self, start_pos: usize) -> bool {
657 let Some(kind) = self
658 .tokens
659 .iter()
660 .skip(start_pos)
661 .find(|token| token.kind != TokenKind::Newline)
662 .map(|token| &token.kind)
663 else {
664 return false;
665 };
666 matches!(
667 kind,
668 TokenKind::Colon
669 | TokenKind::Plus
670 | TokenKind::Star
671 | TokenKind::Slash
672 | TokenKind::Percent
673 | TokenKind::Pow
674 | TokenKind::And
675 | TokenKind::Or
676 | TokenKind::Eq
677 | TokenKind::Neq
678 | TokenKind::Lt
679 | TokenKind::Gt
680 | TokenKind::Lte
681 | TokenKind::Gte
682 | TokenKind::NilCoal
683 | TokenKind::Pipe
684 | TokenKind::Dot
685 | TokenKind::QuestionDot
686 )
687 }
688
689 pub(super) fn parse_primary(&mut self) -> Result<SNode, ParserError> {
690 let tok = self.current().ok_or_else(|| ParserError::UnexpectedEof {
691 expected: "expression".into(),
692 span: self.prev_span(),
693 })?;
694 let start = self.current_span();
695
696 match &tok.kind {
697 TokenKind::StringLiteral(s) => {
698 let s = s.clone();
699 self.advance();
700 Ok(spanned(
701 Node::StringLiteral(s),
702 Span::merge(start, self.prev_span()),
703 ))
704 }
705 TokenKind::RawStringLiteral(s) => {
706 let s = s.clone();
707 self.advance();
708 Ok(spanned(
709 Node::RawStringLiteral(s),
710 Span::merge(start, self.prev_span()),
711 ))
712 }
713 TokenKind::InterpolatedString(segments) => {
714 let segments = segments.clone();
715 self.advance();
716 Ok(spanned(
717 Node::InterpolatedString(segments),
718 Span::merge(start, self.prev_span()),
719 ))
720 }
721 TokenKind::IntLiteral(n) => {
722 let n = *n;
723 self.advance();
724 Ok(spanned(
725 Node::IntLiteral(n),
726 Span::merge(start, self.prev_span()),
727 ))
728 }
729 TokenKind::FloatLiteral(n) => {
730 let n = *n;
731 self.advance();
732 Ok(spanned(
733 Node::FloatLiteral(n),
734 Span::merge(start, self.prev_span()),
735 ))
736 }
737 TokenKind::True => {
738 self.advance();
739 Ok(spanned(
740 Node::BoolLiteral(true),
741 Span::merge(start, self.prev_span()),
742 ))
743 }
744 TokenKind::False => {
745 self.advance();
746 Ok(spanned(
747 Node::BoolLiteral(false),
748 Span::merge(start, self.prev_span()),
749 ))
750 }
751 TokenKind::Nil => {
752 self.advance();
753 Ok(spanned(
754 Node::NilLiteral,
755 Span::merge(start, self.prev_span()),
756 ))
757 }
758 TokenKind::Identifier(name)
759 if name == "cost_route" && self.peek_kind() == Some(&TokenKind::LBrace) =>
760 {
761 self.parse_cost_route()
762 }
763 TokenKind::Identifier(name) => {
764 let name = name.clone();
765 self.advance();
766 Ok(spanned(
767 Node::Identifier(name),
768 Span::merge(start, self.prev_span()),
769 ))
770 }
771 TokenKind::LParen => {
772 self.advance();
773 let expr = self.with_nesting("parenthesized expression", |parser| {
774 let expr = parser.parse_expression()?;
775 parser.consume(&TokenKind::RParen, ")")?;
776 Ok(expr)
777 })?;
778 Ok(expr)
779 }
780 TokenKind::LBracket => self.parse_list_literal(),
781 TokenKind::LBrace => self.parse_dict_or_closure(),
782 TokenKind::Parallel => self.parse_parallel(),
783 TokenKind::Retry => self.parse_retry(),
784 TokenKind::If => self.parse_if_else(),
785 TokenKind::Spawn => self.parse_spawn_expr(),
786 TokenKind::RequestApproval => self.parse_hitl_expr(HitlKind::RequestApproval),
787 TokenKind::DualControl => self.parse_hitl_expr(HitlKind::DualControl),
788 TokenKind::AskUser => self.parse_hitl_expr(HitlKind::AskUser),
789 TokenKind::EscalateTo => self.parse_hitl_expr(HitlKind::EscalateTo),
790 TokenKind::DurationLiteral(ms) => {
791 let ms = *ms;
792 self.advance();
793 Ok(spanned(
794 Node::DurationLiteral(ms),
795 Span::merge(start, self.prev_span()),
796 ))
797 }
798 TokenKind::Deadline => self.parse_deadline(),
799 TokenKind::Try => self.parse_try_catch(),
800 TokenKind::Match => self.parse_match(),
801 TokenKind::Fn => self.parse_fn_expr(),
802 TokenKind::Lt
805 if matches!(self.peek_kind(), Some(&TokenKind::Lt))
806 && matches!(self.peek_kind_at(2), Some(TokenKind::Identifier(_))) =>
807 {
808 Err(ParserError::Unexpected {
809 got: "`<<` heredoc-like syntax".to_string(),
810 expected: "an expression — heredocs are only valid \
811 inside LLM tool-call argument JSON; \
812 for multiline strings in source code use \
813 triple-quoted `\"\"\"...\"\"\"`"
814 .to_string(),
815 span: start,
816 })
817 }
818 _ => Err(self.error("expression")),
819 }
820 }
821
822 pub(super) fn parse_fn_expr(&mut self) -> Result<SNode, ParserError> {
825 let start = self.current_span();
826 self.consume(&TokenKind::Fn, "fn")?;
827 self.consume(&TokenKind::LParen, "(")?;
828 let params = self.parse_typed_param_list()?;
829 self.consume(&TokenKind::RParen, ")")?;
830 self.consume(&TokenKind::LBrace, "{")?;
831 let body = self.parse_block()?;
832 self.consume(&TokenKind::RBrace, "}")?;
833 Ok(spanned(
834 Node::Closure {
835 params,
836 body,
837 fn_syntax: true,
838 },
839 Span::merge(start, self.prev_span()),
840 ))
841 }
842
843 pub(super) fn parse_spawn_expr(&mut self) -> Result<SNode, ParserError> {
844 let start = self.current_span();
845 self.consume(&TokenKind::Spawn, "spawn")?;
846 self.consume(&TokenKind::LBrace, "{")?;
847 let body = self.parse_block()?;
848 self.consume(&TokenKind::RBrace, "}")?;
849 Ok(spanned(
850 Node::SpawnExpr { body },
851 Span::merge(start, self.prev_span()),
852 ))
853 }
854
855 pub(super) fn parse_hitl_expr(&mut self, kind: HitlKind) -> Result<SNode, ParserError> {
867 let start = self.current_span();
868 let kw_token = match kind {
869 HitlKind::RequestApproval => TokenKind::RequestApproval,
870 HitlKind::DualControl => TokenKind::DualControl,
871 HitlKind::AskUser => TokenKind::AskUser,
872 HitlKind::EscalateTo => TokenKind::EscalateTo,
873 };
874 self.consume(&kw_token, kind.as_keyword())?;
875 self.consume(&TokenKind::LParen, "(")?;
876 self.skip_newlines();
877
878 let mut args: Vec<HitlArg> = Vec::new();
879 while !self.is_at_end() && !self.check(&TokenKind::RParen) {
880 let arg_start = self.current_span();
881 let is_named = matches!(
888 (self.peek_kind_at(0), self.peek_kind_at(1)),
889 (Some(TokenKind::Identifier(_)), Some(TokenKind::Colon))
890 );
891 let (name, value) = if is_named {
892 let Some(TokenKind::Identifier(raw)) = self.peek_kind_at(0).cloned() else {
893 unreachable!("named arg dispatch already matched Identifier token")
894 };
895 self.advance();
896 self.consume(&TokenKind::Colon, ":")?;
897 self.skip_newlines();
898 let value = self.parse_nested_expression("HITL argument")?;
899 (Some(raw), value)
900 } else {
901 (None, self.parse_nested_expression("HITL argument")?)
902 };
903 let arg_span = Span::merge(arg_start, self.prev_span());
904 args.push(HitlArg {
905 name,
906 value,
907 span: arg_span,
908 });
909 self.skip_newlines();
910 if self.check(&TokenKind::Comma) {
911 self.advance();
912 self.skip_newlines();
913 } else {
914 break;
915 }
916 }
917
918 self.skip_newlines();
919 self.consume(&TokenKind::RParen, ")")?;
920 Ok(spanned(
921 Node::HitlExpr { kind, args },
922 Span::merge(start, self.prev_span()),
923 ))
924 }
925
926 pub(super) fn parse_list_literal(&mut self) -> Result<SNode, ParserError> {
927 let start = self.current_span();
928 self.consume(&TokenKind::LBracket, "[")?;
929 let mut elements = Vec::new();
930 self.skip_newlines();
931
932 while !self.is_at_end() && !self.check(&TokenKind::RBracket) {
933 if self.check(&TokenKind::Dot) {
934 let saved_pos = self.pos;
935 self.advance();
936 if self.check(&TokenKind::Dot) {
937 self.advance();
938 self.consume(&TokenKind::Dot, ".")?;
939 let spread_start = self.tokens[saved_pos].span;
940 let expr = self.parse_nested_expression("list spread")?;
941 elements.push(spanned(
942 Node::Spread(Box::new(expr)),
943 Span::merge(spread_start, self.prev_span()),
944 ));
945 } else {
946 self.pos = saved_pos;
947 elements.push(self.parse_nested_expression("list element")?);
948 }
949 } else {
950 elements.push(self.parse_nested_expression("list element")?);
951 }
952 self.skip_newlines();
953 if self.check(&TokenKind::Comma) {
954 self.advance();
955 self.skip_newlines();
956 }
957 }
958
959 self.consume(&TokenKind::RBracket, "]")?;
960 Ok(spanned(
961 Node::ListLiteral(elements),
962 Span::merge(start, self.prev_span()),
963 ))
964 }
965
966 pub(super) fn parse_dict_or_closure(&mut self) -> Result<SNode, ParserError> {
967 let start = self.current_span();
968 self.consume(&TokenKind::LBrace, "{")?;
969 self.skip_newlines();
970
971 if self.check(&TokenKind::RBrace) {
972 self.advance();
973 return Ok(spanned(
974 Node::DictLiteral(Vec::new()),
975 Span::merge(start, self.prev_span()),
976 ));
977 }
978
979 let saved = self.pos;
981 if self.is_closure_lookahead() {
982 self.pos = saved;
983 return self.parse_closure_body(start);
984 }
985 self.pos = saved;
986 self.parse_dict_literal(start)
987 }
988
989 pub(super) fn is_struct_construct_lookahead(&self, struct_name: &str) -> bool {
993 if !struct_name
994 .chars()
995 .next()
996 .is_some_and(|ch| ch.is_uppercase())
997 {
998 return false;
999 }
1000
1001 let mut offset = 1;
1002 while matches!(self.peek_kind_at(offset), Some(TokenKind::Newline)) {
1003 offset += 1;
1004 }
1005
1006 match self.peek_kind_at(offset) {
1007 Some(TokenKind::RBrace) => true,
1008 Some(TokenKind::Identifier(_)) | Some(TokenKind::StringLiteral(_)) => {
1009 offset += 1;
1010 while matches!(self.peek_kind_at(offset), Some(TokenKind::Newline)) {
1011 offset += 1;
1012 }
1013 matches!(self.peek_kind_at(offset), Some(TokenKind::Colon))
1014 }
1015 _ => false,
1016 }
1017 }
1018
1019 pub(super) fn is_closure_lookahead(&mut self) -> bool {
1021 let mut depth = 0;
1022 while !self.is_at_end() {
1023 if let Some(tok) = self.current() {
1024 match &tok.kind {
1025 TokenKind::Arrow if depth == 0 => return true,
1026 TokenKind::LBrace | TokenKind::LParen | TokenKind::LBracket => depth += 1,
1027 TokenKind::RBrace if depth == 0 => return false,
1028 TokenKind::RBrace => depth -= 1,
1029 TokenKind::RParen | TokenKind::RBracket if depth > 0 => depth -= 1,
1030 _ => {}
1031 }
1032 self.advance();
1033 } else {
1034 return false;
1035 }
1036 }
1037 false
1038 }
1039
1040 pub(super) fn parse_closure_body(&mut self, start: Span) -> Result<SNode, ParserError> {
1042 let params = self.parse_typed_param_list_until_arrow()?;
1043 self.consume(&TokenKind::Arrow, "->")?;
1044 let body = self.parse_block()?;
1045 self.consume(&TokenKind::RBrace, "}")?;
1046 Ok(spanned(
1047 Node::Closure {
1048 params,
1049 body,
1050 fn_syntax: false,
1051 },
1052 Span::merge(start, self.prev_span()),
1053 ))
1054 }
1055
1056 pub(super) fn parse_typed_param_list_until_arrow(
1058 &mut self,
1059 ) -> Result<Vec<TypedParam>, ParserError> {
1060 self.parse_typed_params_until(|tok| tok == &TokenKind::Arrow)
1061 }
1062
1063 pub(super) fn parse_dict_literal(&mut self, start: Span) -> Result<SNode, ParserError> {
1064 let entries = self.parse_dict_entries()?;
1065 Ok(spanned(
1066 Node::DictLiteral(entries),
1067 Span::merge(start, self.prev_span()),
1068 ))
1069 }
1070
1071 pub(super) fn parse_dict_entries(&mut self) -> Result<Vec<DictEntry>, ParserError> {
1072 let mut entries = Vec::new();
1073 self.skip_newlines();
1074
1075 while !self.is_at_end() && !self.check(&TokenKind::RBrace) {
1076 if self.check(&TokenKind::Dot) {
1077 let saved_pos = self.pos;
1078 self.advance();
1079 if self.check(&TokenKind::Dot) {
1080 self.advance();
1081 if self.check(&TokenKind::Dot) {
1082 self.advance();
1083 let spread_start = self.tokens[saved_pos].span;
1084 let expr = self.parse_nested_expression("dict spread")?;
1085 entries.push(DictEntry {
1086 key: spanned(Node::NilLiteral, spread_start),
1087 value: spanned(
1088 Node::Spread(Box::new(expr)),
1089 Span::merge(spread_start, self.prev_span()),
1090 ),
1091 });
1092 self.skip_newlines();
1093 if self.check(&TokenKind::Comma) {
1094 self.advance();
1095 self.skip_newlines();
1096 }
1097 continue;
1098 }
1099 self.pos = saved_pos;
1100 } else {
1101 self.pos = saved_pos;
1102 }
1103 }
1104 let key = if self.check(&TokenKind::LBracket) {
1105 self.advance();
1106 let k = self.parse_nested_expression("computed dict key")?;
1107 self.consume(&TokenKind::RBracket, "]")?;
1108 k
1109 } else if matches!(
1110 self.current().map(|t| &t.kind),
1111 Some(TokenKind::StringLiteral(_))
1112 ) {
1113 let key_span = self.current_span();
1114 let name =
1115 if let Some(TokenKind::StringLiteral(s)) = self.current().map(|t| &t.kind) {
1116 s.clone()
1117 } else {
1118 unreachable!()
1119 };
1120 self.advance();
1121 spanned(Node::StringLiteral(name), key_span)
1122 } else {
1123 let key_span = self.current_span();
1124 let name = self.consume_identifier_or_keyword("dict key")?;
1125 spanned(Node::StringLiteral(name), key_span)
1126 };
1127 self.consume(&TokenKind::Colon, ":")?;
1128 let value = self.parse_nested_expression("dict value")?;
1129 entries.push(DictEntry { key, value });
1130 self.skip_newlines();
1131 if self.check(&TokenKind::Comma) {
1132 self.advance();
1133 self.skip_newlines();
1134 }
1135 }
1136
1137 self.consume(&TokenKind::RBrace, "}")?;
1138 Ok(entries)
1139 }
1140
1141 pub(super) fn parse_param_list(&mut self) -> Result<Vec<String>, ParserError> {
1143 let mut params = Vec::new();
1144 self.skip_newlines();
1145
1146 while !self.is_at_end() && !self.check(&TokenKind::RParen) {
1147 params.push(self.consume_identifier("parameter name")?);
1148 if self.check(&TokenKind::Comma) {
1149 self.advance();
1150 self.skip_newlines();
1151 }
1152 }
1153 Ok(params)
1154 }
1155
1156 pub(super) fn parse_typed_param_list(&mut self) -> Result<Vec<TypedParam>, ParserError> {
1158 self.parse_typed_params_until(|tok| tok == &TokenKind::RParen)
1159 }
1160
1161 pub(super) fn parse_typed_params_until(
1164 &mut self,
1165 is_terminator: impl Fn(&TokenKind) -> bool,
1166 ) -> Result<Vec<TypedParam>, ParserError> {
1167 let mut params = Vec::new();
1168 let mut seen_default = false;
1169 self.skip_newlines();
1170
1171 while !self.is_at_end() {
1172 if let Some(tok) = self.current() {
1173 if is_terminator(&tok.kind) {
1174 break;
1175 }
1176 } else {
1177 break;
1178 }
1179 let is_rest = if self.check(&TokenKind::Dot) {
1180 let p1 = self.pos + 1;
1181 let p2 = self.pos + 2;
1182 let is_ellipsis = p1 < self.tokens.len()
1183 && p2 < self.tokens.len()
1184 && self.tokens[p1].kind == TokenKind::Dot
1185 && self.tokens[p2].kind == TokenKind::Dot;
1186 if is_ellipsis {
1187 self.advance();
1188 self.advance();
1189 self.advance();
1190 true
1191 } else {
1192 false
1193 }
1194 } else {
1195 false
1196 };
1197 let name = self.consume_identifier("parameter name")?;
1198 let type_expr = self.try_parse_type_annotation()?;
1199 let default_value = if self.check(&TokenKind::Assign) {
1200 self.advance();
1201 seen_default = true;
1202 Some(Box::new(self.parse_nested_expression("parameter default")?))
1203 } else {
1204 if seen_default && !is_rest {
1205 return Err(self.error(
1206 "Required parameter cannot follow a parameter with a default value",
1207 ));
1208 }
1209 None
1210 };
1211 if is_rest
1212 && !is_terminator(
1213 &self
1214 .current()
1215 .map(|t| t.kind.clone())
1216 .unwrap_or(TokenKind::Eof),
1217 )
1218 {
1219 return Err(self.error("Rest parameter must be the last parameter"));
1220 }
1221 params.push(TypedParam {
1222 name,
1223 type_expr,
1224 default_value,
1225 rest: is_rest,
1226 });
1227 if self.check(&TokenKind::Comma) {
1228 self.advance();
1229 self.skip_newlines();
1230 }
1231 }
1232 Ok(params)
1233 }
1234
1235 pub(super) fn parse_arg_list(&mut self) -> Result<Vec<SNode>, ParserError> {
1236 let mut args = Vec::new();
1237 self.skip_newlines();
1238
1239 while !self.is_at_end() && !self.check(&TokenKind::RParen) {
1240 if self.check(&TokenKind::Dot) {
1241 let saved_pos = self.pos;
1242 self.advance();
1243 if self.check(&TokenKind::Dot) {
1244 self.advance();
1245 self.consume(&TokenKind::Dot, ".")?;
1246 let spread_start = self.tokens[saved_pos].span;
1247 let expr = self.parse_nested_expression("spread argument")?;
1248 args.push(spanned(
1249 Node::Spread(Box::new(expr)),
1250 Span::merge(spread_start, self.prev_span()),
1251 ));
1252 } else {
1253 self.pos = saved_pos;
1254 args.push(self.parse_nested_expression("argument")?);
1255 }
1256 } else {
1257 args.push(self.parse_nested_expression("argument")?);
1258 }
1259 self.skip_newlines();
1260 if self.check(&TokenKind::Comma) {
1261 self.advance();
1262 self.skip_newlines();
1263 }
1264 }
1265 Ok(args)
1266 }
1267}