Skip to content

Latest commit

 

History

198 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

shen-lua

Shen on LuaJIT 2.1: pattern matching, optional sequent types, and Prolog, compiled to Lua. Shen 42, 134/134 official kernel tests. Embeds in any Lua host. Plain Lua 5.1/5.4/5.5 works too (slower, still correct).

git clone https://github.com/pyrex41/shen-lua && cd shen-lua
bin/shen                            # REPL
bin/shen -e "(+ 1 2)"               # one-liner
bin/shen examples/family.shen       # a program (Shen Prolog in 20 lines)
luajit examples/hello_embed.lua     # embed in Lua, ~25 lines

LuaJIT 2.1 is the primary host (brew install luajit / apt-get install luajit); the source checkout also supports plain Lua as described below. First boot compiles the kernel and loads the Shen-source standard library; subsequent boots use bytecode and standard-library caches. Loaded programs are cached fasl-style. Cross-port agreement lives in Bifrost. New to Shen? shenlanguage.org.

How it works

KLambda (the ~46-primitive untyped kernel) is compiled to Lua source; LuaJIT trace-compiles that to machine code. Special forms become native if/return; tail calls are real Lua TCO (and self-tails become loops). type is erased at the Kλ boundary — type checking is the kernel’s own Shen, unchanged.

File Role
runtime.lua values, intern, KLambda reader
compiler.lua KLambda → Lua
prims.lua primitives, apply/curry, native overrides
boot.lua kernel load, bytecode + fasl caches
shen.lua embedding API (require("shen"))
lua_interop.lua Lua ⇄ Shen
repl.lua REPL
prolog_engine.lua / prolog_compile.lua / typecheck_native.lua native Prolog / typecheck

Numbers, strings, and booleans are Lua’s. Symbols are interned (identity ==). () is a unique NIL. Cons is {h,t}; vectors are array tables.

CLI

bin/shen                       # REPL (multiline, history, Shen backtraces)
bin/shen prog.shen ...         # (load) each file
bin/shen -e "(+ 1 2)"          # eval and print
bin/shen --hush-load prog.shen # run a program; no load echo (use this for golden suites)
bin/shen -q prog.shen          # *hush*: silences load echo *and* (output ...)

--hush-load (or SHEN_HUSH_LOAD=1) is what batch runners want: the program’s own output stays, load chatter does not. -q sets *hush*, which on kernel 42 gates pr itself.

The launcher finds the vendored klambda/ next to the checkout, so bin/shen works from any cwd.

Embed

local shen = require("shen")
shen.boot{ quiet = true }
shen.eval('(define square X -> (* X X))')
print(shen.call("square", 9))         --> 81
local sq = shen.fn("square")          -- ordinary Lua callable
shen.typecheck("[1 2]", "(list number)")

shen.prims / shen.runtime expose F, the reader, and the printer.

Checked integers at external boundaries

LuaJIT's ordinary Shen numbers are IEEE-754 doubles. Beyond the contiguous exact integer range ±(2^53−1), distinct decimal literals can parse to the same number: 9007199254740993 and 9007199254740992 compare equal. The ordinary Shen number model remains unchanged for kernel compatibility.

For integer IDs, counters, or amounts that must not silently round, pass the original decimal string to the opt-in checked path:

local id = shen.checked_integer("9007199254740991") -- accepts ±(2^53−1)
local next_id = shen.checked_add(id, 0)
-- shen.checked_integer("9007199254740993") raises before tonumber can round

In Shen, use (lua.checked-integer "42") and lua.checked-add, lua.checked-sub, or lua.checked-mul for operations whose result must stay in range. They raise trappable Shen errors on invalid input or overflow. The bridge registers string --> number and curried number --> number --> number signatures for typechecked Shen call sites; the runtime checks enforce the narrower safe-integer range. Never pass an already-parsed numeric literal to lua.checked-integer: its original digits may already be lost. The returned value is an ordinary Shen number; subsequent ordinary arithmetic does not carry this guarantee. For values outside ±(2^53−1), retain decimal text or use a separate exact integer representation instead of converting to a Shen number.

From Shen: (lua.call "string.format" ["%s: %d" "answer" 42]). lua.function registers a Lua function as a typed Shen function so (tc +) can prove call sites. lua.map-new / lua.map-get / lua.map-put are hash maps keyed by Shen values under =, hashed in place over cons cells: the fast path for visited sets and memo tables. One gotcha: a plain {} returned from Lua is an empty array and comes back as (); use (lua.table-new) for a table you mean to keep. Details at the top of lua_interop.lua.

Examples

examples/hello_embed.lua boot, define, call both ways
examples/family.shen Prolog facts and queries
examples/config_check.lua typed validation of Lua tables
examples/openresty/ guestbook: one rules.shen on OpenResty and in the browser

More under examples/. A full tour: demo/walkthrough.md.

Tests

make test                      # port specs (test/*_spec.lua)
luajit run-kernel-tests.lua    # official 42 suite → 134/134

The kernel suite is vendored in tests/. Port specs cover primitives, REPL, interop, tail-call lowering, boot caches.

Install

luarocks install shen                       # launcher + modules
luarocks make --local shen-scm-1.rockspec   # this tree

LuaJIT required (lua == 5.1). Release 0.11.1 uses kernel 42; 0.9.0 was 41.1. Or grab shen-bundle.lua from Releases — one file, require("shen-bundle").

luajit build/make-bundle.lua    # → build/shen-bundle.lua

Performance

workload time
Kernel load, warm bytecode cache (in-process CPU; earlier run) ~0.03 s
Full CLI startup, warm (wall; 2026-09-27 A/B host) 0.119 s
Full CLI startup, cold (wall; same host) 1.383 s
42 suite, warm fasl (wall; same host) 4.592 s
Einstein solve in bench.lua (in-process CPU; same host) 0.0682 s

These measure different boundaries; the warm kernel-load number excludes standard-library loading and process startup. See the dated run and methodology and benchmark guide; times are host- and workload-dependent.

List builders of the [X | (f ...)] shape compile to a loop (tail recursion modulo cons), so they neither use a stack frame per element nor overflow on long lists; SHEN_TRMC=off restores plain recursion for A/B runs. To read LuaJIT trace logs, run luajit -jv bin/shen ....

Prolog and the typechecker run on a native engine (prolog_engine.lua); the portable kernel predicates that show up on compile and execution paths are overridden in prims.lua. Caches (kernel bytecode, stdlib image, user fasl) are content-keyed and safe to delete. Internals: doc/PERF-HANDOFF.md, doc/BENCHMARKS.md.

Requirements

LuaJIT 2.1. Kernel sources are in klambda/ (see klambda/PROVENANCE.md). SHEN_KL_DIR can point at another tree.

PUC Lua 5.1/5.4/5.5: same 134/134. No FFI → legacy Prolog engine; no bit → caches off. Lua 5.3+ arithmetic is forced to floats so it matches LuaJIT.

Old LuaJIT on aarch64 (2.1.0-beta3): boot-time JIT crash, fixed upstream. Use a current rolling LuaJIT, or SHEN_JIT=off.

Nix

Optional. nix develop or direnv allow for a pinned toolchain. packages.toolchain is what Bifrost composes.

About

Shen on LuaJIT — certified port of the Shen 41.1 kernel, with typed Lua <-> Shen interop

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages