feat(simplexpr): allow json literals in simplexprs

This commit is contained in:
elkowar 2021-08-25 13:53:37 +02:00
parent 8de692da8a
commit 3f65733d12
No known key found for this signature in database
GPG key ID: E321AD71B1D1F27F
19 changed files with 189 additions and 44 deletions

View file

@ -32,6 +32,8 @@ pub enum UnaryOp {
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum SimplExpr {
Literal(DynVal),
JsonArray(Span, Vec<SimplExpr>),
JsonObject(Span, Vec<(SimplExpr, SimplExpr)>),
Concat(Span, Vec<SimplExpr>),
VarRef(Span, VarName),
BinOp(Span, Box<SimplExpr>, BinOp, Box<SimplExpr>),
@ -63,6 +65,10 @@ impl std::fmt::Display for SimplExpr {
SimplExpr::FunctionCall(_, function_name, args) => {
write!(f, "{}({})", function_name, args.iter().join(", "))
}
SimplExpr::JsonArray(_, values) => write!(f, "[{}]", values.iter().join(", ")),
SimplExpr::JsonObject(_, entries) => {
write!(f, "{{{}}}", entries.iter().map(|(k, v)| format!("{}: {}", k, v)).join(", "))
}
}
}
}
@ -81,10 +87,13 @@ impl SimplExpr {
Self::Literal(s.into())
}
}
impl Spanned for SimplExpr {
fn span(&self) -> Span {
match self {
SimplExpr::Literal(x) => x.span(),
SimplExpr::JsonArray(span, _) => *span,
SimplExpr::JsonObject(span, _) => *span,
SimplExpr::Concat(span, _) => *span,
SimplExpr::VarRef(span, _) => *span,
SimplExpr::BinOp(span, ..) => *span,

View file

@ -1,7 +1,7 @@
use eww_shared_util::{Span, Spanned};
use itertools::Itertools;
use serde::{Deserialize, Serialize};
use std::{fmt, iter::FromIterator, str::FromStr};
use std::{convert::TryFrom, fmt, iter::FromIterator, str::FromStr};
pub type Result<T> = std::result::Result<T, ConversionError>;
@ -94,6 +94,14 @@ macro_rules! impl_dynval_from {
impl_dynval_from!(bool, i32, u32, f32, u8, f64, &str);
impl TryFrom<serde_json::Value> for DynVal {
type Error = serde_json::Error;
fn try_from(value: serde_json::Value) -> std::result::Result<Self, Self::Error> {
Ok(DynVal(serde_json::to_string(&value)?, Span::DUMMY))
}
}
impl From<std::time::Duration> for DynVal {
fn from(d: std::time::Duration) -> Self {
DynVal(format!("{}ms", d.as_millis()), Span::DUMMY)

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, convert::TryFrom};
#[derive(Debug, thiserror::Error)]
pub enum EvalError {
@ -30,6 +30,9 @@ pub enum EvalError {
#[error("Unable to index into value {0}")]
CannotIndex(String),
#[error("Json operation failed: {0}")]
SerdeError(#[from] serde_json::error::Error),
#[error("{1}")]
Spanned(Span, Box<EvalError>),
}
@ -74,6 +77,16 @@ impl SimplExpr {
FunctionCall(span, name, args.into_iter().map(|x| x.try_map_var_refs(f)).collect::<Result<_, _>>()?)
}
VarRef(span, name) => f(span, name)?,
JsonArray(span, values) => {
JsonArray(span, values.into_iter().map(|x| x.try_map_var_refs(f)).collect::<Result<_, _>>()?)
}
JsonObject(span, entries) => JsonObject(
span,
entries
.into_iter()
.map(|(k, v)| Ok((k.try_map_var_refs(f)?, v.try_map_var_refs(f)?)))
.collect::<Result<_, _>>()?,
),
x @ Literal(..) => x,
})
}
@ -121,6 +134,8 @@ impl SimplExpr {
refs
}
FunctionCall(_, _, args) => args.iter().flat_map(|a| a.var_refs()).collect(),
JsonArray(_, values) => values.iter().flat_map(|v| v.var_refs()).collect(),
JsonObject(_, entries) => entries.iter().flat_map(|(k, v)| k.var_refs().into_iter().chain(v.var_refs())).collect(),
}
}
@ -218,6 +233,20 @@ impl SimplExpr {
let args = args.into_iter().map(|a| a.eval(values)).collect::<Result<_, EvalError>>()?;
call_expr_function(&function_name, args).map(|x| x.at(*span)).map_err(|e| e.at(*span))
}
SimplExpr::JsonArray(span, entries) => {
let entries = entries
.into_iter()
.map(|v| Ok(serde_json::Value::String(v.eval(values)?.as_string()?)))
.collect::<Result<_, EvalError>>()?;
Ok(DynVal::try_from(serde_json::Value::Array(entries))?.at(*span))
}
SimplExpr::JsonObject(span, entries) => {
let entries = entries
.into_iter()
.map(|(k, v)| Ok((k.eval(values)?.as_string()?, serde_json::Value::String(v.eval(values)?.as_string()?))))
.collect::<Result<_, EvalError>>()?;
Ok(DynVal::try_from(serde_json::Value::Object(entries))?.at(*span))
}
};
Ok(value?.at(span))
}

