diff --git a/crates/simplexpr/src/dynval.rs b/crates/simplexpr/src/dynval.rs index e730d0a9..81dd74dd 100644 --- a/crates/simplexpr/src/dynval.rs +++ b/crates/simplexpr/src/dynval.rs @@ -1,7 +1,13 @@ use eww_shared_util::{Span, Spanned}; use itertools::Itertools; use serde::{Deserialize, Serialize}; -use std::{convert::TryFrom, fmt, iter::FromIterator, str::FromStr}; +use std::{ + convert::TryFrom, + fmt, + iter::FromIterator, + ops::{Bound, RangeBounds}, + str::FromStr, +}; pub type Result = std::result::Result; @@ -232,6 +238,23 @@ impl DynVal { .cloned() .ok_or_else(|| ConversionError { value: self.clone(), target_type: "json-object", source: None }) } + + pub fn as_range(&self) -> Result<(Bound, Bound)> { + let range = self.as_string()?; + let Some((from, to)) = range.split_once("..") else { + return Err(ConversionError { value: self.clone(), target_type: "range", source: None }) + }; + let from = match from { + "" => Bound::Unbounded, + v => DynVal::from_str(v)?.as_f64().map(Bound::Included)?, + }; + let to = match to { + "" => Bound::Unbounded, + v if v.starts_with('=') => DynVal::from_str(&v[1..])?.as_f64().map(Bound::Included)?, + v => DynVal::from_str(v)?.as_f64().map(Bound::Excluded)?, + }; + Ok((from, to)) + } } #[cfg(test)] diff --git a/crates/simplexpr/src/eval.rs b/crates/simplexpr/src/eval.rs index a4ff8bad..d6447edd 100644 --- a/crates/simplexpr/src/eval.rs +++ b/crates/simplexpr/src/eval.rs @@ -8,6 +8,7 @@ use eww_shared_util::{Span, Spanned, VarName}; use std::{ collections::HashMap, convert::{TryFrom, TryInto}, + ops::RangeBounds, }; #[derive(Debug, thiserror::Error)] @@ -33,6 +34,10 @@ pub enum EvalError { #[error("Unable to index into value {0}")] CannotIndex(String), + // TODO useful error variant? + #[error("Function {0} failed: {1}")] + FunctionError(&'static str, String), + #[error("Json operation failed: {0}")] SerdeError(#[from] serde_json::error::Error), @@ -341,6 +346,31 @@ fn call_expr_function(name: &str, args: Vec) -> Result Ok(DynVal::from(json.as_json_object()?.len() as i32)), _ => Err(EvalError::WrongArgCount(name.to_string())), }, + "range_select" => match args.as_slice() { + [value, map] => { + let value = value.as_f64()?; + let map = map.as_json_array()?; + for mapping in map { + let mapping = DynVal::from(&mapping); + let array = mapping.as_json_array()?; + let [range, result] = array.as_slice() else { + // TODO what error to use here + // TODO we could also add `DynVal::as_tuple()` + return Err(ConversionError{ + value: mapping, + target_type: "[string, string]", + source: None + }.into()) + }; + let range = DynVal::from(range).as_range()?; + if range.contains(&value) { + return Ok(result.into()); + } + } + Err(EvalError::FunctionError("range_select", format!("No entry matched value: {value}"))) + } + _ => Err(EvalError::WrongArgCount(name.to_string())), + }, _ => Err(EvalError::UnknownFunction(name.to_string())), } diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 3d6465bf..f596d47f 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "nightly-2022-08-27" +channel = "nightly-2022-11-25" components = [ "rust-src" ] profile = "default"