use bumpalo::Bump;
use super::{
CompiledVarSpec, array_get, array_len, current_data, default_or_null, frame_data_at_level,
level_marker_from_array, metadata_hint_lookup, missed, path_str_from_data, value_path_name,
};
use crate::arena::{ContextStack, DataValue};
use crate::node::{MetadataHint, PathSegment, ReduceHint, ScopeBinding};
use crate::{ArenaExt, CompiledNode, Error, Result};
#[inline(always)]
pub(crate) fn evaluate_val_compiled<'a>(
spec: CompiledVarSpec<'a>,
ctx: &mut ContextStack<'a>,
engine: &crate::Engine,
arena: &'a Bump,
) -> Result<&'a DataValue<'a>> {
let CompiledVarSpec {
scope_level,
segments,
reduce_hint,
metadata_hint,
default_value,
binding,
} = spec;
super::debug_check_binding(binding, scope_level, ctx);
if binding == ScopeBinding::Root
&& metadata_hint == MetadataHint::None
&& reduce_hint == ReduceHint::None
{
let root_av = ctx.root_input();
let resolved = if segments.is_empty() {
Some(root_av)
} else {
crate::arena::value::traverse_segments(root_av, segments)
};
return match resolved {
Some(av) => Ok(av),
None => default_or_null(default_value, segments, ctx, engine, arena),
};
}
if metadata_hint != MetadataHint::None {
return Ok(resolve_metadata_hint(
metadata_hint,
scope_level as isize,
ctx,
arena,
));
}
if let Some(res) = resolve_reduce_hint(
reduce_hint,
binding,
segments,
ctx,
engine,
arena,
default_value,
) {
return res;
}
resolve_via_context_stack(scope_level, segments, ctx, engine, arena, default_value)
}
#[inline]
pub(super) fn resolve_metadata_hint<'a>(
hint: MetadataHint,
level: isize,
ctx: &ContextStack<'a>,
arena: &'a Bump,
) -> &'a DataValue<'a> {
debug_assert!(
crate::arena::metadata_climb(level.unsigned_abs()).is_some(),
"metadata hint on level {level}, which names an element frame - the \
compile-time parity gate broke"
);
let frame = ctx.metadata_at_level(level);
let resolved = match hint {
MetadataHint::Index => frame
.and_then(|f| f.get_index())
.map(|idx| arena.i64(idx as i64)),
MetadataHint::Key => frame
.and_then(|f| f.get_key())
.map(|key| &*arena.alloc(DataValue::String(key))),
MetadataHint::None => {
debug_assert!(false, "guarded by the caller");
None
}
};
resolved.unwrap_or_else(crate::arena::singletons::singleton_null)
}
#[inline]
fn resolve_reduce_hint<'a>(
reduce_hint: ReduceHint,
binding: ScopeBinding,
segments: &[PathSegment],
ctx: &mut ContextStack<'a>,
engine: &crate::Engine,
arena: &'a Bump,
default_value: Option<&'a CompiledNode>,
) -> Option<Result<&'a DataValue<'a>>> {
if reduce_hint == ReduceHint::None || binding == ScopeBinding::Root {
return None;
}
use crate::arena::context::ContextRef;
let ContextRef::Frame(f) = ctx.current() else {
return None;
};
match reduce_hint {
ReduceHint::Current => f.get_reduce_current().map(Ok),
ReduceHint::Accumulator => f.get_reduce_accumulator().map(Ok),
ReduceHint::CurrentPath | ReduceHint::AccumulatorPath => {
let slot = if reduce_hint == ReduceHint::CurrentPath {
f.get_reduce_current()
} else {
f.get_reduce_accumulator()
};
let slot = slot?;
let resolved = crate::arena::value::traverse_segments(slot, &segments[1..]);
Some(match resolved {
Some(av) => Ok(av),
None => default_or_null(default_value, segments, ctx, engine, arena),
})
}
ReduceHint::None => unreachable!(),
}
}
#[inline]
fn resolve_via_context_stack<'a>(
scope_level: u32,
segments: &[PathSegment],
ctx: &mut ContextStack<'a>,
engine: &crate::Engine,
arena: &'a Bump,
default_value: Option<&'a CompiledNode>,
) -> Result<&'a DataValue<'a>> {
let aref = if scope_level == 0 {
ctx.current()
} else {
ctx.get_at_level(scope_level as isize)
.ok_or_else(|| Error::invalid_context_level(scope_level as isize))?
};
let av = aref.data();
if segments.is_empty() {
return Ok(av);
}
match crate::arena::value::traverse_segments(av, segments) {
Some(child) => Ok(child),
None => default_or_null(default_value, segments, ctx, engine, arena),
}
}
#[inline]
pub(crate) fn evaluate_val<'a>(
args: &'a [CompiledNode],
ctx: &mut ContextStack<'a>,
engine: &crate::Engine,
arena: &'a Bump,
) -> Result<&'a DataValue<'a>> {
if args.is_empty() {
return Ok(current_data(ctx));
}
if args.len() >= 2 {
return eval_val_multiarg(args, ctx, engine, arena);
}
let path_av = engine.dispatch_node(&args[0], ctx, arena)?;
if matches!(path_av, DataValue::Null) {
return Ok(current_data(ctx));
}
let found = match array_len(path_av) {
Some(arr_len) => lookup_array_path(path_av, arr_len, ctx, arena)?,
None => lookup_scalar_path(path_av, ctx),
};
match found {
Some(av) => Ok(av),
None => missed(engine, || value_path_name(path_av)),
}
}
fn eval_val_multiarg<'a>(
args: &'a [CompiledNode],
ctx: &mut ContextStack<'a>,
engine: &crate::Engine,
arena: &'a Bump,
) -> Result<&'a DataValue<'a>> {
use crate::arena::context::ContextRef;
use crate::arena::value::{access_path_str_ref, apply_path_element};
let first_av = engine.dispatch_node(&args[0], ctx, arena)?;
if let Some(level) = level_marker_from_array(first_av) {
if args.len() == 2 {
let path_av = engine.dispatch_node(&args[1], ctx, arena)?;
let path_str = path_av.as_str().unwrap_or("");
if let Some(av) = metadata_hint_lookup(ctx, level, path_str, arena) {
return Ok(av);
}
let path_str = path_str_from_data(path_av, arena);
let frame_data = frame_data_at_level(ctx, level as isize)
.ok_or_else(|| Error::invalid_context_level(level as isize))?;
return match access_path_str_ref(frame_data, path_str) {
Some(av) => Ok(av),
None => missed(engine, || path_str.to_string()),
};
}
let mut paths: bumpalo::collections::Vec<'a, &'a str> =
bumpalo::collections::Vec::with_capacity_in(args.len() - 1, arena);
for item in args.iter().skip(1) {
let av = engine.dispatch_node(item, ctx, arena)?;
paths.push(path_str_from_data(av, arena));
}
let mut cur = frame_data_at_level(ctx, level as isize)
.ok_or_else(|| Error::invalid_context_level(level as isize))?;
for path in paths.iter() {
match access_path_str_ref(cur, path) {
Some(next) => cur = next,
None => return missed(engine, || paths.join(".")),
}
}
return Ok(cur);
}
let mut evaluated: bumpalo::collections::Vec<'a, &'a DataValue<'a>> =
bumpalo::collections::Vec::with_capacity_in(args.len(), arena);
evaluated.push(first_av);
for arg in args.iter().skip(1) {
evaluated.push(engine.dispatch_node(arg, ctx, arena)?);
}
let mut start: Option<&'a DataValue<'a>> = None;
if let ContextRef::Frame(frame) = ctx.current()
&& let Some(s) = evaluated[0].as_str()
{
start = if s == "current" {
frame.get_reduce_current()
} else if s == "accumulator" {
frame.get_reduce_accumulator()
} else {
None
};
}
let (mut cur, rest_start) = match start {
Some(s) => (s, 1),
None => (current_data(ctx), 0),
};
for elem in &evaluated[rest_start..] {
match apply_path_element(cur, elem) {
Some(next) => cur = next,
None => {
return missed(engine, || {
let parts: Vec<String> = evaluated.iter().map(|v| value_path_name(v)).collect();
parts.join(".")
});
}
}
}
Ok(cur)
}
fn lookup_array_path<'a>(
path_av: &'a DataValue<'a>,
arr_len: usize,
ctx: &mut ContextStack<'a>,
arena: &'a Bump,
) -> Result<Option<&'a DataValue<'a>>> {
use crate::arena::value::apply_path_element;
if arr_len == 0 {
return Ok(Some(current_data(ctx)));
}
if arr_len >= 2 {
let level_opt = array_get(path_av, 0).and_then(level_marker_from_array);
if let Some(level) = level_opt {
if arr_len == 2 {
let second = array_get(path_av, 1)
.unwrap_or_else(|| crate::arena::singletons::singleton_null());
let path_str = second.as_str().unwrap_or("");
if let Some(av) = metadata_hint_lookup(ctx, level, path_str, arena) {
return Ok(Some(av));
}
}
let mut cur = frame_data_at_level(ctx, level as isize)
.ok_or_else(|| Error::invalid_context_level(level as isize))?;
for i in 1..arr_len {
let item = array_get(path_av, i)
.unwrap_or_else(|| crate::arena::singletons::singleton_null());
match apply_path_element(cur, item) {
Some(next) => cur = next,
None => return Ok(None),
}
}
return Ok(Some(cur));
}
}
let mut cur = current_data(ctx);
for i in 0..arr_len {
let elem =
array_get(path_av, i).unwrap_or_else(|| crate::arena::singletons::singleton_null());
match apply_path_element(cur, elem) {
Some(next) => cur = next,
None => return Ok(None),
}
}
Ok(Some(cur))
}
fn lookup_scalar_path<'a>(
path_av: &'a DataValue<'a>,
ctx: &ContextStack<'a>,
) -> Option<&'a DataValue<'a>> {
use crate::arena::context::ContextRef;
use crate::arena::value::access_path_str_ref;
if let Some(s) = path_av.as_str() {
if let ContextRef::Frame(frame) = ctx.current() {
if s == "current" {
if let Some(av) = frame.get_reduce_current() {
return Some(av);
}
} else if s == "accumulator" {
if let Some(av) = frame.get_reduce_accumulator() {
return Some(av);
}
} else if let Some(rest) = s.strip_prefix("current.") {
if let Some(cur) = frame.get_reduce_current() {
return access_path_str_ref(cur, rest);
}
} else if let Some(rest) = s.strip_prefix("accumulator.")
&& let Some(acc) = frame.get_reduce_accumulator()
{
return access_path_str_ref(acc, rest);
}
}
let cur = current_data(ctx);
if let DataValue::Object(pairs) = cur
&& let Some(av) = crate::arena::value::object_lookup_field(pairs, s)
{
return Some(av);
}
return access_path_str_ref(cur, s);
}
if let Some(i) = path_av.as_i64()
&& i >= 0
{
let cur = current_data(ctx);
if let Some(static_key) = super::small_int_str(i) {
return access_path_str_ref(cur, static_key);
}
let mut buf = itoa::Buffer::new();
return access_path_str_ref(cur, buf.format(i));
}
None
}
pub(crate) fn evaluate_var_default<'a>(
args: &'a [CompiledNode],
ctx: &mut ContextStack<'a>,
engine: &crate::Engine,
arena: &'a Bump,
) -> Result<&'a DataValue<'a>> {
let Some(path_node) = args.first() else {
return Ok(current_data(ctx));
};
let path_av = engine.dispatch_node(path_node, ctx, arena)?;
let found = if matches!(path_av, DataValue::Null) {
Some(current_data(ctx))
} else if let Some(arr_len) = array_len(path_av) {
lookup_array_path(path_av, arr_len, ctx, arena)?
} else {
lookup_scalar_path(path_av, ctx)
};
match (found, args.get(1)) {
(Some(av), _) => Ok(av),
(None, Some(default)) => engine.dispatch_node(default, ctx, arena),
(None, None) => missed(engine, || value_path_name(path_av)),
}
}