feat(compiler): package-declarable engine systems + namespaces (#62)
Registry-izes the two hooks that made stdlib namespaces and engine systems compiler-hardcoded, so a package registers them with no compiler edit — the Phase-1 prerequisite for shipping the controller libraries (#58–#61) as real packages rather than in-repo stdlib. - Engine systems are a data-driven registry (component, esys-fn, phase). The core three (SpriteAnim/Motion — Update, Light2D — Render) are seeded in that exact order, so uses_engine_systems / emit_engine_systems_for_phase are now registry-driven with byte-identical output (verified: anim_ecs, light_ecs, snake IR unchanged; 87/0 golden renders; C-free fixpoint holds). A package appends with `@EngineSystem(Component, Phase)` on its esys function. - Namespaces are a registry too: a package marks a provider with `@Namespace(Foo)`, and emit_ns_call aliases an otherwise-unknown Foo.method to the bare foo_method (the same generic path the core namespaces use) — after every hardcoded core block, so core dispatch is untouched. - Both annotations are keyword-free (like #64's @System), so no vocabulary / grammar churn. Proven end-to-end (hermetic, source path, runs everywhere): a package registers Score + esys_score via @EngineSystem and coach_bonus via @Namespace; a consumer game imports it and prints "4 99" — the engine system ran each Update and Coach.bonus() dispatched, with no compiler edit for the package. Package suite 18/0. Core stdlib namespaces stay on their optimized hardcoded blocks by design (byte-identity + determinism); the generic path is proven to carry a namespace and packages ride it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
075a7b1430
commit
7cbd5d175c
6 changed files with 19004 additions and 18230 deletions
|
|
@ -445,6 +445,27 @@ var g_uses_http: bool = false # Http.* (#6) -> splice http.ludic; links http
|
|||
# which Ludic source cannot take). Parallel arrays: fn name -> phase name.
|
||||
var g_mod_sys_fn: []pointer
|
||||
var g_mod_sys_phase: []pointer
|
||||
# issue #62: package-declarable registries (data-driven, additive to the core
|
||||
# hardcoded paths). Engine systems: (component, esys-fn, phase) — @EngineSystem.
|
||||
# Namespaces: names a package provides Foo.* dispatch for — @Namespace.
|
||||
var g_esys_comp: []pointer
|
||||
var g_esys_fn: []pointer
|
||||
var g_esys_phase: []pointer
|
||||
var g_namespaces: []pointer
|
||||
|
||||
# #62: package namespace registry — a package marks a Foo.* provider with
|
||||
# @Namespace(Foo); emit_ns_call aliases an otherwise-unknown Foo.method to the
|
||||
# bare function foo_method (lowercased namespace + "_" + method).
|
||||
function register_namespace(name: pointer) -> void {
|
||||
var i = 0
|
||||
while i < len(g_namespaces) { if (g_namespaces[i] == name) { return }; i = i + 1 }
|
||||
push(g_namespaces, name)
|
||||
}
|
||||
function is_registered_namespace(name: pointer) -> bool {
|
||||
var i = 0
|
||||
while i < len(g_namespaces) { if (g_namespaces[i] == name) { return true }; i = i + 1 }
|
||||
return false
|
||||
}
|
||||
|
||||
function already_loaded(full: pointer) -> bool {
|
||||
var i = 0
|
||||
|
|
@ -473,6 +494,9 @@ function parse_one_decl() -> void {
|
|||
var role: pointer = null # @Server / @Predicted — a handler's network role (N5)
|
||||
var remote_dir: pointer = null # @ToServer / @ToClients — a remote event's direction (N4)
|
||||
var sys_phase: pointer = null # @System(Phase) — a binary module's runtime-registered system (#64)
|
||||
var esys_comp: pointer = null # @EngineSystem(Comp, Phase) — a package compile-time engine system (#62)
|
||||
var esys_phase: pointer = null
|
||||
var ns_name: pointer = null # @Namespace(Name) — a package-provided Foo.* namespace (#62)
|
||||
while is_op("@") {
|
||||
pi = pi + 1; let a = eat_id() # collect a leading @annotation
|
||||
if (a == "export") { is_export = true }
|
||||
|
|
@ -496,8 +520,10 @@ function parse_one_decl() -> void {
|
|||
else { if (a == "ToServer") { remote_dir = "toserver" } # @ToServer event (N4)
|
||||
else { if (a == "ToClients") { remote_dir = "toclients" } # @ToClients event (N4)
|
||||
else { if (a == "System") { sys_phase = "Update"; if is_op("(") { pi = pi + 1; sys_phase = eat_id(); eat_op(")") } } # @System(Phase) binary-module system (#64)
|
||||
else { if (a == "EngineSystem") { eat_op("("); esys_comp = eat_id(); eat_op(","); esys_phase = eat_id(); eat_op(")") } # @EngineSystem(Comp, Phase) package engine system (#62)
|
||||
else { if (a == "Namespace") { eat_op("("); ns_name = eat_id(); eat_op(")") } # @Namespace(Name) package Foo.* namespace (#62)
|
||||
else { if is_op("(") { var d = 0 # any other @anno(args) — parsed and skipped
|
||||
while true { if is_op("(") { d = d + 1 }; if is_op(")") { d = d - 1 }; pi = pi + 1; if d == 0 { break } } } } } } } } } } } } } } } } } } } } } }
|
||||
while true { if is_op("(") { d = d + 1 }; if is_op(")") { d = d - 1 }; pi = pi + 1; if d == 0 { break } } } } } } } } } } } } } } } } } } } } } } } }
|
||||
skipnl()
|
||||
}
|
||||
if is_id("import") { pi = pi + 1
|
||||
|
|
@ -562,6 +588,8 @@ function parse_one_decl() -> void {
|
|||
let f = parse_fn()
|
||||
if is_export { f.ival = 1 }
|
||||
if (sys_phase != null) { push(g_mod_sys_fn, f.s); push(g_mod_sys_phase, sys_phase) } # #64: register at load
|
||||
if (esys_comp != null) { push(g_esys_comp, esys_comp); push(g_esys_fn, f.s); push(g_esys_phase, esys_phase) } # #62: package engine system
|
||||
if (ns_name != null) { register_namespace(ns_name) } # #62: package Foo.* namespace
|
||||
push(prog, f); return
|
||||
}
|
||||
if is_id("extern") { push(prog, parse_extern()); return }
|
||||
|
|
@ -790,6 +818,14 @@ function parse_program() -> void {
|
|||
g_tests = new []Node
|
||||
g_mod_sys_fn = new []pointer
|
||||
g_mod_sys_phase = new []pointer
|
||||
# #62: seed the engine-system registry with the core entries, in the historical
|
||||
# emit order (SpriteAnim, Motion — Update; Light2D — Render), so a core game is
|
||||
# byte-identical; packages append via @EngineSystem.
|
||||
g_esys_comp = new []pointer; g_esys_fn = new []pointer; g_esys_phase = new []pointer
|
||||
push(g_esys_comp, "SpriteAnim"); push(g_esys_fn, "esys_spriteanim"); push(g_esys_phase, "Update")
|
||||
push(g_esys_comp, "Motion"); push(g_esys_fn, "esys_motion"); push(g_esys_phase, "Update")
|
||||
push(g_esys_comp, "Light2D"); push(g_esys_fn, "esys_light2d"); push(g_esys_phase, "Render")
|
||||
g_namespaces = new []pointer
|
||||
loaded_paths = new []pointer
|
||||
skipnl()
|
||||
g_game_name = "Ludic"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue