From c20b172662d8f3e627ac836c254242598f1a51f6 Mon Sep 17 00:00:00 2001 From: elkowar <5300871+elkowar@users.noreply.github.com> Date: Tue, 17 Aug 2021 11:10:48 +0200 Subject: [PATCH] Fix representation of string-interpolations by adding a dedicated concat AST variant --- crates/simplexpr/src/ast.rs | 12 +++ crates/simplexpr/src/dynval.rs | 5 +- crates/simplexpr/src/eval.rs | 73 +++++++++---------- .../simplexpr/src/parser/lalrpop_helpers.rs | 13 ++-- 4 files changed, 54 insertions(+), 49 deletions(-) diff --git a/crates/simplexpr/src/ast.rs b/crates/simplexpr/src/ast.rs index b7aa2da..57769f8 100644 --- a/crates/simplexpr/src/ast.rs +++ b/crates/simplexpr/src/ast.rs @@ -32,6 +32,7 @@ pub enum UnaryOp { #[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum SimplExpr { Literal(DynVal), + Concat(Span, Vec), VarRef(Span, VarName), BinOp(Span, Box, BinOp, Box), UnaryOp(Span, UnaryOp, Box), @@ -44,6 +45,16 @@ impl std::fmt::Display for SimplExpr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { SimplExpr::Literal(x) => write!(f, "\"{}\"", x), + SimplExpr::Concat(_, elems) => { + let text = elems + .iter() + .map(|x| match x { + SimplExpr::Literal(lit) => lit.to_string(), + other => format!("${{{}}}", other), + }) + .join(""); + write!(f, "\"{}\"", text) + } SimplExpr::VarRef(_, x) => write!(f, "{}", x), SimplExpr::BinOp(_, l, op, r) => write!(f, "({} {} {})", l, op, r), SimplExpr::UnaryOp(_, op, x) => write!(f, "{}{}", op, x), @@ -74,6 +85,7 @@ impl Spanned for SimplExpr { fn span(&self) -> Span { match self { SimplExpr::Literal(x) => x.span(), + SimplExpr::Concat(span, _) => *span, SimplExpr::VarRef(span, _) => *span, SimplExpr::BinOp(span, ..) => *span, SimplExpr::UnaryOp(span, ..) => *span, diff --git a/crates/simplexpr/src/dynval.rs b/crates/simplexpr/src/dynval.rs index 9da8e22..9ade8d8 100644 --- a/crates/simplexpr/src/dynval.rs +++ b/crates/simplexpr/src/dynval.rs @@ -119,8 +119,9 @@ impl Spanned for DynVal { } impl DynVal { - pub fn at(self, span: Span) -> Self { - DynVal(self.0, span) + pub fn at(mut self, span: Span) -> Self { + self.1 = span; + self } pub fn from_string(s: String) -> Self { diff --git a/crates/simplexpr/src/eval.rs b/crates/simplexpr/src/eval.rs index 5d02944..e3a347e 100644 --- a/crates/simplexpr/src/eval.rs +++ b/crates/simplexpr/src/eval.rs @@ -5,7 +5,7 @@ use crate::{ dynval::{ConversionError, DynVal}, }; use eww_shared_util::{Span, Spanned, VarName}; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; #[derive(Debug, thiserror::Error)] pub enum EvalError { @@ -64,6 +64,7 @@ impl SimplExpr { use SimplExpr::*; Ok(match self { BinOp(span, box a, op, box b) => BinOp(span, box a.try_map_var_refs(f)?, op, box b.try_map_var_refs(f)?), + Concat(span, elems) => Concat(span, elems.into_iter().map(|x| x.try_map_var_refs(f)).collect::>()?), UnaryOp(span, op, box a) => UnaryOp(span, op, box a.try_map_var_refs(f)?), IfElse(span, box a, box b, box c) => { IfElse(span, box a.try_map_var_refs(f)?, box b.try_map_var_refs(f)?, box c.try_map_var_refs(f)?) @@ -91,50 +92,35 @@ impl SimplExpr { /// 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(x)), - BinOp(span, box a, op, box b) => Ok(BinOp(span, box a.resolve_refs(variables)?, op, box b.resolve_refs(variables)?)), - UnaryOp(span, op, box x) => Ok(UnaryOp(span, op, box x.resolve_refs(variables)?)), - IfElse(span, box a, box b, box c) => { - Ok(IfElse(span, box a.resolve_refs(variables)?, box b.resolve_refs(variables)?, box c.resolve_refs(variables)?)) + self.try_map_var_refs(|span, name| match variables.get(&name) { + Some(value) => Ok(Literal(value.clone())), + None => { + let similar_ish = + variables.keys().filter(|key| levenshtein::levenshtein(&key.0, &name.0) < 3).cloned().collect_vec(); + Err(EvalError::UnknownVariable(name.clone(), similar_ish).at(span)) } - JsonAccess(span, box a, box b) => { - Ok(JsonAccess(span, box a.resolve_refs(variables)?, box b.resolve_refs(variables)?)) - } - FunctionCall(span, function_name, args) => Ok(FunctionCall( - span, - function_name, - args.into_iter().map(|a| a.resolve_refs(variables)).collect::>()?, - )), - VarRef(span, ref name) => match variables.get(name) { - Some(value) => Ok(Literal(value.clone())), - None => { - let similar_ish = - variables.keys().filter(|key| levenshtein::levenshtein(&key.0, &name.0) < 3).cloned().collect_vec(); - Err(EvalError::UnknownVariable(name.clone(), similar_ish).at(span)) - } - }, - } + }) } - pub fn var_refs(&self) -> Vec<&VarName> { + pub fn var_refs(&self) -> HashSet<&VarName> { use SimplExpr::*; match self { - Literal(..) => Vec::new(), - VarRef(_, name) => vec![name], + Literal(..) => HashSet::new(), + Concat(_, elems) => elems.iter().flat_map(|x| x.var_refs().into_iter()).collect(), + VarRef(_, name) => maplit::hashset! { name }, BinOp(_, box a, _, box b) | JsonAccess(_, box a, box b) => { let mut refs = a.var_refs(); - refs.append(&mut b.var_refs()); + refs.extend(b.var_refs().iter()); 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.extend(b.var_refs().iter()); + refs.extend(c.var_refs().iter()); refs } - FunctionCall(_, _, args) => args.iter().flat_map(|a| a.var_refs()).collect_vec(), + FunctionCall(_, _, args) => args.iter().flat_map(|a| a.var_refs()).collect(), } } @@ -152,6 +138,14 @@ impl SimplExpr { let span = self.span(); let value = match self { SimplExpr::Literal(x) => Ok(x.clone()), + SimplExpr::Concat(span, elems) => { + let mut output = String::new(); + for elem in elems { + let result = elem.eval(values)?; + output.push_str(&result.0); + } + Ok(DynVal(output, *span)) + } SimplExpr::VarRef(span, ref name) => { let similar_ish = values.keys().filter(|keys| levenshtein::levenshtein(&keys.0, &name.0) < 3).cloned().collect_vec(); @@ -161,10 +155,10 @@ impl SimplExpr { .ok_or_else(|| EvalError::UnknownVariable(name.clone(), similar_ish).at(*span))? .at(*span)) } - SimplExpr::BinOp(_, a, op, b) => { + SimplExpr::BinOp(span, a, op, b) => { let a = a.eval(values)?; let b = b.eval(values)?; - Ok(match op { + let dynval = match op { BinOp::Equals => DynVal::from(a == b), BinOp::NotEquals => DynVal::from(a != b), BinOp::And => DynVal::from(a.as_bool()? && b.as_bool()?), @@ -185,12 +179,13 @@ impl SimplExpr { let regex = regex::Regex::new(&b.as_string()?)?; DynVal::from(regex.is_match(&a.as_string()?)) } - }) + }; + Ok(dynval.at(*span)) } - SimplExpr::UnaryOp(_, op, a) => { + SimplExpr::UnaryOp(span, op, a) => { let a = a.eval(values)?; Ok(match op { - UnaryOp::Not => DynVal::from(!a.as_bool()?), + UnaryOp::Not => DynVal::from(!a.as_bool()?).at(*span), }) } SimplExpr::IfElse(_, cond, yes, no) => { @@ -207,21 +202,21 @@ impl SimplExpr { 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)) + Ok(DynVal::from(indexed_value).at(*span)) } 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)) + Ok(DynVal::from(indexed_value).at(*span)) } _ => 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)) + call_expr_function(&function_name, args).map(|x| x.at(*span)).map_err(|e| e.at(*span)) } }; Ok(value?.at(span)) diff --git a/crates/simplexpr/src/parser/lalrpop_helpers.rs b/crates/simplexpr/src/parser/lalrpop_helpers.rs index 19e2fa5..4815872 100644 --- a/crates/simplexpr/src/parser/lalrpop_helpers.rs +++ b/crates/simplexpr/src/parser/lalrpop_helpers.rs @@ -1,7 +1,6 @@ use eww_shared_util::Span; -use itertools::Itertools; -use crate::{ast::BinOp, dynval::DynVal, SimplExpr}; +use crate::{dynval::DynVal, SimplExpr}; use super::lexer::{LexicalError, Sp, StrLitSegment, Token}; @@ -16,7 +15,8 @@ pub fn parse_stringlit( let file_id = span.2; let parser = crate::simplexpr_parser::ExprParser::new(); - segs.into_iter() + let elems = segs + .into_iter() .filter_map(|(lo, segment, hi)| { let span = Span(lo, hi, file_id); match segment { @@ -28,9 +28,6 @@ pub fn parse_stringlit( } } }) - .fold_ok(None, |acc, cur| match acc { - Some(ast) => Some(SimplExpr::BinOp(span, Box::new(ast), BinOp::Plus, Box::new(cur))), - None => Some(cur), - }) - .map(|ast| ast.unwrap_or_else(|| SimplExpr::Literal(DynVal(String::new(), span)))) + .collect::, _>>()?; + Ok(SimplExpr::Concat(span, elems)) }