Documentation

Build mode

Play (luke SHOW) is a convenience: GC’d bytecode VM for demos and exploration.
Build (luke BUILD) is the language of record: syntax v2 → native code, no GC, Rust-class lightness.

This is Path A: Python-shaped versatility on the surface, Zig/Rust-shaped cost underneath.

Commands

luke SHOW  examples/build/hello.luke              # prefers Build (native temp); --vm forces Playluke BUILD examples/build/hello.luke              # Build — native binary, no GCluke BUILD examples/build/hello.luke -o helloluke BUILD examples/build/hello_wasm.luke -target wasm -o hello.wasmluke BUILD examples/build/hello_browser.luke -target browser -o hello_webluke BUILD examples/build/functions.luke -target debug -o fn  # -O0 -g for gdbluke DEBUG examples/build/functions.luke --break 10           # interactive gdb (.luke:line)luke DEBUG examples/build/functions.luke --break 10 --batch    # CI: break + next/step/finishluke LSP                                          # stdio LSP: hover/outline/FMT/refs/rename/…# run wasm (WASI): node scripts/run_wasi.cjs hello.wasm# run browser wasm headless: node scripts/luke_browser_loader.cjs hello_web.wasm# or open hello_web.html in a browser

Build emits C with #line N "file.lk" per statement, then compiles with the system C compiler (cc -O2 -g native, or -O0 -g -fno-inline for -target debug / luke DEBUG). WASI SDK for -target wasm|browser. Debugger skips luke_rt.h / stdlib headers so next = step over, step = step into Luke FUNCTION, finish = step out. luke DEBUG --inspect dumps reactive cell values and dependency edges (luke_rx_inspect_cstr). luke DAP is a stdio Debug Adapter Protocol server (gdb backend) for editors — Reactive scope shows named cells + deps. Browser also writes *.html + luke_browser_loader.js (copied beside the wasm) for <script> tags.

IMPORT + stdlib + packages

import "./critter.luke"          # relative moduleimport std/files                 # read/write TEXT filesimport std/json                  # JSON tree helpersimport std/http                  # httpGet (native)import luke/greeter              # package from luke_modules/greeter

