1
//! Compile-verification for the Metro category-tagging sample
2
//! (`scripting/nomiscript/tests/samples/tag-metro-splits.nms`).
3
//!
4
//! The sample walks every transaction and, for one with EXACTLY TWO splits
5
//! where at least one leg is on the "Metro" account (direction is not
6
//! considered), sets `category=transportation` on every leg that lacks one. It
7
//! exercises the host-native registry (`list-transactions` / `get-account` /
8
//! `account-name` / `get-split-tag` / `set-split-tag`), the host-prelude helper
9
//! `split:list-for-transaction` (over the `list-splits-by-transaction` native),
10
//! `filter` with a lambda + `length` over a runtime entity list, plus
11
//! `catch-each`, `dolist` in a defun-body value position, and generic string
12
//! `equal?` on a runtime `account-name`.
13
//!
14
//! The nms sample harness only PARSES samples, so this is the only test
15
//! that compiles the script through the real eval + host-fn path. It is
16
//! the regression lock for the three compiler gaps the script surfaced:
17
//! DOLIST stack handler, EQUAL?/EQ? phantom natives, and runtime-string
18
//! generic equality.
19

            
20
use std::path::PathBuf;
21

            
22
use nomiscript::{Compiler, Reader, SymbolTable};
23
use rpc::natives::all_compiler_specs;
24

            
25
6
fn compile_with_host_fns(src: &str) -> Result<Vec<u8>, String> {
26
6
    let host_fns = all_compiler_specs();
27
6
    let program = Reader::parse(src).map_err(|e| format!("parse: {e}"))?;
28
6
    let mut symbols = SymbolTable::with_builtins();
29
6
    symbols.register_host_fns(&host_fns);
30
    // Load the host-dependent prelude (split:list-for-transaction, …) the same
31
    // way Session::new does, so the sample's prelude helpers resolve.
32
6
    rpc::host_prelude::load(&mut symbols);
33
6
    let mut compiler = Compiler::with_host_fns(host_fns);
34
6
    compiler
35
6
        .compile_eval_with_type(&program, &mut symbols)
36
6
        .map(|(wasm, _ty)| wasm)
37
6
        .map_err(|e| format!("compile: {e}"))
38
6
}
39

            
40
#[test]
41
1
fn metro_sample_compiles_through_host_fn_path() {
42
1
    let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
43
1
        .join("../scripting/nomiscript/tests/samples/tag-metro-splits.nms");
44
1
    let src =
45
1
        std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("read {}: {e}", path.display()));
46
1
    let wasm = compile_with_host_fns(&src).expect("Metro sample must compile");
47
1
    assert!(!wasm.is_empty());
48
1
}
49

            
50
/// The three gaps in isolation, so a failure points at the specific cause
51
/// rather than the whole sample.
52
#[test]
53
1
fn dolist_in_defun_body_value_position_compiles() {
54
1
    let src = "(defun walk (tx) (dolist (s (list-splits (transaction-id tx))) (split-id s))) \
55
1
               (walk (get-transaction \"x\"))";
56
1
    compile_with_host_fns(src).expect("dolist value position");
57
1
}
58

            
59
#[test]
60
1
fn generic_equal_on_runtime_string_compiles() {
61
    // `account-name` is a runtime StringRef; `equal?` against a literal
62
    // must lower through `string_eq`, not the numeric path.
63
1
    let src = "(equal? (account-name (get-account \"a\")) \"Metro\")";
64
1
    compile_with_host_fns(src).expect("runtime-string equal?");
65
1
}
66

            
67
#[test]
68
1
fn equal_question_and_eq_question_aliases_compile() {
69
1
    compile_with_host_fns("(eq? 1 1)").expect("eq?");
70
1
    compile_with_host_fns("(equal? 'a 'a)").expect("equal?");
71
1
}
72

            
73
/// A `StringRef` from an entity accessor can be null at runtime (absent
74
/// field). `string_eq` is now null-guarded (null==null true, null vs
75
/// non-null false) so `(equal? <maybe-null> "x")` compiles to valid wasm
76
/// instead of an `array.len`-on-null trap. The null guard adds `ref.is_null`
77
/// branches the validator checks; a malformed guard would fail compilation.
78
#[test]
79
1
fn equal_on_nullable_string_accessor_compiles() {
80
1
    let src = "(equal? (account-name (get-account \"a\")) \"Metro\")";
81
1
    let wasm = compile_with_host_fns(src).expect("nullable-string equal? compiles");
82
1
    assert!(!wasm.is_empty());
83
1
}