Move stdlib out of spec — clean spec/library boundary
spec/ now contains only the language definition (5 files): evaluator.sx, parser.sx, primitives.sx, render.sx, special-forms.sx lib/ contains code written IN the language (8 files): stdlib.sx, types.sx, freeze.sx, content.sx, bytecode.sx, compiler.sx, vm.sx, callcc.sx Test files follow source: spec/tests/ for core language tests, lib/tests/ for library tests (continuations, freeze, types, vm). Updated all consumers: - JS/Python/OCaml bootstrappers: added lib/ to source search paths - OCaml bridge: spec_dir for parser/render, lib_dir for compiler/freeze - JS test runner: scans spec/tests/ (always) + lib/tests/ (--full) - OCaml test runner: scans spec/tests/, lib tests via explicit request - Docker dev mounts: added ./lib:/app/lib:ro Tests: 1041 JS standard, 1322 JS full, 1101 OCaml — all pass Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -31,8 +31,9 @@ services:
|
||||
- ./sx/sx:/app/sx
|
||||
- ./sx/path_setup.py:/app/path_setup.py
|
||||
- ./sx/entrypoint.sh:/usr/local/bin/entrypoint.sh
|
||||
# Spec + web SX files (loaded by OCaml kernel for aser, parser, render)
|
||||
# Spec + lib + web SX files (loaded by OCaml kernel)
|
||||
- ./spec:/app/spec:ro
|
||||
- ./lib:/app/lib:ro
|
||||
- ./web:/app/web:ro
|
||||
# OCaml SX kernel binary (built with: cd hosts/ocaml && eval $(opam env) && dune build)
|
||||
- ./hosts/ocaml/_build/default/bin/sx_server.exe:/app/bin/sx_server:ro
|
||||
|
||||
@@ -48,6 +48,7 @@ services:
|
||||
- ./shared:/app/shared
|
||||
- ./hosts/ocaml/_build/default/bin/sx_server.exe:/app/bin/sx_server:ro
|
||||
- ./spec:/app/spec:ro
|
||||
- ./lib:/app/lib:ro
|
||||
- ./web:/app/web:ro
|
||||
- ./blog/alembic.ini:/app/blog/alembic.ini:ro
|
||||
- ./blog/alembic:/app/blog/alembic:ro
|
||||
@@ -90,6 +91,7 @@ services:
|
||||
- ./shared:/app/shared
|
||||
- ./hosts/ocaml/_build/default/bin/sx_server.exe:/app/bin/sx_server:ro
|
||||
- ./spec:/app/spec:ro
|
||||
- ./lib:/app/lib:ro
|
||||
- ./web:/app/web:ro
|
||||
- ./market/alembic.ini:/app/market/alembic.ini:ro
|
||||
- ./market/alembic:/app/market/alembic:ro
|
||||
@@ -131,6 +133,7 @@ services:
|
||||
- ./shared:/app/shared
|
||||
- ./hosts/ocaml/_build/default/bin/sx_server.exe:/app/bin/sx_server:ro
|
||||
- ./spec:/app/spec:ro
|
||||
- ./lib:/app/lib:ro
|
||||
- ./web:/app/web:ro
|
||||
- ./cart/alembic.ini:/app/cart/alembic.ini:ro
|
||||
- ./cart/alembic:/app/cart/alembic:ro
|
||||
@@ -172,6 +175,7 @@ services:
|
||||
- ./shared:/app/shared
|
||||
- ./hosts/ocaml/_build/default/bin/sx_server.exe:/app/bin/sx_server:ro
|
||||
- ./spec:/app/spec:ro
|
||||
- ./lib:/app/lib:ro
|
||||
- ./web:/app/web:ro
|
||||
- ./events/alembic.ini:/app/events/alembic.ini:ro
|
||||
- ./events/alembic:/app/events/alembic:ro
|
||||
@@ -213,6 +217,7 @@ services:
|
||||
- ./shared:/app/shared
|
||||
- ./hosts/ocaml/_build/default/bin/sx_server.exe:/app/bin/sx_server:ro
|
||||
- ./spec:/app/spec:ro
|
||||
- ./lib:/app/lib:ro
|
||||
- ./web:/app/web:ro
|
||||
- ./federation/alembic.ini:/app/federation/alembic.ini:ro
|
||||
- ./federation/alembic:/app/federation/alembic:ro
|
||||
@@ -254,6 +259,7 @@ services:
|
||||
- ./shared:/app/shared
|
||||
- ./hosts/ocaml/_build/default/bin/sx_server.exe:/app/bin/sx_server:ro
|
||||
- ./spec:/app/spec:ro
|
||||
- ./lib:/app/lib:ro
|
||||
- ./web:/app/web:ro
|
||||
- ./account/alembic.ini:/app/account/alembic.ini:ro
|
||||
- ./account/alembic:/app/account/alembic:ro
|
||||
@@ -295,6 +301,7 @@ services:
|
||||
- ./shared:/app/shared
|
||||
- ./hosts/ocaml/_build/default/bin/sx_server.exe:/app/bin/sx_server:ro
|
||||
- ./spec:/app/spec:ro
|
||||
- ./lib:/app/lib:ro
|
||||
- ./web:/app/web:ro
|
||||
- ./relations/alembic.ini:/app/relations/alembic.ini:ro
|
||||
- ./relations/alembic:/app/relations/alembic:ro
|
||||
@@ -329,6 +336,7 @@ services:
|
||||
- ./shared:/app/shared
|
||||
- ./hosts/ocaml/_build/default/bin/sx_server.exe:/app/bin/sx_server:ro
|
||||
- ./spec:/app/spec:ro
|
||||
- ./lib:/app/lib:ro
|
||||
- ./web:/app/web:ro
|
||||
- ./likes/alembic.ini:/app/likes/alembic.ini:ro
|
||||
- ./likes/alembic:/app/likes/alembic:ro
|
||||
@@ -363,6 +371,7 @@ services:
|
||||
- ./shared:/app/shared
|
||||
- ./hosts/ocaml/_build/default/bin/sx_server.exe:/app/bin/sx_server:ro
|
||||
- ./spec:/app/spec:ro
|
||||
- ./lib:/app/lib:ro
|
||||
- ./web:/app/web:ro
|
||||
- ./orders/alembic.ini:/app/orders/alembic.ini:ro
|
||||
- ./orders/alembic:/app/orders/alembic:ro
|
||||
@@ -400,6 +409,7 @@ services:
|
||||
- ./shared:/app/shared
|
||||
- ./hosts/ocaml/_build/default/bin/sx_server.exe:/app/bin/sx_server:ro
|
||||
- ./spec:/app/spec:ro
|
||||
- ./lib:/app/lib:ro
|
||||
- ./web:/app/web:ro
|
||||
- ./test/app.py:/app/app.py
|
||||
- ./test/sx:/app/sx
|
||||
@@ -431,6 +441,7 @@ services:
|
||||
- ./shared:/app/shared
|
||||
- ./hosts/ocaml/_build/default/bin/sx_server.exe:/app/bin/sx_server:ro
|
||||
- ./spec:/app/spec:ro
|
||||
- ./lib:/app/lib:ro
|
||||
- ./web:/app/web:ro
|
||||
- ./sx/app.py:/app/app.py
|
||||
- ./sx/sxc:/app/sxc
|
||||
@@ -469,6 +480,7 @@ services:
|
||||
- ./shared:/app/shared
|
||||
- ./hosts/ocaml/_build/default/bin/sx_server.exe:/app/bin/sx_server:ro
|
||||
- ./spec:/app/spec:ro
|
||||
- ./lib:/app/lib:ro
|
||||
- ./web:/app/web:ro
|
||||
- ./artdag/core:/app/artdag/core
|
||||
- ./artdag/l1/tests:/app/artdag/l1/tests
|
||||
@@ -497,6 +509,7 @@ services:
|
||||
- ./shared:/app/shared
|
||||
- ./hosts/ocaml/_build/default/bin/sx_server.exe:/app/bin/sx_server:ro
|
||||
- ./spec:/app/spec:ro
|
||||
- ./lib:/app/lib:ro
|
||||
- ./web:/app/web:ro
|
||||
- ./artdag:/app/artdag
|
||||
profiles:
|
||||
|
||||
@@ -70,9 +70,10 @@ def compile_ref_to_js(
|
||||
"""
|
||||
from datetime import datetime, timezone
|
||||
|
||||
# Source directories: core spec and web framework
|
||||
# Source directories: core spec, standard library, web framework
|
||||
_source_dirs = [
|
||||
os.path.join(_PROJECT, "spec"), # Core spec
|
||||
os.path.join(_PROJECT, "spec"), # Core language spec
|
||||
os.path.join(_PROJECT, "lib"), # Standard library (stdlib, compiler, vm, ...)
|
||||
os.path.join(_PROJECT, "web"), # Web framework
|
||||
]
|
||||
bridge = _get_bridge()
|
||||
|
||||
@@ -270,6 +270,7 @@ env["pop-suite"] = function() {
|
||||
// Load test framework
|
||||
const projectDir = path.join(__dirname, "..", "..");
|
||||
const specTests = path.join(projectDir, "spec", "tests");
|
||||
const libTests = path.join(projectDir, "lib", "tests");
|
||||
const webTests = path.join(projectDir, "web", "tests");
|
||||
|
||||
const frameworkSrc = fs.readFileSync(path.join(specTests, "test-framework.sx"), "utf8");
|
||||
@@ -278,17 +279,17 @@ for (const expr of frameworkExprs) {
|
||||
Sx.eval(expr, env);
|
||||
}
|
||||
|
||||
// Load compiler + VM spec when running full tests
|
||||
// Load compiler + VM from lib/ when running full tests
|
||||
if (fullBuild) {
|
||||
const specDir = path.join(projectDir, "spec");
|
||||
for (const specFile of ["bytecode.sx", "compiler.sx", "vm.sx"]) {
|
||||
const specPath = path.join(specDir, specFile);
|
||||
if (fs.existsSync(specPath)) {
|
||||
const src = fs.readFileSync(specPath, "utf8");
|
||||
const libDir = path.join(projectDir, "lib");
|
||||
for (const libFile of ["bytecode.sx", "compiler.sx", "vm.sx"]) {
|
||||
const libPath = path.join(libDir, libFile);
|
||||
if (fs.existsSync(libPath)) {
|
||||
const src = fs.readFileSync(libPath, "utf8");
|
||||
const exprs = Sx.parse(src);
|
||||
for (const expr of exprs) {
|
||||
try { Sx.eval(expr, env); } catch (e) {
|
||||
console.error(`Error loading ${specFile}: ${e.message}`);
|
||||
console.error(`Error loading ${libFile}: ${e.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -300,28 +301,32 @@ const args = process.argv.slice(2).filter(a => !a.startsWith("--"));
|
||||
let testFiles = [];
|
||||
|
||||
if (args.length > 0) {
|
||||
// Specific test files
|
||||
// Specific test files — search spec, lib, and web test dirs
|
||||
for (const arg of args) {
|
||||
const name = arg.endsWith(".sx") ? arg : `${arg}.sx`;
|
||||
const specPath = path.join(specTests, name);
|
||||
const libPath = path.join(libTests, name);
|
||||
const webPath = path.join(webTests, name);
|
||||
if (fs.existsSync(specPath)) testFiles.push(specPath);
|
||||
else if (fs.existsSync(libPath)) testFiles.push(libPath);
|
||||
else if (fs.existsSync(webPath)) testFiles.push(webPath);
|
||||
else console.error(`Test file not found: ${name}`);
|
||||
}
|
||||
} else {
|
||||
// Tests requiring optional modules (only run with --full)
|
||||
const requiresFull = new Set(["test-continuations.sx", "test-continuations-advanced.sx", "test-types.sx", "test-freeze.sx", "test-vm.sx"]);
|
||||
// All spec tests
|
||||
// All spec tests (core language — always run)
|
||||
for (const f of fs.readdirSync(specTests).sort()) {
|
||||
if (f.startsWith("test-") && f.endsWith(".sx") && f !== "test-framework.sx") {
|
||||
if (!fullBuild && requiresFull.has(f)) {
|
||||
console.log(`Skipping ${f} (requires --full)`);
|
||||
continue;
|
||||
}
|
||||
testFiles.push(path.join(specTests, f));
|
||||
}
|
||||
}
|
||||
// Library tests (only with --full — require compiler, vm, signals, etc.)
|
||||
if (fullBuild) {
|
||||
for (const f of fs.readdirSync(libTests).sort()) {
|
||||
if (f.startsWith("test-") && f.endsWith(".sx")) {
|
||||
testFiles.push(path.join(libTests, f));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run tests
|
||||
|
||||
@@ -386,12 +386,13 @@ let () =
|
||||
in
|
||||
let root = find_root (Sys.getcwd ()) in
|
||||
let spec p = Filename.concat (Filename.concat root "spec") p in
|
||||
let lib p = Filename.concat (Filename.concat root "lib") p in
|
||||
let web p = Filename.concat (Filename.concat root "web") p in
|
||||
|
||||
let env = make_integration_env () in
|
||||
|
||||
(* Load spec + adapters *)
|
||||
Printf.printf "Loading spec + adapters...\n%!";
|
||||
(* Load spec + lib + adapters *)
|
||||
Printf.printf "Loading spec + lib + adapters...\n%!";
|
||||
let load path =
|
||||
if Sys.file_exists path then begin
|
||||
let exprs = Sx_parser.parse_file path in
|
||||
@@ -405,6 +406,7 @@ let () =
|
||||
load (web "signals.sx");
|
||||
load (web "adapter-html.sx");
|
||||
load (web "adapter-sx.sx");
|
||||
ignore lib; (* available for future library loading *)
|
||||
|
||||
(* Helper: render SX source string to HTML *)
|
||||
let render_html src =
|
||||
|
||||
@@ -734,10 +734,11 @@ let run_spec_tests env test_files =
|
||||
Printf.printf "\nLoading test framework...\n%!";
|
||||
load_and_eval framework_path;
|
||||
|
||||
(* Load spec modules needed by tests *)
|
||||
(* Load modules needed by tests *)
|
||||
let spec_dir = Filename.concat project_dir "spec" in
|
||||
let lib_dir = Filename.concat project_dir "lib" in
|
||||
let web_dir = Filename.concat project_dir "web" in
|
||||
let load_spec name dir =
|
||||
let load_module name dir =
|
||||
let path = Filename.concat dir name in
|
||||
if Sys.file_exists path then begin
|
||||
Printf.printf "Loading %s...\n%!" name;
|
||||
@@ -746,36 +747,47 @@ let run_spec_tests env test_files =
|
||||
end
|
||||
in
|
||||
(* Render adapter for test-render-html.sx *)
|
||||
load_spec "render.sx" spec_dir;
|
||||
load_spec "adapter-html.sx" web_dir;
|
||||
(* Compiler + VM for test-vm.sx *)
|
||||
load_spec "bytecode.sx" spec_dir;
|
||||
load_spec "compiler.sx" spec_dir;
|
||||
load_spec "vm.sx" spec_dir;
|
||||
load_module "render.sx" spec_dir;
|
||||
load_module "adapter-html.sx" web_dir;
|
||||
(* Library modules for lib/tests/ *)
|
||||
load_module "bytecode.sx" lib_dir;
|
||||
load_module "compiler.sx" lib_dir;
|
||||
load_module "vm.sx" lib_dir;
|
||||
load_module "signals.sx" web_dir;
|
||||
load_module "freeze.sx" lib_dir;
|
||||
load_module "content.sx" lib_dir;
|
||||
load_module "types.sx" lib_dir;
|
||||
|
||||
(* Determine test files *)
|
||||
(* Determine test files — scan spec/tests/ and lib/tests/ *)
|
||||
let lib_tests_dir = Filename.concat project_dir "lib/tests" in
|
||||
let files = if test_files = [] then begin
|
||||
let entries = Sys.readdir spec_tests_dir in
|
||||
Array.sort String.compare entries;
|
||||
let requires_full = ["test-continuations.sx"; "test-types.sx"; "test-freeze.sx";
|
||||
"test-continuations-advanced.sx"; "test-signals-advanced.sx";
|
||||
"test-vm.sx"] in
|
||||
Array.to_list entries
|
||||
(* Spec tests (core language — always run) *)
|
||||
let spec_entries = Sys.readdir spec_tests_dir in
|
||||
Array.sort String.compare spec_entries;
|
||||
let spec_files = Array.to_list spec_entries
|
||||
|> List.filter (fun f ->
|
||||
String.length f > 5 &&
|
||||
String.sub f 0 5 = "test-" &&
|
||||
Filename.check_suffix f ".sx" &&
|
||||
f <> "test-framework.sx" &&
|
||||
not (List.mem f requires_full))
|
||||
f <> "test-framework.sx")
|
||||
|> List.map (fun f -> Filename.concat spec_tests_dir f)
|
||||
in
|
||||
spec_files
|
||||
end else
|
||||
(* Specific test files — search all test dirs *)
|
||||
List.map (fun name ->
|
||||
if Filename.check_suffix name ".sx" then name
|
||||
else name ^ ".sx") test_files
|
||||
let name = if Filename.check_suffix name ".sx" then name else name ^ ".sx" in
|
||||
let spec_path = Filename.concat spec_tests_dir name in
|
||||
let lib_path = Filename.concat lib_tests_dir name in
|
||||
if Sys.file_exists spec_path then spec_path
|
||||
else if Sys.file_exists lib_path then lib_path
|
||||
else Filename.concat spec_tests_dir name (* will fail with "not found" *)
|
||||
) test_files
|
||||
in
|
||||
|
||||
List.iter (fun name ->
|
||||
let path = Filename.concat spec_tests_dir name in
|
||||
List.iter (fun path ->
|
||||
if Sys.file_exists path then begin
|
||||
let name = Filename.basename path in
|
||||
Printf.printf "\n%s\n" (String.make 60 '=');
|
||||
Printf.printf "Running %s\n" name;
|
||||
Printf.printf "%s\n%!" (String.make 60 '=');
|
||||
|
||||
@@ -1174,18 +1174,20 @@ let cli_load_files env files =
|
||||
let cli_mode mode =
|
||||
let env = make_server_env () in
|
||||
(* Load spec + adapter files for aser modes *)
|
||||
let base = try Sys.getenv "SX_SPEC_DIR" with Not_found -> "spec" in
|
||||
let spec_base = try Sys.getenv "SX_SPEC_DIR" with Not_found -> "spec" in
|
||||
let lib_base = try Sys.getenv "SX_LIB_DIR" with Not_found -> "lib" in
|
||||
let web_base = try Sys.getenv "SX_WEB_DIR" with Not_found -> "web" in
|
||||
let spec_files = [
|
||||
Filename.concat base "parser.sx";
|
||||
Filename.concat base "render.sx";
|
||||
let render_files = [
|
||||
Filename.concat spec_base "parser.sx";
|
||||
Filename.concat spec_base "render.sx";
|
||||
Filename.concat web_base "adapter-html.sx";
|
||||
Filename.concat web_base "adapter-sx.sx";
|
||||
Filename.concat web_base "web-forms.sx";
|
||||
] in
|
||||
(* Load spec files for all CLI modes that need rendering *)
|
||||
(* Load spec + adapter files for rendering CLI modes *)
|
||||
(if mode = "aser" || mode = "aser-slot" || mode = "render" then
|
||||
cli_load_files env spec_files);
|
||||
cli_load_files env render_files);
|
||||
ignore lib_base; (* available for --load paths *)
|
||||
(* Load any files passed via --load *)
|
||||
let load_files = ref [] in
|
||||
let args = Array.to_list Sys.argv in
|
||||
@@ -1256,13 +1258,14 @@ let cli_mode mode =
|
||||
|
||||
let test_mode () =
|
||||
let env = make_server_env () in
|
||||
(* Load full spec + adapter stack *)
|
||||
let base = try Sys.getenv "SX_SPEC_DIR" with Not_found -> "spec" in
|
||||
(* Load spec + lib + adapter stack *)
|
||||
let spec_base = try Sys.getenv "SX_SPEC_DIR" with Not_found -> "spec" in
|
||||
let lib_base = try Sys.getenv "SX_LIB_DIR" with Not_found -> "lib" in
|
||||
let web_base = try Sys.getenv "SX_WEB_DIR" with Not_found -> "web" in
|
||||
let files = [
|
||||
Filename.concat base "parser.sx";
|
||||
Filename.concat base "render.sx";
|
||||
Filename.concat base "compiler.sx";
|
||||
Filename.concat spec_base "parser.sx";
|
||||
Filename.concat spec_base "render.sx";
|
||||
Filename.concat lib_base "compiler.sx";
|
||||
Filename.concat web_base "signals.sx";
|
||||
Filename.concat web_base "adapter-html.sx";
|
||||
Filename.concat web_base "adapter-sx.sx";
|
||||
|
||||
@@ -1448,6 +1448,7 @@ def compile_ref_to_py(
|
||||
_project = os.path.abspath(os.path.join(ref_dir, "..", "..", ".."))
|
||||
_source_dirs = [
|
||||
os.path.join(_project, "spec"),
|
||||
os.path.join(_project, "lib"),
|
||||
os.path.join(_project, "web"),
|
||||
ref_dir,
|
||||
]
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
// =========================================================================
|
||||
|
||||
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
|
||||
var SX_VERSION = "2026-03-24T22:31:01Z";
|
||||
var SX_VERSION = "2026-03-24T22:56:21Z";
|
||||
|
||||
function isNil(x) { return x === NIL || x === null || x === undefined; }
|
||||
function isSxTruthy(x) { return x !== false && !isNil(x); }
|
||||
|
||||
@@ -367,17 +367,24 @@ class OcamlBridge:
|
||||
# Collect files to load
|
||||
all_files: list[str] = []
|
||||
|
||||
# Spec files needed by aser + bytecode compiler
|
||||
# Core spec files
|
||||
spec_dir = os.path.join(os.path.dirname(__file__), "../../spec")
|
||||
for spec_file in ["parser.sx", "render.sx", "bytecode.sx", "compiler.sx"]:
|
||||
for spec_file in ["parser.sx", "render.sx"]:
|
||||
path = os.path.normpath(os.path.join(spec_dir, spec_file))
|
||||
if os.path.isfile(path):
|
||||
all_files.append(path)
|
||||
|
||||
# Library files (compiler, vm, freeze — written in the language)
|
||||
lib_dir = os.path.join(os.path.dirname(__file__), "../../lib")
|
||||
for lib_file in ["bytecode.sx", "compiler.sx"]:
|
||||
path = os.path.normpath(os.path.join(lib_dir, lib_file))
|
||||
if os.path.isfile(path):
|
||||
all_files.append(path)
|
||||
|
||||
# All directories loaded into the Python env
|
||||
all_dirs = list(set(_watched_dirs) | _dirs_from_cache)
|
||||
|
||||
# Isomorphic libraries: signals, rendering, freeze scopes, web forms
|
||||
# Isomorphic libraries: signals, rendering, web forms
|
||||
web_dir = os.path.join(os.path.dirname(__file__), "../../web")
|
||||
if os.path.isdir(web_dir):
|
||||
for web_file in ["signals.sx", "adapter-html.sx", "adapter-sx.sx",
|
||||
@@ -385,9 +392,9 @@ class OcamlBridge:
|
||||
path = os.path.normpath(os.path.join(web_dir, web_file))
|
||||
if os.path.isfile(path):
|
||||
all_files.append(path)
|
||||
# Spec library files (loaded after adapters)
|
||||
for spec_lib in ["freeze.sx"]:
|
||||
path = os.path.normpath(os.path.join(spec_dir, spec_lib))
|
||||
# Library files loaded after adapters (depend on scope primitives)
|
||||
for lib_file in ["freeze.sx"]:
|
||||
path = os.path.normpath(os.path.join(lib_dir, lib_file))
|
||||
if os.path.isfile(path):
|
||||
all_files.append(path)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user