Merge enable/disable + @OnEnable/@OnDisable lifecycle hooks
This commit is contained in:
commit
5ecd300d16
17 changed files with 4772 additions and 3911 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -15,3 +15,4 @@ tools/editors/jetbrains/build/
|
|||
|
||||
# IntelliJ plugin SDK sandbox (tools/editors/jetbrains)
|
||||
.intellijPlatform/
|
||||
.idea
|
||||
|
|
|
|||
40
LANGUAGE.md
40
LANGUAGE.md
|
|
@ -323,15 +323,46 @@ boot ── @OnStart ─▶ spawn ── @OnAttach(P), @OnSpawn(M) ─▶ …
|
|||
@OnQuit handler Save { save() } # once, at shutdown
|
||||
```
|
||||
|
||||
**Enable / disable.** `enable` and `disable` are statements that flip something on
|
||||
or off without destroying it. There are three scopes:
|
||||
|
||||
- **`disable P on e` / `enable P on e`** — one *property* on one entity. Disabling
|
||||
clears the entity's has-flag, so queries stop matching it, but the field values
|
||||
stay in storage — a later `enable` restores them untouched. `@OnDisable(P)` and
|
||||
`@OnEnable(P)` are handler annotations that run at the toggle point with the
|
||||
property bound by name (like a one-entity `@OnSpawn`).
|
||||
- **`disable Model` / `enable Model`** — a whole *model*. Its entities drop out of
|
||||
every query while disabled; the entities and their data are left alone.
|
||||
- **`disable Handler` / `enable Handler`** — a *handler*. It stops being called
|
||||
each phase while disabled, and resumes on `enable`.
|
||||
|
||||
Each toggle is one global flag flip (or one has-flag store), so nothing is copied
|
||||
or freed — enable/disable is cheap and fully reversible.
|
||||
|
||||
```ludic
|
||||
# doc-check: skip — enable/disable
|
||||
@OnDisable(Shield) handler Down { play("shield_break.wav") }
|
||||
@OnEnable(Shield) handler Up { play("shield_up.wav") }
|
||||
|
||||
disable Shield on self() # this entity loses its shield; data kept for later
|
||||
enable Shield on self() # shield back, amount unchanged
|
||||
disable Gravity # a whole model sits out every query
|
||||
disable AiThink # a handler stops running each phase
|
||||
```
|
||||
|
||||
See [`examples/toggle.ludic`](examples/toggle.ludic) for all three scopes in one
|
||||
frame. Still to come: **`@OnDetach`** (the paired hook for a property leaving,
|
||||
needing the same per-property runtime dispatch as despawn).
|
||||
|
||||
**`@Handles` — the handlers a program drives.** Written in front of the
|
||||
`program`, `@Handles(Move)` names the handlers it uses. It parses and reads as
|
||||
documentation; every declared handler still runs (registration is implicit).
|
||||
|
||||
See [`examples/annotations.ludic`](examples/annotations.ludic) (queries, computed
|
||||
fields, one hook) and [`examples/lifecycle.ludic`](examples/lifecycle.ludic) (the
|
||||
whole timeline). Still to come: **`@OnDetach`** (needs the same runtime kind
|
||||
dispatch as despawn, per property) and **scene** hooks (`@OnEnter`/`@OnExit`),
|
||||
which wait on `scene` support landing in the compiler.
|
||||
whole timeline), plus [`examples/toggle.ludic`](examples/toggle.ludic)
|
||||
(enable/disable). Still to come: **scene** hooks (`@OnEnter`/`@OnExit`), which
|
||||
wait on `scene` support landing in the compiler.
|
||||
|
||||
## Structs, arrays and slices
|
||||
|
||||
|
|
@ -425,7 +456,8 @@ another `.ludic` file (see `examples/lib/`).
|
|||
`let x = expr` · `x = expr` (`+= -= *= /=`) · `if/else` · `when cond { }`
|
||||
(if-without-else) · `while cond { }` · `for i in a .. b { }` (numeric range) ·
|
||||
`for (…) in query […] { }` · `break` · `continue` · `return` · `spawn` ·
|
||||
`despawn` · `match` · `machine`.
|
||||
`despawn` · `enable` / `disable` (a property `on e`, a model, or a handler) ·
|
||||
`match` · `machine`.
|
||||
|
||||
**Statements are separated by a newline or `;`** (both lex to the same separator
|
||||
token). Two statements may not sit adjacent with only spaces between them — the
|
||||
|
|
|
|||
37
examples/toggle.ludic
Normal file
37
examples/toggle.ludic
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
# toggle.ludic — enable/disable at the three ECS scopes, each a plain statement.
|
||||
# Disabling never destroys data: a property's values persist in storage, so a
|
||||
# later `enable` restores them. Queries already skip a cleared flag, so nothing
|
||||
# else in the language needs to know. Running it prints: 6 0 7 1 0
|
||||
#
|
||||
# disable P on e clears one entity's has-flag (@OnDisable / @OnEnable fire)
|
||||
# disable Model flips the model's enabled flag (its entities drop from queries)
|
||||
# disable Handler flips the handler's enabled flag (it stops running each phase)
|
||||
program Toggles {
|
||||
property Health { hp: int = 0, max: int = 100 }
|
||||
property Shield { amount: int = 0 }
|
||||
model Player { Health, Shield }
|
||||
|
||||
# hooks run at the toggle point with the property's data bound by name
|
||||
@OnDisable(Shield) handler Down { print_int(Shield.amount + 1) } # 5 + 1
|
||||
@OnEnable(Shield) handler Up { print_int(Shield.amount + 2) } # 5 + 2
|
||||
|
||||
handler Seed phase Start { spawn Player { Health { max: 50 }, Shield { amount: 5 } } }
|
||||
|
||||
handler Run phase Render {
|
||||
for (e) in query [Player] { disable Shield on self() } # @OnDisable -> 6
|
||||
let n = 0
|
||||
for (s) in query [Shield] { n += 1 }
|
||||
print_int(n) # 0 — no live Shield now
|
||||
|
||||
for (e) in query [Player] { enable Shield on self() } # @OnEnable -> 7, data intact
|
||||
n = 0
|
||||
for (s) in query [Shield] { n += 1 }
|
||||
print_int(n) # 1 — amount is still 5
|
||||
|
||||
disable Player # whole model off
|
||||
n = 0
|
||||
for (p) in query [Player] { n += 1 }
|
||||
print_int(n) # 0 — model disabled
|
||||
quit()
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ const N_ARCH: int = 10
|
|||
const N_EXTERN: int = 11
|
||||
const N_UI: int = 12
|
||||
const N_ENUM: int = 27 # enum Name { A, B, ... } — named int constants
|
||||
const S_TOGGLE: int = 28 # enable/disable (ival: 1=enable 0=disable; s=target; a=entity or null)
|
||||
# statements
|
||||
const S_LET: int = 10
|
||||
const S_ASSIGN: int = 11
|
||||
|
|
|
|||
|
|
@ -199,6 +199,26 @@ fn onattach_body(prop: ptr) -> Node {
|
|||
return ptr_null()
|
||||
}
|
||||
|
||||
# @OnEnable(Property) / @OnDisable(Property): run when a property is toggled on an
|
||||
# entity, with the property bound by name.
|
||||
var g_onenable: []Node
|
||||
var g_ondisable: []Node
|
||||
fn register_onenable(prop: ptr, body: Node) -> void { let n = node(N_BLOCK); n.s = prop; n.a = body; push(g_onenable, n) }
|
||||
fn register_ondisable(prop: ptr, body: Node) -> void { let n = node(N_BLOCK); n.s = prop; n.a = body; push(g_ondisable, n) }
|
||||
fn onenable_body(prop: ptr) -> Node {
|
||||
let i = 0
|
||||
while i < len(g_onenable) { if streq(g_onenable[i].s, prop) { return g_onenable[i].a }; i = i + 1 }
|
||||
return ptr_null()
|
||||
}
|
||||
fn ondisable_body(prop: ptr) -> Node {
|
||||
let i = 0
|
||||
while i < len(g_ondisable) { if streq(g_ondisable[i].s, prop) { return g_ondisable[i].a }; i = i + 1 }
|
||||
return ptr_null()
|
||||
}
|
||||
|
||||
# is `name` a model (archetype)? — chooses model-vs-handler for a bare enable/disable
|
||||
fn is_model(name: ptr) -> bool { return find_arch_id(name) > 0 }
|
||||
|
||||
# local variable environment
|
||||
fn loc_reset() -> void { nloc = 0 }
|
||||
fn loc_push(name: ptr, r: ptr, ty: ptr) -> void {
|
||||
|
|
|
|||
|
|
@ -37,6 +37,9 @@ fn emit_ecs_storage() -> void {
|
|||
emith(sconcat("@S_", sconcat(c.s, sconcat(" = internal global [", sconcat(me, sconcat(" x %Cmp_", sconcat(c.s, "] zeroinitializer\n")))))))
|
||||
emith(sconcat("@H_", sconcat(c.s, sconcat(" = internal global [", sconcat(me, " x i8] zeroinitializer\n")))))
|
||||
}
|
||||
# one enabled-flag global per model and per handler (default enabled)
|
||||
if c.kind == N_ARCH { emith(sconcat("@ME_", sconcat(c.s, " = internal global i32 1\n"))) }
|
||||
if c.kind == N_SYS { emith(sconcat("@HE_", sconcat(c.s, " = internal global i32 1\n"))) }
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,14 @@ fn emit_calls_for_phase(phase: ptr) -> void {
|
|||
let i = 0
|
||||
while i < len(prog) {
|
||||
let d = prog[i]
|
||||
if d.kind == N_SYS and streq(d.ty, phase) { emit(" call void @sys_"); emit(d.s); emit("()\n") }
|
||||
if d.kind == N_SYS and streq(d.ty, phase) { # skip a disabled handler
|
||||
let he = emit_bind(sconcat("load i32, ptr @HE_", d.s))
|
||||
let hc = emit_bind(sconcat("icmp ne i32 ", sconcat(he, ", 0")))
|
||||
let run = lbl("hrun"); let skip = lbl("hskip")
|
||||
emit(" br i1 "); emit(hc); emit(", label %"); emit(run); emit(", label %"); emit(skip); emit("\n")
|
||||
emit(run); emit(":\n call void @sys_"); emit(d.s); emit("()\n")
|
||||
emit(" br label %"); emit(skip); emit("\n"); emit(skip); emit(":\n")
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,10 @@ fn emit_query(st: Node) -> void {
|
|||
if ak > 0 {
|
||||
let kp = nreg(); emit(" "); emit(kp); emit(" = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_kind, i32 0, i32 "); emit(i1); emit("\n")
|
||||
let kv = emit_bind(sconcat("load i32, ptr ", kp))
|
||||
ok = emit_bind(sconcat("icmp eq i32 ", sconcat(kv, sconcat(", ", itoa(ak)))))
|
||||
let kok = emit_bind(sconcat("icmp eq i32 ", sconcat(kv, sconcat(", ", itoa(ak)))))
|
||||
let mev = emit_bind(sconcat("load i32, ptr @ME_", tm.s)) # model-enabled flag
|
||||
let meok = emit_bind(sconcat("icmp ne i32 ", sconcat(mev, ", 0")))
|
||||
ok = emit_bind(sconcat("and i1 ", sconcat(kok, sconcat(", ", meok))))
|
||||
} else {
|
||||
let hp = nreg(); emit(" "); emit(hp); emit(" = getelementptr inbounds ["); emit(me); emit(" x i8], ptr @H_"); emit(tm.s); emit(", i32 0, i32 "); emit(i1); emit("\n")
|
||||
let hv = emit_bind(sconcat("load i8, ptr ", hp))
|
||||
|
|
|
|||
|
|
@ -115,3 +115,30 @@ fn emit_despawn(st: Node) -> void {
|
|||
}
|
||||
emit(" call void @L_free_entity(i32 "); emit(v.code); emit(")\n")
|
||||
}
|
||||
|
||||
# enable/disable. `<P> on <e>` toggles a property's has-flag on an entity (its
|
||||
# data persists, so re-enabling restores it, and queries already skip a cleared
|
||||
# flag). A bare `<Model>` / `<Handler>` flips a global enabled flag.
|
||||
fn emit_toggle(st: Node) -> void {
|
||||
let val = "0"; if st.ival == 1 { val = "1" }
|
||||
if not ptr_is_null(st.a) {
|
||||
let ev = emit_expr(st.a)
|
||||
let me = itoa(MAX_ENT)
|
||||
let hp = nreg(); emit(" "); emit(hp); emit(" = getelementptr inbounds ["); emit(me); emit(" x i8], ptr @H_"); emit(st.s); emit(", i32 0, i32 "); emit(ev.code); emit("\n")
|
||||
emit(" store i8 "); emit(val); emit(", ptr "); emit(hp); emit("\n")
|
||||
let hb: Node = ptr_null()
|
||||
if st.ival == 1 { hb = onenable_body(st.s) } else { hb = ondisable_body(st.s) }
|
||||
if not ptr_is_null(hb) {
|
||||
let save = nloc
|
||||
let slot = nreg(); emit(" "); emit(slot); emit(" = getelementptr inbounds ["); emit(me); emit(" x %Cmp_"); emit(st.s); emit("], ptr @S_"); emit(st.s); emit(", i32 0, i32 "); emit(ev.code); emit("\n")
|
||||
let vslot = emit_alloca("ptr")
|
||||
emit(" store ptr "); emit(slot); emit(", ptr "); emit(vslot); emit("\n")
|
||||
loc_push(st.s, vslot, st.s)
|
||||
emit_block(hb)
|
||||
nloc = save
|
||||
}
|
||||
} else {
|
||||
let g = "@HE_"; if is_model(st.s) { g = "@ME_" } # model vs handler
|
||||
emit(" store i32 "); emit(val); emit(", ptr "); emit(g); emit(st.s); emit("\n")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -179,6 +179,7 @@ fn emit_stmt(st: Node) -> void {
|
|||
if st.kind == S_QUERY { emit_query(st); return }
|
||||
if st.kind == S_SPAWN { emit_spawn(st); return }
|
||||
if st.kind == S_DESPAWN { emit_despawn(st); return }
|
||||
if st.kind == S_TOGGLE { emit_toggle(st); return }
|
||||
if st.kind == S_MACHINE { emit_machine(st); return }
|
||||
if st.kind == S_BECOME { emit_become(st); return }
|
||||
if st.kind == S_EXPR { let v = emit_expr(st.a); return }
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -200,6 +200,12 @@ fn stmt() -> Node {
|
|||
if streq(t.text, "become") { pi = pi + 1; let n = node(S_BECOME); n.s = eat_id(); return n }
|
||||
if streq(t.text, "enter") { pi = pi + 1; let n = node(S_BECOME); n.s = eat_id(); n.ival = 1; return n }
|
||||
if streq(t.text, "despawn") { pi = pi + 1; let n = node(S_DESPAWN); n.a = expr(); return n }
|
||||
if streq(t.text, "enable") or streq(t.text, "disable") {
|
||||
let en = 0; if streq(t.text, "enable") { en = 1 }
|
||||
pi = pi + 1; let n = node(S_TOGGLE); n.ival = en; n.s = eat_id() # `enable P on e` / `disable Model` / `disable Handler`
|
||||
if is_id("on") { pi = pi + 1; n.a = expr() } # property on an entity
|
||||
return n
|
||||
}
|
||||
if streq(t.text, "break") { pi = pi + 1; return node(S_BREAK) }
|
||||
if streq(t.text, "continue") { pi = pi + 1; return node(S_CONTINUE) }
|
||||
if streq(t.text, "match") {
|
||||
|
|
@ -285,6 +291,8 @@ fn parse_one_decl() -> void {
|
|||
let onspawn_model: ptr = ptr_null()
|
||||
let ondespawn_model: ptr = ptr_null()
|
||||
let onattach_prop: ptr = ptr_null()
|
||||
let onenable_prop: ptr = ptr_null()
|
||||
let ondisable_prop: ptr = ptr_null()
|
||||
let hook_phase: ptr = ptr_null() # @OnStart / @OnQuit override the phase
|
||||
while is_op("@") {
|
||||
pi = pi + 1; let a = eat_id() # collect a leading @annotation
|
||||
|
|
@ -293,10 +301,12 @@ fn parse_one_decl() -> void {
|
|||
else { if streq(a, "OnSpawn") { eat_op("("); onspawn_model = eat_id(); eat_op(")") }
|
||||
else { if streq(a, "OnDespawn") { eat_op("("); ondespawn_model = eat_id(); eat_op(")") }
|
||||
else { if streq(a, "OnAttach") { eat_op("("); onattach_prop = eat_id(); eat_op(")") }
|
||||
else { if streq(a, "OnEnable") { eat_op("("); onenable_prop = eat_id(); eat_op(")") }
|
||||
else { if streq(a, "OnDisable") { eat_op("("); ondisable_prop = eat_id(); eat_op(")") }
|
||||
else { if streq(a, "OnStart") { hook_phase = "Start" } # boot
|
||||
else { if streq(a, "OnQuit") { hook_phase = "OnQuit" } # shutdown
|
||||
else { if is_op("(") { let 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
|
||||
|
|
@ -315,6 +325,8 @@ fn parse_one_decl() -> void {
|
|||
if not ptr_is_null(onspawn_model) { register_onspawn(onspawn_model, h.a); return } # spawn hook
|
||||
if not ptr_is_null(ondespawn_model) { register_ondespawn(ondespawn_model, h.a); return } # despawn hook
|
||||
if not ptr_is_null(onattach_prop) { register_onattach(onattach_prop, h.a); return } # attach hook
|
||||
if not ptr_is_null(onenable_prop) { register_onenable(onenable_prop, h.a); return } # enable hook
|
||||
if not ptr_is_null(ondisable_prop) { register_ondisable(ondisable_prop, h.a); return } # disable hook
|
||||
if not ptr_is_null(hook_phase) { h.ty = hook_phase } # @OnStart/@OnQuit
|
||||
if not ptr_is_null(qspec) { # @Queries wraps the body in its S_QUERY
|
||||
qspec.a = h.a
|
||||
|
|
@ -363,6 +375,8 @@ fn parse_program() -> void {
|
|||
g_onspawn = new []Node
|
||||
g_ondespawn = new []Node
|
||||
g_onattach = new []Node
|
||||
g_onenable = new []Node
|
||||
g_ondisable = new []Node
|
||||
loaded_paths = new []ptr
|
||||
skipnl()
|
||||
g_game_name = "Ludic"
|
||||
|
|
|
|||
5
test.sh
5
test.sh
|
|
@ -61,6 +61,11 @@ if ./selfhost/game-build.sh build/ludicc examples/lifecycle.ludic "/tmp/ludic_li
|
|||
&& [ "$(/tmp/ludic_life </dev/null | tr '\n' ' ')" = "1 700 50 950 2 " ]; then
|
||||
ok "lifecycle.ludic (@OnStart/@OnAttach/@OnSpawn/@OnDespawn/@OnQuit in order)"
|
||||
else bad "lifecycle: $(tail -1 /tmp/life.out)"; fi
|
||||
# enable/disable at property (with @OnDisable/@OnEnable), model, and query scopes.
|
||||
if ./selfhost/game-build.sh build/ludicc examples/toggle.ludic "/tmp/ludic_tog" >/tmp/tog.out 2>&1 \
|
||||
&& [ "$(/tmp/ludic_tog </dev/null | tr '\n' ' ')" = "6 0 7 1 0 " ]; then
|
||||
ok "toggle.ludic (enable/disable + @OnDisable/@OnEnable across property/model)"
|
||||
else bad "toggle: $(tail -1 /tmp/tog.out)"; fi
|
||||
# --- Toolchain-agent CLI smoke tests append below this line ---
|
||||
echo "== self-hosted front-end binaries (ludicc / ludic) =="
|
||||
# The two commands are one multi-call native binary built from the seed with
|
||||
|
|
|
|||
|
|
@ -45,12 +45,12 @@ object LudicVocabulary {
|
|||
"const", "var", "fn", "extern", "handler", "entry"
|
||||
)
|
||||
val CLAUSE = setOf(
|
||||
"phase", "query", "reads", "writes"
|
||||
"phase", "query", "reads", "writes", "on"
|
||||
)
|
||||
val STMT = setOf(
|
||||
"let", "return", "if", "else", "when", "while", "for", "in", "spawn", "despawn",
|
||||
"match", "machine", "state", "become", "enter", "where", "and", "or", "not",
|
||||
"break", "continue", "new"
|
||||
"enable", "disable", "match", "machine", "state", "become", "enter", "where",
|
||||
"and", "or", "not", "break", "continue", "new"
|
||||
)
|
||||
val PRIMITIVES = setOf("int", "fixed", "bool", "entity", "str", "ptr", "void")
|
||||
val PHASES = setOf("Start", "Input", "FixedUpdate", "Update", "LateUpdate", "Render")
|
||||
|
|
|
|||
|
|
@ -161,9 +161,9 @@
|
|||
},
|
||||
"keyword": {
|
||||
"patterns": [
|
||||
{ "name": "keyword.control.ludic", "match": "\\b(if|else|when|while|for|in|match|machine|become|enter|return|spawn|despawn|where|break|continue|new)\\b" },
|
||||
{ "name": "keyword.control.ludic", "match": "\\b(if|else|when|while|for|in|match|machine|become|enter|return|spawn|despawn|enable|disable|where|break|continue|new)\\b" },
|
||||
{ "name": "keyword.operator.logical.ludic", "match": "\\b(and|or|not)\\b" },
|
||||
{ "name": "keyword.other.clause.ludic", "match": "\\b(phase|query|reads|writes)\\b" },
|
||||
{ "name": "keyword.other.clause.ludic", "match": "\\b(phase|query|reads|writes|on)\\b" },
|
||||
{ "name": "keyword.other.ludic", "match": "\\b(import|extern)\\b" },
|
||||
{ "name": "storage.type.ludic", "match": "\\b(program|property|struct|model|enum|ui|const|var|let|fn|handler|entry|state)\\b" },
|
||||
{ "name": "support.type.primitive.ludic", "match": "\\b(int|fixed|bool|entity|str|ptr|void)\\b" },
|
||||
|
|
|
|||
|
|
@ -161,9 +161,9 @@
|
|||
},
|
||||
"keyword": {
|
||||
"patterns": [
|
||||
{ "name": "keyword.control.ludic", "match": "\\b(if|else|when|while|for|in|match|machine|become|enter|return|spawn|despawn|where|break|continue|new)\\b" },
|
||||
{ "name": "keyword.control.ludic", "match": "\\b(if|else|when|while|for|in|match|machine|become|enter|return|spawn|despawn|enable|disable|where|break|continue|new)\\b" },
|
||||
{ "name": "keyword.operator.logical.ludic", "match": "\\b(and|or|not)\\b" },
|
||||
{ "name": "keyword.other.clause.ludic", "match": "\\b(phase|query|reads|writes)\\b" },
|
||||
{ "name": "keyword.other.clause.ludic", "match": "\\b(phase|query|reads|writes|on)\\b" },
|
||||
{ "name": "keyword.other.ludic", "match": "\\b(import|extern)\\b" },
|
||||
{ "name": "storage.type.ludic", "match": "\\b(program|property|struct|model|enum|ui|const|var|let|fn|handler|entry|state)\\b" },
|
||||
{ "name": "support.type.primitive.ludic", "match": "\\b(int|fixed|bool|entity|str|ptr|void)\\b" },
|
||||
|
|
|
|||
|
|
@ -56,19 +56,19 @@ static const char* LUDIC_KW_DECL[] = {
|
|||
"const","var","fn","extern","handler","entry", 0
|
||||
};
|
||||
static const char* LUDIC_KW_CLAUSE[] = {
|
||||
"phase","query","reads","writes", 0
|
||||
"phase","query","reads","writes","on", 0
|
||||
};
|
||||
/* Documented design targets the self-hosted parser does not accept yet. Kept
|
||||
* out of the highlighted vocabulary (they would read as working keywords) until
|
||||
* they are implemented; check-vocabulary.py verifies the lists above are a
|
||||
* subset of what selfhost/parse*.ludic actually dispatches on. */
|
||||
static const char* LUDIC_KW_RESERVED[] = {
|
||||
"scene","layer","on","start", 0
|
||||
"scene","layer","start", 0
|
||||
};
|
||||
static const char* LUDIC_KW_STMT[] = {
|
||||
"let","return","if","else","when","while","for","in","spawn","despawn",
|
||||
"match","machine","state","become","enter","where","and","or","not",
|
||||
"break","continue","new", 0
|
||||
"enable","disable","match","machine","state","become","enter","where",
|
||||
"and","or","not","break","continue","new", 0
|
||||
};
|
||||
static const char* LUDIC_TYPES[] = {
|
||||
"int","fixed","bool","entity","str","ptr","void", 0
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue