add expression evaluation and dynval
This commit is contained in:
parent
3bb2e6516e
commit
d8ffd3153d
12 changed files with 613 additions and 62 deletions
|
@ -4,7 +4,7 @@ fn main() {
|
||||||
let input = "12 + \"hi\" * foo ) ? bar == baz : false";
|
let input = "12 + \"hi\" * foo ) ? bar == baz : false";
|
||||||
|
|
||||||
let _ = files.add("foo.eww", input);
|
let _ = files.add("foo.eww", input);
|
||||||
let ast = simplexpr::parse_string(input);
|
let ast = simplexpr::parser::parse_string(input);
|
||||||
match ast {
|
match ast {
|
||||||
Ok(ast) => {
|
Ok(ast) => {
|
||||||
println!("{:?}", ast);
|
println!("{:?}", ast);
|
||||||
|
|
0
src/EvalError.rs
Normal file
0
src/EvalError.rs
Normal file
16
src/ast.rs
16
src/ast.rs
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::dynval::DynVal;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
@ -42,7 +43,7 @@ pub enum UnaryOp {
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub enum SimplExpr {
|
pub enum SimplExpr {
|
||||||
Literal(Span, String),
|
Literal(Span, DynVal),
|
||||||
VarRef(Span, String),
|
VarRef(Span, String),
|
||||||
BinOp(Span, Box<SimplExpr>, BinOp, Box<SimplExpr>),
|
BinOp(Span, Box<SimplExpr>, BinOp, Box<SimplExpr>),
|
||||||
UnaryOp(Span, UnaryOp, 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 {
|
impl std::fmt::Debug for SimplExpr {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
|
184
src/dynval.rs
Normal file
184
src/dynval.rs
Normal 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] += ⁢
|
||||||
|
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");
|
||||||
|
//}
|
||||||
|
}
|
52
src/error.rs
52
src/error.rs
|
@ -1,17 +1,24 @@
|
||||||
use crate::{ast::Span, lexer};
|
use crate::{ast::Span, dynval, parser::lexer};
|
||||||
use codespan_reporting::diagnostic;
|
use codespan_reporting::diagnostic;
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
#[error("Parse error: {source}")]
|
||||||
ParseError { source: lalrpop_util::ParseError<usize, lexer::Token, lexer::LexicalError> },
|
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 {
|
#[error(transparent)]
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
Eval(crate::eval::EvalError),
|
||||||
match self {
|
|
||||||
Self::ParseError { source } => write!(f, "Parse error: {}", source),
|
#[error(transparent)]
|
||||||
}
|
Other(#[from] Box<dyn std::error::Error>),
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Error {
|
impl Error {
|
||||||
|
@ -22,6 +29,9 @@ impl Error {
|
||||||
pub fn get_span(&self) -> Option<Span> {
|
pub fn get_span(&self) -> Option<Span> {
|
||||||
match self {
|
match self {
|
||||||
Self::ParseError { source } => get_parse_error_span(source),
|
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> {
|
fn get_parse_error_span(err: &lalrpop_util::ParseError<usize, lexer::Token, lexer::LexicalError>) -> Option<Span> {
|
||||||
match err {
|
match err {
|
||||||
lalrpop_util::ParseError::InvalidToken { location } => Some(Span(*location, *location)),
|
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,
|
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
195
src/eval.rs
Normal 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())),
|
||||||
|
}
|
||||||
|
}
|
51
src/lib.rs
51
src/lib.rs
|
@ -1,50 +1,11 @@
|
||||||
|
#![feature(box_patterns)]
|
||||||
|
#![feature(box_syntax)]
|
||||||
|
#![feature(try_blocks)]
|
||||||
pub mod ast;
|
pub mod ast;
|
||||||
pub mod dynval;
|
pub mod dynval;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
mod lalrpop_helpers;
|
pub mod eval;
|
||||||
mod lexer;
|
pub mod parser;
|
||||||
use ast::SimplExpr;
|
|
||||||
use error::{Error, Result};
|
|
||||||
use lalrpop_util::lalrpop_mod;
|
use lalrpop_util::lalrpop_mod;
|
||||||
|
|
||||||
lalrpop_mod!(pub parser);
|
lalrpop_mod!(pub simplexpr_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]",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
47
src/parser/mod.rs
Normal file
47
src/parser/mod.rs
Normal 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]",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
use crate::ast::{SimplExpr::{self, *}, Span, BinOp::*, UnaryOp::*};
|
use crate::ast::{SimplExpr::{self, *}, Span, BinOp::*, UnaryOp::*};
|
||||||
use crate::lexer::{Token, LexicalError};
|
use crate::parser::lexer::{Token, LexicalError};
|
||||||
use crate::lalrpop_helpers::*;
|
use crate::parser::lalrpop_helpers::*;
|
||||||
|
|
||||||
|
|
||||||
grammar;
|
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> "[" <index: ExprReset> "]" <r:@R> => JsonAccess(Span(l, r), b(value), b(index)),
|
||||||
|
|
||||||
<l:@L> <value:Expr> "." <lit_l:@L> <index:"identifier"> <r:@R> => {
|
<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")]
|
#[precedence(level="2")] #[assoc(side="right")]
|
||||||
|
@ -103,10 +103,10 @@ pub Expr: SimplExpr = {
|
||||||
ExprReset = <Expr>;
|
ExprReset = <Expr>;
|
||||||
|
|
||||||
Literal: SimplExpr = {
|
Literal: SimplExpr = {
|
||||||
<l:@L> <x:StrLit> <r:@R> => Literal(Span(l, r), x),
|
<l:@L> <x:StrLit> <r:@R> => Literal(Span(l, r), x.into()),
|
||||||
<l:@L> <x:"number"> <r:@R> => Literal(Span(l, r), x.to_string()),
|
<l:@L> <x:"number"> <r:@R> => Literal(Span(l, r), x.into()),
|
||||||
<l:@L> "true" <r:@R> => Literal(Span(l, r), "true".to_string()),
|
<l:@L> "true" <r:@R> => Literal(Span(l, r), "true".into()),
|
||||||
<l:@L> "false" <r:@R> => Literal(Span(l, r), "false".to_string()),
|
<l:@L> "false" <r:@R> => Literal(Span(l, r), "false".into()),
|
||||||
}
|
}
|
||||||
|
|
||||||
StrLit: String = {
|
StrLit: String = {
|
114
src/simplexpr_parser.lalrpop
Normal file
114
src/simplexpr_parser.lalrpop
Normal 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(),
|
||||||
|
};
|
Loading…
Add table
Reference in a new issue