Skip to main content

lisette_syntax/parse/
pratt.rs

1use ecow::EcoString;
2
3use super::{ParseError, Parser};
4use crate::ast;
5use crate::lex::TokenKind::{self, *};
6use crate::types::Type;
7
8const RANGE_PREC: u8 = 6;
9const CAST_PREC: u8 = 9;
10
11impl<'source> Parser<'source> {
12    /// Parses by grouping together operations in expressions based on precedence.
13    ///
14    /// 1. Parse a left-hand side expression (primary, unary, or prefix).
15    /// 2. Look for binary or postfix operators.
16    /// 3. For binary operators: If the operator's precedence is higher than `min_prec`,
17    ///    parse the right-hand side recursively with the operator's precedence.
18    /// 4. For postfix operators: Transform the current expression into a larger one.
19    ///
20    /// The `min_prec` param sets the minimum precedence level for this parsing context.
21    pub fn pratt_parse(&mut self, min_prec: u8) -> ast::Expression {
22        if !self.enter_recursion() {
23            let span = self.span_from_token(self.current_token());
24            self.resync_on_error();
25            return ast::Expression::Unit {
26                ty: Type::uninferred(),
27                span,
28            };
29        }
30
31        let start = self.current_token();
32        let mut lhs = self.parse_left_hand_side();
33        let depth_before_loop = self.depth;
34
35        while !self.at_eof() && !self.too_many_errors() {
36            if self.check_go_channel_send() {
37                self.depth = depth_before_loop;
38                self.leave_recursion();
39                return lhs;
40            }
41
42            if self.at_range() && RANGE_PREC > min_prec {
43                lhs = self.parse_range(Some(lhs.into()), start);
44                continue;
45            }
46
47            if self.current_token().kind == As && CAST_PREC > min_prec {
48                self.next();
49                let target_type = self.parse_annotation();
50                lhs = ast::Expression::Cast {
51                    expression: lhs.into(),
52                    target_type,
53                    ty: Type::uninferred(),
54                    span: self.span_from_tokens(start),
55                };
56                continue;
57            }
58
59            if min_prec == 0
60                && self.current_token().kind == PipeDouble
61                && self.newline_before_current()
62            {
63                break;
64            }
65
66            if let Some(prec) = self.binary_operator_precedence(self.current_token().kind)
67                && prec > min_prec
68            {
69                let operator = self.parse_binary_operator();
70                let rhs = self.pratt_parse(prec);
71                lhs = ast::Expression::Binary {
72                    operator,
73                    left: lhs.into(),
74                    right: rhs.into(),
75                    ty: Type::uninferred(),
76                    span: self.span_from_tokens(start),
77                };
78                continue;
79            }
80
81            if self.is_postfix_operator(&lhs) {
82                if !self.enter_recursion() {
83                    break;
84                }
85                if self.is_format_string(&lhs)
86                    && (self.current_token().kind == LeftParen
87                        || self.current_token().kind == LeftSquareBracket)
88                    && self.newline_before_current()
89                {
90                    break;
91                }
92                lhs = self.include_in_larger_expression(lhs);
93                continue;
94            }
95
96            break;
97        }
98
99        self.depth = depth_before_loop;
100        self.leave_recursion();
101
102        lhs
103    }
104
105    fn prefix_operator_precedence(&self, kind: TokenKind) -> u8 {
106        match kind {
107            Minus | Bang | Caret | Ampersand => 15,
108            _ => {
109                debug_assert!(false, "unexpected prefix operator: {:?}", kind);
110                15
111            }
112        }
113    }
114
115    fn binary_operator_precedence(&self, kind: TokenKind) -> Option<u8> {
116        match kind {
117            LeftAngleBracket if self.is_type_args_call() => None,
118            Pipeline => Some(1),
119            PipeDouble if self.stream.peek_ahead(1).kind == Arrow => None,
120            PipeDouble => Some(3),
121            AmpersandDouble => Some(4),
122            EqualDouble | NotEqual | LeftAngleBracket | RightAngleBracket | LessThanOrEqual
123            | GreaterThanOrEqual => Some(5),
124            Plus | Minus | Pipe | Caret => Some(7),
125            Star | Slash | Percent | ShiftLeft | ShiftRight | Ampersand | AndNot => Some(8),
126            _ => None,
127        }
128    }
129
130    fn is_postfix_operator(&self, lhs: &ast::Expression) -> bool {
131        match self.current_token().kind {
132            LeftParen | LeftSquareBracket | QuestionMark | Dot => true,
133            LeftCurlyBrace => match lhs {
134                ast::Expression::Identifier { .. } | ast::Expression::DotAccess { .. } => {
135                    self.is_struct_instantiation()
136                }
137                _ => false,
138            },
139            LeftAngleBracket => self.is_type_args_call(),
140            Colon if self.stream.peek_ahead(1).kind == Colon => true,
141            _ => false,
142        }
143    }
144
145    fn is_format_string(&self, expression: &ast::Expression) -> bool {
146        matches!(
147            expression,
148            ast::Expression::Literal {
149                literal: ast::Literal::FormatString(_),
150                ..
151            }
152        )
153    }
154
155    fn parse_left_hand_side(&mut self) -> ast::Expression {
156        let start = self.current_token();
157
158        match start.kind {
159            Bang | Minus | Caret => {
160                self.next();
161
162                let operator = match start.kind {
163                    Bang => ast::UnaryOperator::Not,
164                    Minus => ast::UnaryOperator::Negative,
165                    Caret => ast::UnaryOperator::BitwiseNot,
166                    _ => unreachable!("guarded by match arm"),
167                };
168
169                let prec = self.prefix_operator_precedence(start.kind);
170
171                ast::Expression::Unary {
172                    operator,
173                    expression: self.pratt_parse(prec).into(),
174                    ty: Type::uninferred(),
175                    span: self.span_from_tokens(start),
176                }
177            }
178
179            Ampersand => {
180                self.next();
181                if self.current_token().kind == Mut {
182                    let span = ast::Span::new(
183                        self.file_id,
184                        start.byte_offset,
185                        self.current_token().byte_offset + self.current_token().byte_length
186                            - start.byte_offset,
187                    );
188                    self.track_error_at(
189                        span,
190                        "invalid syntax",
191                        "Lisette has no mutable references. Use `&x` instead",
192                    );
193                    self.next(); // consume `mut`
194                }
195                let prec = self.prefix_operator_precedence(start.kind);
196                ast::Expression::Reference {
197                    expression: self.pratt_parse(prec).into(),
198                    ty: Type::uninferred(),
199                    span: self.span_from_tokens(start),
200                }
201            }
202
203            _ => self.parse_atomic_expression(),
204        }
205    }
206
207    pub fn include_in_larger_expression(&mut self, lhs: ast::Expression) -> ast::Expression {
208        match self.current_token().kind {
209            LeftParen => self.parse_function_call(lhs, vec![]),
210            LeftSquareBracket => self.parse_index_expression(lhs),
211            LeftCurlyBrace => self.parse_struct_call(lhs),
212            QuestionMark => self.parse_try(lhs),
213            Dot => self.parse_field_access(lhs),
214            LeftAngleBracket => {
215                let type_args = self.parse_type_args();
216
217                if self.current_token().kind == Dot && self.stream.peek_ahead(1).kind == Identifier
218                {
219                    let type_name = match &lhs {
220                        ast::Expression::Identifier { value, .. } => value.as_str(),
221                        ast::Expression::DotAccess { member, .. } => member.as_str(),
222                        _ => "",
223                    };
224                    let method = self.stream.peek_ahead(1).text;
225                    let args_str = type_args
226                        .iter()
227                        .map(format_annotation)
228                        .collect::<Vec<_>>()
229                        .join(", ");
230                    let plural = type_args.len() != 1;
231                    let title = if plural {
232                        "Misplaced type arguments"
233                    } else {
234                        "Misplaced type argument"
235                    };
236                    let help = if !type_name.is_empty() {
237                        format!(
238                            "Set the type {} on the method: `{}.{}<{}>()`",
239                            if plural { "arguments" } else { "argument" },
240                            type_name,
241                            method,
242                            args_str,
243                        )
244                    } else {
245                        format!(
246                            "Set the type {} on the method: `.{}<{}>()`",
247                            if plural { "arguments" } else { "argument" },
248                            method,
249                            args_str,
250                        )
251                    };
252                    let Some(first) = type_args.first() else {
253                        return self.parse_function_call(lhs, type_args);
254                    };
255                    let first_span = first.get_span();
256                    let last_span = type_args.last().expect("non-empty").get_span();
257                    let span = ast::Span::new(
258                        self.file_id,
259                        first_span.byte_offset,
260                        (last_span.byte_offset + last_span.byte_length)
261                            .saturating_sub(first_span.byte_offset),
262                    );
263                    let error = ParseError::new(title, span, "misplaced")
264                        .with_parse_code("syntax_error")
265                        .with_help(help);
266                    self.errors.push(error);
267
268                    let dot_access = self.parse_field_access(lhs);
269                    return self.parse_function_call(dot_access, type_args);
270                }
271
272                self.parse_function_call(lhs, type_args)
273            }
274
275            Colon => {
276                let lhs_name = match &lhs {
277                    ast::Expression::Identifier { value, .. } => value.to_string(),
278                    ast::Expression::DotAccess { member, .. } => member.to_string(),
279                    _ => std::string::String::new(),
280                };
281                let colon_token = self.current_token();
282                let span = ast::Span::new(self.file_id, colon_token.byte_offset, 2);
283                let after = self.stream.peek_ahead(2);
284
285                if after.kind == LeftAngleBracket {
286                    let help = if !lhs_name.is_empty() {
287                        format!(
288                            "Lisette does not use turbofish syntax. Use `{}<T>(...)` instead",
289                            lhs_name
290                        )
291                    } else {
292                        "Lisette does not use turbofish syntax. Use `func<T>(...)` instead"
293                            .to_string()
294                    };
295                    self.track_error_at(span, "invalid syntax", help);
296                    self.next(); // consume first `:`
297                    self.next(); // consume second `:`
298                    let type_args = self.parse_type_args();
299                    self.parse_function_call(lhs, type_args)
300                } else {
301                    let help = if !lhs_name.is_empty() && after.kind == Identifier {
302                        format!(
303                            "Use `.` instead of `::` for enum variant access, e.g. `{}.{}`",
304                            lhs_name, after.text
305                        )
306                    } else {
307                        "Use `.` instead of `::` for enum variant access".to_string()
308                    };
309                    self.track_error_at(span, "invalid syntax", help);
310                    self.next(); // consume first `:`
311                    self.next(); // consume second `:`
312                    let field_start = self.current_token();
313                    let field: EcoString = self.current_token().text.into();
314                    self.ensure(Identifier);
315                    ast::Expression::DotAccess {
316                        ty: Type::uninferred(),
317                        expression: lhs.into(),
318                        member: field,
319                        span: self.span_from_tokens(field_start),
320                        dot_access_kind: None,
321                        receiver_coercion: None,
322                    }
323                }
324            }
325
326            _ => {
327                debug_assert!(
328                    false,
329                    "is_postfix_operator and include_in_larger_expression are out of sync"
330                );
331                self.track_error("internal error", "Unexpected token in postfix position");
332                self.resync_on_error();
333                lhs
334            }
335        }
336    }
337
338    pub fn parse_range_end(&mut self) -> ast::Expression {
339        self.pratt_parse(RANGE_PREC)
340    }
341
342    fn check_go_channel_send(&mut self) -> bool {
343        if self.current_token().kind != LeftAngleBracket {
344            return false;
345        }
346        let next = self.stream.peek_ahead(1);
347        if next.kind != Minus {
348            return false;
349        }
350        let current = self.current_token();
351        if current.byte_offset + current.byte_length != next.byte_offset {
352            return false;
353        }
354
355        let span = ast::Span::new(
356            self.file_id,
357            self.current_token().byte_offset,
358            self.current_token().byte_length + 1,
359        );
360        self.track_error_at(
361            span,
362            "invalid syntax",
363            "Use `ch.Send(value)` inside a `select` expression",
364        );
365        self.resync_on_error();
366        true
367    }
368}
369
370fn format_annotation(ann: &ast::Annotation) -> std::string::String {
371    match ann {
372        ast::Annotation::Constructor { name, params, .. } => {
373            if params.is_empty() {
374                name.to_string()
375            } else {
376                format!(
377                    "{}<{}>",
378                    name,
379                    params
380                        .iter()
381                        .map(format_annotation)
382                        .collect::<Vec<_>>()
383                        .join(", ")
384                )
385            }
386        }
387        ast::Annotation::Tuple { elements, .. } => {
388            format!(
389                "({})",
390                elements
391                    .iter()
392                    .map(format_annotation)
393                    .collect::<Vec<_>>()
394                    .join(", ")
395            )
396        }
397        ast::Annotation::Function {
398            params,
399            return_type,
400            ..
401        } => {
402            format!(
403                "fn({}) -> {}",
404                params
405                    .iter()
406                    .map(format_annotation)
407                    .collect::<Vec<_>>()
408                    .join(", "),
409                format_annotation(return_type)
410            )
411        }
412        ast::Annotation::Unknown | ast::Annotation::Opaque { .. } => "_".to_string(),
413    }
414}