Files
start-os/system-images/compat/src/config/rules.rs
Aiden McClelland a7e501d874 pack compressed assets into single binary (#2344)
* pack compressed assets into single binary

* update naming

* tweaks

* fix build

* fix cargo lock

* rename CLI

* remove explicit ref name
2023-07-12 22:51:05 +00:00

1256 lines
47 KiB
Rust

use std::borrow::Cow;
use std::sync::Arc;
use linear_map::LinearMap;
use pest::iterators::Pairs;
use pest::Parser;
use rand::SeedableRng;
use serde_json::Value;
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<Rule> = {
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<Rule> = {
use pest::prec_climber::*;
use Rule::*;
use Assoc::*;
PrecClimber::new(vec![
Operator::new(add, Left)
])
};
static ref BOOL_PREC_CLIMBER: pest::prec_climber::PrecClimber<Rule> = {
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<Config>>) -> VarRes<&'a Value> + Send + Sync,
>;
pub type AccessorMut = Box<
dyn for<'a> Fn(&'a mut Value, &LinearMap<&str, Cow<Config>>) -> Option<&'a mut Value>
+ Send
+ Sync,
>;
pub type CompiledExpr<T> = Box<dyn Fn(&Config, &LinearMap<&str, Cow<Config>>) -> T + Send + Sync>;
pub type CompiledReference = Box<
dyn for<'a> Fn(&'a mut Config, &LinearMap<&str, Cow<Config>>) -> Option<&'a mut Value>
+ Send
+ Sync,
>;
pub type Mutator = Box<dyn Fn(&mut Config, &LinearMap<&str, Cow<Config>>) + Send + Sync>;
pub type CompiledRule = Box<dyn Fn(&Config, &LinearMap<&str, Cow<Config>>) -> bool + Send + Sync>;
pub type CompiledRuleRes = Result<CompiledRule, failure::Error>;
#[derive(Clone)]
pub struct ConfigRule {
pub src: String,
pub compiled: Arc<CompiledRule>,
}
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<D>(deserializer: D) -> Result<Self, D::Error>
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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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<Config>>,
) -> 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<Mutator>,
},
Delete {
src: String,
compiled: Arc<Mutator>,
},
Push {
to: String,
value: Value,
compiled: Arc<Mutator>,
},
}
impl SuggestionVariant {
pub fn apply<'a>(
&self,
id: &'a str,
cfg: &mut Config,
cfgs: &mut LinearMap<&'a str, Cow<Config>>,
) {
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<D>(deserializer: D) -> Result<Self, D::Error>
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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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<ConfigRule>,
#[serde(flatten)]
pub variant: SuggestionVariant,
}
impl Suggestion {
pub fn apply<'a>(
&self,
id: &'a str,
cfg: &mut Config,
cfgs: &mut LinearMap<&'a str, Cow<Config>>,
) {
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<Suggestion>,
}
impl ConfigRuleEntryWithSuggestions {
pub fn apply<'a>(
&self,
id: &'a str,
cfg: &mut Config,
cfgs: &mut LinearMap<&'a str, Cow<Config>>,
) -> 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<T> {
Exactly(T),
Any(Vec<VarRes<T>>),
All(Vec<VarRes<T>>),
}
impl<T> VarRes<T> {
fn map<U, F: FnMut(T) -> U>(self, mut f: F) -> VarRes<U> {
fn map_rec<T, U, F: FnMut(T) -> U>(s: VarRes<T>, f: &mut F) -> VarRes<U> {
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<U, F: FnMut(T) -> VarRes<U>>(self, mut f: F) -> VarRes<U> {
fn and_then_rec<T, U, F: FnMut(T) -> VarRes<U>>(s: VarRes<T>, f: &mut F) -> VarRes<U> {
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<bool> {
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<Rule>) -> Option<Accessor> {
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 = pred_iter.next().unwrap().as_str().to_owned();
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 = pred_iter.next().unwrap().as_str().to_owned();
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 = pred_iter.next().unwrap().as_str().to_owned();
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 = pred_iter.next().unwrap().as_str().to_owned();
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<Rule>) -> CompiledExpr<VarRes<Value>> {
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<Rule>) -> Result<Option<AccessorMut>, 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 = pred_iter.next().unwrap().as_str().to_owned();
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 = pred_iter.next().unwrap().as_str().to_owned();
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 = idx.as_str().to_owned();
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(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(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<Rule>) -> Result<CompiledReference, failure::Error> {
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 = first_seg.as_str().to_owned();
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<Rule>) -> 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<Rule>) -> CompiledExpr<VarRes<f64>> {
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<VarRes<f64>> {
let num = VarRes::Exactly(num_str.parse().unwrap());
Box::new(move |_, _| num.clone())
}
fn compile_num_expr(pairs: Pairs<Rule>) -> CompiledExpr<VarRes<f64>> {
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<Rule>) -> 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<Rule>) -> CompiledExpr<VarRes<Option<String>>> {
let var = compile_var(var);
Box::new(move |cfg, cfgs| {
var(cfg, cfgs).map(|a| match a {
Value::String(s) => Some(s),
Value::Number(n) => Some(format!("{}", n)),
Value::Bool(b) => Some(format!("{}", b)),
_ => None,
})
})
}
fn compile_str(str_str: &str) -> CompiledExpr<VarRes<Option<String>>> {
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));
Box::new(move |_, _| res.clone())
}
fn compile_str_expr(pairs: Pairs<Rule>) -> CompiledExpr<VarRes<Option<String>>> {
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.clone()?;
let rhs = rhs?;
Some(lhs + &rhs)
})
})
}),
_ => unreachable!(),
},
)
}
fn compile_str_cmp_expr(mut pairs: Pairs<Rule>) -> 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<Rule>) -> 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<Rule>) -> 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<Rule>) -> CompiledExpr<VarRes<Value>> {
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(Value::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<Rule>) -> Result<Mutator, failure::Error> {
let list_mut = compile_var_mut(pairs.next().unwrap().into_inner())?;
let var = pairs.next().unwrap().as_str().to_owned();
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<Rule>, value: Value) -> Result<Mutator, failure::Error> {
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(value.clone())
}))
}
fn compile_set_action(var: &str, to: &SetVariant) -> Result<Mutator, failure::Error> {
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(entropy.gen(&mut rand::rngs::StdRng::from_entropy()));
}
})
}
})
}
pub fn validate_key(key: &str) -> Result<(), pest::error::Error<Rule>> {
RuleParser::parse(Rule::obj_key, key)?;
Ok(())
}
pub fn parse_and<T, F: FnOnce(Pairs<Rule>) -> T>(
rule: &str,
f: F,
) -> Result<T, pest::error::Error<Rule>> {
let mut parsed = RuleParser::parse(Rule::rule, rule)?;
let pairs = parsed.next().unwrap().into_inner();
Ok(f(pairs))
}
pub fn compile(rule: &str) -> Result<CompiledRule, failure::Error> {
parse_and(rule, compile_bool_expr).map_err(From::from)
}
pub fn compile_expr(expr: &str) -> Result<CompiledExpr<Value>, 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
))
}
}