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 _ = 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
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 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
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;
|
||||
|
||||
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
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 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
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::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 = {
|
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