use datavalue::OwnedDataValue;
use crate::node::{CompileCtx, CompiledNode, node_is_static};
use crate::opcode::OpCode;
use crate::{Engine, Result};
use super::missing::{compile_missing, compile_missing_some};
use super::operator;
use super::optimize;
pub(super) fn compile_node(
value: &OwnedDataValue,
engine: Option<&Engine>,
templating: bool,
ctx: &mut CompileCtx,
) -> Result<CompiledNode> {
match value {
OwnedDataValue::Object(pairs) if pairs.len() > 1 => {
compile_multi_key_object(pairs, engine, templating, ctx)
}
OwnedDataValue::Object(pairs) if pairs.len() == 1 => {
let (op_name, args_value) = &pairs[0];
compile_operator_invocation(op_name, args_value, engine, templating, ctx)
}
OwnedDataValue::Array(arr) => compile_array(arr, engine, templating, ctx),
_ => Ok(CompiledNode::value_with_id(
Some(ctx.next_id()),
value.clone(),
)),
}
}
fn compile_multi_key_object(
pairs: &[(String, OwnedDataValue)],
engine: Option<&Engine>,
templating: bool,
ctx: &mut CompileCtx,
) -> Result<CompiledNode> {
#[cfg(feature = "templating")]
if templating {
let fields: Vec<_> = pairs
.iter()
.map(|(key, val)| {
compile_node(val, engine, templating, ctx)
.map(|compiled_val| (key.clone(), compiled_val))
})
.collect::<Result<Vec<_>>>()?;
return Ok(CompiledNode::StructuredObject(Box::new(
crate::node::StructuredObjectData {
id: Some(ctx.next_id()),
fields: fields.into_boxed_slice(),
},
)));
}
let _ = (pairs, engine, templating, ctx);
Err(crate::error::Error::invalid_operator("Unknown Operator"))
}
fn compile_operator_invocation(
op_name: &str,
args_value: &OwnedDataValue,
engine: Option<&Engine>,
templating: bool,
ctx: &mut CompileCtx,
) -> Result<CompiledNode> {
if let Ok(opcode) = op_name.parse::<OpCode>() {
return compile_builtin(op_name, opcode, args_value, engine, templating, ctx);
}
#[cfg(feature = "templating")]
if templating {
return compile_templating_unknown(op_name, args_value, engine, templating, ctx);
}
let args = compile_args(args_value, engine, templating, ctx)?;
Ok(CompiledNode::CustomOperator(Box::new(
crate::node::CustomOperatorData {
id: Some(ctx.next_id()),
name: op_name.to_string(),
args,
},
)))
}
fn compile_builtin(
op_name: &str,
opcode: OpCode,
args_value: &OwnedDataValue,
engine: Option<&Engine>,
templating: bool,
ctx: &mut CompileCtx,
) -> Result<CompiledNode> {
let requires_array = matches!(opcode, OpCode::And | OpCode::Or | OpCode::If);
if requires_array && !matches!(args_value, OwnedDataValue::Array(_)) {
return Ok(invalid_args_marker(opcode, args_value, ctx));
}
let args = compile_args(args_value, engine, templating, ctx)?;
if let Some(node) = try_specialised(op_name, opcode, &args, ctx) {
return Ok(node);
}
if opcode == OpCode::Missing {
return Ok(compile_missing(args, ctx));
}
if opcode == OpCode::MissingSome {
return Ok(compile_missing_some(args, ctx));
}
#[cfg(feature = "error-handling")]
if let Some(node) = try_compile_throw_literal(opcode, &args, ctx) {
return Ok(node);
}
let mut node = CompiledNode::BuiltinOperator {
id: Some(ctx.next_id()),
opcode,
args,
predicate_hint: None,
iter_arg_kind: crate::operators::array::IterArgKind::General,
};
if let Some(eng) = engine {
if !ctx.skip_fold() {
node = optimize::optimize(node, eng);
if node_is_static(&node) {
if let Some(value) = optimize::constant_fold::fold_static_node(&node, eng) {
return Ok(CompiledNode::value_with_id(Some(ctx.next_id()), value));
}
}
}
}
Ok(node)
}
fn try_specialised(
op_name: &str,
opcode: OpCode,
args: &[CompiledNode],
ctx: &mut CompileCtx,
) -> Option<CompiledNode> {
match opcode {
OpCode::Val => {
if op_name == "var" {
operator::try_compile_var(args, ctx)
} else {
operator::try_compile_val(args, ctx)
}
}
#[cfg(feature = "ext-control")]
OpCode::Exists => operator::try_compile_exists(args, ctx),
_ => None,
}
}
fn invalid_args_marker(
opcode: OpCode,
_args_value: &OwnedDataValue,
ctx: &mut CompileCtx,
) -> CompiledNode {
CompiledNode::InvalidArgs {
id: Some(ctx.next_id()),
op_name: opcode.as_str(),
}
}
#[cfg(feature = "error-handling")]
fn try_compile_throw_literal(
opcode: OpCode,
args: &[CompiledNode],
ctx: &mut CompileCtx,
) -> Option<CompiledNode> {
if opcode != OpCode::Throw || args.len() != 1 {
return None;
}
let CompiledNode::Value {
value: OwnedDataValue::String(s),
..
} = &args[0]
else {
return None;
};
Some(CompiledNode::Throw(Box::new(
crate::node::CompiledThrowData {
id: Some(ctx.next_id()),
error: OwnedDataValue::Object(vec![(
"type".to_string(),
OwnedDataValue::String(s.clone()),
)]),
},
)))
}
#[cfg(feature = "templating")]
fn compile_templating_unknown(
op_name: &str,
args_value: &OwnedDataValue,
engine: Option<&Engine>,
templating: bool,
ctx: &mut CompileCtx,
) -> Result<CompiledNode> {
if let Some(eng) = engine {
if eng.has_custom_operator(op_name) {
let args = compile_args(args_value, engine, templating, ctx)?;
return Ok(CompiledNode::CustomOperator(Box::new(
crate::node::CustomOperatorData {
id: Some(ctx.next_id()),
name: op_name.to_string(),
args,
},
)));
}
}
let compiled_val = compile_node(args_value, engine, templating, ctx)?;
let fields = vec![(op_name.to_string(), compiled_val)].into_boxed_slice();
Ok(CompiledNode::StructuredObject(Box::new(
crate::node::StructuredObjectData {
id: Some(ctx.next_id()),
fields,
},
)))
}
fn compile_array(
arr: &[OwnedDataValue],
engine: Option<&Engine>,
templating: bool,
ctx: &mut CompileCtx,
) -> Result<CompiledNode> {
let nodes = arr
.iter()
.map(|v| compile_node(v, engine, templating, ctx))
.collect::<Result<Vec<_>>>()?;
let nodes_boxed = nodes.into_boxed_slice();
let node = CompiledNode::Array {
id: Some(ctx.next_id()),
nodes: nodes_boxed,
};
if let Some(eng) = engine {
if !ctx.skip_fold() && node_is_static(&node) {
if let Some(value) = optimize::constant_fold::fold_static_node(&node, eng) {
return Ok(CompiledNode::value_with_id(Some(ctx.next_id()), value));
}
}
}
Ok(node)
}
pub(super) fn compile_args(
value: &OwnedDataValue,
engine: Option<&Engine>,
templating: bool,
ctx: &mut CompileCtx,
) -> Result<Box<[CompiledNode]>> {
match value {
OwnedDataValue::Array(arr) => arr
.iter()
.map(|v| compile_node(v, engine, templating, ctx))
.collect::<Result<Vec<_>>>()
.map(Vec::into_boxed_slice),
_ => Ok(vec![compile_node(value, engine, templating, ctx)?].into_boxed_slice()),
}
}