Skip to main content

lisette_syntax/parse/
expressions.rs

1use ecow::EcoString;
2
3use super::strings::cook_string_contents;
4use super::{MAX_TUPLE_ARITY, ParseError, Parser};
5use crate::ast::{
6    Annotation, Attribute, BinaryOperator, Binding, Expression, FormatStringPart, ImportAlias,
7    Literal, SelectArm, SelectArmPattern, Span, StructFieldAssignment, StructSpread, UnaryOperator,
8    Visibility,
9};
10use crate::lex::TokenKind::{self, *};
11use crate::types::Type;
12
13impl<'source> Parser<'source> {
14    pub fn parse_expression(&mut self) -> Expression {
15        if !self.enter_recursion() {
16            let span = self.span_from_token(self.current_token());
17            self.resync_on_error();
18            return Expression::Unit {
19                ty: Type::uninferred(),
20                span,
21            };
22        }
23        let result = self.pratt_parse(0);
24        self.leave_recursion();
25        result
26    }
27
28    pub fn parse_atomic_expression(&mut self) -> Expression {
29        if self.keyword_in_value_position() {
30            return self.recover_keyword_as_identifier();
31        }
32
33        match self.current_token().kind {
34            Integer | Imaginary | Boolean | Char | String | RawString | Float => {
35                self.parse_literal()
36            }
37            FormatStringStart => self.parse_format_string(),
38            LeftParen => self.parse_parenthesized_expression(),
39            LeftCurlyBrace => self.parse_block_expression(),
40            LeftSquareBracket => self.parse_slice_literal(),
41            Identifier => self.parse_identifier(),
42            Function if self.stream.peek_ahead(1).kind == LeftParen => {
43                self.parse_fn_as_lambda_recovery()
44            }
45            Function => self.parse_function(None, vec![]),
46            Match => self.parse_match(),
47            If => self.parse_if(),
48            Pipe | PipeDouble => self.parse_lambda(),
49            Task => self.parse_task(),
50            Defer => self.parse_defer(),
51            Try => self.parse_try_block(),
52            Recover => self.parse_recover_block(),
53            Select => self.parse_select(),
54            Loop => self.parse_loop(),
55            Return => self.parse_return(),
56            Break => self.parse_break(),
57            Continue => self.parse_continue(),
58            DotDot | DotDotEqual => self.parse_range(None, self.current_token()),
59
60            LeftAngleBracket
61                if self.stream.peek_ahead(1).kind == Minus
62                    && self.current_token().byte_offset + self.current_token().byte_length
63                        == self.stream.peek_ahead(1).byte_offset =>
64            {
65                let start = self.current_token();
66                let span = Span::new(self.file_id, start.byte_offset, start.byte_length + 1);
67                self.track_error_at(
68                    span,
69                    "invalid syntax for channel receive",
70                    "Use `select { let v = ch.receive() => ... }` to receive from a channel",
71                );
72                self.resync_on_error();
73                Expression::Unit {
74                    ty: Type::uninferred(),
75                    span,
76                }
77            }
78
79            Backtick => self.recover_unexpected_backtick(),
80
81            _ => self.unexpected_token("expr"),
82        }
83    }
84
85    pub fn parse_range(
86        &mut self,
87        start: Option<Box<Expression>>,
88        span_start: crate::lex::Token<'source>,
89    ) -> Expression {
90        if matches!(start.as_deref(), Some(Expression::Range { .. })) {
91            self.track_error("not allowed", "Chained range operators are not supported");
92        }
93
94        let inclusive = self.is(DotDotEqual);
95
96        self.next();
97
98        let has_end = !matches!(
99            self.current_token().kind,
100            RightCurlyBrace
101                | RightSquareBracket
102                | RightParen
103                | LeftCurlyBrace
104                | Semicolon
105                | Comma
106                | EOF
107        );
108
109        if inclusive && !has_end {
110            self.track_error(
111                "expected end value",
112                "Inclusive ranges require an end value.",
113            );
114        }
115
116        let end = if has_end {
117            Some(Box::new(self.parse_range_end()))
118        } else {
119            None
120        };
121
122        Expression::Range {
123            start,
124            end,
125            inclusive,
126            ty: Type::uninferred(),
127            span: self.span_from_tokens(span_start),
128        }
129    }
130
131    fn parse_literal(&mut self) -> Expression {
132        let start = self.current_token();
133
134        let literal = match self.current_token().kind {
135            Integer => {
136                let text = self.current_token().text;
137                let literal = self.parse_integer_text(text);
138                self.next();
139                literal
140            }
141            Float => {
142                let raw = self.current_token().text;
143                let cleaned = raw.replace('_', "");
144                let f: f64 = cleaned.parse().unwrap_or_else(|_| {
145                    self.track_error(
146                        format!("float literal '{}' is out of range", raw),
147                        "Value must be a valid 64-bit floating point number.",
148                    );
149                    0.0
150                });
151                let text = if raw.contains('e') || raw.contains('E') || raw.contains('_') {
152                    Some(raw.to_string())
153                } else {
154                    None
155                };
156                self.next();
157                Literal::Float { value: f, text }
158            }
159            Imaginary => {
160                let text = self.current_token().text;
161                let coef: f64 = text[..text.len() - 1]
162                    .replace('_', "")
163                    .parse()
164                    .unwrap_or_else(|_| {
165                        self.track_error(
166                            format!("imaginary literal '{}' is out of range", text),
167                            "Value must be a valid 64-bit floating point number.",
168                        );
169                        0.0
170                    });
171                self.next();
172                Literal::Imaginary(coef)
173            }
174            Boolean => {
175                let b = self.current_token().text == "true";
176                self.next();
177                Literal::Boolean(b)
178            }
179            String => {
180                let s = self.current_token().text;
181                self.next();
182                let s_stripped = if s.len() >= 2 && s.starts_with('"') && s.ends_with('"') {
183                    &s[1..s.len() - 1]
184                } else {
185                    debug_assert!(false, "lexer produced String token without quotes: {:?}", s);
186                    s
187                };
188                Literal::String {
189                    value: cook_string_contents(s_stripped),
190                    raw: false,
191                }
192            }
193            RawString => {
194                let s = self.current_token().text;
195                self.next();
196                let s_stripped = if s.len() >= 3 && s.starts_with("r\"") && s.ends_with('"') {
197                    &s[2..s.len() - 1]
198                } else if s.len() >= 2 && s.starts_with("r\"") {
199                    // unterminated raw string — strip prefix only
200                    &s[2..]
201                } else {
202                    debug_assert!(
203                        false,
204                        "lexer produced RawString token without prefix: {:?}",
205                        s
206                    );
207                    s
208                };
209                Literal::String {
210                    value: cook_string_contents(s_stripped),
211                    raw: true,
212                }
213            }
214            Char => {
215                let c = self.current_token().text;
216                self.next();
217                let c_stripped = if c.len() >= 2 && c.starts_with('\'') && c.ends_with('\'') {
218                    &c[1..c.len() - 1]
219                } else {
220                    debug_assert!(false, "lexer produced Char token without quotes: {:?}", c);
221                    c
222                };
223                Literal::Char(c_stripped.to_string())
224            }
225            _ => return self.unexpected_token("literal"),
226        };
227
228        Expression::Literal {
229            literal,
230            ty: Type::uninferred(),
231            span: self.span_from_tokens(start),
232        }
233    }
234
235    fn parse_slice_literal(&mut self) -> Expression {
236        let start = self.current_token();
237        let (expressions, _) =
238            self.collect_delimited_expressions(LeftSquareBracket, RightSquareBracket);
239
240        Expression::Literal {
241            literal: Literal::Slice(expressions),
242            ty: Type::uninferred(),
243            span: self.span_from_tokens(start),
244        }
245    }
246
247    fn parse_identifier(&mut self) -> Expression {
248        let start = self.current_token();
249        let text = self.current_token().text;
250
251        if text == "go" {
252            let next = self.stream.peek_ahead(1).kind;
253            if next == LeftCurlyBrace || next == Identifier {
254                self.track_error(
255                    "invalid syntax",
256                    "Use `task { ... }` or `task my_function()` to spawn a concurrent task.",
257                );
258            }
259        }
260
261        self.ensure(Identifier);
262
263        Expression::Identifier {
264            value: text.into(),
265            ty: Type::uninferred(),
266            span: self.span_from_tokens(start),
267            binding_id: None,
268            qualified: None,
269        }
270    }
271
272    pub fn parse_struct_call(&mut self, expression: Expression) -> Expression {
273        let name = self.make_expression_name(&expression);
274        let name_span = expression.get_span();
275        let start_offset = name_span.byte_offset; // Start from the name, not the brace
276
277        self.ensure(LeftCurlyBrace);
278
279        let mut field_assignments = vec![];
280        let mut spread = StructSpread::None;
281        let mut seen_fields: Vec<(EcoString, Span)> = vec![];
282
283        while self.is_not(RightCurlyBrace) {
284            if self.is(DotDot) {
285                if spread.is_some() {
286                    self.track_error(
287                        "spread must be last",
288                        "Move the `..spread` to the end of the struct.",
289                    );
290                    break;
291                }
292
293                let dotdot_token = self.current_token();
294                let dotdot_span = self.span_from_token(dotdot_token);
295                self.ensure(DotDot);
296
297                if self.is(RightCurlyBrace) || self.is(Comma) {
298                    spread = StructSpread::ZeroFill { span: dotdot_span };
299                } else {
300                    spread = StructSpread::From(Box::new(self.parse_expression()));
301                }
302
303                self.expect_comma_or(RightCurlyBrace);
304                continue;
305            }
306
307            if spread.is_some() {
308                self.track_error(
309                    "field after spread",
310                    "The `..spread` must be the last element in a struct expression. Move explicit fields before the spread.",
311                );
312                break;
313            }
314
315            let field_name_token = self.current_token();
316            let field_name_span = self.span_from_token(field_name_token);
317            let field_name = self.read_identifier();
318
319            if let Some((_, first_span)) = seen_fields.iter().find(|(n, _)| n == &field_name) {
320                self.error_duplicate_struct_field(&field_name, *first_span, field_name_span);
321            } else {
322                seen_fields.push((field_name.clone(), field_name_span));
323            }
324
325            let field_value = if self.advance_if(Colon) {
326                self.parse_expression()
327            } else {
328                Expression::Identifier {
329                    value: field_name.clone(),
330                    ty: Type::uninferred(),
331                    span: self.span_from_tokens(field_name_token),
332                    binding_id: None,
333                    qualified: None,
334                }
335            };
336
337            field_assignments.push(StructFieldAssignment {
338                name: field_name,
339                name_span: field_name_span,
340                value: Box::new(field_value),
341            });
342
343            self.expect_comma_or(RightCurlyBrace);
344        }
345
346        self.ensure(RightCurlyBrace);
347
348        Expression::StructCall {
349            ty: Type::uninferred(),
350            name,
351            field_assignments,
352            spread,
353            span: self.span_from_offset(start_offset),
354        }
355    }
356
357    pub fn parse_index_expression(&mut self, expression: Expression) -> Expression {
358        let start = self.current_token();
359
360        self.ensure(LeftSquareBracket);
361
362        let index_start = self.current_token();
363        let lower = if self.is(Colon) {
364            None
365        } else {
366            Some(Box::new(self.parse_expression()))
367        };
368
369        let mut from_colon_syntax = false;
370        let index = if self.is(Colon) {
371            from_colon_syntax = true;
372            self.next();
373            let upper = if self.is(RightSquareBracket) {
374                None
375            } else {
376                Some(Box::new(self.parse_expression()))
377            };
378            Expression::Range {
379                start: lower,
380                end: upper,
381                inclusive: false,
382                ty: Type::uninferred(),
383                span: self.span_from_tokens(index_start),
384            }
385        } else {
386            *lower.expect("non-colon index must have a lower expression")
387        };
388
389        self.ensure(RightSquareBracket);
390
391        Expression::IndexedAccess {
392            ty: Type::uninferred(),
393            expression: expression.into(),
394            index: index.into(),
395            span: self.span_from_tokens(start),
396            from_colon_syntax,
397        }
398    }
399
400    pub fn parse_function_call(
401        &mut self,
402        expression: Expression,
403        type_args: Vec<Annotation>,
404    ) -> Expression {
405        let start_offset = expression.get_span().byte_offset;
406        let (args, spread) = self.collect_call_args();
407
408        Expression::Call {
409            ty: Type::uninferred(),
410            expression: expression.into(),
411            args,
412            spread: spread.into(),
413            type_args,
414            span: self.span_from_offset(start_offset),
415            call_kind: None,
416        }
417    }
418
419    fn collect_call_args(&mut self) -> (Vec<Expression>, Option<Expression>) {
420        self.ensure(LeftParen);
421        let mut args = vec![];
422
423        while !self.at_eof() && !self.is(RightParen) {
424            if self.handle_fn_as_lambda_in_call(&mut args) {
425                continue;
426            }
427            if self.at_item_boundary()
428                && !matches!(self.stream.peek_ahead(1).kind, RightParen | Comma)
429            {
430                break;
431            }
432            let arg = self.parse_expression();
433            if self.is(Ellipsis) {
434                self.next();
435                self.expect_comma_or(RightParen);
436                if !self.is(RightParen) && !self.at_eof() {
437                    self.track_error(
438                        "argument after spread",
439                        "The `spread...` must be the last argument in the call.",
440                    );
441                    while !self.at_eof() && !self.is(RightParen) {
442                        self.next();
443                    }
444                }
445                self.advance_if(RightParen);
446                return (args, Some(arg));
447            }
448            args.push(arg);
449            self.expect_comma_or(RightParen);
450        }
451
452        self.advance_if(RightParen);
453        (args, None)
454    }
455
456    fn handle_fn_as_lambda_in_call(&mut self, args: &mut Vec<Expression>) -> bool {
457        if !(self.is(Function) && self.stream.peek_ahead(1).kind == LeftParen) {
458            return false;
459        }
460        let span = self.track_fn_as_lambda_error();
461        self.resync_on_error();
462        args.push(Expression::Unit {
463            ty: Type::uninferred(),
464            span,
465        });
466        true
467    }
468
469    fn parse_fn_as_lambda_recovery(&mut self) -> Expression {
470        let start = self.current_token();
471        self.track_fn_as_lambda_error();
472
473        self.ensure(Function);
474        let params = self.parse_function_params();
475        let return_annotation = self.parse_function_return_annotation();
476
477        let body = if self.is(LeftCurlyBrace) {
478            self.parse_block_expression()
479        } else {
480            Expression::NoOp
481        };
482
483        Expression::Lambda {
484            params,
485            return_annotation,
486            body: body.into(),
487            ty: Type::uninferred(),
488            span: self.span_from_tokens(start),
489        }
490    }
491
492    fn track_fn_as_lambda_error(&mut self) -> Span {
493        let start = self.current_token();
494        let span = Span::new(self.file_id, start.byte_offset, start.byte_length + 1);
495        let error = ParseError::new("Syntax error", span, "expected a lambda")
496            .with_parse_code("fn_as_lambda")
497            .with_help("Use a lambda instead: `|x| x * 2`");
498        self.errors.push(error);
499        span
500    }
501
502    pub fn parse_type_args(&mut self) -> Vec<Annotation> {
503        self.ensure(LeftAngleBracket);
504
505        let mut type_args = vec![];
506
507        loop {
508            if self.at_eof() {
509                break;
510            }
511
512            type_args.push(self.parse_annotation());
513
514            if self.is(RightAngleBracket) {
515                break;
516            }
517
518            self.ensure(Comma);
519        }
520
521        self.ensure(RightAngleBracket);
522
523        type_args
524    }
525
526    pub fn parse_binary_operator(&mut self) -> BinaryOperator {
527        let operator = match self.current_token().kind {
528            Plus => BinaryOperator::Addition,
529            Minus => BinaryOperator::Subtraction,
530            Star => BinaryOperator::Multiplication,
531            Slash => BinaryOperator::Division,
532            LeftAngleBracket => BinaryOperator::LessThan,
533            LessThanOrEqual => BinaryOperator::LessThanOrEqual,
534            RightAngleBracket => BinaryOperator::GreaterThan,
535            GreaterThanOrEqual => BinaryOperator::GreaterThanOrEqual,
536            Percent => BinaryOperator::Remainder,
537            EqualDouble => BinaryOperator::Equal,
538            NotEqual => BinaryOperator::NotEqual,
539            AmpersandDouble => BinaryOperator::And,
540            PipeDouble => BinaryOperator::Or,
541            Pipeline => BinaryOperator::Pipeline,
542
543            _ => {
544                self.track_error(format!(
545                    "expected binary operator, found {}",
546                    self.current_token().kind
547                ), "Binary operators: `+`, `-`, `*`, `/`, `%`, `==`, `!=`, `<`, `>`, `<=`, `>=`, `&&`, `||`.");
548                BinaryOperator::Addition // meaningless fallback
549            }
550        };
551
552        self.next();
553
554        operator
555    }
556
557    fn parse_parenthesized_expression(&mut self) -> Expression {
558        let start = self.current_token();
559
560        let (expressions, has_trailing_comma) =
561            self.collect_delimited_expressions(LeftParen, RightParen);
562        let span = self.span_from_tokens(start);
563
564        match expressions.len() {
565            0 => Expression::Unit {
566                ty: Type::uninferred(),
567                span,
568            },
569            1 => {
570                if has_trailing_comma {
571                    self.error_tuple_arity(1, span);
572                }
573                let expression = expressions.into_iter().next().expect("len is 1");
574                Expression::Paren {
575                    ty: Type::uninferred(),
576                    expression: expression.into(),
577                    span,
578                }
579            }
580            n => {
581                if n > MAX_TUPLE_ARITY {
582                    self.error_tuple_arity(n, span);
583                }
584                Expression::Tuple {
585                    ty: Type::uninferred(),
586                    elements: expressions,
587                    span,
588                }
589            }
590        }
591    }
592
593    pub fn parse_try(&mut self, expression: Expression) -> Expression {
594        let start_offset = expression.get_span().byte_offset;
595
596        self.ensure(QuestionMark);
597
598        Expression::Propagate {
599            ty: Type::uninferred(),
600            expression: expression.into(),
601            span: self.span_from_offset(start_offset),
602        }
603    }
604
605    fn parse_lambda(&mut self) -> Expression {
606        let start = self.current_token();
607
608        let params = if self.is(Pipe) {
609            self.parse_lambda_params()
610        } else {
611            self.next();
612            vec![]
613        };
614
615        let has_return_type = self.is(Arrow);
616        let return_annotation = if has_return_type {
617            self.next();
618            self.parse_annotation()
619        } else {
620            Annotation::Unknown
621        };
622
623        if has_return_type && self.current_token().kind != LeftCurlyBrace {
624            self.track_error(
625                "not allowed",
626                "A lambda with a return type requires a block body",
627            );
628        }
629
630        let body = self.parse_expression();
631
632        Expression::Lambda {
633            params,
634            return_annotation,
635            body: body.into(),
636            ty: Type::uninferred(),
637            span: self.span_from_tokens(start),
638        }
639    }
640
641    pub fn parse_block_expression(&mut self) -> Expression {
642        let start = self.current_token();
643
644        self.ensure(LeftCurlyBrace);
645
646        if self.looks_like_map_literal() {
647            let key_span = self.span_from_token(self.current_token());
648            self.consume_to_matching_close_brace();
649            self.error_map_literal_not_supported(key_span);
650            return Expression::Block {
651                ty: Type::uninferred(),
652                items: vec![],
653                span: self.span_from_tokens(start),
654            };
655        }
656
657        if !self.enter_recursion() {
658            let span = self.span_from_token(self.current_token());
659            let mut brace_depth = 1u32;
660            while brace_depth > 0 && !self.at_eof() {
661                match self.current_token().kind {
662                    LeftCurlyBrace => brace_depth += 1,
663                    RightCurlyBrace => brace_depth -= 1,
664                    _ => {}
665                }
666                if brace_depth > 0 {
667                    self.next();
668                }
669            }
670            self.advance_if(RightCurlyBrace);
671            return Expression::Block {
672                ty: Type::uninferred(),
673                items: vec![],
674                span,
675            };
676        }
677
678        let mut items = vec![];
679
680        while self.is_not(RightCurlyBrace) && !self.too_many_errors() {
681            let position = self.position();
682            let item = self.parse_block_item();
683
684            self.advance_if(Semicolon);
685
686            items.push(item);
687            if self.position() == position {
688                self.next();
689            }
690        }
691
692        let span = self.close_brace_span(start, start);
693
694        self.leave_recursion();
695
696        Expression::Block {
697            ty: Type::uninferred(),
698            items,
699            span,
700        }
701    }
702
703    pub fn parse_function_params(&mut self) -> Vec<Binding> {
704        self.ensure(LeftParen);
705
706        let mut params = vec![];
707
708        while self.is_not(RightParen) {
709            params.push(self.parse_binding_with_type());
710            self.expect_comma_or(RightParen);
711        }
712
713        self.ensure(RightParen);
714
715        params
716    }
717
718    pub fn parse_lambda_params(&mut self) -> Vec<Binding> {
719        self.ensure(Pipe);
720
721        let mut params = vec![];
722
723        while self.is_not(Pipe) {
724            params.push(self.parse_binding());
725            self.expect_comma_or(Pipe);
726        }
727
728        self.ensure(Pipe);
729
730        params
731    }
732
733    pub fn parse_function(
734        &mut self,
735        doc: Option<std::string::String>,
736        attributes: Vec<Attribute>,
737    ) -> Expression {
738        let start = self.current_token();
739
740        self.ensure(Function);
741
742        let name_token = self.current_token();
743        let name_span = Span::new(self.file_id, name_token.byte_offset, name_token.byte_length);
744
745        let name = self.read_identifier_sequence();
746
747        let name_span = Span::new(name_span.file_id, name_span.byte_offset, name.len() as u32);
748
749        let generics = self.parse_generics();
750        let params = self.parse_function_params();
751        let return_annotation = self.parse_function_return_annotation();
752
753        let body = if self.is(LeftCurlyBrace) {
754            self.parse_block_expression()
755        } else {
756            Expression::NoOp
757        };
758
759        Expression::Function {
760            doc,
761            attributes,
762            name,
763            name_span,
764            generics,
765            params,
766            return_annotation,
767            return_type: Type::uninferred(),
768            visibility: Visibility::Private,
769            body: body.into(),
770            ty: Type::uninferred(),
771            span: self.span_from_tokens(start),
772        }
773    }
774
775    pub fn parse_field_access(&mut self, expression: Expression) -> Expression {
776        self.ensure(Dot);
777
778        let expression_start = expression.get_span().byte_offset;
779        let start = self.current_token();
780
781        if self.advance_if(Star) {
782            return Expression::Unary {
783                ty: Type::uninferred(),
784                operator: UnaryOperator::Deref,
785                expression: expression.into(),
786                span: self.span_from_tokens(start),
787            };
788        }
789
790        if self.is(Integer) {
791            let text = self.current_token().text;
792            let index: u32 = text.parse().unwrap_or_else(|_| {
793                self.track_error(
794                    format!("tuple index '{}' is too large", text),
795                    "Maximum index is `4294967295`.",
796                );
797                0
798            });
799
800            self.ensure(Integer);
801
802            return Expression::DotAccess {
803                ty: Type::uninferred(),
804                expression: expression.into(),
805                member: index.to_string().into(),
806                span: self.span_from_offset(expression_start),
807                dot_access_kind: None,
808                receiver_coercion: None,
809            };
810        }
811
812        let field = self.current_token().text;
813
814        self.ensure(Identifier);
815
816        Expression::DotAccess {
817            ty: Type::uninferred(),
818            expression: expression.into(),
819            member: field.into(),
820            span: self.span_from_offset(expression_start),
821            dot_access_kind: None,
822            receiver_coercion: None,
823        }
824    }
825
826    pub fn collect_delimited_expressions(
827        &mut self,
828        open: TokenKind,
829        close: TokenKind,
830    ) -> (Vec<Expression>, bool) {
831        self.ensure(open);
832
833        let mut expressions = vec![];
834        let mut has_trailing_comma = false;
835        loop {
836            if self.at_eof() || self.is(close) {
837                break;
838            }
839
840            if self.is(Function) && self.stream.peek_ahead(1).kind == LeftParen {
841                let span = self.track_fn_as_lambda_error();
842                self.resync_on_error();
843                expressions.push(Expression::Unit {
844                    ty: Type::uninferred(),
845                    span,
846                });
847                continue;
848            }
849
850            if self.at_item_boundary() {
851                let next = self.stream.peek_ahead(1).kind;
852                if next != close && next != Comma {
853                    break;
854                }
855            }
856            expressions.push(self.parse_expression());
857            has_trailing_comma = self.is(Comma);
858            self.expect_comma_or(close);
859        }
860
861        self.advance_if(close);
862
863        (expressions, has_trailing_comma)
864    }
865
866    fn make_expression_name(&mut self, expression: &Expression) -> EcoString {
867        let mut parts = Vec::new();
868        let mut current = expression;
869
870        loop {
871            match current {
872                Expression::Identifier { value, .. } => {
873                    parts.push(value.clone());
874                    break;
875                }
876                Expression::DotAccess {
877                    expression, member, ..
878                } => {
879                    parts.push(member.clone());
880                    current = expression;
881                }
882                _ => {
883                    self.track_error(
884                        "unexpected expression",
885                        "Expected an identifier or dotted path.",
886                    );
887                    return "_".into();
888                }
889            }
890        }
891
892        parts.reverse();
893        parts.join(".").into()
894    }
895
896    pub fn parse_let(&mut self) -> Expression {
897        let start = self.current_token();
898
899        self.ensure(Let);
900
901        let (mutable, mut_span) = if self.is(Mut) {
902            let mut_token = self.current_token();
903            let span = Span::new(self.file_id, mut_token.byte_offset, mut_token.byte_length);
904            self.next(); // consume `mut`
905            (true, Some(span))
906        } else {
907            (false, None)
908        };
909
910        let binding = self.parse_binding_allowing_or();
911
912        if !self.is(Equal)
913            && let Some(Annotation::Constructor { span, .. }) = binding.annotation.as_ref()
914        {
915            self.error_missing_initializer(*span);
916            let stub_span = self.span_from_tokens(start);
917            return Expression::Let {
918                binding: Box::new(binding),
919                value: Box::new(Expression::Block {
920                    ty: Type::uninferred(),
921                    items: vec![],
922                    span: stub_span,
923                }),
924                mutable,
925                mut_span,
926                else_block: None,
927                else_span: None,
928                typed_pattern: None,
929                ty: Type::uninferred(),
930                span: stub_span,
931            };
932        }
933
934        self.ensure(Equal);
935
936        let expression = self.parse_expression();
937
938        let (else_block, else_span) = if self.is(Else) {
939            let else_token = self.current_token();
940            let span = Span::new(self.file_id, else_token.byte_offset, else_token.byte_length);
941            self.next(); // consume `else`
942            (Some(Box::new(self.parse_block_expression())), Some(span))
943        } else {
944            (None, None)
945        };
946
947        Expression::Let {
948            binding: Box::new(binding),
949            value: expression.into(),
950            mutable,
951            mut_span,
952            else_block,
953            else_span,
954            typed_pattern: None,
955            ty: Type::uninferred(),
956            span: self.span_from_tokens(start),
957        }
958    }
959
960    pub fn parse_import(&mut self) -> Expression {
961        let start = self.current_token();
962
963        self.ensure(Import);
964
965        let alias = if self.current_token().kind == Identifier {
966            let alias_token = self.current_token();
967            let alias_text = alias_token.text;
968            let alias_span = Span::new(
969                self.file_id,
970                alias_token.byte_offset,
971                alias_token.byte_length,
972            );
973
974            if alias_text == "_" {
975                self.next();
976                Some(ImportAlias::Blank(alias_span))
977            } else if self.stream.peek_ahead(1).kind == String {
978                self.next();
979                Some(ImportAlias::Named(alias_text.into(), alias_span))
980            } else {
981                None
982            }
983        } else {
984            None
985        };
986
987        let name_token = self.current_token();
988
989        if name_token.kind != String {
990            let (label, help) = if name_token.kind == Identifier
991                && self.stream.peek_ahead(1).kind == Colon
992            {
993                let module_name = name_token.text;
994                (
995                    "expected double quotes".to_string(),
996                    format!(
997                        "Wrap the import path in double quotes: `import \"{0}:...\"`",
998                        module_name
999                    ),
1000                )
1001            } else if name_token.kind == Identifier {
1002                let module_name = name_token.text;
1003                (
1004                    "expected double quotes".to_string(),
1005                    format!(
1006                        "Wrap the import path in double quotes: `import \"{}\"`",
1007                        module_name
1008                    ),
1009                )
1010            } else {
1011                (
1012                    "expected module path".to_string(),
1013                    "Wrap the import path in double quotes, e.g. `import \"go:os\"`".to_string(),
1014                )
1015            };
1016
1017            self.track_error(label, help);
1018            self.resync_on_error();
1019            return Expression::Unit {
1020                ty: Type::uninferred(),
1021                span: self.span_from_tokens(start),
1022            };
1023        }
1024
1025        self.next();
1026
1027        let raw = name_token.text;
1028        let unquoted: &str = if raw.len() >= 2 && raw.starts_with('"') && raw.ends_with('"') {
1029            &raw[1..raw.len() - 1]
1030        } else {
1031            debug_assert!(
1032                false,
1033                "lexer produced String token without quotes: {:?}",
1034                raw
1035            );
1036            raw
1037        };
1038
1039        if alias.is_none() && self.is(As) && self.stream.peek_ahead(1).kind == Identifier {
1040            let as_token = self.current_token();
1041            let alias_identifier = self.stream.peek_ahead(1);
1042            self.next();
1043            self.next();
1044            self.error_import_alias_after_path(
1045                self.span_from_tokens(as_token),
1046                alias_identifier.text,
1047                unquoted,
1048            );
1049        }
1050
1051        let name: EcoString = unquoted.into();
1052        let name_span = Span::new(self.file_id, name_token.byte_offset, name_token.byte_length);
1053
1054        Expression::ModuleImport {
1055            name,
1056            name_span,
1057            alias,
1058            span: self.span_from_tokens(start),
1059        }
1060    }
1061
1062    pub fn parse_assignment(&mut self) -> Expression {
1063        let start = self.current_token();
1064
1065        let lhs = self.parse_expression();
1066
1067        let compound_operator = match self.current_token().kind {
1068            PlusEqual => Some(BinaryOperator::Addition),
1069            MinusEqual => Some(BinaryOperator::Subtraction),
1070            StarEqual => Some(BinaryOperator::Multiplication),
1071            SlashEqual => Some(BinaryOperator::Division),
1072            PercentEqual => Some(BinaryOperator::Remainder),
1073            _ => None,
1074        };
1075
1076        if let Some(operator) = compound_operator {
1077            if !self.is_valid_assignment_target(&lhs) {
1078                self.track_error(
1079                    "invalid assignment target",
1080                    "Only variables, fields, and indices can be assigned to.",
1081                );
1082                self.next();
1083                let _rhs = self.parse_expression();
1084                return lhs;
1085            }
1086            self.next();
1087            let rhs = self.parse_expression();
1088            return Expression::Assignment {
1089                target: lhs.clone().into(),
1090                value: Expression::Binary {
1091                    left: lhs.into(),
1092                    operator,
1093                    right: rhs.into(),
1094                    ty: Type::uninferred(),
1095                    span: self.span_from_tokens(start),
1096                }
1097                .into(),
1098                compound_operator: Some(operator),
1099                span: self.span_from_tokens(start),
1100            };
1101        }
1102
1103        if self.current_token().kind == Colon && self.stream.peek_ahead(1).kind == Equal {
1104            let span = Span::new(self.file_id, self.current_token().byte_offset, 2);
1105            self.track_error_at(
1106                span,
1107                "Go-style short declaration",
1108                "Use `let x = ...` instead of `:=` for variable declarations",
1109            );
1110            self.next();
1111            self.next();
1112            let _ = self.parse_expression();
1113            return lhs;
1114        }
1115
1116        if !self.is(Equal) {
1117            return lhs;
1118        }
1119
1120        if !self.is_valid_assignment_target(&lhs) {
1121            self.track_error(
1122                "invalid assignment target",
1123                "Only variables, fields, and indices can be assigned to.",
1124            );
1125        }
1126
1127        self.ensure(Equal);
1128
1129        Expression::Assignment {
1130            target: lhs.into(),
1131            value: self.parse_expression().into(),
1132            compound_operator: None,
1133            span: self.span_from_tokens(start),
1134        }
1135    }
1136
1137    fn is_valid_assignment_target(&self, expression: &Expression) -> bool {
1138        use Expression::*;
1139
1140        matches!(
1141            expression,
1142            Identifier { .. }
1143                | DotAccess { .. }
1144                | IndexedAccess { .. }
1145                | Unary {
1146                    operator: UnaryOperator::Deref,
1147                    ..
1148                }
1149        )
1150    }
1151
1152    fn parse_format_string(&mut self) -> Expression {
1153        let start = self.current_token();
1154        self.ensure(FormatStringStart);
1155
1156        let mut parts = Vec::new();
1157
1158        loop {
1159            if self.at_eof() || self.at_item_boundary() {
1160                break;
1161            }
1162            match self.current_token().kind {
1163                FormatStringText => {
1164                    let text = self.current_token().text;
1165                    self.next();
1166                    parts.push(FormatStringPart::Text(cook_string_contents(text)));
1167                }
1168                FormatStringInterpolationStart => {
1169                    self.ensure(FormatStringInterpolationStart);
1170                    let expression = self.parse_expression();
1171                    parts.push(FormatStringPart::Expression(Box::new(expression)));
1172                    if self.is(Colon) {
1173                        let start_offset = self.current_token().byte_offset;
1174                        self.next();
1175                        while !self.at_eof()
1176                            && !self.is(FormatStringInterpolationEnd)
1177                            && !self.is(FormatStringEnd)
1178                            && !self.at_item_boundary()
1179                        {
1180                            self.next();
1181                        }
1182                        let span = self.span_from_offset(start_offset);
1183                        let error = ParseError::new(
1184                            "Format specifiers not supported",
1185                            span,
1186                            "not supported in format strings",
1187                        )
1188                        .with_parse_code("format_specifier")
1189                        .with_help(
1190                            "Use `fmt.Sprintf` for formatted output, e.g. `fmt.Sprintf(\"%02x\", n)`",
1191                        );
1192                        self.errors.push(error);
1193                    }
1194                    self.advance_if(FormatStringInterpolationEnd);
1195                }
1196                FormatStringEnd => {
1197                    self.ensure(FormatStringEnd);
1198                    break;
1199                }
1200                _ => break,
1201            }
1202        }
1203
1204        Expression::Literal {
1205            literal: Literal::FormatString(parts),
1206            ty: Type::uninferred(),
1207            span: self.span_from_tokens(start),
1208        }
1209    }
1210
1211    pub fn parse_task(&mut self) -> Expression {
1212        let start = self.current_token();
1213
1214        self.ensure(Task);
1215
1216        let expression = if self.is(LeftCurlyBrace) {
1217            self.parse_block_expression()
1218        } else {
1219            self.parse_expression()
1220        };
1221
1222        if !matches!(
1223            expression,
1224            Expression::Call { .. } | Expression::Block { .. }
1225        ) {
1226            let span = expression.get_span();
1227            let error = ParseError::new("Invalid `task`", span, "expected `()`")
1228                .with_parse_code("task_missing_parens")
1229                .with_help("Add parens to call the function");
1230
1231            self.errors.push(error);
1232        }
1233
1234        Expression::Task {
1235            expression: Box::new(expression),
1236            ty: Type::uninferred(),
1237            span: self.span_from_tokens(start),
1238        }
1239    }
1240
1241    pub fn parse_defer(&mut self) -> Expression {
1242        let start = self.current_token();
1243
1244        self.ensure(Defer);
1245
1246        let expression = if self.is(LeftCurlyBrace) {
1247            self.parse_block_expression()
1248        } else {
1249            self.parse_expression()
1250        };
1251
1252        if !matches!(
1253            expression,
1254            Expression::Call { .. } | Expression::Block { .. }
1255        ) {
1256            let span = expression.get_span();
1257            let error = ParseError::new("Invalid `defer`", span, "expected `()`")
1258                .with_parse_code("defer_missing_parens")
1259                .with_help("Add parens to call the function");
1260
1261            self.errors.push(error);
1262        }
1263
1264        Expression::Defer {
1265            expression: Box::new(expression),
1266            ty: Type::uninferred(),
1267            span: self.span_from_tokens(start),
1268        }
1269    }
1270
1271    pub fn parse_try_block(&mut self) -> Expression {
1272        let start = self.current_token();
1273        let try_keyword_span = Span::new(self.file_id, start.byte_offset, start.byte_length);
1274
1275        self.ensure(Try);
1276
1277        if !self.is(LeftCurlyBrace) {
1278            let span = self.span_from_tokens(start);
1279            let error = ParseError::new("Invalid `try`", span, "requires a block")
1280                .with_parse_code("syntax_error")
1281                .with_help("Use `try { expression }` instead of `try expression`");
1282            self.errors.push(error);
1283            let expression = self.parse_expression();
1284            return Expression::TryBlock {
1285                items: vec![expression],
1286                ty: Type::uninferred(),
1287                try_keyword_span,
1288                span: self.span_from_tokens(start),
1289            };
1290        }
1291
1292        let brace_token = self.current_token();
1293        self.ensure(LeftCurlyBrace);
1294
1295        if !self.enter_recursion() {
1296            let span = self.span_from_token(self.current_token());
1297            let mut brace_depth = 1u32;
1298            while brace_depth > 0 && !self.at_eof() {
1299                match self.current_token().kind {
1300                    LeftCurlyBrace => brace_depth += 1,
1301                    RightCurlyBrace => brace_depth -= 1,
1302                    _ => {}
1303                }
1304                if brace_depth > 0 {
1305                    self.next();
1306                }
1307            }
1308            self.advance_if(RightCurlyBrace);
1309            return Expression::TryBlock {
1310                items: vec![],
1311                ty: Type::uninferred(),
1312                try_keyword_span,
1313                span,
1314            };
1315        }
1316
1317        let mut items = vec![];
1318
1319        while self.is_not(RightCurlyBrace) && !self.too_many_errors() {
1320            let position = self.position();
1321            let item = self.parse_block_item();
1322
1323            self.advance_if(Semicolon);
1324
1325            items.push(item);
1326            if self.position() == position {
1327                self.next();
1328            }
1329        }
1330
1331        let span = self.close_brace_span(start, brace_token);
1332
1333        self.leave_recursion();
1334
1335        Expression::TryBlock {
1336            items,
1337            ty: Type::uninferred(),
1338            try_keyword_span,
1339            span,
1340        }
1341    }
1342
1343    pub fn parse_recover_block(&mut self) -> Expression {
1344        let start = self.current_token();
1345        let recover_keyword_span = Span::new(self.file_id, start.byte_offset, start.byte_length);
1346
1347        self.ensure(Recover);
1348
1349        if !self.is(LeftCurlyBrace) {
1350            let span = self.span_from_tokens(start);
1351            let error = ParseError::new("Invalid `recover`", span, "requires a block")
1352                .with_parse_code("syntax_error")
1353                .with_help("Use `recover { expression }` instead of `recover expression`");
1354            self.errors.push(error);
1355            let expression = self.parse_expression();
1356            return Expression::RecoverBlock {
1357                items: vec![expression],
1358                ty: Type::uninferred(),
1359                recover_keyword_span,
1360                span: self.span_from_tokens(start),
1361            };
1362        }
1363
1364        let brace_token = self.current_token();
1365        self.ensure(LeftCurlyBrace);
1366
1367        if !self.enter_recursion() {
1368            let span = self.span_from_token(self.current_token());
1369            let mut brace_depth = 1u32;
1370            while brace_depth > 0 && !self.at_eof() {
1371                match self.current_token().kind {
1372                    LeftCurlyBrace => brace_depth += 1,
1373                    RightCurlyBrace => brace_depth -= 1,
1374                    _ => {}
1375                }
1376                if brace_depth > 0 {
1377                    self.next();
1378                }
1379            }
1380            self.advance_if(RightCurlyBrace);
1381            return Expression::RecoverBlock {
1382                items: vec![],
1383                ty: Type::uninferred(),
1384                recover_keyword_span,
1385                span,
1386            };
1387        }
1388
1389        let mut items = vec![];
1390
1391        while self.is_not(RightCurlyBrace) && !self.too_many_errors() {
1392            let position = self.position();
1393            let item = self.parse_block_item();
1394
1395            self.advance_if(Semicolon);
1396
1397            items.push(item);
1398            if self.position() == position {
1399                self.next();
1400            }
1401        }
1402
1403        let span = self.close_brace_span(start, brace_token);
1404
1405        self.leave_recursion();
1406
1407        Expression::RecoverBlock {
1408            items,
1409            ty: Type::uninferred(),
1410            recover_keyword_span,
1411            span,
1412        }
1413    }
1414
1415    pub fn parse_select(&mut self) -> Expression {
1416        let start = self.current_token();
1417
1418        self.ensure(Select);
1419        self.ensure(LeftCurlyBrace);
1420
1421        let mut arms = Vec::new();
1422
1423        while self.is_not(RightCurlyBrace) {
1424            let arm = self.parse_select_arm();
1425            arms.push(arm);
1426
1427            if self.is(RightCurlyBrace) {
1428                break;
1429            }
1430
1431            self.ensure(Comma);
1432        }
1433
1434        self.ensure(RightCurlyBrace);
1435
1436        Expression::Select {
1437            arms,
1438            ty: Type::uninferred(),
1439            span: self.span_from_tokens(start),
1440        }
1441    }
1442
1443    fn parse_select_arm(&mut self) -> SelectArm {
1444        match self.current_token().kind {
1445            Let => {
1446                self.ensure(Let);
1447                let binding = self.parse_pattern();
1448                self.ensure(Equal);
1449                let receive_expression = Box::new(self.parse_expression());
1450                self.ensure(ArrowDouble);
1451                let body = Box::new(self.parse_expression());
1452                SelectArm {
1453                    pattern: SelectArmPattern::Receive {
1454                        binding: Box::new(binding),
1455                        typed_pattern: None,
1456                        receive_expression,
1457                        body,
1458                    },
1459                }
1460            }
1461            Match => {
1462                let match_expression = self.parse_match();
1463                if let Expression::Match { subject, arms, .. } = match_expression {
1464                    SelectArm {
1465                        pattern: SelectArmPattern::MatchReceive {
1466                            receive_expression: subject,
1467                            arms,
1468                        },
1469                    }
1470                } else {
1471                    self.ensure(ArrowDouble);
1472                    let body = Box::new(self.parse_expression());
1473                    SelectArm {
1474                        pattern: SelectArmPattern::Send {
1475                            send_expression: Box::new(match_expression),
1476                            body,
1477                        },
1478                    }
1479                }
1480            }
1481            Identifier if self.current_token().text == "_" => {
1482                self.next();
1483                self.ensure(ArrowDouble);
1484                let body = Box::new(self.parse_expression());
1485                SelectArm {
1486                    pattern: SelectArmPattern::WildCard { body },
1487                }
1488            }
1489            _ => {
1490                let send_expression = Box::new(self.parse_expression());
1491                self.ensure(ArrowDouble);
1492                let body = Box::new(self.parse_expression());
1493                SelectArm {
1494                    pattern: SelectArmPattern::Send {
1495                        send_expression,
1496                        body,
1497                    },
1498                }
1499            }
1500        }
1501    }
1502
1503    pub fn with_control_flow_header<F, R>(&mut self, f: F) -> R
1504    where
1505        F: FnOnce(&mut Self) -> R,
1506    {
1507        let old = self.in_control_flow_header;
1508        self.in_control_flow_header = true;
1509        let result = f(self);
1510        self.in_control_flow_header = old;
1511        result
1512    }
1513
1514    fn keyword_in_value_position(&self) -> bool {
1515        if !self.current_token().kind.is_keyword() {
1516            return false;
1517        }
1518
1519        match self.current_token().kind {
1520            Return | Break | Continue => false,
1521
1522            Match | If | Task | Defer | Try | Recover | Select | Loop | Function => matches!(
1523                self.stream.peek_ahead(1).kind,
1524                RightParen
1525                    | Comma
1526                    | Dot
1527                    | Semicolon
1528                    | RightCurlyBrace
1529                    | RightSquareBracket
1530                    | ArrowDouble
1531                    | QuestionMark
1532                    | EOF
1533                    | Plus
1534                    | Star
1535                    | Slash
1536                    | Percent
1537                    | EqualDouble
1538                    | NotEqual
1539                    | LeftAngleBracket
1540                    | RightAngleBracket
1541                    | LessThanOrEqual
1542                    | GreaterThanOrEqual
1543                    | AmpersandDouble
1544                    | Pipeline
1545                    | Equal
1546                    | PlusEqual
1547                    | MinusEqual
1548                    | StarEqual
1549                    | SlashEqual
1550                    | PercentEqual
1551                    | DotDot
1552                    | DotDotEqual
1553                    | As
1554            ),
1555
1556            _ => true,
1557        }
1558    }
1559
1560    fn recover_keyword_as_identifier(&mut self) -> Expression {
1561        let token = self.current_token();
1562        let keyword = token.text.to_string();
1563        let span = self.span_from_token(token);
1564        let error = ParseError::new("Reserved keyword", span, "reserved keyword")
1565            .with_parse_code("keyword_as_identifier")
1566            .with_help(format!("Rename `{}`", keyword));
1567        self.errors.push(error);
1568        self.next();
1569        Expression::Identifier {
1570            value: keyword.into(),
1571            ty: Type::uninferred(),
1572            span,
1573            binding_id: None,
1574            qualified: None,
1575        }
1576    }
1577
1578    fn looks_like_map_literal(&self) -> bool {
1579        let first = self.current_token().kind;
1580        let second = self.stream.peek_ahead(1).kind;
1581        matches!(first, String | RawString | Integer | Float) && second == Colon
1582    }
1583
1584    fn consume_to_matching_close_brace(&mut self) {
1585        let mut brace_depth = 1u32;
1586        while brace_depth > 0 && !self.at_eof() {
1587            match self.current_token().kind {
1588                LeftCurlyBrace => brace_depth += 1,
1589                RightCurlyBrace => brace_depth -= 1,
1590                _ => {}
1591            }
1592            if brace_depth > 0 {
1593                self.next();
1594            }
1595        }
1596        self.advance_if(RightCurlyBrace);
1597    }
1598
1599    fn recover_unexpected_backtick(&mut self) -> Expression {
1600        let token = self.current_token();
1601        let token_span = self.span_from_token(token);
1602        let opening_span = Span::new(self.file_id, token.byte_offset, 1);
1603        let error = ParseError::new("Unexpected backtick", opening_span, "not allowed here")
1604            .with_parse_code("unexpected_backtick")
1605            .with_help(
1606                "Use a regular string `\"...\"` or a raw string `r\"...\"` for single- or multi-line strings. Backticks in Lisette are reserved for struct-tag attributes.",
1607            );
1608        self.errors.push(error);
1609        self.next();
1610        Expression::Literal {
1611            literal: Literal::String {
1612                value: std::string::String::new(),
1613                raw: true,
1614            },
1615            ty: Type::uninferred(),
1616            span: token_span,
1617        }
1618    }
1619}