use std::borrow::Cow; use std::sync::Arc; use imbl_value::{InternedString, Value}; use linear_map::LinearMap; use pest::iterators::Pairs; use pest::Parser; use rand::SeedableRng; use startos::config::util::STATIC_NULL; use startos::config::Config; #[derive(Parser)] #[grammar = "config/rule_parser.pest"] struct RuleParser; lazy_static::lazy_static! { static ref NUM_PREC_CLIMBER: pest::prec_climber::PrecClimber = { use pest::prec_climber::*; use Rule::*; use Assoc::*; PrecClimber::new(vec![ Operator::new(add, Left) | Operator::new(sub, Left), Operator::new(mul, Left) | Operator::new(div, Left), Operator::new(pow, Right) ]) }; static ref STR_PREC_CLIMBER: pest::prec_climber::PrecClimber = { use pest::prec_climber::*; use Rule::*; use Assoc::*; PrecClimber::new(vec![ Operator::new(add, Left) ]) }; static ref BOOL_PREC_CLIMBER: pest::prec_climber::PrecClimber = { use pest::prec_climber::*; use Rule::*; use Assoc::*; PrecClimber::new(vec![ Operator::new(or, Left), Operator::new(xor, Left), Operator::new(and, Left) ]) }; } pub type Accessor = Box< dyn for<'a> Fn(&'a Value, &LinearMap<&str, Cow>) -> VarRes<&'a Value> + Send + Sync, >; pub type AccessorMut = Box< dyn for<'a> Fn(&'a mut Value, &LinearMap<&str, Cow>) -> Option<&'a mut Value> + Send + Sync, >; pub type CompiledExpr = Box>) -> T + Send + Sync>; pub type CompiledReference = Box< dyn for<'a> Fn(&'a mut Config, &LinearMap<&str, Cow>) -> Option<&'a mut Value> + Send + Sync, >; pub type Mutator = Box>) + Send + Sync>; pub type CompiledRule = Box>) -> bool + Send + Sync>; pub type CompiledRuleRes = Result; #[derive(Clone)] pub struct ConfigRule { pub src: String, pub compiled: Arc, } impl std::fmt::Debug for ConfigRule { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("ConfigRule") .field("src", &self.src) .field("compiled", &"Fn(&Config, &Config) -> bool") .finish() } } impl<'de> serde::de::Deserialize<'de> for ConfigRule { fn deserialize(deserializer: D) -> Result where D: serde::de::Deserializer<'de>, { let src = String::deserialize(deserializer)?; let compiled = compile(&src).map_err(serde::de::Error::custom)?; Ok(ConfigRule { src, compiled: Arc::new(compiled), }) } } impl serde::ser::Serialize for ConfigRule { fn serialize(&self, serializer: S) -> Result where S: serde::ser::Serializer, { serializer.serialize_str(&self.src) } } #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] pub struct ConfigRuleEntry { pub rule: ConfigRule, pub description: String, } impl ConfigRuleEntry { pub fn check( &self, cfg: &Config, cfgs: &LinearMap<&str, Cow>, ) -> Result<(), anyhow::Error> { if !(self.rule.compiled)(cfg, cfgs) { return Err(anyhow::anyhow!("{}", self.description)); } Ok(()) } } #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "kebab-case")] pub enum SetVariant { To(String), ToValue(Value), ToEntropy(super::spec::Entropy), } #[derive(Clone)] pub enum SuggestionVariant { Set { var: String, to: SetVariant, compiled: Arc, }, Delete { src: String, compiled: Arc, }, Push { to: String, value: Value, compiled: Arc, }, } impl SuggestionVariant { pub fn apply<'a>( &self, id: &'a str, cfg: &mut Config, cfgs: &mut LinearMap<&'a str, Cow>, ) { match self { SuggestionVariant::Set { ref compiled, .. } => compiled(cfg, cfgs), SuggestionVariant::Delete { ref compiled, .. } => compiled(cfg, cfgs), SuggestionVariant::Push { ref compiled, .. } => compiled(cfg, cfgs), } cfgs.insert(id, Cow::Owned(cfg.clone())); } } impl std::fmt::Debug for SuggestionVariant { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { SuggestionVariant::Set { ref var, ref to, .. } => f .debug_struct("SuggestionVariant::Set") .field("var", var) .field("to", to) .field("compiled", &"Fn(&mut Config, Config)") .finish(), SuggestionVariant::Delete { ref src, .. } => f .debug_struct("SuggestionVariant::Delete") .field("src", src) .field("compiled", &"Fn(&mut Config, Config)") .finish(), SuggestionVariant::Push { ref to, ref value, .. } => f .debug_struct("SuggestionVariant::Delete") .field("to", to) .field("value", value) .field("compiled", &"Fn(&mut Config, Config)") .finish(), } } } impl<'de> serde::de::Deserialize<'de> for SuggestionVariant { fn deserialize(deserializer: D) -> Result where D: serde::de::Deserializer<'de>, { #[derive(serde::Deserialize)] enum _SuggestionVariant { SET { var: String, #[serde(flatten)] to: SetVariant, }, DELETE(String), PUSH { to: String, value: Value, }, } let raw = _SuggestionVariant::deserialize(deserializer)?; Ok(match raw { _SuggestionVariant::SET { var, to } => SuggestionVariant::Set { compiled: Arc::new( compile_set_action(&var, &to).map_err(serde::de::Error::custom)?, ), to: to, var: var, }, _SuggestionVariant::DELETE(src) => SuggestionVariant::Delete { compiled: Arc::new( compile_del_action( RuleParser::parse(Rule::del_action, &src) .map_err(serde::de::Error::custom)?, ) .map_err(serde::de::Error::custom)?, ), src, }, _SuggestionVariant::PUSH { to, value } => SuggestionVariant::Push { compiled: Arc::new( compile_push_action( RuleParser::parse(Rule::reference, &to) .map_err(serde::de::Error::custom)?, value.clone(), ) .map_err(serde::de::Error::custom)?, ), to, value, }, }) } } impl serde::ser::Serialize for SuggestionVariant { fn serialize(&self, serializer: S) -> Result where S: serde::ser::Serializer, { #[derive(serde::Serialize)] enum _SuggestionVariant<'a> { SET { var: &'a str, #[serde(flatten)] to: &'a SetVariant, }, DELETE(&'a str), PUSH { to: &'a str, value: &'a Value, }, } match self { SuggestionVariant::Set { ref var, ref to, .. } => serde::ser::Serialize::serialize(&_SuggestionVariant::SET { var, to }, serializer), SuggestionVariant::Delete { ref src, .. } => { serde::ser::Serialize::serialize(&_SuggestionVariant::DELETE(src), serializer) } SuggestionVariant::Push { ref to, ref value, .. } => serde::ser::Serialize::serialize( &_SuggestionVariant::PUSH { to, value }, serializer, ), } } } #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "kebab-case")] pub struct Suggestion { #[serde(rename = "if")] #[serde(skip_serializing_if = "Option::is_none")] #[serde(default)] pub condition: Option, #[serde(flatten)] pub variant: SuggestionVariant, } impl Suggestion { pub fn apply<'a>( &self, id: &'a str, cfg: &mut Config, cfgs: &mut LinearMap<&'a str, Cow>, ) { match &self.condition { Some(condition) if !(condition.compiled)(cfg, cfgs) => (), _ => self.variant.apply(id, cfg, cfgs), } } } #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "kebab-case")] pub struct ConfigRuleEntryWithSuggestions { #[serde(flatten)] pub entry: ConfigRuleEntry, pub suggestions: Vec, } impl ConfigRuleEntryWithSuggestions { pub fn apply<'a>( &self, id: &'a str, cfg: &mut Config, cfgs: &mut LinearMap<&'a str, Cow>, ) -> Result<(), anyhow::Error> { if self.entry.check(cfg, cfgs).is_err() { for suggestion in &self.suggestions { suggestion.apply(id, cfg, cfgs); } self.entry.check(cfg, cfgs) } else { Ok(()) } } } #[derive(Clone, Debug, PartialEq, Eq)] pub enum VarRes { Exactly(T), Any(Vec>), All(Vec>), } impl VarRes { fn map U>(self, mut f: F) -> VarRes { fn map_rec U>(s: VarRes, f: &mut F) -> VarRes { match s { VarRes::Exactly(a) => VarRes::Exactly(f(a)), VarRes::Any(a) => VarRes::Any(a.into_iter().map(|a| map_rec(a, f)).collect()), VarRes::All(a) => VarRes::All(a.into_iter().map(|a| map_rec(a, f)).collect()), } } map_rec(self, &mut f) } fn and_then VarRes>(self, mut f: F) -> VarRes { fn and_then_rec VarRes>(s: VarRes, f: &mut F) -> VarRes { match s { VarRes::Exactly(a) => f(a), VarRes::Any(a) => VarRes::Any(a.into_iter().map(|a| and_then_rec(a, f)).collect()), VarRes::All(a) => VarRes::All(a.into_iter().map(|a| and_then_rec(a, f)).collect()), } } and_then_rec(self, &mut f) } } impl VarRes { fn resolve(self) -> bool { match self { VarRes::Exactly(a) => a, VarRes::Any(a) => a.into_iter().any(|a| a.resolve()), VarRes::All(a) => a.into_iter().all(|a| a.resolve()), } } } fn compile_var_rec(mut ident: Pairs) -> Option { let idx = ident.next(); if let Some(idx) = idx { let deref: Accessor = match idx.as_rule() { Rule::sub_ident_any => Box::new(|v, _| match v { Value::Array(l) => VarRes::Any(l.iter().map(VarRes::Exactly).collect()), Value::Object(o) => { VarRes::Any(o.iter().map(|(_, a)| VarRes::Exactly(a)).collect()) } _ => VarRes::Exactly(&STATIC_NULL), }), Rule::sub_ident_all => Box::new(|v, _| match v { Value::Array(l) => VarRes::All(l.iter().map(VarRes::Exactly).collect()), Value::Object(o) => { VarRes::All(o.iter().map(|(_, a)| VarRes::Exactly(a)).collect()) } _ => VarRes::Exactly(&STATIC_NULL), }), Rule::sub_ident_fn => { let idx = idx.into_inner().next().unwrap(); match idx.as_rule() { Rule::list_access_function_first => { let mut pred_iter = idx.into_inner(); let item_var: InternedString = pred_iter.next().unwrap().as_str().into(); let predicate = compile_bool_expr(pred_iter.next().unwrap().into_inner()); Box::new(move |v, cfgs| match v { Value::Array(l) => VarRes::Exactly( l.iter() .filter(|item| { let mut cfg = Config::default(); cfg.insert(item_var.clone(), (*item).clone()); predicate(&cfg, cfgs) }) .next() .unwrap_or(&STATIC_NULL), ), Value::Object(o) => VarRes::Exactly( &o.iter() .map(|(_, item)| item) .filter(|item| { let mut cfg = Config::default(); cfg.insert(item_var.clone(), (*item).clone()); predicate(&cfg, cfgs) }) .next() .unwrap_or(&STATIC_NULL), ), _ => VarRes::Exactly(&STATIC_NULL), }) } Rule::list_access_function_last => { let mut pred_iter = idx.into_inner(); let item_var: InternedString = pred_iter.next().unwrap().as_str().into(); let predicate = compile_bool_expr(pred_iter.next().unwrap().into_inner()); Box::new(move |v, cfgs| match v { Value::Array(l) => VarRes::Exactly( l.iter() .filter(|item| { let mut cfg = Config::default(); cfg.insert(item_var.clone(), (*item).clone()); predicate(&cfg, cfgs) }) .next_back() .unwrap_or(&STATIC_NULL), ), Value::Object(o) => VarRes::Exactly( &o.iter() .map(|(_, item)| item) .filter(|item| { let mut cfg = Config::default(); cfg.insert(item_var.clone(), (*item).clone()); predicate(&cfg, cfgs) }) .next_back() .unwrap_or(&STATIC_NULL), ), _ => VarRes::Exactly(&STATIC_NULL), }) } Rule::list_access_function_any => { let mut pred_iter = idx.into_inner(); let item_var: InternedString = pred_iter.next().unwrap().as_str().into(); let predicate = compile_bool_expr(pred_iter.next().unwrap().into_inner()); Box::new(move |v, cfgs| match v { Value::Array(l) => VarRes::Any( l.iter() .filter(|item| { let mut cfg = Config::default(); cfg.insert(item_var.clone(), (*item).clone()); predicate(&cfg, cfgs) }) .map(VarRes::Exactly) .collect(), ), Value::Object(o) => VarRes::Any( o.iter() .map(|(_, item)| item) .filter(|item| { let mut cfg = Config::default(); cfg.insert(item_var.clone(), (*item).clone()); predicate(&cfg, cfgs) }) .map(VarRes::Exactly) .collect(), ), _ => VarRes::Exactly(&STATIC_NULL), }) } Rule::list_access_function_all => { let mut pred_iter = idx.into_inner(); let item_var: InternedString = pred_iter.next().unwrap().as_str().into(); let predicate = compile_bool_expr(pred_iter.next().unwrap().into_inner()); Box::new(move |v, cfgs| match v { Value::Array(l) => VarRes::All( l.iter() .filter(|item| { let mut cfg = Config::default(); cfg.insert(item_var.clone(), (*item).clone()); predicate(&cfg, cfgs) }) .map(VarRes::Exactly) .collect(), ), Value::Object(o) => VarRes::All( o.iter() .map(|(_, item)| item) .filter(|item| { let mut cfg = Config::default(); cfg.insert(item_var.clone(), (*item).clone()); predicate(&cfg, cfgs) }) .map(VarRes::Exactly) .collect(), ), _ => VarRes::Exactly(&STATIC_NULL), }) } _ => unreachable!(), } } Rule::sub_ident_regular => { let idx = idx.into_inner().next().unwrap(); match idx.as_rule() { Rule::sub_ident_regular_base => { let idx = idx.as_str().to_owned(); Box::new(move |v, _| match v { Value::Object(o) => { VarRes::Exactly(o.get(&*idx).unwrap_or(&STATIC_NULL)) } _ => VarRes::Exactly(&STATIC_NULL), }) } Rule::sub_ident_regular_expr => { let idx = compile_str_expr(idx.into_inner().next().unwrap().into_inner()); Box::new(move |v, dep_cfg| match v { Value::Object(o) => idx(&Config::default(), dep_cfg).map(|idx| { idx.and_then(|idx| o.get(&*idx)).unwrap_or(&STATIC_NULL) }), _ => VarRes::Exactly(&STATIC_NULL), }) } _ => unreachable!(), } } Rule::sub_ident_index => { let idx = idx.into_inner().next().unwrap(); match idx.as_rule() { Rule::sub_ident_index_base => { let idx: usize = idx.as_str().parse().unwrap(); Box::new(move |v, _| match v { Value::Array(l) => VarRes::Exactly(l.get(idx).unwrap_or(&STATIC_NULL)), _ => VarRes::Exactly(&STATIC_NULL), }) } Rule::sub_ident_index_expr => { let idx = compile_num_expr(idx.into_inner().next().unwrap().into_inner()); Box::new(move |v, dep_cfg| match v { Value::Array(l) => idx(&Config::default(), dep_cfg) .map(|idx| l.get(idx as usize).unwrap_or(&STATIC_NULL)), _ => VarRes::Exactly(&STATIC_NULL), }) } _ => unreachable!(), } } _ => unreachable!(), }; Some(if let Some(rest) = compile_var_rec(ident) { Box::new(move |v, cfgs| deref(v, cfgs).and_then(|v| rest(v, cfgs))) } else { deref }) } else { None } } fn compile_var(mut var: Pairs) -> CompiledExpr> { let mut first_seg = var.next().unwrap(); let app_id = if first_seg.as_rule() == Rule::app_id { let app_id = first_seg.into_inner().next().unwrap().as_str().to_owned(); first_seg = var.next().unwrap(); Some(app_id) } else { None }; let first_seg_string = first_seg.as_str().to_owned(); let accessor = compile_var_rec(var); Box::new(move |cfg, cfgs| { let mut cfg: &Config = cfg; if let Some(ref app_id) = app_id { cfg = if let Some(cfg) = cfgs.get(&app_id.as_str()) { cfg } else { return VarRes::Exactly(Value::Null); }; } let val = cfg.get(&*first_seg_string).unwrap_or(&STATIC_NULL); if let Some(accessor) = &accessor { accessor(val, cfgs).map(|v| v.clone()) } else { VarRes::Exactly(val.clone()) } }) } fn compile_var_mut_rec(mut ident: Pairs) -> Result, failure::Error> { let idx = ident.next(); Ok(if let Some(idx) = idx { let deref: AccessorMut = match idx.as_rule() { Rule::sub_ident_fn => { let idx = idx.into_inner().next().unwrap(); match idx.as_rule() { Rule::list_access_function_first => { let mut pred_iter = idx.into_inner(); let item_var: InternedString = pred_iter.next().unwrap().as_str().into(); let predicate = compile_bool_expr(pred_iter.next().unwrap().into_inner()); Box::new(move |v, cfgs| match v { Value::Array(l) => l .iter_mut() .filter(|item| { let mut cfg = Config::default(); cfg.insert(item_var.clone(), (*item).clone()); predicate(&cfg, cfgs) }) .next(), Value::Object(o) => o .iter_mut() .map(|(_, item)| item) .filter(|item| { let mut cfg = Config::default(); cfg.insert(item_var.clone(), (*item).clone()); predicate(&cfg, cfgs) }) .next(), _ => None, }) } Rule::list_access_function_last => { let mut pred_iter = idx.into_inner(); let item_var: InternedString = pred_iter.next().unwrap().as_str().into(); let predicate = compile_bool_expr(pred_iter.next().unwrap().into_inner()); Box::new(move |v, cfgs| match v { Value::Array(l) => l .iter_mut() .filter(|item| { let mut cfg = Config::default(); cfg.insert(item_var.clone(), (*item).clone()); predicate(&cfg, cfgs) }) .next_back(), Value::Object(o) => o .iter_mut() .map(|(_, item)| item) .filter(|item| { let mut cfg = Config::default(); cfg.insert(item_var.clone(), (*item).clone()); predicate(&cfg, cfgs) }) .next_back(), _ => None, }) } Rule::list_access_function_any | Rule::list_access_function_all => { failure::bail!("Any and All are immutable") } _ => unreachable!(), } } Rule::sub_ident_regular => { let idx = idx.into_inner().next().unwrap(); match idx.as_rule() { Rule::sub_ident_regular_base => { let idx: InternedString = idx.as_str().into(); Box::new(move |v, _| match v { Value::Object(ref mut o) => { if o.contains_key(&*idx) { o.get_mut(&*idx) } else { o.insert(idx.clone(), Value::Null); o.get_mut(&*idx) } } _ => None, }) } Rule::sub_ident_regular_expr => { let idx = compile_str_expr(idx.into_inner().next().unwrap().into_inner()); Box::new( move |v, dep_cfg| match (v, idx(&Config::default(), dep_cfg)) { (Value::Object(ref mut o), VarRes::Exactly(Some(ref idx))) => { if o.contains_key(&**idx) { o.get_mut(&**idx) } else { o.insert(idx.clone(), Value::Null); o.get_mut(&**idx) } } _ => None, }, ) } _ => unreachable!(), } } Rule::sub_ident_index => { let idx = idx.into_inner().next().unwrap(); match idx.as_rule() { Rule::sub_ident_index_base => { let idx: usize = idx.as_str().parse().unwrap(); Box::new(move |v, _| match v { Value::Array(l) => { if l.len() > idx { l.get_mut(idx) } else if idx == l.len() { l.push_back(Value::Null); l.get_mut(idx) } else { None } } _ => None, }) } Rule::sub_ident_index_expr => { let idx = compile_num_expr(idx.into_inner().next().unwrap().into_inner()); Box::new( move |v, dep_cfg| match (v, idx(&Config::default(), dep_cfg)) { (Value::Array(l), VarRes::Exactly(idx)) => { let idx = idx as usize; if l.len() > idx { l.get_mut(idx) } else if idx == l.len() { l.push_back(Value::Null); l.get_mut(idx) } else { None } } _ => None, }, ) } _ => unreachable!(), } } _ => failure::bail!("invalid token: {:?}", idx.as_rule()), }; Some(if let Some(rest) = compile_var_mut_rec(ident)? { Box::new(move |v, cfgs| deref(v, cfgs).and_then(|v| rest(v, cfgs))) } else { deref }) } else { None }) } fn compile_var_mut(mut var: Pairs) -> Result { let first_seg = var.next().unwrap(); if first_seg.as_rule() == Rule::app_id { failure::bail!("Can only assign to relative path"); } let first_seg_string: InternedString = first_seg.as_str().into(); let accessor_mut = compile_var_mut_rec(var)?; Ok(Box::new(move |cfg, cfgs| { let var = if cfg.contains_key(&*first_seg_string) { cfg.get_mut(&*first_seg_string).unwrap() } else { cfg.insert(first_seg_string.clone(), Value::Null); cfg.get_mut(&first_seg_string).unwrap() }; if let Some(accessor_mut) = &accessor_mut { accessor_mut(var, cfgs) } else { Some(var) } })) } fn compile_bool_var(var: Pairs) -> CompiledRule { let var = compile_var(var); Box::new(move |cfg, cfgs| { var(cfg, cfgs) .map(|a| match a { Value::Bool(false) | Value::Null => false, _ => true, }) .resolve() }) } fn compile_num_var(var: Pairs) -> CompiledExpr> { let var = compile_var(var); Box::new(move |cfg, cfgs| { var(cfg, cfgs).map(|a| match a { Value::Number(n) => n.as_f64().unwrap(), Value::String(s) => match s.parse() { Ok(n) => n, Err(_) => panic!("string cannot be parsed as an f64"), }, Value::Bool(b) => { if b { 1.0 } else { 0.0 } } _ => panic!("object or list cannot be parsed as an f64"), }) }) } fn compile_num(num_str: &str) -> CompiledExpr> { let num = VarRes::Exactly(num_str.parse().unwrap()); Box::new(move |_, _| num.clone()) } fn compile_num_expr(pairs: Pairs) -> CompiledExpr> { NUM_PREC_CLIMBER.climb( pairs, |pair| match pair.as_rule() { Rule::num_var => compile_num_var(pair.into_inner()), Rule::num => compile_num(pair.as_str()), Rule::num_expr => compile_num_expr(pair.into_inner()), _ => unreachable!(), }, |lhs, op, rhs| match op.as_rule() { Rule::add => Box::new(move |cfg, cfgs| { lhs(cfg, cfgs).and_then(|lhs| rhs(cfg, cfgs).map(|rhs| lhs + rhs)) }), Rule::sub => Box::new(move |cfg, cfgs| { lhs(cfg, cfgs).and_then(|lhs| rhs(cfg, cfgs).map(|rhs| lhs - rhs)) }), Rule::mul => Box::new(move |cfg, cfgs| { lhs(cfg, cfgs).and_then(|lhs| rhs(cfg, cfgs).map(|rhs| lhs * rhs)) }), Rule::div => Box::new(move |cfg, cfgs| { lhs(cfg, cfgs).and_then(|lhs| rhs(cfg, cfgs).map(|rhs| lhs / rhs)) }), Rule::pow => Box::new(move |cfg, cfgs| { lhs(cfg, cfgs).and_then(|lhs| rhs(cfg, cfgs).map(|rhs| lhs.powf(rhs))) }), _ => unreachable!(), }, ) } fn compile_num_cmp_expr(mut pairs: Pairs) -> CompiledRule { let lhs = compile_num_expr(pairs.next().unwrap().into_inner()); let op = pairs.next().unwrap(); let rhs = compile_num_expr(pairs.next().unwrap().into_inner()); match op.as_rule() { Rule::lt => Box::new(move |cfg, cfgs| { lhs(cfg, cfgs) .and_then(|lhs| rhs(cfg, cfgs).map(|rhs| lhs < rhs)) .resolve() }), Rule::lte => Box::new(move |cfg, cfgs| { lhs(cfg, cfgs) .and_then(|lhs| rhs(cfg, cfgs).map(|rhs| lhs <= rhs)) .resolve() }), Rule::eq => Box::new(move |cfg, cfgs| { lhs(cfg, cfgs) .and_then(|lhs| rhs(cfg, cfgs).map(|rhs| lhs == rhs)) .resolve() }), Rule::neq => Box::new(move |cfg, cfgs| { lhs(cfg, cfgs) .and_then(|lhs| rhs(cfg, cfgs).map(|rhs| lhs != rhs)) .resolve() }), Rule::gt => Box::new(move |cfg, cfgs| { lhs(cfg, cfgs) .and_then(|lhs| rhs(cfg, cfgs).map(|rhs| lhs > rhs)) .resolve() }), Rule::gte => Box::new(move |cfg, cfgs| { lhs(cfg, cfgs) .and_then(|lhs| rhs(cfg, cfgs).map(|rhs| lhs >= rhs)) .resolve() }), _ => unreachable!(), } } fn compile_str_var(var: Pairs) -> CompiledExpr>> { let var = compile_var(var); Box::new(move |cfg, cfgs| { var(cfg, cfgs).map(|a| match a { Value::String(s) => Some(InternedString::from(&*s)), Value::Number(n) => Some(InternedString::from_display(&n)), Value::Bool(b) => Some(InternedString::from_display(&b)), _ => None, }) }) } fn compile_str(str_str: &str) -> CompiledExpr>> { let str_str = &str_str[1..str_str.len() - 1]; let mut out = String::with_capacity(str_str.len()); let mut escape = false; for c in str_str.chars() { match c { '\\' => { if escape { out.push('\\'); } else { escape = true; } } 'n' if escape => out.push('\n'), 'r' if escape => out.push('\r'), 't' if escape => out.push('\t'), '0' if escape => out.push('\0'), '"' if escape => out.push('"'), '\'' if escape => out.push('\''), _ => { if escape { out.push('\\') } out.push(c) } } } let res = VarRes::Exactly(Some(out.into())); Box::new(move |_, _| res.clone()) } fn compile_str_expr(pairs: Pairs) -> CompiledExpr>> { STR_PREC_CLIMBER.climb( pairs, |pair| match pair.as_rule() { Rule::str_var => compile_str_var(pair.into_inner()), Rule::str => compile_str(pair.as_str()), Rule::str_expr => compile_str_expr(pair.into_inner()), _ => unreachable!(), }, |lhs, op, rhs| match op.as_rule() { Rule::add => Box::new(move |cfg, cfgs| { lhs(cfg, cfgs).and_then(|lhs| { rhs(cfg, cfgs).map(|rhs| { let lhs = lhs.as_ref()?.to_string(); let rhs = rhs?; Some(InternedString::from(lhs + &*rhs)) }) }) }), _ => unreachable!(), }, ) } fn compile_str_cmp_expr(mut pairs: Pairs) -> CompiledRule { let lhs = compile_str_expr(pairs.next().unwrap().into_inner()); let op = pairs.next().unwrap(); let rhs = compile_str_expr(pairs.next().unwrap().into_inner()); match op.as_rule() { Rule::lt => Box::new(move |cfg, cfgs| { lhs(cfg, cfgs) .and_then(|lhs| { rhs(cfg, cfgs).map(|rhs| match (&lhs, &rhs) { (Some(lhs), Some(rhs)) => rhs.contains(&**lhs) && lhs.len() < rhs.len(), _ => false, }) }) .resolve() }), Rule::lte => Box::new(move |cfg, cfgs| { lhs(cfg, cfgs) .and_then(|lhs| { rhs(cfg, cfgs).map(|rhs| match (&lhs, &rhs) { (Some(lhs), Some(rhs)) => rhs.contains(&**lhs), _ => false, }) }) .resolve() }), Rule::eq => Box::new(move |cfg, cfgs| { lhs(cfg, cfgs) .and_then(|lhs| { rhs(cfg, cfgs).map(|rhs| match (&lhs, &rhs) { (Some(lhs), Some(rhs)) => lhs == rhs, (None, None) => true, _ => false, }) }) .resolve() }), Rule::neq => Box::new(move |cfg, cfgs| { lhs(cfg, cfgs) .and_then(|lhs| { rhs(cfg, cfgs).map(|rhs| match (&lhs, &rhs) { (Some(lhs), Some(rhs)) => lhs != rhs, (None, None) => false, _ => true, }) }) .resolve() }), Rule::gt => Box::new(move |cfg, cfgs| { lhs(cfg, cfgs) .and_then(|lhs| { rhs(cfg, cfgs).map(|rhs| match (&lhs, &rhs) { (Some(lhs), Some(rhs)) => lhs.contains(&**rhs) && lhs.len() > rhs.len(), _ => true, }) }) .resolve() }), Rule::gte => Box::new(move |cfg, cfgs| { lhs(cfg, cfgs) .and_then(|lhs| { rhs(cfg, cfgs).map(|rhs| match (&lhs, &rhs) { (Some(lhs), Some(rhs)) => lhs.contains(&**rhs), _ => true, }) }) .resolve() }), _ => unreachable!(), } } fn compile_inv_bool_expr(mut pairs: Pairs) -> CompiledRule { let expr = compile_bool_expr(pairs.next().unwrap().into_inner()); Box::new(move |cfg, cfgs| !expr(cfg, cfgs)) } fn compile_bool_expr(pairs: Pairs) -> CompiledRule { BOOL_PREC_CLIMBER.climb( pairs, |pair| match pair.as_rule() { Rule::bool_var => compile_bool_var(pair.into_inner()), Rule::bool_expr => compile_bool_expr(pair.into_inner()), Rule::inv_bool_expr => compile_inv_bool_expr(pair.into_inner()), Rule::num_cmp_expr => compile_num_cmp_expr(pair.into_inner()), Rule::str_cmp_expr => compile_str_cmp_expr(pair.into_inner()), _ => unreachable!(), }, |lhs, op, rhs| -> CompiledRule { match op.as_rule() { Rule::and => Box::new(move |cfg, cfgs| lhs(cfg, cfgs) && rhs(cfg, cfgs)), Rule::or => Box::new(move |cfg, cfgs| lhs(cfg, cfgs) || rhs(cfg, cfgs)), Rule::xor => Box::new(move |cfg, cfgs| lhs(cfg, cfgs) ^ rhs(cfg, cfgs)), _ => unreachable!(), } }, ) } fn compile_value_expr(mut pairs: Pairs) -> CompiledExpr> { let expr = pairs.next().unwrap(); match expr.as_rule() { Rule::any_var => compile_var(expr.into_inner()), Rule::str_expr => { let expr = compile_str_expr(expr.into_inner()); Box::new(move |cfg, cfgs| { expr(cfg, cfgs).map(|s| { s.map(|s| Value::String(Arc::new(s.to_string()))) .unwrap_or(Value::Null) }) }) } Rule::num_expr => { let expr = compile_num_expr(expr.into_inner()); Box::new(move |cfg, cfgs| { expr(cfg, cfgs).map(|n| match serde_json::Number::from_f64(n) { Some(a) => Value::Number(a), None => panic!("cannot coerce f64 into numberc type"), }) }) } Rule::bool_expr => { let expr = compile_bool_expr(expr.into_inner()); Box::new(move |cfg, cfgs| VarRes::Exactly(expr(cfg, cfgs)).map(Value::Bool)) } _ => unreachable!(), } } fn compile_del_action(mut pairs: Pairs) -> Result { let list_mut = compile_var_mut(pairs.next().unwrap().into_inner())?; let var: InternedString = pairs.next().unwrap().as_str().into(); let predicate = compile_bool_expr(pairs.next().unwrap().into_inner()); Ok(Box::new(move |cfg, cfgs| match (&list_mut)(cfg, cfgs) { Some(Value::Array(ref mut l)) => { *l = std::mem::take(l) .into_iter() .filter(|item| { let mut obj = Config::default(); obj.insert(var.clone(), item.clone()); !predicate(&obj, cfgs) }) .collect(); } Some(Value::Object(ref mut o)) => { *o = std::mem::take(o) .into_iter() .filter(|(_, item)| { let mut obj = Config::default(); obj.insert(var.clone(), item.clone()); !predicate(&obj, cfgs) }) .collect(); } _ => return, })) } fn compile_push_action(mut pairs: Pairs, value: Value) -> Result { let list_mut = compile_var_mut(pairs.next().unwrap().into_inner())?; Ok(Box::new(move |cfg, cfgs| { let vec = match (&list_mut)(cfg, cfgs) { Some(Value::Array(ref mut a)) => a, _ => return, }; vec.push_back(value.clone()) })) } fn compile_set_action(var: &str, to: &SetVariant) -> Result { let mut var = RuleParser::parse(Rule::reference, var)?; let get_mut = compile_var_mut(var.next().unwrap().into_inner())?; Ok(match to { SetVariant::To(expr) => { let expr = compile_expr(&expr)?; Box::new(move |cfg, cfgs| { let val = expr(cfg, cfgs); if let Some(var) = get_mut(cfg, cfgs) { *var = val; } }) } SetVariant::ToValue(val) => { let val = val.clone(); Box::new(move |cfg, cfgs| { if let Some(var) = get_mut(cfg, cfgs) { *var = val.clone() } }) } SetVariant::ToEntropy(entropy) => { let entropy = entropy.clone(); Box::new(move |cfg, cfgs| { if let Some(var) = get_mut(cfg, cfgs) { *var = Value::String(Arc::new( entropy.gen(&mut rand::rngs::StdRng::from_entropy()), )); } }) } }) } pub fn validate_key(key: &str) -> Result<(), pest::error::Error> { RuleParser::parse(Rule::obj_key, key)?; Ok(()) } pub fn parse_and) -> T>( rule: &str, f: F, ) -> Result> { let mut parsed = RuleParser::parse(Rule::rule, rule)?; let pairs = parsed.next().unwrap().into_inner(); Ok(f(pairs)) } pub fn compile(rule: &str) -> Result { parse_and(rule, compile_bool_expr).map_err(From::from) } pub fn compile_expr(expr: &str) -> Result, failure::Error> { let compiled = compile_value_expr(RuleParser::parse(Rule::value, expr)?); Ok(Box::new(move |cfg, cfgs| match compiled(cfg, cfgs) { VarRes::Exactly(v) => v, _ => Value::Null, })) } #[cfg(test)] mod test { use serde_json::json; use super::*; #[test] fn test_compile_str() { assert_eq!( compile_str("\"foo\"")(&Default::default(), &Default::default()), VarRes::Exactly(Some("foo".to_owned())) ); } #[test] fn test_access_expr() { let mut cfg = Config::default(); let mut cfgs = LinearMap::new(); let mut foo = Config::default(); foo.insert("bar!\"".to_owned(), json!(3.0)); cfg.insert( "foo".to_owned(), Value::Array(vec![Value::Null, Value::Object(foo), json!(3)]), ); cfgs.insert("my-app", Cow::Borrowed(&cfg)); assert!((compile("#[my-app].foo.1.[\"ba\" + \"r!\\\"\"] = 3") .map_err(|e| eprintln!("{}", e)) .expect("compile failed"))(&cfg, &cfgs)); assert!((compile("#[my-app].foo.[0 + 1].[\"bar!\\\"\"] = 3") .map_err(|e| eprintln!("{}", e)) .expect("compile failed"))(&cfg, &cfgs)); } #[test] fn test_any_all() { let mut cfg = Config::default(); let mut cfgs = LinearMap::new(); let mut foo = Config::default(); foo.insert("bar".to_owned(), json!(3.0)); cfg.insert( "foo".to_owned(), Value::Array(vec![Value::Null, Value::Object(foo), json!(3.0)]), ); cfgs.insert("my-app", Cow::Borrowed(&cfg)); // NOTE: these now fail due to added panic for parsing f64's // assert!((compile("#[my-app].foo.*.bar = 3") // .map_err(|e| eprintln!("{}", e)) // .expect("compile failed"))(&cfg, &cfgs)); // assert!(!(compile("#[my-app].foo.&.bar = 3") // .map_err(|e| eprintln!("{}", e)) // .expect("compile failed"))(&cfg, &cfgs)); } #[test] fn test_first_last() { let mut cfg = Config::default(); let mut cfgs = LinearMap::new(); let mut foo = Config::default(); foo.insert("bar".to_owned(), json!(3.0)); foo.insert("baz".to_owned(), json!(4.0)); let mut qux = Config::default(); qux.insert("bar".to_owned(), json!(7.0)); qux.insert("baz".to_owned(), json!(4.0)); cfg.insert( "foo".to_owned(), Value::Array(vec![ Value::Null, Value::Object(foo), Value::Object(qux), json!(3.0), ]), ); cfgs.insert("my-app", Cow::Borrowed(&cfg)); // NOTE: these now fail due to added panic for parsing f64's // assert!((compile("#foo.[first(item => #item.baz = 4)].bar = 3") // .map_err(|e| eprintln!("{}", e)) // .expect("compile failed"))(&cfg, &cfgs)); // assert!((compile("#foo.[last(item => #item.baz = 4)].bar = 7") // .map_err(|e| eprintln!("{}", e)) // .expect("compile failed"))(&cfg, &cfgs)); } #[test] fn test_app_id() { let mut dependent_cfg = Config::default(); let mut dependency_cfg = Config::default(); let mut cfgs = LinearMap::new(); dependent_cfg.insert("foo".to_owned(), Value::String("bar".to_owned())); dependency_cfg.insert("foo".to_owned(), Value::String("bar!".to_owned())); cfgs.insert("my-dependent", Cow::Borrowed(&dependent_cfg)); cfgs.insert("my-dependency", Cow::Borrowed(&dependency_cfg)); assert!((compile("'foo = '[my-dependent].foo + \"!\"") .map_err(|e| eprintln!("{}", e)) .expect("compile failed"))( &dependency_cfg, &cfgs )) } }