Make test.sx self-executing: evaluators run it directly, no codegen

test.sx now defines deftest/defsuite as macros. Any host that provides
5 platform functions (try-call, report-pass, report-fail, push-suite,
pop-suite) can evaluate the file directly — no bootstrap compilation
step needed for JS.

- Added defmacro for deftest (wraps body in thunk, catches via try-call)
- Added defmacro for defsuite (push/pop suite context stack)
- Created run.js: sx-browser.js evaluates test.sx directly (81/81 pass)
- Created run.py: Python evaluator evaluates test.sx directly (81/81 pass)
- Deleted bootstrap_test_js.py and generated test_sx_spec.js
- Updated testing docs page to reflect self-executing architecture

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-07 10:50:28 +00:00
parent 754e7557f5
commit e9d86d628b
7 changed files with 299 additions and 508 deletions

View File

@@ -1,210 +0,0 @@
#!/usr/bin/env python3
"""
Bootstrap compiler: test.sx -> Node.js test script.
Reads test.sx and emits a standalone JavaScript file that runs each
deftest case using sx-browser.js's evaluator. Reports results as TAP.
Uses the bootstrapped sx-browser.js (from the spec), NOT sx.js.
Usage:
python bootstrap_test_js.py --output shared/sx/tests/test_sx_spec.js
node shared/sx/tests/test_sx_spec.js
"""
from __future__ import annotations
import os
import sys
import tempfile
import argparse
_HERE = os.path.dirname(os.path.abspath(__file__))
_PROJECT = os.path.abspath(os.path.join(_HERE, "..", "..", ".."))
sys.path.insert(0, _PROJECT)
from shared.sx.ref.bootstrap_test import _parse_test_sx, _sx_to_source, _slugify
def _js_escape(s: str) -> str:
"""Escape a string for embedding in JS single-quoted string."""
return s.replace('\\', '\\\\').replace("'", "\\'").replace('\n', '\\n')
def _emit_js(suites: list[dict], preamble: list) -> str:
"""Emit a Node.js test script from parsed suites."""
preamble_sx = "\\n".join(_js_escape(_sx_to_source(expr)) for expr in preamble)
lines = []
lines.append('// Auto-generated from test.sx — SX spec self-tests for JavaScript.')
lines.append('//')
lines.append('// DO NOT EDIT. Regenerate with:')
lines.append('// python shared/sx/ref/bootstrap_test_js.py --output shared/sx/tests/test_sx_spec.js')
lines.append('//')
lines.append('// Run:')
lines.append('// node shared/sx/tests/test_sx_spec.js')
lines.append('')
lines.append('// --- Load sx-browser.js (bootstrapped from spec) ---')
lines.append('Object.defineProperty(globalThis, "document", { value: undefined, writable: true });')
lines.append('var _sxPath = require("path").resolve(__dirname, "../../static/scripts/sx-browser.js");')
lines.append('var Sx = require(_sxPath);')
lines.append('')
lines.append('// --- Inject spec primitives into env ---')
lines.append('// sx-browser.js has assert but is missing some spec primitives.')
lines.append('// We inject them into the test env since PRIMITIVES is not exposed.')
lines.append('var NIL = Sx.NIL;')
lines.append('function _isNil(x) { return x === NIL || x === null || x === undefined; }')
lines.append('function _deepEqual(a, b) {')
lines.append(' if (a === b) return true;')
lines.append(' if (_isNil(a) && _isNil(b)) return true;')
lines.append(' if (typeof a !== typeof b) return false;')
lines.append(' if (Array.isArray(a) && Array.isArray(b)) {')
lines.append(' if (a.length !== b.length) return false;')
lines.append(' for (var i = 0; i < a.length; i++) if (!_deepEqual(a[i], b[i])) return false;')
lines.append(' return true;')
lines.append(' }')
lines.append(' if (a && typeof a === "object" && b && typeof b === "object") {')
lines.append(' var ka = Object.keys(a), kb = Object.keys(b);')
lines.append(' if (ka.length !== kb.length) return false;')
lines.append(' for (var j = 0; j < ka.length; j++) if (!_deepEqual(a[ka[j]], b[ka[j]])) return false;')
lines.append(' return true;')
lines.append(' }')
lines.append(' return false;')
lines.append('}')
lines.append('')
lines.append('// Primitives injected into every test env')
lines.append('var _envPrimitives = {')
lines.append(' "equal?": function(a, b) { return _deepEqual(a, b); },')
lines.append(' "eq?": function(a, b) { return a === b; },')
lines.append(' "boolean?": function(x) { return typeof x === "boolean"; },')
lines.append(' "string-length": function(s) { return String(s).length; },')
lines.append(' "substring": function(s, start, end) { return String(s).slice(start, end); },')
lines.append(' "string-contains?": function(s, needle) { return String(s).indexOf(needle) !== -1; },')
lines.append(' "upcase": function(s) { return String(s).toUpperCase(); },')
lines.append(' "downcase": function(s) { return String(s).toLowerCase(); },')
lines.append(' "reverse": function(c) { return c ? c.slice().reverse() : []; },')
lines.append(' "flatten": function(c) {')
lines.append(' var r = []; for (var i = 0; i < (c||[]).length; i++) {')
lines.append(' if (Array.isArray(c[i])) for (var j = 0; j < c[i].length; j++) r.push(c[i][j]);')
lines.append(' else r.push(c[i]);')
lines.append(' } return r;')
lines.append(' },')
lines.append(' "has-key?": function(d, k) { return d && typeof d === "object" && k in d; },')
lines.append(' // Fix append to concatenate when x is a list')
lines.append(' "append": function(c, x) { return Array.isArray(x) ? (c||[]).concat(x) : (c||[]).concat([x]); },')
lines.append('};')
lines.append('')
lines.append('// --- Test infrastructure ---')
lines.append('var _passed = 0, _failed = 0, _errors = [];')
lines.append('var _testNum = 0;')
lines.append('')
lines.append('function _makeEnv() {')
lines.append(' var env = {};')
lines.append(' // Copy injected primitives into env')
lines.append(' for (var k in _envPrimitives) env[k] = _envPrimitives[k];')
lines.append(f" var src = '{preamble_sx}';")
lines.append(' var exprs = Sx.parseAll(src);')
lines.append(' for (var i = 0; i < exprs.length; i++) Sx.eval(exprs[i], env);')
lines.append(' return env;')
lines.append('}')
lines.append('')
lines.append('function _run(name, sxSource) {')
lines.append(' _testNum++;')
lines.append(' try {')
lines.append(' var env = _makeEnv();')
lines.append(' var exprs = Sx.parseAll(sxSource);')
lines.append(' for (var i = 0; i < exprs.length; i++) Sx.eval(exprs[i], env);')
lines.append(' _passed++;')
lines.append(' console.log("ok " + _testNum + " - " + name);')
lines.append(' } catch (e) {')
lines.append(' _failed++;')
lines.append(' _errors.push({ name: name, error: e.message || String(e) });')
lines.append(' console.log("not ok " + _testNum + " - " + name);')
lines.append(' console.log(" # " + (e.message || String(e)));')
lines.append(' }')
lines.append('}')
lines.append('')
# Count total tests
total = _count_tests(suites)
lines.append(f'console.log("TAP version 13");')
lines.append(f'console.log("1..{total}");')
lines.append('')
# Emit test calls
for suite in suites:
_emit_js_suite(suite, lines, prefix="")
lines.append('')
lines.append('// --- Summary ---')
lines.append('console.log("");')
lines.append('console.log("# tests " + (_passed + _failed));')
lines.append('console.log("# pass " + _passed);')
lines.append('if (_failed > 0) {')
lines.append(' console.log("# fail " + _failed);')
lines.append(' for (var ei = 0; ei < _errors.length; ei++) {')
lines.append(' console.log("# FAIL: " + _errors[ei].name + "" + _errors[ei].error);')
lines.append(' }')
lines.append(' process.exit(1);')
lines.append('}')
return "\n".join(lines)
def _count_tests(items: list[dict]) -> int:
total = 0
for item in items:
if item["type"] == "test":
total += 1
elif item["type"] == "suite":
total += _count_tests(item["tests"])
return total
def _emit_js_suite(suite: dict, lines: list[str], prefix: str):
"""Emit test calls for a suite."""
suite_prefix = f"{prefix}{suite['name']} > " if prefix else f"{suite['name']} > "
lines.append(f'// --- {suite["name"]} ---')
for item in suite["tests"]:
if item["type"] == "test":
_emit_js_test(item, lines, suite_prefix)
elif item["type"] == "suite":
_emit_js_suite(item, lines, suite_prefix)
def _emit_js_test(test: dict, lines: list[str], prefix: str):
"""Emit a single test call."""
body_parts = [_sx_to_source(expr) for expr in test["body"]]
if len(body_parts) == 1:
sx_source = body_parts[0]
else:
sx_source = "(do " + " ".join(body_parts) + ")"
name = f'{prefix}{test["name"]}'
lines.append(f"_run('{_js_escape(name)}', '{_js_escape(sx_source)}');")
def main():
parser = argparse.ArgumentParser(description="Bootstrap test.sx to Node.js")
parser.add_argument("--output", "-o", help="Output file path")
parser.add_argument("--dry-run", action="store_true", help="Print to stdout")
args = parser.parse_args()
test_sx = os.path.join(_HERE, "test.sx")
suites, preamble = _parse_test_sx(test_sx)
print(f"Parsed {len(suites)} suites, {len(preamble)} preamble defines from test.sx", file=sys.stderr)
total = _count_tests(suites)
print(f"Total test cases: {total}", file=sys.stderr)
output = _emit_js(suites, preamble)
if args.output and not args.dry_run:
with open(args.output, "w") as f:
f.write(output)
print(f"Wrote {args.output}", file=sys.stderr)
else:
print(output)
if __name__ == "__main__":
main()

