add expression evaluation and dynval

This commit is contained in:
elkowar 2021-07-16 17:39:35 +02:00
parent 3bb2e6516e
commit d8ffd3153d
No known key found for this signature in database
GPG key ID: E321AD71B1D1F27F
12 changed files with 613 additions and 62 deletions

View file

@ -4,7 +4,7 @@ fn main() {
let input = "12 + \"hi\" * foo ) ? bar == baz : false";
let _ = files.add("foo.eww", input);
let ast = simplexpr::parse_string(input);
let ast = simplexpr::parser::parse_string(input);
match ast {
Ok(ast) => {
println!("{:?}", ast);

0
src/EvalError.rs Normal file
View file

View file

@ -1,3 +1,4 @@
use crate::dynval::DynVal;
use itertools::Itertools;
use serde::{Deserialize, Serialize};
@ -42,7 +43,7 @@ pub enum UnaryOp {
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub enum SimplExpr {
Literal(Span, String),
Literal(Span, DynVal),
VarRef(Span, String),
BinOp(Span, Box<SimplExpr>, BinOp, Box<SimplExpr>),
UnaryOp(Span, UnaryOp, Box<SimplExpr>),
@ -66,6 +67,19 @@ impl std::fmt::Display for SimplExpr {
}
}
}
impl SimplExpr {
pub fn span(&self) -> Span {
match self {
SimplExpr::Literal(span, _) => *span,
SimplExpr::VarRef(span, _) => *span,
SimplExpr::BinOp(span, ..) => *span,
SimplExpr::UnaryOp(span, ..) => *span,
SimplExpr::IfElse(span, ..) => *span,
SimplExpr::JsonAccess(span, ..) => *span,
SimplExpr::FunctionCall(span, ..) => *span,
}
}
}
impl std::fmt::Debug for SimplExpr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {

184
src/dynval.rs Normal file
View file

@ -0,0 +1,184 @@
use crate::ast::Span;
use itertools::Itertools;
use serde::{Deserialize, Serialize};
use std::{convert::TryFrom, fmt, iter::FromIterator};
pub type Result<T> = std::result::Result<T, ConversionError>;
#[derive(Debug, thiserror::Error)]
#[error("Type error: Failed to turn {value} into a {target_type}")]
pub struct ConversionError {
value: DynVal,
target_type: &'static str,
source: Option<Box<dyn std::error::Error>>,
}
impl ConversionError {
fn new(value: DynVal, target_type: &'static str, source: Box<dyn std::error::Error>) -> Self {
ConversionError { value, target_type, source: Some(source) }
}
}
#[derive(Clone, Deserialize, Serialize, Default)]
pub struct DynVal(pub String, pub Option<Span>);
impl From<String> for DynVal {
fn from(s: String) -> Self {
DynVal(s, None)
}
}
impl fmt::Display for DynVal {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl fmt::Debug for DynVal {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "\"{}\"", self.0)
}
}
/// Manually implement equality, to allow for values in different formats (i.e. "1" and "1.0") to still be considered as equal.
impl std::cmp::PartialEq<Self> for DynVal {
fn eq(&self, other: &Self) -> bool {
if let (Ok(a), Ok(b)) = (self.as_f64(), other.as_f64()) {
a == b
} else {
self.0 == other.0
}
}
}
impl FromIterator<DynVal> for DynVal {
fn from_iter<T: IntoIterator<Item = DynVal>>(iter: T) -> Self {
DynVal(iter.into_iter().join(""), None)
}
}
impl std::str::FromStr for DynVal {
type Err = ConversionError;
/// parses the value, trying to turn it into a number and a boolean first,
/// before deciding that it is a string.
fn from_str(s: &str) -> Result<Self> {
Ok(DynVal::from_string(s.to_string()))
}
}
macro_rules! impl_try_from {
(impl From<$typ:ty> {
$(for $for:ty => |$arg:ident| $code:expr);*;
}) => {
$(impl TryFrom<$typ> for $for {
type Error = ConversionError;
fn try_from($arg: $typ) -> std::result::Result<Self, Self::Error> { $code }
})*
};
}
macro_rules! impl_primval_from {
($($t:ty),*) => {
$(impl From<$t> for DynVal {
fn from(x: $t) -> Self { DynVal(x.to_string(), None) }
})*
};
}
impl_try_from!(impl From<DynVal> {
for String => |x| x.as_string();
for f64 => |x| x.as_f64();
for i32 => |x| x.as_i32();
for bool => |x| x.as_bool();
for Vec<String> => |x| x.as_vec();
});
impl_primval_from!(bool, i32, u32, f32, u8, f64, &str);
impl From<&serde_json::Value> for DynVal {
fn from(v: &serde_json::Value) -> Self {
DynVal(
v.as_str()
.map(|x| x.to_string())
.or_else(|| serde_json::to_string(v).ok())
.unwrap_or_else(|| "<invalid json value>".to_string()),
None,
)
}
}
impl DynVal {
pub fn from_string(s: String) -> Self {
DynVal(s, None)
}
pub fn into_inner(self) -> String {
self.0
}
/// This will never fail
pub fn as_string(&self) -> Result<String> {
Ok(self.0.to_owned())
}
pub fn as_f64(&self) -> Result<f64> {
self.0.parse().map_err(|e| ConversionError::new(self.clone(), "f64", Box::new(e)))
}
pub fn as_i32(&self) -> Result<i32> {
self.0.parse().map_err(|e| ConversionError::new(self.clone(), "i32", Box::new(e)))
}
pub fn as_bool(&self) -> Result<bool> {
self.0.parse().map_err(|e| ConversionError::new(self.clone(), "bool", Box::new(e)))
}
pub fn as_vec(&self) -> Result<Vec<String>> {
match self.0.strip_prefix('[').and_then(|x| x.strip_suffix(']')) {
Some(content) => {
let mut items: Vec<String> = content.split(',').map(|x: &str| x.to_string()).collect();
let mut removed = 0;
for times_ran in 0..items.len() {
// escapes `,` if there's a `\` before em
if items[times_ran - removed].ends_with('\\') {
items[times_ran - removed].pop();
let it = items.remove((times_ran + 1) - removed);
items[times_ran - removed] += ",";
items[times_ran - removed] += &it;
removed += 1;
}
}
Ok(items)
}
None => Err(ConversionError { value: self.clone(), target_type: "vec", source: None }),
}
}
pub fn as_json_value(&self) -> Result<serde_json::Value> {
serde_json::from_str::<serde_json::Value>(&self.0)
.map_err(|e| ConversionError::new(self.clone(), "json-value", Box::new(e)))
}
}
#[cfg(test)]
mod test {
// use super::*;
// use pretty_assertions::assert_eq;
//#[test]
// fn test_parse_vec() {
// assert_eq!(vec![""], parse_vec("[]".to_string()).unwrap(), "should be able to parse empty lists");
// assert_eq!(vec!["hi"], parse_vec("[hi]".to_string()).unwrap(), "should be able to parse single element list");
// assert_eq!(
// vec!["hi", "ho", "hu"],
// parse_vec("[hi,ho,hu]".to_string()).unwrap(),
//"should be able to parse three element list"
//);
// assert_eq!(vec!["hi,ho"], parse_vec("[hi\\,ho]".to_string()).unwrap(), "should be able to parse list with escaped comma");
// assert_eq!(
// vec!["hi,ho", "hu"],
// parse_vec("[hi\\,ho,hu]".to_string()).unwrap(),
//"should be able to parse two element list with escaped comma"
//);
// assert!(parse_vec("".to_string()).is_err(), "Should fail when parsing empty string");
// assert!(parse_vec("[a,b".to_string()).is_err(), "Should fail when parsing unclosed list");
// assert!(parse_vec("a]".to_string()).is_err(), "Should fail when parsing unopened list");
//}
}

View file

@ -1,17 +1,24 @@
use crate::{ast::Span, lexer};
use crate::{ast::Span, dynval, parser::lexer};
use codespan_reporting::diagnostic;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("Parse error: {source}")]
ParseError { source: lalrpop_util::ParseError<usize, lexer::Token, lexer::LexicalError> },
}
#[error("Conversion error: {source}")]
ConversionError {
#[from]
source: dynval::ConversionError,
},
#[error("At: {0}: {1}")]
Spanned(Span, Box<dyn std::error::Error>),
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ParseError { source } => write!(f, "Parse error: {}", source),
}
}
#[error(transparent)]
Eval(crate::eval::EvalError),
#[error(transparent)]
Other(#[from] Box<dyn std::error::Error>),
}
impl Error {
@ -22,6 +29,9 @@ impl Error {
pub fn get_span(&self) -> Option<Span> {
match self {
Self::ParseError { source } => get_parse_error_span(source),
Self::Spanned(span, _) => Some(*span),
Self::Eval(err) => err.span(),
_ => None,
}
}
@ -35,6 +45,23 @@ impl Error {
}
}
pub trait ErrorExt {
fn at(self, span: Span) -> Error;
}
impl ErrorExt for Box<dyn std::error::Error> {
fn at(self, span: Span) -> Error {
Error::Spanned(span, self)
}
}
pub trait ResultExt<T> {
fn at(self, span: Span) -> std::result::Result<T, Error>;
}
impl<T, E: std::error::Error + 'static> ResultExt<T> for std::result::Result<T, E> {
fn at(self, span: Span) -> std::result::Result<T, Error> {
self.map_err(|x| Error::Spanned(span, Box::new(x)))
}
}
fn get_parse_error_span(err: &lalrpop_util::ParseError<usize, lexer::Token, lexer::LexicalError>) -> Option<Span> {
match err {
lalrpop_util::ParseError::InvalidToken { location } => Some(Span(*location, *location)),
@ -44,3 +71,12 @@ fn get_parse_error_span(err: &lalrpop_util::ParseError<usize, lexer::Token, lexe
lalrpop_util::ParseError::User { error: _ } => None,
}
}
#[macro_export]
macro_rules! spanned {
($err:ty, $span:expr, $block:expr) => {{
let span = $span;
let result: Result<_, $err> = try { $block };
result.at(span)
}};
}

195
src/eval.rs Normal file
View file

@ -0,0 +1,195 @@
use itertools::Itertools;
use crate::{
ast::{BinOp, SimplExpr, Span, UnaryOp},
dynval::{ConversionError, DynVal},
};
use std::collections::HashMap;
#[derive(Debug, thiserror::Error)]
pub enum EvalError {
#[error("Invalid regex: {0}")]
InvalidRegex(#[from] regex::Error),
#[error("got unresolved variable {0}")]
UnresolvedVariable(VarName),
#[error("Conversion error: {0}")]
ConversionError(#[from] ConversionError),
#[error("Incorrect number of arguments given to function: {0}")]
WrongArgCount(String),
#[error("Unknown function {0}")]
UnknownFunction(String),
#[error("Unable to index into value {0}")]
CannotIndex(String),
#[error("At {0}: {1}")]
Spanned(Span, Box<EvalError>),
}
impl EvalError {
pub fn span(&self) -> Option<Span> {
match self {
EvalError::Spanned(span, _) => Some(*span),
_ => None,
}
}
pub fn at(self, span: Span) -> Self {
Self::Spanned(span, Box::new(self))
}
}
type VarName = String;
impl SimplExpr {
pub fn map_terminals_into(self, f: impl Fn(Self) -> Self) -> Self {
use SimplExpr::*;
match self {
BinOp(span, box a, op, box b) => BinOp(span, box f(a), op, box f(b)),
UnaryOp(span, op, box a) => UnaryOp(span, op, box f(a)),
IfElse(span, box a, box b, box c) => IfElse(span, box f(a), box f(b), box f(c)),
other => f(other),
}
}
/// resolve variable references in the expression. Fails if a variable cannot be resolved.
// pub fn resolve_refs(self, variables: &HashMap<VarName, DynVal>) -> Result<Self> {
// use SimplExpr::*;
// match self {
//// Literal(x) => Ok(Literal(AttrValue::from_primitive(x.resolve_fully(&variables)?))),
// Literal(x) => Ok(Literal(x)),
// VarRef(ref name) => Ok(Literal(AttrVal::from_primitive(
// variables.get(name).with_context(|| format!("Unknown variable {} referenced in {:?}", &name, &self))?.clone(),
//))),
// BinOp(box a, op, box b) => {
// Ok(BinOp(box a.resolve_refs(variables?), op, box b.resolve_refs(variables?)))
//}
// UnaryOp(op, box x) => Ok(UnaryOp(op, box x.resolve_refs(variables?))),
// IfElse(box a, box b, box c) => Ok(IfElse(
// box a.resolve_refs(variables?),
// box b.resolve_refs(variables?),
// box c.resolve_refs(variables?),
//)),
// JsonAccess(box a, box b) => {
// Ok(JsonAccess(box a.resolve_refs(variables?), box b.resolve_refs(variables?)))
//}
// FunctionCall(function_name, args) => {
// Ok(FunctionCall(function_name, args.into_iter().map(|a| a.resolve_refs(variables)).collect::<Result<_>>()?))
//}
pub fn var_refs(&self) -> Vec<&String> {
use SimplExpr::*;
match self {
Literal(..) => Vec::new(),
VarRef(_, name) => vec![name],
BinOp(_, box a, _, box b) | JsonAccess(_, box a, box b) => {
let mut refs = a.var_refs();
refs.append(&mut b.var_refs());
refs
}
UnaryOp(_, _, box x) => x.var_refs(),
IfElse(_, box a, box b, box c) => {
let mut refs = a.var_refs();
refs.append(&mut b.var_refs());
refs.append(&mut c.var_refs());
refs
}
FunctionCall(_, _, args) => args.iter().flat_map(|a| a.var_refs()).collect_vec(),
}
}
pub fn eval(self, values: &HashMap<VarName, DynVal>) -> Result<DynVal, EvalError> {
match self {
SimplExpr::Literal(_, x) => Ok(x),
SimplExpr::VarRef(span, ref name) => {
values.get(name).cloned().ok_or_else(|| EvalError::UnresolvedVariable(name.to_string()).at(span))
}
SimplExpr::BinOp(_, a, op, b) => {
let a = a.eval(values)?;
let b = b.eval(values)?;
Ok(match op {
BinOp::Equals => DynVal::from(a == b),
BinOp::NotEquals => DynVal::from(a != b),
BinOp::And => DynVal::from(a.as_bool()? && b.as_bool()?),
BinOp::Or => DynVal::from(a.as_bool()? || b.as_bool()?),
BinOp::Plus => DynVal::from(a.as_f64()? + b.as_f64()?),
BinOp::Minus => DynVal::from(a.as_f64()? - b.as_f64()?),
BinOp::Times => DynVal::from(a.as_f64()? * b.as_f64()?),
BinOp::Div => DynVal::from(a.as_f64()? / b.as_f64()?),
BinOp::Mod => DynVal::from(a.as_f64()? % b.as_f64()?),
BinOp::GT => DynVal::from(a.as_f64()? > b.as_f64()?),
BinOp::LT => DynVal::from(a.as_f64()? < b.as_f64()?),
BinOp::Elvis => DynVal::from(if a.0.is_empty() { b } else { a }),
BinOp::RegexMatch => {
let regex = regex::Regex::new(&b.as_string()?)?;
DynVal::from(regex.is_match(&a.as_string()?))
}
})
}
SimplExpr::UnaryOp(_, op, a) => {
let a = a.eval(values)?;
Ok(match op {
UnaryOp::Not => DynVal::from(!a.as_bool()?),
})
}
SimplExpr::IfElse(_, cond, yes, no) => {
if cond.eval(values)?.as_bool()? {
yes.eval(values)
} else {
no.eval(values)
}
}
SimplExpr::JsonAccess(span, val, index) => {
let val = val.eval(values)?;
let index = index.eval(values)?;
match val.as_json_value()? {
serde_json::Value::Array(val) => {
let index = index.as_i32()?;
let indexed_value = val.get(index as usize).unwrap_or(&serde_json::Value::Null);
Ok(DynVal::from(indexed_value))
}
serde_json::Value::Object(val) => {
let indexed_value = val
.get(&index.as_string()?)
.or_else(|| val.get(&index.as_i32().ok()?.to_string()))
.unwrap_or(&serde_json::Value::Null);
Ok(DynVal::from(indexed_value))
}
_ => Err(EvalError::CannotIndex(format!("{}", val)).at(span)),
}
}
SimplExpr::FunctionCall(span, function_name, args) => {
let args = args.into_iter().map(|a| a.eval(values)).collect::<Result<_, EvalError>>()?;
call_expr_function(&function_name, args).map_err(|e| e.at(span))
}
}
}
}
fn call_expr_function(name: &str, args: Vec<DynVal>) -> Result<DynVal, EvalError> {
match name {
"round" => match args.as_slice() {
[num, digits] => {
let num = num.as_f64()?;
let digits = digits.as_i32()?;
Ok(DynVal::from(format!("{:.1$}", num, digits as usize)))
}
_ => Err(EvalError::WrongArgCount(name.to_string())),
},
"replace" => match args.as_slice() {
[string, pattern, replacement] => {
let string = string.as_string()?;
let pattern = regex::Regex::new(&pattern.as_string()?)?;
let replacement = replacement.as_string()?;
Ok(DynVal::from(pattern.replace_all(&string, replacement.replace("$", "$$").replace("\\", "$")).into_owned()))
}
_ => Err(EvalError::WrongArgCount(name.to_string())),
},
_ => Err(EvalError::UnknownFunction(name.to_string())),
}
}

View file

@ -1,50 +1,11 @@
#![feature(box_patterns)]
#![feature(box_syntax)]
#![feature(try_blocks)]
pub mod ast;
pub mod dynval;
pub mod error;
mod lalrpop_helpers;
mod lexer;
use ast::SimplExpr;
use error::{Error, Result};
pub mod eval;
pub mod parser;
use lalrpop_util::lalrpop_mod;
lalrpop_mod!(pub parser);
pub fn parse_string(s: &str) -> Result<SimplExpr> {
let lexer = lexer::Lexer::new(s);
let parser = parser::ExprParser::new();
Ok(parser.parse(lexer).map_err(|e| Error::from_parse_error(e))?)
}
#[cfg(test)]
mod tests {
macro_rules! test_parser {
($($text:literal),* $(,)?) => {{
let p = crate::parser::ExprParser::new();
use crate::lexer::Lexer;
::insta::with_settings!({sort_maps => true}, {
$(
::insta::assert_debug_snapshot!(p.parse(Lexer::new($text)));
)*
});
}}
}
#[test]
fn test() {
test_parser!(
"1",
"2 + 5",
"2 * 5 + 1 * 1 + 3",
"(1 + 2) * 2",
"1 + true ? 2 : 5",
"1 + true ? 2 : 5 + 2",
"1 + (true ? 2 : 5) + 2",
"foo(1, 2)",
"! false || ! true",
"\"foo\" + 12.4",
"hi[\"ho\"]",
"foo.bar.baz",
"foo.bar[2 + 2] * asdf[foo.bar]",
);
}
}
lalrpop_mod!(pub simplexpr_parser);

47
src/parser/mod.rs Normal file
View file

@ -0,0 +1,47 @@
pub mod lalrpop_helpers;
pub mod lexer;
use crate::{
ast::SimplExpr,
error::{Error, Result},
};
pub fn parse_string(s: &str) -> Result<SimplExpr> {
let lexer = lexer::Lexer::new(s);
let parser = crate::simplexpr_parser::ExprParser::new();
Ok(parser.parse(lexer).map_err(|e| Error::from_parse_error(e))?)
}
#[cfg(test)]
mod tests {
macro_rules! test_parser {
($($text:literal),* $(,)?) => {{
let p = crate::simplexpr_parser::ExprParser::new();
use crate::parser::lexer::Lexer;
::insta::with_settings!({sort_maps => true}, {
$(
::insta::assert_debug_snapshot!(p.parse(Lexer::new($text)));
)*
});
}}
}
#[test]
fn test() {
test_parser!(
"1",
"2 + 5",
"2 * 5 + 1 * 1 + 3",
"(1 + 2) * 2",
"1 + true ? 2 : 5",
"1 + true ? 2 : 5 + 2",
"1 + (true ? 2 : 5) + 2",
"foo(1, 2)",
"! false || ! true",
"\"foo\" + 12.4",
"hi[\"ho\"]",
"foo.bar.baz",
"foo.bar[2 + 2] * asdf[foo.bar]",
);
}
}

View file

@ -1,7 +1,7 @@
use crate::ast::{SimplExpr::{self, *}, Span, BinOp::*, UnaryOp::*};
use crate::lexer::{Token, LexicalError};
use crate::lalrpop_helpers::*;
use crate::parser::lexer::{Token, LexicalError};
use crate::parser::lalrpop_helpers::*;
grammar;
@ -67,7 +67,7 @@ pub Expr: SimplExpr = {
<l:@L> <value:Expr> "[" <index: ExprReset> "]" <r:@R> => JsonAccess(Span(l, r), b(value), b(index)),
<l:@L> <value:Expr> "." <lit_l:@L> <index:"identifier"> <r:@R> => {
JsonAccess(Span(l, r), b(value), b(Literal(Span(lit_l, r), index)))
JsonAccess(Span(l, r), b(value), b(Literal(Span(lit_l, r), index.into())))
},
#[precedence(level="2")] #[assoc(side="right")]
@ -103,10 +103,10 @@ pub Expr: SimplExpr = {
ExprReset = <Expr>;
Literal: SimplExpr = {
<l:@L> <x:StrLit> <r:@R> => Literal(Span(l, r), x),
<l:@L> <x:"number"> <r:@R> => Literal(Span(l, r), x.to_string()),
<l:@L> "true" <r:@R> => Literal(Span(l, r), "true".to_string()),
<l:@L> "false" <r:@R> => Literal(Span(l, r), "false".to_string()),
<l:@L> <x:StrLit> <r:@R> => Literal(Span(l, r), x.into()),
<l:@L> <x:"number"> <r:@R> => Literal(Span(l, r), x.into()),
<l:@L> "true" <r:@R> => Literal(Span(l, r), "true".into()),
<l:@L> "false" <r:@R> => Literal(Span(l, r), "false".into()),
}
StrLit: String = {

View file

@ -0,0 +1,114 @@
use crate::ast::{SimplExpr::{self, *}, Span, BinOp::*, UnaryOp::*};
use crate::parser::lexer::{Token, LexicalError};
use crate::parser::lalrpop_helpers::*;
grammar;
extern {
type Location = usize;
type Error = LexicalError;
enum Token {
"+" => Token::Plus,
"-" => Token::Minus,
"*" => Token::Times,
"/" => Token::Div,
"%" => Token::Mod,
"==" => Token::Equals,
"!=" => Token::NotEquals,
"&&" => Token::And,
"||" => Token::Or,
">" => Token::GT,
"<" => Token::LT,
"?:" => Token::Elvis,
"=~" => Token::RegexMatch,
"!" => Token::Not,
"," => Token::Comma,
"?" => Token::Question,
":" => Token::Colon,
"(" => Token::LPren,
")" => Token::RPren,
"[" => Token::LBrack,
"]" => Token::RBrack,
"." => Token::Dot,
"true" => Token::True,
"false" => Token::False,
"identifier" => Token::Ident(<String>),
"number" => Token::NumLit(<String>),
"string" => Token::StrLit(<String>),
}
}
Comma<T>: Vec<T> = {
<mut v:(<T> ",")*> <e:T?> => match e {
None => v,
Some(e) => {
v.push(e);
v
}
}
};
pub Expr: SimplExpr = {
#[precedence(level="0")]
<Literal>,
<l:@L> <ident:"identifier"> <r:@R> => VarRef(Span(l, r), ident.to_string()),
"(" <ExprReset> ")",
#[precedence(level="1")] #[assoc(side="right")]
<l:@L> <ident:"identifier"> "(" <args: Comma<ExprReset>> ")" <r:@R> => FunctionCall(Span(l, r), ident, args),
<l:@L> <value:Expr> "[" <index: ExprReset> "]" <r:@R> => JsonAccess(Span(l, r), b(value), b(index)),
<l:@L> <value:Expr> "." <lit_l:@L> <index:"identifier"> <r:@R> => {
JsonAccess(Span(l, r), b(value), b(Literal(Span(lit_l, r), index.into())))
},
#[precedence(level="2")] #[assoc(side="right")]
<l:@L> "!" <e:Expr> <r:@R> => UnaryOp(Span(l, r), Not, b(e)),
#[precedence(level="3")] #[assoc(side="left")]
<l:@L> <le:Expr> "*" <re:Expr> <r:@R> => BinOp(Span(l, r), b(le), Times, b(re)),
<l:@L> <le:Expr> "/" <re:Expr> <r:@R> => BinOp(Span(l, r), b(le), Div, b(re)),
<l:@L> <le:Expr> "%" <re:Expr> <r:@R> => BinOp(Span(l, r), b(le), Mod, b(re)),
#[precedence(level="4")] #[assoc(side="left")]
<l:@L> <le:Expr> "+" <re:Expr> <r:@R> => BinOp(Span(l, r), b(le), Plus, b(re)),
<l:@L> <le:Expr> "-" <re:Expr> <r:@R> => BinOp(Span(l, r), b(le), Minus, b(re)),
#[precedence(level="5")] #[assoc(side="left")]
<l:@L> <le:Expr> "==" <re:Expr> <r:@R> => BinOp(Span(l, r), b(le), Equals, b(re)),
<l:@L> <le:Expr> "!=" <re:Expr> <r:@R> => BinOp(Span(l, r), b(le), NotEquals, b(re)),
<l:@L> <le:Expr> "<" <re:Expr> <r:@R> => BinOp(Span(l, r), b(le), GT, b(re)),
<l:@L> <le:Expr> ">" <re:Expr> <r:@R> => BinOp(Span(l, r), b(le), LT, b(re)),
<l:@L> <le:Expr> "=~" <re:Expr> <r:@R> => BinOp(Span(l, r), b(le), RegexMatch, b(re)),
#[precedence(level="6")] #[assoc(side="left")]
<l:@L> <le:Expr> "&&" <re:Expr> <r:@R> => BinOp(Span(l, r), b(le), And, b(re)),
<l:@L> <le:Expr> "||" <re:Expr> <r:@R> => BinOp(Span(l, r), b(le), Or, b(re)),
<l:@L> <le:Expr> "?:" <re:Expr> <r:@R> => BinOp(Span(l, r), b(le), Elvis, b(re)),
#[precedence(level="7")] #[assoc(side="right")]
<l:@L> <cond:Expr> "?" <then:ExprReset> ":" <els:Expr> <r:@R> => {
IfElse(Span(l, r), b(cond), b(then), b(els))
},
};
ExprReset = <Expr>;
Literal: SimplExpr = {
<l:@L> <x:StrLit> <r:@R> => Literal(Span(l, r), x.into()),
<l:@L> <x:"number"> <r:@R> => Literal(Span(l, r), x.into()),
<l:@L> "true" <r:@R> => Literal(Span(l, r), "true".into()),
<l:@L> "false" <r:@R> => Literal(Span(l, r), "false".into()),
}
StrLit: String = {
<x:"string"> => x[1..x.len() - 1].to_owned(),
};