Fully implement string interpolation with ${} syntax
This commit is contained in:
parent
5cde0228fb
commit
24c9fee204
36 changed files with 440 additions and 163 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -482,6 +482,7 @@ name = "eww_shared_util"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"derive_more",
|
"derive_more",
|
||||||
|
"insta",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ use codespan_reporting::{
|
||||||
};
|
};
|
||||||
use eww_shared_util::Span;
|
use eww_shared_util::Span;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use simplexpr::eval::EvalError;
|
use simplexpr::{dynval::ConversionError, eval::EvalError};
|
||||||
use yuck::{config::file_provider::YuckFiles, error::AstError, format_diagnostic::ToDiagnostic, gen_diagnostic};
|
use yuck::{config::file_provider::YuckFiles, error::AstError, format_diagnostic::ToDiagnostic, gen_diagnostic};
|
||||||
|
|
||||||
use crate::error::DiagError;
|
use crate::error::DiagError;
|
||||||
|
@ -22,6 +22,8 @@ pub fn anyhow_err_to_diagnostic(err: &anyhow::Error) -> Diagnostic<usize> {
|
||||||
err.diag.clone()
|
err.diag.clone()
|
||||||
} else if let Some(err) = err.downcast_ref::<AstError>() {
|
} else if let Some(err) = err.downcast_ref::<AstError>() {
|
||||||
err.to_diagnostic()
|
err.to_diagnostic()
|
||||||
|
} else if let Some(err) = err.downcast_ref::<ConversionError>() {
|
||||||
|
err.to_diagnostic()
|
||||||
} else if let Some(err) = err.downcast_ref::<EvalError>() {
|
} else if let Some(err) = err.downcast_ref::<EvalError>() {
|
||||||
err.to_diagnostic()
|
err.to_diagnostic()
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -6,3 +6,7 @@ edition = "2018"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = {version = "1.0", features = ["derive"]}
|
serde = {version = "1.0", features = ["derive"]}
|
||||||
derive_more = "0.99"
|
derive_more = "0.99"
|
||||||
|
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
insta = "1.7"
|
||||||
|
|
|
@ -3,3 +3,36 @@ pub mod wrappers;
|
||||||
|
|
||||||
pub use span::*;
|
pub use span::*;
|
||||||
pub use wrappers::*;
|
pub use wrappers::*;
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! snapshot_debug {
|
||||||
|
( $($name:ident => $test:expr),* $(,)?) => {
|
||||||
|
$(
|
||||||
|
#[test]
|
||||||
|
fn $name() { ::insta::assert_debug_snapshot!($test); }
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! snapshot_string {
|
||||||
|
( $($name:ident => $test:expr),* $(,)?) => {
|
||||||
|
$(
|
||||||
|
#[test]
|
||||||
|
fn $name() { ::insta::assert_snapshot!($test); }
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! snapshot_ron {
|
||||||
|
( $($name:ident => $test:expr),* $(,)?) => {
|
||||||
|
$(
|
||||||
|
#[test]
|
||||||
|
fn $name() {
|
||||||
|
::insta::with_settings!({sort_maps => true}, {
|
||||||
|
::insta::assert_ron_snapshot!($test);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -15,9 +15,13 @@ pub enum EvalError {
|
||||||
#[error("Invalid regex: {0}")]
|
#[error("Invalid regex: {0}")]
|
||||||
InvalidRegex(#[from] regex::Error),
|
InvalidRegex(#[from] regex::Error),
|
||||||
|
|
||||||
|
// TODO unresolved and unknown are the same for the user,....
|
||||||
#[error("got unresolved variable `{0}`")]
|
#[error("got unresolved variable `{0}`")]
|
||||||
UnresolvedVariable(VarName),
|
UnresolvedVariable(VarName),
|
||||||
|
|
||||||
|
#[error("Unknown variable {0}")]
|
||||||
|
UnknownVariable(VarName),
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
ConversionError(#[from] ConversionError),
|
ConversionError(#[from] ConversionError),
|
||||||
|
|
||||||
|
@ -27,9 +31,6 @@ pub enum EvalError {
|
||||||
#[error("Unknown function {0}")]
|
#[error("Unknown function {0}")]
|
||||||
UnknownFunction(String),
|
UnknownFunction(String),
|
||||||
|
|
||||||
#[error("Unknown variable {0}")]
|
|
||||||
UnknownVariable(VarName),
|
|
||||||
|
|
||||||
#[error("Unable to index into value {0}")]
|
#[error("Unable to index into value {0}")]
|
||||||
CannotIndex(String),
|
CannotIndex(String),
|
||||||
|
|
||||||
|
@ -41,6 +42,13 @@ impl EvalError {
|
||||||
pub fn at(self, span: Span) -> Self {
|
pub fn at(self, span: Span) -> Self {
|
||||||
Self::Spanned(span, Box::new(self))
|
Self::Spanned(span, Box::new(self))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn map_in_span(self, f: impl FnOnce(Self) -> Self) -> Self {
|
||||||
|
match self {
|
||||||
|
EvalError::Spanned(span, err) => EvalError::Spanned(span, Box::new(err.map_in_span(f))),
|
||||||
|
other => f(other),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Spanned for EvalError {
|
impl Spanned for EvalError {
|
||||||
|
@ -145,8 +153,10 @@ impl SimplExpr {
|
||||||
pub fn eval_no_vars(&self) -> Result<DynVal, EvalError> {
|
pub fn eval_no_vars(&self) -> Result<DynVal, EvalError> {
|
||||||
match self.eval(&HashMap::new()) {
|
match self.eval(&HashMap::new()) {
|
||||||
Ok(x) => Ok(x),
|
Ok(x) => Ok(x),
|
||||||
Err(EvalError::UnknownVariable(name)) => Err(EvalError::NoVariablesAllowed(name)),
|
Err(x) => Err(x.map_in_span(|err| match err {
|
||||||
Err(x) => Err(x),
|
EvalError::UnknownVariable(name) | EvalError::UnresolvedVariable(name) => EvalError::NoVariablesAllowed(name),
|
||||||
|
other => other,
|
||||||
|
})),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#![feature(box_patterns)]
|
#![feature(box_patterns)]
|
||||||
|
#![feature(pattern)]
|
||||||
#![feature(box_syntax)]
|
#![feature(box_syntax)]
|
||||||
#![feature(try_blocks)]
|
#![feature(try_blocks)]
|
||||||
#![feature(unwrap_infallible)]
|
#![feature(unwrap_infallible)]
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::str::pattern::Pattern;
|
||||||
|
|
||||||
use eww_shared_util::{Span, Spanned};
|
use eww_shared_util::{Span, Spanned};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use regex::{escape, Regex, RegexSet};
|
use regex::{escape, Regex, RegexSet};
|
||||||
|
@ -63,9 +65,10 @@ macro_rules! regex_rules {
|
||||||
}
|
}
|
||||||
|
|
||||||
static ESCAPE_REPLACE_REGEX: Lazy<regex::Regex> = Lazy::new(|| Regex::new(r"\\(.)").unwrap());
|
static ESCAPE_REPLACE_REGEX: Lazy<regex::Regex> = Lazy::new(|| Regex::new(r"\\(.)").unwrap());
|
||||||
|
pub static STR_INTERPOLATION_START: &str = "${";
|
||||||
|
pub static STR_INTERPOLATION_END: &str = "}";
|
||||||
|
|
||||||
regex_rules! {
|
regex_rules! {
|
||||||
|
|
||||||
escape(r"+") => |_| Token::Plus,
|
escape(r"+") => |_| Token::Plus,
|
||||||
escape(r"-") => |_| Token::Minus,
|
escape(r"-") => |_| Token::Minus,
|
||||||
escape(r"*") => |_| Token::Times,
|
escape(r"*") => |_| Token::Times,
|
||||||
|
@ -100,6 +103,7 @@ regex_rules! {
|
||||||
r"[+-]?(?:[0-9]+[.])?[0-9]+" => |x| Token::NumLit(x.to_string())
|
r"[+-]?(?:[0-9]+[.])?[0-9]+" => |x| Token::NumLit(x.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Lexer<'s> {
|
pub struct Lexer<'s> {
|
||||||
file_id: usize,
|
file_id: usize,
|
||||||
source: &'s str,
|
source: &'s str,
|
||||||
|
@ -117,6 +121,10 @@ impl<'s> Lexer<'s> {
|
||||||
&self.source[self.pos..]
|
&self.source[self.pos..]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn continues_with(&self, pat: impl Pattern<'s>) -> bool {
|
||||||
|
self.remaining().starts_with(pat)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn next_token(&mut self) -> Option<Result<Sp<Token>, LexicalError>> {
|
pub fn next_token(&mut self) -> Option<Result<Sp<Token>, LexicalError>> {
|
||||||
loop {
|
loop {
|
||||||
if self.failed || self.pos >= self.source.len() {
|
if self.failed || self.pos >= self.source.len() {
|
||||||
|
@ -125,9 +133,7 @@ impl<'s> Lexer<'s> {
|
||||||
let remaining = self.remaining();
|
let remaining = self.remaining();
|
||||||
|
|
||||||
if remaining.starts_with(&['"', '\'', '`'][..]) {
|
if remaining.starts_with(&['"', '\'', '`'][..]) {
|
||||||
return self
|
return self.string_lit().map(|x| x.map(|(lo, segs, hi)| (lo, Token::StringLit(segs), hi)));
|
||||||
.string_lit()
|
|
||||||
.map(|x| x.map(|(lo, segs, hi)| (lo + self.offset, Token::StringLit(segs), hi + self.offset)));
|
|
||||||
} else {
|
} else {
|
||||||
let match_set = LEXER_REGEX_SET.matches(remaining);
|
let match_set = LEXER_REGEX_SET.matches(remaining);
|
||||||
let matched_token = match_set
|
let matched_token = match_set
|
||||||
|
@ -148,7 +154,7 @@ impl<'s> Lexer<'s> {
|
||||||
|
|
||||||
let tok_str = &self.source[self.pos..self.pos + len];
|
let tok_str = &self.source[self.pos..self.pos + len];
|
||||||
let old_pos = self.pos;
|
let old_pos = self.pos;
|
||||||
self.pos += len;
|
self.advance_by(len);
|
||||||
match LEXER_FNS[i](tok_str.to_string()) {
|
match LEXER_FNS[i](tok_str.to_string()) {
|
||||||
Token::Skip | Token::Comment => {}
|
Token::Skip | Token::Comment => {}
|
||||||
token => {
|
token => {
|
||||||
|
@ -159,54 +165,56 @@ impl<'s> Lexer<'s> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn advance_until_char_boundary(&mut self) {
|
fn advance_by(&mut self, n: usize) {
|
||||||
|
self.pos += n;
|
||||||
while self.pos < self.source.len() && !self.source.is_char_boundary(self.pos) {
|
while self.pos < self.source.len() && !self.source.is_char_boundary(self.pos) {
|
||||||
self.pos += 1;
|
self.pos += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn advance_until_one_of(&mut self, pat: &[char]) -> Option<char> {
|
fn advance_until_one_of<'a>(&mut self, pat: &[&'a str]) -> Option<&'a str> {
|
||||||
for (idx, cur) in self.remaining().char_indices() {
|
loop {
|
||||||
if let Some(matched) = pat.iter().find(|p| **p == cur) {
|
let remaining = self.remaining();
|
||||||
self.pos += idx + 1;
|
if remaining.is_empty() {
|
||||||
return Some(*matched);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.pos = self.source.len();
|
|
||||||
return None;
|
return None;
|
||||||
|
} else if let Some(matched) = pat.iter().find(|&&p| remaining.starts_with(p)) {
|
||||||
|
self.advance_by(matched.len());
|
||||||
|
return Some(matched);
|
||||||
|
} else {
|
||||||
|
self.advance_by(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn advance_until_unescaped_one_of(&mut self, pat: &[char]) -> Option<char> {
|
fn advance_until_unescaped_one_of<'a>(&mut self, pat: &[&'a str]) -> Option<&'a str> {
|
||||||
let mut pattern = pat.to_vec();
|
let mut pattern = pat.to_vec();
|
||||||
pattern.push('\\');
|
pattern.push("\\");
|
||||||
match self.advance_until_one_of(pattern.as_slice()) {
|
match self.advance_until_one_of(pattern.as_slice()) {
|
||||||
Some('\\') => {
|
Some("\\") => {
|
||||||
self.pos += 1;
|
self.advance_by(1);
|
||||||
self.advance_until_char_boundary();
|
|
||||||
self.advance_until_unescaped_one_of(pat)
|
self.advance_until_unescaped_one_of(pat)
|
||||||
}
|
}
|
||||||
result => result,
|
result => result,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn string_lit(&mut self) -> Option<Result<Sp<Vec<Sp<StrLitSegment>>>, LexicalError>> {
|
pub fn string_lit(&mut self) -> Option<Result<Sp<Vec<Sp<StrLitSegment>>>, LexicalError>> {
|
||||||
let quote = self.remaining().chars().next().unwrap();
|
let quote = self.remaining().chars().next()?.to_string();
|
||||||
let str_lit_start = self.pos;
|
let str_lit_start = self.pos;
|
||||||
self.pos += 1;
|
self.advance_by(quote.len());
|
||||||
self.advance_until_char_boundary();
|
|
||||||
|
|
||||||
let mut elements = Vec::new();
|
let mut elements = Vec::new();
|
||||||
let mut in_string_lit = true;
|
let mut in_string_lit = true;
|
||||||
loop {
|
loop {
|
||||||
if in_string_lit {
|
if in_string_lit {
|
||||||
let segment_start = self.pos - 1;
|
let segment_start = self.pos - quote.len();
|
||||||
|
|
||||||
let segment_ender = self.advance_until_unescaped_one_of(&['{', quote][..])?;
|
let segment_ender = self.advance_until_unescaped_one_of(&[STR_INTERPOLATION_START, "e])?;
|
||||||
let lit_content = &self.source[segment_start + 1..self.pos - 1];
|
let lit_content = &self.source[segment_start + quote.len()..self.pos - segment_ender.len()];
|
||||||
let lit_content = ESCAPE_REPLACE_REGEX.replace_all(lit_content, "$1").to_string();
|
let lit_content = ESCAPE_REPLACE_REGEX.replace_all(lit_content, "$1").to_string();
|
||||||
elements.push((segment_start + self.offset, StrLitSegment::Literal(lit_content), self.pos + self.offset));
|
elements.push((segment_start + self.offset, StrLitSegment::Literal(lit_content), self.pos + self.offset));
|
||||||
|
|
||||||
if segment_ender == '{' {
|
if segment_ender == STR_INTERPOLATION_START {
|
||||||
in_string_lit = false;
|
in_string_lit = false;
|
||||||
} else if segment_ender == quote {
|
} else if segment_ender == quote {
|
||||||
return Some(Ok((str_lit_start + self.offset, elements, self.pos + self.offset)));
|
return Some(Ok((str_lit_start + self.offset, elements, self.pos + self.offset)));
|
||||||
|
@ -214,14 +222,14 @@ impl<'s> Lexer<'s> {
|
||||||
} else {
|
} else {
|
||||||
let segment_start = self.pos;
|
let segment_start = self.pos;
|
||||||
let mut toks = Vec::new();
|
let mut toks = Vec::new();
|
||||||
while self.pos < self.source.len() && !self.remaining().starts_with('}') {
|
while self.pos < self.source.len() && !self.remaining().starts_with(STR_INTERPOLATION_END) {
|
||||||
match self.next_token()? {
|
match self.next_token()? {
|
||||||
Ok(tok) => toks.push(tok),
|
Ok(tok) => toks.push(tok),
|
||||||
Err(err) => return Some(Err(err)),
|
Err(err) => return Some(Err(err)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
elements.push((segment_start + self.offset, StrLitSegment::Interp(toks), self.pos + self.offset));
|
elements.push((segment_start + self.offset, StrLitSegment::Interp(toks), self.pos + self.offset));
|
||||||
self.pos += 1;
|
self.advance_by(STR_INTERPOLATION_END.len());
|
||||||
in_string_lit = true;
|
in_string_lit = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -254,20 +262,27 @@ impl std::fmt::Display for LexicalError {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use eww_shared_util::snapshot_string;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
|
||||||
#[test]
|
macro_rules! v {
|
||||||
fn test_simplexpr_lexer_basic() {
|
($x:literal) => {
|
||||||
insta::assert_debug_snapshot!(Lexer::new(0, 0, r#"bar "foo""#).collect_vec());
|
Lexer::new(0, 0, $x)
|
||||||
|
.map(|x| match x {
|
||||||
|
Ok((l, x, r)) => format!("({}, {:?}, {})", l, x, r),
|
||||||
|
Err(err) => format!("{}", err),
|
||||||
|
})
|
||||||
|
.join("\n")
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
snapshot_string! {
|
||||||
fn test_simplexpr_lexer_str_interpolate() {
|
basic => v!(r#"bar "foo""#),
|
||||||
insta::assert_debug_snapshot!(Lexer::new(0, 0, r#" "foo {2 * 2} bar" "#).collect_vec());
|
interpolation_1 => v!(r#" "foo ${2 * 2} bar" "#),
|
||||||
insta::assert_debug_snapshot!(Lexer::new(0, 0, r#" "foo {(2 * 2) + "{5 + 5}"} bar" "#).collect_vec());
|
interpolation_nested => v!(r#" "foo ${(2 * 2) + "${5 + 5}"} bar" "#),
|
||||||
insta::assert_debug_snapshot!(Lexer::new(0, 0, r#" "a\"b\{}" "#).collect_vec());
|
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)"#),
|
||||||
}
|
}
|
||||||
// insta::assert_debug_snapshot!(Lexer::new(0, 0, r#"(foo + - "()" "a\"b" true false [] 12.2)"#).collect_vec());
|
|
||||||
// insta::assert_debug_snapshot!(Lexer::new(0, 0, r#"" " + music"#).collect_vec());
|
|
||||||
// insta::assert_debug_snapshot!(Lexer::new(0, 0, r#"foo ; bar"#).collect_vec());
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
source: crates/simplexpr/src/parser/lexer.rs
|
||||||
|
expression: "v!(r#\"bar \"foo\"\"#)"
|
||||||
|
|
||||||
|
---
|
||||||
|
(0, Ident("bar"), 3)
|
||||||
|
(4, StringLit([(4, Literal("foo"), 9)]), 9)
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
source: crates/simplexpr/src/parser/lexer.rs
|
||||||
|
expression: "v!(\"foo ; bar\")"
|
||||||
|
|
||||||
|
---
|
||||||
|
(0, Ident("foo"), 3)
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
source: crates/simplexpr/src/parser/lexer.rs
|
||||||
|
expression: "v!(r#\" \"a\\\"b\\{}\" \"#)"
|
||||||
|
|
||||||
|
---
|
||||||
|
(1, StringLit([(1, Literal("a\"b{}"), 10)]), 10)
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
source: crates/simplexpr/src/parser/lexer.rs
|
||||||
|
expression: "v!(r#\" \"foo ${2 * 2} bar\" \"#)"
|
||||||
|
|
||||||
|
---
|
||||||
|
(1, StringLit([(1, Literal("foo "), 8), (8, Interp([(8, NumLit("2"), 9), (10, Times, 11), (12, NumLit("2"), 13)]), 13), (13, Literal(" bar"), 19)]), 19)
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
source: crates/simplexpr/src/parser/lexer.rs
|
||||||
|
expression: "v!(r#\" \"foo ${(2 * 2) + \"${5 + 5}\"} bar\" \"#)"
|
||||||
|
|
||||||
|
---
|
||||||
|
(1, StringLit([(1, Literal("foo "), 8), (8, Interp([(8, LPren, 9), (9, NumLit("2"), 10), (11, Times, 12), (13, NumLit("2"), 14), (14, RPren, 15), (16, Plus, 17), (18, StringLit([(18, Literal(""), 21), (21, Interp([(21, NumLit("5"), 22), (23, Plus, 24), (25, NumLit("5"), 26)]), 26), (26, Literal(""), 28)]), 28)]), 28), (28, Literal(" bar"), 34)]), 34)
|
|
@ -0,0 +1,17 @@
|
||||||
|
---
|
||||||
|
source: crates/simplexpr/src/parser/lexer.rs
|
||||||
|
expression: "v!(r#\"(foo + - \"()\" \"a\\\"b\" true false [] 12.2)\"#)"
|
||||||
|
|
||||||
|
---
|
||||||
|
(0, LPren, 1)
|
||||||
|
(1, Ident("foo"), 4)
|
||||||
|
(5, Plus, 6)
|
||||||
|
(7, Minus, 8)
|
||||||
|
(9, StringLit([(9, Literal("()"), 13)]), 13)
|
||||||
|
(14, StringLit([(14, Literal("a\"b"), 20)]), 20)
|
||||||
|
(21, True, 25)
|
||||||
|
(26, False, 31)
|
||||||
|
(32, LBrack, 33)
|
||||||
|
(33, RBrack, 34)
|
||||||
|
(35, NumLit("12.2"), 39)
|
||||||
|
(39, RPren, 40)
|
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
source: crates/simplexpr/src/parser/lexer.rs
|
||||||
|
expression: "v!(r#\"\" \" + music\"#)"
|
||||||
|
|
||||||
|
---
|
||||||
|
(0, StringLit([(0, Literal("\u{f001} "), 8)]), 8)
|
||||||
|
(9, Plus, 10)
|
||||||
|
(11, Ident("music"), 16)
|
|
@ -18,7 +18,7 @@ Config(
|
||||||
attrs: {
|
attrs: {
|
||||||
AttrName("arg"): AttrEntry(
|
AttrName("arg"): AttrEntry(
|
||||||
key_span: Span(52, 56, 0),
|
key_span: Span(52, 56, 0),
|
||||||
value: Literal(Span(57, 61, 0), DynVal("hi", Span(18446744073709551615, 18446744073709551615, 18446744073709551615))),
|
value: SimplExpr(Span(57, 61, 0), Literal(Span(57, 61, 0), DynVal("hi", Span(57, 61, 0)))),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -56,7 +56,7 @@ Config(
|
||||||
attrs: {
|
attrs: {
|
||||||
AttrName("arg"): AttrEntry(
|
AttrName("arg"): AttrEntry(
|
||||||
key_span: Span(468, 472, 0),
|
key_span: Span(468, 472, 0),
|
||||||
value: Literal(Span(473, 478, 0), DynVal("bla", Span(18446744073709551615, 18446744073709551615, 18446744073709551615))),
|
value: SimplExpr(Span(473, 478, 0), Literal(Span(473, 478, 0), DynVal("bla", Span(473, 478, 0)))),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -79,7 +79,7 @@ Config(
|
||||||
var_definitions: {
|
var_definitions: {
|
||||||
VarName("some_var"): VarDefinition(
|
VarName("some_var"): VarDefinition(
|
||||||
name: VarName("some_var"),
|
name: VarName("some_var"),
|
||||||
initial_value: DynVal("bla", Span(18446744073709551615, 18446744073709551615, 18446744073709551615)),
|
initial_value: DynVal("bla", Span(89, 94, 0)),
|
||||||
span: Span(72, 95, 0),
|
span: Span(72, 95, 0),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
|
@ -39,6 +39,9 @@ pub enum AstError {
|
||||||
#[error("{1}")]
|
#[error("{1}")]
|
||||||
ErrorNote(String, #[source] Box<AstError>),
|
ErrorNote(String, #[source] Box<AstError>),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
SimplExpr(#[from] simplexpr::error::Error),
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
ConversionError(#[from] dynval::ConversionError),
|
ConversionError(#[from] dynval::ConversionError),
|
||||||
|
|
||||||
|
@ -94,6 +97,7 @@ impl Spanned for AstError {
|
||||||
AstError::ParseError { file_id, source } => get_parse_error_span(*file_id, source),
|
AstError::ParseError { file_id, source } => get_parse_error_span(*file_id, source),
|
||||||
AstError::ErrorNote(_, err) => err.span(),
|
AstError::ErrorNote(_, err) => err.span(),
|
||||||
AstError::NoMoreElementsExpected(span) => *span,
|
AstError::NoMoreElementsExpected(span) => *span,
|
||||||
|
AstError::SimplExpr(err) => err.span(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -118,6 +118,7 @@ impl ToDiagnostic for AstError {
|
||||||
AstError::ErrorNote(note, source) => source.to_diagnostic().with_notes(vec![note.to_string()]),
|
AstError::ErrorNote(note, source) => source.to_diagnostic().with_notes(vec![note.to_string()]),
|
||||||
AstError::ValidationError(source) => source.to_diagnostic(),
|
AstError::ValidationError(source) => source.to_diagnostic(),
|
||||||
AstError::NoMoreElementsExpected(span) => gen_diagnostic!(self, span),
|
AstError::NoMoreElementsExpected(span) => gen_diagnostic!(self, span),
|
||||||
|
AstError::SimplExpr(source) => source.to_diagnostic(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -185,12 +186,13 @@ fn lalrpop_error_to_diagnostic<T: std::fmt::Display, E: Spanned + ToDiagnostic>(
|
||||||
impl ToDiagnostic for simplexpr::error::Error {
|
impl ToDiagnostic for simplexpr::error::Error {
|
||||||
fn to_diagnostic(&self) -> Diagnostic<usize> {
|
fn to_diagnostic(&self) -> Diagnostic<usize> {
|
||||||
use simplexpr::error::Error::*;
|
use simplexpr::error::Error::*;
|
||||||
|
dbg!(&self);
|
||||||
match self {
|
match self {
|
||||||
ParseError { source, file_id } => lalrpop_error_to_diagnostic(source, *file_id),
|
ParseError { source, file_id } => lalrpop_error_to_diagnostic(source, *file_id),
|
||||||
ConversionError(error) => error.to_diagnostic(),
|
ConversionError(error) => error.to_diagnostic(),
|
||||||
Eval(error) => error.to_diagnostic(),
|
Eval(error) => error.to_diagnostic(),
|
||||||
Other(error) => gen_diagnostic!(error),
|
Other(error) => gen_diagnostic!(error),
|
||||||
Spanned(span, error) => gen_diagnostic!(error, span),
|
Spanned(span, error) => error.to_diagnostic().with_label(span_to_primary_label(*span)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -205,7 +207,10 @@ impl ToDiagnostic for simplexpr::eval::EvalError {
|
||||||
fn to_diagnostic(&self) -> Diagnostic<usize> {
|
fn to_diagnostic(&self) -> Diagnostic<usize> {
|
||||||
use simplexpr::eval::EvalError::*;
|
use simplexpr::eval::EvalError::*;
|
||||||
match self {
|
match self {
|
||||||
UnresolvedVariable(name) | UnknownVariable(name) | NoVariablesAllowed(name) => gen_diagnostic! {
|
NoVariablesAllowed(name) => gen_diagnostic!(self),
|
||||||
|
// TODO the note here is confusing when it's an unknown variable being used _within_ a string literal / simplexpr
|
||||||
|
// it only really makes sense on top-level symbols
|
||||||
|
UnresolvedVariable(name) | UnknownVariable(name) => gen_diagnostic! {
|
||||||
msg = self,
|
msg = self,
|
||||||
note = format!("If you meant to use the literal value \"{}\", surround the value in quotes", name)
|
note = format!("If you meant to use the literal value \"{}\", surround the value in quotes", name)
|
||||||
},
|
},
|
||||||
|
|
|
@ -39,12 +39,29 @@ macro_rules! return_or_put_back {
|
||||||
impl<I: Iterator<Item = Ast>> AstIterator<I> {
|
impl<I: Iterator<Item = Ast>> AstIterator<I> {
|
||||||
return_or_put_back!(expect_symbol, AstType::Symbol, (Span, String) = Ast::Symbol(span, x) => (span, x));
|
return_or_put_back!(expect_symbol, AstType::Symbol, (Span, String) = Ast::Symbol(span, x) => (span, x));
|
||||||
|
|
||||||
return_or_put_back!(expect_literal, AstType::Literal, (Span, DynVal) = Ast::Literal(span, x) => (span, x));
|
// return_or_put_back!(expect_literal, AstType::Literal, (Span, DynVal) = Ast::Literal(span, x) => (span, x));
|
||||||
|
|
||||||
return_or_put_back!(expect_list, AstType::List, (Span, Vec<Ast>) = Ast::List(span, x) => (span, x));
|
return_or_put_back!(expect_list, AstType::List, (Span, Vec<Ast>) = Ast::List(span, x) => (span, x));
|
||||||
|
|
||||||
return_or_put_back!(expect_array, AstType::Array, (Span, Vec<Ast>) = Ast::Array(span, x) => (span, x));
|
return_or_put_back!(expect_array, AstType::Array, (Span, Vec<Ast>) = Ast::Array(span, x) => (span, x));
|
||||||
|
|
||||||
|
pub fn expect_literal(&mut self) -> AstResult<(Span, DynVal)> {
|
||||||
|
match self.expect_any()? {
|
||||||
|
// Ast::List(_, _) => todo!(),
|
||||||
|
// Ast::Array(_, _) => todo!(),
|
||||||
|
// Ast::Keyword(_, _) => todo!(),
|
||||||
|
// Ast::Symbol(_, _) => todo!(),
|
||||||
|
// Ast::Literal(_, _) => todo!(),
|
||||||
|
Ast::SimplExpr(span, expr) => Ok((span, dbg!(expr.eval_no_vars()).map_err(|e| AstError::SimplExpr(e.into()))?)),
|
||||||
|
other => {
|
||||||
|
let span = other.span();
|
||||||
|
let actual_type = other.expr_type();
|
||||||
|
self.put_back(other);
|
||||||
|
Err(AstError::WrongExprType(span, AstType::Literal, actual_type))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new(span: Span, iter: I) -> Self {
|
pub fn new(span: Span, iter: I) -> Self {
|
||||||
AstIterator { remaining_span: span, iter: itertools::put_back(iter) }
|
AstIterator { remaining_span: span, iter: itertools::put_back(iter) }
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use regex::{Regex, RegexSet};
|
use regex::{escape, Regex, RegexSet};
|
||||||
|
use simplexpr::parser::lexer::{STR_INTERPOLATION_END, STR_INTERPOLATION_START};
|
||||||
|
|
||||||
use super::parse_error;
|
use super::parse_error;
|
||||||
use eww_shared_util::{AttrName, Span, VarName};
|
use eww_shared_util::{AttrName, Span, Spanned, VarName};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
pub enum Token {
|
pub enum Token {
|
||||||
|
@ -12,11 +13,10 @@ pub enum Token {
|
||||||
RBrack,
|
RBrack,
|
||||||
True,
|
True,
|
||||||
False,
|
False,
|
||||||
StrLit(String),
|
|
||||||
NumLit(String),
|
NumLit(String),
|
||||||
Symbol(String),
|
Symbol(String),
|
||||||
Keyword(String),
|
Keyword(String),
|
||||||
SimplExpr(String),
|
SimplExpr(Vec<(usize, simplexpr::parser::lexer::Token, usize)>),
|
||||||
Comment,
|
Comment,
|
||||||
Skip,
|
Skip,
|
||||||
}
|
}
|
||||||
|
@ -30,11 +30,10 @@ impl std::fmt::Display for Token {
|
||||||
Token::RBrack => write!(f, "']'"),
|
Token::RBrack => write!(f, "']'"),
|
||||||
Token::True => write!(f, "true"),
|
Token::True => write!(f, "true"),
|
||||||
Token::False => write!(f, "false"),
|
Token::False => write!(f, "false"),
|
||||||
Token::StrLit(x) => write!(f, "\"{}\"", x),
|
|
||||||
Token::NumLit(x) => write!(f, "{}", x),
|
Token::NumLit(x) => write!(f, "{}", x),
|
||||||
Token::Symbol(x) => write!(f, "{}", x),
|
Token::Symbol(x) => write!(f, "{}", x),
|
||||||
Token::Keyword(x) => write!(f, "{}", x),
|
Token::Keyword(x) => write!(f, "{}", x),
|
||||||
Token::SimplExpr(x) => write!(f, "{{{}}}", x),
|
Token::SimplExpr(x) => write!(f, "{{{:?}}}", x.iter().map(|x| &x.1)),
|
||||||
Token::Comment => write!(f, ""),
|
Token::Comment => write!(f, ""),
|
||||||
Token::Skip => write!(f, ""),
|
Token::Skip => write!(f, ""),
|
||||||
}
|
}
|
||||||
|
@ -42,7 +41,7 @@ impl std::fmt::Display for Token {
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! regex_rules {
|
macro_rules! regex_rules {
|
||||||
($( $regex:literal => $token:expr),*) => {
|
($( $regex:expr => $token:expr),*) => {
|
||||||
static LEXER_REGEX_SET: Lazy<RegexSet> = Lazy::new(|| { RegexSet::new(&[
|
static LEXER_REGEX_SET: Lazy<RegexSet> = Lazy::new(|| { RegexSet::new(&[
|
||||||
$(format!("^{}", $regex)),*
|
$(format!("^{}", $regex)),*
|
||||||
]).unwrap()});
|
]).unwrap()});
|
||||||
|
@ -58,15 +57,12 @@ macro_rules! regex_rules {
|
||||||
static ESCAPE_REPLACE_REGEX: Lazy<regex::Regex> = Lazy::new(|| Regex::new(r"\\(.)").unwrap());
|
static ESCAPE_REPLACE_REGEX: Lazy<regex::Regex> = Lazy::new(|| Regex::new(r"\\(.)").unwrap());
|
||||||
|
|
||||||
regex_rules! {
|
regex_rules! {
|
||||||
r"\(" => |_| Token::LPren,
|
escape("(") => |_| Token::LPren,
|
||||||
r"\)" => |_| Token::RPren,
|
escape(")") => |_| Token::RPren,
|
||||||
r"\[" => |_| Token::LBrack,
|
escape("[") => |_| Token::LBrack,
|
||||||
r"\]" => |_| Token::RBrack,
|
escape("]") => |_| Token::RBrack,
|
||||||
r"true" => |_| Token::True,
|
escape("true") => |_| Token::True,
|
||||||
r"false" => |_| Token::False,
|
escape("false") => |_| Token::False,
|
||||||
r#""(?:[^"\\]|\\.)*""# => |x| Token::StrLit(ESCAPE_REPLACE_REGEX.replace_all(&x, "$1").to_string()),
|
|
||||||
r#"`(?:[^`\\]|\\.)*`"# => |x| Token::StrLit(ESCAPE_REPLACE_REGEX.replace_all(&x, "$1").to_string()),
|
|
||||||
r#"'(?:[^'\\]|\\.)*'"# => |x| Token::StrLit(ESCAPE_REPLACE_REGEX.replace_all(&x, "$1").to_string()),
|
|
||||||
r#"[+-]?(?:[0-9]+[.])?[0-9]+"# => |x| Token::NumLit(x),
|
r#"[+-]?(?:[0-9]+[.])?[0-9]+"# => |x| Token::NumLit(x),
|
||||||
r#":[^\s\)\]}]+"# => |x| Token::Keyword(x),
|
r#":[^\s\)\]}]+"# => |x| Token::Keyword(x),
|
||||||
r#"[a-zA-Z_!\?<>/\.\*-\+\-][^\s{}\(\)\[\](){}]*"# => |x| Token::Symbol(x),
|
r#"[a-zA-Z_!\?<>/\.\*-\+\-][^\s{}\(\)\[\](){}]*"# => |x| Token::Symbol(x),
|
||||||
|
@ -85,9 +81,53 @@ impl Lexer {
|
||||||
pub fn new(file_id: usize, source: String) -> Self {
|
pub fn new(file_id: usize, source: String) -> Self {
|
||||||
Lexer { source, file_id, failed: false, pos: 0 }
|
Lexer { source, file_id, failed: false, pos: 0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn string_lit(&mut self) -> Option<Result<(usize, Token, usize), parse_error::ParseError>> {
|
||||||
|
let mut simplexpr_lexer = simplexpr::parser::lexer::Lexer::new(self.file_id, self.pos, &self.source[self.pos..]);
|
||||||
|
match simplexpr_lexer.string_lit() {
|
||||||
|
Some(Ok((lo, segments, hi))) => {
|
||||||
|
self.pos = hi;
|
||||||
|
self.advance_until_char_boundary();
|
||||||
|
Some(Ok((lo, Token::SimplExpr(vec![(lo, simplexpr::parser::lexer::Token::StringLit(segments), hi)]), hi)))
|
||||||
|
}
|
||||||
|
Some(Err(e)) => Some(Err(parse_error::ParseError::LexicalError(e.0))),
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO string literal interpolation stuff by looking for indexes of {{ and }}?
|
fn simplexpr(&mut self) -> Option<Result<(usize, Token, usize), parse_error::ParseError>> {
|
||||||
|
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 end = self.pos;
|
||||||
|
loop {
|
||||||
|
match simplexpr_lexer.next_token() {
|
||||||
|
Some(Ok((lo, tok, hi))) => {
|
||||||
|
end = hi;
|
||||||
|
toks.push((lo, tok, hi));
|
||||||
|
}
|
||||||
|
Some(Err(err)) => {
|
||||||
|
dbg!(&simplexpr_lexer);
|
||||||
|
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())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => return None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn advance_until_char_boundary(&mut self) {
|
||||||
|
while self.pos < self.source.len() && !self.source.is_char_boundary(self.pos) {
|
||||||
|
self.pos += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Iterator for Lexer {
|
impl Iterator for Lexer {
|
||||||
type Item = Result<(usize, Token, usize), parse_error::ParseError>;
|
type Item = Result<(usize, Token, usize), parse_error::ParseError>;
|
||||||
|
@ -97,45 +137,17 @@ impl Iterator for Lexer {
|
||||||
if self.failed || self.pos >= self.source.len() {
|
if self.failed || self.pos >= self.source.len() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let string = &self.source[self.pos..];
|
let remaining = &self.source[self.pos..];
|
||||||
|
if remaining.starts_with(&['"', '\'', '`'][..]) {
|
||||||
if string.starts_with('{') {
|
return self.string_lit();
|
||||||
let expr_start = self.pos;
|
} else if remaining.starts_with('{') {
|
||||||
let mut in_string = None;
|
return self.simplexpr();
|
||||||
loop {
|
|
||||||
if self.pos >= self.source.len() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
while !self.source.is_char_boundary(self.pos) {
|
|
||||||
self.pos += 1;
|
|
||||||
}
|
|
||||||
let string = &self.source[self.pos..];
|
|
||||||
|
|
||||||
if string.starts_with('}') && in_string.is_none() {
|
|
||||||
self.pos += 1;
|
|
||||||
let tok_str = &self.source[expr_start..self.pos];
|
|
||||||
return Some(Ok((expr_start, Token::SimplExpr(tok_str.to_string()), self.pos - 1)));
|
|
||||||
} else if string.starts_with('"') || string.starts_with('\'') || string.starts_with('`') {
|
|
||||||
if let Some(quote) = in_string {
|
|
||||||
if string.starts_with(quote) {
|
|
||||||
in_string = None;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
in_string = Some(string.chars().next().unwrap());
|
let match_set = LEXER_REGEX_SET.matches(remaining);
|
||||||
}
|
|
||||||
self.pos += 1;
|
|
||||||
} else if string.starts_with("\\\"") {
|
|
||||||
self.pos += 2;
|
|
||||||
} else {
|
|
||||||
self.pos += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let match_set = LEXER_REGEX_SET.matches(string);
|
|
||||||
let matched_token = match_set
|
let matched_token = match_set
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|i: usize| {
|
.map(|i: usize| {
|
||||||
let m = LEXER_REGEXES[i].find(string).unwrap();
|
let m = LEXER_REGEXES[i].find(remaining).unwrap();
|
||||||
(m.end(), i)
|
(m.end(), i)
|
||||||
})
|
})
|
||||||
.min_by_key(|(_, x)| *x);
|
.min_by_key(|(_, x)| *x);
|
||||||
|
@ -163,12 +175,28 @@ impl Iterator for Lexer {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[test]
|
mod test {
|
||||||
fn test_yuck_lexer() {
|
|
||||||
|
use super::*;
|
||||||
|
use eww_shared_util::snapshot_string;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
insta::assert_debug_snapshot!(Lexer::new(0, r#"(foo + - "text" )"#.to_string()).collect_vec());
|
|
||||||
insta::assert_debug_snapshot!(Lexer::new(0, r#"{ bla "} \" }" " \" "}"#.to_string()).collect_vec());
|
macro_rules! v {
|
||||||
insta::assert_debug_snapshot!(Lexer::new(0, r#""< \" >""#.to_string()).collect_vec());
|
($x:literal) => {
|
||||||
insta::assert_debug_snapshot!(Lexer::new(0, r#"{ " " + music}"#.to_string()).collect_vec());
|
Lexer::new(0, 0, $x)
|
||||||
insta::assert_debug_snapshot!(Lexer::new(0, r#"{ " } ' }" }"#.to_string()).collect_vec());
|
.map(|x| match x {
|
||||||
|
Ok((l, x, r)) => format!("({}, {:?}, {})", l, x, r),
|
||||||
|
Err(err) => format!("{}", err),
|
||||||
|
})
|
||||||
|
.join("\n")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshot_string! {
|
||||||
|
basic => r#"(foo + - "text" )"#,
|
||||||
|
escaped_strings => r#"{ bla "} \" }" " \" "}"#,
|
||||||
|
escaped_quote => r#""< \" >""#,
|
||||||
|
char_boundary => r#"{ " " + music}"#,
|
||||||
|
quotes_in_quotes => r#"{ " } ' }" }"#,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,11 +18,10 @@ extern {
|
||||||
"]" => Token::RBrack,
|
"]" => Token::RBrack,
|
||||||
"true" => Token::True,
|
"true" => Token::True,
|
||||||
"false" => Token::False,
|
"false" => Token::False,
|
||||||
"string" => Token::StrLit(<String>),
|
|
||||||
"number" => Token::NumLit(<String>),
|
"number" => Token::NumLit(<String>),
|
||||||
"symbol" => Token::Symbol(<String>),
|
"symbol" => Token::Symbol(<String>),
|
||||||
"keyword" => Token::Keyword(<String>),
|
"keyword" => Token::Keyword(<String>),
|
||||||
"simplexpr" => Token::SimplExpr(<String>),
|
"simplexpr" => Token::SimplExpr(<Vec<(usize, simplexpr::parser::lexer::Token, usize)>>),
|
||||||
"comment" => Token::Comment,
|
"comment" => Token::Comment,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,22 +44,17 @@ Keyword: Ast = <l:@L> <x:"keyword"> <r:@R> => Ast::Keyword(Span(l, r, file_id),
|
||||||
Symbol: Ast = <l:@L> <x:"symbol"> <r:@R> => Ast::Symbol(Span(l, r, file_id), x.to_string());
|
Symbol: Ast = <l:@L> <x:"symbol"> <r:@R> => Ast::Symbol(Span(l, r, file_id), x.to_string());
|
||||||
|
|
||||||
Literal: String = {
|
Literal: String = {
|
||||||
<StrLit> => <>,
|
|
||||||
<Num> => <>,
|
<Num> => <>,
|
||||||
<Bool> => <>,
|
<Bool> => <>,
|
||||||
};
|
};
|
||||||
|
|
||||||
StrLit: String = {
|
|
||||||
<x:"string"> => {
|
|
||||||
x[1..x.len() - 1].to_owned()
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
SimplExpr: SimplExpr = {
|
SimplExpr: SimplExpr = {
|
||||||
<l:@L> <x:"simplexpr"> =>? {
|
<l:@L> <x:"simplexpr"> =>? {
|
||||||
let expr = x[1..x.len() - 1].to_string();
|
let parser = simplexpr::simplexpr_parser::ExprParser::new();
|
||||||
simplexpr::parse_string(l + 1, file_id, &expr).map_err(|e| {
|
parser.parse(file_id, x.into_iter().map(Ok))
|
||||||
ParseError::User { error: parse_error::ParseError::SimplExpr(e) }})
|
.map_err(|e| ParseError::User {
|
||||||
|
error: parse_error::ParseError::SimplExpr(simplexpr::error::Error::from_parse_error(file_id, e))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
source: crates/yuck/src/parser/lexer.rs
|
||||||
|
expression: "r#\"(foo + - \"text\" )\"#"
|
||||||
|
|
||||||
|
---
|
||||||
|
(foo + - "text" )
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
source: crates/yuck/src/parser/lexer.rs
|
||||||
|
expression: "r#\"{ \" \" + music}\"#"
|
||||||
|
|
||||||
|
---
|
||||||
|
{ " " + music}
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
source: crates/yuck/src/parser/lexer.rs
|
||||||
|
expression: "r#\"\"< \\\" >\"\"#"
|
||||||
|
|
||||||
|
---
|
||||||
|
"< \" >"
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
source: crates/yuck/src/parser/lexer.rs
|
||||||
|
expression: "r#\"{ bla \"} \\\" }\" \" \\\" \"}\"#"
|
||||||
|
|
||||||
|
---
|
||||||
|
{ bla "} \" }" " \" "}
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
source: crates/yuck/src/parser/lexer.rs
|
||||||
|
expression: "r#\"{ \" } ' }\" }\"#"
|
||||||
|
|
||||||
|
---
|
||||||
|
{ " } ' }" }
|
|
@ -6,9 +6,47 @@ expression: "Lexer::new(0, r#\"{ bla \"} \\\" }\" \" \\\" \"}\"#.to_string()).co
|
||||||
[
|
[
|
||||||
Ok(
|
Ok(
|
||||||
(
|
(
|
||||||
0,
|
2,
|
||||||
SimplExpr(
|
SimplExpr(
|
||||||
"{ bla \"} \\\" }\" \" \\\" \"}",
|
[
|
||||||
|
(
|
||||||
|
2,
|
||||||
|
Ident(
|
||||||
|
"bla",
|
||||||
|
),
|
||||||
|
5,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
6,
|
||||||
|
StringLit(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
6,
|
||||||
|
Literal(
|
||||||
|
"} \" }",
|
||||||
|
),
|
||||||
|
14,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
14,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
15,
|
||||||
|
StringLit(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
15,
|
||||||
|
Literal(
|
||||||
|
" \" ",
|
||||||
|
),
|
||||||
|
21,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
21,
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
21,
|
21,
|
||||||
),
|
),
|
||||||
|
|
|
@ -7,8 +7,24 @@ expression: "Lexer::new(0, r#\"\"< \\\" >\"\"#.to_string()).collect_vec()"
|
||||||
Ok(
|
Ok(
|
||||||
(
|
(
|
||||||
0,
|
0,
|
||||||
StrLit(
|
SimplExpr(
|
||||||
"\"< \" >\"",
|
[
|
||||||
|
(
|
||||||
|
0,
|
||||||
|
StringLit(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
0,
|
||||||
|
Literal(
|
||||||
|
"< \" >",
|
||||||
|
),
|
||||||
|
8,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
8,
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
8,
|
8,
|
||||||
),
|
),
|
||||||
|
|
|
@ -6,9 +6,37 @@ expression: "Lexer::new(0, r#\"{ \" \" + music}\"#.to_string()).collect_vec
|
||||||
[
|
[
|
||||||
Ok(
|
Ok(
|
||||||
(
|
(
|
||||||
0,
|
2,
|
||||||
SimplExpr(
|
SimplExpr(
|
||||||
"{ \"\u{f001} \" + music}",
|
[
|
||||||
|
(
|
||||||
|
2,
|
||||||
|
StringLit(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
2,
|
||||||
|
Literal(
|
||||||
|
"\u{f001} ",
|
||||||
|
),
|
||||||
|
10,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
10,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
11,
|
||||||
|
Plus,
|
||||||
|
12,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
13,
|
||||||
|
Ident(
|
||||||
|
"music",
|
||||||
|
),
|
||||||
|
18,
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
18,
|
18,
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
---
|
|
||||||
source: crates/yuck/src/parser/lexer.rs
|
|
||||||
expression: "Lexer::new(0, r#\"{ \" } ' }\" }\"#.to_string()).collect_vec()"
|
|
||||||
|
|
||||||
---
|
|
||||||
[
|
|
||||||
Ok(
|
|
||||||
(
|
|
||||||
0,
|
|
||||||
SimplExpr(
|
|
||||||
"{ \" } ' }\" }",
|
|
||||||
),
|
|
||||||
11,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -41,8 +41,24 @@ expression: "Lexer::new(0, r#\"(foo + - \"text\" )\"#.to_string()).collect_vec()
|
||||||
Ok(
|
Ok(
|
||||||
(
|
(
|
||||||
9,
|
9,
|
||||||
StrLit(
|
SimplExpr(
|
||||||
"\"text\"",
|
[
|
||||||
|
(
|
||||||
|
9,
|
||||||
|
StringLit(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
9,
|
||||||
|
Literal(
|
||||||
|
"text",
|
||||||
|
),
|
||||||
|
15,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
15,
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
15,
|
15,
|
||||||
),
|
),
|
||||||
|
|
|
@ -4,5 +4,5 @@ expression: "p.parse(0, Lexer::new(0, r#\"(test \"hi\")\"#.to_string()))"
|
||||||
|
|
||||||
---
|
---
|
||||||
Ok(
|
Ok(
|
||||||
(test "hi"),
|
(test {"hi"}),
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,5 +4,5 @@ expression: "p.parse(0, Lexer::new(0, r#\"(test \"h\\\"i\")\"#.to_string()))"
|
||||||
|
|
||||||
---
|
---
|
||||||
Ok(
|
Ok(
|
||||||
(test "h"i"),
|
(test {"h"i"}),
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,5 +4,5 @@ expression: "p.parse(0, Lexer::new(0, r#\"(test \" hi \")\"#.to_string()))"
|
||||||
|
|
||||||
---
|
---
|
||||||
Ok(
|
Ok(
|
||||||
(test " hi "),
|
(test {" hi "}),
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,5 +4,5 @@ expression: "p.parse(0, Lexer::new(0, \"\\\"h\\\\\\\"i\\\"\".to_string()))"
|
||||||
|
|
||||||
---
|
---
|
||||||
Ok(
|
Ok(
|
||||||
"h"i",
|
{"h"i"},
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
(workspaces)
|
(workspaces)
|
||||||
(music)
|
(music)
|
||||||
(sidestuff)))
|
(sidestuff)))
|
||||||
|
|
||||||
(defwidget sidestuff []
|
(defwidget sidestuff []
|
||||||
(box :class "sidestuff" :orientation "h" :space-evenly false :halign "end"
|
(box :class "sidestuff" :orientation "h" :space-evenly false :halign "end"
|
||||||
(slider-vol)
|
(slider-vol)
|
||||||
|
@ -23,12 +24,12 @@
|
||||||
|
|
||||||
(defwidget music []
|
(defwidget music []
|
||||||
(box :class "music" :orientation "h" :space-evenly false :halign "center"
|
(box :class "music" :orientation "h" :space-evenly false :halign "center"
|
||||||
{ ' ' + music}))
|
' ${music}'))
|
||||||
|
|
||||||
(defwidget slider-vol []
|
(defwidget slider-vol []
|
||||||
(box :class "slider-vol" :orientation "h" :space-evenly "false"
|
(box :class "slider-vol" :orientation "h" :space-evenly "false"
|
||||||
(box :class "label-vol" ""
|
(box :class "label-vol" ""
|
||||||
(scale :min 0 :max 101 :value volume :onchange "amixer -D pulse sset Master {}%"))))
|
(scale :min 0 :max 101 :value volume :onchange "amixer -D pulse sset Master \{}%"))))
|
||||||
|
|
||||||
(defwidget slider-ram []
|
(defwidget slider-ram []
|
||||||
(box :orientation "h" :class "slider-ram" :space-evenly false
|
(box :orientation "h" :class "slider-ram" :space-evenly false
|
||||||
|
@ -38,14 +39,10 @@
|
||||||
|
|
||||||
(defwidget time []
|
(defwidget time []
|
||||||
(box :class "time"
|
(box :class "time"
|
||||||
{"{hour}:{min} {month + "the month" * 2} {number_day}, {year_full}"}))
|
"${hour}:${min} ${month} ${number_day}, ${year_full}"))
|
||||||
;{hour + ":" + min + " " + month + " " + number_day + ", " + year_full}))
|
|
||||||
|
|
||||||
(defvar music "bruh")
|
(defpoll music :interval "5s" "playerctl metadata --format '{{ artist }} - {{ title }}' || true")
|
||||||
|
(defpoll volume :interval "1s" "scripts/getvol")
|
||||||
;(defpoll music :interval "5s" "playerctl metadata --format '{{ artist }} - {{ title }}' || true")
|
|
||||||
(defvar volume "20")
|
|
||||||
;(defpoll volume :interval "16s" "scripts/getvol")
|
|
||||||
|
|
||||||
(defpoll number_day :interval "5h" "date '+%d'")
|
(defpoll number_day :interval "5h" "date '+%d'")
|
||||||
(defpoll month :interval "10h" "date '+%b'")
|
(defpoll month :interval "10h" "date '+%b'")
|
||||||
|
@ -61,5 +58,3 @@
|
||||||
:geometry (geometry :x "0%" :y "0%" :width "100%" :height "4%")
|
:geometry (geometry :x "0%" :y "0%" :width "100%" :height "4%")
|
||||||
:reserve (struts :side "top" :distance "4%")
|
:reserve (struts :side "top" :distance "4%")
|
||||||
(bar))
|
(bar))
|
||||||
|
|
||||||
; asdf
|
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
amixer -D pulse sget Master | grep 'Left:' | awk -F'[][]' '{ print $2 }' | tr -d '%'
|
amixer -D pulse sget Master | grep 'Left:' | awk -F'[][]' '{ print $2 }' | tr -d '%' | head -1
|
||||||
|
|
Loading…
Add table
Reference in a new issue