View File

@@ -1,58 +1,43 @@
;; ==========================================================================
;; test.sx — Self-hosting SX test framework
;;
;; Defines a minimal test framework in SX that bootstraps to every host.
;; Tests are written in SX and verify SX semantics — the language tests
;; itself. Bootstrap compilers emit native test runners from this spec.
;;
;; The framework uses only primitives already in primitives.sx:
;; assert, equal?, type-of, str, list, len, error
;;
;; Usage:
;; (defsuite "Suite name"
;; (deftest "test name" (assert (equal? 1 1)))
;; (deftest "another" (assert (= (+ 1 2) 3))))
;; Defines a minimal test framework in SX that tests SX — the language
;; proves its own correctness. The framework is self-executing: any host
;; that provides 5 platform functions can evaluate this file directly.
;;
;; Platform functions required:
;; (test-report suite-name results) → platform-specific output
;; results is a list of {:name "..." :passed bool :error "..."|nil}
;; try-call (thunk) → {:ok true} | {:ok false :error "msg"}
;; report-pass (name) → platform-specific pass output
;; report-fail (name error) → platform-specific fail output
;; push-suite (name) → push suite name onto context stack
;; pop-suite () → pop suite name from context stack
;;
;; Bootstrap compilers read this file and:
;; 1. Emit the test runner infrastructure
;; 2. Emit each test suite as native test cases
;; 3. Wire up to the platform test framework (pytest, Jest, etc.)
;; Usage:
;; ;; Host injects platform functions into env, then:
;; (eval-file "test.sx" env)
;;
;; The same test.sx runs on every host — Python, JavaScript, etc.
;; ==========================================================================
;; --------------------------------------------------------------------------
;; 1. Test framework forms
;; 1. Test framework macros
;; --------------------------------------------------------------------------
;;
;; deftest and defsuite are declarative — bootstrap compilers parse them
;; and emit native test functions. They are NOT runtime macros.
;;
;; (deftest "name" body ...)
;; Declares a test case. Body expressions are evaluated sequentially.
;; The test passes if no assertion fails (no error is raised).
;; Uses `assert` primitive for assertions.
;;
;; (defsuite "name" test ...)
;; Groups tests into a named suite. Suites can be nested.
;; deftest and defsuite are macros that make test.sx directly executable.
;; The host provides try-call (error catching), reporting, and suite
;; context — everything else is pure SX.
;; (define-special-form "deftest"
;; :syntax (deftest name body ...)
;; :doc "Declare a test case. Name is a string literal.
;; Body expressions are evaluated in a fresh env extended from
;; the suite env. Test passes if all assertions succeed."
;; :example '(deftest "addition" (assert (= (+ 1 2) 3))))
;;
;; (define-special-form "defsuite"
;; :syntax (defsuite name tests ...)
;; :doc "Declare a test suite. Name is a string. Tests are deftest forms
;; or nested defsuite forms."
;; :example '(defsuite "arithmetic"
;; (deftest "add" (assert (= (+ 1 2) 3)))
;; (deftest "sub" (assert (= (- 5 3) 2)))))
(defmacro deftest (name &rest body)
`(let ((result (try-call (fn () ,@body))))
(if (get result "ok")
(report-pass ,name)
(report-fail ,name (get result "error")))))
(defmacro defsuite (name &rest items)
`(do (push-suite ,name)
,@items
(pop-suite)))
;; --------------------------------------------------------------------------
@@ -113,9 +98,9 @@
(define assert-throws
(fn (thunk)
;; Platform must implement try-catch or equivalent.
;; Bootstrap compilers emit this as a native try/catch block.
(platform-assert-throws thunk)))
(let ((result (try-call thunk)))
(assert (not (get result "ok"))
"Expected an error to be thrown but none was"))))
;; ==========================================================================

108
shared/sx/tests/run.js Normal file
View File

