feat(simplexpr): allow json literals in simplexprs
This commit is contained in:
parent
8de692da8a
commit
3f65733d12
19 changed files with 189 additions and 44 deletions
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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
|
||||
"#),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 }"#,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
|
@ -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]],
|
||||
)
|
|
@ -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"},
|
||||
)
|
|
@ -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>;
|
||||
|
||||
|
|
|
@ -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),
|
||||
)),
|
||||
|
|
|
@ -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}")"#),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Reference in a new issue