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_name = self.expect_ident("for quantifier type")?;
135        let ty = match ty_name.as_str() {
136            "Int" => SpecType::Int,
137            "Float" => SpecType::Float,
138            "Bool" => SpecType::Bool,
139            "Str" => SpecType::Str,
140            other => return Err(self.err(format!("unknown spec type `{other}`"))),
141        };
142        Ok(Quantifier { name, ty, constraint: None })
143    }
144
145    /// The body may be a sequence of `let` bindings followed by a single
146    /// expression, all inside the spec's outer `{ ... }`.
147    fn parse_body_block(&mut self) -> Result<SpecExpr, SpecParseError> {
148        // Optional sequence of lets, then the final expression.
149        self.skip_newlines();
150        let mut lets: Vec<(String, SpecExpr)> = Vec::new();
151        while matches!(self.peek(), Some(TokenKind::Let)) {
152            self.bump(); // 'let'
153            let name = self.expect_ident("after `let`")?;
154            // Allow `:: Type` annotation; ignored here.
155            if self.eat(&TokenKind::ColonColon) {
156                let _ = self.expect_ident("after `::`")?;
157            }
158            if !self.eat(&TokenKind::ColonEq) {
159                return Err(self.err("expected `:=` in let"));
160            }
161            let value = self.parse_expr()?;
162            lets.push((name, value));
163            self.skip_newlines();
164        }
165        let mut body = self.parse_expr()?;
166        // Wrap in nested Let from inside out.
167        for (name, value) in lets.into_iter().rev() {
168            body = SpecExpr::Let { name, value: Box::new(value), body: Box::new(body) };
169        }
170        Ok(body)
171    }
172
173    // ---- expressions ----
174
175    fn parse_expr(&mut self) -> Result<SpecExpr, SpecParseError> {
176        self.parse_or()
177    }
178
179    fn parse_or(&mut self) -> Result<SpecExpr, SpecParseError> {
180        let mut lhs = self.parse_and()?;
181        while self.peek_kw("or") {
182            self.bump();
183            let rhs = self.parse_and()?;
184            lhs = SpecExpr::BinOp { op: SpecOp::Or, lhs: Box::new(lhs), rhs: Box::new(rhs) };
185        }
186        Ok(lhs)
187    }
188
189    fn parse_and(&mut self) -> Result<SpecExpr, SpecParseError> {
190        let mut lhs = self.parse_cmp()?;
191        while self.peek_kw("and") {
192            self.bump();
193            let rhs = self.parse_cmp()?;
194            lhs = SpecExpr::BinOp { op: SpecOp::And, lhs: Box::new(lhs), rhs: Box::new(rhs) };
195        }
196        Ok(lhs)
197    }
198
199    fn parse_cmp(&mut self) -> Result<SpecExpr, SpecParseError> {
200        let lhs = self.parse_add()?;
201        let op = match self.peek() {
202            Some(TokenKind::EqEq) => Some(SpecOp::Eq),
203            Some(TokenKind::BangEq) => Some(SpecOp::Neq),
204            Some(TokenKind::Lt) => Some(SpecOp::Lt),
205            Some(TokenKind::LtEq) => Some(SpecOp::Le),
206            Some(TokenKind::Gt) => Some(SpecOp::Gt),
207            Some(TokenKind::GtEq) => Some(SpecOp::Ge),
208            _ => None,
209        };
210        if let Some(op) = op {
211            self.bump();
212            let rhs = self.parse_add()?;
213            return Ok(SpecExpr::BinOp { op, lhs: Box::new(lhs), rhs: Box::new(rhs) });
214        }
215        Ok(lhs)
216    }
217
218    fn parse_add(&mut self) -> Result<SpecExpr, SpecParseError> {
219        let mut lhs = self.parse_mul()?;
220        loop {
221            let op = match self.peek() {
222                Some(TokenKind::Plus) => SpecOp::Add,
223                Some(TokenKind::Minus) => SpecOp::Sub,
224                _ => break,
225            };
226            self.bump();
227            let rhs = self.parse_mul()?;
228            lhs = SpecExpr::BinOp { op, lhs: Box::new(lhs), rhs: Box::new(rhs) };
229        }
230        Ok(lhs)
231    }
232
233    fn parse_mul(&mut self) -> Result<SpecExpr, SpecParseError> {
234        let mut lhs = self.parse_unary()?;
235        loop {
236            let op = match self.peek() {
237                Some(TokenKind::Star) => SpecOp::Mul,
238                Some(TokenKind::Slash) => SpecOp::Div,
239                Some(TokenKind::Percent) => SpecOp::Mod,
240                _ => break,
241            };
242            self.bump();
243            let rhs = self.parse_unary()?;
244            lhs = SpecExpr::BinOp { op, lhs: Box::new(lhs), rhs: Box::new(rhs) };
245        }
246        Ok(lhs)
247    }
248
249    fn parse_unary(&mut self) -> Result<SpecExpr, SpecParseError> {
250        if self.peek_kw("not") {
251            self.bump();
252            let e = self.parse_unary()?;
253            return Ok(SpecExpr::Not { expr: Box::new(e) });
254        }
255        self.parse_postfix()
256    }
257
258    fn parse_postfix(&mut self) -> Result<SpecExpr, SpecParseError> {
259        let mut e = self.parse_primary()?;
260        while matches!(self.peek(), Some(TokenKind::LParen)) {
261            self.bump();
262            let mut args = Vec::new();
263            self.skip_newlines();
264            if !matches!(self.peek(), Some(TokenKind::RParen)) {
265                args.push(self.parse_expr()?);
266                while self.eat(&TokenKind::Comma) { args.push(self.parse_expr()?); }
267            }
268            if !self.eat(&TokenKind::RParen) {
269                return Err(self.err("expected `)` to close call"));
270            }
271            let func = match e {
272                SpecExpr::Var { name } => name,
273                other => return Err(self.err(format!("only ident-callable; got {other:?}"))),
274            };
275            e = SpecExpr::Call { func, args };
276        }
277        Ok(e)
278    }
279
280    fn parse_primary(&mut self) -> Result<SpecExpr, SpecParseError> {
281        self.skip_newlines();
282        match self.peek() {
283            Some(TokenKind::Int(_)) => match self.bump().unwrap().kind {
284                TokenKind::Int(n) => Ok(SpecExpr::IntLit { value: n }), _ => unreachable!(),
285            },
286            Some(TokenKind::Float(_)) => match self.bump().unwrap().kind {
287                TokenKind::Float(n) => Ok(SpecExpr::FloatLit { value: n }), _ => unreachable!(),
288            },
289            Some(TokenKind::Str(_)) => match self.bump().unwrap().kind {
290                TokenKind::Str(s) => Ok(SpecExpr::StrLit { value: s }), _ => unreachable!(),
291            },
292            Some(TokenKind::True) => { self.bump(); Ok(SpecExpr::BoolLit { value: true }) }
293            Some(TokenKind::False) => { self.bump(); Ok(SpecExpr::BoolLit { value: false }) }
294            Some(TokenKind::Ident(_)) => {
295                let name = self.expect_ident("")?;
296                Ok(SpecExpr::Var { name })
297            }
298            Some(TokenKind::LParen) => {
299                self.bump();
300                let e = self.parse_expr()?;
301                if !self.eat(&TokenKind::RParen) {
302                    return Err(self.err("expected `)`"));
303                }
304                Ok(e)
305            }
306            other => Err(self.err(format!("expected primary expression, got {other:?}"))),
307        }
308    }
309
310    fn peek_kw(&self, name: &str) -> bool {
311        match self.toks.get(self.idx).map(|t| &t.kind) {
312            Some(TokenKind::Ident(n)) => n == name,
313            Some(TokenKind::And) => name == "and",
314            Some(TokenKind::Or) => name == "or",
315            Some(TokenKind::Not) => name == "not",
316            _ => false,
317        }
318    }
319}