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