Fix representation of string-interpolations by adding a dedicated concat AST variant
This commit is contained in:
parent
f0b27d2ec1
commit
c20b172662
4 changed files with 54 additions and 49 deletions
|
@ -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,
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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))))
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue