Lines
100 %
Functions
Branches
use nomiscript::{
Compiler, Expr, Fraction, Reader, Symbol, SymbolKind, SymbolTable, Value, WasmType,
eval_program,
};
fn eval_to_value(symbols: &mut SymbolTable, code: &str) -> Value {
let program = Reader::parse(code).unwrap();
eval_program(symbols, &program).unwrap()
}
// --- Constant folding regression tests ---
#[test]
fn test_add_constant_fold() {
let mut symbols = SymbolTable::with_builtins();
let result = eval_to_value(&mut symbols, "(+ 1 2 3)");
assert_eq!(result, Value::Number(Fraction::from_integer(6)));
fn test_sub_constant_fold() {
let result = eval_to_value(&mut symbols, "(- 10 3)");
assert_eq!(result, Value::Number(Fraction::from_integer(7)));
fn test_mul_constant_fold() {
let result = eval_to_value(&mut symbols, "(* 3 4)");
assert_eq!(result, Value::Number(Fraction::from_integer(12)));
fn test_div_constant_fold() {
let result = eval_to_value(&mut symbols, "(/ 10 3)");
assert_eq!(result, Value::Number(Fraction::new(10, 3)));
fn test_ratio_arithmetic() {
// Reader doesn't support 1/3 literals; use (/ 1 3) instead
let result = eval_to_value(&mut symbols, "(+ (/ 1 3) (/ 1 4))");
assert_eq!(result, Value::Number(Fraction::new(7, 12)));
fn test_comparison_constant_fold() {
assert_eq!(eval_to_value(&mut symbols, "(= 1 1)"), Value::Bool(true));
assert_eq!(eval_to_value(&mut symbols, "(< 1 2)"), Value::Bool(true));
assert_eq!(eval_to_value(&mut symbols, "(> 3 2)"), Value::Bool(true));
// --- WASM compilation tests ---
fn test_compile_constant_arithmetic_produces_valid_wasm() {
let program = Reader::parse("(+ (/ 1 3) (/ 1 4))").unwrap();
let mut compiler = Compiler::new();
let wasm = compiler
.compile(&program, &mut SymbolTable::with_builtins_for_wasm())
.unwrap();
assert_eq!(&wasm[0..4], b"\0asm");
fn test_compile_comparison_produces_valid_wasm() {
let program = Reader::parse("(= 1 2)").unwrap();
fn test_compile_nested_arithmetic_produces_valid_wasm() {
let program = Reader::parse("(* (+ 1 2) (- 5 3))").unwrap();
// --- WasmRuntime propagation tests (unit tests need internal access) ---
// These test the compile-time type tracking through the eval path.
// Since eval_value is crate-private, we test via WASM compilation
// with symbols pre-set to WasmRuntime values.
fn test_arithmetic_rejects_i32_type() {
let mut symbols = SymbolTable::with_builtins_for_wasm();
symbols.define(
Symbol::new("IDX", SymbolKind::Variable).with_value(Expr::WasmRuntime(WasmType::I32)),
);
let program = Reader::parse("(+ IDX 1)").unwrap();
let result = compiler.compile(&program, &mut symbols);
assert!(result.is_err(), "should reject I32 in arithmetic");
let err = result.unwrap_err().to_string();
assert!(
err.contains("ratio") || err.contains("indices"),
"error should mention type mismatch: {err}"
fn test_compile_runtime_addition_produces_valid_wasm() {
Symbol::new("X", SymbolKind::Variable).with_value(Expr::WasmRuntime(WasmType::Ratio)),
let program = Reader::parse("(+ X (/ 1 3))").unwrap();
let wasm = compiler.compile(&program, &mut symbols).unwrap();
fn test_compile_runtime_subtraction_produces_valid_wasm() {
let program = Reader::parse("(- X 1)").unwrap();
fn test_compile_runtime_multiplication_produces_valid_wasm() {
let program = Reader::parse("(* X 2)").unwrap();
fn test_compile_runtime_division_produces_valid_wasm() {
let program = Reader::parse("(/ X 3)").unwrap();
fn test_compile_runtime_nested_arithmetic_produces_valid_wasm() {
let program = Reader::parse("(+ (* X 2) (/ 1 3))").unwrap();
fn test_compile_runtime_comparison_produces_valid_wasm() {
let program = Reader::parse("(= X 0)").unwrap();
// --- Runtime control flow tests ---
fn test_compile_runtime_if_produces_valid_wasm() {
let program = Reader::parse("(if (= X 0) (+ X 1) (- X 1))").unwrap();
fn test_compile_runtime_if_no_else_produces_valid_wasm() {
let program = Reader::parse("(if (= X 0) (+ X 1))").unwrap();
fn test_compile_runtime_and_produces_valid_wasm() {
let program = Reader::parse("(and (= X 0) (> X 1))").unwrap();
fn test_compile_runtime_or_produces_valid_wasm() {
let program = Reader::parse("(or (= X 0) (< X 1))").unwrap();
fn test_compile_runtime_cond_produces_valid_wasm() {
let program = Reader::parse("(cond ((= X 0) (+ X 1)) ((> X 0) (- X 1)))").unwrap();
// WHEN expands to (if test (begin body...) nil), UNLESS to (if test nil (begin body...))
// Test the equivalent IF forms since stdlib isn't loaded for WASM
fn test_compile_runtime_when_equivalent_produces_valid_wasm() {
let program = Reader::parse("(if (= X 0) (begin (+ X 1)) nil)").unwrap();
fn test_compile_runtime_unless_equivalent_produces_valid_wasm() {
let program = Reader::parse("(if (= X 0) nil (begin (- X 1)))").unwrap();
fn test_compile_runtime_not_produces_valid_wasm() {
let program = Reader::parse("(not (= X 0))").unwrap();
// --- LET/LET* with runtime bindings ---
fn test_compile_let_star_runtime_binding_produces_valid_wasm() {
// LET* binds Y to a runtime value (X + 1), then uses Y in body
let program = Reader::parse("(let* ((Y (+ X 1))) (+ Y 2))").unwrap();
fn test_compile_let_star_multiple_runtime_bindings_produces_valid_wasm() {
let program = Reader::parse("(let* ((Y (+ X 1)) (Z (* Y 2))) (- Z X))").unwrap();
fn test_compile_let_runtime_with_if_produces_valid_wasm() {
let program = Reader::parse("(let* ((Y (+ X 1))) (if (= Y 0) (+ Y 1) (- Y 1)))").unwrap();
// --- DO / DO* with runtime bindings ---
fn test_compile_runtime_do_simple_produces_valid_wasm() {
Symbol::new("N", SymbolKind::Variable).with_value(Expr::WasmRuntime(WasmType::Ratio)),
let program = Reader::parse("(do ((I 0 (+ I 1))) ((>= I N) I))").unwrap();
fn test_compile_runtime_do_with_body_produces_valid_wasm() {
let program =
Reader::parse("(let* ((SUM 0)) (do ((I 0 (+ I 1))) ((>= I N) SUM) (setf SUM (+ SUM I))))")
fn test_compile_runtime_do_star_produces_valid_wasm() {
let program = Reader::parse("(do* ((I 0 (+ I 1))) ((>= I N) I))").unwrap();
fn test_compile_runtime_do_multiple_vars_produces_valid_wasm() {
let program = Reader::parse("(do ((I 0 (+ I 1)) (J N (- J 1))) ((>= I J) (+ I J)))").unwrap();
fn test_compile_runtime_do_no_result_produces_valid_wasm() {
let program = Reader::parse("(do ((I 0 (+ I 1))) ((>= I N)))").unwrap();
// --- Entity accessors ---
fn test_entity_count_produces_valid_wasm() {
let program = Reader::parse("(entity-count)").unwrap();
fn test_context_type_produces_valid_wasm() {
let program = Reader::parse("(context-type)").unwrap();
fn test_primary_entity_type_produces_valid_wasm() {
let program = Reader::parse("(primary-entity-type)").unwrap();
fn test_primary_entity_idx_produces_valid_wasm() {
let program = Reader::parse("(primary-entity-idx)").unwrap();
fn test_entity_count_in_comparison_produces_valid_wasm() {
let program = Reader::parse("(if (= (entity-count) 0) \"empty\" \"has-entities\")").unwrap();
fn test_entity_count_in_do_loop_produces_valid_wasm() {
let program = Reader::parse(
"(let* ((n (entity-count)) (sum 0))
(do ((i 0 (+ i 1))) ((>= i n) sum)
(setf sum (+ sum 1))))",
)
fn test_entity_type_with_idx_produces_valid_wasm() {
let program = Reader::parse("(entity-type IDX)").unwrap();
fn test_entity_parent_idx_produces_valid_wasm() {
let program = Reader::parse("(entity-parent-idx IDX)").unwrap();
fn test_entity_constants_defined() {
let symbols = SymbolTable::with_builtins_for_wasm();
for name in [
"+ENTITY-TRANSACTION+",
"+ENTITY-SPLIT+",
"+ENTITY-TAG+",
"+ENTITY-ACCOUNT+",
"+ENTITY-COMMODITY+",
"+CONTEXT-CREATE+",
"+CONTEXT-UPDATE+",
"+CONTEXT-DELETE+",
"+CONTEXT-BATCH+",
] {
assert!(symbols.lookup(name).is_some(), "missing constant: {name}");
// --- SETF with WasmLocal ---
fn test_compile_runtime_setf_wasm_local_produces_valid_wasm() {
let program = Reader::parse("(let* ((Y (+ X 1))) (setf Y (+ Y 2)) Y)").unwrap();