purrchance/src/parser.rs

125 lines
3.5 KiB
Rust

use super::*;
use std::iter::FromIterator;
use nom::{Err, error::{ErrorKind, ParseError}};
use nom::{FindSubstring, InputLength, InputTake, IResult};
use nom::branch::*;
use nom::bytes::complete::*;
use nom::combinator::*;
use nom::multi::*;
use nom::number::complete::*;
use nom::sequence::*;
fn take_until_any<T, Input, Error: ParseError<Input>>(tags: Vec<T>) -> impl Fn(Input) -> IResult<Input, Input, Error>
where
Input: InputTake + FindSubstring<T>,
T: InputLength + Clone,
{
move |i: Input| {
let min_index = tags.iter().filter_map(|tag| i.find_substring(tag.clone())).min();
let res: IResult<_, _, Error> = match min_index {
None => Err(Err::Error(Error::from_error_kind(i, ErrorKind::TakeUntil))),
Some(index) => Ok(i.take_split(index)),
};
res
}
}
fn take_all(input: &str) -> IResult<&str, &str> {
take_while(|_| true)(input)
}
fn terminal(input: &str) -> IResult<&str, Symbol> {
let (input, mut terminal_val) = verify(alt((take_until_any(vec!["[", "//", "^", "\n"]), take_all)), |s: &str| s.len() > 0)(input)?;
if !input.starts_with("[") {
terminal_val = terminal_val.trim_end();
}
Ok((input, Symbol::Terminal(String::from(terminal_val))))
}
fn nonterminal_name(input: &str) -> IResult<&str, String> {
let (input, head) = take_while_m_n(1, 1, |c: char| (c.is_alphabetic() || c == '_'))(input)?;
let (input, tail) = take_while(|c: char| (c.is_alphanumeric() || c == '_'))(input)?;
Ok((input, String::from(head.to_owned() + tail)))
}
fn nonterminal(input: &str) -> IResult<&str, Symbol> {
map(delimited(tag("["), nonterminal_name, tag("]")), Symbol::NonTerminal)(input)
}
fn rat(input: &str) -> IResult<&str, f64> {
let (input, num) = double(input)?;
let (input, _) = tag("/")(input)?;
let (input, denom) = verify(double, |&f| f != 0.0)(input)?;
Ok((input, num / denom))
}
fn weight(input: &str) -> IResult<&str, f64> {
preceded(tag("^"), alt((rat, double)))(input)
}
fn comment(input: &str) -> IResult<&str, ()> {
map(tuple((tag("//"), is_not("\n"))), |_| ())(input)
}
fn whitespace(input: &str) -> IResult<&str, ()> {
map(take_while(|c| (c == ' ' || c == '\t')), |_| ())(input)
}
fn eol(input: &str) -> IResult<&str, ()> {
map(alt((tag("\n"), all_consuming(take(0usize)))), |_| ())(input)
}
fn empty_lines(input: &str) -> IResult<&str, ()> {
map(
tuple((
many0(tuple((whitespace, opt(comment), tag("\n")))),
opt(tuple((whitespace, opt(comment), eol))),
)),
|_| ()
)(input)
}
fn expr(input: &str) -> IResult<&str, (Expr, f64)> {
let (input, (_, _, syms, _, weight, _, _, _, _)) = tuple((
empty_lines,
alt((tag(" "), tag("\t"))),
many1(alt((terminal, nonterminal))),
whitespace,
opt(weight),
whitespace,
opt(comment),
eol,
empty_lines,
))(input)?;
Ok((input, (Expr(syms), weight.unwrap_or(1.0))))
}
fn list(input: &str) -> IResult<&str, (String, List)> {
let (input, (_, name, _, _, _, exprs)) = tuple((
empty_lines,
nonterminal_name,
whitespace,
opt(comment),
tag("\n"),
many1(expr),
))(input)?;
Ok((input, (name, List(exprs))))
}
fn grammar(input: &str) -> IResult<&str, Grammar> {
let (input, lists) = many1(list)(input)?;
Ok((input, Grammar(HashMap::from_iter(lists.into_iter()))))
}
pub fn load_grammar(input: &str) -> Result<Grammar, Err<(&str, ErrorKind)>> {
all_consuming(grammar)(input).map(|(_, g)| g)
}