use crate::ast::{AtomicFormula, FunctionTerm, Name, Term, TypedGroup, TypedList, Variable};
use crate::error::ParseError;
use super::cursor::Parser;
pub(super) fn parse_typed_list_names(p: &mut Parser) -> Result<TypedList<Name>, ParseError> {
parse_typed_list(p, |p| p.expect_symbol())
}
pub(super) fn parse_typed_list_vars(p: &mut Parser) -> Result<TypedList<Variable>, ParseError> {
parse_typed_list(p, |p| p.expect_variable())
}
pub(super) fn parse_typed_list<T, F>(
p: &mut Parser,
mut item_fn: F,
) -> Result<Vec<TypedGroup<T>>, ParseError>
where
T: serde::Serialize,
F: FnMut(&mut Parser) -> Result<T, ParseError>,
{
let mut groups: Vec<TypedGroup<T>> = Vec::new();
let mut pending: Vec<T> = Vec::new();
while !p.at_rparen() && p.peek().is_some() {
if p.at_symbol("-") {
p.advance()?;
let type_name = parse_type_name(p)?;
groups.push(TypedGroup {
items: std::mem::take(&mut pending),
type_name: Some(type_name),
});
continue;
}
if let Some(tok) = p.peek() {
if let crate::lexer::TokenKind::Symbol(s) = &tok.kind {
if s.starts_with('-') && s.len() > 1 && !pending.is_empty() {
let type_name = s[1..].to_string();
p.advance()?;
groups.push(TypedGroup {
items: std::mem::take(&mut pending),
type_name: Some(type_name),
});
continue;
}
}
}
let item = item_fn(p)?;
pending.push(item);
}
if !pending.is_empty() {
groups.push(TypedGroup {
items: pending,
type_name: None,
});
}
Ok(groups)
}
pub(super) fn parse_type_name(p: &mut Parser) -> Result<Name, ParseError> {
if p.at_lparen() {
p.expect_lparen()?;
p.expect_symbol_eq("either")?;
let mut types = Vec::new();
while !p.at_rparen() {
types.push(p.expect_symbol()?);
}
p.expect_rparen()?;
Ok(format!("either:{}", types.join(":")))
} else {
p.expect_symbol()
}
}
pub(super) fn parse_term(p: &mut Parser) -> Result<Term, ParseError> {
let tok = p.advance()?;
match &tok.kind {
crate::lexer::TokenKind::Symbol(s) => Ok(Term::Name(s.clone())),
crate::lexer::TokenKind::Variable(v) => Ok(Term::Variable(v.clone())),
_ => Err(ParseError::new(
format!("expected term (name or variable), got {:?}", tok.kind),
tok.span,
)),
}
}
pub(super) fn parse_term_name_only(p: &mut Parser) -> Result<Term, ParseError> {
let tok = p.advance()?;
match &tok.kind {
crate::lexer::TokenKind::Symbol(s) => Ok(Term::Name(s.clone())),
_ => Err(ParseError::new(
format!("expected name, got {:?}", tok.kind),
tok.span,
)),
}
}
pub(super) fn parse_atomic_formula(p: &mut Parser) -> Result<AtomicFormula, ParseError> {
p.expect_lparen()?;
let name = p.expect_symbol()?;
let mut args = Vec::new();
while !p.at_rparen() {
args.push(parse_term(p)?);
}
p.expect_rparen()?;
Ok(AtomicFormula { name, args })
}
pub(super) fn parse_function_term(p: &mut Parser) -> Result<FunctionTerm, ParseError> {
if p.at_lparen() {
p.expect_lparen()?;
let name = p.expect_symbol()?;
let mut args = Vec::new();
while !p.at_rparen() {
args.push(parse_term(p)?);
}
p.expect_rparen()?;
Ok(FunctionTerm { name, args })
} else {
let name = p.expect_symbol()?;
Ok(FunctionTerm {
name,
args: Vec::new(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::lexer::tokenize;
use crate::parser::cursor::Parser;
#[test]
fn typed_lists_support_dash_prefixed_types_and_either() {
let tokens = tokenize("a b -thing c - (either x y))").unwrap();
let mut parser = Parser::new(&tokens);
let groups = parse_typed_list_names(&mut parser).unwrap();
assert_eq!(groups[0].type_name.as_deref(), Some("thing"));
assert_eq!(groups[1].type_name.as_deref(), Some("either:x:y"));
let tokens = tokenize("?a ?b ?c").unwrap();
let mut parser = Parser::new(&tokens);
let groups = parse_typed_list_vars(&mut parser).unwrap();
assert_eq!(groups[0].items.len(), 3);
assert!(groups[0].type_name.is_none());
}
#[test]
fn term_parsers_report_invalid_tokens() {
let tokens = tokenize(")").unwrap();
let mut parser = Parser::new(&tokens);
assert!(parse_term(&mut parser).is_err());
let tokens = tokenize("?x").unwrap();
let mut parser = Parser::new(&tokens);
assert!(parse_term_name_only(&mut parser).is_err());
}
#[test]
fn function_term_supports_bare_zero_arity_name() {
let tokens = tokenize("total-cost").unwrap();
let mut parser = Parser::new(&tokens);
let function = parse_function_term(&mut parser).unwrap();
assert_eq!(function.name, "total-cost");
assert!(function.args.is_empty());
}
}