std/* resolves from vm/stdlib/. Relative paths are next to the entry file.

Packages (luke/<name>)

Look up order: 1. luke_modules/ next to the source file 2. ./luke_modules, LUKE_PACKAGES (colon-separated roots) 3. Package dir must contain luke.pkg (with entry=…), or main.luke, or <name>.luke

luke_modules/greeter/luke.pkg    # entry=main.lukeluke_modules/greeter/main.luke

IMPORT package:greeter is an alias for IMPORT luke/greeter.

Foreign FFI imports (C/JS/Python bridges) are intentionally not magic IMPORT numpy — parked for later; C wrappers will come first.

Module Helpers
std/files readFile, writeFile, fileExists
std/json jsonParse, jsonGet, jsonIndex, jsonLen, jsonHas, jsonAsText / Number / Flag, jsonStringify, jsonString
std/http httpGet (native via curl; empty on WASI)
std/server httpListen, httpAccept, httpReply, httpPath / Method / Query / Body, httpMatch, httpQueryMap, httpHeader / Cookie / SetCookie, httpJson, SSE helpers
std/sqlite dbOpen/dbClose thread-local pool, stmt cache, WAL; dbExecBind / dbQueryBind; auto -lsqlite3
std/pg pgOpen/pgClose/pgCheckout/pgCheckin, pgQueryBind/pgExecBind/pgRowsBindSlipstream default (LUKE_PG_ASYNC=0 → blocking); auto -lpq
std/auth authInit, authCreateAccount, authLogin / Logout, authRequire, authCsrf / authCheckCsrf (links -lsodium; set LUKE_AUTH_SECURE=1 behind HTTPS)
std/args argCount, getArg
std/env getEnv, setEnv
std/paths cwd, pathJoin, pathBasename, pathDirname
std/process shell, exitWith
std/js jsSetText, jsSetHtml, jsGetValue, jsFetch, jsOnClick, jsLoadFont, jsAddStyle, jsSetTitle
luke/… Your packages under luke_modules/ (luke PKG init <name>)

Browser page ownership (conversational)

page.title("LukeLang")page.font("Syne", "./fonts/syne-700.woff2"   # local pack → @font-face + copy)page.style("""  body { font-family: Syne, sans-serif; }""")fill("root", """  <h1>LukeLang</h1>  <button id="go">Go</button>  <p id="out"></p>""")raw """WHEN "go" IS CLICKED DO  FILL "out" WITH "Still LukeLang."END WHEN"""

-target browser emits HTML with Luke title/CSS/body/fonts baked in, wasm beside it, and inlines vm/runtime/luke_browser_boot.js (runtime — not app JS). Dist has no luke_browser_loader.js.

See sample/landing.luke.

Collections + problems (conversational)

var nums: list = []nums.push("one")print(nums[0])print(nums.len())var bag: map = {}bag["name"] = "Luke"print(bag["name"])try {  throw "could not finish"} catch (problem) {  print(problem)}

TEST

test "math" {  assert 1 + 1 == 2}
luke TEST examples/build/collections_test.luke

Packages

luke PKG init mylibluke PKG install echoluke PKG publish mylibluke PKG lock          # writes luke.lock

Arena scopes

raw "IN ARENA DO"let tmp: str = "ephemeral"print(tmp)raw "END ARENA"

Bump pointer is restored at END ARENA — request/frame-scoped memory without GC.

Guarantees (Build)

Rule Meaning
No GC Memory comes from stack, statics, or a bump arena freed in bulk
Fixed layouts BLUEPRINT fields are a C struct — not open hash maps
Known types Locals/fields are NUMBER, FLAG, TEXT, or a blueprint type
Native code Ahead-of-time C → machine code (and later WASM)
Same voice SPEAK, MY NAME IS, ASK, BLUEPRINT still work

Types (Luke words)

Luke Build representation
NUMBER double
INTEGER int64_t (exact IDs / money cents / counters)
FLAG int (0/1)
TEXT LukeText { ptr, len } (arena or literal)
JSON LukeJson * (arena tree — parse / get / stringify)
LIST LukeList * (arena-backed text items)
MAP LukeMap * (arena-backed text keys/values)
SERVER / REQUEST HTTP server / request handles
DATABASE SQLite handle
BLUEPRINT Foo typedef struct Foo { ... } Foo

Inference (v0): - 42INTEGER (no . / exponent); 3.14NUMBER - "hi" / wordy strings → TEXT - TRUE / FALSEFLAG - ASK jsonParse WITH …JSON - HAS name SET TO "..." → field TEXT - HAS count SET TO 0 → field INTEGER - HAS x AS NUMBER / AS INTEGER / AS TEXT / AS FLAG / AS JSON — explicit when needed - First assignment to a local fixes its type; later SET must match (INTEGER widens to NUMBER) - Function args/arity and GIVE BACK types are checked - Optional: THIS IS FUNCTION f … GIVES BACK TEXT DO - Concurrent HTTP: ASK httpServe WITH server, handler, maxConn (SO_REUSEPORT multi-loop + handler pools; links -lpthread) - Routing / request shape: httpMatch, httpQueryMap, httpHeader / httpCookie / httpSetCookie, httpJson - Parameterized SQL: dbExecBind / dbQueryBind (prefer over string-concat dbExec / dbQuery) - Auth: IMPORT std/auth — Argon2id passwords (libsodium), REQUIRE LOGIN, THE CURRENT USER, CSRF; see BACKEND_ROADMAP.md - SSE: httpSseOpen / httpSseData / httpSseId / httpSseComment; httpLastEventId for resume - INTEGER rules: see INTEGER.md — checked overflow, DIVIDENUMBER, widening vs truncating conversion - Backend track: BACKEND_ROADMAP.md · master checklist TaskList.md

Backend concurrency ceiling

httpServe is N SO_REUSEPORT event loops (default: one per CPU) plus per-loop handler pools:

  • Event-loop I/Oepoll / kqueue / poll; non-blocking accept4, read, writev. Edge-triggered on Linux (EPOLLET). Idle / half-open clients do not occupy a thread.
  • Multi-core accept — each loop owns its listen socket with SO_REUSEPORT so the kernel load-balances connections (nginx / Go-netpoller model). LUKE_HTTP_LOOPS overrides the count.
  • Handler pool — workers run only after a complete request is buffered. Pools are per loop (no global enqueue mutex across cores). LUKE_HTTP_INLINE=1 runs the handler on the loop thread (REST bench / run-to-completion).
  • Per-request cost — pooled arenas (luke_arena_clear, default first block 8 KiB), embedded LukeHttpServeJob on the connection, TCP_NODELAY on accept.
  • HTTP/1.1 keep-alive — default LUKE_HTTP_KEEPALIVE_MAX=100000 (env 0 = unlimited).
  • Timeouts — idle READ/WRITE sweep (LUKE_HTTP_TIMEOUT_MS).
  • Graceful stopSIGTERM/SIGINT stop accepts, close idle sockets, drain in-flight handlers.
  • Chunked / SSE — leave the loop; may block one worker for the stream lifetime.
  • Escape hatchesLUKE_HTTP_IO=pool (legacy blocking recv); see DEPLOY.md.

Positive maxConn is a lifetime accept budget across all loops; maxConn ≤ 0 accepts until signal/failure.

Memory (Luke words)

Word Meaning in Build
(default locals) Stack / registers
NEW instances Allocated in the thread arena (bump); live until arena reset/program end
TEXT concat / dynamic strings Arena-backed
IN ARENAEND ARENA Checkpoint + reset bump pointer (scoped bulk free)
DROP (roadmap) Early release of a single value

There is no mark-sweep collector in Build binaries. Peak memory is predictable: stack + arena high-water mark.

Blueprints

struct Dog : Animal {  sound = "Woof!"  init(name) {    self.name = name  }  fn speak() {    print(self.name + " says " + self.sound)  }}

Lowers to roughly:

typedef struct Dog {  Animal base; /* or flattened parent fields */  LukeText sound;} Dog;void Dog_speak(Dog *self);Dog *Dog_born(LukeArena *a, LukeText name);
  • ASK buddy TO speakDog_speak(buddy) (static dispatch when type known)
  • CALL PARENT speakAnimal_speak((Animal*)self)
  • PRIVATE fields → C name mangling; only methods of that blueprint may touch them
  • ALWAYS HAS → static/globals on the blueprint’s C file scope