@@ -0,0 +1,108 @@
// Run test.sx directly against sx-browser.js.
//
// sx-browser.js parses and evaluates test.sx — SX tests itself.
// This script provides only platform functions (error catching, reporting).
//
// Usage: node shared/sx/tests/run.js
Object.defineProperty(globalThis, "document", { value: undefined, writable: true });
var path = require("path");
var fs = require("fs");
var Sx = require(path.resolve(__dirname, "../../static/scripts/sx-browser.js"));
// --- Test state ---
var suiteStack = [];
var passed = 0, failed = 0, testNum = 0;
// --- Helpers ---
function isNil(x) { return x === Sx.NIL || x === null || x === undefined; }
function deepEqual(a, b) {
if (a === b) return true;
if (isNil(a) && isNil(b)) return true;
if (typeof a !== typeof b) return false;
if (Array.isArray(a) && Array.isArray(b)) {
if (a.length !== b.length) return false;
for (var i = 0; i < a.length; i++) if (!deepEqual(a[i], b[i])) return false;
return true;
}
if (a && typeof a === "object" && b && typeof b === "object") {
var ka = Object.keys(a), kb = Object.keys(b);
if (ka.length !== kb.length) return false;
for (var j = 0; j < ka.length; j++) if (!deepEqual(a[ka[j]], b[ka[j]])) return false;
return true;
}
return false;
}
// --- Platform functions injected into the SX env ---
var env = {
// Error catching — calls an SX thunk, returns result dict
"try-call": function(thunk) {
try {
Sx.eval([thunk], env);
return { ok: true };
} catch(e) {
return { ok: false, error: e.message || String(e) };
}
},
// Test reporting
"report-pass": function(name) {
testNum++;
passed++;
var fullName = suiteStack.concat([name]).join(" > ");
console.log("ok " + testNum + " - " + fullName);
},
"report-fail": function(name, error) {
testNum++;
failed++;
var fullName = suiteStack.concat([name]).join(" > ");
console.log("not ok " + testNum + " - " + fullName);
console.log(" # " + error);
},
// Suite context
"push-suite": function(name) { suiteStack.push(name); },
"pop-suite": function() { suiteStack.pop(); },
// Primitives that sx-browser.js has internally but doesn't expose through env
"equal?": function(a, b) { return deepEqual(a, b); },
"eq?": function(a, b) { return a === b; },
"boolean?": function(x) { return typeof x === "boolean"; },
"string-length": function(s) { return String(s).length; },
"substring": function(s, start, end) { return String(s).slice(start, end); },
"string-contains?": function(s, needle) { return String(s).indexOf(needle) !== -1; },
"upcase": function(s) { return String(s).toUpperCase(); },
"downcase": function(s) { return String(s).toLowerCase(); },
"reverse": function(c) { return c ? c.slice().reverse() : []; },
"flatten": function(c) {
var r = [];
for (var i = 0; i < (c||[]).length; i++) {
if (Array.isArray(c[i])) for (var j = 0; j < c[i].length; j++) r.push(c[i][j]);
else r.push(c[i]);
}
return r;
},
"has-key?": function(d, k) { return d && typeof d === "object" && k in d; },
"append": function(c, x) { return Array.isArray(x) ? (c||[]).concat(x) : (c||[]).concat([x]); },
};
// --- Read and evaluate test.sx ---
var src = fs.readFileSync(path.resolve(__dirname, "../ref/test.sx"), "utf8");
var exprs = Sx.parseAll(src);
console.log("TAP version 13");
for (var i = 0; i < exprs.length; i++) {
Sx.eval(exprs[i], env);
}
// --- Summary ---
console.log("");
console.log("1.." + testNum);
console.log("# tests " + (passed + failed));
console.log("# pass " + passed);
if (failed > 0) {
console.log("# fail " + failed);
process.exit(1);
}

92
shared/sx/tests/run.py Normal file
View File

