From d8ffd3153d951eb4d4c315cda4ba44bd438c95ae Mon Sep 17 00:00:00 2001 From: elkowar <5300871+elkowar@users.noreply.github.com> Date: Fri, 16 Jul 2021 17:39:35 +0200 Subject: [PATCH] add expression evaluation and dynval --- examples/errors.rs | 2 +- src/EvalError.rs | 0 src/ast.rs | 16 +- src/dynval.rs | 184 +++++++++++++++++ src/error.rs | 52 ++++- src/eval.rs | 195 ++++++++++++++++++ src/lib.rs | 51 +---- src/{ => parser}/lalrpop_helpers.rs | 0 src/{ => parser}/lexer.rs | 0 src/parser/mod.rs | 47 +++++ .../simplexpr_parser.lalrpop} | 14 +- src/simplexpr_parser.lalrpop | 114 ++++++++++ 12 files changed, 613 insertions(+), 62 deletions(-) create mode 100644 src/EvalError.rs create mode 100644 src/dynval.rs create mode 100644 src/eval.rs rename src/{ => parser}/lalrpop_helpers.rs (100%) rename src/{ => parser}/lexer.rs (100%) create mode 100644 src/parser/mod.rs rename src/{parser.lalrpop => parser/simplexpr_parser.lalrpop} (90%) create mode 100644 src/simplexpr_parser.lalrpop diff --git a/examples/errors.rs b/examples/errors.rs index d089959..9bd7053 100644 --- a/examples/errors.rs +++ b/examples/errors.rs @@ -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); diff --git a/src/EvalError.rs b/src/EvalError.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/ast.rs b/src/ast.rs index 9ef345a..96ab4bb 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -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, BinOp, Box), UnaryOp(Span, UnaryOp, Box), @@ -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 { diff --git a/src/dynval.rs b/src/dynval.rs new file mode 100644 index 0000000..f9352ec --- /dev/null +++ b/src/dynval.rs @@ -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 = std::result::Result; + +#[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>, +} + +impl ConversionError { + fn new(value: DynVal, target_type: &'static str, source: Box) -> Self { + ConversionError { value, target_type, source: Some(source) } + } +} + +#[derive(Clone, Deserialize, Serialize, Default)] +pub struct DynVal(pub String, pub Option); + +impl From 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 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 for DynVal { + fn from_iter>(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 { + 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 { $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 { + 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 => |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(|| "".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 { + Ok(self.0.to_owned()) + } + + pub fn as_f64(&self) -> Result { + self.0.parse().map_err(|e| ConversionError::new(self.clone(), "f64", Box::new(e))) + } + + pub fn as_i32(&self) -> Result { + self.0.parse().map_err(|e| ConversionError::new(self.clone(), "i32", Box::new(e))) + } + + pub fn as_bool(&self) -> Result { + self.0.parse().map_err(|e| ConversionError::new(self.clone(), "bool", Box::new(e))) + } + + pub fn as_vec(&self) -> Result> { + match self.0.strip_prefix('[').and_then(|x| x.strip_suffix(']')) { + Some(content) => { + let mut items: Vec = 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] += ⁢ + removed += 1; + } + } + Ok(items) + } + None => Err(ConversionError { value: self.clone(), target_type: "vec", source: None }), + } + } + + pub fn as_json_value(&self) -> Result { + serde_json::from_str::(&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"); + //} +} diff --git a/src/error.rs b/src/error.rs index 2f04c9b..b794344 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,17 +1,24 @@ -use crate::{ast::Span, lexer}; +use crate::{ast::Span, dynval, parser::lexer}; use codespan_reporting::diagnostic; pub type Result = std::result::Result; +#[derive(thiserror::Error, Debug)] pub enum Error { + #[error("Parse error: {source}")] ParseError { source: lalrpop_util::ParseError }, -} + #[error("Conversion error: {source}")] + ConversionError { + #[from] + source: dynval::ConversionError, + }, + #[error("At: {0}: {1}")] + Spanned(Span, Box), -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), } impl Error { @@ -22,6 +29,9 @@ impl Error { pub fn get_span(&self) -> Option { 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 { + fn at(self, span: Span) -> Error { + Error::Spanned(span, self) + } +} +pub trait ResultExt { + fn at(self, span: Span) -> std::result::Result; +} +impl ResultExt for std::result::Result { + fn at(self, span: Span) -> std::result::Result { + self.map_err(|x| Error::Spanned(span, Box::new(x))) + } +} + fn get_parse_error_span(err: &lalrpop_util::ParseError) -> Option { match err { lalrpop_util::ParseError::InvalidToken { location } => Some(Span(*location, *location)), @@ -44,3 +71,12 @@ fn get_parse_error_span(err: &lalrpop_util::ParseError None, } } + +#[macro_export] +macro_rules! spanned { + ($err:ty, $span:expr, $block:expr) => {{ + let span = $span; + let result: Result<_, $err> = try { $block }; + result.at(span) + }}; +} diff --git a/src/eval.rs b/src/eval.rs new file mode 100644 index 0000000..66034c2 --- /dev/null +++ b/src/eval.rs @@ -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), +} + +impl EvalError { + pub fn span(&self) -> Option { + 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) -> Result { + // 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::>()?)) + //} + + 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) -> Result { + 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::>()?; + call_expr_function(&function_name, args).map_err(|e| e.at(span)) + } + } + } +} + +fn call_expr_function(name: &str, args: Vec) -> Result { + 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())), + } +} diff --git a/src/lib.rs b/src/lib.rs index 84702b1..617a06b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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 { - 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); diff --git a/src/lalrpop_helpers.rs b/src/parser/lalrpop_helpers.rs similarity index 100% rename from src/lalrpop_helpers.rs rename to src/parser/lalrpop_helpers.rs diff --git a/src/lexer.rs b/src/parser/lexer.rs similarity index 100% rename from src/lexer.rs rename to src/parser/lexer.rs diff --git a/src/parser/mod.rs b/src/parser/mod.rs new file mode 100644 index 0000000..ab47e58 --- /dev/null +++ b/src/parser/mod.rs @@ -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 { + 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]", + ); + } +} diff --git a/src/parser.lalrpop b/src/parser/simplexpr_parser.lalrpop similarity index 90% rename from src/parser.lalrpop rename to src/parser/simplexpr_parser.lalrpop index f44a845..46dc13a 100644 --- a/src/parser.lalrpop +++ b/src/parser/simplexpr_parser.lalrpop @@ -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 = { "[" "]" => JsonAccess(Span(l, r), b(value), b(index)), "." => { - 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 = ; Literal: SimplExpr = { - => Literal(Span(l, r), x), - => Literal(Span(l, r), x.to_string()), - "true" => Literal(Span(l, r), "true".to_string()), - "false" => Literal(Span(l, r), "false".to_string()), + => Literal(Span(l, r), x.into()), + => Literal(Span(l, r), x.into()), + "true" => Literal(Span(l, r), "true".into()), + "false" => Literal(Span(l, r), "false".into()), } StrLit: String = { diff --git a/src/simplexpr_parser.lalrpop b/src/simplexpr_parser.lalrpop new file mode 100644 index 0000000..46dc13a --- /dev/null +++ b/src/simplexpr_parser.lalrpop @@ -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(), + "number" => Token::NumLit(), + "string" => Token::StrLit(), + + } +} + +Comma: Vec = { + ",")*> => match e { + None => v, + Some(e) => { + v.push(e); + v + } + } +}; + +pub Expr: SimplExpr = { + #[precedence(level="0")] + , + => VarRef(Span(l, r), ident.to_string()), + "(" ")", + + #[precedence(level="1")] #[assoc(side="right")] + "(" > ")" => FunctionCall(Span(l, r), ident, args), + "[" "]" => JsonAccess(Span(l, r), b(value), b(index)), + + "." => { + JsonAccess(Span(l, r), b(value), b(Literal(Span(lit_l, r), index.into()))) + }, + + #[precedence(level="2")] #[assoc(side="right")] + "!" => UnaryOp(Span(l, r), Not, b(e)), + + #[precedence(level="3")] #[assoc(side="left")] + "*" => BinOp(Span(l, r), b(le), Times, b(re)), + "/" => BinOp(Span(l, r), b(le), Div, b(re)), + "%" => BinOp(Span(l, r), b(le), Mod, b(re)), + + #[precedence(level="4")] #[assoc(side="left")] + "+" => BinOp(Span(l, r), b(le), Plus, b(re)), + "-" => BinOp(Span(l, r), b(le), Minus, b(re)), + + #[precedence(level="5")] #[assoc(side="left")] + "==" => BinOp(Span(l, r), b(le), Equals, b(re)), + "!=" => BinOp(Span(l, r), b(le), NotEquals, b(re)), + "<" => BinOp(Span(l, r), b(le), GT, b(re)), + ">" => BinOp(Span(l, r), b(le), LT, b(re)), + "=~" => BinOp(Span(l, r), b(le), RegexMatch, b(re)), + + #[precedence(level="6")] #[assoc(side="left")] + "&&" => BinOp(Span(l, r), b(le), And, b(re)), + "||" => BinOp(Span(l, r), b(le), Or, b(re)), + "?:" => BinOp(Span(l, r), b(le), Elvis, b(re)), + + #[precedence(level="7")] #[assoc(side="right")] + "?" ":" => { + IfElse(Span(l, r), b(cond), b(then), b(els)) + }, +}; + +ExprReset = ; + +Literal: SimplExpr = { + => Literal(Span(l, r), x.into()), + => Literal(Span(l, r), x.into()), + "true" => Literal(Span(l, r), "true".into()), + "false" => Literal(Span(l, r), "false".into()), +} + +StrLit: String = { + => x[1..x.len() - 1].to_owned(), +};