1
//! Entity accessor natives — `ENTITY-*`, `TRANSACTION-*`, `SPLIT-*`,
2
//! `TAG-*`, plus `CREATE-TAG` and `DELETE-ENTITY`.
3
//!
4
//! Split into topic-focused submodules so each file stays under the
5
//! ~500-line CLAUDE.md guideline:
6
//! - [`meta`] — `ENTITY-COUNT` / `CONTEXT-TYPE` / `PRIMARY-ENTITY-*` /
7
//!   `ENTITY-TYPE` / `ENTITY-PARENT-IDX` + `GET-INPUT-ENTITIES`.
8
//! - [`transaction`] — `TRANSACTION-*` accessors (5 fields).
9
//! - [`split`] — `SPLIT-*` accessors (5 fields).
10
//! - [`tag`] — `TAG-NAME` / `TAG-VALUE` + the shared GC-array string
11
//!   reader.
12
//! - [`create_tag`] — `CREATE-TAG` (constant + runtime codegen).
13
//! - [`delete_entity`] — `DELETE-ENTITY`.
14
//!
15
//! Common helpers (`arity_check`, `compile_idx_to_stack`,
16
//! `emit_entity_header_offset`, `emit_entity_data_offset`) live here
17
//! in mod.rs since every accessor uses them.
18

            
19
mod create_tag;
20
mod delete_entity;
21
mod meta;
22
mod split;
23
mod tag;
24
mod transaction;
25

            
26
#[cfg(test)]
27
mod tests;
28

            
29
use crate::ast::{Expr, WasmType};
30
use crate::compiler::context::CompileContext;
31
use crate::compiler::emit::FunctionEmitter;
32
use crate::compiler::expr::{compile_for_stack, eval_value};
33
use crate::error::{Error, Result};
34
use crate::runtime::SymbolTable;
35

            
36
use super::NativeSpec;
37

            
38
pub(super) const NATIVES: &[NativeSpec] = &[
39
    NativeSpec {
40
        name: "ENTITY-COUNT",
41
        eval: meta::entity_count,
42
        stack: Some(meta::compile_entity_count_to_stack),
43
        effect: None,
44
    },
45
    NativeSpec {
46
        name: "CONTEXT-TYPE",
47
        eval: meta::context_type,
48
        stack: Some(meta::compile_context_type_to_stack),
49
        effect: None,
50
    },
51
    NativeSpec {
52
        name: "PRIMARY-ENTITY-TYPE",
53
        eval: meta::primary_entity_type,
54
        stack: Some(meta::compile_primary_entity_type_to_stack),
55
        effect: None,
56
    },
57
    NativeSpec {
58
        name: "PRIMARY-ENTITY-IDX",
59
        eval: meta::primary_entity_idx,
60
        stack: Some(meta::compile_primary_entity_idx_to_stack),
61
        effect: None,
62
    },
63
    NativeSpec {
64
        name: "ENTITY-TYPE",
65
        eval: meta::entity_type,
66
        stack: Some(meta::compile_entity_type_to_stack),
67
        effect: None,
68
    },
69
    NativeSpec {
70
        name: "ENTITY-PARENT-IDX",
71
        eval: meta::entity_parent_idx,
72
        stack: Some(meta::compile_entity_parent_idx_to_stack),
73
        effect: None,
74
    },
75
    NativeSpec {
76
        name: "TRANSACTION-SPLIT-COUNT",
77
        eval: transaction::transaction_split_count,
78
        stack: Some(transaction::compile_transaction_split_count_to_stack),
79
        effect: None,
80
    },
81
    NativeSpec {
82
        name: "TRANSACTION-TAG-COUNT",
83
        eval: transaction::transaction_tag_count,
84
        stack: Some(transaction::compile_transaction_tag_count_to_stack),
85
        effect: None,
86
    },
87
    NativeSpec {
88
        name: "TRANSACTION-IS-MULTI-CURRENCY",
89
        eval: transaction::transaction_is_multi_currency,
90
        stack: Some(transaction::compile_transaction_is_multi_currency_to_stack),
91
        effect: None,
92
    },
93
    NativeSpec {
94
        name: "TRANSACTION-POST-DATE",
95
        eval: transaction::transaction_post_date,
96
        stack: Some(transaction::compile_transaction_post_date_to_stack),
97
        effect: None,
98
    },
99
    NativeSpec {
100
        name: "TRANSACTION-ENTER-DATE",
101
        eval: transaction::transaction_enter_date,
102
        stack: Some(transaction::compile_transaction_enter_date_to_stack),
103
        effect: None,
104
    },
105
    NativeSpec {
106
        name: "SPLIT-VALUE-NUM",
107
        eval: split::split_value_num,
108
        stack: Some(split::compile_split_value_num_to_stack),
109
        effect: None,
110
    },
111
    NativeSpec {
112
        name: "SPLIT-VALUE-DENOM",
113
        eval: split::split_value_denom,
114
        stack: Some(split::compile_split_value_denom_to_stack),
115
        effect: None,
116
    },
117
    NativeSpec {
118
        name: "SPLIT-VALUE",
119
        eval: split::split_value,
120
        stack: Some(split::compile_split_value_to_stack),
121
        effect: None,
122
    },
123
    NativeSpec {
124
        name: "SPLIT-RECONCILE-STATE",
125
        eval: split::split_reconcile_state,
126
        stack: Some(split::compile_split_reconcile_state_to_stack),
127
        effect: None,
128
    },
129
    NativeSpec {
130
        name: "SPLIT-RECONCILE-DATE",
131
        eval: split::split_reconcile_date,
132
        stack: Some(split::compile_split_reconcile_date_to_stack),
133
        effect: None,
134
    },
135
    NativeSpec {
136
        name: "SPLIT-ACCOUNT-NAME",
137
        eval: split::split_account_name,
138
        stack: Some(split::compile_split_account_name_to_stack),
139
        effect: None,
140
    },
141
    NativeSpec {
142
        name: "TAG-NAME",
143
        eval: tag::tag_name,
144
        stack: Some(tag::compile_tag_name_to_stack),
145
        effect: None,
146
    },
147
    NativeSpec {
148
        name: "TAG-VALUE",
149
        eval: tag::tag_value,
150
        stack: Some(tag::compile_tag_value_to_stack),
151
        effect: None,
152
    },
153
    NativeSpec {
154
        name: "CREATE-TAG",
155
        eval: create_tag::create_tag,
156
        stack: None,
157
        effect: Some(create_tag::compile_create_tag),
158
    },
159
    NativeSpec {
160
        name: "DELETE-ENTITY",
161
        eval: delete_entity::delete_entity,
162
        stack: None,
163
        effect: Some(delete_entity::compile_delete_entity),
164
    },
165
    NativeSpec {
166
        name: "GET-INPUT-ENTITIES",
167
        eval: meta::get_input_entities,
168
        stack: Some(compile_get_input_entities_stack),
169
        effect: None,
170
    },
171
];
172

            
173
/// Adapter so the registry sees the standard `(ctx, emit, sym, args)`
174
/// shape on the `GET-INPUT-ENTITIES` slot; the underlying builder in
175
/// [`meta`] takes only `(ctx, emit)` because it ignores symbols/args.
176
544
fn compile_get_input_entities_stack(
177
544
    ctx: &mut CompileContext,
178
544
    emit: &mut FunctionEmitter,
179
544
    _symbols: &mut SymbolTable,
180
544
    _args: &[Expr],
181
544
) -> Result<WasmType> {
182
544
    meta::compile_get_input_entities_to_stack(ctx, emit)
183
544
}
184

            
185
pub(in crate::compiler) use create_tag::compile_create_tag;
186

            
187
111444
pub(super) fn arity_check(name: &str, args: &[Expr], expected: usize) -> Result<()> {
188
111444
    if args.len() != expected {
189
3
        return Err(Error::Arity {
190
3
            name: name.to_string(),
191
3
            expected,
192
3
            actual: args.len(),
193
3
        });
194
111441
    }
195
111441
    Ok(())
196
111444
}
197

            
198
/// Compile an entity index argument to i32 on the WASM stack.
199
24348
pub(super) fn compile_idx_to_stack(
200
24348
    ctx: &mut CompileContext,
201
24348
    emit: &mut FunctionEmitter,
202
24348
    symbols: &mut SymbolTable,
203
24348
    expr: &Expr,
204
24348
) -> Result<()> {
205
24348
    let resolved = eval_value(symbols, expr)?;
206
13736
    match &resolved {
207
        Expr::WasmRuntime(WasmType::I32) | Expr::WasmLocal(_, WasmType::I32) => {
208
10612
            compile_for_stack(ctx, emit, symbols, expr)?;
209
        }
210
        Expr::WasmRuntime(WasmType::Ratio) | Expr::WasmLocal(_, WasmType::Ratio) => {
211
            compile_for_stack(ctx, emit, symbols, expr)?;
212
            emit.struct_get(ctx.ids.ty_ratio, 0);
213
            emit.i32_wrap_i64();
214
        }
215
13736
        Expr::Number(n) if *n.denom() == 1 => {
216
13736
            emit.i32_const(*n.numer() as i32);
217
13736
        }
218
        _ => {
219
            return Err(Error::Compile(format!(
220
                "entity index must be i32, got {resolved:?}"
221
            )));
222
        }
223
    }
224
24348
    Ok(())
225
24348
}
226

            
227
/// Emit WASM to compute the entity header offset for a given index.
228
/// Expects entity index on the WASM stack. Leaves header offset on stack.
229
/// `header_offset` = `entities_offset` + idx * 32
230
/// where `entities_offset` is an absolute offset read from `GlobalHeader`.
231
22230
pub(super) fn emit_entity_header_offset(
232
22230
    ctx: &CompileContext,
233
22230
    emit: &mut FunctionEmitter,
234
22230
    temp_local: u32,
235
22230
) -> Result<()> {
236
    // Stack: [idx]
237
22230
    emit.i32_const(32);
238
22230
    emit.i32_mul(); // [idx * 32]
239
22230
    emit.local_set(temp_local); // save idx*32
240
22230
    emit.call(ctx.ids.get_input_offset()?); // [input_base]
241
22230
    emit.i32_load(0x0C); // [entities_offset] (absolute)
242
22230
    emit.local_get(temp_local); // [entities_offset, idx*32]
243
22230
    emit.i32_add(); // [header_offset]
244
22230
    Ok(())
245
22230
}
246

            
247
/// Emit WASM to compute the entity data offset for a given index.
248
/// Expects entity index on the WASM stack. Leaves data offset on stack.
249
/// `data_offset` = `entity_header`[idx].`data_offset` (absolute memory address)
250
17916
pub(super) fn emit_entity_data_offset(
251
17916
    ctx: &CompileContext,
252
17916
    emit: &mut FunctionEmitter,
253
17916
    temp_local: u32,
254
17916
) -> Result<()> {
255
17916
    emit_entity_header_offset(ctx, emit, temp_local)?;
256
17916
    emit.i32_load(0x18); // data_offset at EntityHeader offset 0x18
257
17916
    Ok(())
258
17916
}