View file

@ -35,6 +35,8 @@ pub enum Token {
Colon,
LPren,
RPren,
LCurl,
RCurl,
LBrack,
RBrack,
Dot,
@ -92,6 +94,8 @@ regex_rules! {
escape(r")") => |_| Token::RPren,
escape(r"[") => |_| Token::LBrack,
escape(r"]") => |_| Token::RBrack,
escape(r"{") => |_| Token::LCurl,
escape(r"}") => |_| Token::RCurl,
escape(r".") => |_| Token::Dot,
escape(r"true") => |_| Token::True,
escape(r"false") => |_| Token::False,
@ -222,14 +226,31 @@ impl<'s> Lexer<'s> {
} else {
let segment_start = self.pos;
let mut toks = Vec::new();
while self.pos < self.source.len() && !self.remaining().starts_with(STR_INTERPOLATION_END) {
match self.next_token()? {
Ok(tok) => toks.push(tok),
Err(err) => return Some(Err(err)),
let mut curly_nesting = 0;
'inner: while let Some(tok) = self.next_token() {
if self.pos >= self.source.len() {
break 'inner;
}
let tok = match tok {
Ok(x) => x,
Err(e) => return Some(Err(e)),
};
if tok.1 == Token::LCurl {
curly_nesting += 1;
} else if tok.1 == Token::RCurl {
curly_nesting -= 1;
}
if curly_nesting < 0 {
break 'inner;
} else {
toks.push(tok);
}
}
elements.push((segment_start + self.offset, StrLitSegment::Interp(toks), self.pos + self.offset));
self.advance_by(STR_INTERPOLATION_END.len());
elements.push((segment_start + self.offset, StrLitSegment::Interp(toks), self.pos + self.offset - 1));
in_string_lit = true;
}
}
@ -282,9 +303,13 @@ mod test {
number_in_ident => v!(r#"foo_1_bar"#),
interpolation_1 => v!(r#" "foo ${2 * 2} bar" "#),
interpolation_nested => v!(r#" "foo ${(2 * 2) + "${5 + 5}"} bar" "#),
json_in_interpolation => v!(r#" "${ {1: 2} }" "#),
escaping => v!(r#" "a\"b\{}" "#),
comments => v!("foo ; bar"),
weird_char_boundaries => v!(r#"" " + music"#),
symbol_spam => v!(r#"(foo + - "()" "a\"b" true false [] 12.2)"#),
weird_nesting => v!(r#"
"${ {"hi": "ho"}.hi }".hi
"#),
}
}

View file

@ -42,6 +42,8 @@ mod tests {
"hi[\"ho\"]",
"foo.bar.baz",
"foo.bar[2 + 2] * asdf[foo.bar]",
r#"[1, 2, 3 + 4, "bla", [blub, blo]]"#,
r#"{ "key": "value", 5: 1+2, true: false }"#,
);
}
}

View file

@ -0,0 +1,6 @@
---
source: crates/simplexpr/src/parser/lexer.rs
expression: "v!(r#\" \"${ {1: 2} }\" \"#)"
---
(1, StringLit([(1, Literal(""), 4), (4, Interp([(5, LCurl, 6), (6, NumLit("1"), 7), (7, Colon, 8), (9, NumLit("2"), 10), (10, RCurl, 11)]), 12), (12, Literal(""), 14)]), 14)

View file

@ -0,0 +1,8 @@
---
source: crates/simplexpr/src/parser/lexer.rs
expression: "v!(r#\"\n \"${ {\"hi\": \"ho\"}.hi }\".hi\n \"#)"
---
(13, StringLit([(13, Literal(""), 16), (16, Interp([(17, LCurl, 18), (18, StringLit([(18, Literal("hi"), 22)]), 22), (22, Colon, 23), (24, StringLit([(24, Literal("ho"), 28)]), 28), (28, RCurl, 29), (29, Dot, 30), (30, Ident("hi"), 32)]), 33), (33, Literal(""), 35)]), 35)
(35, Dot, 36)
(36, Ident("hi"), 38)

View file

@ -0,0 +1,8 @@
---
source: crates/simplexpr/src/parser/mod.rs
expression: "p.parse(0, Lexer::new(0, 0, r#\"[1, 2, 3 + 4, \"bla\", [blub, blo]]\"#))"
---
Ok(
["1", "2", ("3" + "4"), "bla", [blub, blo]],
)

View file

@ -0,0 +1,8 @@
---
source: crates/simplexpr/src/parser/mod.rs
expression: "p.parse(0, Lexer::new(0, 0, r#\"{ \"key\": \"value\", 5: 1+2, true: false }\"#))"
---
Ok(
{"key": "value", "5": ("1" + "2"), "true": "false"},
)

View file

@ -34,6 +34,8 @@ extern {
")" => Token::RPren,
"[" => Token::LBrack,
"]" => Token::RBrack,
"{" => Token::LCurl,
"}" => Token::RCurl,
"." => Token::Dot,
"true" => Token::True,
@ -59,10 +61,6 @@ Comma<T>: Vec<T> = {
pub Expr: SimplExpr = {
#[precedence(level="0")]
//<l:@L> "lexer_error" <r:@R> =>? {
// Err(ParseError::User { error: LexicalError(l, r, fid) })
//},
<l:@L> <x:"string"> <r:@R> =>? parse_stringlit(Span(l, r, fid), x),
<l:@L> <x:"number"> <r:@R> => SimplExpr::literal(Span(l, r, fid), x),
<l:@L> "true" <r:@R> => SimplExpr::literal(Span(l, r, fid), "true".into()),
@ -70,6 +68,8 @@ pub Expr: SimplExpr = {
<l:@L> <ident:"identifier"> <r:@R> => VarRef(Span(l, r, fid), VarName(ident.to_string())),
"(" <ExprReset> ")",
<l:@L> "[" <values: Comma<ExprReset>> "]" <r:@R> => SimplExpr::JsonArray(Span(l, r, fid), values),
<l:@L> "{" <values: Comma<JsonKeyValue>> "}" <r:@R> => SimplExpr::JsonObject(Span(l, r, fid), values),
#[precedence(level="1")] #[assoc(side="right")]
<l:@L> <ident:"identifier"> "(" <args: Comma<ExprReset>> ")" <r:@R> => FunctionCall(Span(l, r, fid), ident, args),
@ -110,3 +110,7 @@ pub Expr: SimplExpr = {
};
ExprReset = <Expr>;
JsonKeyValue = <Expr> ":" <Expr>;

View file

@ -8,8 +8,16 @@ Config(
"bar": WidgetDefinition(
name: "bar",
expected_args: [
AttrName("arg"),
AttrName("arg2"),
AttrSpec(
name: AttrName("arg"),
optional: false,
span: Span(25, 28, 0),
),
AttrSpec(
name: AttrName("arg2"),
optional: false,
span: Span(29, 33, 0),
),
],
widget: WidgetUse(
name: "foo",
@ -87,6 +95,7 @@ Config(
VarName("stuff"): Listen(ListenScriptVar(
name: VarName("stuff"),
command: "tail -f stuff",
initial_value: DynVal("", Span(18446744073709551615, 18446744073709551615, 18446744073709551615)),
command_span: Span(168, 183, 0),
name_span: Span(162, 167, 0),
)),

View file

@ -96,27 +96,32 @@ impl Lexer {
}
fn simplexpr(&mut self) -> Option<Result<(usize, Token, usize), parse_error::ParseError>> {
use simplexpr::parser::lexer as simplexpr_lexer;
self.pos += 1;
let mut simplexpr_lexer = simplexpr::parser::lexer::Lexer::new(self.file_id, self.pos, &self.source[self.pos..]);
let mut toks = Vec::new();
let mut simplexpr_lexer = simplexpr_lexer::Lexer::new(self.file_id, self.pos, &self.source[self.pos..]);
let mut toks: Vec<(usize, _, usize)> = Vec::new();
let mut end = self.pos;
let mut curly_nesting = 0;
loop {
match simplexpr_lexer.next_token() {
Some(Ok((lo, tok, hi))) => {
match simplexpr_lexer.next_token()? {
Ok((lo, tok, hi)) => {
end = hi;
if tok == simplexpr_lexer::Token::LCurl {
curly_nesting += 1;
} else if tok == simplexpr_lexer::Token::RCurl {
curly_nesting -= 1;
if curly_nesting < 0 {
let start = toks.first().map(|(start, ..)| *start).unwrap_or(end);
self.pos = end;
self.advance_until_char_boundary();
return Some(Ok((start, Token::SimplExpr(toks), end)));
}
}
toks.push((lo, tok, hi));
}
Some(Err(err)) => {
if simplexpr_lexer.continues_with('}') {
let start = toks.first().map(|x| x.0).unwrap_or(end);
self.pos = end + 1;
self.advance_until_char_boundary();
return Some(Ok((start, Token::SimplExpr(toks), end)));
} else {
return Some(Err(parse_error::ParseError::LexicalError(err.span())));
}
Err(err) => {
return Some(Err(parse_error::ParseError::LexicalError(err.span())));
}
None => return None,
}
}
}
@ -182,7 +187,7 @@ mod test {
macro_rules! v {
($x:literal) => {
Lexer::new(0, 0, $x)
Lexer::new(0, $x.to_string())
.map(|x| match x {
Ok((l, x, r)) => format!("({}, {:?}, {})", l, x, r),
Err(err) => format!("{}", err),
@ -192,10 +197,12 @@ mod test {
}
snapshot_string! {
basic => r#"(foo + - "text" )"#,
escaped_strings => r#"{ bla "} \" }" " \" "}"#,
escaped_quote => r#""< \" >""#,
char_boundary => r#"{ " " + music}"#,
quotes_in_quotes => r#"{ " } ' }" }"#,
basic => v!(r#"(foo + - "text" )"#),
basic_simplexpr => v!(r#"({2})"#),
escaped_strings => v!(r#"{ bla "} \" }" " \" "}"#),
escaped_quote => v!(r#""< \" >""#),
char_boundary => v!(r#"{ " " + music}"#),
quotes_in_quotes => v!(r#"{ " } ' }" }"#),
end_with_string_interpolation => v!(r#"(box "foo ${1 + 2}")"#),
}
}

View file

@ -1,6 +1,11 @@
---
source: crates/yuck/src/parser/lexer.rs
expression: "r#\"(foo + - \"text\" )\"#"
expression: "v!(r#\"(foo + - \"text\" )\"#)"
---
(foo + - "text" )
(0, LPren, 1)
(1, Symbol("foo"), 4)
(5, Symbol("+"), 6)
(7, Symbol("-"), 8)
(9, SimplExpr([(9, StringLit([(9, Literal("text"), 15)]), 15)]), 15)
(16, RPren, 17)

View file

@ -0,0 +1,8 @@
---
source: crates/yuck/src/parser/lexer.rs
expression: "v!(r#\"({2})\"#)"
---
(0, LPren, 1)
(2, SimplExpr([(2, NumLit("2"), 3)]), 4)
(4, RPren, 5)

View file

@ -1,6 +1,6 @@
---
source: crates/yuck/src/parser/lexer.rs
expression: "r#\"{ \" \" + music}\"#"
expression: "v!(r#\"{ \" \" + music}\"#)"
---
{ " " + music}
(2, SimplExpr([(2, StringLit([(2, Literal("\u{f001} "), 10)]), 10), (11, Plus, 12), (13, Ident("music"), 18)]), 19)

View file

@ -0,0 +1,9 @@
---
source: crates/yuck/src/parser/lexer.rs
expression: "v!(r#\"(box \"foo ${1 + 2}\")\"#)"
---
(0, LPren, 1)
(1, Symbol("box"), 4)
(5, SimplExpr([(5, StringLit([(5, Literal("foo "), 12), (12, Interp([(12, NumLit("1"), 13), (14, Plus, 15), (16, NumLit("2"), 17)]), 17), (17, Literal(""), 19)]), 19)]), 19)
(19, RPren, 20)

View file

@ -1,6 +1,6 @@
---
source: crates/yuck/src/parser/lexer.rs
expression: "r#\"\"< \\\" >\"\"#"
expression: "v!(r#\"\"< \\\" >\"\"#)"
---
"< \" >"
(0, SimplExpr([(0, StringLit([(0, Literal("< \" >"), 8)]), 8)]), 8)

View file

@ -1,6 +1,6 @@
---
source: crates/yuck/src/parser/lexer.rs
expression: "r#\"{ bla \"} \\\" }\" \" \\\" \"}\"#"
expression: "v!(r#\"{ bla \"} \\\" }\" \" \\\" \"}\"#)"
---
{ bla "} \" }" " \" "}
(2, SimplExpr([(2, Ident("bla"), 5), (6, StringLit([(6, Literal("} \" }"), 14)]), 14), (15, StringLit([(15, Literal(" \" "), 21)]), 21)]), 22)

View file

@ -1,6 +1,6 @@
---
source: crates/yuck/src/parser/lexer.rs
expression: "r#\"{ \" } ' }\" }\"#"
expression: "v!(r#\"{ \" } ' }\" }\"#)"
---
{ " } ' }" }
(2, SimplExpr([(2, StringLit([(2, Literal(" } ' }"), 10)]), 10)]), 12)