@@ -0,0 +1,92 @@
#!/usr/bin/env python3
"""Run test.sx directly against the Python SX evaluator.
The Python evaluator parses and evaluates test.sx — SX tests itself.
This script provides only platform functions (error catching, reporting).
Usage: python shared/sx/tests/run.py
"""
from __future__ import annotations
import os
import sys
import traceback
_HERE = os.path.dirname(os.path.abspath(__file__))
_PROJECT = os.path.abspath(os.path.join(_HERE, "..", "..", ".."))
sys.path.insert(0, _PROJECT)
from shared.sx.parser import parse_all
from shared.sx.evaluator import _eval, _trampoline
# --- Test state ---
suite_stack: list[str] = []
passed = 0
failed = 0
test_num = 0
def try_call(thunk):
"""Call an SX thunk, catching errors."""
try:
_trampoline(_eval([thunk], {}))
return {"ok": True}
except Exception as e:
return {"ok": False, "error": str(e)}
def report_pass(name):
global passed, test_num
test_num += 1
passed += 1
full_name = " > ".join(suite_stack + [name])
print(f"ok {test_num} - {full_name}")
def report_fail(name, error):
global failed, test_num
test_num += 1
failed += 1
full_name = " > ".join(suite_stack + [name])
print(f"not ok {test_num} - {full_name}")
print(f" # {error}")
def push_suite(name):
suite_stack.append(name)
def pop_suite():
suite_stack.pop()
def main():
env = {
"try-call": try_call,
"report-pass": report_pass,
"report-fail": report_fail,
"push-suite": push_suite,
"pop-suite": pop_suite,
}
test_sx = os.path.join(_HERE, "..", "ref", "test.sx")
with open(test_sx) as f:
src = f.read()
exprs = parse_all(src)
print("TAP version 13")
for expr in exprs:
_trampoline(_eval(expr, env))
print()
print(f"1..{test_num}")
print(f"# tests {passed + failed}")
print(f"# pass {passed}")
if failed > 0:
print(f"# fail {failed}")
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -1,199 +0,0 @@
// Auto-generated from test.sx — SX spec self-tests for JavaScript.
//
// DO NOT EDIT. Regenerate with:
// python shared/sx/ref/bootstrap_test_js.py --output shared/sx/tests/test_sx_spec.js
//
// Run:
// node shared/sx/tests/test_sx_spec.js
// --- Load sx-browser.js (bootstrapped from spec) ---
Object.defineProperty(globalThis, "document", { value: undefined, writable: true });
var _sxPath = require("path").resolve(__dirname, "../../static/scripts/sx-browser.js");
var Sx = require(_sxPath);
// --- Inject spec primitives into env ---
// sx-browser.js has assert but is missing some spec primitives.
// We inject them into the test env since PRIMITIVES is not exposed.
var NIL = Sx.NIL;
function _isNil(x) { return x === NIL || x === null || x === undefined; }
function _deepEqual(a, b) {
if (a === b) return true;
if (_isNil(a) && _isNil(b)) return true;
if (typeof a !== typeof b) return false;
if (Array.isArray(a) && Array.isArray(b)) {
if (a.length !== b.length) return false;
for (var i = 0; i < a.length; i++) if (!_deepEqual(a[i], b[i])) return false;
return true;
}
if (a && typeof a === "object" && b && typeof b === "object") {
var ka = Object.keys(a), kb = Object.keys(b);
if (ka.length !== kb.length) return false;
for (var j = 0; j < ka.length; j++) if (!_deepEqual(a[ka[j]], b[ka[j]])) return false;
return true;
}
return false;
}
// Primitives injected into every test env
var _envPrimitives = {
"equal?": function(a, b) { return _deepEqual(a, b); },
"eq?": function(a, b) { return a === b; },
"boolean?": function(x) { return typeof x === "boolean"; },
"string-length": function(s) { return String(s).length; },
"substring": function(s, start, end) { return String(s).slice(start, end); },
"string-contains?": function(s, needle) { return String(s).indexOf(needle) !== -1; },
"upcase": function(s) { return String(s).toUpperCase(); },
"downcase": function(s) { return String(s).toLowerCase(); },
"reverse": function(c) { return c ? c.slice().reverse() : []; },
"flatten": function(c) {
var r = []; for (var i = 0; i < (c||[]).length; i++) {
if (Array.isArray(c[i])) for (var j = 0; j < c[i].length; j++) r.push(c[i][j]);
else r.push(c[i]);
} return r;
},
"has-key?": function(d, k) { return d && typeof d === "object" && k in d; },
// Fix append to concatenate when x is a list
"append": function(c, x) { return Array.isArray(x) ? (c||[]).concat(x) : (c||[]).concat([x]); },
};
// --- Test infrastructure ---
var _passed = 0, _failed = 0, _errors = [];
var _testNum = 0;
function _makeEnv() {
var env = {};
// Copy injected primitives into env
for (var k in _envPrimitives) env[k] = _envPrimitives[k];
var src = '(define assert-equal (fn (expected actual) (assert (equal? expected actual) (str "Expected " (str expected) " but got " (str actual)))))\n(define assert-not-equal (fn (a b) (assert (not (equal? a b)) (str "Expected values to differ but both are " (str a)))))\n(define assert-true (fn (val) (assert val (str "Expected truthy but got " (str val)))))\n(define assert-false (fn (val) (assert (not val) (str "Expected falsy but got " (str val)))))\n(define assert-nil (fn (val) (assert (nil? val) (str "Expected nil but got " (str val)))))\n(define assert-type (fn (expected-type val) (let ((actual-type (if (nil? val) "nil" (if (boolean? val) "boolean" (if (number? val) "number" (if (string? val) "string" (if (list? val) "list" (if (dict? val) "dict" "unknown")))))))) (assert (= expected-type actual-type) (str "Expected type " expected-type " but got " actual-type)))))\n(define assert-length (fn (expected-len col) (assert (= (len col) expected-len) (str "Expected length " expected-len " but got " (len col)))))\n(define assert-contains (fn (item col) (assert (some (fn (x) (equal? x item)) col) (str "Expected collection to contain " (str item)))))\n(define assert-throws (fn (thunk) (platform-assert-throws thunk)))';
var exprs = Sx.parseAll(src);
for (var i = 0; i < exprs.length; i++) Sx.eval(exprs[i], env);
return env;
}
function _run(name, sxSource) {
_testNum++;
try {
var env = _makeEnv();
var exprs = Sx.parseAll(sxSource);
for (var i = 0; i < exprs.length; i++) Sx.eval(exprs[i], env);
_passed++;
console.log("ok " + _testNum + " - " + name);
} catch (e) {
_failed++;
_errors.push({ name: name, error: e.message || String(e) });
console.log("not ok " + _testNum + " - " + name);
console.log(" # " + (e.message || String(e)));
}
}
console.log("TAP version 13");
console.log("1..81");
// --- literals ---
_run('literals > numbers are numbers', '(do (assert-type "number" 42) (assert-type "number" 3.14) (assert-type "number" -1))');
_run('literals > strings are strings', '(do (assert-type "string" "hello") (assert-type "string" ""))');
_run('literals > booleans are booleans', '(do (assert-type "boolean" true) (assert-type "boolean" false))');
_run('literals > nil is nil', '(do (assert-type "nil" nil) (assert-nil nil))');
_run('literals > lists are lists', '(do (assert-type "list" (list 1 2 3)) (assert-type "list" (list)))');
_run('literals > dicts are dicts', '(assert-type "dict" {:a 1 :b 2})');
// --- arithmetic ---
_run('arithmetic > addition', '(do (assert-equal 3 (+ 1 2)) (assert-equal 0 (+ 0 0)) (assert-equal -1 (+ 1 -2)) (assert-equal 10 (+ 1 2 3 4)))');
_run('arithmetic > subtraction', '(do (assert-equal 1 (- 3 2)) (assert-equal -1 (- 2 3)))');
_run('arithmetic > multiplication', '(do (assert-equal 6 (* 2 3)) (assert-equal 0 (* 0 100)) (assert-equal 24 (* 1 2 3 4)))');
_run('arithmetic > division', '(do (assert-equal 2 (/ 6 3)) (assert-equal 2.5 (/ 5 2)))');
_run('arithmetic > modulo', '(do (assert-equal 1 (mod 7 3)) (assert-equal 0 (mod 6 3)))');
// --- comparison ---
_run('comparison > equality', '(do (assert-true (= 1 1)) (assert-false (= 1 2)) (assert-true (= "a" "a")) (assert-false (= "a" "b")))');
_run('comparison > deep equality', '(do (assert-true (equal? (list 1 2 3) (list 1 2 3))) (assert-false (equal? (list 1 2) (list 1 3))) (assert-true (equal? {:a 1} {:a 1})) (assert-false (equal? {:a 1} {:a 2})))');
_run('comparison > ordering', '(do (assert-true (< 1 2)) (assert-false (< 2 1)) (assert-true (> 2 1)) (assert-true (<= 1 1)) (assert-true (<= 1 2)) (assert-true (>= 2 2)) (assert-true (>= 3 2)))');
// --- strings ---
_run('strings > str concatenation', '(do (assert-equal "abc" (str "a" "b" "c")) (assert-equal "hello world" (str "hello" " " "world")) (assert-equal "42" (str 42)) (assert-equal "" (str)))');
_run('strings > string-length', '(do (assert-equal 5 (string-length "hello")) (assert-equal 0 (string-length "")))');
_run('strings > substring', '(do (assert-equal "ell" (substring "hello" 1 4)) (assert-equal "hello" (substring "hello" 0 5)))');
_run('strings > string-contains?', '(do (assert-true (string-contains? "hello world" "world")) (assert-false (string-contains? "hello" "xyz")))');
_run('strings > upcase and downcase', '(do (assert-equal "HELLO" (upcase "hello")) (assert-equal "hello" (downcase "HELLO")))');
_run('strings > trim', '(do (assert-equal "hello" (trim " hello ")) (assert-equal "hello" (trim "hello")))');
_run('strings > split and join', '(do (assert-equal (list "a" "b" "c") (split "a,b,c" ",")) (assert-equal "a-b-c" (join "-" (list "a" "b" "c"))))');
// --- lists ---
_run('lists > constructors', '(do (assert-equal (list 1 2 3) (list 1 2 3)) (assert-equal (list) (list)) (assert-length 3 (list 1 2 3)))');
_run('lists > first and rest', '(do (assert-equal 1 (first (list 1 2 3))) (assert-equal (list 2 3) (rest (list 1 2 3))) (assert-nil (first (list))) (assert-equal (list) (rest (list))))');
_run('lists > nth', '(do (assert-equal 1 (nth (list 1 2 3) 0)) (assert-equal 2 (nth (list 1 2 3) 1)) (assert-equal 3 (nth (list 1 2 3) 2)))');
_run('lists > last', '(do (assert-equal 3 (last (list 1 2 3))) (assert-nil (last (list))))');
_run('lists > cons and append', '(do (assert-equal (list 0 1 2) (cons 0 (list 1 2))) (assert-equal (list 1 2 3 4) (append (list 1 2) (list 3 4))))');
_run('lists > reverse', '(do (assert-equal (list 3 2 1) (reverse (list 1 2 3))) (assert-equal (list) (reverse (list))))');
_run('lists > empty?', '(do (assert-true (empty? (list))) (assert-false (empty? (list 1))))');
_run('lists > len', '(do (assert-equal 0 (len (list))) (assert-equal 3 (len (list 1 2 3))))');
_run('lists > contains?', '(do (assert-true (contains? (list 1 2 3) 2)) (assert-false (contains? (list 1 2 3) 4)))');
_run('lists > flatten', '(assert-equal (list 1 2 3 4) (flatten (list (list 1 2) (list 3 4))))');
// --- dicts ---
_run('dicts > dict literal', '(do (assert-type "dict" {:a 1 :b 2}) (assert-equal 1 (get {:a 1} "a")) (assert-equal 2 (get {:a 1 :b 2} "b")))');
_run('dicts > assoc', '(do (assert-equal {:a 1 :b 2} (assoc {:a 1} "b" 2)) (assert-equal {:a 99} (assoc {:a 1} "a" 99)))');
_run('dicts > dissoc', '(assert-equal {:b 2} (dissoc {:a 1 :b 2} "a"))');
_run('dicts > keys and vals', '(let ((d {:a 1 :b 2})) (assert-length 2 (keys d)) (assert-length 2 (vals d)) (assert-contains "a" (keys d)) (assert-contains "b" (keys d)))');
_run('dicts > has-key?', '(do (assert-true (has-key? {:a 1} "a")) (assert-false (has-key? {:a 1} "b")))');
_run('dicts > merge', '(do (assert-equal {:a 1 :b 2 :c 3} (merge {:a 1 :b 2} {:c 3})) (assert-equal {:a 99 :b 2} (merge {:a 1 :b 2} {:a 99})))');
// --- predicates ---
_run('predicates > nil?', '(do (assert-true (nil? nil)) (assert-false (nil? 0)) (assert-false (nil? false)) (assert-false (nil? "")))');
_run('predicates > number?', '(do (assert-true (number? 42)) (assert-true (number? 3.14)) (assert-false (number? "42")))');
_run('predicates > string?', '(do (assert-true (string? "hello")) (assert-false (string? 42)))');
_run('predicates > list?', '(do (assert-true (list? (list 1 2))) (assert-false (list? "not a list")))');
_run('predicates > dict?', '(do (assert-true (dict? {:a 1})) (assert-false (dict? (list 1))))');
_run('predicates > boolean?', '(do (assert-true (boolean? true)) (assert-true (boolean? false)) (assert-false (boolean? nil)) (assert-false (boolean? 0)))');
_run('predicates > not', '(do (assert-true (not false)) (assert-true (not nil)) (assert-false (not true)) (assert-false (not 1)) (assert-false (not "x")))');
// --- special-forms ---
_run('special-forms > if', '(do (assert-equal "yes" (if true "yes" "no")) (assert-equal "no" (if false "yes" "no")) (assert-equal "no" (if nil "yes" "no")) (assert-nil (if false "yes")))');
_run('special-forms > when', '(do (assert-equal "yes" (when true "yes")) (assert-nil (when false "yes")))');
_run('special-forms > cond', '(do (assert-equal "a" (cond true "a" :else "b")) (assert-equal "b" (cond false "a" :else "b")) (assert-equal "c" (cond false "a" false "b" :else "c")))');
_run('special-forms > and', '(do (assert-true (and true true)) (assert-false (and true false)) (assert-false (and false true)) (assert-equal 3 (and 1 2 3)))');
_run('special-forms > or', '(do (assert-equal 1 (or 1 2)) (assert-equal 2 (or false 2)) (assert-equal "fallback" (or nil false "fallback")) (assert-false (or false false)))');
_run('special-forms > let', '(do (assert-equal 3 (let ((x 1) (y 2)) (+ x y))) (assert-equal "hello world" (let ((a "hello") (b " world")) (str a b))))');
_run('special-forms > let clojure-style', '(assert-equal 3 (let (x 1 y 2) (+ x y)))');
_run('special-forms > do / begin', '(do (assert-equal 3 (do 1 2 3)) (assert-equal "last" (begin "first" "middle" "last")))');
_run('special-forms > define', '(do (define x 42) (assert-equal 42 x))');
_run('special-forms > set!', '(do (define x 1) (set! x 2) (assert-equal 2 x))');
// --- lambdas ---
_run('lambdas > basic lambda', '(let ((add (fn (a b) (+ a b)))) (assert-equal 3 (add 1 2)))');
_run('lambdas > closure captures env', '(let ((x 10)) (let ((add-x (fn (y) (+ x y)))) (assert-equal 15 (add-x 5))))');
_run('lambdas > lambda as argument', '(assert-equal (list 2 4 6) (map (fn (x) (* x 2)) (list 1 2 3)))');
_run('lambdas > recursive lambda via define', '(do (define factorial (fn (n) (if (<= n 1) 1 (* n (factorial (- n 1)))))) (assert-equal 120 (factorial 5)))');
_run('lambdas > higher-order returns lambda', '(let ((make-adder (fn (n) (fn (x) (+ n x))))) (let ((add5 (make-adder 5))) (assert-equal 8 (add5 3))))');
// --- higher-order ---
_run('higher-order > map', '(do (assert-equal (list 2 4 6) (map (fn (x) (* x 2)) (list 1 2 3))) (assert-equal (list) (map (fn (x) x) (list))))');
_run('higher-order > filter', '(do (assert-equal (list 2 4) (filter (fn (x) (= (mod x 2) 0)) (list 1 2 3 4))) (assert-equal (list) (filter (fn (x) false) (list 1 2 3))))');
_run('higher-order > reduce', '(do (assert-equal 10 (reduce (fn (acc x) (+ acc x)) 0 (list 1 2 3 4))) (assert-equal 0 (reduce (fn (acc x) (+ acc x)) 0 (list))))');
_run('higher-order > some', '(do (assert-true (some (fn (x) (> x 3)) (list 1 2 3 4 5))) (assert-false (some (fn (x) (> x 10)) (list 1 2 3))))');
_run('higher-order > every?', '(do (assert-true (every? (fn (x) (> x 0)) (list 1 2 3))) (assert-false (every? (fn (x) (> x 2)) (list 1 2 3))))');
_run('higher-order > map-indexed', '(assert-equal (list "0:a" "1:b" "2:c") (map-indexed (fn (i x) (str i ":" x)) (list "a" "b" "c")))');
// --- components ---
_run('components > defcomp creates component', '(do (defcomp ~test-comp (&key title) (div title)) (assert-true (not (nil? ~test-comp))))');
_run('components > component renders with keyword args', '(do (defcomp ~greeting (&key name) (span (str "Hello, " name "!"))) (assert-true (not (nil? ~greeting))))');
_run('components > component with children', '(do (defcomp ~box (&key &rest children) (div :class "box" children)) (assert-true (not (nil? ~box))))');
_run('components > component with default via or', '(do (defcomp ~label (&key text) (span (or text "default"))) (assert-true (not (nil? ~label))))');
// --- macros ---
_run('macros > defmacro creates macro', '(do (defmacro unless (cond &rest body) (quasiquote (if (not (unquote cond)) (do (splice-unquote body))))) (assert-equal "yes" (unless false "yes")) (assert-nil (unless true "no")))');
_run('macros > quasiquote and unquote', '(let ((x 42)) (assert-equal (list 1 42 3) (quasiquote (1 (unquote x) 3))))');
_run('macros > splice-unquote', '(let ((xs (list 2 3 4))) (assert-equal (list 1 2 3 4 5) (quasiquote (1 (splice-unquote xs) 5))))');
// --- threading ---
_run('threading > thread-first', '(do (assert-equal 8 (-> 5 (+ 1) (+ 2))) (assert-equal "HELLO" (-> "hello" upcase)) (assert-equal "HELLO WORLD" (-> "hello" (str " world") upcase)))');
// --- truthiness ---
_run('truthiness > truthy values', '(do (assert-true (if 1 true false)) (assert-true (if "x" true false)) (assert-true (if (list 1) true false)) (assert-true (if true true false)))');
_run('truthiness > falsy values', '(do (assert-false (if false true false)) (assert-false (if nil true false)))');
// --- edge-cases ---
_run('edge-cases > nested let scoping', '(let ((x 1)) (let ((x 2)) (assert-equal 2 x)))');
_run('edge-cases > recursive map', '(assert-equal (list (list 2 4) (list 6 8)) (map (fn (sub) (map (fn (x) (* x 2)) sub)) (list (list 1 2) (list 3 4))))');
_run('edge-cases > keyword as value', '(do (assert-equal "class" :class) (assert-equal "id" :id))');
_run('edge-cases > dict with evaluated values', '(let ((x 42)) (assert-equal 42 (get {:val x} "val")))');
_run('edge-cases > nil propagation', '(do (assert-nil (get {:a 1} "missing")) (assert-equal "default" (or (get {:a 1} "missing") "default")))');
_run('edge-cases > empty operations', '(do (assert-equal (list) (map (fn (x) x) (list))) (assert-equal (list) (filter (fn (x) true) (list))) (assert-equal 0 (reduce (fn (acc x) (+ acc x)) 0 (list))) (assert-equal 0 (len (list))) (assert-equal "" (str)))');
// --- Summary ---
console.log("");
console.log("# tests " + (_passed + _failed));
console.log("# pass " + _passed);
if (_failed > 0) {
console.log("# fail " + _failed);
for (var ei = 0; ei < _errors.length; ei++) {
console.log("# FAIL: " + _errors[ei].name + " — " + _errors[ei].error);
}
process.exit(1);
}

