feat(stdlib): namespaced standard library (issue #2)

Implement the bulk of the namespaced-stdlib proposal (workshopsoft/ludic#2):
156 namespace methods across Math, Text, List, Ease, Collide, World, Net,
Sys, Save, Mem, extended Screen, Color functions, extended Random, and Time.
All deterministic fixed-point; self-hosting (C-free bootstrap fixpoint holds).

Compiler (selfhost/):
- Math.*: sqrt/sin/cos/tan/atan2/asin/acos (fixed-point runtime prelude —
  bit-by-bit isqrt, 256-entry interpolated sine table, Ross atan2), plus
  hypot/dist/dist2/deg_to_rad/rad_to_deg/posmod/wrap/ping_pong/snapped/
  move_toward/smoothstep/lerp/remap/sign/floor/ceil/round.
- Text.* (complete): upper/lower/trim/repeat/pad, split/join/replace,
  and the libc-backed queries.
- List.* (complete): insert/remove_at/remove/sort plus the earlier ops.
- Ease.* (in/out/in_out/back/bounce) and Collide.* (rects/point_rect/
  circles/rect_circle).
- Phase 3: World/Net/Sys/Save namespaced over the bare builtins (byte-
  identical IR) and Mem.* (bytes/words/copy/fill/peek/poke).
- Screen.* extended (line/circle/fill_circle/triangle/fill_triangle via new
  runtime primitives; sprite/sprite_scaled aliases), Color.* functions,
  Random.* (value/int/sign), Time.* (frame/delta/elapsed/now — new
  game-loop frame counter).
- Fix a lexer bug: fixed-point literals with >4 fractional digits overflowed.

Docs & tooling:
- 129 new per-symbol doc pages; gen.py made data-driven (namespaces
  discovered from the docs, no hardcoded list); new check-impl.py enforces
  that every implemented namespace method / keyword / type / phase has a
  doc page, wired into `x test-tools`. Document the previously-undocumented
  keywords (break/continue/where/entry/new/public + and/or/not tokens).
- LSP: namespaced signature help (ns_method_sig) covering every namespace.

Tests: 12 new self-host/regression tests + a golden render for the drawing
primitives. All suites green (selfhost 21, regression 45, tools 29).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-08-30 00:26:19 +03:00
parent ff15c4e01d
commit a38195128f
235 changed files with 24676 additions and 7762 deletions

View file

@ -160,6 +160,40 @@ fn param_labels(fn: Node) -> []ptr {
# after reordering we rewrite the callee to that bare name and fall back into the
# ordinary builtin path (which resolves it to its rt_ function).
fn emit_ns_call(ns: ptr, meth: ptr, e: Node) -> Val {
# Math.* is computed inline (deterministic fixed-point), not routed through a
# bare rt_ name — so `floor`/`round`/`lerp` never leak into the bare namespace.
if (ns == "Math") {
if is_math_ns(meth) { return emit_math_ns(meth, e) }
perr(`unknown builtin Math.{meth}`)
}
if (ns == "Text") {
if is_text_ns(meth) { return emit_text_ns(meth, e) }
perr(`unknown builtin Text.{meth}`)
}
if (ns == "List") {
if is_list_ns(meth) { return emit_list_ns(meth, e) }
perr(`unknown builtin List.{meth}`)
}
if (ns == "Ease") {
if is_ease_ns(meth) { return emit_ease_ns(meth, e) }
perr(`unknown builtin Ease.{meth}`)
}
if (ns == "Collide") {
if is_collide_ns(meth) { return emit_collide_ns(meth, e) }
perr(`unknown builtin Collide.{meth}`)
}
if (ns == "Mem") {
if is_mem_ns(meth) { return emit_mem_ns(meth, e) }
perr(`unknown builtin Mem.{meth}`)
}
if (ns == "Color") {
if is_colorfn_ns(meth) { return emit_colorfn_ns(meth, e) }
perr(`unknown builtin Color.{meth}`)
}
if (ns == "Time") {
if is_time_ns(meth) { return emit_time_ns(meth, e) }
perr(`unknown builtin Time.{meth}`)
}
var bare: ptr = null
let labels = new []ptr
if (ns == "Screen") {
@ -173,6 +207,13 @@ fn emit_ns_call(ns: ptr, meth: ptr, e: Node) -> Val {
if (meth == "width") { bare = "screen_w" }
if (meth == "height") { bare = "screen_h" }
if (meth == "status") { bare = "status"; push(labels, "text") }
if (meth == "line") { bare = "line"; push(labels, "x1"); push(labels, "y1"); push(labels, "x2"); push(labels, "y2"); push(labels, "color") }
if (meth == "circle") { bare = "circle"; push(labels, "x"); push(labels, "y"); push(labels, "radius"); push(labels, "color") }
if (meth == "fill_circle") { bare = "fill_circle"; push(labels, "x"); push(labels, "y"); push(labels, "radius"); push(labels, "color") }
if (meth == "triangle") { bare = "triangle"; push(labels, "x1"); push(labels, "y1"); push(labels, "x2"); push(labels, "y2"); push(labels, "x3"); push(labels, "y3"); push(labels, "color") }
if (meth == "fill_triangle") { bare = "fill_triangle"; push(labels, "x1"); push(labels, "y1"); push(labels, "x2"); push(labels, "y2"); push(labels, "x3"); push(labels, "y3"); push(labels, "color") }
if (meth == "sprite") { bare = "draw_sprite"; push(labels, "id"); push(labels, "x"); push(labels, "y") }
if (meth == "sprite_scaled") { bare = "draw_sprite_scaled"; push(labels, "id"); push(labels, "x"); push(labels, "y"); push(labels, "scale") }
}
if (ns == "Map") {
if (meth == "size") { bare = "map_size"; push(labels, "width"); push(labels, "height") }
@ -183,10 +224,64 @@ fn emit_ns_call(ns: ptr, meth: ptr, e: Node) -> Val {
if (meth == "range") { bare = "rng_range"; push(labels, "low"); push(labels, "high") }
if (meth == "chance") { bare = "rng_chance"; push(labels, "percent") }
if (meth == "seed") { bare = "seed"; push(labels, "value") }
if (meth == "value") { bare = "rng_value" }
if (meth == "int") { bare = "rng_int"; push(labels, "max") }
if (meth == "sign") { bare = "rng_sign" }
}
if (ns == "Input") {
if (meth == "key") { bare = "key" }
}
# Phase 3: the bare reflection / networking / process builtins, namespaced.
# Each is a pure alias — the callee is rewritten to the bare name below.
if (ns == "World") {
if (meth == "get") { bare = "world_get" }
if (meth == "set") { bare = "world_set" }
if (meth == "has") { bare = "world_has" }
if (meth == "count") { bare = "world_count" }
if (meth == "size") { bare = "world_size" }
if (meth == "spawn") { bare = "world_spawn" }
if (meth == "save") { bare = "world_save" }
if (meth == "load") { bare = "world_load" }
if (meth == "prop_id") { bare = "world_prop_id" }
if (meth == "field_id") { bare = "world_field_id" }
if (meth == "model_id") { bare = "world_model_id" }
if (meth == "kind") { bare = "world_kind" }
if (meth == "register_prop") { bare = "world_register_prop" }
if (meth == "attach") { bare = "world_attach_dyn" }
if (meth == "detach") { bare = "world_detach_dyn" }
if (meth == "query_next") { bare = "world_query_next" }
}
if (ns == "Net") {
if (meth == "send") { bare = "net_send" }
if (meth == "poll") { bare = "net_poll" }
if (meth == "serialize") { bare = "serialize" }
if (meth == "apply") { bare = "apply" }
if (meth == "owner") { bare = "owner" }
if (meth == "set_owner") { bare = "set_owner" }
if (meth == "is_server") { bare = "is_server" }
if (meth == "is_owner") { bare = "is_owner" }
if (meth == "local_id") { bare = "local_id" }
}
if (ns == "Sys") {
if (meth == "arg") { bare = "arg" }
if (meth == "arg_count") { bare = "arg_count" }
if (meth == "exit") { bare = "exit" }
if (meth == "run") { bare = "run" }
if (meth == "env") { bare = "getenv" }
if (meth == "read_char") { bare = "read_char" }
if (meth == "file_open") { bare = "file_open" }
if (meth == "file_read") { bare = "file_read" }
if (meth == "file_write") { bare = "file_write" }
if (meth == "file_seek") { bare = "file_seek" }
if (meth == "file_tell") { bare = "file_tell" }
if (meth == "file_close") { bare = "file_close" }
if (meth == "stdout") { bare = "file_stdout" }
if (meth == "stderr") { bare = "file_stderr" }
}
if (ns == "Save") {
if (meth == "write") { bare = "save" }
if (meth == "read") { bare = "load" }
}
if (bare == null) { perr(`unknown builtin {ns}.{meth}`) }
reorder_named(e, labels)
let id = node(E_ID); id.s = bare; e.a = id