Skip to main content

specl_syntax/
parser.rs

1//! Recursive descent parser for the Specl specification language.
2
3use crate::ast::*;
4use crate::lexer::Lexer;
5use crate::token::{Span, Token, TokenKind};
6use thiserror::Error;
7
8/// Parser error.
9#[derive(Debug, Error)]
10pub enum ParseError {
11    #[error("unexpected token at {span}: expected {expected}, found {found}")]
12    UnexpectedToken {
13        expected: String,
14        found: String,
15        span: Span,
16    },
17    #[error("unexpected end of file at {span}")]
18    UnexpectedEof { span: Span },
19    #[error("invalid syntax at {span}: {message}")]
20    InvalidSyntax { message: String, span: Span },
21}
22
23impl ParseError {
24    /// Get the source span where this error occurred.
25    pub fn span(&self) -> Span {
26        match self {
27            ParseError::UnexpectedToken { span, .. } => *span,
28            ParseError::UnexpectedEof { span } => *span,
29            ParseError::InvalidSyntax { span, .. } => *span,
30        }
31    }
32}
33
34pub type ParseResult<T> = Result<T, ParseError>;
35
36/// Parser for Specl source code.
37pub struct Parser {
38    tokens: Vec<Token>,
39    pos: usize,
40    /// Nesting depth inside brackets. When > 0, `..` is reserved for slice syntax.
41    in_bracket: usize,
42}
43
44impl Parser {
45    /// Create a new parser from source text.
46    pub fn new(source: &str) -> Self {
47        let tokens: Vec<_> = Lexer::new(source)
48            .tokenize()
49            .into_iter()
50            .filter(|t| !t.kind.is_trivia())
51            .collect();
52        Self {
53            tokens,
54            pos: 0,
55            in_bracket: 0,
56        }
57    }
58
59    /// Parse a complete module.
60    pub fn parse_module(&mut self) -> ParseResult<Module> {
61        let start = self.current_span();
62
63        self.expect(TokenKind::Module)?;
64        let name = self.parse_ident()?;
65
66        let mut decls = Vec::new();
67        while !self.is_at_end() {
68            decls.push(self.parse_decl()?);
69        }
70
71        let span = start.merge(self.prev_span());
72        Ok(Module { name, decls, span })
73    }
74
75    /// Parse a declaration.
76    fn parse_decl(&mut self) -> ParseResult<Decl> {
77        match self.peek_kind() {
78            TokenKind::Use => self.parse_use_decl().map(Decl::Use),
79            TokenKind::Const => self.parse_const_decl().map(Decl::Const),
80            TokenKind::Var => self.parse_var_decl().map(Decl::Var),
81            TokenKind::Type => self.parse_type_decl().map(Decl::Type),
82            TokenKind::Func => self.parse_func_decl().map(Decl::Func),
83            TokenKind::Init => self.parse_init_decl().map(Decl::Init),
84            TokenKind::Action => self.parse_action_decl().map(Decl::Action),
85            TokenKind::Invariant => self.parse_invariant_decl().map(Decl::Invariant),
86            TokenKind::Property => self.parse_property_decl().map(Decl::Property),
87            TokenKind::Fairness => self.parse_fairness_decl().map(Decl::Fairness),
88            _ => Err(ParseError::UnexpectedToken {
89                expected: "declaration".to_string(),
90                found: self.peek_kind().to_string(),
91                span: self.current_span(),
92            }),
93        }
94    }
95
96    fn parse_use_decl(&mut self) -> ParseResult<UseDecl> {
97        let start = self.current_span();
98        self.expect(TokenKind::Use)?;
99        let module = self.parse_ident()?;
100        let span = start.merge(self.prev_span());
101        Ok(UseDecl { module, span })
102    }
103
104    fn parse_const_decl(&mut self) -> ParseResult<ConstDecl> {
105        let start = self.current_span();
106        self.expect(TokenKind::Const)?;
107        let name = self.parse_ident()?;
108        self.expect(TokenKind::Colon)?;
109
110        // Check if this is a scalar constant (integer not followed by ..)
111        let value = if let TokenKind::Integer(n) = self.peek_kind() {
112            if self.peek_ahead_kind(1) != TokenKind::DotDot {
113                // Scalar constant: const X: 3
114                self.advance();
115                ConstValue::Scalar(n)
116            } else {
117                // Range type: const X: 0..10
118                ConstValue::Type(self.parse_type_expr()?)
119            }
120        } else {
121            // Type expression: const X: Bool, const X: Set[Int], etc.
122            ConstValue::Type(self.parse_type_expr()?)
123        };
124
125        let span = start.merge(self.prev_span());
126        Ok(ConstDecl { name, value, span })
127    }
128
129    fn parse_var_decl(&mut self) -> ParseResult<VarDecl> {
130        let start = self.current_span();
131        self.expect(TokenKind::Var)?;
132        let name = self.parse_ident()?;
133        self.expect(TokenKind::Colon)?;
134        let ty = self.parse_type_expr()?;
135        let span = start.merge(self.prev_span());
136        Ok(VarDecl { name, ty, span })
137    }
138
139    fn parse_type_decl(&mut self) -> ParseResult<TypeDecl> {
140        let start = self.current_span();
141        self.expect(TokenKind::Type)?;
142        let name = self.parse_ident()?;
143        self.expect(TokenKind::Assign)?;
144        let ty = self.parse_type_expr()?;
145        let span = start.merge(self.prev_span());
146        Ok(TypeDecl { name, ty, span })
147    }
148
149    /// Parse `func Name(param1, param2) { expr }`
150    fn parse_func_decl(&mut self) -> ParseResult<FuncDecl> {
151        let start = self.current_span();
152        self.expect(TokenKind::Func)?;
153        let name = self.parse_ident()?;
154        self.expect(TokenKind::LParen)?;
155        let params = self.parse_func_params()?;
156        self.expect(TokenKind::RParen)?;
157        self.expect(TokenKind::LBrace)?;
158        let body = self.parse_expr()?;
159        self.expect(TokenKind::RBrace)?;
160        let span = start.merge(self.prev_span());
161        Ok(FuncDecl {
162            name,
163            params,
164            body,
165            span,
166        })
167    }
168
169    /// Parse function parameters (just names, no types).
170    fn parse_func_params(&mut self) -> ParseResult<Vec<FuncParam>> {
171        let mut params = Vec::new();
172        if self.peek_kind() == TokenKind::RParen {
173            return Ok(params);
174        }
175        loop {
176            let start = self.current_span();
177            let name = self.parse_ident()?;
178            let span = start.merge(self.prev_span());
179            params.push(FuncParam { name, span });
180            if self.peek_kind() == TokenKind::Comma {
181                self.advance();
182            } else {
183                break;
184            }
185        }
186        Ok(params)
187    }
188
189    fn parse_init_decl(&mut self) -> ParseResult<InitDecl> {
190        let start = self.current_span();
191        self.expect(TokenKind::Init)?;
192        self.expect(TokenKind::LBrace)?;
193        let body = self.parse_expr()?;
194        self.expect(TokenKind::RBrace)?;
195        let span = start.merge(self.prev_span());
196        Ok(InitDecl { body, span })
197    }
198
199    fn parse_action_decl(&mut self) -> ParseResult<ActionDecl> {
200        let start = self.current_span();
201        self.expect(TokenKind::Action)?;
202        let name = self.parse_ident()?;
203        self.expect(TokenKind::LParen)?;
204        let params = self.parse_params()?;
205        self.expect(TokenKind::RParen)?;
206        self.expect(TokenKind::LBrace)?;
207        let body = self.parse_action_body()?;
208        self.expect(TokenKind::RBrace)?;
209        let span = start.merge(self.prev_span());
210        Ok(ActionDecl {
211            name,
212            params,
213            body,
214            span,
215        })
216    }
217
218    fn parse_params(&mut self) -> ParseResult<Vec<Param>> {
219        let mut params = Vec::new();
220        if !self.check(TokenKind::RParen) {
221            loop {
222                params.push(self.parse_param()?);
223                if !self.match_token(TokenKind::Comma) {
224                    break;
225                }
226            }
227        }
228        Ok(params)
229    }
230
231    fn parse_param(&mut self) -> ParseResult<Param> {
232        let start = self.current_span();
233        let name = self.parse_ident()?;
234        self.expect(TokenKind::Colon)?;
235        let ty = self.parse_type_expr()?;
236        let span = start.merge(self.prev_span());
237        Ok(Param { name, ty, span })
238    }
239
240    fn parse_action_body(&mut self) -> ParseResult<ActionBody> {
241        let start = self.current_span();
242        let mut requires = Vec::new();
243
244        // Parse require statements
245        while self.check(TokenKind::Require) {
246            self.advance();
247            let expr = self.parse_expr()?;
248            requires.push(expr);
249        }
250
251        // Parse effect (optional, the rest of the expression)
252        let effect = if !self.check(TokenKind::RBrace) {
253            Some(self.parse_expr()?)
254        } else {
255            None
256        };
257
258        let span = start.merge(self.prev_span());
259        Ok(ActionBody {
260            requires,
261            effect,
262            span,
263        })
264    }
265
266    fn parse_invariant_decl(&mut self) -> ParseResult<InvariantDecl> {
267        let start = self.current_span();
268        self.expect(TokenKind::Invariant)?;
269        let name = self.parse_ident()?;
270        self.expect(TokenKind::LBrace)?;
271        let body = self.parse_expr()?;
272        self.expect(TokenKind::RBrace)?;
273        let span = start.merge(self.prev_span());
274        Ok(InvariantDecl { name, body, span })
275    }
276
277    fn parse_property_decl(&mut self) -> ParseResult<PropertyDecl> {
278        let start = self.current_span();
279        self.expect(TokenKind::Property)?;
280        let name = self.parse_ident()?;
281        self.expect(TokenKind::LBrace)?;
282        let body = self.parse_expr()?;
283        self.expect(TokenKind::RBrace)?;
284        let span = start.merge(self.prev_span());
285        Ok(PropertyDecl { name, body, span })
286    }
287
288    fn parse_fairness_decl(&mut self) -> ParseResult<FairnessDecl> {
289        let start = self.current_span();
290        self.expect(TokenKind::Fairness)?;
291        self.expect(TokenKind::LBrace)?;
292
293        let mut constraints = Vec::new();
294        while !self.check(TokenKind::RBrace) {
295            constraints.push(self.parse_fairness_constraint()?);
296        }
297
298        self.expect(TokenKind::RBrace)?;
299        let span = start.merge(self.prev_span());
300        Ok(FairnessDecl { constraints, span })
301    }
302
303    fn parse_fairness_constraint(&mut self) -> ParseResult<FairnessConstraint> {
304        let start = self.current_span();
305        let kind = if self.match_token(TokenKind::WeakFair) {
306            FairnessKind::Weak
307        } else if self.match_token(TokenKind::StrongFair) {
308            FairnessKind::Strong
309        } else {
310            return Err(ParseError::UnexpectedToken {
311                expected: "weak_fair or strong_fair".to_string(),
312                found: self.peek_kind().to_string(),
313                span: self.current_span(),
314            });
315        };
316        self.expect(TokenKind::LParen)?;
317        let action = self.parse_ident()?;
318        self.expect(TokenKind::RParen)?;
319        let span = start.merge(self.prev_span());
320        Ok(FairnessConstraint { kind, action, span })
321    }
322
323    // === Type expression parsing ===
324
325    fn parse_type_expr(&mut self) -> ParseResult<TypeExpr> {
326        let start = self.current_span();
327
328        // Check for built-in parameterized types
329        match self.peek_kind() {
330            TokenKind::Set => {
331                self.advance();
332                self.expect(TokenKind::LBracket)?;
333                let inner = self.parse_type_expr()?;
334                self.expect(TokenKind::RBracket)?;
335                let span = start.merge(self.prev_span());
336                Ok(TypeExpr::Set(Box::new(inner), span))
337            }
338            TokenKind::Seq => {
339                self.advance();
340                self.expect(TokenKind::LBracket)?;
341                let inner = self.parse_type_expr()?;
342                self.expect(TokenKind::RBracket)?;
343                let span = start.merge(self.prev_span());
344                Ok(TypeExpr::Seq(Box::new(inner), span))
345            }
346            TokenKind::Dict => {
347                self.advance();
348                self.expect(TokenKind::LBracket)?;
349                let key = self.parse_type_expr()?;
350                self.expect(TokenKind::Comma)?;
351                let value = self.parse_type_expr()?;
352                self.expect(TokenKind::RBracket)?;
353                let span = start.merge(self.prev_span());
354                Ok(TypeExpr::Dict(Box::new(key), Box::new(value), span))
355            }
356            TokenKind::Option_ => {
357                self.advance();
358                self.expect(TokenKind::LBracket)?;
359                let inner = self.parse_type_expr()?;
360                self.expect(TokenKind::RBracket)?;
361                let span = start.merge(self.prev_span());
362                Ok(TypeExpr::Option(Box::new(inner), span))
363            }
364            TokenKind::Integer(_) => {
365                // Range type like 0..10
366                let lo = self.parse_primary_expr()?;
367                self.expect(TokenKind::DotDot)?;
368                let hi = self.parse_primary_expr()?;
369                let span = start.merge(self.prev_span());
370                Ok(TypeExpr::Range(Box::new(lo), Box::new(hi), span))
371            }
372            TokenKind::LParen => {
373                // Tuple type like (T1, T2, ...)
374                self.advance();
375                let first = self.parse_type_expr()?;
376                if self.match_token(TokenKind::Comma) {
377                    // It's a tuple type
378                    let mut elements = vec![first];
379                    loop {
380                        elements.push(self.parse_type_expr()?);
381                        if !self.match_token(TokenKind::Comma) {
382                            break;
383                        }
384                    }
385                    self.expect(TokenKind::RParen)?;
386                    let span = start.merge(self.prev_span());
387                    Ok(TypeExpr::Tuple(elements, span))
388                } else {
389                    // Just a parenthesized type - unwrap it
390                    self.expect(TokenKind::RParen)?;
391                    Ok(first)
392                }
393            }
394            _ => {
395                // Named type or identifier that could start a range
396                let name = self.parse_ident()?;
397
398                // Check for range
399                if self.check(TokenKind::DotDot) {
400                    self.advance();
401                    let hi = self.parse_primary_expr()?;
402                    let span = start.merge(self.prev_span());
403                    let lo = Expr::new(ExprKind::Ident(name.name), name.span);
404                    Ok(TypeExpr::Range(Box::new(lo), Box::new(hi), span))
405                } else {
406                    Ok(TypeExpr::Named(name))
407                }
408            }
409        }
410    }
411
412    // === Expression parsing with precedence climbing ===
413
414    fn parse_expr(&mut self) -> ParseResult<Expr> {
415        self.parse_expr_impl(0, true)
416    }
417
418    /// Parse an expression but don't treat `in` as a binary operator.
419    /// Used in contexts where `in` is a keyword separator (let, fn, comprehensions).
420    fn parse_expr_no_in(&mut self) -> ParseResult<Expr> {
421        self.parse_expr_impl(0, false)
422    }
423
424    fn parse_expr_impl(&mut self, min_prec: u8, allow_in_op: bool) -> ParseResult<Expr> {
425        let mut left = self.parse_unary_expr()?;
426
427        // Handle assignment specially: `x = expr` becomes `x' == expr`
428        if self.check(TokenKind::Assign) {
429            let assign_span = self.current_span();
430            self.advance(); // consume =
431
432            // Left side must be an identifier
433            let primed_left = match &left.kind {
434                ExprKind::Ident(name) => {
435                    let span = left.span.merge(assign_span);
436                    Expr::new(ExprKind::Primed(name.clone()), span)
437                }
438                _ => {
439                    return Err(ParseError::InvalidSyntax {
440                        message: "left side of assignment must be a variable".to_string(),
441                        span: left.span,
442                    });
443                }
444            };
445
446            // Parse RHS at precedence 5 (same as ==) so `and` stops it
447            // This makes `x = 1 and y = 2` parse as `(x = 1) and (y = 2)`
448            let right = self.parse_expr_impl(5, allow_in_op)?;
449            let span = left.span.merge(right.span);
450            left = Expr::new(
451                ExprKind::Binary {
452                    op: BinOp::Eq,
453                    left: Box::new(primed_left),
454                    right: Box::new(right),
455                },
456                span,
457            );
458        }
459
460        while let Some(op) = self.peek_binop(allow_in_op) {
461            let prec = op.precedence();
462            if prec < min_prec {
463                break;
464            }
465
466            self.advance(); // consume operator
467
468            // Handle right-associativity
469            let next_prec = if op.is_right_assoc() { prec } else { prec + 1 };
470            let right = self.parse_expr_impl(next_prec, allow_in_op)?;
471
472            let span = left.span.merge(right.span);
473            left = Expr::new(
474                ExprKind::Binary {
475                    op,
476                    left: Box::new(left),
477                    right: Box::new(right),
478                },
479                span,
480            );
481        }
482
483        // Handle leads_to specially (infix keyword)
484        if self.check(TokenKind::LeadsTo) {
485            self.advance();
486            let right = self.parse_expr_impl(1, allow_in_op)?;
487            let span = left.span.merge(right.span);
488            left = Expr::new(
489                ExprKind::LeadsTo {
490                    left: Box::new(left),
491                    right: Box::new(right),
492                },
493                span,
494            );
495        }
496
497        Ok(left)
498    }
499
500    fn peek_binop(&self, allow_in_op: bool) -> Option<BinOp> {
501        match self.peek_kind() {
502            TokenKind::And => Some(BinOp::And),
503            TokenKind::Or => Some(BinOp::Or),
504            TokenKind::Implies => Some(BinOp::Implies),
505            TokenKind::Iff => Some(BinOp::Iff),
506            TokenKind::Eq => Some(BinOp::Eq),
507            TokenKind::Ne => Some(BinOp::Ne),
508            TokenKind::Lt => Some(BinOp::Lt),
509            TokenKind::Le => Some(BinOp::Le),
510            TokenKind::Gt => Some(BinOp::Gt),
511            TokenKind::Ge => Some(BinOp::Ge),
512            TokenKind::Plus => Some(BinOp::Add),
513            TokenKind::Minus => Some(BinOp::Sub),
514            TokenKind::Star => Some(BinOp::Mul),
515            TokenKind::Slash => Some(BinOp::Div),
516            TokenKind::Percent => Some(BinOp::Mod),
517            TokenKind::In if allow_in_op => Some(BinOp::In),
518            TokenKind::Union => Some(BinOp::Union),
519            TokenKind::Intersect => Some(BinOp::Intersect),
520            TokenKind::Diff => Some(BinOp::Diff),
521            TokenKind::SubsetOf => Some(BinOp::SubsetOf),
522            TokenKind::PlusPlus => Some(BinOp::Concat),
523            TokenKind::Ampersand => Some(BinOp::Intersect),
524            TokenKind::Pipe => Some(BinOp::Union),
525            _ => None,
526        }
527    }
528
529    fn parse_unary_expr(&mut self) -> ParseResult<Expr> {
530        self.parse_unary_expr_with_in(true)
531    }
532
533    fn parse_unary_expr_with_in(&mut self, allow_in_op: bool) -> ParseResult<Expr> {
534        let start = self.current_span();
535
536        if self.match_token(TokenKind::Not) {
537            // Parse operand at precedence 5 (comparison level) so `not m in S` parses as `not (m in S)`
538            // but `not A and B` parses as `(not A) and B` since `and` has prec 4
539            let operand = self.parse_expr_impl(5, allow_in_op)?;
540            let span = start.merge(operand.span);
541            return Ok(Expr::new(
542                ExprKind::Unary {
543                    op: UnaryOp::Not,
544                    operand: Box::new(operand),
545                },
546                span,
547            ));
548        }
549
550        if self.match_token(TokenKind::Minus) {
551            // Negation binds tightly - just parse the next unary/postfix
552            let operand = self.parse_unary_expr_with_in(allow_in_op)?;
553            let span = start.merge(operand.span);
554            return Ok(Expr::new(
555                ExprKind::Unary {
556                    op: UnaryOp::Neg,
557                    operand: Box::new(operand),
558                },
559                span,
560            ));
561        }
562
563        // Temporal prefix operators
564        if self.match_token(TokenKind::Always) {
565            let operand = self.parse_unary_expr_with_in(allow_in_op)?;
566            let span = start.merge(operand.span);
567            return Ok(Expr::new(ExprKind::Always(Box::new(operand)), span));
568        }
569
570        if self.match_token(TokenKind::Eventually) {
571            let operand = self.parse_unary_expr_with_in(allow_in_op)?;
572            let span = start.merge(operand.span);
573            return Ok(Expr::new(ExprKind::Eventually(Box::new(operand)), span));
574        }
575
576        self.parse_postfix_expr()
577    }
578
579    fn parse_postfix_expr(&mut self) -> ParseResult<Expr> {
580        let mut expr = self.parse_primary_expr()?;
581
582        loop {
583            if self.match_token(TokenKind::LBracket) {
584                // Index or slice: suppress range parsing so `..` is available for slice
585                self.in_bracket += 1;
586                let index = self.parse_expr()?;
587                self.in_bracket -= 1;
588                if self.match_token(TokenKind::DotDot) {
589                    let hi = self.parse_expr()?;
590                    self.expect(TokenKind::RBracket)?;
591                    let span = expr.span.merge(self.prev_span());
592                    expr = Expr::new(
593                        ExprKind::Slice {
594                            base: Box::new(expr),
595                            lo: Box::new(index),
596                            hi: Box::new(hi),
597                        },
598                        span,
599                    );
600                } else {
601                    self.expect(TokenKind::RBracket)?;
602                    let span = expr.span.merge(self.prev_span());
603                    expr = Expr::new(
604                        ExprKind::Index {
605                            base: Box::new(expr),
606                            index: Box::new(index),
607                        },
608                        span,
609                    );
610                }
611            } else if self.match_token(TokenKind::Dot) {
612                let field = self.parse_ident()?;
613                let span = expr.span.merge(field.span);
614                expr = Expr::new(
615                    ExprKind::Field {
616                        base: Box::new(expr),
617                        field,
618                    },
619                    span,
620                );
621            } else if self.match_token(TokenKind::LParen) {
622                // Function call
623                let args = self.parse_call_args()?;
624                self.expect(TokenKind::RParen)?;
625                let span = expr.span.merge(self.prev_span());
626                expr = Expr::new(
627                    ExprKind::Call {
628                        func: Box::new(expr),
629                        args,
630                    },
631                    span,
632                );
633            } else if self.match_token(TokenKind::Prime) {
634                // Prime notation is deprecated - use assignment syntax instead
635                return Err(ParseError::InvalidSyntax {
636                    message: "prime notation (x') is deprecated, use assignment syntax: x = value"
637                        .to_string(),
638                    span: self.prev_span(),
639                });
640            } else if self.in_bracket == 0 && self.match_token(TokenKind::DotDot) {
641                // Range expression (e.g., 0..10 or 1..m.field)
642                // Suppressed inside brackets where `..` means slice.
643                let hi = self.parse_postfix_expr()?;
644                let span = expr.span.merge(hi.span);
645                expr = Expr::new(
646                    ExprKind::Range {
647                        lo: Box::new(expr),
648                        hi: Box::new(hi),
649                    },
650                    span,
651                );
652                // Range is terminal - don't allow chaining
653                break;
654            } else if self.match_token(TokenKind::With) {
655                // Record/dict update: expr with { field: value, [key]: value, ... }
656                self.expect(TokenKind::LBrace)?;
657                let updates = self.parse_record_updates()?;
658                self.expect(TokenKind::RBrace)?;
659                let span = expr.span.merge(self.prev_span());
660                expr = Expr::new(
661                    ExprKind::RecordUpdate {
662                        base: Box::new(expr),
663                        updates,
664                    },
665                    span,
666                );
667            } else {
668                break;
669            }
670        }
671
672        Ok(expr)
673    }
674
675    fn parse_call_args(&mut self) -> ParseResult<Vec<Expr>> {
676        let mut args = Vec::new();
677        if !self.check(TokenKind::RParen) {
678            loop {
679                args.push(self.parse_expr()?);
680                if !self.match_token(TokenKind::Comma) {
681                    break;
682                }
683            }
684        }
685        Ok(args)
686    }
687
688    fn parse_primary_expr(&mut self) -> ParseResult<Expr> {
689        let start = self.current_span();
690
691        match self.peek_kind() {
692            TokenKind::True => {
693                self.advance();
694                Ok(Expr::new(ExprKind::Bool(true), start))
695            }
696            TokenKind::False => {
697                self.advance();
698                Ok(Expr::new(ExprKind::Bool(false), start))
699            }
700            TokenKind::Integer(n) => {
701                self.advance();
702                Ok(Expr::new(ExprKind::Int(n), start))
703            }
704            TokenKind::StringLit(s) => {
705                let s = s.clone();
706                self.advance();
707                Ok(Expr::new(ExprKind::String(s), start))
708            }
709            TokenKind::Ident(name) => {
710                let name = name.clone();
711                self.advance();
712                // Check for range expression (suppressed inside brackets for slice)
713                if self.in_bracket == 0 && self.check(TokenKind::DotDot) {
714                    self.advance();
715                    // Use parse_postfix_expr to handle field access on the right side (e.g., 1..m.field)
716                    let hi = self.parse_postfix_expr()?;
717                    let span = start.merge(hi.span);
718                    let lo = Expr::new(ExprKind::Ident(name), start);
719                    return Ok(Expr::new(
720                        ExprKind::Range {
721                            lo: Box::new(lo),
722                            hi: Box::new(hi),
723                        },
724                        span,
725                    ));
726                }
727                Ok(Expr::new(ExprKind::Ident(name), start))
728            }
729            TokenKind::LParen => {
730                self.advance();
731                let first = self.parse_expr()?;
732                if self.match_token(TokenKind::Comma) {
733                    // It's a tuple literal
734                    let mut elements = vec![first];
735                    loop {
736                        elements.push(self.parse_expr()?);
737                        if !self.match_token(TokenKind::Comma) {
738                            break;
739                        }
740                    }
741                    self.expect(TokenKind::RParen)?;
742                    let span = start.merge(self.prev_span());
743                    Ok(Expr::new(ExprKind::TupleLit(elements), span))
744                } else {
745                    // Just a parenthesized expression
746                    self.expect(TokenKind::RParen)?;
747                    let span = start.merge(self.prev_span());
748                    Ok(Expr::new(ExprKind::Paren(Box::new(first)), span))
749                }
750            }
751            TokenKind::LBrace => self.parse_set_or_record_lit(),
752            TokenKind::LBracket => self.parse_seq_lit(),
753            TokenKind::All => self.parse_quantifier(QuantifierKind::Forall),
754            TokenKind::Any => self.parse_quantifier(QuantifierKind::Exists),
755            TokenKind::Choose => self.parse_choose(),
756            TokenKind::Let => self.parse_let(),
757            TokenKind::If => self.parse_if(),
758            TokenKind::Require => {
759                self.advance();
760                let expr = self.parse_expr()?;
761                let span = start.merge(expr.span);
762                Ok(Expr::new(ExprKind::Require(Box::new(expr)), span))
763            }
764            TokenKind::Changes => {
765                self.advance();
766                self.expect(TokenKind::LParen)?;
767                let var = self.parse_ident()?;
768                self.expect(TokenKind::RParen)?;
769                let span = start.merge(self.prev_span());
770                Ok(Expr::new(ExprKind::Changes(var), span))
771            }
772            TokenKind::Enabled => {
773                self.advance();
774                self.expect(TokenKind::LParen)?;
775                let action = self.parse_ident()?;
776                self.expect(TokenKind::RParen)?;
777                let span = start.merge(self.prev_span());
778                Ok(Expr::new(ExprKind::Enabled(action), span))
779            }
780            TokenKind::Head => {
781                self.advance();
782                self.expect(TokenKind::LParen)?;
783                let seq = self.parse_expr()?;
784                self.expect(TokenKind::RParen)?;
785                let span = start.merge(self.prev_span());
786                Ok(Expr::new(ExprKind::SeqHead(Box::new(seq)), span))
787            }
788            TokenKind::Tail => {
789                self.advance();
790                self.expect(TokenKind::LParen)?;
791                let seq = self.parse_expr()?;
792                self.expect(TokenKind::RParen)?;
793                let span = start.merge(self.prev_span());
794                Ok(Expr::new(ExprKind::SeqTail(Box::new(seq)), span))
795            }
796            TokenKind::Len => {
797                self.advance();
798                self.expect(TokenKind::LParen)?;
799                let expr = self.parse_expr()?;
800                self.expect(TokenKind::RParen)?;
801                let span = start.merge(self.prev_span());
802                Ok(Expr::new(ExprKind::Len(Box::new(expr)), span))
803            }
804            TokenKind::Keys => {
805                self.advance();
806                self.expect(TokenKind::LParen)?;
807                let expr = self.parse_expr()?;
808                self.expect(TokenKind::RParen)?;
809                let span = start.merge(self.prev_span());
810                Ok(Expr::new(ExprKind::Keys(Box::new(expr)), span))
811            }
812            TokenKind::Values => {
813                self.advance();
814                self.expect(TokenKind::LParen)?;
815                let expr = self.parse_expr()?;
816                self.expect(TokenKind::RParen)?;
817                let span = start.merge(self.prev_span());
818                Ok(Expr::new(ExprKind::Values(Box::new(expr)), span))
819            }
820            // Built-in type names can be used as identifiers for constructor calls
821            TokenKind::Some_ => {
822                self.advance();
823                Ok(Expr::new(ExprKind::Ident("Some".to_string()), start))
824            }
825            TokenKind::None_ => {
826                self.advance();
827                Ok(Expr::new(ExprKind::Ident("None".to_string()), start))
828            }
829            TokenKind::Powerset => {
830                self.advance();
831                self.expect(TokenKind::LParen)?;
832                let expr = self.parse_expr()?;
833                self.expect(TokenKind::RParen)?;
834                let span = start.merge(self.prev_span());
835                Ok(Expr::new(ExprKind::Powerset(Box::new(expr)), span))
836            }
837            TokenKind::UnionAll => {
838                self.advance();
839                self.expect(TokenKind::LParen)?;
840                let expr = self.parse_expr()?;
841                self.expect(TokenKind::RParen)?;
842                let span = start.merge(self.prev_span());
843                Ok(Expr::new(ExprKind::BigUnion(Box::new(expr)), span))
844            }
845            _ => Err(ParseError::UnexpectedToken {
846                expected: "expression".to_string(),
847                found: self.peek_kind().to_string(),
848                span: start,
849            }),
850        }
851    }
852
853    fn parse_set_or_record_lit(&mut self) -> ParseResult<Expr> {
854        let start = self.current_span();
855        self.expect(TokenKind::LBrace)?;
856
857        // Empty set
858        if self.check(TokenKind::RBrace) {
859            self.advance();
860            let span = start.merge(self.prev_span());
861            return Ok(Expr::new(ExprKind::SetLit(vec![]), span));
862        }
863
864        // Peek ahead to determine the form:
865        // - Record: { field: value, ... }
866        // - Set filter: { x in S if P } - starts with ident followed by `in`
867        // - Set map: { expr for x in S }
868        // - Set literal: { elem, ... }
869
870        // Check for set filter form: { ident in ... if ... }
871        // We need to detect this before parsing to avoid `in` being consumed as binop
872        if self.is_set_filter_comprehension() {
873            return self.parse_set_filter_comprehension(start);
874        }
875
876        // Parse first element (allow `in` as binop for cases like `{ a in S, b in T }`)
877        let first = self.parse_expr()?;
878
879        if self.check(TokenKind::Colon) {
880            // Could be:
881            // - Dict literal { key: value, ... }
882            // - Dict comprehension { k: v for x in S }
883            self.advance(); // consume :
884            let value = self.parse_expr()?;
885
886            // Check for dict comprehension: { k: v for x in S }
887            if self.check(TokenKind::For) {
888                self.advance(); // consume 'for'
889                let var = self.parse_ident()?;
890                self.expect(TokenKind::In)?;
891                let domain = self.parse_expr()?;
892                self.expect(TokenKind::RBrace)?;
893                let span = start.merge(self.prev_span());
894
895                // Verify that the key matches the variable (for dict comprehension)
896                let key_name = match &first.kind {
897                    ExprKind::Ident(s) => s.clone(),
898                    _ => {
899                        return Err(ParseError::InvalidSyntax {
900                            message: "dict comprehension key must be an identifier".to_string(),
901                            span: first.span,
902                        })
903                    }
904                };
905                if key_name != var.name {
906                    return Err(ParseError::InvalidSyntax {
907                        message: format!(
908                            "dict comprehension key '{}' must match iteration variable '{}'",
909                            key_name, var.name
910                        ),
911                        span: first.span,
912                    });
913                }
914
915                // Convert to FnLit: { k: v for k in S } => fn(k in S) => v
916                return Ok(Expr::new(
917                    ExprKind::FnLit {
918                        var,
919                        domain: Box::new(domain),
920                        body: Box::new(value),
921                    },
922                    span,
923                ));
924            }
925
926            // Dict literal { key: value, ... } - keys are expressions
927            let mut entries = vec![(first, value)];
928
929            while self.match_token(TokenKind::Comma) {
930                if self.check(TokenKind::RBrace) {
931                    break; // trailing comma
932                }
933                let key = self.parse_expr()?;
934                self.expect(TokenKind::Colon)?;
935                let val = self.parse_expr()?;
936                entries.push((key, val));
937            }
938
939            self.expect(TokenKind::RBrace)?;
940            let span = start.merge(self.prev_span());
941            Ok(Expr::new(ExprKind::DictLit(entries), span))
942        } else if self.check(TokenKind::For) {
943            // Set comprehension { expr for x in S } or { expr for x in S if P }
944            self.advance(); // consume 'for'
945            let var = self.parse_ident()?;
946            self.expect(TokenKind::In)?;
947            let domain = self.parse_expr()?;
948            let filter = if self.match_token(TokenKind::If) {
949                Some(Box::new(self.parse_expr()?))
950            } else {
951                None
952            };
953            self.expect(TokenKind::RBrace)?;
954            let span = start.merge(self.prev_span());
955            Ok(Expr::new(
956                ExprKind::SetComprehension {
957                    element: Box::new(first),
958                    var,
959                    domain: Box::new(domain),
960                    filter,
961                },
962                span,
963            ))
964        } else {
965            // Regular set literal { a, b, c }
966            let mut elements = vec![first];
967            while self.match_token(TokenKind::Comma) {
968                if self.check(TokenKind::RBrace) {
969                    break; // trailing comma
970                }
971                elements.push(self.parse_expr()?);
972            }
973            self.expect(TokenKind::RBrace)?;
974            let span = start.merge(self.prev_span());
975            Ok(Expr::new(ExprKind::SetLit(elements), span))
976        }
977    }
978
979    /// Check if we're looking at a set filter comprehension: { ident in ... if ... }
980    fn is_set_filter_comprehension(&self) -> bool {
981        // Check pattern: identifier followed by `in`
982        matches!(self.peek_kind(), TokenKind::Ident(_)) && self.peek_ahead_kind(1) == TokenKind::In
983    }
984
985    /// Parse set filter comprehension: { x in S if P }
986    fn parse_set_filter_comprehension(&mut self, start: Span) -> ParseResult<Expr> {
987        let var = self.parse_ident()?;
988        self.expect(TokenKind::In)?;
989        // Parse domain - use regular parse_expr since `in` after domain would be weird
990        // and `if` is not a binop so it will stop there
991        let domain = self.parse_expr()?;
992        self.expect(TokenKind::If)?;
993        let predicate = self.parse_expr()?;
994        self.expect(TokenKind::RBrace)?;
995        let span = start.merge(self.prev_span());
996        // element is just the variable
997        let element = Expr::new(ExprKind::Ident(var.name.clone()), var.span);
998        Ok(Expr::new(
999            ExprKind::SetComprehension {
1000                element: Box::new(element),
1001                var,
1002                domain: Box::new(domain),
1003                filter: Some(Box::new(predicate)),
1004            },
1005            span,
1006        ))
1007    }
1008
1009    fn parse_seq_lit(&mut self) -> ParseResult<Expr> {
1010        let start = self.current_span();
1011        self.expect(TokenKind::LBracket)?;
1012
1013        let mut elements = Vec::new();
1014        if !self.check(TokenKind::RBracket) {
1015            loop {
1016                elements.push(self.parse_expr()?);
1017                if !self.match_token(TokenKind::Comma) {
1018                    break;
1019                }
1020                if self.check(TokenKind::RBracket) {
1021                    break; // trailing comma
1022                }
1023            }
1024        }
1025
1026        self.expect(TokenKind::RBracket)?;
1027        let span = start.merge(self.prev_span());
1028        Ok(Expr::new(ExprKind::SeqLit(elements), span))
1029    }
1030
1031    fn parse_quantifier(&mut self, kind: QuantifierKind) -> ParseResult<Expr> {
1032        let start = self.current_span();
1033        self.advance(); // consume forall/exists
1034
1035        let mut bindings = Vec::new();
1036        loop {
1037            let var = self.parse_ident()?;
1038            self.expect(TokenKind::In)?;
1039            // Don't allow `in` as binary op in domain to avoid ambiguity with outer `let...in`
1040            let domain = self.parse_expr_no_in()?;
1041            let span = var.span.merge(domain.span);
1042            bindings.push(Binding { var, domain, span });
1043            if !self.match_token(TokenKind::Comma) {
1044                break;
1045            }
1046        }
1047
1048        self.expect(TokenKind::Colon)?;
1049        // Don't allow `in` as binary op to avoid ambiguity with outer `let...in`
1050        // Use parentheses for membership tests: `all x in S: (y in T)`
1051        let body = self.parse_expr_no_in()?;
1052        let span = start.merge(body.span);
1053        Ok(Expr::new(
1054            ExprKind::Quantifier {
1055                kind,
1056                bindings,
1057                body: Box::new(body),
1058            },
1059            span,
1060        ))
1061    }
1062
1063    fn parse_choose(&mut self) -> ParseResult<Expr> {
1064        let start = self.current_span();
1065        self.expect(TokenKind::Choose)?;
1066        let var = self.parse_ident()?;
1067        self.expect(TokenKind::In)?;
1068        // Don't allow `in` as binary op in domain to avoid ambiguity with outer `let...in`
1069        let domain = self.parse_expr_no_in()?;
1070        self.expect(TokenKind::Colon)?;
1071        // Don't allow `in` as binary op to avoid ambiguity with outer `let...in`
1072        let predicate = self.parse_expr_no_in()?;
1073        let span = start.merge(predicate.span);
1074        Ok(Expr::new(
1075            ExprKind::Choose {
1076                var,
1077                domain: Box::new(domain),
1078                predicate: Box::new(predicate),
1079            },
1080            span,
1081        ))
1082    }
1083
1084    fn parse_let(&mut self) -> ParseResult<Expr> {
1085        let start = self.current_span();
1086        self.expect(TokenKind::Let)?;
1087        let var = self.parse_ident()?;
1088        self.expect(TokenKind::Assign)?;
1089        // Use parse_expr_no_in so `in` is not consumed as binary operator
1090        let value = self.parse_expr_no_in()?;
1091        self.expect(TokenKind::In)?;
1092        let body = self.parse_expr()?;
1093        let span = start.merge(body.span);
1094        Ok(Expr::new(
1095            ExprKind::Let {
1096                var,
1097                value: Box::new(value),
1098                body: Box::new(body),
1099            },
1100            span,
1101        ))
1102    }
1103
1104    fn parse_if(&mut self) -> ParseResult<Expr> {
1105        let start = self.current_span();
1106        self.expect(TokenKind::If)?;
1107        let cond = self.parse_expr()?;
1108        self.expect(TokenKind::Then)?;
1109        let then_branch = self.parse_expr()?;
1110        self.expect(TokenKind::Else)?;
1111        let else_branch = self.parse_expr()?;
1112        let span = start.merge(else_branch.span);
1113        Ok(Expr::new(
1114            ExprKind::If {
1115                cond: Box::new(cond),
1116                then_branch: Box::new(then_branch),
1117                else_branch: Box::new(else_branch),
1118            },
1119            span,
1120        ))
1121    }
1122
1123    /// Parse record update fields: { field: value, [key]: value, ... }
1124    fn parse_record_updates(&mut self) -> ParseResult<Vec<RecordFieldUpdate>> {
1125        let mut updates = Vec::new();
1126
1127        if self.check(TokenKind::RBrace) {
1128            return Ok(updates);
1129        }
1130
1131        loop {
1132            let update = if self.match_token(TokenKind::LBracket) {
1133                // Dynamic key: [key]: value
1134                let key = self.parse_expr()?;
1135                self.expect(TokenKind::RBracket)?;
1136                self.expect(TokenKind::Colon)?;
1137                let value = self.parse_expr()?;
1138                RecordFieldUpdate::Dynamic { key, value }
1139            } else {
1140                // Static field: field: value
1141                let name = self.parse_ident()?;
1142                self.expect(TokenKind::Colon)?;
1143                let value = self.parse_expr()?;
1144                RecordFieldUpdate::Field { name, value }
1145            };
1146            updates.push(update);
1147
1148            if !self.match_token(TokenKind::Comma) {
1149                break;
1150            }
1151            if self.check(TokenKind::RBrace) {
1152                break; // trailing comma
1153            }
1154        }
1155
1156        Ok(updates)
1157    }
1158
1159    // === Helper methods ===
1160
1161    fn parse_ident(&mut self) -> ParseResult<Ident> {
1162        let span = self.current_span();
1163        match self.peek_kind() {
1164            TokenKind::Ident(name) => {
1165                let name = name.clone();
1166                self.advance();
1167                Ok(Ident::new(name, span))
1168            }
1169            // Allow built-in type names as identifiers in some contexts
1170            TokenKind::Nat => {
1171                self.advance();
1172                Ok(Ident::new("Nat", span))
1173            }
1174            TokenKind::Int => {
1175                self.advance();
1176                Ok(Ident::new("Int", span))
1177            }
1178            TokenKind::Bool => {
1179                self.advance();
1180                Ok(Ident::new("Bool", span))
1181            }
1182            TokenKind::String_ => {
1183                self.advance();
1184                Ok(Ident::new("String", span))
1185            }
1186            _ => Err(ParseError::UnexpectedToken {
1187                expected: "identifier".to_string(),
1188                found: self.peek_kind().to_string(),
1189                span,
1190            }),
1191        }
1192    }
1193
1194    fn peek(&self) -> &Token {
1195        self.tokens
1196            .get(self.pos)
1197            .unwrap_or_else(|| self.tokens.last().expect("tokens should have at least EOF"))
1198    }
1199
1200    fn peek_kind(&self) -> TokenKind {
1201        self.peek().kind.clone()
1202    }
1203
1204    /// Peek ahead by `offset` tokens (0 = current token).
1205    fn peek_ahead_kind(&self, offset: usize) -> TokenKind {
1206        self.tokens
1207            .get(self.pos + offset)
1208            .map(|t| t.kind.clone())
1209            .unwrap_or(TokenKind::Eof)
1210    }
1211
1212    fn current_span(&self) -> Span {
1213        self.peek().span
1214    }
1215
1216    fn prev_span(&self) -> Span {
1217        if self.pos > 0 {
1218            self.tokens[self.pos - 1].span
1219        } else {
1220            Span::dummy()
1221        }
1222    }
1223
1224    fn is_at_end(&self) -> bool {
1225        matches!(self.peek_kind(), TokenKind::Eof)
1226    }
1227
1228    fn check(&self, kind: TokenKind) -> bool {
1229        std::mem::discriminant(&self.peek_kind()) == std::mem::discriminant(&kind)
1230    }
1231
1232    fn match_token(&mut self, kind: TokenKind) -> bool {
1233        if self.check(kind) {
1234            self.advance();
1235            true
1236        } else {
1237            false
1238        }
1239    }
1240
1241    fn advance(&mut self) {
1242        if !self.is_at_end() {
1243            self.pos += 1;
1244        }
1245    }
1246
1247    fn expect(&mut self, kind: TokenKind) -> ParseResult<()> {
1248        if self.check(kind.clone()) {
1249            self.advance();
1250            Ok(())
1251        } else {
1252            Err(ParseError::UnexpectedToken {
1253                expected: kind.to_string(),
1254                found: self.peek_kind().to_string(),
1255                span: self.current_span(),
1256            })
1257        }
1258    }
1259}
1260
1261/// Parse source text into a module.
1262pub fn parse(source: &str) -> ParseResult<Module> {
1263    Parser::new(source).parse_module()
1264}
1265
1266#[cfg(test)]
1267mod tests {
1268    use super::*;
1269
1270    fn parse_expr_str(source: &str) -> Expr {
1271        // Wrap in minimal module for parsing
1272        let full = format!("module Test\ninit {{ {} }}", source);
1273        let module = parse(&full).unwrap();
1274        match &module.decls[0] {
1275            Decl::Init(init) => init.body.clone(),
1276            _ => panic!("expected init decl"),
1277        }
1278    }
1279
1280    #[test]
1281    fn test_parse_empty_module() {
1282        let module = parse("module Empty").unwrap();
1283        assert_eq!(module.name.name, "Empty");
1284        assert!(module.decls.is_empty());
1285    }
1286
1287    #[test]
1288    fn test_parse_var_decl() {
1289        let module = parse("module Test\nvar count: Nat").unwrap();
1290        assert_eq!(module.decls.len(), 1);
1291        match &module.decls[0] {
1292            Decl::Var(v) => {
1293                assert_eq!(v.name.name, "count");
1294            }
1295            _ => panic!("expected var decl"),
1296        }
1297    }
1298
1299    #[test]
1300    fn test_parse_const_decl() {
1301        let module = parse("module Test\nconst MAX: Nat").unwrap();
1302        match &module.decls[0] {
1303            Decl::Const(c) => {
1304                assert_eq!(c.name.name, "MAX");
1305            }
1306            _ => panic!("expected const decl"),
1307        }
1308    }
1309
1310    #[test]
1311    fn test_parse_type_decl() {
1312        let module = parse("module Test\ntype Balance = 0..100").unwrap();
1313        match &module.decls[0] {
1314            Decl::Type(t) => {
1315                assert_eq!(t.name.name, "Balance");
1316            }
1317            _ => panic!("expected type decl"),
1318        }
1319    }
1320
1321    #[test]
1322    fn test_parse_init_block() {
1323        let module = parse("module Test\ninit { count == 0 }").unwrap();
1324        match &module.decls[0] {
1325            Decl::Init(i) => match &i.body.kind {
1326                ExprKind::Binary { op, .. } => {
1327                    assert_eq!(*op, BinOp::Eq);
1328                }
1329                _ => panic!("expected binary expr"),
1330            },
1331            _ => panic!("expected init decl"),
1332        }
1333    }
1334
1335    #[test]
1336    fn test_parse_action() {
1337        let source = r#"
1338module Test
1339action Increment() {
1340    require count < MAX
1341    count = count + 1
1342}
1343"#;
1344        let module = parse(source).unwrap();
1345        match &module.decls[0] {
1346            Decl::Action(a) => {
1347                assert_eq!(a.name.name, "Increment");
1348                assert_eq!(a.body.requires.len(), 1);
1349                assert!(a.body.effect.is_some());
1350            }
1351            _ => panic!("expected action decl"),
1352        }
1353    }
1354
1355    #[test]
1356    fn test_parse_assignment() {
1357        // Assignment syntax `x = x + 1` becomes `x' == x + 1` internally
1358        let expr = parse_expr_str("x = x + 1");
1359        match &expr.kind {
1360            ExprKind::Binary { left, .. } => match &left.kind {
1361                ExprKind::Primed(name) => assert_eq!(name, "x"),
1362                _ => panic!("expected primed var"),
1363            },
1364            _ => panic!("expected binary"),
1365        }
1366    }
1367
1368    #[test]
1369    fn test_parse_quantifier() {
1370        let expr = parse_expr_str("all x in S: P(x)");
1371        match &expr.kind {
1372            ExprKind::Quantifier { kind, bindings, .. } => {
1373                assert_eq!(*kind, QuantifierKind::Forall);
1374                assert_eq!(bindings.len(), 1);
1375                assert_eq!(bindings[0].var.name, "x");
1376            }
1377            _ => panic!("expected quantifier"),
1378        }
1379    }
1380
1381    #[test]
1382    fn test_parse_set_comprehension() {
1383        let expr = parse_expr_str("{ x in S if P(x) }");
1384        match &expr.kind {
1385            ExprKind::SetComprehension {
1386                var,
1387                filter: Some(_),
1388                ..
1389            } => {
1390                assert_eq!(var.name, "x");
1391            }
1392            _ => panic!("expected set comprehension"),
1393        }
1394    }
1395
1396    #[test]
1397    fn test_parse_dict_merge() {
1398        // Dict merge uses | operator: r | {k: v}
1399        let expr = parse_expr_str("r | {a: 1, k: v}");
1400        match &expr.kind {
1401            ExprKind::Binary { op, right, .. } => {
1402                assert_eq!(*op, BinOp::Union);
1403                // Right side should be a dict literal
1404                match &right.kind {
1405                    ExprKind::DictLit(entries) => {
1406                        assert_eq!(entries.len(), 2);
1407                    }
1408                    _ => panic!("expected dict literal on right"),
1409                }
1410            }
1411            _ => panic!("expected binary union"),
1412        }
1413    }
1414
1415    #[test]
1416    fn test_parse_if_then_else() {
1417        let expr = parse_expr_str("if x > 0 then 1 else 0");
1418        match &expr.kind {
1419            ExprKind::If { .. } => {}
1420            _ => panic!("expected if"),
1421        }
1422    }
1423
1424    #[test]
1425    fn test_parse_let() {
1426        let expr = parse_expr_str("let x = 1 in x + 1");
1427        match &expr.kind {
1428            ExprKind::Let { var, .. } => {
1429                assert_eq!(var.name, "x");
1430            }
1431            _ => panic!("expected let"),
1432        }
1433    }
1434
1435    #[test]
1436    fn test_parse_dict_comprehension() {
1437        // Dict comprehension { k: v for k in S } parses as FnLit
1438        let expr = parse_expr_str("{ x: x + 1 for x in S }");
1439        match &expr.kind {
1440            ExprKind::FnLit { var, .. } => {
1441                assert_eq!(var.name, "x");
1442            }
1443            _ => panic!("expected fn lit from dict comprehension"),
1444        }
1445    }
1446
1447    #[test]
1448    fn test_parse_precedence() {
1449        // a + b * c should parse as a + (b * c)
1450        let expr = parse_expr_str("a + b * c");
1451        match &expr.kind {
1452            ExprKind::Binary { op, right, .. } => {
1453                assert_eq!(*op, BinOp::Add);
1454                match &right.kind {
1455                    ExprKind::Binary { op, .. } => {
1456                        assert_eq!(*op, BinOp::Mul);
1457                    }
1458                    _ => panic!("expected binary"),
1459                }
1460            }
1461            _ => panic!("expected binary"),
1462        }
1463    }
1464
1465    #[test]
1466    fn test_parse_full_spec() {
1467        let source = r#"
1468module Counter
1469
1470const MAX: Nat
1471
1472var count: Nat
1473
1474init {
1475    count == 0
1476}
1477
1478action Increment() {
1479    require count < MAX
1480    count = count + 1
1481}
1482
1483invariant Bounded {
1484    count <= MAX
1485}
1486"#;
1487        let module = parse(source).unwrap();
1488        assert_eq!(module.name.name, "Counter");
1489        assert_eq!(module.decls.len(), 5);
1490    }
1491}