View File

@@ -18,7 +18,7 @@ _PREAMBLE = '''(define assert-equal (fn (expected actual) (assert (equal? expect
(define assert-type (fn (expected-type val) (let ((actual-type (if (nil? val) "nil" (if (boolean? val) "boolean" (if (number? val) "number" (if (string? val) "string" (if (list? val) "list" (if (dict? val) "dict" "unknown")))))))) (assert (= expected-type actual-type) (str "Expected type " expected-type " but got " actual-type)))))
(define assert-length (fn (expected-len col) (assert (= (len col) expected-len) (str "Expected length " expected-len " but got " (len col)))))
(define assert-contains (fn (item col) (assert (some (fn (x) (equal? x item)) col) (str "Expected collection to contain " (str item)))))
(define assert-throws (fn (thunk) (platform-assert-throws thunk)))'''
(define assert-throws (fn (thunk) (let ((result (try-call thunk))) (assert (not (get result "ok")) "Expected an error to be thrown but none was"))))'''
def _make_env() -> dict:

View File

@@ -7,49 +7,69 @@
;; Intro
(div :class "space-y-4"
(p :class "text-lg text-stone-600"
"SX tests itself. The test spec is written in SX and defines a complete test framework — assertion helpers, test suites, and 81 test cases covering every language feature. Bootstrap compilers read "
"SX tests itself. "
(code :class "text-violet-700 text-sm" "test.sx")
" and emit native test runners for each host platform.")
" is a self-executing test spec — it defines "
(code :class "text-violet-700 text-sm" "deftest")
" and "
(code :class "text-violet-700 text-sm" "defsuite")
" as macros, writes 81 test cases, and runs them. Any host that provides five platform functions can evaluate the file directly.")
(p :class "text-stone-600"
"This is not a test "
(em "of") " SX — it is a test " (em "in") " SX. The same s-expressions that define how "
(code :class "text-violet-700 text-sm" "if")
" works are used to verify that "
(code :class "text-violet-700 text-sm" "if")
" works. The language proves its own correctness, on every platform it compiles to."))
" works. No code generation, no intermediate files — the evaluator runs the spec."))
;; How it works
(div :class "space-y-3"
(h2 :class "text-2xl font-semibold text-stone-800" "Architecture")
(p :class "text-stone-600"
"The test framework needs five platform functions. Everything else — macros, assertion helpers, test suites — is pure SX:")
(div :class "not-prose bg-stone-100 rounded-lg p-5 mx-auto max-w-3xl"
(pre :class "text-sm leading-relaxed whitespace-pre-wrap break-words font-mono text-stone-700"
"test.sx The spec: assertion helpers + 15 suites + 81 tests
"test.sx Self-executing: macros + helpers + 81 tests
|
|--- bootstrap_test.py Reads test.sx, emits pytest module
|--- run.js Injects 5 platform fns, evaluates test.sx
| |
| +-> test_sx_spec.py 81 pytest test cases
| |
| +-> shared/sx/evaluator.py (Python SX evaluator)
| +-> sx-browser.js JS evaluator (bootstrapped from spec)
|
|--- bootstrap_test_js.py Reads test.sx, emits Node.js TAP script
|--- run.py Injects 5 platform fns, evaluates test.sx
|
+-> test_sx_spec.js 81 TAP test cases
|
+-> sx-browser.js (JS SX evaluator)")))
+-> evaluator.py Python evaluator
Platform functions:
try-call (thunk) -> {:ok true} | {:ok false :error \"msg\"}
report-pass (name) -> output pass
report-fail (name error) -> output fail
push-suite (name) -> push suite context
pop-suite () -> pop suite context")))
;; Framework
(div :class "space-y-3"
(h2 :class "text-2xl font-semibold text-stone-800" "The test framework")
(p :class "text-stone-600"
"The framework defines two declarative forms and nine assertion helpers, all in pure SX:")
"The framework defines two macros and nine assertion helpers, all in SX. The macros are the key — they make "
(code :class "text-violet-700 text-sm" "defsuite")
" and "
(code :class "text-violet-700 text-sm" "deftest")
" executable forms, not just declarations:")
(div :class "rounded-lg border border-stone-200 bg-white p-5 space-y-3"
(h3 :class "text-sm font-medium text-stone-500 uppercase tracking-wide" "Forms")
(h3 :class "text-sm font-medium text-stone-500 uppercase tracking-wide" "Macros")
(~doc-code :code
(highlight "(defsuite \"name\" ...tests)\n(deftest \"name\" ...body)" "lisp")))
(highlight "(defmacro deftest (name &rest body)\n `(let ((result (try-call (fn () ,@body))))\n (if (get result \"ok\")\n (report-pass ,name)\n (report-fail ,name (get result \"error\")))))\n\n(defmacro defsuite (name &rest items)\n `(do (push-suite ,name)\n ,@items\n (pop-suite)))" "lisp")))
(p :class "text-stone-600 text-sm"
(code :class "text-violet-700 text-sm" "deftest")
" wraps the body in a thunk, passes it to "
(code :class "text-violet-700 text-sm" "try-call")
" (the one platform function that catches errors), then reports pass or fail. "
(code :class "text-violet-700 text-sm" "defsuite")
" pushes a name onto the context stack, runs its children, and pops.")
(div :class "rounded-lg border border-stone-200 bg-white p-5 space-y-3"
(h3 :class "text-sm font-medium text-stone-500 uppercase tracking-wide" "Assertion helpers")
(~doc-code :code
(highlight "(define assert-equal\n (fn (expected actual)\n (assert (equal? expected actual)\n (str \"Expected \" (str expected) \" but got \" (str actual)))))\n\n(define assert-true (fn (val) (assert val ...)))\n(define assert-false (fn (val) (assert (not val) ...)))\n(define assert-nil (fn (val) (assert (nil? val) ...)))\n(define assert-type (fn (expected-type val) ...))\n(define assert-length (fn (expected-len col) ...))\n(define assert-contains (fn (item col) ...))" "lisp"))))
(highlight "(define assert-equal\n (fn (expected actual)\n (assert (equal? expected actual)\n (str \"Expected \" (str expected) \" but got \" (str actual)))))\n\n(define assert-true (fn (val) (assert val ...)))\n(define assert-false (fn (val) (assert (not val) ...)))\n(define assert-nil (fn (val) (assert (nil? val) ...)))\n(define assert-type (fn (expected-type val) ...))\n(define assert-length (fn (expected-len col) ...))\n(define assert-contains (fn (item col) ...))\n(define assert-throws (fn (thunk) ...))" "lisp"))))
;; Example tests
(div :class "space-y-3"
@@ -61,54 +81,49 @@
(~doc-code :code
(highlight "(defsuite \"arithmetic\"\n (deftest \"addition\"\n (assert-equal 3 (+ 1 2))\n (assert-equal 0 (+ 0 0))\n (assert-equal -1 (+ 1 -2))\n (assert-equal 10 (+ 1 2 3 4)))\n\n (deftest \"subtraction\"\n (assert-equal 1 (- 3 2))\n (assert-equal -1 (- 2 3)))\n\n (deftest \"multiplication\"\n (assert-equal 6 (* 2 3))\n (assert-equal 0 (* 0 100))\n (assert-equal 24 (* 1 2 3 4)))\n\n (deftest \"division\"\n (assert-equal 2 (/ 6 3))\n (assert-equal 2.5 (/ 5 2)))\n\n (deftest \"modulo\"\n (assert-equal 1 (mod 7 3))\n (assert-equal 0 (mod 6 3))))" "lisp"))))
;; Bootstrapping to Python
;; Running tests — JS
(div :class "space-y-3"
(h2 :class "text-2xl font-semibold text-stone-800" "Bootstrap to Python")
(h2 :class "text-2xl font-semibold text-stone-800" "JavaScript: direct evaluation")
(p :class "text-stone-600"
(code :class "text-violet-700 text-sm" "bootstrap_test.py")
" parses "
(code :class "text-violet-700 text-sm" "test.sx")
", extracts the "
(code :class "text-violet-700 text-sm" "define")
" forms (assertion helpers) as a preamble, then emits each "
(code :class "text-violet-700 text-sm" "defsuite")
" as a pytest class and each "
(code :class "text-violet-700 text-sm" "deftest")
" as a test method.")
(div :class "rounded-lg border border-stone-200 bg-white p-5 space-y-3"
(h3 :class "text-sm font-medium text-stone-500 uppercase tracking-wide" "Generated Python")
(~doc-code :code
(highlight "# Auto-generated from test.sx\nfrom shared.sx.parser import parse_all\nfrom shared.sx.evaluator import _eval, _trampoline\n\ndef _make_env():\n env = {}\n for expr in parse_all(_PREAMBLE):\n _trampoline(_eval(expr, env))\n return env\n\ndef _run(sx_source, env=None):\n if env is None:\n env = _make_env()\n exprs = parse_all(sx_source)\n for expr in exprs:\n result = _trampoline(_eval(expr, env))\n return result\n\nclass TestSpecArithmetic:\n def test_addition(self):\n _run('(do (assert-equal 3 (+ 1 2)) ...)')\n\n def test_subtraction(self):\n _run('(do (assert-equal 1 (- 3 2)) ...)')" "python")))
(div :class "rounded-lg border border-stone-200 bg-white p-5 space-y-3"
(h3 :class "text-sm font-medium text-stone-500 uppercase tracking-wide" "Run it")
(~doc-code :code
(highlight "$ python bootstrap_test.py --output test_sx_spec.py\nParsed 15 suites, 9 preamble defines from test.sx\nTotal test cases: 81\n\n$ pytest test_sx_spec.py -v\n81 passed in 0.15s" "bash"))))
;; Bootstrapping to JavaScript
(div :class "space-y-3"
(h2 :class "text-2xl font-semibold text-stone-800" "Bootstrap to JavaScript")
(p :class "text-stone-600"
(code :class "text-violet-700 text-sm" "bootstrap_test_js.py")
" emits a standalone Node.js script that loads "
(code :class "text-violet-700 text-sm" "sx-browser.js")
" (bootstrapped from the spec), injects any platform-specific primitives into the test env, then runs all 81 tests with TAP output.")
" evaluates "
(code :class "text-violet-700 text-sm" "test.sx")
" directly. The runner injects platform functions and calls "
(code :class "text-violet-700 text-sm" "Sx.eval")
" on each parsed expression:")
(div :class "rounded-lg border border-stone-200 bg-white p-5 space-y-3"
(h3 :class "text-sm font-medium text-stone-500 uppercase tracking-wide" "Generated JavaScript")
(h3 :class "text-sm font-medium text-stone-500 uppercase tracking-wide" "run.js")
(~doc-code :code
(highlight "// Auto-generated from test.sx\nvar Sx = require('./sx-browser.js');\n\nvar _envPrimitives = {\n 'equal?': function(a, b) { return deepEqual(a, b); },\n 'boolean?': function(x) { return typeof x === 'boolean'; },\n // ... platform primitives injected into env\n};\n\nfunction _makeEnv() {\n var env = {};\n for (var k in _envPrimitives) env[k] = _envPrimitives[k];\n var exprs = Sx.parseAll(PREAMBLE);\n for (var i = 0; i < exprs.length; i++) Sx.eval(exprs[i], env);\n return env;\n}\n\nfunction _run(name, sxSource) {\n var env = _makeEnv();\n var exprs = Sx.parseAll(sxSource);\n for (var i = 0; i < exprs.length; i++) Sx.eval(exprs[i], env);\n}" "javascript")))
(highlight "var Sx = require('./sx-browser.js');\nvar src = fs.readFileSync('test.sx', 'utf8');\n\nvar env = {\n 'try-call': function(thunk) {\n try {\n Sx.eval([thunk], env); // call the SX lambda\n return { ok: true };\n } catch(e) {\n return { ok: false, error: e.message };\n }\n },\n 'report-pass': function(name) { console.log('ok - ' + name); },\n 'report-fail': function(name, err) { console.log('not ok - ' + name); },\n 'push-suite': function(n) { stack.push(n); },\n 'pop-suite': function() { stack.pop(); },\n};\n\nvar exprs = Sx.parseAll(src);\nfor (var i = 0; i < exprs.length; i++) Sx.eval(exprs[i], env);" "javascript")))
(div :class "rounded-lg border border-stone-200 bg-white p-5 space-y-3"
(h3 :class "text-sm font-medium text-stone-500 uppercase tracking-wide" "Run it")
(h3 :class "text-sm font-medium text-stone-500 uppercase tracking-wide" "Output")
(~doc-code :code
(highlight "$ python bootstrap_test_js.py --output test_sx_spec.js\nParsed 15 suites, 9 preamble defines from test.sx\nTotal test cases: 81\n\n$ node test_sx_spec.js\nTAP version 13\n1..81\nok 1 - literals > numbers are numbers\nok 2 - literals > strings are strings\n...\nok 81 - edge-cases > empty operations\n\n# tests 81\n# pass 81" "bash"))))
(highlight "$ node shared/sx/tests/run.js\nTAP version 13\nok 1 - literals > numbers are numbers\nok 2 - literals > strings are strings\n...\nok 81 - edge-cases > empty operations\n\n# tests 81\n# pass 81" "bash"))))
;; Running tests — Python
(div :class "space-y-3"
(h2 :class "text-2xl font-semibold text-stone-800" "Python: direct evaluation")
(p :class "text-stone-600"
"Same approach — the Python evaluator runs "
(code :class "text-violet-700 text-sm" "test.sx")
" directly:")
(div :class "rounded-lg border border-stone-200 bg-white p-5 space-y-3"
(h3 :class "text-sm font-medium text-stone-500 uppercase tracking-wide" "run.py")
(~doc-code :code
(highlight "from shared.sx.parser import parse_all\nfrom shared.sx.evaluator import _eval, _trampoline\n\ndef try_call(thunk):\n try:\n _trampoline(_eval([thunk], {}))\n return {'ok': True}\n except Exception as e:\n return {'ok': False, 'error': str(e)}\n\nenv = {\n 'try-call': try_call,\n 'report-pass': report_pass,\n 'report-fail': report_fail,\n 'push-suite': push_suite,\n 'pop-suite': pop_suite,\n}\n\nfor expr in parse_all(src):\n _trampoline(_eval(expr, env))" "python")))
(div :class "rounded-lg border border-stone-200 bg-white p-5 space-y-3"
(h3 :class "text-sm font-medium text-stone-500 uppercase tracking-wide" "Output")
(~doc-code :code
(highlight "$ python shared/sx/tests/run.py\nTAP version 13\nok 1 - literals > numbers are numbers\n...\nok 81 - edge-cases > empty operations\n\n# tests 81\n# pass 81" "bash"))))
;; What it proves
(div :class "rounded-lg border border-blue-200 bg-blue-50 p-5 space-y-3"
(h2 :class "text-lg font-semibold text-blue-900" "What this proves")
(ol :class "list-decimal list-inside text-blue-800 space-y-2 text-sm"
(li "The test spec is " (strong "written in SX") " — the same language it tests")
(li "The same 81 tests run on " (strong "both Python and JavaScript"))
(li "Both hosts produce " (strong "identical results") " from identical SX source")
(li "Adding a new host requires only a new bootstrapper — the tests are " (strong "already written"))
(li "The test spec is " (strong "written in SX") " and " (strong "executed by SX") " — no code generation")
(li "The same 81 tests run on " (strong "both Python and JavaScript") " from the same file")
(li "Each host provides only " (strong "5 platform functions") " — everything else is pure SX")
(li "Adding a new host means implementing 5 functions, not rewriting tests")
(li "Platform divergences (truthiness of 0, [], \"\") are " (strong "documented, not hidden"))
(li "The spec is " (strong "executable") " — it doesn't just describe behavior, it verifies it")))
@@ -188,7 +203,7 @@
(h2 :class "text-2xl font-semibold text-stone-800" "Full specification source")
(p :class "text-xs text-stone-400 italic"
"The s-expression source below is the canonical test specification. "
"Bootstrap compilers read this file to generate native test runners.")
"Any host that implements the five platform functions can evaluate it directly.")
(div :class "not-prose bg-stone-100 rounded-lg p-5 mx-auto max-w-3xl"
(pre :class "text-sm leading-relaxed whitespace-pre-wrap break-words"
(code (highlight spec-source "sx"))))))))