use std::collections::HashSet;
use crate::ast::{Rule, Term, TermKind};
use crate::error::Error;
use crate::lex::{Spanned, Token, tokens};
const RESULT: &str = "result";
const RESERVED: [&str; 5] = ["rule", "lower", "if", "spec", "bounded"];
pub fn parse(path: &str, text: &str) -> Result<Vec<Rule>, Vec<Error>> {
let tokens = match tokens(path, text) {
Ok(tokens) => tokens,
Err(error) => return Err(vec![error]),
};
let mut reader = Reader { path, tokens: &tokens, at: 0, end: end_of(text) };
let mut rules = Vec::new();
let mut errors = Vec::new();
while reader.at < reader.tokens.len() {
match reader.rule() {
Ok(rule) => {
check(path, &rule, &mut errors);
rules.push(rule);
}
Err(error) => {
errors.push(error);
reader.resync();
}
}
}
if errors.is_empty() { Ok(rules) } else { Err(errors) }
}
pub fn parse_terms(path: &str, text: &str) -> Result<Vec<Term>, Vec<Error>> {
let tokens = match tokens(path, text) {
Ok(tokens) => tokens,
Err(error) => return Err(vec![error]),
};
let mut reader = Reader { path, tokens: &tokens, at: 0, end: end_of(text) };
let mut out = Vec::new();
while reader.at < reader.tokens.len() {
match reader.term() {
Ok(term) => out.push(term),
Err(error) => return Err(vec![error]),
}
}
Ok(out)
}
fn end_of(text: &str) -> (u32, u32) {
let line = 1 + u32::try_from(text.matches('\n').count()).unwrap_or(u32::MAX);
let column = 1 + u32::try_from(text.rsplit('\n').next().unwrap_or_default().chars().count())
.unwrap_or(u32::MAX);
(line, column)
}
#[derive(Debug)]
struct Reader<'a> {
path: &'a str,
tokens: &'a [Spanned<'a>],
at: usize,
end: (u32, u32),
}
impl<'a> Reader<'a> {
fn error(&self, message: String) -> Error {
let (line, column) = match self.tokens.get(self.at) {
Some(token) => (token.line, token.column),
None => self.end,
};
Error { path: self.path.to_owned(), line, column, message }
}
fn peek(&self) -> Option<&'a Token<'a>> {
self.tokens.get(self.at).map(|t| &t.token)
}
fn at_clause(&self, name: &str) -> bool {
matches!(self.peek(), Some(Token::Open))
&& matches!(self.tokens.get(self.at + 1).map(|t| &t.token), Some(Token::Atom(a)) if *a == name)
}
fn open(&mut self) -> Result<(), Error> {
match self.peek() {
Some(Token::Open) => {
self.at += 1;
Ok(())
}
_ => Err(self.error("expected a `(`".to_owned())),
}
}
fn close(&mut self, what: &str) -> Result<(), Error> {
match self.peek() {
Some(Token::Close) => {
self.at += 1;
Ok(())
}
None => Err(self.error(format!("`({what}` was never closed"))),
_ => Err(self.error("expected a `)`".to_owned())),
}
}
fn keyword(&mut self, name: &str) -> Result<(), Error> {
match self.peek() {
Some(Token::Atom(a)) if *a == name => {
self.at += 1;
Ok(())
}
_ => Err(self.error(format!("expected `{name}`"))),
}
}
fn rule(&mut self) -> Result<Rule, Error> {
let (line, column) = match self.tokens.get(self.at) {
Some(token) => (token.line, token.column),
None => self.end,
};
self.open()?;
self.keyword("rule")?;
self.open()?;
self.keyword("lower")?;
let pattern = self.term()?;
self.close("lower")?;
let guard = if self.at_clause("if") {
self.at += 1;
self.at += 1;
let guard = self.term()?;
self.close("if")?;
Some(guard)
} else {
None
};
let replacement = self.term()?;
self.open()?;
self.keyword("spec")?;
let spec = self.term()?;
self.close("spec")?;
let bounded = if self.at_clause("bounded") {
self.at += 2;
let why = self.string()?;
self.close("bounded")?;
Some(why)
} else {
None
};
self.close("rule")?;
Ok(Rule { pattern, guard, replacement, spec, bounded, line, column })
}
fn string(&mut self) -> Result<String, Error> {
match self.peek() {
Some(Token::Str(text)) if !text.trim().is_empty() => {
let text = (*text).to_owned();
self.at += 1;
Ok(text)
}
Some(Token::Str(_)) => {
Err(self.error("a bounded proof needs a reason somebody signed for".to_owned()))
}
_ => Err(self.error("expected a reason, in quotation marks".to_owned())),
}
}
fn term(&mut self) -> Result<Term, Error> {
let Some(token) = self.tokens.get(self.at) else {
return Err(self.error("expected a term and the file ended".to_owned()));
};
let (line, column) = (token.line, token.column);
match &token.token {
Token::Int(value) => {
self.at += 1;
Ok(Term { kind: TermKind::Int(*value), line, column })
}
Token::Atom(name) => {
self.at += 1;
Ok(Term { kind: TermKind::Var((*name).to_owned()), line, column })
}
Token::Close => Err(self.error("expected a term and found a `)`".to_owned())),
Token::Str(_) => Err(self.error(
"a string is prose for a person and is not something a term can be".to_owned(),
)),
Token::Open => {
self.at += 1;
let head = match self.peek() {
Some(Token::Atom(head)) => {
let head = *head;
self.at += 1;
head
}
_ => return Err(self.error("expected a name after the `(`".to_owned())),
};
if RESERVED.contains(&head) {
let message =
format!("`{head}` belongs to a rule's own shape, not inside a term");
self.at -= 2;
return Err(self.error(message));
}
let mut args = Vec::new();
while !matches!(self.peek(), Some(Token::Close)) {
if self.peek().is_none() {
return Err(self.error(format!("`({head}` was never closed")));
}
args.push(self.term()?);
}
self.at += 1;
Ok(Term { kind: TermKind::App { head: head.to_owned(), args }, line, column })
}
}
}
fn resync(&mut self) {
self.at += 1;
while self.at < self.tokens.len() && !self.at_clause("rule") {
self.at += 1;
}
}
}
fn check(path: &str, rule: &Rule, errors: &mut Vec<Error>) {
let mut found = Vec::new();
if !matches!(rule.pattern.kind, TermKind::App { .. }) {
found.push((&rule.pattern, "a pattern has to name something to match".to_owned()));
}
let mut bound: HashSet<&str> = HashSet::new();
let mut twice = Vec::new();
let mut in_pattern = Vec::new();
rule.pattern.walk(&mut |term| match &term.kind {
TermKind::Var(name) => {
if !bound.insert(name.as_str()) {
twice.push((term, format!("`{name}` is bound twice in one pattern")));
}
}
TermKind::App { head, .. } if head == RESULT => {
let said = "`(result)` is what the replacement produces, so it means nothing here";
in_pattern.push((term, said.to_owned()));
}
_ => {}
});
found.extend(twice);
found.extend(in_pattern);
let mut clauses =
vec![("the replacement", &rule.replacement), ("the specification", &rule.spec)];
if let Some(guard) = &rule.guard {
clauses.insert(0, ("the guard", guard));
}
let mut loose = Vec::new();
for (what, term) in clauses {
term.walk(&mut |term| match &term.kind {
TermKind::Var(name) if !bound.contains(name.as_str()) => {
let said = format!("`{name}` is used in {what} and the pattern never bound it");
loose.push((term, said));
}
TermKind::App { head, .. } if head == RESULT && what != "the specification" => {
let said = format!("`(result)` belongs in the specification, not in {what}");
loose.push((term, said));
}
_ => {}
});
}
found.extend(loose);
for (term, message) in found {
errors.push(Error { path: path.to_owned(), line: term.line, column: term.column, message });
}
}