use winnow::{
ascii::{alpha1, digit1, multispace0, multispace1},
combinator::alt,
combinator::repeat,
combinator::{cut_err, opt},
combinator::{delimited, preceded, terminated},
error::ContextError,
error::StrContext,
prelude::*,
token::one_of,
};
pub(crate) fn eval_from_str(src: &str) -> Result<Expr, String> {
parse_expr
.parse(src)
.map_err(|e| e.to_string())
.and_then(|exp| eval_expression(exp).ok_or_else(|| "Eval failed".to_owned()))
}
#[derive(Debug, Eq, PartialEq, Clone)]
pub(crate) enum Expr {
Constant(Atom),
Application(Box<Expr>, Vec<Expr>),
If(Box<Expr>, Box<Expr>),
IfElse(Box<Expr>, Box<Expr>, Box<Expr>),
Quote(Vec<Expr>),
}
#[derive(Debug, Eq, PartialEq, Clone)]
pub(crate) enum Atom {
Num(i32),
Keyword(String),
Boolean(bool),
BuiltIn(BuiltIn),
}
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub(crate) enum BuiltIn {
Plus,
Minus,
Times,
Divide,
Equal,
Not,
}
fn parse_expr(i: &mut &'_ str) -> ModalResult<Expr> {
preceded(
multispace0,
alt((parse_constant, parse_application, parse_if, parse_quote)),
)
.parse_next(i)
}
fn parse_constant(i: &mut &'_ str) -> ModalResult<Expr> {
parse_atom.map(Expr::Constant).parse_next(i)
}
fn parse_atom(i: &mut &'_ str) -> ModalResult<Atom> {
alt((
parse_num,
parse_bool,
parse_builtin.map(Atom::BuiltIn),
parse_keyword,
))
.parse_next(i)
}
fn parse_num(i: &mut &'_ str) -> ModalResult<Atom> {
alt((
digit1.try_map(|digit_str: &str| digit_str.parse::<i32>().map(Atom::Num)),
preceded("-", digit1).map(|digit_str: &str| Atom::Num(-digit_str.parse::<i32>().unwrap())),
))
.parse_next(i)
}
fn parse_bool(i: &mut &'_ str) -> ModalResult<Atom> {
alt((
"#t".map(|_| Atom::Boolean(true)),
"#f".map(|_| Atom::Boolean(false)),
))
.parse_next(i)
}
fn parse_builtin(i: &mut &'_ str) -> ModalResult<BuiltIn> {
alt((
parse_builtin_op,
"not".map(|_| BuiltIn::Not),
))
.parse_next(i)
}
fn parse_builtin_op(i: &mut &'_ str) -> ModalResult<BuiltIn> {
let t = one_of(['+', '-', '*', '/', '=']).parse_next(i)?;
Ok(match t {
'+' => BuiltIn::Plus,
'-' => BuiltIn::Minus,
'*' => BuiltIn::Times,
'/' => BuiltIn::Divide,
'=' => BuiltIn::Equal,
_ => unreachable!(),
})
}
fn parse_keyword(i: &mut &'_ str) -> ModalResult<Atom> {
preceded(":", cut_err(alpha1))
.context(StrContext::Label("keyword"))
.map(|sym_str: &str| Atom::Keyword(sym_str.to_owned()))
.parse_next(i)
}
fn parse_application(i: &mut &'_ str) -> ModalResult<Expr> {
let application_inner = (parse_expr, repeat(0.., parse_expr))
.map(|(head, tail)| Expr::Application(Box::new(head), tail));
s_exp(application_inner).parse_next(i)
}
fn parse_if(i: &mut &'_ str) -> ModalResult<Expr> {
let if_inner = preceded(
terminated("if", multispace1),
cut_err((parse_expr, parse_expr, opt(parse_expr))),
)
.map(|(pred, true_branch, maybe_false_branch)| {
if let Some(false_branch) = maybe_false_branch {
Expr::IfElse(
Box::new(pred),
Box::new(true_branch),
Box::new(false_branch),
)
} else {
Expr::If(Box::new(pred), Box::new(true_branch))
}
})
.context(StrContext::Label("if expression"));
s_exp(if_inner).parse_next(i)
}
fn parse_quote(i: &mut &'_ str) -> ModalResult<Expr> {
preceded("'", cut_err(s_exp(repeat(0.., parse_expr))))
.context(StrContext::Label("quote"))
.map(Expr::Quote)
.parse_next(i)
}
fn s_exp<'a, O1, F>(inner: F) -> impl ModalParser<&'a str, O1, ContextError>
where
F: ModalParser<&'a str, O1, ContextError>,
{
delimited(
'(',
preceded(multispace0, inner),
cut_err(preceded(multispace0, ')')).context(StrContext::Label("closing paren")),
)
}
fn eval_expression(e: Expr) -> Option<Expr> {
match e {
Expr::Constant(_) | Expr::Quote(_) => Some(e),
Expr::If(pred, true_branch) => {
let reduce_pred = eval_expression(*pred)?;
if get_bool_from_expr(reduce_pred)? {
eval_expression(*true_branch)
} else {
None
}
}
Expr::IfElse(pred, true_branch, false_branch) => {
let reduce_pred = eval_expression(*pred)?;
if get_bool_from_expr(reduce_pred)? {
eval_expression(*true_branch)
} else {
eval_expression(*false_branch)
}
}
Expr::Application(head, tail) => {
let reduced_head = eval_expression(*head)?;
let reduced_tail = tail
.into_iter()
.map(eval_expression)
.collect::<Option<Vec<Expr>>>()?;
if let Expr::Constant(Atom::BuiltIn(bi)) = reduced_head {
Some(Expr::Constant(match bi {
BuiltIn::Plus => Atom::Num(
reduced_tail
.into_iter()
.map(get_num_from_expr)
.collect::<Option<Vec<i32>>>()?
.into_iter()
.sum(),
),
BuiltIn::Times => Atom::Num(
reduced_tail
.into_iter()
.map(get_num_from_expr)
.collect::<Option<Vec<i32>>>()?
.into_iter()
.product(),
),
BuiltIn::Equal => Atom::Boolean(
reduced_tail
.iter()
.zip(reduced_tail.iter().skip(1))
.all(|(a, b)| a == b),
),
BuiltIn::Not => {
if reduced_tail.len() != 1 {
return None;
} else {
Atom::Boolean(!get_bool_from_expr(
reduced_tail.first().cloned().unwrap(),
)?)
}
}
BuiltIn::Minus => {
Atom::Num(if let Some(first_elem) = reduced_tail.first().cloned() {
let fe = get_num_from_expr(first_elem)?;
reduced_tail
.into_iter()
.map(get_num_from_expr)
.collect::<Option<Vec<i32>>>()?
.into_iter()
.skip(1)
.fold(fe, |a, b| a - b)
} else {
Default::default()
})
}
BuiltIn::Divide => {
Atom::Num(if let Some(first_elem) = reduced_tail.first().cloned() {
let fe = get_num_from_expr(first_elem)?;
reduced_tail
.into_iter()
.map(get_num_from_expr)
.collect::<Option<Vec<i32>>>()?
.into_iter()
.skip(1)
.fold(fe, |a, b| a / b)
} else {
Default::default()
})
}
}))
} else {
None
}
}
}
}
fn get_num_from_expr(e: Expr) -> Option<i32> {
if let Expr::Constant(Atom::Num(n)) = e {
Some(n)
} else {
None
}
}
fn get_bool_from_expr(e: Expr) -> Option<bool> {
if let Expr::Constant(Atom::Boolean(b)) = e {
Some(b)
} else {
None
}
}