Lines
90.16 %
Functions
40 %
Branches
100 %
//! Entity accessor natives — `ENTITY-*`, `TRANSACTION-*`, `SPLIT-*`,
//! `TAG-*`, plus `CREATE-TAG` and `DELETE-ENTITY`.
//!
//! Split into topic-focused submodules so each file stays under the
//! ~500-line CLAUDE.md guideline:
//! - [`meta`] — `ENTITY-COUNT` / `CONTEXT-TYPE` / `PRIMARY-ENTITY-*` /
//! `ENTITY-TYPE` / `ENTITY-PARENT-IDX` + `GET-INPUT-ENTITIES`.
//! - [`transaction`] — `TRANSACTION-*` accessors (5 fields).
//! - [`split`] — `SPLIT-*` accessors (5 fields).
//! - [`tag`] — `TAG-NAME` / `TAG-VALUE` + the shared GC-array string
//! reader.
//! - [`create_tag`] — `CREATE-TAG` (constant + runtime codegen).
//! - [`delete_entity`] — `DELETE-ENTITY`.
//! Common helpers (`arity_check`, `compile_idx_to_stack`,
//! `emit_entity_header_offset`, `emit_entity_data_offset`) live here
//! in mod.rs since every accessor uses them.
mod create_tag;
mod delete_entity;
mod meta;
mod split;
mod tag;
mod transaction;
#[cfg(test)]
mod tests;
use crate::ast::{Expr, WasmType};
use crate::compiler::context::CompileContext;
use crate::compiler::emit::FunctionEmitter;
use crate::compiler::expr::{compile_for_stack, eval_value};
use crate::error::{Error, Result};
use crate::runtime::SymbolTable;
use super::NativeSpec;
pub(super) const NATIVES: &[NativeSpec] = &[
NativeSpec {
name: "ENTITY-COUNT",
eval: meta::entity_count,
stack: Some(meta::compile_entity_count_to_stack),
effect: None,
},
name: "CONTEXT-TYPE",
eval: meta::context_type,
stack: Some(meta::compile_context_type_to_stack),
name: "PRIMARY-ENTITY-TYPE",
eval: meta::primary_entity_type,
stack: Some(meta::compile_primary_entity_type_to_stack),
name: "PRIMARY-ENTITY-IDX",
eval: meta::primary_entity_idx,
stack: Some(meta::compile_primary_entity_idx_to_stack),
name: "ENTITY-TYPE",
eval: meta::entity_type,
stack: Some(meta::compile_entity_type_to_stack),
name: "ENTITY-PARENT-IDX",
eval: meta::entity_parent_idx,
stack: Some(meta::compile_entity_parent_idx_to_stack),
name: "TRANSACTION-SPLIT-COUNT",
eval: transaction::transaction_split_count,
stack: Some(transaction::compile_transaction_split_count_to_stack),
name: "TRANSACTION-TAG-COUNT",
eval: transaction::transaction_tag_count,
stack: Some(transaction::compile_transaction_tag_count_to_stack),
name: "TRANSACTION-IS-MULTI-CURRENCY",
eval: transaction::transaction_is_multi_currency,
stack: Some(transaction::compile_transaction_is_multi_currency_to_stack),
name: "TRANSACTION-POST-DATE",
eval: transaction::transaction_post_date,
stack: Some(transaction::compile_transaction_post_date_to_stack),
name: "TRANSACTION-ENTER-DATE",
eval: transaction::transaction_enter_date,
stack: Some(transaction::compile_transaction_enter_date_to_stack),
name: "SPLIT-VALUE-NUM",
eval: split::split_value_num,
stack: Some(split::compile_split_value_num_to_stack),
name: "SPLIT-VALUE-DENOM",
eval: split::split_value_denom,
stack: Some(split::compile_split_value_denom_to_stack),
name: "SPLIT-VALUE",
eval: split::split_value,
stack: Some(split::compile_split_value_to_stack),
name: "SPLIT-RECONCILE-STATE",
eval: split::split_reconcile_state,
stack: Some(split::compile_split_reconcile_state_to_stack),
name: "SPLIT-RECONCILE-DATE",
eval: split::split_reconcile_date,
stack: Some(split::compile_split_reconcile_date_to_stack),
name: "SPLIT-ACCOUNT-NAME",
eval: split::split_account_name,
stack: Some(split::compile_split_account_name_to_stack),
name: "TAG-NAME",
eval: tag::tag_name,
stack: Some(tag::compile_tag_name_to_stack),
name: "TAG-VALUE",
eval: tag::tag_value,
stack: Some(tag::compile_tag_value_to_stack),
name: "CREATE-TAG",
eval: create_tag::create_tag,
stack: None,
effect: Some(create_tag::compile_create_tag),
name: "DELETE-ENTITY",
eval: delete_entity::delete_entity,
effect: Some(delete_entity::compile_delete_entity),
name: "GET-INPUT-ENTITIES",
eval: meta::get_input_entities,
stack: Some(compile_get_input_entities_stack),
];
/// Adapter so the registry sees the standard `(ctx, emit, sym, args)`
/// shape on the `GET-INPUT-ENTITIES` slot; the underlying builder in
/// [`meta`] takes only `(ctx, emit)` because it ignores symbols/args.
fn compile_get_input_entities_stack(
ctx: &mut CompileContext,
emit: &mut FunctionEmitter,
_symbols: &mut SymbolTable,
_args: &[Expr],
) -> Result<WasmType> {
meta::compile_get_input_entities_to_stack(ctx, emit)
}
pub(in crate::compiler) use create_tag::compile_create_tag;
pub(super) fn arity_check(name: &str, args: &[Expr], expected: usize) -> Result<()> {
if args.len() != expected {
return Err(Error::Arity {
name: name.to_string(),
expected,
actual: args.len(),
});
Ok(())
/// Compile an entity index argument to i32 on the WASM stack.
pub(super) fn compile_idx_to_stack(
symbols: &mut SymbolTable,
expr: &Expr,
) -> Result<()> {
let resolved = eval_value(symbols, expr)?;
match &resolved {
Expr::WasmRuntime(WasmType::I32) | Expr::WasmLocal(_, WasmType::I32) => {
compile_for_stack(ctx, emit, symbols, expr)?;
Expr::WasmRuntime(WasmType::Ratio) | Expr::WasmLocal(_, WasmType::Ratio) => {
emit.struct_get(ctx.ids.ty_ratio, 0);
emit.i32_wrap_i64();
Expr::Number(n) if *n.denom() == 1 => {
emit.i32_const(*n.numer() as i32);
_ => {
return Err(Error::Compile(format!(
"entity index must be i32, got {resolved:?}"
)));
/// Emit WASM to compute the entity header offset for a given index.
/// Expects entity index on the WASM stack. Leaves header offset on stack.
/// `header_offset` = `entities_offset` + idx * 32
/// where `entities_offset` is an absolute offset read from `GlobalHeader`.
pub(super) fn emit_entity_header_offset(
ctx: &CompileContext,
temp_local: u32,
// Stack: [idx]
emit.i32_const(32);
emit.i32_mul(); // [idx * 32]
emit.local_set(temp_local); // save idx*32
emit.call(ctx.ids.get_input_offset()?); // [input_base]
emit.i32_load(0x0C); // [entities_offset] (absolute)
emit.local_get(temp_local); // [entities_offset, idx*32]
emit.i32_add(); // [header_offset]
/// Emit WASM to compute the entity data offset for a given index.
/// Expects entity index on the WASM stack. Leaves data offset on stack.
/// `data_offset` = `entity_header`[idx].`data_offset` (absolute memory address)
pub(super) fn emit_entity_data_offset(
emit_entity_header_offset(ctx, emit, temp_local)?;
emit.i32_load(0x18); // data_offset at EntityHeader offset 0x18