An implementation of a parser, evaluator, printer, and visualizer for SKI.
- TypeScript
- Node.js as a bootstrap for the repo-pinned toolchain
- C (compiled to WebAssembly)
- Bazelisk, which downloads the hermetic Zig-based C/C++ toolchain on first native build
This project uses Bazelisk for common development tasks:
bazelisk build //:thanatos //:release_wasm
bazelisk test //:native_tests
bazelisk test //:node_tests
bazelisk run //:build
bazelisk run //:test
bazelisk run //:coverage
bazelisk run //:ci
bazelisk run //:serve_hephaestus
bazelisk run //:vs_projectThe Bazel graph now includes hermetic native thanatos and wasm/release.wasm
targets alongside the Node.js-based build, lint, coverage, and packaging flows,
without requiring WSL, Nix, or Visual Studio Build Tools on Windows.
If Bazelisk is installed as bazel on your machine, the same commands work with
bazel in place of bazelisk. The Bazel version is pinned in .bazelversion.
- Install
Node.js - Install
Bazelisk - Clone the repository
- Run
bazelisk build //:thanatos //:release_wasm - Run
bazelisk run //:build - Open the project in VS Code
The required Node.js toolchain version is pinned in the repository configuration.
Bazelisk commands use your installed Node.js only as a bootstrap shim, then run
the repo-pinned Node.js version for the actual build/test command.
If your system Node.js does not match, the first Bazelisk run will install the
exact pinned binary into a local toolchain cache. Set
TYPED_SKI_NODE_TOOLCHAIN_DIR if you want that cache in a specific location.
Run the test suite with:
bazelisk test //:node_testsFor a local single-process workspace run of the same Node.js suite, you can still use:
bazelisk run //:testOn Windows, pass --enable_runfiles to bazelisk test //:node_tests if your
Bazel setup does not expose a runfiles tree by default.
Run the native C targets with:
bazelisk build //:thanatos //:release_wasm
bazelisk test //:native_testsCheck which repo-pinned Node.js version Bazelisk will use with:
bazelisk run //:verify_versionOther useful commands:
bazelisk run //:buildbuilds generated metadata and distributable artifactsbazelisk run //:typecheckruns TypeScript type checking over the test suitebazelisk run //:coverageruns the tests with coverage outputbazelisk run //:ciruns formatting, lint, type checking, build, and a single coverage-producing local test passbazelisk run //:vs_projectwritescompile_commands.json,CppProperties.json,.vs/tasks.vs.json,.vs/launch.vs.json,typed-ski-thanatos.sln, andtyped-ski-thanatos.vcxprojfor Visual Studio workflows
To generate Visual Studio Open Folder metadata from the Bazel C targets, run:
bazelisk run //:vs_projectThen open the repository directory in Visual Studio with File > Open > Folder. The generated metadata gives Visual Studio:
CppProperties.jsonfor Bazel-derived include paths and defines.vs/tasks.vs.jsonfor Bazel build and test tasks.vs/launch.vs.jsonwith a starterthanatosdebug configurationtyped-ski-thanatos.slnandtyped-ski-thanatos.vcxprojfor the classic solution/project workflow, including one project forthanatosand one for each native test target
The launch configuration uses gdb (type: "cppdbg"). If gdb.exe is not on
your PATH, edit .vs/launch.vs.json and set miDebuggerPath to your local
GDB installation before starting a debug session.
If you want the old-school Visual Studio debugger or profiler, open
typed-ski-native.sln. The generated projects are Makefile-style C++ projects:
Visual Studio invokes Bazel for build/rebuild/clean, and each startup project
launches its Bazel-built binary directly. The generated solution includes:
typed-ski-thanatostyped-ski-dag-codec-testtyped-ski-performance-testtyped-ski-session-testtyped-ski-ski-io-testtyped-ski-util-test
The generated typed-ski-performance-test project includes a starter debugger
argument preset tuned for faster iteration inside Visual Studio. Edit the
project's Debugging properties if you want to switch back to a larger arena or
workload.
The generated Visual Studio metadata is local-only and gitignored, including
compile_commands.json, CppProperties.json, .vs/, typed-ski-native.sln,
typed-ski-*.vcxproj, typed-ski-*.vcxproj.filters, and *.vcxproj.user.
Regenerate them at any time with bazelisk run //:vs_project.
Build the browser assets for the workbench with:
bazelisk run //:hephaestus_assetsStart the server with:
bazelisk run //:serve_hephaestusThen open http://127.0.0.1:8080/workbench.html.
To use a different port:
PORT=9000 bazelisk run //:serve_hephaestusOn PowerShell:
$env:PORT = "9000"
bazelisk run //:serve_hephaestusNotes:
//:serve_hephaestusbuildsdist/workbench.js,dist/webglForest.js, anddist/arenaWorker.jsbefore starting the server.bazelisk build //:release_wasmwrites the hermetic wasm artifact tobazel-bin/wasm/release.wasm. The Node.js-side build flow stages that artifact intowasm/release.wasmwhen present so browser and publish paths can use the Bazel-built module.
This project uses Bazel as the primary build entrypoint. The supported workflow for this branch is Bazel plus Node.js, with generated metadata, packaging, linting, coverage, and the test suite exposed through Bazel commands.
The TypeScript bootstrap pipeline treats compiler artifacts as canonical, ASCII-only outputs:
- Top-level Trip unparse preserves the original source-level definition kind and
emits parseable canonical syntax such as
poly recandcombinator, while internal lowering stages uselambdaduring linking and execution. .tripcobject files are emitted with canonical import/export/definition ordering and recursively sorted object keys.- Link-time dependency traversal and SCC processing use explicit ASCII ordering
instead of incidental
Map/Setiteration order. - Final SKI output is the fully parenthesized canonical
unparseSKIform and should be compared as UTF-8 bytes.
This project implements a high-performance, multi-threaded SKI reducer:
- Parallel Request Execution: Multiple Web Workers reduce independent requests against a shared arena.
- Preemptive Yielding: Workers yield suspended computations so long-running jobs do not monopolize execution.
- Lock-Free Communication: io_uring-style submission and completion rings enable low-latency communication between the main thread and workers.
- Structural Sharing: Global hash-consing ensures that identical sub-expressions share the same memory, significantly reducing the memory footprint of large reductions.
Thanatos is the native C11/pthreads orchestrator for compute-heavy reductions.
The same C core (arena and reduction logic) is compiled in two ways: as the
native thanatos binary for CLI/batch use, and to WebAssembly
(wasm/release.wasm) for use by the parallel arena evaluator in Node.js. The
native binary keeps the SKI evaluator on-metal by managing worker dispatch and
completion queues directly, which avoids Node.js/WASM bridge overhead and improves
throughput and runtime stability for long-running workloads.
- Combinators: A Centennial View, Stephen Wolfram
- To Mock a Mockingbird, Raymond Smullyan
- Combinatory Logic Volume I, Haskell Brooks Curry & Robert Feys
- D. A. Turner, "A new implementation technique for applicative languages," Software: Practice and Experience, vol. 9, no. 1, pp. 31-49, 1979. DOI: 10.1002/spe.4380090105
- W. Stoye, T. J. W. Clarke, and A. C. Norman, "Some practical methods for rapid combinator reduction," in Proceedings of the 1984 ACM Symposium on LISP and Functional Programming (LFP '84), ACM, New York, NY, USA, pp. 159-166, 1984. DOI: 10.1145/800055.802038
- H. G. Baker, "CONS should not CONS its arguments, or, a lazy alloc is a smart alloc," ACM SIGPLAN Notices, vol. 27, no. 3, pp. 24-34, 1992. DOI: 10.1145/130854.130858
GitHub Actions use Bazel on both Ubuntu and native Windows. Native C targets run
through ordinary Bazel build/test steps, and the Node.js suite runs through the
sharded //:node_tests Bazel test target so each shard owns its own Thanatos
session. See the workflow files in .github/workflows/ for details.