What Play allows that Build may reject

  • Adding undeclared fields at runtime
  • Truly dynamic ASK on unknown types (Build wants a known blueprint type)
  • Unlimited runtime mutation of object shapes
  • Relying on GC to clean cycles (Build uses arenas — don’t build immortal graphs by accident)

Play remains for sketching. Shipping artifacts should BUILD.

Roadmap toward “Python everywhere + Rust light”

  1. ~~Build → C + arena runtime~~
  2. ~~Clearer Luke-voice Build errors; AS TYPE annotations~~
  3. ~~Packages / relative IMPORT + std/files + std/json~~
  4. ~~luke BUILD -target wasm (WASI)~~
  5. ~~Richer typechecking; fuller JSON; thin HTTP GET~~
  6. ~~Browser-oriented WASM packaging (-target browser)~~
  7. ~~Package registry (IMPORT luke/<name> + luke_modules/)~~
  8. ~~SHOW prefers Build; Play VM is the compatibility layer (--vm)~~
  9. ~~Tooling stdlib (args/env/paths/process) + luke PKG init~~
  10. ~~Browser JS bridge (std/js)~~
  11. ~~IN ARENA / END ARENA scopes~~
  12. ~~Remote package registry (luke PKG install + registry/index.json)~~
  13. ~~Foreign imports (IMPORT c: + FOREIGN FUNCTION)~~
  14. ~~Build IR shared frontend (expand/soften for Play; luke IR dump)~~
  15. ~~LIST / MAP + ATTEMPT / OTHERWISE + luke TEST~~
  16. ~~std/server + std/sqlite + browser fetch/click~~
  17. ~~luke PKG publish + luke.lock~~
  18. ~~luke LSP stdio diagnostics beachhead~~
  19. Richer remote registry (signed packages)
  20. Explicit Python bridges (beyond C FFI)
  21. Optional: emit Play bytecode opcodes directly from Build IR nodes

Rendering / layout (engine track)

  • Argus (rendering): ARGUS.md — scene paint → DOM presentment.
  • Hanka (layout): HANKA.mdCOLUMN / ROW / STACK → frames → Argus.
  • Production web: PRODUCTION_WEB.md — inputs, routes, deploy checklist.
  • Reactive (Phases 1–8): REACTIVE.md — full reactive stack shipped.
import std/hankaimport std/argusraw "BEGIN COLUMN AT 48, 420 SIZE 1184, 280 PAD 0 GAP 16"raw "SLOT TEXT \"brand\" SIZE 900, 80 SAY \"LukeLang\""raw "END COLUMN"raw "LAY OUT THE SCREEN"raw "PAINT THE SCREEN"
luke BUILD examples/build/hanka_demo.luke -target browser -o build/hanka_demo

Philosophy

Conversational syntax is the UI.
Build-mode layouts, types, and arenas are the truth.
That split is how Luke can feel like Python and weigh like Rust.

Parser ceiling (known limit)

Build codegen (vm/src/build_c.cpp) is line-based: one statement per line, startsWithCI / keyword scans, not a full lexer + AST.

Syntax v2 parses to an AST and lowers to that line-based surface, so the ceiling still applies underneath: multi-line expressions, richer nesting, and flexible punctuation will need codegen to consume the AST directly.

Today Ceiling
Prefix/findOutsideQuotes stmt match Awkward multi-line constructs
One line ≈ one stmt Soft line-wrap / continued statements
Hand-rolled expression splits (AND, ADD, …) Precedence tables / proper AST

Roadmap: expression parentheses + paren-aware op scan shipped (stripOuterParens, luke_ast.hpp IR stub). Next: tokenize → Pratt expr AST → lower into Build codegen; then statement AST so LSP/formatter share one tree. Do not reintroduce a parallel JS parser.