Fix representation of string-interpolations by adding a dedicated concat AST variant

This commit is contained in:
elkowar 2021-08-17 11:10:48 +02:00
parent f0b27d2ec1
commit c20b172662
No known key found for this signature in database
GPG key ID: E321AD71B1D1F27F
4 changed files with 54 additions and 49 deletions

View file

@ -32,6 +32,7 @@ pub enum UnaryOp {
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum SimplExpr { pub enum SimplExpr {
Literal(DynVal), Literal(DynVal),
Concat(Span, Vec<SimplExpr>),
VarRef(Span, VarName), VarRef(Span, VarName),
BinOp(Span, Box<SimplExpr>, BinOp, Box<SimplExpr>), BinOp(Span, Box<SimplExpr>, BinOp, Box<SimplExpr>),
UnaryOp(Span, UnaryOp, Box<SimplExpr>), UnaryOp(Span, UnaryOp, Box<SimplExpr>),
@ -44,6 +45,16 @@ impl std::fmt::Display for SimplExpr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
SimplExpr::Literal(x) => write!(f, "\"{}\"", x), 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::VarRef(_, x) => write!(f, "{}", x),
SimplExpr::BinOp(_, l, op, r) => write!(f, "({} {} {})", l, op, r), SimplExpr::BinOp(_, l, op, r) => write!(f, "({} {} {})", l, op, r),
SimplExpr::UnaryOp(_, op, x) => write!(f, "{}{}", op, x), SimplExpr::UnaryOp(_, op, x) => write!(f, "{}{}", op, x),
@ -74,6 +85,7 @@ impl Spanned for SimplExpr {
fn span(&self) -> Span { fn span(&self) -> Span {
match self { match self {
SimplExpr::Literal(x) => x.span(), SimplExpr::Literal(x) => x.span(),
SimplExpr::Concat(span, _) => *span,
SimplExpr::VarRef(span, _) => *span, SimplExpr::VarRef(span, _) => *span,
SimplExpr::BinOp(span, ..) => *span, SimplExpr::BinOp(span, ..) => *span,
SimplExpr::UnaryOp(span, ..) => *span, SimplExpr::UnaryOp(span, ..) => *span,

View file

@ -119,8 +119,9 @@ impl Spanned for DynVal {
} }
impl DynVal { impl DynVal {
pub fn at(self, span: Span) -> Self { pub fn at(mut self, span: Span) -> Self {
DynVal(self.0, span) self.1 = span;
self
} }
pub fn from_string(s: String) -> Self { pub fn from_string(s: String) -> Self {

View file

@ -5,7 +5,7 @@ use crate::{
dynval::{ConversionError, DynVal}, dynval::{ConversionError, DynVal},
}; };
use eww_shared_util::{Span, Spanned, VarName}; use eww_shared_util::{Span, Spanned, VarName};
use std::collections::HashMap; use std::collections::{HashMap, HashSet};
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum EvalError { pub enum EvalError {
@ -64,6 +64,7 @@ impl SimplExpr {
use SimplExpr::*; use SimplExpr::*;
Ok(match self { 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)?), 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::<Result<_, _>>()?),
UnaryOp(span, op, box a) => UnaryOp(span, op, box a.try_map_var_refs(f)?), 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, 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)?) 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. /// resolve variable references in the expression. Fails if a variable cannot be resolved.
pub fn resolve_refs(self, variables: &HashMap<VarName, DynVal>) -> Result<Self, EvalError> { pub fn resolve_refs(self, variables: &HashMap<VarName, DynVal>) -> Result<Self, EvalError> {
use SimplExpr::*; use SimplExpr::*;
match self { self.try_map_var_refs(|span, name| match variables.get(&name) {
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)?))
}
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::<Result<_, EvalError>>()?,
)),
VarRef(span, ref name) => match variables.get(name) {
Some(value) => Ok(Literal(value.clone())), Some(value) => Ok(Literal(value.clone())),
None => { None => {
let similar_ish = let similar_ish =
variables.keys().filter(|key| levenshtein::levenshtein(&key.0, &name.0) < 3).cloned().collect_vec(); variables.keys().filter(|key| levenshtein::levenshtein(&key.0, &name.0) < 3).cloned().collect_vec();
Err(EvalError::UnknownVariable(name.clone(), similar_ish).at(span)) 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::*; use SimplExpr::*;
match self { match self {
Literal(..) => Vec::new(), Literal(..) => HashSet::new(),
VarRef(_, name) => vec![name], 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) => { BinOp(_, box a, _, box b) | JsonAccess(_, box a, box b) => {
let mut refs = a.var_refs(); let mut refs = a.var_refs();
refs.append(&mut b.var_refs()); refs.extend(b.var_refs().iter());
refs refs
} }
UnaryOp(_, _, box x) => x.var_refs(), UnaryOp(_, _, box x) => x.var_refs(),
IfElse(_, box a, box b, box c) => { IfElse(_, box a, box b, box c) => {
let mut refs = a.var_refs(); let mut refs = a.var_refs();
refs.append(&mut b.var_refs()); refs.extend(b.var_refs().iter());
refs.append(&mut c.var_refs()); refs.extend(c.var_refs().iter());
refs 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 span = self.span();
let value = match self { let value = match self {
SimplExpr::Literal(x) => Ok(x.clone()), 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) => { SimplExpr::VarRef(span, ref name) => {
let similar_ish = let similar_ish =
values.keys().filter(|keys| levenshtein::levenshtein(&keys.0, &name.0) < 3).cloned().collect_vec(); 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))? .ok_or_else(|| EvalError::UnknownVariable(name.clone(), similar_ish).at(*span))?
.at(*span)) .at(*span))
} }
SimplExpr::BinOp(_, a, op, b) => { SimplExpr::BinOp(span, a, op, b) => {
let a = a.eval(values)?; let a = a.eval(values)?;
let b = b.eval(values)?; let b = b.eval(values)?;
Ok(match op { let dynval = match op {
BinOp::Equals => DynVal::from(a == b), BinOp::Equals => DynVal::from(a == b),
BinOp::NotEquals => DynVal::from(a != b), BinOp::NotEquals => DynVal::from(a != b),
BinOp::And => DynVal::from(a.as_bool()? && b.as_bool()?), BinOp::And => DynVal::from(a.as_bool()? && b.as_bool()?),
@ -185,12 +179,13 @@ impl SimplExpr {
let regex = regex::Regex::new(&b.as_string()?)?; let regex = regex::Regex::new(&b.as_string()?)?;
DynVal::from(regex.is_match(&a.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)?; let a = a.eval(values)?;
Ok(match op { Ok(match op {
UnaryOp::Not => DynVal::from(!a.as_bool()?), UnaryOp::Not => DynVal::from(!a.as_bool()?).at(*span),
}) })
} }
SimplExpr::IfElse(_, cond, yes, no) => { SimplExpr::IfElse(_, cond, yes, no) => {
@ -207,21 +202,21 @@ impl SimplExpr {
serde_json::Value::Array(val) => { serde_json::Value::Array(val) => {
let index = index.as_i32()?; let index = index.as_i32()?;
let indexed_value = val.get(index as usize).unwrap_or(&serde_json::Value::Null); 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) => { serde_json::Value::Object(val) => {
let indexed_value = val let indexed_value = val
.get(&index.as_string()?) .get(&index.as_string()?)
.or_else(|| val.get(&index.as_i32().ok()?.to_string())) .or_else(|| val.get(&index.as_i32().ok()?.to_string()))
.unwrap_or(&serde_json::Value::Null); .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)), _ => Err(EvalError::CannotIndex(format!("{}", val)).at(*span)),
} }
} }
SimplExpr::FunctionCall(span, function_name, args) => { SimplExpr::FunctionCall(span, function_name, args) => {
let args = args.into_iter().map(|a| a.eval(values)).collect::<Result<_, EvalError>>()?; 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)) call_expr_function(&function_name, args).map(|x| x.at(*span)).map_err(|e| e.at(*span))
} }
}; };
Ok(value?.at(span)) Ok(value?.at(span))

View file

@ -1,7 +1,6 @@
use eww_shared_util::Span; 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}; use super::lexer::{LexicalError, Sp, StrLitSegment, Token};
@ -16,7 +15,8 @@ pub fn parse_stringlit(
let file_id = span.2; let file_id = span.2;
let parser = crate::simplexpr_parser::ExprParser::new(); let parser = crate::simplexpr_parser::ExprParser::new();
segs.into_iter() let elems = segs
.into_iter()
.filter_map(|(lo, segment, hi)| { .filter_map(|(lo, segment, hi)| {
let span = Span(lo, hi, file_id); let span = Span(lo, hi, file_id);
match segment { match segment {
@ -28,9 +28,6 @@ pub fn parse_stringlit(
} }
} }
}) })
.fold_ok(None, |acc, cur| match acc { .collect::<Result<Vec<SimplExpr>, _>>()?;
Some(ast) => Some(SimplExpr::BinOp(span, Box::new(ast), BinOp::Plus, Box::new(cur))), Ok(SimplExpr::Concat(span, elems))
None => Some(cur),
})
.map(|ast| ast.unwrap_or_else(|| SimplExpr::Literal(DynVal(String::new(), span))))
} }