use datavalue::OwnedDataValue;
use crate::OpCode;
use crate::node::{CompileCtx, CompiledNode, node_is_static};
use crate::{Engine, Result};
use super::optimize;
use crate::operators::meta::{ArgsForm, CompileHook, HookArgs, Hooked, LiteralArgs};
pub(super) fn compile_node(
value: &OwnedDataValue,
engine: &Engine,
templating: bool,
ctx: &mut CompileCtx,
) -> Result<CompiledNode> {
ctx.enter()?;
let result = compile_node_inner(value, engine, templating, ctx);
ctx.leave();
result
}
fn compile_node_inner(
value: &OwnedDataValue,
engine: &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, true, 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: &Engine,
templating: bool,
ctx: &mut CompileCtx,
) -> Result<CompiledNode> {
#[cfg(feature = "templating")]
if templating {
let fields: Vec<_> = pairs
.iter()
.map(|(key, val)| {
keyed(key, ctx, |ctx| compile_node(val, engine, templating, ctx))
.map(|compiled_val| (key.clone(), compiled_val))
})
.collect::<Result<Vec<_>>>()?;
let escape = engine
.template_key_escape()
.filter(|&c| key_escape_present(&fields, c));
return Ok(CompiledNode::StructuredObject(Box::new(
crate::node::StructuredObjectData {
id: Some(ctx.next_id()),
fields: fields.into_boxed_slice(),
escape,
},
)));
}
let _ = (pairs, engine, templating, ctx);
Err(crate::error::Error::invalid_operator("Unknown Operator"))
}
fn compile_operator_invocation(
op_name: &str,
args_value: &OwnedDataValue,
engine: &Engine,
templating: bool,
fold: bool,
ctx: &mut CompileCtx,
) -> Result<CompiledNode> {
#[cfg(feature = "templating")]
if templating {
let escape = engine.template_key_escape();
if escape.is_some_and(|c| op_name.starts_with(c)) {
return single_field_object(op_name, args_value, engine, templating, escape, ctx);
}
}
if let Some(opcode) = engine.builtin(op_name) {
return compile_builtin(op_name, opcode, args_value, engine, templating, fold, ctx);
}
#[cfg(feature = "templating")]
if templating {
return compile_templating_unknown(op_name, args_value, engine, templating, fold, ctx);
}
let args = keyed(op_name, ctx, |ctx| {
compile_args(args_value, engine, templating, ctx)
})?;
Ok(custom_operator_node(op_name, args, engine, fold, ctx))
}
fn custom_operator_node(
op_name: &str,
args: Box<[CompiledNode]>,
engine: &Engine,
fold: bool,
ctx: &mut CompileCtx,
) -> CompiledNode {
let info = engine
.custom_operator_info(op_name)
.unwrap_or_else(crate::CustomOperatorInfo::opaque);
let node = CompiledNode::CustomOperator(Box::new(crate::node::CustomOperatorData {
id: Some(ctx.next_id()),
name: op_name.to_string(),
args,
info,
slot: engine.custom_operator_slot(op_name),
}));
if fold
&& !ctx.skip_fold()
&& node_is_static(&node)
&& let Some(value) = optimize::constant_fold::fold_static_node(&node, engine)
{
ctx.note_config_fold();
return CompiledNode::compile_time_value(Some(ctx.next_id()), value);
}
node
}
fn compile_builtin(
op_name: &str,
opcode: OpCode,
args_value: &OwnedDataValue,
engine: &Engine,
templating: bool,
fold: bool,
ctx: &mut CompileCtx,
) -> Result<CompiledNode> {
let meta = opcode.meta();
if meta.args_form == ArgsForm::ArrayOnly && !matches!(args_value, OwnedDataValue::Array(_)) {
return Ok(invalid_args_marker(opcode, args_value, ctx));
}
if let Some(CompileHook::Raw(hook)) = meta.compile
&& let Some(node) = hook(args_value, ctx)
{
return Ok(node);
}
let mut args = keyed(op_name, ctx, |ctx| {
compile_builtin_args(meta.literal_args, args_value, engine, templating, ctx)
})?;
if let Some(CompileHook::Args(hook)) = meta.compile {
let hook_args = HookArgs {
opcode,
op_name,
args_value,
ctx,
};
match hook(args, hook_args) {
Hooked::Node(node) => return Ok(node),
Hooked::Generic(back) => args = back,
}
}
let mut node = CompiledNode::BuiltinOperator {
id: Some(ctx.next_id()),
opcode,
args,
predicate_hint: None,
iter_arg_kind: crate::operators::array::IterArgKind::General,
};
if fold && !ctx.skip_fold() {
let (optimized, observed) = optimize::optimize(node, engine);
node = optimized;
if observed {
ctx.note_config_fold();
}
if node_is_static(&node)
&& let Some(value) = optimize::constant_fold::fold_static_node(&node, engine)
{
ctx.note_config_fold();
return Ok(CompiledNode::compile_time_value(Some(ctx.next_id()), value));
}
}
Ok(node)
}
pub(super) 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(),
args: Box::new(args_value.clone()),
}
}
#[cfg(feature = "templating")]
fn compile_templating_unknown(
op_name: &str,
args_value: &OwnedDataValue,
engine: &Engine,
templating: bool,
fold: bool,
ctx: &mut CompileCtx,
) -> Result<CompiledNode> {
if engine.has_custom_operator(op_name) {
let args = keyed(op_name, ctx, |ctx| {
compile_args(args_value, engine, templating, ctx)
})?;
return Ok(custom_operator_node(op_name, args, engine, fold, ctx));
}
single_field_object(op_name, args_value, engine, templating, None, ctx)
}
#[cfg(feature = "templating")]
fn single_field_object(
key: &str,
value: &OwnedDataValue,
engine: &Engine,
templating: bool,
escape: Option<char>,
ctx: &mut CompileCtx,
) -> Result<CompiledNode> {
let compiled_val = keyed(key, ctx, |ctx| compile_node(value, engine, templating, ctx))?;
let fields = vec![(key.to_string(), compiled_val)].into_boxed_slice();
Ok(CompiledNode::StructuredObject(Box::new(
crate::node::StructuredObjectData {
id: Some(ctx.next_id()),
fields,
escape,
},
)))
}
#[cfg(feature = "templating")]
fn key_escape_present(fields: &[(String, CompiledNode)], escape: char) -> bool {
fields.iter().any(|(key, _)| key.starts_with(escape))
}
fn compile_array(
arr: &[OwnedDataValue],
engine: &Engine,
templating: bool,
ctx: &mut CompileCtx,
) -> Result<CompiledNode> {
let nodes = arr
.iter()
.enumerate()
.map(|(i, v)| indexed(i, ctx, |ctx| 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 !ctx.skip_fold()
&& node_is_static(&node)
&& let Some(value) = optimize::constant_fold::fold_static_node(&node, engine)
{
return Ok(CompiledNode::compile_time_value(Some(ctx.next_id()), value));
}
Ok(node)
}
fn compile_builtin_args(
literal: LiteralArgs,
value: &OwnedDataValue,
engine: &Engine,
templating: bool,
ctx: &mut CompileCtx,
) -> Result<Box<[CompiledNode]>> {
if literal == LiteralArgs::None {
return compile_args(value, engine, templating, ctx);
}
let (items, listed) = match value {
OwnedDataValue::Array(arr) => (arr.as_slice(), true),
other => (std::slice::from_ref(other), false),
};
let len = items.len();
items
.iter()
.enumerate()
.map(|(i, v)| {
let compile = |ctx: &mut CompileCtx| {
if literal.reads(i, len) {
compile_as_written(v, engine, templating, ctx)
} else {
compile_node(v, engine, templating, ctx)
}
};
if listed {
indexed(i, ctx, compile)
} else {
compile(ctx)
}
})
.collect::<Result<Vec<_>>>()
.map(Vec::into_boxed_slice)
}
fn has_expression(value: &OwnedDataValue) -> bool {
match value {
OwnedDataValue::Object(pairs) => !pairs.is_empty(),
OwnedDataValue::Array(items) => items.iter().any(has_expression),
_ => false,
}
}
fn compile_as_written(
value: &OwnedDataValue,
engine: &Engine,
templating: bool,
ctx: &mut CompileCtx,
) -> Result<CompiledNode> {
if !has_expression(value) {
return compile_node(value, engine, templating, ctx);
}
ctx.enter()?;
let result = match value {
OwnedDataValue::Object(pairs) if pairs.len() == 1 => {
let (op_name, args_value) = &pairs[0];
compile_operator_invocation(op_name, args_value, engine, templating, false, ctx)
}
OwnedDataValue::Array(items) => items
.iter()
.enumerate()
.map(|(i, v)| indexed(i, ctx, |ctx| compile_as_written(v, engine, templating, ctx)))
.collect::<Result<Vec<_>>>()
.map(|nodes| CompiledNode::Array {
id: Some(ctx.next_id()),
nodes: nodes.into_boxed_slice(),
}),
other => compile_node_inner(other, engine, templating, ctx),
};
ctx.leave();
result
}
pub(super) fn compile_args(
value: &OwnedDataValue,
engine: &Engine,
templating: bool,
ctx: &mut CompileCtx,
) -> Result<Box<[CompiledNode]>> {
match value {
OwnedDataValue::Array(arr) => arr
.iter()
.enumerate()
.map(|(i, v)| indexed(i, ctx, |ctx| 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()),
}
}
#[inline]
fn keyed<T>(key: &str, ctx: &mut CompileCtx, compile: impl FnOnce(&mut CompileCtx) -> T) -> T {
let mark = ctx.descend(key);
let out = compile(ctx);
ctx.ascend(mark);
out
}
#[inline]
fn indexed<T>(i: usize, ctx: &mut CompileCtx, compile: impl FnOnce(&mut CompileCtx) -> T) -> T {
let mark = ctx.descend_index(i);
let out = compile(ctx);
ctx.ascend(mark);
out
}