Lines
100 %
Functions
66.67 %
Branches
//! Compile-verification for the Metro category-tagging sample
//! (`scripting/nomiscript/tests/samples/tag-metro-splits.nms`).
//!
//! The sample walks every transaction and, for one with EXACTLY TWO splits
//! where at least one leg is on the "Metro" account (direction is not
//! considered), sets `category=transportation` on every leg that lacks one. It
//! exercises the host-native registry (`list-transactions` / `get-account` /
//! `account-name` / `get-split-tag` / `set-split-tag`), the host-prelude helper
//! `split:list-for-transaction` (over the `list-splits-by-transaction` native),
//! `filter` with a lambda + `length` over a runtime entity list, plus
//! `catch-each`, `dolist` in a defun-body value position, and generic string
//! `equal?` on a runtime `account-name`.
//! The nms sample harness only PARSES samples, so this is the only test
//! that compiles the script through the real eval + host-fn path. It is
//! the regression lock for the three compiler gaps the script surfaced:
//! DOLIST stack handler, EQUAL?/EQ? phantom natives, and runtime-string
//! generic equality.
use std::path::PathBuf;
use nomiscript::{Compiler, Reader, SymbolTable};
use rpc::natives::all_compiler_specs;
fn compile_with_host_fns(src: &str) -> Result<Vec<u8>, String> {
let host_fns = all_compiler_specs();
let program = Reader::parse(src).map_err(|e| format!("parse: {e}"))?;
let mut symbols = SymbolTable::with_builtins();
symbols.register_host_fns(&host_fns);
// Load the host-dependent prelude (split:list-for-transaction, …) the same
// way Session::new does, so the sample's prelude helpers resolve.
rpc::host_prelude::load(&mut symbols);
let mut compiler = Compiler::with_host_fns(host_fns);
compiler
.compile_eval_with_type(&program, &mut symbols)
.map(|(wasm, _ty)| wasm)
.map_err(|e| format!("compile: {e}"))
}
#[test]
fn metro_sample_compiles_through_host_fn_path() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../scripting/nomiscript/tests/samples/tag-metro-splits.nms");
let src =
std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("read {}: {e}", path.display()));
let wasm = compile_with_host_fns(&src).expect("Metro sample must compile");
assert!(!wasm.is_empty());
/// The three gaps in isolation, so a failure points at the specific cause
/// rather than the whole sample.
fn dolist_in_defun_body_value_position_compiles() {
let src = "(defun walk (tx) (dolist (s (list-splits (transaction-id tx))) (split-id s))) \
(walk (get-transaction \"x\"))";
compile_with_host_fns(src).expect("dolist value position");
fn generic_equal_on_runtime_string_compiles() {
// `account-name` is a runtime StringRef; `equal?` against a literal
// must lower through `string_eq`, not the numeric path.
let src = "(equal? (account-name (get-account \"a\")) \"Metro\")";
compile_with_host_fns(src).expect("runtime-string equal?");
fn equal_question_and_eq_question_aliases_compile() {
compile_with_host_fns("(eq? 1 1)").expect("eq?");
compile_with_host_fns("(equal? 'a 'a)").expect("equal?");
/// A `StringRef` from an entity accessor can be null at runtime (absent
/// field). `string_eq` is now null-guarded (null==null true, null vs
/// non-null false) so `(equal? <maybe-null> "x")` compiles to valid wasm
/// instead of an `array.len`-on-null trap. The null guard adds `ref.is_null`
/// branches the validator checks; a malformed guard would fail compilation.
fn equal_on_nullable_string_accessor_compiles() {
let wasm = compile_with_host_fns(src).expect("nullable-string equal? compiles");