Skip to main content

aver/parser/
expr.rs

1use super::*;
2
3impl Parser {
4    fn is_upper_camel_segment(name: &str) -> bool {
5        name.chars().next().is_some_and(|c| c.is_uppercase())
6    }
7
8    fn is_constructor_path(path: &str) -> bool {
9        path.rsplit('.')
10            .next()
11            .is_some_and(Self::is_upper_camel_segment)
12    }
13
14    fn reject_zero_arg_constructor_call(&self, path: &str) -> Result<(), ParseError> {
15        if Self::is_constructor_path(path) && self.peek(1).kind == TokenKind::RParen {
16            return Err(self.error(format!(
17                "Zero-argument constructor call '{}()' is not allowed. Use '{}' (no parentheses).",
18                path, path
19            )));
20        }
21        Ok(())
22    }
23
24    /// Wrap an Expr with the current token's line.
25    fn spanned(&self, expr: Expr, line: usize) -> Spanned<Expr> {
26        Spanned::new(expr, line)
27    }
28
29    pub fn parse_expr(&mut self) -> Result<Spanned<Expr>, ParseError> {
30        // Iron — B4: every recursive descent into a sub-expression
31        // (LParen, LBrace, LBracket, Option.Some(...), nested
32        // interpolation, …) re-enters `parse_expr`. Counting only
33        // here is sufficient — `parse_unary`'s self-recursion is
34        // bounded by the same chain (`-(-(-(x)))` parses through
35        // `parse_unary → parse_postfix → parse_atom → LParen →
36        // parse_expr` once the first non-`-` token appears).
37        self.enter_recursion()?;
38        let result = self.parse_comparison();
39        self.exit_recursion();
40        result
41    }
42
43    pub(super) fn parse_comparison(&mut self) -> Result<Spanned<Expr>, ParseError> {
44        let mut left = self.parse_additive()?;
45
46        loop {
47            let line = self.current().line;
48            let op = match &self.current().kind {
49                TokenKind::Eq => BinOp::Eq,
50                TokenKind::Neq => BinOp::Neq,
51                TokenKind::Lt => BinOp::Lt,
52                TokenKind::Gt => BinOp::Gt,
53                TokenKind::Lte => BinOp::Lte,
54                TokenKind::Gte => BinOp::Gte,
55                _ => break,
56            };
57            self.advance();
58            let right = self.parse_additive()?;
59            left = self.spanned(Expr::BinOp(op, Box::new(left), Box::new(right)), line);
60        }
61
62        Ok(left)
63    }
64
65    pub(super) fn parse_additive(&mut self) -> Result<Spanned<Expr>, ParseError> {
66        let mut left = self.parse_multiplicative()?;
67
68        loop {
69            let line = self.current().line;
70            let op = match &self.current().kind {
71                TokenKind::Plus => BinOp::Add,
72                TokenKind::Minus => BinOp::Sub,
73                _ => break,
74            };
75            self.advance();
76            let right = self.parse_multiplicative()?;
77            left = self.spanned(Expr::BinOp(op, Box::new(left), Box::new(right)), line);
78        }
79
80        Ok(left)
81    }
82
83    pub(super) fn parse_multiplicative(&mut self) -> Result<Spanned<Expr>, ParseError> {
84        let mut left = self.parse_unary()?;
85
86        loop {
87            let line = self.current().line;
88            let op = match &self.current().kind {
89                TokenKind::Star => BinOp::Mul,
90                TokenKind::Slash => BinOp::Div,
91                _ => break,
92            };
93            self.advance();
94            let right = self.parse_unary()?;
95            left = self.spanned(Expr::BinOp(op, Box::new(left), Box::new(right)), line);
96        }
97
98        Ok(left)
99    }
100
101    pub(super) fn parse_unary(&mut self) -> Result<Spanned<Expr>, ParseError> {
102        // Iron — B4 followup: `------x` patterns don't pass through
103        // `parse_expr`, so the parse_expr-only guard doesn't bound
104        // their stack. Count unary minus consecutive tokens
105        // iteratively, then descend once. Folds an N-deep `Neg(...)`
106        // chain into the same shape the recursive form produces.
107        let mut neg_depth: u32 = 0;
108        let mut first_line = self.current().line;
109        while self.check_exact(&TokenKind::Minus) {
110            if neg_depth == 0 {
111                first_line = self.current().line;
112            }
113            self.advance();
114            neg_depth = neg_depth.saturating_add(1);
115            if neg_depth > super::MAX_PARSE_DEPTH {
116                return Err(self.error(format!(
117                    "Unary minus chained too deeply (max {} levels). Refactor with named bindings.",
118                    super::MAX_PARSE_DEPTH
119                )));
120            }
121        }
122        let mut operand = self.parse_postfix()?;
123        for _ in 0..neg_depth {
124            operand = self.spanned(Expr::Neg(Box::new(operand)), first_line);
125        }
126        Ok(operand)
127    }
128
129    pub(super) fn parse_postfix(&mut self) -> Result<Spanned<Expr>, ParseError> {
130        let mut expr = self.parse_call_or_atom()?;
131        // Iron — B4 followup: bound the postfix chain length. The
132        // parser itself wraps each `?` / `.field` / `(args)` in a
133        // single AST node via the surrounding loop — that's
134        // iteration, not recursion, so this side is safe. The AST
135        // it produces is what carries the depth: every downstream
136        // walker (`run_type_check`, `resolve_program`, codegen
137        // visitors) recurses into `Box<Spanned<Expr>>` arms and
138        // explodes the thread stack on a 10 000-deep chain. Cap the
139        // *chain* at parse time so the AST never reaches that depth.
140        let mut postfix_depth: u32 = 0;
141
142        loop {
143            if self.check_exact(&TokenKind::Question) && self.peek(1).kind == TokenKind::Bang {
144                // ?! on tuple: independent product with Result unwrapping
145                let line = self.current().line;
146                if let Expr::Tuple(elements) = expr.node {
147                    self.advance(); // consume ?
148                    self.advance(); // consume !
149                    expr = self.spanned(Expr::IndependentProduct(elements, true), line);
150                } else {
151                    return Err(self.error(
152                        "Operator '?!' can only be applied to a tuple expression, e.g. (a, b)?!"
153                            .to_string(),
154                    ));
155                }
156            } else if self.check_exact(&TokenKind::Bang)
157                && !matches!(self.peek(1).kind, TokenKind::LBracket)
158            {
159                // ! on tuple: independent product without unwrapping
160                // (but not `! [Effects]` which is an effect declaration)
161                let line = self.current().line;
162                if let Expr::Tuple(elements) = expr.node {
163                    self.advance(); // consume !
164                    expr = self.spanned(Expr::IndependentProduct(elements, false), line);
165                } else {
166                    break; // bare ! on non-tuple — not ours, leave for caller
167                }
168            } else if self.check_exact(&TokenKind::Question) {
169                let line = self.current().line;
170                self.advance();
171                expr = self.spanned(Expr::ErrorProp(Box::new(expr)), line);
172            } else if self.check_exact(&TokenKind::Dot) {
173                let dot_line = self.current().line;
174                self.advance();
175                let field_tok = self.expect_kind(
176                    &TokenKind::Ident(String::new()),
177                    "Expected field name after '.'",
178                )?;
179                let field = match field_tok.kind {
180                    TokenKind::Ident(s) => s,
181                    _ => unreachable!(),
182                };
183                expr = self.spanned(Expr::Attr(Box::new(expr), field), dot_line);
184                if self.check_exact(&TokenKind::LParen) {
185                    // Detect `Type.update(base, field = val, ...)` for record update
186                    if let Some(path) = Self::dotted_name(&expr)
187                        && path.ends_with(".update")
188                    {
189                        let prefix = &path[..path.len() - ".update".len()];
190                        if !prefix.is_empty()
191                            && prefix.chars().next().is_some_and(|c| c.is_uppercase())
192                        {
193                            let update_line = self.current().line;
194                            self.advance(); // consume (
195                            let base = self.parse_expr()?;
196                            let updates = if self.check_exact(&TokenKind::Comma) {
197                                self.advance();
198                                self.skip_formatting();
199                                self.parse_record_create_fields()?
200                            } else {
201                                Vec::new()
202                            };
203                            self.expect_exact(&TokenKind::RParen)?;
204                            expr = self.spanned(
205                                Expr::RecordUpdate {
206                                    type_name: prefix.to_string(),
207                                    base: Box::new(base),
208                                    updates,
209                                },
210                                update_line,
211                            );
212                            continue;
213                        }
214                    }
215                    if let Some(path) = Self::dotted_name(&expr) {
216                        self.reject_zero_arg_constructor_call(&path)?;
217                    }
218                    let named_arg_start = matches!(&self.peek(1).kind, TokenKind::Ident(_))
219                        && self.peek(2).kind == TokenKind::Assign;
220                    if named_arg_start && let Some(path) = Self::dotted_name(&expr) {
221                        // Dotted record constructor: Tcp.Connection(id = ..., host = ...)
222                        let ctor_line = self.current().line;
223                        self.advance();
224                        let fields = self.parse_record_create_fields()?;
225                        self.expect_exact(&TokenKind::RParen)?;
226                        expr = self.spanned(
227                            Expr::RecordCreate {
228                                type_name: path,
229                                fields,
230                            },
231                            ctor_line,
232                        );
233                    } else {
234                        let call_line = self.current().line;
235                        self.advance();
236                        let args = self.parse_args()?;
237                        self.expect_exact(&TokenKind::RParen)?;
238                        expr = self.spanned(Expr::FnCall(Box::new(expr), args), call_line);
239                    }
240                }
241            } else {
242                break;
243            }
244            postfix_depth = postfix_depth.saturating_add(1);
245            if postfix_depth > super::MAX_PARSE_DEPTH {
246                return Err(self.error(format!(
247                    "Postfix chain too long (max {} steps). Refactor with named bindings; downstream walkers (typecheck, resolve, codegen) recurse into each step and exhaust the test-runner stack on longer chains.",
248                    super::MAX_PARSE_DEPTH
249                )));
250            }
251        }
252
253        Ok(expr)
254    }
255
256    pub(super) fn dotted_name(expr: &Spanned<Expr>) -> Option<String> {
257        // Iron — B4 round 4: AFL nightly on main caught a 29 KB
258        // shape with a deeply-nested Attr chain (`a.b.c.…` 3000+
259        // segments via match-arm-body construction) that overflowed
260        // the stack here because the old recursive form descended
261        // through every `Attr` node before returning. Walk
262        // iteratively instead: collect segments tail-first, then
263        // join in source order. Same observable behaviour, bounded
264        // stack regardless of chain depth.
265        let mut segments: Vec<&str> = Vec::new();
266        let mut cur = expr;
267        loop {
268            match &cur.node {
269                Expr::Ident(name) => {
270                    segments.push(name.as_str());
271                    break;
272                }
273                Expr::Attr(inner, field) => {
274                    segments.push(field.as_str());
275                    cur = inner;
276                }
277                _ => return None,
278            }
279        }
280        segments.reverse();
281        Some(segments.join("."))
282    }
283
284    pub(super) fn parse_call_or_atom(&mut self) -> Result<Spanned<Expr>, ParseError> {
285        let atom = self.parse_atom()?;
286
287        if self.check_exact(&TokenKind::LParen) {
288            if let Some(path) = Self::dotted_name(&atom) {
289                self.reject_zero_arg_constructor_call(&path)?;
290            }
291
292            // Lookahead: is this `Name(field = value, ...)` (record creation)?
293            // Detect by checking if token after `(` is `Ident` followed by `=`.
294            // Use peek_skip_formatting to handle multiline constructor syntax.
295            let is_record_create = if let Expr::Ident(ref name) = atom.node {
296                name.chars().next().is_some_and(|c| c.is_uppercase())
297                    && matches!(&self.peek_skip_formatting(1).kind, TokenKind::Ident(_))
298                    && self.peek_skip_formatting(2).kind == TokenKind::Assign
299            } else {
300                false
301            };
302            let named_arg_start = matches!(&self.peek_skip_formatting(1).kind, TokenKind::Ident(_))
303                && self.peek_skip_formatting(2).kind == TokenKind::Assign;
304
305            if is_record_create && let Expr::Ident(type_name) = atom.node {
306                let line = atom.line;
307                self.advance(); // consume (
308                let fields = self.parse_record_create_fields()?;
309                self.expect_exact(&TokenKind::RParen)?;
310                return Ok(self.spanned(Expr::RecordCreate { type_name, fields }, line));
311            }
312
313            // Dotted record constructor: Tcp.Connection(id = ..., host = ...)
314            if named_arg_start && let Some(path) = Self::dotted_name(&atom) {
315                let line = atom.line;
316                self.advance();
317                let fields = self.parse_record_create_fields()?;
318                self.expect_exact(&TokenKind::RParen)?;
319                return Ok(self.spanned(
320                    Expr::RecordCreate {
321                        type_name: path,
322                        fields,
323                    },
324                    line,
325                ));
326            }
327
328            let call_line = self.current().line;
329            self.advance();
330            let args = self.parse_args()?;
331            self.expect_exact(&TokenKind::RParen)?;
332            return Ok(self.spanned(Expr::FnCall(Box::new(atom), args), call_line));
333        }
334
335        Ok(atom)
336    }
337
338    /// Parse named-field arguments for record creation: `name = expr, name2 = expr2`
339    pub(super) fn parse_record_create_fields(
340        &mut self,
341    ) -> Result<Vec<(String, Spanned<Expr>)>, ParseError> {
342        let mut fields = Vec::new();
343        self.skip_formatting();
344
345        while !self.check_exact(&TokenKind::RParen) && !self.is_eof() {
346            if self.check_exact(&TokenKind::Comma) {
347                self.advance();
348                self.skip_formatting();
349                continue;
350            }
351            let name_tok =
352                self.expect_kind(&TokenKind::Ident(String::new()), "Expected field name")?;
353            let field_name = match name_tok.kind {
354                TokenKind::Ident(s) => s,
355                _ => unreachable!(),
356            };
357            self.expect_exact(&TokenKind::Assign)?;
358            let value = self.parse_expr()?;
359            fields.push((field_name, value));
360            self.skip_formatting();
361        }
362
363        Ok(fields)
364    }
365
366    pub(super) fn parse_args(&mut self) -> Result<Vec<Spanned<Expr>>, ParseError> {
367        let mut args = Vec::new();
368        self.skip_formatting();
369
370        while !self.check_exact(&TokenKind::RParen) && !self.is_eof() {
371            if self.check_exact(&TokenKind::Comma) {
372                self.advance();
373                self.skip_formatting();
374                continue;
375            }
376            args.push(self.parse_expr()?);
377            self.skip_formatting();
378        }
379
380        Ok(args)
381    }
382
383    pub(super) fn parse_map_literal(&mut self) -> Result<Expr, ParseError> {
384        self.expect_exact(&TokenKind::LBrace)?;
385        let mut entries = Vec::new();
386        self.skip_formatting();
387
388        while !self.check_exact(&TokenKind::RBrace) && !self.is_eof() {
389            if self.check_exact(&TokenKind::Comma) {
390                self.advance();
391                self.skip_formatting();
392                continue;
393            }
394
395            let key = self.parse_expr()?;
396            self.skip_formatting();
397            if !self.check_exact(&TokenKind::FatArrow) {
398                return Err(
399                    self.error("Expected '=>' between key and value in map literal".to_string())
400                );
401            }
402            self.advance(); // =>
403            self.skip_formatting();
404            let value = self.parse_expr()?;
405            entries.push((key, value));
406            self.skip_formatting();
407
408            if self.check_exact(&TokenKind::Comma) {
409                self.advance();
410                self.skip_formatting();
411            }
412        }
413
414        self.expect_exact(&TokenKind::RBrace)?;
415        Ok(Expr::MapLiteral(entries))
416    }
417
418    pub(super) fn parse_atom(&mut self) -> Result<Spanned<Expr>, ParseError> {
419        let line = self.current().line;
420        match self.current().kind.clone() {
421            TokenKind::Int(i) => {
422                self.advance();
423                Ok(self.spanned(Expr::Literal(Literal::Int(i)), line))
424            }
425            TokenKind::Float(f) => {
426                self.advance();
427                Ok(self.spanned(Expr::Literal(Literal::Float(f)), line))
428            }
429            TokenKind::Str(s) => {
430                self.advance();
431                Ok(self.spanned(Expr::Literal(Literal::Str(s)), line))
432            }
433            TokenKind::InterpStr(parts) => {
434                self.advance();
435                let mut str_parts = Vec::new();
436                for (is_expr, s) in parts {
437                    if is_expr {
438                        // Parse the interpolation expression; empty `{}` → empty literal.
439                        if s.trim().is_empty() {
440                            str_parts.push(StrPart::Literal(String::new()));
441                        } else {
442                            let mut lexer = crate::lexer::Lexer::new(&s);
443                            let tokens = lexer.tokenize().map_err(|e| ParseError::Error {
444                                msg: format!("Error in interpolation: {}", e),
445                                line: self.current().line,
446                                col: self.current().col,
447                            })?;
448                            let mut sub_parser = Parser::new(tokens);
449                            let expr = sub_parser.parse_expr().map_err(|e| ParseError::Error {
450                                msg: format!("Error in interpolation: {}", e),
451                                line: self.current().line,
452                                col: self.current().col,
453                            })?;
454                            str_parts.push(StrPart::Parsed(Box::new(expr)));
455                        }
456                    } else {
457                        str_parts.push(StrPart::Literal(s));
458                    }
459                }
460                Ok(self.spanned(Expr::InterpolatedStr(str_parts), line))
461            }
462            TokenKind::Bool(b) => {
463                self.advance();
464                Ok(self.spanned(Expr::Literal(Literal::Bool(b)), line))
465            }
466            TokenKind::Match => {
467                let m = self.parse_match()?;
468                Ok(self.spanned(m, line))
469            }
470            TokenKind::LParen => {
471                self.advance();
472                let first = self.parse_expr()?;
473                if self.check_exact(&TokenKind::Comma) {
474                    let mut items = vec![first];
475                    while self.check_exact(&TokenKind::Comma) {
476                        self.advance();
477                        items.push(self.parse_expr()?);
478                    }
479                    self.expect_exact(&TokenKind::RParen)?;
480                    Ok(self.spanned(Expr::Tuple(items), line))
481                } else {
482                    self.expect_exact(&TokenKind::RParen)?;
483                    Ok(first)
484                }
485            }
486            TokenKind::Ident(s) => {
487                self.advance();
488                if s == "Unit" {
489                    Ok(self.spanned(Expr::Literal(Literal::Unit), line))
490                } else {
491                    Ok(self.spanned(Expr::Ident(s), line))
492                }
493            }
494            TokenKind::LBracket => {
495                self.advance(); // consume [
496                let mut elements = Vec::new();
497                self.skip_formatting();
498                while !self.check_exact(&TokenKind::RBracket) && !self.is_eof() {
499                    if self.check_exact(&TokenKind::Comma) {
500                        self.advance();
501                        self.skip_formatting();
502                        continue;
503                    }
504                    elements.push(self.parse_expr()?);
505                    self.skip_formatting();
506                }
507                self.expect_exact(&TokenKind::RBracket)?;
508                Ok(self.spanned(Expr::List(elements), line))
509            }
510            TokenKind::LBrace => {
511                let map = self.parse_map_literal()?;
512                Ok(self.spanned(map, line))
513            }
514            TokenKind::Fn => Err(self.error(
515                "Anonymous functions are not supported in Aver. Define a top-level function and pass its name."
516                    .to_string(),
517            )),
518            _ => Err(self.error(format!(
519                "Expected expression (identifier, literal, '[', or '{{'), found {}",
520                self.current().kind
521            ))),
522        }
523    }
524}