use crate::DataLogic;
use crate::node::CompiledNode;
use crate::opcode::OpCode;
use serde_json::Value;
use super::helpers::is_truthy_literal;
pub fn eliminate(node: CompiledNode, engine: &DataLogic) -> CompiledNode {
match &node {
CompiledNode::BuiltinOperator { opcode, args } => match opcode {
OpCode::If => eliminate_if(args, engine),
OpCode::Ternary => eliminate_ternary(args, engine),
OpCode::And => eliminate_and(args, engine),
OpCode::Or => eliminate_or(args, engine),
_ => node,
},
_ => node,
}
}
fn eliminate_if(args: &[CompiledNode], engine: &DataLogic) -> CompiledNode {
if args.is_empty() {
return CompiledNode::Value { value: Value::Null };
}
let mut i = 0;
let mut new_args: Vec<CompiledNode> = Vec::new();
while i < args.len() {
if i == args.len() - 1 {
if new_args.is_empty() {
return args[i].clone();
}
new_args.push(args[i].clone());
break;
}
match is_truthy_literal(&args[i], engine) {
Some(true) => {
if i + 1 < args.len() {
if new_args.is_empty() {
return args[i + 1].clone();
}
new_args.push(args[i + 1].clone());
break;
}
return args[i].clone();
}
Some(false) => {
i += 2;
continue;
}
None => {
new_args.push(args[i].clone());
if i + 1 < args.len() {
new_args.push(args[i + 1].clone());
}
i += 2;
}
}
}
if new_args.is_empty() {
return CompiledNode::Value { value: Value::Null };
}
if new_args.len() == 1 {
return new_args.into_iter().next().unwrap();
}
CompiledNode::BuiltinOperator {
opcode: OpCode::If,
args: new_args.into_boxed_slice(),
}
}
fn eliminate_ternary(args: &[CompiledNode], engine: &DataLogic) -> CompiledNode {
if args.len() < 3 {
return CompiledNode::BuiltinOperator {
opcode: OpCode::Ternary,
args: args.to_vec().into_boxed_slice(),
};
}
match is_truthy_literal(&args[0], engine) {
Some(true) => args[1].clone(),
Some(false) => args[2].clone(),
None => CompiledNode::BuiltinOperator {
opcode: OpCode::Ternary,
args: args.to_vec().into_boxed_slice(),
},
}
}
fn eliminate_and(args: &[CompiledNode], engine: &DataLogic) -> CompiledNode {
if args.is_empty() {
return CompiledNode::BuiltinOperator {
opcode: OpCode::And,
args: args.to_vec().into_boxed_slice(),
};
}
let mut remaining: Vec<CompiledNode> = Vec::new();
for arg in args {
match is_truthy_literal(arg, engine) {
Some(false) => {
return arg.clone();
}
Some(true) => {
continue;
}
None => {
remaining.push(arg.clone());
}
}
}
if remaining.is_empty() {
return args.last().unwrap().clone();
}
if remaining.len() == 1 {
return remaining.into_iter().next().unwrap();
}
if remaining.len() == args.len() {
return CompiledNode::BuiltinOperator {
opcode: OpCode::And,
args: args.to_vec().into_boxed_slice(),
};
}
CompiledNode::BuiltinOperator {
opcode: OpCode::And,
args: remaining.into_boxed_slice(),
}
}
fn eliminate_or(args: &[CompiledNode], engine: &DataLogic) -> CompiledNode {
if args.is_empty() {
return CompiledNode::BuiltinOperator {
opcode: OpCode::Or,
args: args.to_vec().into_boxed_slice(),
};
}
let mut remaining: Vec<CompiledNode> = Vec::new();
for arg in args {
match is_truthy_literal(arg, engine) {
Some(true) => {
return arg.clone();
}
Some(false) => {
continue;
}
None => {
remaining.push(arg.clone());
}
}
}
if remaining.is_empty() {
return args.last().unwrap().clone();
}
if remaining.len() == 1 {
return remaining.into_iter().next().unwrap();
}
if remaining.len() == args.len() {
return CompiledNode::BuiltinOperator {
opcode: OpCode::Or,
args: args.to_vec().into_boxed_slice(),
};
}
CompiledNode::BuiltinOperator {
opcode: OpCode::Or,
args: remaining.into_boxed_slice(),
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
fn val(v: Value) -> CompiledNode {
CompiledNode::Value { value: v }
}
fn var_node(name: &str) -> CompiledNode {
CompiledNode::CompiledVar {
scope_level: 0,
segments: vec![crate::node::PathSegment::Field(name.into())].into_boxed_slice(),
reduce_hint: crate::node::ReduceHint::None,
metadata_hint: crate::node::MetadataHint::None,
default_value: None,
}
}
fn builtin(opcode: OpCode, args: Vec<CompiledNode>) -> CompiledNode {
CompiledNode::BuiltinOperator {
opcode,
args: args.into_boxed_slice(),
}
}
#[test]
fn test_if_true_condition() {
let engine = DataLogic::new();
let node = builtin(
OpCode::If,
vec![val(json!(true)), var_node("x"), var_node("y")],
);
let result = eliminate(node, &engine);
assert!(matches!(result, CompiledNode::CompiledVar { .. }));
}
#[test]
fn test_if_false_condition() {
let engine = DataLogic::new();
let node = builtin(
OpCode::If,
vec![val(json!(false)), var_node("x"), var_node("y")],
);
let result = eliminate(node, &engine);
assert!(matches!(result, CompiledNode::CompiledVar { .. }));
}
#[test]
fn test_and_with_true_prefix() {
let engine = DataLogic::new();
let node = builtin(OpCode::And, vec![val(json!(true)), var_node("x")]);
let result = eliminate(node, &engine);
assert!(matches!(result, CompiledNode::CompiledVar { .. }));
}
#[test]
fn test_and_with_false() {
let engine = DataLogic::new();
let node = builtin(OpCode::And, vec![val(json!(false)), var_node("x")]);
let result = eliminate(node, &engine);
assert!(matches!(result, CompiledNode::Value { .. }));
}
#[test]
fn test_or_with_true() {
let engine = DataLogic::new();
let node = builtin(OpCode::Or, vec![val(json!(true)), var_node("x")]);
let result = eliminate(node, &engine);
assert!(matches!(result, CompiledNode::Value { .. }));
}
#[test]
fn test_or_with_false_prefix() {
let engine = DataLogic::new();
let node = builtin(OpCode::Or, vec![val(json!(false)), var_node("x")]);
let result = eliminate(node, &engine);
assert!(matches!(result, CompiledNode::CompiledVar { .. }));
}
#[test]
fn test_ternary_true() {
let engine = DataLogic::new();
let node = builtin(
OpCode::Ternary,
vec![val(json!(true)), var_node("x"), var_node("y")],
);
let result = eliminate(node, &engine);
assert!(matches!(result, CompiledNode::CompiledVar { .. }));
}
}