use std::collections::HashSet;
use std::hash::Hash;
use std::rc::Rc;
mod aggregation_method;
mod delta;
mod last;
mod mirror;
use aggregation_method::AggrMethodToWindow;
use delta::Delta;
use last::Last;
use mirror::Mirror as SynSugMirror;
use crate::ast::{
CloseSpec, EvalSpec, Expression, ExpressionKind, Input, Mirror as AstMirror, NodeId, Output, RtLolaAst, SpawnSpec,
Trigger,
};
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
enum ChangeInstruction {
#[allow(dead_code)] ReplaceExpr(NodeId, Expression),
RemoveStream(NodeId),
AddOutput(Box<Output>),
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
enum LocalChangeInstruction {
ReplaceExpr(Expression),
}
#[derive(Debug, Clone)]
struct ChangeSet {
_local_applied_flag: bool,
local_instructions: Option<LocalChangeInstruction>,
global_instructions: HashSet<ChangeInstruction>,
}
#[allow(unused_variables)] trait SynSugar {
fn desugarize_expr<'a>(&self, exp: &'a Expression, ast: &'a RtLolaAst) -> ChangeSet {
ChangeSet::empty()
}
fn desugarize_stream_out<'a>(&self, stream: &'a Output, ast: &'a RtLolaAst) -> ChangeSet {
ChangeSet::empty()
}
fn desugarize_stream_in<'a>(&self, stream: &'a Input, ast: &'a RtLolaAst) -> ChangeSet {
ChangeSet::empty()
}
fn desugarize_stream_mirror<'a>(&self, stream: &'a AstMirror, ast: &'a RtLolaAst) -> ChangeSet {
ChangeSet::empty()
}
fn desugarize_stream_trigger<'a>(&self, stream: &'a Trigger, ast: &'a RtLolaAst) -> ChangeSet {
ChangeSet::empty()
}
}
#[allow(missing_debug_implementations)] pub struct Desugarizer {
sugar_transformers: Vec<Box<dyn SynSugar>>,
}
impl Desugarizer {
pub fn all() -> Self {
let all_transformers: Vec<Box<dyn SynSugar>> = vec![
Box::new(AggrMethodToWindow {}),
Box::new(Last {}),
Box::new(SynSugMirror {}),
Box::new(Delta {}),
];
Self {
sugar_transformers: all_transformers,
}
}
pub fn remove_syn_sugar(&self, mut ast: RtLolaAst) -> RtLolaAst {
while {
let (new_ast, change_flag) = self.desugarize_fix_point(ast);
ast = new_ast;
change_flag
} {} ast
}
fn desugarize_fix_point(&self, mut ast: RtLolaAst) -> (RtLolaAst, bool) {
let mut change_flag = false;
for current_sugar in self.sugar_transformers.iter() {
let mut change_set = ChangeSet::empty();
for mirror in ast.mirrors.iter() {
change_set += self.desugarize_mirror(mirror, &ast, current_sugar);
}
for ix in 0..ast.outputs.len() {
let out = &ast.outputs[ix];
let out_clone: Output = Output::clone(out);
let Output { spawn, eval, close, .. } = out_clone;
let new_spawn_spec = if let Some(spawn_spec) = spawn {
let SpawnSpec {
expression,
condition,
annotated_pacing,
id,
span,
} = spawn_spec;
let expression = expression.map(|expr| {
let (new_expr, spawn_cs) = Self::desugarize_expression(expr, &ast, current_sugar);
change_set += spawn_cs;
new_expr
});
let condition = condition.map(|expr| {
let (new_expr, spawn_cond_cs) = Self::desugarize_expression(expr, &ast, current_sugar);
change_set += spawn_cond_cs;
new_expr
});
Some(SpawnSpec {
expression,
condition,
annotated_pacing,
id,
span,
})
} else {
None
};
let transformed_eval = eval
.into_iter()
.flat_map(|eval_spec| {
let EvalSpec {
annotated_pacing,
condition,
eval_expression,
id,
span,
} = eval_spec;
let new_eval = eval_expression.map(|e| {
let (res, eval_cs) = Self::desugarize_expression(e, &ast, current_sugar);
change_set += eval_cs;
res
});
let new_condition = condition.map(|e| {
let (res, cond_cs) = Self::desugarize_expression(e, &ast, current_sugar);
change_set += cond_cs;
res
});
Some(EvalSpec {
annotated_pacing,
condition: new_condition,
eval_expression: new_eval,
id,
span,
})
})
.collect();
let new_close_spec = if let Some(close_spec) = close {
let CloseSpec {
condition,
id,
span,
annotated_pacing,
} = close_spec;
let (new_condition, close_cs) = Self::desugarize_expression(condition, &ast, current_sugar);
change_set += close_cs;
Some(CloseSpec {
condition: new_condition,
id,
span,
annotated_pacing,
})
} else {
None
};
let new_out = Output {
spawn: new_spawn_spec,
eval: transformed_eval,
close: new_close_spec,
..out_clone
};
ast.outputs[ix] = Rc::new(new_out);
}
for ix in 0..ast.trigger.len() {
let trigger = &ast.trigger[ix];
let (new_out_expr, cs) = Self::desugarize_expression(trigger.expression.clone(), &ast, current_sugar);
change_set += cs;
let trigger_clone: Trigger = Trigger::clone(trigger);
let new_trigger = Trigger {
expression: new_out_expr,
..trigger_clone
};
ast.trigger[ix] = Rc::new(new_trigger);
}
for input in ast.inputs.iter() {
change_set += self.desugarize_input(input, &ast, current_sugar);
}
for output in ast.outputs.iter() {
change_set += self.desugarize_output(output, &ast, current_sugar);
}
for trigger in ast.trigger.iter() {
change_set += self.desugarize_trigger(trigger, &ast, current_sugar);
}
change_flag |= change_set._local_applied_flag || !change_set.global_instructions.is_empty();
ast = self.apply_global_changes(change_set, ast);
}
(ast, change_flag)
}
fn apply_global_changes(&self, c_s: ChangeSet, mut ast: RtLolaAst) -> RtLolaAst {
c_s.global_iter().for_each(|ci| {
match ci {
ChangeInstruction::AddOutput(o) => {
ast.outputs.push(Rc::new(*o));
},
ChangeInstruction::RemoveStream(id) => {
if let Some(idx) = ast.outputs.iter().position(|o| o.id == id) {
assert_eq!(Rc::strong_count(&ast.outputs[idx]), 1);
ast.outputs.remove(idx);
} else if let Some(idx) = ast.inputs.iter().position(|o| o.id == id) {
assert_eq!(Rc::strong_count(&ast.inputs[idx]), 1);
ast.inputs.remove(idx);
} else if let Some(idx) = ast.mirrors.iter().position(|o| o.id == id) {
assert_eq!(Rc::strong_count(&ast.mirrors[idx]), 1);
ast.mirrors.remove(idx);
} else if let Some(idx) = ast.trigger.iter().position(|o| o.id == id) {
assert_eq!(Rc::strong_count(&ast.trigger[idx]), 1);
ast.trigger.remove(idx);
} else {
debug_assert!(false, "id in changeset does not belong to any stream");
}
},
ChangeInstruction::ReplaceExpr(id, expr) => {
for ix in 0..ast.outputs.len() {
let out = &ast.outputs[ix];
let out_clone: Output = Output::clone(out);
let Output { spawn, eval, close, .. } = out_clone;
let new_spawn_spec = if let Some(spawn_spec) = spawn {
let SpawnSpec {
expression,
condition,
annotated_pacing,
id,
span,
} = spawn_spec;
let expression = expression
.map(|tar_expression| Self::apply_expr_global_change(id, &expr, &tar_expression));
let condition = condition
.map(|cond_expression| Self::apply_expr_global_change(id, &expr, &cond_expression));
Some(SpawnSpec {
expression,
condition,
annotated_pacing,
id,
span,
})
} else {
None
};
let new_eval_spec = eval
.into_iter()
.flat_map(|eval_spec| {
let EvalSpec {
eval_expression,
condition,
annotated_pacing,
id,
span,
} = eval_spec;
let eval_expression =
eval_expression.map(|e| Self::apply_expr_global_change(id, &expr, &e));
let condition = condition.map(|e| Self::apply_expr_global_change(id, &expr, &e));
Some(EvalSpec {
annotated_pacing,
condition,
eval_expression,
id,
span,
})
})
.collect();
let new_close_spec = if let Some(close_spec) = close {
let CloseSpec {
condition,
id,
span,
annotated_pacing,
} = close_spec;
let new_condition = Self::apply_expr_global_change(id, &expr, &condition);
Some(CloseSpec {
condition: new_condition,
id,
span,
annotated_pacing,
})
} else {
None
};
let new_out = Output {
spawn: new_spawn_spec,
eval: new_eval_spec,
close: new_close_spec,
..out_clone
};
ast.outputs[ix] = Rc::new(new_out);
}
for ix in 0..ast.trigger.len() {
let trigger: &Rc<Trigger> = &ast.trigger[ix];
let new_trigger_expr = Self::apply_expr_global_change(id, &expr, &trigger.expression);
let trigger_clone: Trigger = Trigger::clone(trigger);
let new_trigger = Trigger {
expression: new_trigger_expr,
..trigger_clone
};
ast.trigger[ix] = Rc::new(new_trigger);
}
},
}
});
ast
}
fn apply_expr_global_change(target_id: NodeId, new_expr: &Expression, ast_expr: &Expression) -> Expression {
if ast_expr.id == target_id {
return new_expr.clone();
}
let span = ast_expr.span.clone();
use ExpressionKind::*;
match &ast_expr.kind {
Lit(_) | Ident(_) | MissingExpression => ast_expr.clone(),
Unary(op, inner) => {
Expression {
kind: Unary(
*op,
Box::new(Self::apply_expr_global_change(target_id, new_expr, inner)),
),
span,
..*ast_expr
}
},
Field(inner, ident) => {
Expression {
kind: Field(
Box::new(Self::apply_expr_global_change(target_id, new_expr, inner)),
ident.clone(),
),
span,
..*ast_expr
}
},
StreamAccess(inner, acc_kind) => {
Expression {
kind: StreamAccess(
Box::new(Self::apply_expr_global_change(target_id, new_expr, inner)),
*acc_kind,
),
span,
..*ast_expr
}
},
Offset(inner, offset) => {
Expression {
kind: Offset(
Box::new(Self::apply_expr_global_change(target_id, new_expr, inner)),
*offset,
),
span,
..*ast_expr
}
},
ParenthesizedExpression(lp, inner, rp) => {
Expression {
kind: ParenthesizedExpression(
lp.clone(),
Box::new(Self::apply_expr_global_change(target_id, new_expr, inner)),
rp.clone(),
),
span,
..*ast_expr
}
},
Binary(bin_op, left, right) => {
Expression {
kind: Binary(
*bin_op,
Box::new(Self::apply_expr_global_change(target_id, new_expr, left)),
Box::new(Self::apply_expr_global_change(target_id, new_expr, right)),
),
span,
..*ast_expr
}
},
Default(left, right) => {
Expression {
kind: Default(
Box::new(Self::apply_expr_global_change(target_id, new_expr, left)),
Box::new(Self::apply_expr_global_change(target_id, new_expr, right)),
),
span,
..*ast_expr
}
},
DiscreteWindowAggregation {
expr: left,
duration: right,
wait,
aggregation,
..
} => {
Expression {
kind: DiscreteWindowAggregation {
expr: Box::new(Self::apply_expr_global_change(target_id, new_expr, left)),
duration: Box::new(Self::apply_expr_global_change(target_id, new_expr, right)),
wait: *wait,
aggregation: *aggregation,
},
span,
..*ast_expr
}
},
SlidingWindowAggregation {
expr: left,
duration: right,
wait,
aggregation,
} => {
Expression {
kind: SlidingWindowAggregation {
expr: Box::new(Self::apply_expr_global_change(target_id, new_expr, left)),
duration: Box::new(Self::apply_expr_global_change(target_id, new_expr, right)),
wait: *wait,
aggregation: *aggregation,
},
span,
..*ast_expr
}
},
Ite(condition, normal, alternative) => {
Expression {
kind: Ite(
Box::new(Self::apply_expr_global_change(target_id, new_expr, condition)),
Box::new(Self::apply_expr_global_change(target_id, new_expr, normal)),
Box::new(Self::apply_expr_global_change(target_id, new_expr, alternative)),
),
span,
..*ast_expr
}
},
Tuple(entries) => {
Expression {
kind: Tuple(
entries
.iter()
.map(|t_expr| Self::apply_expr_global_change(target_id, new_expr, t_expr))
.collect(),
),
span,
..*ast_expr
}
},
Function(name, types, entries) => {
Expression {
kind: Function(
name.clone(),
types.clone(),
entries
.iter()
.map(|t_expr| Self::apply_expr_global_change(target_id, new_expr, t_expr))
.collect(),
),
span,
..*ast_expr
}
},
Method(base, name, types, arguments) => {
Expression {
kind: Method(
Box::new(Self::apply_expr_global_change(target_id, new_expr, base)),
name.clone(),
types.clone(),
arguments
.iter()
.map(|t_expr| Self::apply_expr_global_change(target_id, new_expr, t_expr))
.collect(),
),
span,
..*ast_expr
}
},
}
}
#[allow(clippy::borrowed_box)]
fn desugarize_expression(
ast_expr: Expression,
ast: &RtLolaAst,
current_sugar: &Box<dyn SynSugar>,
) -> (Expression, ChangeSet) {
let mut return_cs = ChangeSet::empty();
use ExpressionKind::*;
let Expression { kind, id, span } = ast_expr;
let new_expr = match kind {
Lit(_) | Ident(_) | MissingExpression => Expression { kind, id, span },
Unary(op, inner) => {
let (inner, cs) = Self::desugarize_expression(*inner, ast, current_sugar);
return_cs += cs;
Expression {
kind: Unary(op, Box::new(inner)),
span,
id,
}
},
Field(inner, ident) => {
let (inner, cs) = Self::desugarize_expression(*inner, ast, current_sugar);
return_cs += cs;
Expression {
kind: Field(Box::new(inner), ident),
span,
id,
}
},
StreamAccess(inner, acc_kind) => {
let (inner, cs) = Self::desugarize_expression(*inner, ast, current_sugar);
return_cs += cs;
Expression {
kind: StreamAccess(Box::new(inner), acc_kind),
span,
id,
}
},
Offset(inner, offset) => {
let (inner, cs) = Self::desugarize_expression(*inner, ast, current_sugar);
return_cs += cs;
Expression {
kind: Offset(Box::new(inner), offset),
span,
id,
}
},
ParenthesizedExpression(lp, inner, rp) => {
let (inner, cs) = Self::desugarize_expression(*inner, ast, current_sugar);
return_cs += cs;
Expression {
kind: ParenthesizedExpression(lp, Box::new(inner), rp),
span,
id,
}
},
Binary(bin_op, left, right) => {
let (left, lcs) = Self::desugarize_expression(*left, ast, current_sugar);
return_cs += lcs;
let (right, rcs) = Self::desugarize_expression(*right, ast, current_sugar);
return_cs += rcs;
Expression {
kind: Binary(bin_op, Box::new(left), Box::new(right)),
span,
id,
}
},
Default(left, right) => {
let (left, lcs) = Self::desugarize_expression(*left, ast, current_sugar);
return_cs += lcs;
let (right, rcs) = Self::desugarize_expression(*right, ast, current_sugar);
return_cs += rcs;
Expression {
kind: Default(Box::new(left), Box::new(right)),
span,
id,
}
},
DiscreteWindowAggregation {
expr: left,
duration: right,
wait,
aggregation,
..
} => {
let (expr, ecs) = Self::desugarize_expression(*left, ast, current_sugar);
return_cs += ecs;
let (dur, dcs) = Self::desugarize_expression(*right, ast, current_sugar);
return_cs += dcs;
Expression {
kind: DiscreteWindowAggregation {
expr: Box::new(expr),
duration: Box::new(dur),
wait,
aggregation,
},
span,
id,
}
},
SlidingWindowAggregation {
expr: left,
duration: right,
wait,
aggregation,
} => {
let (expr, ecs) = Self::desugarize_expression(*left, ast, current_sugar);
return_cs += ecs;
let (dur, dcs) = Self::desugarize_expression(*right, ast, current_sugar);
return_cs += dcs;
Expression {
kind: SlidingWindowAggregation {
expr: Box::new(expr),
duration: Box::new(dur),
wait,
aggregation,
},
span,
id,
}
},
Ite(condition, normal, alternative) => {
let (condition, ccs) = Self::desugarize_expression(*condition, ast, current_sugar);
return_cs += ccs;
let (normal, ncs) = Self::desugarize_expression(*normal, ast, current_sugar);
return_cs += ncs;
let (alternative, acs) = Self::desugarize_expression(*alternative, ast, current_sugar);
return_cs += acs;
Expression {
kind: Ite(Box::new(condition), Box::new(normal), Box::new(alternative)),
span,
id,
}
},
Tuple(entries) => {
let (v_expr, v_cs): (Vec<Expression>, Vec<ChangeSet>) = entries
.into_iter()
.map(|t_expr| Self::desugarize_expression(t_expr, ast, current_sugar))
.unzip();
return_cs += v_cs.into_iter().fold(ChangeSet::empty(), |acc, x| acc + x);
Expression {
kind: Tuple(v_expr),
span,
id,
}
},
Function(name, types, entries) => {
let (v_expr, v_cs): (Vec<Expression>, Vec<ChangeSet>) = entries
.into_iter()
.map(|t_expr| Self::desugarize_expression(t_expr, ast, current_sugar))
.unzip();
return_cs += v_cs.into_iter().fold(ChangeSet::empty(), |acc, x| acc + x);
Expression {
kind: Function(name, types, v_expr),
span,
id,
}
},
Method(base, name, types, arguments) => {
let (base_expr, ecs) = Self::desugarize_expression(*base, ast, current_sugar);
return_cs += ecs;
let (v_expr, v_cs): (Vec<Expression>, Vec<ChangeSet>) = arguments
.into_iter()
.map(|t_expr| Self::desugarize_expression(t_expr, ast, current_sugar))
.unzip();
return_cs += v_cs.into_iter().fold(ChangeSet::empty(), |acc, x| acc + x);
Expression {
kind: Method(Box::new(base_expr), name, types, v_expr),
span,
id,
}
},
};
let mut current_level_cs = current_sugar.desugarize_expr(&new_expr, ast);
let return_expr =
if let Some(LocalChangeInstruction::ReplaceExpr(replace_expr)) = current_level_cs.extract_local_change() {
replace_expr
} else {
new_expr
};
let final_cs = current_level_cs + return_cs;
(return_expr, final_cs)
}
#[allow(clippy::borrowed_box)]
fn desugarize_input(&self, input: &Input, ast: &RtLolaAst, current_sugar: &Box<dyn SynSugar>) -> ChangeSet {
current_sugar.desugarize_stream_in(input, ast)
}
#[allow(clippy::borrowed_box)]
fn desugarize_mirror(&self, mirror: &AstMirror, ast: &RtLolaAst, current_sugar: &Box<dyn SynSugar>) -> ChangeSet {
current_sugar.desugarize_stream_mirror(mirror, ast)
}
#[allow(clippy::borrowed_box)]
fn desugarize_output(&self, output: &Output, ast: &RtLolaAst, current_sugar: &Box<dyn SynSugar>) -> ChangeSet {
current_sugar.desugarize_stream_out(output, ast)
}
#[allow(clippy::borrowed_box)]
fn desugarize_trigger(&self, trigger: &Trigger, ast: &RtLolaAst, current_sugar: &Box<dyn SynSugar>) -> ChangeSet {
current_sugar.desugarize_stream_trigger(trigger, ast)
}
}
impl Default for Desugarizer {
fn default() -> Self {
Self::all()
}
}
impl ChangeSet {
fn empty() -> Self {
Self {
_local_applied_flag: false,
local_instructions: None,
global_instructions: HashSet::new(),
}
}
#[allow(dead_code)] pub(crate) fn add_output(stream: Output) -> Self {
let mut cs = ChangeSet::empty();
cs.global_instructions
.insert(ChangeInstruction::AddOutput(Box::new(stream)));
cs
}
#[allow(dead_code)] pub(crate) fn replace_expression(target_id: NodeId, expr: Expression) -> Self {
let mut cs = Self::empty();
cs.global_instructions
.insert(ChangeInstruction::ReplaceExpr(target_id, expr));
cs
}
#[allow(dead_code)] pub(crate) fn remove_stream(target_id: NodeId) -> Self {
let mut cs = Self::empty();
cs.global_instructions
.insert(ChangeInstruction::RemoveStream(target_id));
cs
}
pub(crate) fn replace_current_expression(expr: Expression) -> Self {
let mut cs = Self::empty();
cs.local_instructions = Some(LocalChangeInstruction::ReplaceExpr(expr));
cs
}
pub(crate) fn replace_stream(target_stream: NodeId, new_stream: Output) -> Self {
let mut cs = ChangeSet::empty();
cs.global_instructions
.insert(ChangeInstruction::RemoveStream(target_stream));
cs.global_instructions
.insert(ChangeInstruction::AddOutput(Box::new(new_stream)));
cs
}
fn global_iter<'a>(self) -> Box<dyn Iterator<Item = ChangeInstruction> + 'a> {
Box::new(self.global_instructions.into_iter())
}
fn extract_local_change(&mut self) -> Option<LocalChangeInstruction> {
let mut ret = None;
std::mem::swap(&mut self.local_instructions, &mut ret);
ret
}
}
impl std::ops::Add<ChangeSet> for ChangeSet {
type Output = ChangeSet;
fn add(self, rhs: Self) -> Self {
let set = self
.global_instructions
.union(&rhs.global_instructions)
.cloned()
.collect();
assert!(self.local_instructions.is_none() || rhs.local_instructions.is_none());
let local = self.local_instructions.or(rhs.local_instructions);
Self {
_local_applied_flag: self._local_applied_flag || rhs._local_applied_flag,
local_instructions: local,
global_instructions: set,
}
}
}
impl std::ops::AddAssign<ChangeSet> for ChangeSet {
fn add_assign(&mut self, rhs: Self) {
let set = self
.global_instructions
.union(&rhs.global_instructions)
.cloned()
.collect();
assert!(self.local_instructions.is_none() || rhs.local_instructions.is_none());
let local = self.local_instructions.clone().or(rhs.local_instructions);
*self = Self {
_local_applied_flag: self._local_applied_flag || rhs._local_applied_flag,
local_instructions: local,
global_instructions: set,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::{BinOp, Ident, LitKind, Literal, Offset, UnOp, WindowOperation};
#[test]
fn test_aggr_replace() {
let spec = "output x eval @5Hz with x.count(6s)".to_string();
let ast = crate::parse(crate::ParserConfig::for_string(spec)).unwrap();
assert!(matches!(
ast.outputs[0].eval[0].clone().eval_expression.unwrap().kind,
ExpressionKind::SlidingWindowAggregation {
aggregation: WindowOperation::Count,
..
}
));
}
#[test]
fn test_aggr_replace_nested() {
let spec = "output x eval @ 5hz with -x.sum(6s)".to_string();
let ast = crate::parse(crate::ParserConfig::for_string(spec)).unwrap();
let out_kind = ast.outputs[0].eval[0].clone().eval_expression.unwrap().kind.clone();
assert!(matches!(out_kind, ExpressionKind::Unary(UnOp::Neg, _)));
let inner_kind = if let ExpressionKind::Unary(UnOp::Neg, inner) = out_kind {
inner.kind
} else {
unreachable!()
};
assert!(matches!(
inner_kind,
ExpressionKind::SlidingWindowAggregation {
aggregation: WindowOperation::Sum,
..
}
));
}
#[test]
fn test_aggr_replace_multiple() {
let spec = "output x eval @5hz with x.avg(5s) - x.integral(2.5s)".to_string();
let ast = crate::parse(crate::ParserConfig::for_string(spec)).unwrap();
let out_kind = ast.outputs[0].eval[0].clone().eval_expression.unwrap().kind.clone();
assert!(matches!(out_kind, ExpressionKind::Binary(BinOp::Sub, _, _)));
let (left, right) = if let ExpressionKind::Binary(BinOp::Sub, left, right) = out_kind {
(left.kind, right.kind)
} else {
unreachable!()
};
assert!(matches!(
left,
ExpressionKind::SlidingWindowAggregation {
aggregation: WindowOperation::Average,
..
}
));
assert!(matches!(
right,
ExpressionKind::SlidingWindowAggregation {
aggregation: WindowOperation::Integral,
..
}
));
}
#[test]
fn test_last_replace() {
let spec = "output x eval @5hz with x.last(or: 3)".to_string();
let ast = crate::parse(crate::ParserConfig::for_string(spec)).unwrap();
let out_kind = ast.outputs[0].eval[0].clone().eval_expression.unwrap().kind.clone();
let (access, dft) = if let ExpressionKind::Default(access, dft) = out_kind {
(access.kind, dft.kind)
} else {
panic!("Last should result in a top-level default access with its argument as default value")
};
assert!(
matches!(
&dft,
&ExpressionKind::Lit(Literal {
kind: LitKind::Numeric(ref x, None),
..
}) if x == &String::from("3")
),
"The argument of last should be the default expression."
);
let stream = if let ExpressionKind::Offset(stream, Offset::Discrete(-1)) = access {
stream
} else {
panic!("expected an offset expression, but found {:?}", access);
};
assert!(
matches!(*stream, Expression { kind: ExpressionKind::Ident(Ident { name, .. }), ..} if name == String::from("x") )
);
}
#[test]
fn test_delta_replace() {
let spec = "output y eval with delta(x,dft:0)".to_string();
let expected = "output y eval with x - x.offset(by: -1).defaults(to: 0)";
let ast = crate::parse(crate::ParserConfig::for_string(spec)).unwrap();
assert_eq!(expected, format!("{}", ast).trim());
}
#[test]
fn test_delta_replace_float() {
let spec = "output y eval with delta(x, or: 0.0)".to_string();
let expected = "output y eval with x - x.offset(by: -1).defaults(to: 0.0)";
let ast = crate::parse(crate::ParserConfig::for_string(spec)).unwrap();
assert_eq!(expected, format!("{}", ast).trim());
}
#[test]
fn test_mirror_replace() {
let spec = "output x eval with 3 \noutput y mirrors x when x > 5".to_string();
let ast = crate::parse(crate::ParserConfig::for_string(spec)).unwrap();
assert_eq!(ast.outputs.len(), 2);
assert!(ast.mirrors.is_empty());
let new = &ast.outputs[1];
let target = &ast.outputs[0];
assert_eq!(target.name.name, "x");
assert_eq!(new.name.name, "y");
assert_eq!(new.annotated_type, target.annotated_type);
assert_eq!(
new.eval[0].clone().annotated_pacing,
target.eval[0].clone().annotated_pacing
);
assert_eq!(new.close, target.close);
assert_eq!(
new.eval[0].eval_expression.clone(),
target.eval[0].eval_expression.clone()
);
assert!(new.eval[0].clone().condition.is_some());
assert!(matches!(
new.eval[0].clone().condition.as_ref().unwrap(),
Expression {
kind: ExpressionKind::Binary(..),
..
}
));
assert_eq!(new.params, target.params);
assert_eq!(new.spawn, target.spawn);
}
#[test]
fn test_mirror_replace_str_cmp() {
let spec = "output x eval with 3 \noutput y mirrors x when x > 5".to_string();
let expected = "output x eval with 3\noutput y eval when x > 5 with 3";
let ast = crate::parse(crate::ParserConfig::for_string(spec)).unwrap();
assert_eq!(expected, format!("{}", ast).trim());
}
#[test]
fn test_mirror_replace_multiple_eval() {
let spec = "output x eval when a > 0 with 3 eval when a < 0 with -3\noutput y mirrors x when x > 5".to_string();
let expected = "output x eval when a > 0 with 3 eval when a < 0 with -3\noutput y eval when a > 0 ∧ x > 5 with 3 eval when a < 0 ∧ x > 5 with -3";
let ast = crate::parse(crate::ParserConfig::for_string(spec)).unwrap();
assert_eq!(expected, format!("{}", ast).trim());
}
}