Skip to main content

nifty_syntax/parser/
expr.rs

1//! Expressions, parsed by precedence climbing.
2
3use std::str::FromStr;
4
5use super::{ParseResult, Parser};
6use crate::ast::{Arg, BinaryOp, Expr, ExprKind, StringPart, UnaryOp};
7use crate::lexer::unescape;
8use crate::span::Span;
9use crate::token::TokenKind;
10
11/// Whether `call() { … }` may take a trailing block here. It is forbidden where a `{`
12/// opens the statement's own block, as in `if ready() { … }`.
13#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub(super) enum TrailingBlocks {
15    Allowed,
16    Forbidden,
17}
18
19/// Binding power of each binary operator; higher binds tighter.
20fn binding_power(op: BinaryOp) -> u8 {
21    match op {
22        BinaryOp::Or => 1,
23        BinaryOp::And => 2,
24        BinaryOp::Eq
25        | BinaryOp::NotEq
26        | BinaryOp::Lt
27        | BinaryOp::LtEq
28        | BinaryOp::Gt
29        | BinaryOp::GtEq => 3,
30        BinaryOp::Add | BinaryOp::Sub => 4,
31        BinaryOp::Mul | BinaryOp::Div | BinaryOp::Rem => 5,
32    }
33}
34
35fn binary_op(kind: TokenKind) -> Option<BinaryOp> {
36    Some(match kind {
37        TokenKind::OrOr => BinaryOp::Or,
38        TokenKind::AndAnd => BinaryOp::And,
39        TokenKind::EqEq => BinaryOp::Eq,
40        TokenKind::BangEq => BinaryOp::NotEq,
41        TokenKind::Lt => BinaryOp::Lt,
42        TokenKind::LtEq => BinaryOp::LtEq,
43        TokenKind::Gt => BinaryOp::Gt,
44        TokenKind::GtEq => BinaryOp::GtEq,
45        TokenKind::Plus => BinaryOp::Add,
46        TokenKind::Minus => BinaryOp::Sub,
47        TokenKind::Star => BinaryOp::Mul,
48        TokenKind::Slash => BinaryOp::Div,
49        TokenKind::Percent => BinaryOp::Rem,
50        _ => return None,
51    })
52}
53
54impl Parser<'_> {
55    pub(super) fn parse_expr(&mut self, trailing: TrailingBlocks) -> ParseResult<Expr> {
56        self.parse_binary(0, trailing)
57    }
58
59    fn parse_binary(&mut self, min_power: u8, trailing: TrailingBlocks) -> ParseResult<Expr> {
60        let mut lhs = self.parse_prefix(trailing)?;
61        while let Some(op) = binary_op(self.peek()) {
62            let power = binding_power(op);
63            if power <= min_power {
64                break;
65            }
66            self.bump();
67            self.skip_newlines();
68            let rhs = self.parse_binary(power, trailing)?;
69            if op.is_comparison() && binary_op(self.peek()).is_some_and(BinaryOp::is_comparison) {
70                let chained = self.current().span;
71                self.report(
72                    "comparisons can't be chained; combine them with `&&`",
73                    chained,
74                );
75            }
76            lhs = Expr {
77                span: lhs.span.to(rhs.span),
78                kind: ExprKind::Binary {
79                    op,
80                    lhs: Box::new(lhs),
81                    rhs: Box::new(rhs),
82                },
83            };
84        }
85        Ok(lhs)
86    }
87
88    fn parse_prefix(&mut self, trailing: TrailingBlocks) -> ParseResult<Expr> {
89        let op = match self.peek() {
90            TokenKind::Bang => UnaryOp::Not,
91            TokenKind::Minus => UnaryOp::Neg,
92            TokenKind::Dollar => UnaryOp::Bind,
93            _ => return self.parse_postfix(trailing),
94        };
95        let start = self.bump().span;
96        let operand = self.parse_prefix(trailing)?;
97        Ok(Expr {
98            span: start.to(operand.span),
99            kind: ExprKind::Unary {
100                op,
101                operand: Box::new(operand),
102            },
103        })
104    }
105
106    fn parse_postfix(&mut self, trailing: TrailingBlocks) -> ParseResult<Expr> {
107        let mut expr = self.parse_primary()?;
108        loop {
109            expr = match self.peek() {
110                TokenKind::LParen => self.parse_call(expr, trailing)?,
111                TokenKind::Dot => {
112                    self.bump();
113                    let name = self.expect_ident()?;
114                    Expr {
115                        span: expr.span.to(name.span),
116                        kind: ExprKind::Field {
117                            base: Box::new(expr),
118                            name,
119                        },
120                    }
121                }
122                TokenKind::LBracket => {
123                    self.bump();
124                    self.skip_newlines();
125                    let index = self.parse_expr(TrailingBlocks::Allowed)?;
126                    self.skip_newlines();
127                    let close = self.expect(TokenKind::RBracket)?;
128                    Expr {
129                        span: expr.span.to(close.span),
130                        kind: ExprKind::Index {
131                            base: Box::new(expr),
132                            index: Box::new(index),
133                        },
134                    }
135                }
136                _ => return Ok(expr),
137            };
138        }
139    }
140
141    pub(super) fn parse_call(
142        &mut self,
143        callee: Expr,
144        trailing: TrailingBlocks,
145    ) -> ParseResult<Expr> {
146        self.bump();
147        let args = self.comma_list(TokenKind::RParen, Self::parse_arg)?;
148        self.check_argument_order(&args);
149        let trailing_block = if trailing == TrailingBlocks::Allowed && self.at(TokenKind::LBrace) {
150            Some(self.parse_block()?)
151        } else {
152            None
153        };
154        Ok(Expr {
155            span: self.span_from(callee.span.start),
156            kind: ExprKind::Call {
157                callee: Box::new(callee),
158                args,
159                trailing_block,
160            },
161        })
162    }
163
164    fn parse_arg(&mut self) -> ParseResult<Arg> {
165        let label = if self.at(TokenKind::Ident) && self.peek_nth(1) == TokenKind::Colon {
166            let label = self.expect_ident()?;
167            self.bump();
168            self.skip_newlines();
169            Some(label)
170        } else {
171            None
172        };
173        let value = self.parse_expr(TrailingBlocks::Allowed)?;
174        Ok(Arg { label, value })
175    }
176
177    fn check_argument_order(&mut self, args: &[Arg]) {
178        let mut named_seen = false;
179        for arg in args {
180            match arg.label {
181                Some(_) => named_seen = true,
182                None if named_seen => {
183                    self.report(
184                        "positional arguments must come before named ones",
185                        arg.value.span,
186                    );
187                }
188                None => {}
189            }
190        }
191    }
192
193    fn parse_primary(&mut self) -> ParseResult<Expr> {
194        let token = self.current();
195        let kind = match token.kind {
196            TokenKind::Int => {
197                self.bump();
198                self.number_literal(
199                    token.span,
200                    ExprKind::Int,
201                    "this number is too large for an `int`",
202                )
203            }
204            TokenKind::Float => {
205                self.bump();
206                self.number_literal(token.span, ExprKind::Float, "this is not a valid `float`")
207            }
208            TokenKind::True | TokenKind::False => {
209                self.bump();
210                ExprKind::Bool(token.kind == TokenKind::True)
211            }
212            TokenKind::Ident => {
213                self.bump();
214                ExprKind::Name(self.text(token.span).to_owned())
215            }
216            TokenKind::StringStart => return self.parse_string(),
217            TokenKind::Dot => {
218                self.bump();
219                ExprKind::ImplicitMember(self.expect_ident()?)
220            }
221            TokenKind::LBracket => {
222                self.bump();
223                let items = self.comma_list(TokenKind::RBracket, |parser| {
224                    parser.parse_expr(TrailingBlocks::Allowed)
225                })?;
226                ExprKind::List(items)
227            }
228            TokenKind::LParen => {
229                self.bump();
230                self.skip_newlines();
231                let inner = self.parse_expr(TrailingBlocks::Allowed)?;
232                self.skip_newlines();
233                self.expect(TokenKind::RParen)?;
234                return Ok(inner);
235            }
236            _ => return self.unexpected("an expression"),
237        };
238        Ok(Expr {
239            kind,
240            span: self.span_from(token.span.start),
241        })
242    }
243
244    fn parse_string(&mut self) -> ParseResult<Expr> {
245        let start = self.bump().span.start;
246        let mut parts = Vec::new();
247        loop {
248            match self.peek() {
249                TokenKind::StringText => {
250                    let text = self.bump();
251                    parts.push(StringPart::Text(unescape(self.text(text.span))));
252                }
253                TokenKind::InterpolationStart => {
254                    self.bump();
255                    self.skip_newlines();
256                    parts.push(StringPart::Expr(self.parse_expr(TrailingBlocks::Allowed)?));
257                    self.skip_newlines();
258                    self.expect(TokenKind::InterpolationEnd)?;
259                }
260                TokenKind::StringEnd => {
261                    self.bump();
262                    return Ok(Expr {
263                        kind: ExprKind::String(parts),
264                        span: self.span_from(start),
265                    });
266                }
267                _ => return self.unexpected("the end of the string"),
268            }
269        }
270    }
271
272    /// Parses a number token, reporting `problem` if it doesn't fit the target type.
273    fn number_literal<T: FromStr>(
274        &mut self,
275        span: Span,
276        wrap: fn(T) -> ExprKind,
277        problem: &str,
278    ) -> ExprKind {
279        if let Ok(value) = self.text(span).replace('_', "").parse() {
280            wrap(value)
281        } else {
282            self.report(problem, span);
283            ExprKind::Error
284        }
285    }
286}
287
288#[cfg(test)]
289mod tests {
290    use super::binary_op;
291    use crate::token::SYMBOLS;
292
293    #[test]
294    fn binary_operators_round_trip_through_their_tokens() {
295        for &kind in SYMBOLS {
296            if let Some(op) = binary_op(kind) {
297                assert_eq!(op.token(), kind);
298            }
299        }
300    }
301}