Merge Phase 6d: unify struct into property
This commit is contained in:
commit
c5bd50d544
15 changed files with 4054 additions and 4378 deletions
34
LANGUAGE.md
34
LANGUAGE.md
|
|
@ -22,8 +22,8 @@ A program is one `program` block containing declarations:
|
||||||
# doc-check: skip — illustrative: elided import list
|
# doc-check: skip — illustrative: elided import list
|
||||||
program Name {
|
program Name {
|
||||||
import ... # pull declarations in from another file
|
import ... # pull declarations in from another file
|
||||||
property ... # data (per entity)
|
property ... # a record of typed fields — a per-entity component, or a
|
||||||
struct ... # a plain record, not tied to an entity
|
# plain `new`-allocated record; its use decides which
|
||||||
model ... # a named entity KIND (bundle of properties)
|
model ... # a named entity KIND (bundle of properties)
|
||||||
const ... # compile-time constants
|
const ... # compile-time constants
|
||||||
fn ... # functions
|
fn ... # functions
|
||||||
|
|
@ -366,13 +366,24 @@ whole timeline), plus [`examples/toggle.ludic`](examples/toggle.ludic)
|
||||||
(enable/disable). Still to come: **scene** hooks (`@OnEnter`/`@OnExit`), which
|
(enable/disable). Still to come: **scene** hooks (`@OnEnter`/`@OnExit`), which
|
||||||
wait on `scene` support landing in the compiler.
|
wait on `scene` support landing in the compiler.
|
||||||
|
|
||||||
## Structs, arrays and slices
|
## Records (`property`), arrays and slices
|
||||||
|
|
||||||
`struct` is the aggregate that is *not* tied to an entity — a plain record, for
|
There is one record keyword, `property` — a named set of typed fields with
|
||||||
the data a program keeps outside the ECS.
|
defaults. How a property is *stored* follows from how it is *used*, so the same
|
||||||
|
declaration covers both ECS components and the plain records a program keeps
|
||||||
|
outside the ECS:
|
||||||
|
|
||||||
|
- listed in a `model` (or attached by `spawn`) → a **component**, stored in the
|
||||||
|
engine's per-entity arrays and bound in queries;
|
||||||
|
- constructed with **`new`** → a **heap record**, addressed by a pointer.
|
||||||
|
|
||||||
|
A program that only declares `property` records and functions — never a `model`
|
||||||
|
or `handler` — is not an ECS program at all: it gets no entity storage or
|
||||||
|
runtime, just the record layouts and `new`. (This is exactly how the Ludic
|
||||||
|
compiler is written in itself.)
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
struct Tok { kind: int = 0, line: int = 0, next: Tok }
|
property Tok { kind: int = 0, line: int = 0, next: Tok }
|
||||||
|
|
||||||
handler Lex phase Update {
|
handler Lex phase Update {
|
||||||
let t = new Tok # allocates; every field seeded from its default
|
let t = new Tok # allocates; every field seeded from its default
|
||||||
|
|
@ -380,11 +391,11 @@ handler Lex phase Update {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Struct values have **reference semantics**: a struct value is a pointer to the
|
A `new` record has **reference semantics**: the value is a pointer to the
|
||||||
object, so assigning or passing one shares it rather than copying.
|
object, so assigning or passing one shares it rather than copying.
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
struct Tok { kind: int = 0, line: int = 0, next: Tok }
|
property Tok { kind: int = 0, line: int = 0, next: Tok }
|
||||||
|
|
||||||
fn bump(t: Tok) -> void { t.kind = t.kind + 1 }
|
fn bump(t: Tok) -> void { t.kind = t.kind + 1 }
|
||||||
|
|
||||||
|
|
@ -398,7 +409,7 @@ handler Share phase Update {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Fields chain, so a struct can refer to its own type and be walked without
|
Fields chain, so a record can refer to its own type and be walked without
|
||||||
temporaries — which is what an AST or a linked list needs:
|
temporaries — which is what an AST or a linked list needs:
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
|
|
@ -648,8 +659,9 @@ are future work.
|
||||||
formatting lives in `build/ludic-fmt` instead). Output-path and IR flags are in
|
formatting lives in `build/ludic-fmt` instead). Output-path and IR flags are in
|
||||||
flux as the CLI front-end is rebuilt — check `ludicc` usage for the current set.
|
flux as the CLI front-end is rebuilt — check `ludicc` usage for the current set.
|
||||||
|
|
||||||
`struct` and array types, `break`/`continue`, and argv/stderr — once listed here
|
Records (`property` used with `new`) and array types, `break`/`continue`, and
|
||||||
as near-term — are now implemented and self-hosting; their lowerings are in
|
argv/stderr — once listed here as near-term — are now implemented and
|
||||||
|
self-hosting; their lowerings are in
|
||||||
[BOOTSTRAP.md](BOOTSTRAP.md) §4.
|
[BOOTSTRAP.md](BOOTSTRAP.md) §4.
|
||||||
|
|
||||||
## Scenes & layers
|
## Scenes & layers
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ const E_FLOAT: int = 40
|
||||||
const E_REC: int = 41
|
const E_REC: int = 41
|
||||||
const E_FINIT: int = 42
|
const E_FINIT: int = 42
|
||||||
|
|
||||||
struct Node {
|
property Node {
|
||||||
kind: int = 0
|
kind: int = 0
|
||||||
s: ptr = ptr_null() # name / operator / string / type-of-new
|
s: ptr = ptr_null() # name / operator / string / type-of-new
|
||||||
ival: int = 0 # int literal, bool, flags
|
ival: int = 0 # int literal, bool, flags
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
# module-level material (types, globals, string constants) into one buffer and
|
# module-level material (types, globals, string constants) into one buffer and
|
||||||
# function bodies into another, then prints them in order.
|
# function bodies into another, then prints them in order.
|
||||||
|
|
||||||
struct Buf { data: ptr = ptr_null(), len: int = 0, cap: int = 0 }
|
property Buf { data: ptr = ptr_null(), len: int = 0, cap: int = 0 }
|
||||||
|
|
||||||
fn buf_new() -> Buf {
|
fn buf_new() -> Buf {
|
||||||
let b = new Buf
|
let b = new Buf
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
# module header. Mirrors the pieces of compiler/back/ that this subset needs.
|
# module header. Mirrors the pieces of compiler/back/ that this subset needs.
|
||||||
# structs and slices are references, so every non-scalar type lowers to `ptr`.
|
# structs and slices are references, so every non-scalar type lowers to `ptr`.
|
||||||
|
|
||||||
struct Val { code: ptr = ptr_null(), ty: ptr = ptr_null() }
|
property Val { code: ptr = ptr_null(), ty: ptr = ptr_null() }
|
||||||
fn val(code: ptr, ty: ptr) -> Val { let v = new Val; v.code = code; v.ty = ty; return v }
|
fn val(code: ptr, ty: ptr) -> Val { let v = new Val; v.code = code; v.ty = ty; return v }
|
||||||
|
|
||||||
var head: Buf # module-level: types, globals, string constants
|
var head: Buf # module-level: types, globals, string constants
|
||||||
|
|
@ -66,17 +66,6 @@ fn llty(t: ptr) -> ptr {
|
||||||
fn is_slice_ty(t: ptr) -> bool { return peek8(t, 0) == 91 and peek8(t, 1) == 93 } # "[]"
|
fn is_slice_ty(t: ptr) -> bool { return peek8(t, 0) == 91 and peek8(t, 1) == 93 } # "[]"
|
||||||
fn slice_elem(t: ptr) -> ptr { return substr(t, 2, slen(t) - 2) }
|
fn slice_elem(t: ptr) -> ptr { return substr(t, 2, slen(t) - 2) }
|
||||||
|
|
||||||
fn find_struct(name: ptr) -> Node {
|
|
||||||
let i = 0
|
|
||||||
while i < len(prog) {
|
|
||||||
let d = prog[i]
|
|
||||||
if d.kind == N_STRUCT and streq(d.s, name) { return d }
|
|
||||||
i = i + 1
|
|
||||||
}
|
|
||||||
return ptr_null()
|
|
||||||
}
|
|
||||||
fn is_struct_ty(t: ptr) -> bool { return not ptr_is_null(find_struct(t)) }
|
|
||||||
|
|
||||||
fn find_arch(name: ptr) -> Node {
|
fn find_arch(name: ptr) -> Node {
|
||||||
let i = 0
|
let i = 0
|
||||||
while i < len(prog) { let d = prog[i]; if d.kind == N_ARCH and streq(d.s, name) { return d }; i = i + 1 }
|
while i < len(prog) { let d = prog[i]; if d.kind == N_ARCH and streq(d.s, name) { return d }; i = i + 1 }
|
||||||
|
|
@ -87,15 +76,10 @@ fn find_comp(name: ptr) -> Node {
|
||||||
while i < len(prog) { let d = prog[i]; if d.kind == N_COMP and streq(d.s, name) { return d }; i = i + 1 }
|
while i < len(prog) { let d = prog[i]; if d.kind == N_COMP and streq(d.s, name) { return d }; i = i + 1 }
|
||||||
return ptr_null()
|
return ptr_null()
|
||||||
}
|
}
|
||||||
# a struct or a component — both have %Str_/%Cmp_ layouts with named fields
|
# every record is a `property` with a %Cmp_ layout of named fields — whether it
|
||||||
fn layout_node(name: ptr) -> Node {
|
# is stored per-entity by the ECS or heap-allocated by `new` is a matter of use.
|
||||||
let s = find_struct(name); if not ptr_is_null(s) { return s }
|
fn layout_node(name: ptr) -> Node { return find_comp(name) }
|
||||||
return find_comp(name)
|
fn layout_ty(name: ptr) -> ptr { return sconcat("%Cmp_", name) }
|
||||||
}
|
|
||||||
fn layout_ty(name: ptr) -> ptr {
|
|
||||||
if not ptr_is_null(find_struct(name)) { return sconcat("%Str_", name) }
|
|
||||||
return sconcat("%Cmp_", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn field_index(s: Node, fname: ptr) -> int {
|
fn field_index(s: Node, fname: ptr) -> int {
|
||||||
let i = 0
|
let i = 0
|
||||||
|
|
|
||||||
|
|
@ -5,16 +5,22 @@
|
||||||
|
|
||||||
const MAX_ENT: int = 1024
|
const MAX_ENT: int = 1024
|
||||||
|
|
||||||
fn has_ecs() -> bool {
|
|
||||||
let i = 0
|
|
||||||
while i < len(prog) { let k = prog[i].kind; if k == N_COMP or k == N_SYS { return true }; i = i + 1 }
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
fn has_systems() -> bool {
|
fn has_systems() -> bool {
|
||||||
let i = 0
|
let i = 0
|
||||||
while i < len(prog) { if prog[i].kind == N_SYS { return true }; i = i + 1 }
|
while i < len(prog) { if prog[i].kind == N_SYS { return true }; i = i + 1 }
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
fn has_models() -> bool {
|
||||||
|
let i = 0
|
||||||
|
while i < len(prog) { if prog[i].kind == N_ARCH { return true }; i = i + 1 }
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
# Does this program run the ECS? A property alone no longer answers that — the
|
||||||
|
# same `property` keyword also declares plain `new`-allocated records (the merged
|
||||||
|
# `struct`). A program uses the ECS when it has a handler or a model; a tool that
|
||||||
|
# only declares record types and functions does not, and gets no entity storage,
|
||||||
|
# allocator, snapshot or runtime splice.
|
||||||
|
fn has_ecs() -> bool { return has_systems() or has_models() }
|
||||||
|
|
||||||
fn emit_ecs_storage() -> void {
|
fn emit_ecs_storage() -> void {
|
||||||
emith("@L_running = internal global i32 1\n")
|
emith("@L_running = internal global i32 1\n")
|
||||||
|
|
@ -28,12 +34,10 @@ fn emit_ecs_storage() -> void {
|
||||||
let i = 0
|
let i = 0
|
||||||
while i < len(prog) {
|
while i < len(prog) {
|
||||||
let c = prog[i]
|
let c = prog[i]
|
||||||
|
# per-entity storage for a property (its %Cmp_ layout is emitted in the
|
||||||
|
# header). Every property in an ECS program is a component today; a property
|
||||||
|
# used only via `new` would not need these, but no such program mixes the two.
|
||||||
if c.kind == N_COMP {
|
if c.kind == N_COMP {
|
||||||
emith(sconcat("%Cmp_", sconcat(c.s, " = type { ")))
|
|
||||||
if len(c.kids) == 0 { emith("i32") }
|
|
||||||
let f = 0
|
|
||||||
while f < len(c.kids) { if f > 0 { emith(", ") }; emith(llty(c.kids[f].ty)); f = f + 1 }
|
|
||||||
emith(" }\n")
|
|
||||||
emith(sconcat("@S_", sconcat(c.s, sconcat(" = internal global [", sconcat(me, sconcat(" x %Cmp_", sconcat(c.s, "] zeroinitializer\n")))))))
|
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")))))
|
emith(sconcat("@H_", sconcat(c.s, sconcat(" = internal global [", sconcat(me, " x i8] zeroinitializer\n")))))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -76,12 +76,14 @@ fn emit_header() -> void {
|
||||||
emith("@.gametitle = private unnamed_addr constant [")
|
emith("@.gametitle = private unnamed_addr constant [")
|
||||||
emith(itoa(slen(g_game_name) + 1)); emith(" x i8] c\""); emith(g_game_name); emith("\\00\"\n")
|
emith(itoa(slen(g_game_name) + 1)); emith(" x i8] c\""); emith(g_game_name); emith("\\00\"\n")
|
||||||
emith("%LSlice = type { ptr, i32, i32 }\n")
|
emith("%LSlice = type { ptr, i32, i32 }\n")
|
||||||
# struct layouts
|
# property layouts — a %Cmp_ record of named fields, emitted here so `new`
|
||||||
|
# works whether or not the program runs the ECS. The per-entity @S_/@H_ arrays
|
||||||
|
# are separate (emit_ecs_storage), emitted only for a program that runs the ECS.
|
||||||
let i = 0
|
let i = 0
|
||||||
while i < len(prog) {
|
while i < len(prog) {
|
||||||
let d = prog[i]
|
let d = prog[i]
|
||||||
if d.kind == N_STRUCT {
|
if d.kind == N_COMP {
|
||||||
emith("%Str_"); emith(d.s); emith(" = type { ")
|
emith(layout_ty(d.s)); emith(" = type { ")
|
||||||
if len(d.kids) == 0 { emith("i32") }
|
if len(d.kids) == 0 { emith("i32") }
|
||||||
let f = 0
|
let f = 0
|
||||||
while f < len(d.kids) {
|
while f < len(d.kids) {
|
||||||
|
|
|
||||||
|
|
@ -8,15 +8,16 @@ fn emit_sizeof(llt: ptr) -> ptr {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn emit_new_struct(name: ptr) -> Val {
|
fn emit_new_struct(name: ptr) -> Val {
|
||||||
let s = find_struct(name)
|
let s = layout_node(name) # a struct or a property — same shape
|
||||||
if ptr_is_null(s) { perr("unknown struct in new") }
|
if ptr_is_null(s) { perr("unknown record type in new") }
|
||||||
let sz = emit_sizeof(sconcat("%Str_", name))
|
let lty = layout_ty(name)
|
||||||
|
let sz = emit_sizeof(lty)
|
||||||
let obj = emit_bind(sconcat("call ptr @malloc(i64 ", sconcat(sz, ")")))
|
let obj = emit_bind(sconcat("call ptr @malloc(i64 ", sconcat(sz, ")")))
|
||||||
let f = 0
|
let f = 0
|
||||||
while f < len(s.kids) {
|
while f < len(s.kids) {
|
||||||
let fd = s.kids[f]
|
let fd = s.kids[f]
|
||||||
let addr = nreg()
|
let addr = nreg()
|
||||||
emit(" "); emit(addr); emit(" = getelementptr inbounds %Str_"); emit(name)
|
emit(" "); emit(addr); emit(" = getelementptr inbounds "); emit(lty)
|
||||||
emit(", ptr "); emit(obj); emit(", i32 0, i32 "); emit(itoa(f)); emit("\n")
|
emit(", ptr "); emit(obj); emit(", i32 0, i32 "); emit(itoa(f)); emit("\n")
|
||||||
let lt = llty(fd.ty)
|
let lt = llty(fd.ty)
|
||||||
let v = "0"
|
let v = "0"
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ const TK_NL: int = 4
|
||||||
const TK_EOF: int = 5
|
const TK_EOF: int = 5
|
||||||
const TK_FLOAT: int = 6
|
const TK_FLOAT: int = 6
|
||||||
|
|
||||||
struct Tok { kind: int = 0, text: ptr = ptr_null(), ival: int = 0, line: int = 0 }
|
property Tok { kind: int = 0, text: ptr = ptr_null(), ival: int = 0, line: int = 0 }
|
||||||
|
|
||||||
var toks: []Tok
|
var toks: []Tok
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -224,14 +224,6 @@ fn stmt() -> Node {
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---- declarations ----------------------------------------------------------
|
# ---- declarations ----------------------------------------------------------
|
||||||
fn parse_struct() -> Node {
|
|
||||||
pi = pi + 1; let n = node(N_STRUCT); n.s = eat_id(); skipnl(); eat_op("{")
|
|
||||||
while true { skipnl(); if is_op("}") { break }
|
|
||||||
let f = node(N_FIELD); f.s = eat_id(); eat_op(":"); f.ty = ptype()
|
|
||||||
if is_op("=") { pi = pi + 1; f.a = expr() }
|
|
||||||
push(n.kids, f); if is_op(",") { pi = pi + 1 } }
|
|
||||||
eat_op("}"); return n
|
|
||||||
}
|
|
||||||
fn parse_var() -> Node {
|
fn parse_var() -> Node {
|
||||||
pi = pi + 1; let n = node(N_VAR); n.s = eat_id(); eat_op(":"); n.ty = ptype()
|
pi = pi + 1; let n = node(N_VAR); n.s = eat_id(); eat_op(":"); n.ty = ptype()
|
||||||
if is_op("=") { pi = pi + 1; n.a = expr() }
|
if is_op("=") { pi = pi + 1; n.a = expr() }
|
||||||
|
|
@ -311,7 +303,6 @@ fn parse_one_decl() -> void {
|
||||||
do_import(rel)
|
do_import(rel)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if is_id("struct") { push(prog, parse_struct()); return }
|
|
||||||
if is_id("enum") { push(prog, parse_enum()); return }
|
if is_id("enum") { push(prog, parse_enum()); return }
|
||||||
if is_id("property") { push(prog, parse_component()); return }
|
if is_id("property") { push(prog, parse_component()); return }
|
||||||
if is_id("model") { push(prog, parse_archetype()); return }
|
if is_id("model") { push(prog, parse_archetype()); return }
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
program T {
|
program T {
|
||||||
struct P { x: int = 0, y: int = 7, next: P }
|
property P { x: int = 0, y: int = 7, next: P }
|
||||||
fn bump(p: P) -> void { p.x = p.x + 100 }
|
fn bump(p: P) -> void { p.x = p.x + 100 }
|
||||||
entry {
|
entry {
|
||||||
let a = new P
|
let a = new P
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ object LudicTokens {
|
||||||
*/
|
*/
|
||||||
object LudicVocabulary {
|
object LudicVocabulary {
|
||||||
val DECL = setOf(
|
val DECL = setOf(
|
||||||
"program", "import", "property", "struct", "model", "enum", "ui",
|
"program", "import", "property", "model", "enum", "ui",
|
||||||
"const", "var", "fn", "extern", "handler", "entry"
|
"const", "var", "fn", "extern", "handler", "entry"
|
||||||
)
|
)
|
||||||
val CLAUSE = setOf(
|
val CLAUSE = setOf(
|
||||||
|
|
|
||||||
|
|
@ -165,7 +165,7 @@
|
||||||
{ "name": "keyword.operator.logical.ludic", "match": "\\b(and|or|not)\\b" },
|
{ "name": "keyword.operator.logical.ludic", "match": "\\b(and|or|not)\\b" },
|
||||||
{ "name": "keyword.other.clause.ludic", "match": "\\b(phase|query|on)\\b" },
|
{ "name": "keyword.other.clause.ludic", "match": "\\b(phase|query|on)\\b" },
|
||||||
{ "name": "keyword.other.ludic", "match": "\\b(import|extern)\\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": "storage.type.ludic", "match": "\\b(program|property|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" },
|
{ "name": "support.type.primitive.ludic", "match": "\\b(int|fixed|bool|entity|str|ptr|void)\\b" },
|
||||||
{ "name": "constant.language.boolean.ludic", "match": "\\b(true|false)\\b" },
|
{ "name": "constant.language.boolean.ludic", "match": "\\b(true|false)\\b" },
|
||||||
{ "name": "constant.language.phase.ludic", "match": "\\b(Start|Input|FixedUpdate|Update|LateUpdate|Render)\\b" }
|
{ "name": "constant.language.phase.ludic", "match": "\\b(Start|Input|FixedUpdate|Update|LateUpdate|Render)\\b" }
|
||||||
|
|
|
||||||
|
|
@ -165,7 +165,7 @@
|
||||||
{ "name": "keyword.operator.logical.ludic", "match": "\\b(and|or|not)\\b" },
|
{ "name": "keyword.operator.logical.ludic", "match": "\\b(and|or|not)\\b" },
|
||||||
{ "name": "keyword.other.clause.ludic", "match": "\\b(phase|query|on)\\b" },
|
{ "name": "keyword.other.clause.ludic", "match": "\\b(phase|query|on)\\b" },
|
||||||
{ "name": "keyword.other.ludic", "match": "\\b(import|extern)\\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": "storage.type.ludic", "match": "\\b(program|property|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" },
|
{ "name": "support.type.primitive.ludic", "match": "\\b(int|fixed|bool|entity|str|ptr|void)\\b" },
|
||||||
{ "name": "constant.language.boolean.ludic", "match": "\\b(true|false)\\b" },
|
{ "name": "constant.language.boolean.ludic", "match": "\\b(true|false)\\b" },
|
||||||
{ "name": "constant.language.phase.ludic", "match": "\\b(Start|Input|FixedUpdate|Update|LateUpdate|Render)\\b" }
|
{ "name": "constant.language.phase.ludic", "match": "\\b(Start|Input|FixedUpdate|Update|LateUpdate|Render)\\b" }
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ typedef struct {
|
||||||
* mirror the compiler's parser: anything parse_decl() dispatches on is a
|
* mirror the compiler's parser: anything parse_decl() dispatches on is a
|
||||||
* declaration keyword, anything stmt() dispatches on is a statement keyword. */
|
* declaration keyword, anything stmt() dispatches on is a statement keyword. */
|
||||||
static const char* LUDIC_KW_DECL[] = {
|
static const char* LUDIC_KW_DECL[] = {
|
||||||
"program","import","property","struct","model","enum","ui",
|
"program","import","property","model","enum","ui",
|
||||||
"const","var","fn","extern","handler","entry", 0
|
"const","var","fn","extern","handler","entry", 0
|
||||||
};
|
};
|
||||||
static const char* LUDIC_KW_CLAUSE[] = {
|
static const char* LUDIC_KW_CLAUSE[] = {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue