Skip to main content

spec_checker/
parser.rs

1//! Spec DSL parser. Hand-rolled recursive-descent.
2
3use crate::ast::*;
4use lex_syntax::token::{lex, Token, TokenKind};
5
6#[derive(Debug, thiserror::Error)]
7#[error("spec parse error at byte {pos}: {msg}")]
8pub struct SpecParseError {
9    pub pos: usize,
10    pub msg: String,
11}
12
13pub fn parse_spec(src: &str) -> Result<Spec, SpecParseError> {
14    let toks = lex(src).map_err(|e| SpecParseError {
15        pos: e.span.start, msg: format!("lex: {}", e.snippet),
16    })?;
17    let mut p = Parser { toks, idx: 0 };
18    let spec = p.parse_spec()?;
19    p.skip_newlines();
20    if !p.at_eof() {
21        return Err(p.err("trailing input after spec"));
22    }
23    Ok(spec)
24}
25
26struct Parser {
27    toks: Vec<Token>,
28    idx: usize,
29}
30
31impl Parser {
32    fn at_eof(&self) -> bool { self.idx >= self.toks.len() }
33
34    fn peek(&self) -> Option<&TokenKind> { self.toks.get(self.idx).map(|t| &t.kind) }
35
36    fn bump(&mut self) -> Option<Token> {
37        let t = self.toks.get(self.idx).cloned();
38        if t.is_some() { self.idx += 1; }
39        t
40    }
41
42    fn pos(&self) -> usize {
43        self.toks.get(self.idx).map(|t| t.span.start).unwrap_or(0)
44    }
45
46    fn err(&self, m: impl Into<String>) -> SpecParseError {
47        SpecParseError { pos: self.pos(), msg: m.into() }
48    }
49
50    fn skip_newlines(&mut self) {
51        while matches!(self.peek(), Some(TokenKind::Newline) | Some(TokenKind::Semi)) {
52            self.idx += 1;
53        }
54    }
55
56    fn eat(&mut self, k: &TokenKind) -> bool {
57        self.skip_newlines();
58        if let Some(cur) = self.peek() {
59            if std::mem::discriminant(cur) == std::mem::discriminant(k) {
60                self.bump();
61                return true;
62            }
63        }
64        false
65    }
66
67    fn expect_ident(&mut self, ctx: &str) -> Result<String, SpecParseError> {
68        self.skip_newlines();
69        match self.peek() {
70            Some(TokenKind::Ident(_)) => match self.bump().unwrap().kind {
71                TokenKind::Ident(n) => Ok(n), _ => unreachable!(),
72            },
73            other => Err(self.err(format!("expected ident {ctx}, got {other:?}"))),
74        }
75    }
76
77    fn expect_keyword(&mut self, kw: &str) -> Result<(), SpecParseError> {
78        // We don't have a `spec`/`forall`/`where` keyword in the lexer;
79        // they come through as Ident.
80        let id = self.expect_ident(&format!("(keyword `{kw}`)"))?;
81        if id != kw {
82            return Err(self.err(format!("expected `{kw}`, got `{id}`")));
83        }
84        Ok(())
85    }
86
87    fn parse_spec(&mut self) -> Result<Spec, SpecParseError> {
88        // `spec <name> { forall ... : body }`
89        self.expect_keyword("spec")?;
90        let name = self.expect_ident("for spec name")?;
91        if !self.eat(&TokenKind::LBrace) {
92            return Err(self.err("expected `{` after spec name"));
93        }
94        self.expect_keyword("forall")?;
95
96        let mut quantifiers = Vec::new();
97        loop {
98            quantifiers.push(self.parse_quantifier()?);
99            self.skip_newlines();
100            if self.eat(&TokenKind::Comma) {
101                continue;
102            }
103            break;
104        }
105
106        // Optional `where <constraint>` applies to the *last* quantifier.
107        if let Some(TokenKind::Ident(n)) = self.peek() {
108            if n == "where" {
109                self.bump();
110                let c = self.parse_expr()?;
111                if let Some(last) = quantifiers.last_mut() {
112                    last.constraint = Some(c);
113                }
114            }
115        }
116
117        // Body separator: `:` or `=>`. We accept either.
118        if !self.eat(&TokenKind::Colon) && !self.eat(&TokenKind::FatArrow) {
119            return Err(self.err("expected `:` or `=>` before spec body"));
120        }
121
122        let body = self.parse_body_block()?;
123        if !self.eat(&TokenKind::RBrace) {
124            return Err(self.err("expected `}` to close spec"));
125        }
126        Ok(Spec { name, quantifiers, body })
127    }
128
129    fn parse_quantifier(&mut self) -> Result<Quantifier, SpecParseError> {
130        let name = self.expect_ident("for quantifier var")?;
131        if !self.eat(&TokenKind::ColonColon) {
132            return Err(self.err("expected `::` after quantifier var"));
133        }
134        let ty = self.parse_spec_type()?;
135        Ok(Quantifier { name, ty, constraint: None })
136    }
137
138    /// Parse a `SpecType` (#208). Scalar idents (`Int`, `Float`, `Bool`,
139    /// `Str`) plus structured `{ name :: Ty, ... }` for records.
140    /// Records nest, so `{ s :: { used :: Int, ceiling :: Int } }` works.
141    fn parse_spec_type(&mut self) -> Result<SpecType, SpecParseError> {
142        if self.eat(&TokenKind::LBrace) {
143            // Record type: `{ name :: Ty, name :: Ty, ... }`
144            let mut fields: Vec<(String, SpecType)> = Vec::new();
145            self.skip_newlines();
146            if !matches!(self.peek(), Some(TokenKind::RBrace)) {
147                loop {
148                    let field_name = self.expect_ident("for record field name")?;
149                    if !self.eat(&TokenKind::ColonColon) {
150                        return Err(self.err("expected `::` after record field name"));
151                    }
152                    let field_ty = self.parse_spec_type()?;
153                    fields.push((field_name, field_ty));
154                    self.skip_newlines();
155                    if !self.eat(&TokenKind::Comma) { break; }
156                    self.skip_newlines();
157                }
158            }
159            if !self.eat(&TokenKind::RBrace) {
160                return Err(self.err("expected `}` to close record type"));
161            }
162            return Ok(SpecType::Record { fields });
163        }
164        let ty_name = self.expect_ident("for quantifier type")?;
165        match ty_name.as_str() {
166            "Int" => Ok(SpecType::Int),
167            "Float" => Ok(SpecType::Float),
168            "Bool" => Ok(SpecType::Bool),
169            "Str" => Ok(SpecType::Str),
170            "List" => {
171                // `List[T]` — element type in brackets.
172                if !self.eat(&TokenKind::LBracket) {
173                    return Err(self.err("expected `[` after `List`"));
174                }
175                let elem = self.parse_spec_type()?;
176                if !self.eat(&TokenKind::RBracket) {
177                    return Err(self.err("expected `]` to close `List[...]`"));
178                }
179                Ok(SpecType::List { element: Box::new(elem) })
180            }
181            // #208 slice 3: any other capitalized ident is treated as
182            // a user-defined ADT name (`Message`, `Order`, …). The
183            // gate path uses the value's runtime variant tag for
184            // discrimination, so no compile-time variant table is
185            // needed; the random-input prover doesn't enumerate these.
186            other if other.chars().next().is_some_and(|c| c.is_ascii_uppercase()) => {
187                Ok(SpecType::Named { name: other.to_string() })
188            }
189            other => Err(self.err(format!("unknown spec type `{other}`"))),
190        }
191    }
192
193    /// The body may be a sequence of `let` bindings followed by a single
194    /// expression, all inside the spec's outer `{ ... }`.
195    fn parse_body_block(&mut self) -> Result<SpecExpr, SpecParseError> {
196        // Optional sequence of lets, then the final expression.
197        self.skip_newlines();
198        let mut lets: Vec<(String, SpecExpr)> = Vec::new();
199        while matches!(self.peek(), Some(TokenKind::Let)) {
200            self.bump(); // 'let'
201            let name = self.expect_ident("after `let`")?;
202            // Allow `:: Type` annotation; ignored here.
203            if self.eat(&TokenKind::ColonColon) {
204                let _ = self.expect_ident("after `::`")?;
205            }
206            if !self.eat(&TokenKind::ColonEq) {
207                return Err(self.err("expected `:=` in let"));
208            }
209            let value = self.parse_expr()?;
210            lets.push((name, value));
211            self.skip_newlines();
212        }
213        let mut body = self.parse_expr()?;
214        // Wrap in nested Let from inside out.
215        for (name, value) in lets.into_iter().rev() {
216            body = SpecExpr::Let { name, value: Box::new(value), body: Box::new(body) };
217        }
218        Ok(body)
219    }
220
221    // ---- expressions ----
222
223    fn parse_expr(&mut self) -> Result<SpecExpr, SpecParseError> {
224        self.parse_or()
225    }
226
227    fn parse_or(&mut self) -> Result<SpecExpr, SpecParseError> {
228        let mut lhs = self.parse_and()?;
229        while self.peek_kw("or") {
230            self.bump();
231            let rhs = self.parse_and()?;
232            lhs = SpecExpr::BinOp { op: SpecOp::Or, lhs: Box::new(lhs), rhs: Box::new(rhs) };
233        }
234        Ok(lhs)
235    }
236
237    fn parse_and(&mut self) -> Result<SpecExpr, SpecParseError> {
238        let mut lhs = self.parse_cmp()?;
239        while self.peek_kw("and") {
240            self.bump();
241            let rhs = self.parse_cmp()?;
242            lhs = SpecExpr::BinOp { op: SpecOp::And, lhs: Box::new(lhs), rhs: Box::new(rhs) };
243        }
244        Ok(lhs)
245    }
246
247    fn parse_cmp(&mut self) -> Result<SpecExpr, SpecParseError> {
248        let lhs = self.parse_add()?;
249        let op = match self.peek() {
250            Some(TokenKind::EqEq) => Some(SpecOp::Eq),
251            Some(TokenKind::BangEq) => Some(SpecOp::Neq),
252            Some(TokenKind::Lt) => Some(SpecOp::Lt),
253            Some(TokenKind::LtEq) => Some(SpecOp::Le),
254            Some(TokenKind::Gt) => Some(SpecOp::Gt),
255            Some(TokenKind::GtEq) => Some(SpecOp::Ge),
256            _ => None,
257        };
258        if let Some(op) = op {
259            self.bump();
260            let rhs = self.parse_add()?;
261            return Ok(SpecExpr::BinOp { op, lhs: Box::new(lhs), rhs: Box::new(rhs) });
262        }
263        Ok(lhs)
264    }
265
266    fn parse_add(&mut self) -> Result<SpecExpr, SpecParseError> {
267        let mut lhs = self.parse_mul()?;
268        loop {
269            let op = match self.peek() {
270                Some(TokenKind::Plus) => SpecOp::Add,
271                Some(TokenKind::Minus) => SpecOp::Sub,
272                _ => break,
273            };
274            self.bump();
275            let rhs = self.parse_mul()?;
276            lhs = SpecExpr::BinOp { op, lhs: Box::new(lhs), rhs: Box::new(rhs) };
277        }
278        Ok(lhs)
279    }
280
281    fn parse_mul(&mut self) -> Result<SpecExpr, SpecParseError> {
282        let mut lhs = self.parse_unary()?;
283        loop {
284            let op = match self.peek() {
285                Some(TokenKind::Star) => SpecOp::Mul,
286                Some(TokenKind::Slash) => SpecOp::Div,
287                Some(TokenKind::Percent) => SpecOp::Mod,
288                _ => break,
289            };
290            self.bump();
291            let rhs = self.parse_unary()?;
292            lhs = SpecExpr::BinOp { op, lhs: Box::new(lhs), rhs: Box::new(rhs) };
293        }
294        Ok(lhs)
295    }
296
297    fn parse_unary(&mut self) -> Result<SpecExpr, SpecParseError> {
298        if self.peek_kw("not") {
299            self.bump();
300            let e = self.parse_unary()?;
301            return Ok(SpecExpr::Not { expr: Box::new(e) });
302        }
303        self.parse_postfix()
304    }
305
306    fn parse_postfix(&mut self) -> Result<SpecExpr, SpecParseError> {
307        let mut e = self.parse_primary()?;
308        loop {
309            match self.peek() {
310                Some(TokenKind::LParen) => {
311                    self.bump();
312                    let mut args = Vec::new();
313                    self.skip_newlines();
314                    if !matches!(self.peek(), Some(TokenKind::RParen)) {
315                        args.push(self.parse_expr()?);
316                        while self.eat(&TokenKind::Comma) { args.push(self.parse_expr()?); }
317                    }
318                    if !self.eat(&TokenKind::RParen) {
319                        return Err(self.err("expected `)` to close call"));
320                    }
321                    let func = match e {
322                        SpecExpr::Var { name } => name,
323                        other => return Err(self.err(format!("only ident-callable; got {other:?}"))),
324                    };
325                    e = SpecExpr::Call { func, args };
326                }
327                Some(TokenKind::Dot) => {
328                    self.bump();
329                    let field = self.expect_ident("after `.`")?;
330                    e = SpecExpr::FieldAccess { value: Box::new(e), field };
331                }
332                Some(TokenKind::LBracket) => {
333                    // `xs[i]` indexed access (#208). The expression
334                    // inside brackets is parsed at full precedence so
335                    // `xs[i + 1]`, `xs[length(xs) - 1]`, etc. work.
336                    self.bump();
337                    let index = self.parse_expr()?;
338                    if !self.eat(&TokenKind::RBracket) {
339                        return Err(self.err("expected `]` to close index"));
340                    }
341                    e = SpecExpr::Index { list: Box::new(e), index: Box::new(index) };
342                }
343                _ => break,
344            }
345        }
346        Ok(e)
347    }
348
349    fn parse_primary(&mut self) -> Result<SpecExpr, SpecParseError> {
350        self.skip_newlines();
351        match self.peek() {
352            Some(TokenKind::Int(_)) => match self.bump().unwrap().kind {
353                TokenKind::Int(n) => Ok(SpecExpr::IntLit { value: n }), _ => unreachable!(),
354            },
355            Some(TokenKind::Float(_)) => match self.bump().unwrap().kind {
356                TokenKind::Float(n) => Ok(SpecExpr::FloatLit { value: n }), _ => unreachable!(),
357            },
358            Some(TokenKind::Str(_)) => match self.bump().unwrap().kind {
359                TokenKind::Str(s) => Ok(SpecExpr::StrLit { value: s }), _ => unreachable!(),
360            },
361            Some(TokenKind::True) => { self.bump(); Ok(SpecExpr::BoolLit { value: true }) }
362            Some(TokenKind::False) => { self.bump(); Ok(SpecExpr::BoolLit { value: false }) }
363            Some(TokenKind::Match) => self.parse_match(),
364            Some(TokenKind::Ident(_)) => {
365                let name = self.expect_ident("")?;
366                Ok(SpecExpr::Var { name })
367            }
368            Some(TokenKind::LParen) => {
369                self.bump();
370                let e = self.parse_expr()?;
371                if !self.eat(&TokenKind::RParen) {
372                    return Err(self.err("expected `)`"));
373                }
374                Ok(e)
375            }
376            other => Err(self.err(format!("expected primary expression, got {other:?}"))),
377        }
378    }
379
380    /// Parse `match scrutinee { Pattern => body, _ => body, ... }`
381    /// (#208 slice 3). Arms are separated by `,`; trailing comma OK.
382    fn parse_match(&mut self) -> Result<SpecExpr, SpecParseError> {
383        self.bump(); // consume `match`
384        let scrutinee = self.parse_expr()?;
385        if !self.eat(&TokenKind::LBrace) {
386            return Err(self.err("expected `{` after match scrutinee"));
387        }
388        let mut arms: Vec<crate::ast::MatchArm> = Vec::new();
389        self.skip_newlines();
390        while !matches!(self.peek(), Some(TokenKind::RBrace)) {
391            let pattern = self.parse_pattern()?;
392            if !self.eat(&TokenKind::FatArrow) {
393                return Err(self.err("expected `=>` after match pattern"));
394            }
395            let body = self.parse_expr()?;
396            arms.push(crate::ast::MatchArm { pattern, body });
397            self.skip_newlines();
398            if !self.eat(&TokenKind::Comma) { break; }
399            self.skip_newlines();
400        }
401        if !self.eat(&TokenKind::RBrace) {
402            return Err(self.err("expected `}` to close match"));
403        }
404        Ok(SpecExpr::Match {
405            scrutinee: Box::new(scrutinee),
406            arms,
407        })
408    }
409
410    /// Parse a single pattern: `_`, `Variant`, `Variant(a, b, ...)`.
411    fn parse_pattern(&mut self) -> Result<crate::ast::SpecPattern, SpecParseError> {
412        self.skip_newlines();
413        if self.eat(&TokenKind::Underscore) {
414            return Ok(crate::ast::SpecPattern::Wildcard);
415        }
416        let name = self.expect_ident("for variant pattern")?;
417        let bindings = if self.eat(&TokenKind::LParen) {
418            let mut bs = Vec::new();
419            self.skip_newlines();
420            if !matches!(self.peek(), Some(TokenKind::RParen)) {
421                bs.push(self.parse_variant_binding()?);
422                while self.eat(&TokenKind::Comma) {
423                    bs.push(self.parse_variant_binding()?);
424                }
425            }
426            if !self.eat(&TokenKind::RParen) {
427                return Err(self.err("expected `)` to close variant pattern"));
428            }
429            bs
430        } else {
431            Vec::new()
432        };
433        Ok(crate::ast::SpecPattern::Variant { name, bindings })
434    }
435
436    /// Parse a single binding inside a variant pattern: either an
437    /// ident name or `_` for "ignore this positional arg". Internally
438    /// `_` is bound to the literal name `_`; the body never references
439    /// it, so collisions across `_` bindings are inert.
440    fn parse_variant_binding(&mut self) -> Result<String, SpecParseError> {
441        if self.eat(&TokenKind::Underscore) {
442            return Ok("_".to_string());
443        }
444        self.expect_ident("for variant binding")
445    }
446
447    fn peek_kw(&self, name: &str) -> bool {
448        match self.toks.get(self.idx).map(|t| &t.kind) {
449            Some(TokenKind::Ident(n)) => n == name,
450            Some(TokenKind::And) => name == "and",
451            Some(TokenKind::Or) => name == "or",
452            Some(TokenKind::Not) => name == "not",
453            _ => false,
454        }
455    }
456}