Skip to main content

dada_parser/
expr.rs

1use dada_ir_ast::ast::{
2    AstBinaryOp, AstBlock, AstConstructorField, AstExpr, AstExprKind, AstPath, AstPathKind,
3    DeferredParse, Identifier, IfArm, Literal, LiteralKind, PermissionOp, SpannedBinaryOp,
4    SpannedIdentifier, SpannedUnaryOp, SquareBracketArgs, UnaryOp,
5};
6
7use crate::{
8    Parse, Parser,
9    tokenizer::{
10        Keyword, Token, TokenKind,
11        operator::{self, Op},
12    },
13};
14
15impl<'db> Parse<'db> for AstExpr<'db> {
16    type Output = Self;
17
18    fn opt_parse(
19        db: &'db dyn crate::Db,
20        parser: &mut Parser<'_, 'db>,
21    ) -> Result<Option<Self::Output>, crate::ParseFail<'db>> {
22        opt_parse_expr_with_precedence(db, parser, binary_expr_precedence::<SELECT_ALL>)
23    }
24
25    fn expected() -> crate::Expected {
26        crate::Expected::Nonterminal("expression")
27    }
28}
29
30const SELECT_ALL: u32 = u32::MAX;
31const SELECT_STRUCT: u32 = 1;
32
33fn eat_expr_with_precedence<'db>(
34    db: &'db dyn crate::Db,
35    parser: &mut Parser<'_, 'db>,
36    precedence: impl FnOnce(
37        &'db dyn crate::Db,
38        &mut Parser<'_, 'db>,
39    ) -> Result<Option<AstExprKind<'db>>, crate::ParseFail<'db>>,
40) -> Result<AstExpr<'db>, crate::ParseFail<'db>> {
41    match opt_parse_expr_with_precedence(db, parser, precedence)? {
42        Some(e) => Ok(e),
43        None => Err(parser.illformed(AstExpr::expected())),
44    }
45}
46
47fn opt_parse_expr_with_precedence<'db>(
48    db: &'db dyn crate::Db,
49    parser: &mut Parser<'_, 'db>,
50    precedence: impl FnOnce(
51        &'db dyn crate::Db,
52        &mut Parser<'_, 'db>,
53    ) -> Result<Option<AstExprKind<'db>>, crate::ParseFail<'db>>,
54) -> Result<Option<AstExpr<'db>>, crate::ParseFail<'db>> {
55    let start_span = parser.peek_span();
56    let Some(kind) = precedence(db, parser)? else {
57        return Ok(None);
58    };
59    Ok(Some(AstExpr::new(
60        start_span.to(db, parser.last_span()),
61        kind,
62    )))
63}
64
65const BINARY_OP_PRECEDENCE: &[&[(Op, AstBinaryOp)]] = &[
66    &[
67        (operator::PLUS, AstBinaryOp::Add),
68        (operator::MINUS, AstBinaryOp::Sub),
69    ],
70    &[
71        (operator::STAR, AstBinaryOp::Mul),
72        (operator::SLASH, AstBinaryOp::Div),
73    ],
74    &[
75        (operator::GREATERTHANEQ, AstBinaryOp::GreaterEqual),
76        (operator::LESSTHANEQ, AstBinaryOp::LessEqual),
77        (operator::GREATERTHAN, AstBinaryOp::GreaterThan),
78        (operator::LESSTHAN, AstBinaryOp::LessThan),
79        (operator::EQEQ, AstBinaryOp::EqualEqual),
80    ],
81    &[(operator::ANDAND, AstBinaryOp::AndAnd)],
82    &[(operator::PIPEPIPE, AstBinaryOp::OrOr)],
83    &[(operator::EQ, AstBinaryOp::Assign)],
84];
85
86fn binary_expr_precedence<'db, const SELECT: u32>(
87    db: &'db dyn crate::Db,
88    parser: &mut Parser<'_, 'db>,
89) -> Result<Option<AstExprKind<'db>>, crate::ParseFail<'db>> {
90    binary_expr_with_precedence_level::<SELECT>(db, parser, 0)
91}
92
93fn binary_expr_with_precedence_level<'db, const SELECT: u32>(
94    db: &'db dyn crate::Db,
95    parser: &mut Parser<'_, 'db>,
96    precedence: usize,
97) -> Result<Option<AstExprKind<'db>>, crate::ParseFail<'db>> {
98    let start_span = parser.peek_span();
99
100    if precedence >= BINARY_OP_PRECEDENCE.len() {
101        return postfix_expr_precedence::<SELECT>(db, parser);
102    }
103
104    // Parse the LHS at one higher level of precedence than
105    // the current one.
106    let Some(mut lhs_kind) =
107        binary_expr_with_precedence_level::<SELECT>(db, parser, precedence + 1)?
108    else {
109        return Ok(None);
110    };
111
112    // Parse as many RHS at the current level of precedence as we can find.
113    // Note that the binary operator must appear on the current line;
114    // binary operators on the *next line* don't count, those are prefix unary operators (or errors,
115    // as the case may be).
116    'outer: loop {
117        let mid_span = parser.peek_span();
118
119        if parser.next_token_on_same_line() {
120            for &(op_text, op) in BINARY_OP_PRECEDENCE[precedence] {
121                if let Ok(op_span) = parser.eat_op(op_text) {
122                    let lhs = AstExpr::new(start_span.to(db, mid_span), lhs_kind);
123                    let rhs = eat_expr_with_precedence(db, parser, |db, parser| {
124                        // Parse RHS at the current level of precedence:
125                        binary_expr_with_precedence_level::<SELECT>(db, parser, precedence)
126                    })?;
127                    lhs_kind =
128                        AstExprKind::BinaryOp(SpannedBinaryOp { span: op_span, op }, lhs, rhs);
129                    continue 'outer;
130                }
131            }
132        }
133
134        return Ok(Some(lhs_kind));
135    }
136}
137
138fn postfix_expr_precedence<'db, const SELECT: u32>(
139    db: &'db dyn crate::Db,
140    parser: &mut Parser<'_, 'db>,
141) -> Result<Option<AstExprKind<'db>>, crate::ParseFail<'db>> {
142    let start_span = parser.peek_span();
143
144    let Some(mut kind) = base_expr_precedence::<SELECT>(db, parser)? else {
145        return Ok(None);
146    };
147
148    loop {
149        let mid_span = parser.last_span();
150
151        // `.` can skip newlines
152        if parser.eat_op(operator::DOT).is_ok() {
153            if let Ok(id) = parser.eat_id() {
154                let owner = AstExpr::new(start_span.to(db, mid_span), kind);
155                kind = AstExprKind::DotId(owner, id);
156                continue;
157            }
158
159            if let Ok(await_keyword) = parser.eat_keyword(Keyword::Await) {
160                let future = AstExpr::new(start_span.to(db, mid_span), kind);
161                kind = AstExprKind::Await {
162                    future,
163                    await_keyword,
164                };
165                continue;
166            }
167
168            if let Some(op) = PermissionOp::opt_parse(db, parser)? {
169                let value = AstExpr::new(start_span.to(db, mid_span), kind);
170                kind = AstExprKind::PermissionOp { value, op };
171                continue;
172            }
173        }
174
175        // Postfix `[]` is only valid on the same line, since `[..]` is also valid as the start of an expression
176        if parser.next_token_on_same_line()
177            && let Ok(text) = parser.eat_delimited(crate::tokenizer::Delimiter::SquareBrackets)
178        {
179            let owner = AstExpr::new(start_span.to(db, mid_span), kind);
180            let deferred = DeferredParse {
181                span: parser.last_span(),
182                contents: text.to_string(),
183            };
184            let args = SquareBracketArgs::new(db, deferred);
185            kind = AstExprKind::SquareBracketOp(owner, args);
186            continue;
187        }
188
189        // Postfix `()` is only valid on the same line, since `[..]` is also valid as the start of an expression
190        if parser.next_token_on_same_line()
191            && let Some(args) = AstExpr::opt_parse_delimited(
192                db,
193                parser,
194                crate::tokenizer::Delimiter::Parentheses,
195                AstExpr::eat_comma,
196            )?
197        {
198            let owner = AstExpr::new(start_span.to(db, mid_span), kind);
199            kind = AstExprKind::ParenthesisOp(owner, args);
200            continue;
201        }
202
203        return Ok(Some(kind));
204    }
205}
206
207/// Parses base expressions - the "atoms" of the expression grammar.
208///
209/// Base expressions are those that don't involve operators or complex precedence:
210/// - **Literals**: Numbers, strings, booleans (`42`, `"hello"`, `true`)
211/// - **Identifiers**: Variable names and `self`
212/// - **Control flow**: `if` expressions, `return` statements
213/// - **Constructors**: `Type { field: value }` (when `SELECT_STRUCT` is enabled)
214/// - **Unary operators**: `!expr`, `-expr`
215///
216/// This function is called at the highest precedence level, meaning these expressions
217/// bind most tightly and are parsed first before any binary operators.
218///
219/// # String Literal Handling
220///
221/// String literals are parsed through [`Literal::opt_parse`], which:
222/// 1. Recognizes `TokenKind::Literal(LiteralKind::String, text)` tokens
223/// 2. Creates a `Literal` AST node with the raw tokenizer text
224/// 3. Wraps it in `AstExprKind::Literal` for the expression tree
225///
226/// Note: The current implementation has a bug where escape sequences are validated
227/// but never interpreted - `"hello\nworld"` remains as literal `\n` characters.
228fn base_expr_precedence<'db, const SELECT: u32>(
229    db: &'db dyn crate::Db,
230    parser: &mut Parser<'_, 'db>,
231) -> Result<Option<AstExprKind<'db>>, crate::ParseFail<'db>> {
232    if let Some(literal) = Literal::opt_parse(db, parser)? {
233        return Ok(Some(AstExprKind::Literal(literal)));
234    }
235
236    if let Ok(if_span) = parser.eat_keyword(Keyword::If) {
237        return Ok(Some(if_chain(db, parser, if_span)?));
238    }
239
240    if let Ok(id) = parser.eat_id() {
241        // Could be `X { field1: value1, .. }`
242        if (SELECT & SELECT_STRUCT != 0)
243            && parser.next_token_on_same_line()
244            && let Some(fields) = AstConstructorField::opt_parse_delimited(
245                db,
246                parser,
247                crate::tokenizer::Delimiter::CurlyBraces,
248                AstConstructorField::eat_comma,
249            )?
250        {
251            let path = AstPath::new(db, AstPathKind::Identifier(id));
252            return Ok(Some(AstExprKind::Constructor(path, fields)));
253        }
254
255        return Ok(Some(AstExprKind::Id(id)));
256    }
257
258    if let Ok(span) = parser.eat_keyword(Keyword::Self_) {
259        let id = SpannedIdentifier {
260            span,
261            id: Identifier::self_ident(db),
262        };
263        return Ok(Some(AstExprKind::Id(id)));
264    }
265
266    if parser.eat_keyword(Keyword::Return).is_ok() {
267        // Could be `return foo`
268        if parser.next_token_on_same_line()
269            && let Some(expr) = AstExpr::opt_parse(db, parser)?
270        {
271            return Ok(Some(AstExprKind::Return(Some(expr))));
272        }
273        return Ok(Some(AstExprKind::Return(None)));
274    }
275
276    if let Ok(span) = parser.eat_op(operator::BANG) {
277        let expr = eat_expr_with_precedence(db, parser, postfix_expr_precedence::<SELECT>)?;
278        return Ok(Some(AstExprKind::UnaryOp(
279            SpannedUnaryOp {
280                span,
281                op: UnaryOp::Not,
282            },
283            expr,
284        )));
285    }
286
287    if let Ok(span) = parser.eat_op(operator::MINUS) {
288        let expr = eat_expr_with_precedence(db, parser, postfix_expr_precedence::<SELECT>)?;
289        return Ok(Some(AstExprKind::UnaryOp(
290            SpannedUnaryOp {
291                span,
292                op: UnaryOp::Negate,
293            },
294            expr,
295        )));
296    }
297
298    Ok(None)
299}
300
301fn if_chain<'db>(
302    db: &'db dyn crate::Db,
303    parser: &mut Parser<'_, 'db>,
304    _if_span: dada_ir_ast::span::Span<'db>,
305) -> Result<AstExprKind<'db>, crate::ParseFail<'db>> {
306    let condition0 = eat_expr_with_precedence(
307        db,
308        parser,
309        binary_expr_precedence::<{ SELECT_ALL - SELECT_STRUCT }>,
310    )?;
311
312    let block0 = AstBlock::eat(db, parser)?;
313
314    let mut arms = vec![IfArm {
315        condition: Some(condition0),
316        result: block0,
317    }];
318
319    while parser.eat_keyword(Keyword::Else).is_ok() {
320        if let Ok(_if_span) = parser.eat_keyword(Keyword::If) {
321            let else_if_condition = eat_expr_with_precedence(
322                db,
323                parser,
324                binary_expr_precedence::<{ SELECT_ALL - SELECT_STRUCT }>,
325            )?;
326            let else_if_block = AstBlock::eat(db, parser)?;
327            arms.push(IfArm {
328                condition: Some(else_if_condition),
329                result: else_if_block,
330            });
331        } else {
332            let else_block = AstBlock::eat(db, parser)?;
333            arms.push(IfArm {
334                condition: None,
335                result: else_block,
336            });
337            break;
338        }
339    }
340
341    Ok(AstExprKind::If(arms))
342}
343
344impl<'db> Parse<'db> for PermissionOp {
345    type Output = Self;
346
347    fn opt_parse(
348        _db: &'db dyn crate::Db,
349        parser: &mut Parser<'_, 'db>,
350    ) -> Result<Option<Self::Output>, crate::ParseFail<'db>> {
351        if parser.eat_keyword(Keyword::Give).is_ok() {
352            Ok(Some(PermissionOp::Give))
353        } else if parser.eat_keyword(Keyword::Mut).is_ok() {
354            Ok(Some(PermissionOp::Mutate))
355        } else if parser.eat_keyword(Keyword::Ref).is_ok() {
356            Ok(Some(PermissionOp::Reference))
357        } else if parser.eat_keyword(Keyword::Share).is_ok() {
358            Ok(Some(PermissionOp::Share))
359        } else {
360            Ok(None)
361        }
362    }
363
364    fn expected() -> crate::Expected {
365        crate::Expected::Nonterminal("permission operator")
366    }
367}
368
369impl<'db> Parse<'db> for Literal<'db> {
370    type Output = Self;
371
372    /// Parses literal values from tokens.
373    ///
374    /// This implementation demonstrates the parser's commitment model:
375    /// - **No commitment**: Returns `Ok(None)` if no literal token is found
376    /// - **Commitment**: Once a literal token is detected, consumes it and returns the value
377    /// - **Error after commitment**: If token consumption fails, would return `Err` (though this is unlikely for literals)
378    ///
379    /// # Supported Literals
380    ///
381    /// * **Tokenizer literals**: Integers and strings from `TokenKind::Literal`
382    /// * **Boolean keywords**: `true` and `false` keywords are treated as boolean literals
383    ///
384    /// # String Literal Processing
385    ///
386    /// For string literals, this method:
387    /// 1. Takes the raw text from the tokenizer (with escape sequences still as text)
388    /// 2. Creates a `Literal::new(db, LiteralKind::String, raw_text)`
389    /// 3. **Does not interpret escape sequences** - this is currently a bug
390    ///
391    /// Future implementations should either have the tokenizer pre-process escapes
392    /// or add escape interpretation here.
393    fn opt_parse(
394        db: &'db dyn crate::Db,
395        parser: &mut Parser<'_, 'db>,
396    ) -> Result<Option<Self::Output>, crate::ParseFail<'db>> {
397        match parser.peek() {
398            Some(Token {
399                kind: TokenKind::Literal(kind, token_text),
400                ..
401            }) => {
402                let literal = Literal::new(db, *kind, token_text.text(db).clone());
403
404                parser.eat_next_token().unwrap();
405
406                Ok(Some(literal))
407            }
408
409            Some(Token {
410                kind: TokenKind::Keyword(Keyword::True),
411                ..
412            }) => {
413                parser.eat_next_token().unwrap();
414                Ok(Some(Literal::new(db, LiteralKind::Boolean, "true")))
415            }
416
417            Some(Token {
418                kind: TokenKind::Keyword(Keyword::False),
419                ..
420            }) => {
421                parser.eat_next_token().unwrap();
422                Ok(Some(Literal::new(db, LiteralKind::Boolean, "false")))
423            }
424
425            _ => Ok(None),
426        }
427    }
428
429    fn expected() -> crate::Expected {
430        crate::Expected::Nonterminal("literal")
431    }
432}
433
434impl<'db> Parse<'db> for AstConstructorField<'db> {
435    type Output = Self;
436
437    fn opt_parse(
438        db: &'db dyn crate::Db,
439        parser: &mut Parser<'_, 'db>,
440    ) -> Result<Option<Self::Output>, crate::ParseFail<'db>> {
441        let Ok(name) = parser.eat_id() else {
442            return Ok(None);
443        };
444
445        let _colon = parser.eat_op(operator::COLON)?;
446
447        let value = AstExpr::eat(db, parser)?;
448
449        Ok(Some(AstConstructorField { name, value }))
450    }
451
452    fn expected() -> crate::Expected {
453        crate::Expected::Nonterminal("field initializer")
454    }
455}