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() {
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) - first_span.byte_offset,
272                    );
273                    let error = ParseError::new(title, span, "misplaced")
274                        .with_parse_code("syntax_error")
275                        .with_help(help);
276                    self.errors.push(error);
277
278                    let dot_access = self.parse_field_access(lhs);
279                    return self.parse_function_call(dot_access, type_args);
280                }
281
282                self.parse_function_call(lhs, type_args)
283            }
284
285            Colon => {
286                let lhs_name = match &lhs {
287                    ast::Expression::Identifier { value, .. } => value.to_string(),
288                    ast::Expression::DotAccess { member, .. } => member.to_string(),
289                    _ => std::string::String::new(),
290                };
291                let colon_token = self.current_token();
292                let span = ast::Span::new(self.file_id, colon_token.byte_offset, 2);
293                let after = self.stream.peek_ahead(2);
294
295                if after.kind == LeftAngleBracket {
296                    let help = if !lhs_name.is_empty() {
297                        format!(
298                            "Lisette does not use turbofish syntax. Use `{}<T>(...)` instead",
299                            lhs_name
300                        )
301                    } else {
302                        "Lisette does not use turbofish syntax. Use `func<T>(...)` instead"
303                            .to_string()
304                    };
305                    self.track_error_at(span, "invalid syntax", help);
306                    self.next(); // consume first `:`
307                    self.next(); // consume second `:`
308                    let type_args = self.parse_type_args();
309                    self.parse_function_call(lhs, type_args)
310                } else {
311                    let help = if !lhs_name.is_empty() && after.kind == Identifier {
312                        format!(
313                            "Use `.` instead of `::` for enum variant access, e.g. `{}.{}`",
314                            lhs_name, after.text
315                        )
316                    } else {
317                        "Use `.` instead of `::` for enum variant access".to_string()
318                    };
319                    self.track_error_at(span, "invalid syntax", help);
320                    self.next(); // consume first `:`
321                    self.next(); // consume second `:`
322                    let field_start = self.current_token();
323                    let field: EcoString = self.current_token().text.into();
324                    self.ensure(Identifier);
325                    ast::Expression::DotAccess {
326                        ty: Type::uninferred(),
327                        expression: lhs.into(),
328                        member: field,
329                        span: self.span_from_tokens(field_start),
330                    }
331                }
332            }
333
334            _ => {
335                debug_assert!(
336                    false,
337                    "is_postfix_operator and include_in_larger_expression are out of sync"
338                );
339                self.track_error("internal error", "Unexpected token in postfix position");
340                self.resync_on_error();
341                lhs
342            }
343        }
344    }
345
346    pub fn parse_range_end(&mut self) -> ast::Expression {
347        self.pratt_parse(RANGE_PREC)
348    }
349
350    fn check_go_channel_send(&mut self) -> bool {
351        if self.current_token().kind != LeftAngleBracket {
352            return false;
353        }
354        let next = self.stream.peek_ahead(1);
355        if next.kind != Minus {
356            return false;
357        }
358        let current = self.current_token();
359        if current.byte_offset + current.byte_length != next.byte_offset {
360            return false;
361        }
362
363        let span = ast::Span::new(
364            self.file_id,
365            self.current_token().byte_offset,
366            self.current_token().byte_length + 1,
367        );
368        self.track_error_at(
369            span,
370            "invalid syntax",
371            "Use `ch.Send(value)` inside a `select` expression",
372        );
373        self.resync_on_error();
374        true
375    }
376}
377
378fn format_annotation(ann: &ast::Annotation) -> std::string::String {
379    match ann {
380        ast::Annotation::Constructor { name, params, .. } => {
381            if params.is_empty() {
382                name.to_string()
383            } else {
384                format!(
385                    "{}<{}>",
386                    name,
387                    params
388                        .iter()
389                        .map(format_annotation)
390                        .collect::<Vec<_>>()
391                        .join(", ")
392                )
393            }
394        }
395        ast::Annotation::Tuple { elements, .. } => {
396            format!(
397                "({})",
398                elements
399                    .iter()
400                    .map(format_annotation)
401                    .collect::<Vec<_>>()
402                    .join(", ")
403            )
404        }
405        ast::Annotation::Function {
406            params,
407            return_type,
408            ..
409        } => {
410            format!(
411                "fn({}) -> {}",
412                params
413                    .iter()
414                    .map(format_annotation)
415                    .collect::<Vec<_>>()
416                    .join(", "),
417                format_annotation(return_type)
418            )
419        }
420        ast::Annotation::Unknown | ast::Annotation::Opaque { .. } => "_".to_string(),
421    }
422}