1use std::collections::HashSet;
4
5use crate::ast::{Rule, Term, TermKind};
6use crate::error::Error;
7use crate::lex::{Spanned, Token, tokens};
8
9const RESULT: &str = "result";
11
12const RESERVED: [&str; 5] = ["rule", "lower", "if", "spec", "bounded"];
16
17pub fn parse(path: &str, text: &str) -> Result<Vec<Rule>, Vec<Error>> {
24 let tokens = match tokens(path, text) {
25 Ok(tokens) => tokens,
26 Err(error) => return Err(vec![error]),
27 };
28 let mut reader = Reader { path, tokens: &tokens, at: 0, end: end_of(text) };
29 let mut rules = Vec::new();
30 let mut errors = Vec::new();
31
32 while reader.at < reader.tokens.len() {
33 match reader.rule() {
34 Ok(rule) => {
35 check(path, &rule, &mut errors);
36 rules.push(rule);
37 }
38 Err(error) => {
39 errors.push(error);
40 reader.resync();
41 }
42 }
43 }
44
45 if errors.is_empty() { Ok(rules) } else { Err(errors) }
46}
47
48pub fn parse_terms(path: &str, text: &str) -> Result<Vec<Term>, Vec<Error>> {
58 let tokens = match tokens(path, text) {
59 Ok(tokens) => tokens,
60 Err(error) => return Err(vec![error]),
61 };
62 let mut reader = Reader { path, tokens: &tokens, at: 0, end: end_of(text) };
63 let mut out = Vec::new();
64 while reader.at < reader.tokens.len() {
65 match reader.term() {
66 Ok(term) => out.push(term),
67 Err(error) => return Err(vec![error]),
68 }
69 }
70 Ok(out)
71}
72
73fn end_of(text: &str) -> (u32, u32) {
75 let line = 1 + u32::try_from(text.matches('\n').count()).unwrap_or(u32::MAX);
76 let column = 1 + u32::try_from(text.rsplit('\n').next().unwrap_or_default().chars().count())
77 .unwrap_or(u32::MAX);
78 (line, column)
79}
80
81#[derive(Debug)]
83struct Reader<'a> {
84 path: &'a str,
85 tokens: &'a [Spanned<'a>],
86 at: usize,
87 end: (u32, u32),
88}
89
90impl<'a> Reader<'a> {
91 fn error(&self, message: String) -> Error {
92 let (line, column) = match self.tokens.get(self.at) {
93 Some(token) => (token.line, token.column),
94 None => self.end,
95 };
96 Error { path: self.path.to_owned(), line, column, message }
97 }
98
99 fn peek(&self) -> Option<&'a Token<'a>> {
100 self.tokens.get(self.at).map(|t| &t.token)
101 }
102
103 fn at_clause(&self, name: &str) -> bool {
106 matches!(self.peek(), Some(Token::Open))
107 && matches!(self.tokens.get(self.at + 1).map(|t| &t.token), Some(Token::Atom(a)) if *a == name)
108 }
109
110 fn open(&mut self) -> Result<(), Error> {
111 match self.peek() {
112 Some(Token::Open) => {
113 self.at += 1;
114 Ok(())
115 }
116 _ => Err(self.error("expected a `(`".to_owned())),
117 }
118 }
119
120 fn close(&mut self, what: &str) -> Result<(), Error> {
124 match self.peek() {
125 Some(Token::Close) => {
126 self.at += 1;
127 Ok(())
128 }
129 None => Err(self.error(format!("`({what}` was never closed"))),
130 _ => Err(self.error("expected a `)`".to_owned())),
131 }
132 }
133
134 fn keyword(&mut self, name: &str) -> Result<(), Error> {
135 match self.peek() {
136 Some(Token::Atom(a)) if *a == name => {
137 self.at += 1;
138 Ok(())
139 }
140 _ => Err(self.error(format!("expected `{name}`"))),
141 }
142 }
143
144 fn rule(&mut self) -> Result<Rule, Error> {
146 let (line, column) = match self.tokens.get(self.at) {
147 Some(token) => (token.line, token.column),
148 None => self.end,
149 };
150 self.open()?;
151 self.keyword("rule")?;
152
153 self.open()?;
154 self.keyword("lower")?;
155 let pattern = self.term()?;
156 self.close("lower")?;
157
158 let guard = if self.at_clause("if") {
159 self.at += 1;
160 self.at += 1;
161 let guard = self.term()?;
162 self.close("if")?;
163 Some(guard)
164 } else {
165 None
166 };
167
168 let replacement = self.term()?;
169
170 self.open()?;
171 self.keyword("spec")?;
172 let spec = self.term()?;
173 self.close("spec")?;
174
175 let bounded = if self.at_clause("bounded") {
178 self.at += 2;
179 let why = self.string()?;
180 self.close("bounded")?;
181 Some(why)
182 } else {
183 None
184 };
185
186 self.close("rule")?;
187 Ok(Rule { pattern, guard, replacement, spec, bounded, line, column })
188 }
189
190 fn string(&mut self) -> Result<String, Error> {
192 match self.peek() {
193 Some(Token::Str(text)) if !text.trim().is_empty() => {
194 let text = (*text).to_owned();
195 self.at += 1;
196 Ok(text)
197 }
198 Some(Token::Str(_)) => {
199 Err(self.error("a bounded proof needs a reason somebody signed for".to_owned()))
200 }
201 _ => Err(self.error("expected a reason, in quotation marks".to_owned())),
202 }
203 }
204
205 fn term(&mut self) -> Result<Term, Error> {
206 let Some(token) = self.tokens.get(self.at) else {
207 return Err(self.error("expected a term and the file ended".to_owned()));
208 };
209 let (line, column) = (token.line, token.column);
210 match &token.token {
211 Token::Int(value) => {
212 self.at += 1;
213 Ok(Term { kind: TermKind::Int(*value), line, column })
214 }
215 Token::Atom(name) => {
220 self.at += 1;
221 Ok(Term { kind: TermKind::Var((*name).to_owned()), line, column })
222 }
223 Token::Close => Err(self.error("expected a term and found a `)`".to_owned())),
224 Token::Str(_) => Err(self.error(
225 "a string is prose for a person and is not something a term can be".to_owned(),
226 )),
227 Token::Open => {
228 self.at += 1;
229 let head = match self.peek() {
230 Some(Token::Atom(head)) => {
231 let head = *head;
232 self.at += 1;
233 head
234 }
235 _ => return Err(self.error("expected a name after the `(`".to_owned())),
236 };
237 if RESERVED.contains(&head) {
238 let message =
242 format!("`{head}` belongs to a rule's own shape, not inside a term");
243 self.at -= 2;
244 return Err(self.error(message));
245 }
246 let mut args = Vec::new();
247 while !matches!(self.peek(), Some(Token::Close)) {
248 if self.peek().is_none() {
249 return Err(self.error(format!("`({head}` was never closed")));
250 }
251 args.push(self.term()?);
252 }
253 self.at += 1;
254 Ok(Term { kind: TermKind::App { head: head.to_owned(), args }, line, column })
255 }
256 }
257 }
258
259 fn resync(&mut self) {
262 self.at += 1;
263 while self.at < self.tokens.len() && !self.at_clause("rule") {
264 self.at += 1;
265 }
266 }
267}
268
269fn check(path: &str, rule: &Rule, errors: &mut Vec<Error>) {
271 let mut found = Vec::new();
272
273 if !matches!(rule.pattern.kind, TermKind::App { .. }) {
274 found.push((&rule.pattern, "a pattern has to name something to match".to_owned()));
275 }
276
277 let mut bound: HashSet<&str> = HashSet::new();
281 let mut twice = Vec::new();
282 let mut in_pattern = Vec::new();
283 rule.pattern.walk(&mut |term| match &term.kind {
284 TermKind::Var(name) => {
285 if !bound.insert(name.as_str()) {
286 twice.push((term, format!("`{name}` is bound twice in one pattern")));
287 }
288 }
289 TermKind::App { head, .. } if head == RESULT => {
290 let said = "`(result)` is what the replacement produces, so it means nothing here";
291 in_pattern.push((term, said.to_owned()));
292 }
293 _ => {}
294 });
295 found.extend(twice);
296 found.extend(in_pattern);
297
298 let mut clauses =
299 vec![("the replacement", &rule.replacement), ("the specification", &rule.spec)];
300 if let Some(guard) = &rule.guard {
301 clauses.insert(0, ("the guard", guard));
302 }
303 let mut loose = Vec::new();
304 for (what, term) in clauses {
305 term.walk(&mut |term| match &term.kind {
306 TermKind::Var(name) if !bound.contains(name.as_str()) => {
307 let said = format!("`{name}` is used in {what} and the pattern never bound it");
308 loose.push((term, said));
309 }
310 TermKind::App { head, .. } if head == RESULT && what != "the specification" => {
313 let said = format!("`(result)` belongs in the specification, not in {what}");
314 loose.push((term, said));
315 }
316 _ => {}
317 });
318 }
319 found.extend(loose);
320
321 for (term, message) in found {
322 errors.push(Error { path: path.to_owned(), line: term.line, column: term.column, message });
323 }
324}