#[cfg(not(feature = "std"))]
use alloc::{boxed::Box, string::String, vec::Vec};
#[cfg(not(feature = "std"))]
use alloc::format;
use nom::{
branch::alt,
bytes::complete::tag,
character::complete::{alpha1, alphanumeric1, char, multispace0},
combinator::{map, opt, recognize},
multi::{many0, separated_list0},
number::complete::double,
sequence::{delimited, pair},
IResult, Parser,
};
use crate::ast::{BinOp, Expr};
use crate::error::ParseError;
fn number(input: &str) -> IResult<&str, Expr> {
map(double, Expr::Number).parse(input)
}
fn ident(input: &str) -> IResult<&str, String> {
map(
recognize(pair(
alt((alpha1, tag("_"))),
many0(alt((alphanumeric1, tag("_")))),
)),
String::from,
)
.parse(input)
}
fn function_call(input: &str) -> IResult<&str, Expr> {
let (input, name) = ident(input)?;
let (input, _) = multispace0.parse(input)?;
let (input, _) = char('(').parse(input)?;
let (input, _) = multispace0.parse(input)?;
let (input, args) =
separated_list0(delimited(multispace0, char(','), multispace0), expr).parse(input)?;
let (input, _) = multispace0.parse(input)?;
let (input, _) = char(')').parse(input)?;
Ok((input, Expr::FunctionCall { name, args }))
}
fn variable(input: &str) -> IResult<&str, Expr> {
map(ident, |name| {
if name == "_" {
Expr::CurrentValue
} else if name == "pi" {
Expr::FunctionCall {
name: String::from("pi"),
args: Vec::new(),
}
} else if name == "e" {
Expr::FunctionCall {
name: String::from("e"),
args: Vec::new(),
}
} else {
Expr::Variable(name)
}
})
.parse(input)
}
fn primary(input: &str) -> IResult<&str, Expr> {
alt((
delimited((multispace0, char('(')), expr, (multispace0, char(')'))),
number,
function_call,
variable,
))
.parse(input)
}
fn unary(input: &str) -> IResult<&str, Expr> {
let (input, _) = multispace0.parse(input)?;
let (input, neg) = opt(char('-')).parse(input)?;
let (input, _) = multispace0.parse(input)?;
let (input, e) = primary(input)?;
Ok((
input,
match neg {
Some(_) => Expr::UnaryMinus(Box::new(e)),
None => e,
},
))
}
fn power(input: &str) -> IResult<&str, Expr> {
let (input, base) = unary(input)?;
let (input, _) = multispace0.parse(input)?;
let (input, exp) = opt(|i| {
let (i, _) = char('^').parse(i)?;
let (i, _) = multispace0.parse(i)?;
power(i) })
.parse(input)?;
Ok((
input,
match exp {
Some(e) => Expr::BinaryOp {
op: BinOp::Pow,
left: Box::new(base),
right: Box::new(e),
},
None => base,
},
))
}
fn term(input: &str) -> IResult<&str, Expr> {
let (input, first) = power(input)?;
let (input, rest) = many0((
delimited(
multispace0,
alt((char('*'), char('/'), char('%'))),
multispace0,
),
power,
))
.parse(input)?;
Ok((
input,
rest.into_iter().fold(first, |acc, (op_char, val)| {
let op = match op_char {
'*' => BinOp::Mul,
'/' => BinOp::Div,
'%' => BinOp::Mod,
_ => unreachable!(),
};
Expr::BinaryOp {
op,
left: Box::new(acc),
right: Box::new(val),
}
}),
))
}
fn expr(input: &str) -> IResult<&str, Expr> {
let (input, first) = term(input)?;
let (input, rest) = many0((
delimited(multispace0, alt((char('+'), char('-'))), multispace0),
term,
))
.parse(input)?;
Ok((
input,
rest.into_iter().fold(first, |acc, (op_char, val)| {
let op = match op_char {
'+' => BinOp::Add,
'-' => BinOp::Sub,
_ => unreachable!(),
};
Expr::BinaryOp {
op,
left: Box::new(acc),
right: Box::new(val),
}
}),
))
}
pub fn parse(input: &str) -> Result<Expr, ParseError> {
match expr(input.trim()) {
Ok(("", expr)) => Ok(expr),
Ok((remaining, _)) => Err(ParseError::UnexpectedTrailingInput(String::from(remaining))),
Err(e) => Err(ParseError::InvalidSyntax(format!("{:?}", e))),
}
}