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)]
pub enum SimplExpr {
Literal(DynVal),
Concat(Span, Vec<SimplExpr>),
VarRef(Span, VarName),
BinOp(Span, Box<SimplExpr>, BinOp, 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 {
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,

View file

@ -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 {

View file

@ -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::<Result<_, _>>()?),
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<VarName, DynVal>) -> Result<Self, EvalError> {
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::<Result<_, EvalError>>()?,
)),
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::<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))

View file

@ -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::<Result<Vec<SimplExpr>, _>>()?;
Ok(SimplExpr::Concat(span, elems))
}