diff --git a/CHANGELOG.md b/CHANGELOG.md index 943905a5..5c0eccdb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,21 @@ All notable changes to eww will be listed here, starting at changes since versio - Add support for output names in X11 to select `:monitor`. - Add support for `:active`-pseudoselector on eventbox (By: viandoxdev) - Add support for `:password` on input (By: viandoxdev) +- Add let-in syntax to simplexpr (By: oldwomanjosiah) + ```ocaml + (def-widget dataView [login] + (label + :text { + let + user = global_data.users[login] + email = user.email + first = user.name[0] + last = user.name[2] + in + "${first} ${last} <${email}>" + end + })) + ``` ### Notable fixes and other changes - Scale now only runs the onchange command on changes caused by user-interaction diff --git a/crates/eww/Cargo.toml b/crates/eww/Cargo.toml index f2a384e4..dc078736 100644 --- a/crates/eww/Cargo.toml +++ b/crates/eww/Cargo.toml @@ -65,4 +65,4 @@ codespan-reporting = "0.11" simplexpr = { version = "0.1.0", path = "../simplexpr" } eww_shared_util = { version = "0.1.0", path = "../eww_shared_util" } -yuck = { version = "0.1.0", path = "../yuck", default-features = false} +yuck = { version = "0.1.0", path = "../yuck", default-features = false } diff --git a/crates/simplexpr/src/ast.rs b/crates/simplexpr/src/ast.rs index aa961d1f..761aa0e3 100644 --- a/crates/simplexpr/src/ast.rs +++ b/crates/simplexpr/src/ast.rs @@ -40,6 +40,103 @@ pub enum AccessType { Safe, } +#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum DefinitionList { + Cons(Span, VarName, Box, Box), + End(Span, VarName, Box), +} + +impl Spanned for DefinitionList { + fn span(&self) -> Span { + match self { + DefinitionList::Cons(span, ..) => *span, + DefinitionList::End(span, ..) => *span, + } + } +} + +impl std::fmt::Display for DefinitionList { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + DefinitionList::Cons(_, ident, body, rest) => write!(f, "{ident} = {body}; {rest}"), + DefinitionList::End(_, ident, body) => write!(f, "{ident} = {body}"), + } + } +} + +impl DefinitionList { + pub fn references_var(&self, var: &VarName) -> bool { + match self { + DefinitionList::Cons(_, _, b, r) => b.references_var(var) || r.references_var(var), + DefinitionList::End(_, _, b) => b.references_var(var), + } + } + + pub fn collect_var_refs_into(&self, refs: &mut Vec) { + fn collect_undefined(body: &Box, defd: &Vec<&VarName>, refs: &mut Vec) { + let mut body_refs = body.collect_var_refs(); + body_refs.retain(|it| !defd.contains(&it)); + + for it in body_refs.into_iter() { + refs.push(it); + } + } + + fn inner<'d>(it: &'d DefinitionList, mut defd: Vec<&'d VarName>, refs: &mut Vec) { + match it { + DefinitionList::Cons(_, d, b, r) => { + collect_undefined(b, &defd, refs); + defd.push(d); + inner(r, defd, refs); + } + DefinitionList::End(_, _, b) => { + collect_undefined(b, &defd, refs); + } + } + } + + inner(self, Vec::new(), refs); + } + + pub fn collect_var_defs(&self) -> Vec { + match self { + DefinitionList::Cons(_, d, _, r) => { + let mut it = r.collect_var_defs(); + it.push(d.clone()); + it + } + DefinitionList::End(_, d, _) => Vec::from([d.clone()]), + } + } + + pub fn var_refs_with_span(&self) -> Vec<(Span, &VarName)> { + fn collect_undefined<'b>(body: &'b Box, defd: &Vec<&VarName>, refs: &mut Vec<(Span, &'b VarName)>) { + let mut body_refs = body.var_refs_with_span(); + + body_refs.retain(|it| !defd.contains(&it.1)); + + refs.extend(body_refs.into_iter()); + } + + fn inner<'d>(it: &'d DefinitionList, mut defd: Vec<&'d VarName>, refs: &mut Vec<(Span, &'d VarName)>) { + match it { + DefinitionList::Cons(_, n, e, r) => { + collect_undefined(e, &defd, refs); + defd.push(n); + inner(r, defd, refs); + } + DefinitionList::End(_, _, e) => { + collect_undefined(e, &defd, refs); + } + } + } + + let mut result = Vec::new(); + inner(self, Vec::new(), &mut result); + result + } +} + #[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum SimplExpr { Literal(DynVal), @@ -52,6 +149,7 @@ pub enum SimplExpr { IfElse(Span, Box, Box, Box), JsonAccess(Span, AccessType, Box, Box), FunctionCall(Span, String, Vec), + LetIn(Span, DefinitionList, Box), } impl std::fmt::Display for SimplExpr { @@ -81,6 +179,9 @@ impl std::fmt::Display for SimplExpr { SimplExpr::JsonObject(_, entries) => { write!(f, "{{{}}}", entries.iter().map(|(k, v)| format!("{}: {}", k, v)).join(", ")) } + SimplExpr::LetIn(_, defs, body) => { + write!(f, "let {defs} in {body} end") + } } } } @@ -113,6 +214,7 @@ impl SimplExpr { UnaryOp(_, _, x) => x.references_var(var), IfElse(_, a, b, c) => a.references_var(var) || b.references_var(var) || c.references_var(var), VarRef(_, x) => x == var, + LetIn(_, defs, body) => defs.references_var(var) || body.references_var(var), } } @@ -136,6 +238,20 @@ impl SimplExpr { v.collect_var_refs_into(dest); }), Literal(_) => {} + LetIn(_, defs, body) => { + let defvars = defs.collect_var_defs(); + + let mut refvars = body.collect_var_refs(); + + // Remove references which must be referring only to the inner scope + refvars.retain(|it| !defvars.contains(it)); + + defs.collect_var_refs_into(dest); + + for it in refvars.into_iter() { + dest.push(it); + } + } }; } @@ -159,6 +275,7 @@ impl Spanned for SimplExpr { SimplExpr::IfElse(span, ..) => *span, SimplExpr::JsonAccess(span, ..) => *span, SimplExpr::FunctionCall(span, ..) => *span, + SimplExpr::LetIn(span, ..) => *span, } } } diff --git a/crates/simplexpr/src/eval.rs b/crates/simplexpr/src/eval.rs index b31db4a3..e640fe57 100644 --- a/crates/simplexpr/src/eval.rs +++ b/crates/simplexpr/src/eval.rs @@ -1,7 +1,7 @@ use itertools::Itertools; use crate::{ - ast::{AccessType, BinOp, SimplExpr, UnaryOp}, + ast::{AccessType, BinOp, DefinitionList, SimplExpr, UnaryOp}, dynval::{ConversionError, DynVal}, }; use eww_shared_util::{Span, Spanned, VarName}; @@ -93,6 +93,9 @@ impl SimplExpr { .collect::>()?, ), x @ Literal(..) => x, + LetIn(_, d, b) => { + todo!("Resolve Refs is unused, TODO(josiah)"); + } }) } @@ -143,6 +146,17 @@ impl SimplExpr { JsonObject(_, entries) => { entries.iter().flat_map(|(k, v)| k.var_refs_with_span().into_iter().chain(v.var_refs_with_span())).collect() } + LetIn(_, defs, body) => { + let defvars = defs.collect_var_defs(); + + let mut body = body.var_refs_with_span(); + + body.retain(|it| !defvars.contains(it.1)); + + body.extend(defs.var_refs_with_span().into_iter()); + + body + } } } @@ -270,11 +284,36 @@ impl SimplExpr { .collect::>()?; Ok(DynVal::try_from(serde_json::Value::Object(entries))?.at(*span)) } + SimplExpr::LetIn(_, defs, body) => { + let child_env = defs.eval(values.clone())?; + + body.eval(&child_env) + } }; Ok(value?.at(span)) } } +impl DefinitionList { + pub fn eval(&self, mut base_env: HashMap) -> Result, EvalError> { + match self { + DefinitionList::Cons(_, n, e, r) => { + let output = e.eval(&base_env)?; + base_env.insert(n.clone(), output); + + r.eval(base_env) + } + DefinitionList::End(_, n, e) => { + let output = e.eval(&base_env)?; + + base_env.insert(n.clone(), output); + + Ok(base_env) + } + } + } +} + fn call_expr_function(name: &str, args: Vec) -> Result { match name { "round" => match args.as_slice() { @@ -395,5 +434,40 @@ mod tests { safe_access_to_missing(r#"{ "a": { "b": 2 } }.b?.b"#) => Ok(DynVal::from(&serde_json::Value::Null)), normal_access_to_existing(r#"{ "a": { "b": 2 } }.a.b"#) => Ok(DynVal::from(2)), normal_access_to_missing(r#"{ "a": { "b": 2 } }.b.b"#) => Err(super::EvalError::CannotIndex("null".to_string())), + assert_access_to_existing(r#"{ "a": { "b": 2 } }.a.b"#) => Ok(DynVal::from(2)), + assert_access_to_missing(r#"{ "a": { "b": 2 } }.b.b"#) => Err(super::EvalError::CannotIndex("null".to_string())), + let_in(r#"let name = 2 in name end"#) => Ok(DynVal::from(2)), + pathological_let_in( + r#" + let + name = let + name = let + name = "World" + in + { "name": name } + end + in + { "name": name } + end + in + let + name = name.name.name + in + "Hello, ${name}!" + end + end + "#) => Ok(DynVal::from(String::from("Hello, World!"))), + pathological_let_in_2( + r#" + let + value = "Hello", + value = value + ", ", + value = value + "Wor", + value = value + "ld!" + in + value + end + "# + ) => Ok(DynVal::from(String::from("Hello, World!"))), } } diff --git a/crates/simplexpr/src/parser/lexer.rs b/crates/simplexpr/src/parser/lexer.rs index ce9919cf..dced9328 100644 --- a/crates/simplexpr/src/parser/lexer.rs +++ b/crates/simplexpr/src/parser/lexer.rs @@ -44,8 +44,13 @@ pub enum Token { LBrack, RBrack, Dot, + Assign, + True, False, + Let, + In, + End, Ident(String), NumLit(String), @@ -105,8 +110,14 @@ regex_rules! { r"\{" => |_| Token::LCurl, r"\}" => |_| Token::RCurl, r"\." => |_| Token::Dot, - r"true" => |_| Token::True, - r"false" => |_| Token::False, + r"=" => |_| Token::Assign, + + r"\btrue\b" => |_| Token::True, + r"\bfalse\b" => |_| Token::False, + + r"\blet\b" => |_| Token::Let, + r"\bin\b" => |_| Token::In, + r"\bend\b" => |_| Token::End, r"\s+" => |_| Token::Skip, r";.*"=> |_| Token::Comment, diff --git a/crates/simplexpr/src/simplexpr_parser.lalrpop b/crates/simplexpr/src/simplexpr_parser.lalrpop index c215ee44..7221ea31 100644 --- a/crates/simplexpr/src/simplexpr_parser.lalrpop +++ b/crates/simplexpr/src/simplexpr_parser.lalrpop @@ -1,4 +1,4 @@ -use crate::ast::{SimplExpr::{self, *}, BinOp::*, UnaryOp::*, AccessType}; +use crate::ast::{SimplExpr::{self, *}, BinOp::*, UnaryOp::*, AccessType, DefinitionList}; use eww_shared_util::{Span, VarName}; use crate::parser::lexer::{Token, LexicalError, StrLitSegment, Sp}; use crate::parser::lalrpop_helpers::*; @@ -40,10 +40,15 @@ extern { "{" => Token::LCurl, "}" => Token::RCurl, "." => Token::Dot, + "=" => Token::Assign, "true" => Token::True, "false" => Token::False, + "let" => Token::Let, + "in" => Token::In, + "end" => Token::End, + "identifier" => Token::Ident(), "number" => Token::NumLit(), "string" => Token::StringLit(>>), @@ -61,6 +66,15 @@ Comma: Vec = { } }; +pub DefList: DefinitionList = { + "=" => { + DefinitionList::End(Span(l, r, fid), VarName(ident.to_string()), b(expr)) + }, + "=" "," => { + DefinitionList::Cons(Span(l, r, fid), VarName(ident.to_string()), b(expr), b(rest)) + } +}; + pub Expr: SimplExpr = { #[precedence(level="0")] @@ -119,6 +133,10 @@ pub Expr: SimplExpr = { "?" ":" => { IfElse(Span(l, r, fid), b(cond), b(then), b(els)) }, + + "let" "in" "end" => { + LetIn(Span(l, r, fid), defs, b(body)) + }, }; ExprReset = ;