parser: an attribute before export is kept - @ToClients export event E was a local event (the attributes read in front of export were dropped when the declaration was parsed afresh one call down), as were @Sync / @Owned / @Server / @On / @Public ... before export; and export @ToClients event was refused. Attributes and export now read in either order into one declaration, and a wrapper marks what it added exported (g_at_keep goes); examples/networking/net_export and three schema cases (added, not run); reseeded, bootstrap-cfree fixpoint holds
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
64adb052b4
commit
26b986de5b
7 changed files with 89034 additions and 88841 deletions
7
changes/attrs-before-export.md
Normal file
7
changes/attrs-before-export.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
bump: patch
|
||||
type: fix
|
||||
**An attribute before `export` is kept.** `@ToClients export event E`, `@Sync export property P`,
|
||||
`@Owned export model M` and the rest lost the attributes written in front of `export`: the parser read
|
||||
them, then parsed the declaration afresh and forgot them - so an exported remote event was silently
|
||||
local; and `export @ToClients event` was refused outright. Attributes and `export` now read in
|
||||
either order into the same declaration.
|
||||
18
examples/networking/net_export.ludic
Normal file
18
examples/networking/net_export.ludic
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# net_export.ludic - attributes and `export` in either order: `@ToClients export event` is as remote
|
||||
# as `export @ToClients event`, and `@Sync export property` / `@Owned export model` keep theirs.
|
||||
# (Before, the attributes read in front of `export` were dropped, and the event went local unsaid.)
|
||||
program NetExport {
|
||||
@ToClients
|
||||
export event Said { x: int = 0 }
|
||||
export @ToClients event Told { x: int = 0 }
|
||||
@ToServer export event Asked { x: int = 0 }
|
||||
@ToClients event Local { x: int = 0 }
|
||||
@Sync export property Spot { x: int = 0 }
|
||||
@Owned export model Walker { @Sync Spot }
|
||||
handler Talk phase Update {
|
||||
emit Said(x: 1)
|
||||
emit Told(x: 1)
|
||||
emit Asked(x: 1)
|
||||
emit Local(x: 1)
|
||||
}
|
||||
}
|
||||
|
|
@ -36,7 +36,6 @@ var g_at_nargs: []int = new []int
|
|||
var g_at_unres: []bool = new []bool # its target is not in this program (a warning; the schema says so)
|
||||
var g_at_open: int = -1 # the group the attributes being read go into
|
||||
var g_at_decl: []pointer = new []pointer # @AppendOnly / @ByKey read before a declaration
|
||||
var g_at_keep: bool = false # `export` parses its declaration one call down
|
||||
|
||||
# a new group, empty; its index
|
||||
function at_group() -> int {
|
||||
|
|
@ -165,10 +164,6 @@ function at_decl_with(a: pointer, args: pointer) -> void {
|
|||
push(g_at_decl_args, args)
|
||||
}
|
||||
function at_decl_reset() -> void {
|
||||
if g_at_keep {
|
||||
g_at_keep = false
|
||||
return
|
||||
}
|
||||
if len(g_at_decl) > 0 { at_decl_clear() }
|
||||
}
|
||||
function at_decl_clear() -> void {
|
||||
|
|
|
|||
|
|
@ -982,7 +982,36 @@ function decl_hook_target(ev: pointer, sp: pointer, dsp: pointer, att: pointer,
|
|||
# parse one top-level declaration (or resolve an import) into `prog`.
|
||||
# Modifiers are `@annotations` in front of the declaration: `@export`, `@edge`,
|
||||
# `@pure`, `@deterministic`, … — one channel, not a zoo of prefix keywords.
|
||||
var g_decl_exported: bool = false # `export` read in front of the declaration being parsed
|
||||
function parse_one_decl() -> void {
|
||||
let p0 = len(prog)
|
||||
let e0 = len(g_events)
|
||||
let r0 = len(g_rg_var)
|
||||
let outer = g_decl_exported
|
||||
g_decl_exported = false
|
||||
parse_one_decl_in()
|
||||
if g_decl_exported { decl_mark_exported(p0, e0, r0) }
|
||||
g_decl_exported = outer
|
||||
}
|
||||
# `export function f`, `export var v`, ...: what the declaration added is visible from other modules (L3)
|
||||
function decl_mark_exported(p0: int, e0: int, r0: int) -> void {
|
||||
var k = p0
|
||||
while k < len(prog) {
|
||||
prog[k].vis = 1
|
||||
k += 1
|
||||
}
|
||||
k = r0
|
||||
while k < len(g_rg_var) { # a @PerMap registry's own node is not in prog
|
||||
g_rg_var[k].vis = 1
|
||||
k += 1
|
||||
}
|
||||
k = e0
|
||||
while k < len(g_events) {
|
||||
g_events[k].vis = 1
|
||||
k += 1
|
||||
}
|
||||
}
|
||||
function parse_one_decl_in() -> void {
|
||||
at_decl_reset() # attrs.ludic: @AppendOnly / @ByKey belong to one declaration
|
||||
var is_export = false
|
||||
var qspec: Node = null
|
||||
|
|
@ -1005,7 +1034,14 @@ function parse_one_decl() -> void {
|
|||
var esys_phase: pointer = null
|
||||
var ns_name: pointer = null # @Namespace(Name) — a package-provided Foo.* namespace (#62)
|
||||
var is_det = false # @deterministic — no floating point inside (emit_float.ludic)
|
||||
while is_op("@") {
|
||||
# attributes and `export` in any order: `@ToClients export event E` keeps its @ToClients
|
||||
while is_op("@") or (is_id("export") and (toks[pi + 1].kind == TK_ID or toks[pi + 1].text == "@")) {
|
||||
if is_id("export") {
|
||||
pi += 1
|
||||
g_decl_exported = true
|
||||
skipnl()
|
||||
continue
|
||||
}
|
||||
pi += 1; let a = eat_id() # collect a leading @annotation
|
||||
if a == "export" { is_export = true }
|
||||
else if a == "Public" { is_public = true } # @Public hook promotion
|
||||
|
|
@ -1045,7 +1081,7 @@ function parse_one_decl() -> void {
|
|||
let why = expr()
|
||||
if why.kind != E_STR or len(why.s) == 0 { perr("@alloc_ok needs its reason: @alloc_ok(\"a memo miss, bounded by MM_CAP\")") }
|
||||
eat_op(")")
|
||||
g_aok_pending = true # a global: `export` parses the declaration one call down
|
||||
g_aok_pending = true # a global: the declaration after the attributes reads it
|
||||
}
|
||||
else if a == "creates" or a == "releases" { # 25.5e: @creates(PhysShape) / @releases(PhysShape)
|
||||
eat_op("(")
|
||||
|
|
@ -1073,31 +1109,6 @@ function parse_one_decl() -> void {
|
|||
mod_parse_line() # module NAME [uses A, B]
|
||||
return
|
||||
}
|
||||
# `export function f`, `export var v`, ...: visible from other modules (L3)
|
||||
if is_id("export") and (toks[pi + 1].kind == TK_ID) {
|
||||
pi += 1
|
||||
g_at_keep = true
|
||||
let p0 = len(prog)
|
||||
let e0 = len(g_events)
|
||||
let r0 = len(g_rg_var)
|
||||
parse_one_decl()
|
||||
var k = p0
|
||||
while k < len(prog) {
|
||||
prog[k].vis = 1
|
||||
k += 1
|
||||
}
|
||||
k = r0
|
||||
while k < len(g_rg_var) { # a @PerMap registry's own node is not in prog
|
||||
g_rg_var[k].vis = 1
|
||||
k += 1
|
||||
}
|
||||
k = e0
|
||||
while k < len(g_events) {
|
||||
g_events[k].vis = 1
|
||||
k += 1
|
||||
}
|
||||
return
|
||||
}
|
||||
if is_id("numbers") and (toks[pi + 1].text == "float") {
|
||||
pi += 2
|
||||
if not is_float_file(g_parse_file) { float_file_add(g_parse_file) }
|
||||
|
|
|
|||
77222
selfhost/ludicc.seed.ll
77222
selfhost/ludicc.seed.ll
File diff suppressed because one or more lines are too long
100555
selfhost/ludicc.win.seed.ll
100555
selfhost/ludicc.win.seed.ll
File diff suppressed because it is too large
Load diff
|
|
@ -1365,6 +1365,9 @@ function cmd_dev_test() -> int {
|
|||
schema_case("modules/friend_of", "{\"name\": \"lab\", \"package\": null, \"layer\": null, \"uses\": null, \"uses_at\": null, \"friend\": {\"of\": [\"bank\"]}", "--emit-schema: code.modules - a friend module and whose names it sees")
|
||||
schema_case("networking/net_demo", "{\"name\": \"Move\", \"module\": null, \"at\": \"examples/networking/net_demo.ludic:24:19\", \"cancellable\": false, \"net\": \"toserver\", \"emits\": [\"examples/networking/net_demo.ludic:32:10\"], \"listeners\": [{\"handler\": \"DoMove\", \"module\": null, \"at\": \"examples/networking/net_demo.ludic:25:21\"}]}", "--emit-schema: code.events - a remote event, its emit sites and @On listeners")
|
||||
schema_case("networking/net_demo", "{\"name\": \"Ship\", \"module\": null, \"at\": \"examples/networking/net_demo.ludic:22:16\", \"owned\": true, \"properties\": [{\"name\": \"Pos\", \"sync\": true}]}", "--emit-schema: code.models - an @Owned model and its @Sync property")
|
||||
schema_case("networking/net_export", "\"name\": \"Said\", \"module\": null, \"at\": \"examples/networking/net_export.ludic:6:16\", \"cancellable\": false, \"net\": \"toclients\"", "--emit-schema: an attribute before `export` is kept - @ToClients export event is remote")
|
||||
schema_case("networking/net_export", "\"name\": \"Asked\", \"module\": null, \"at\": \"examples/networking/net_export.ludic:8:26\", \"cancellable\": false, \"net\": \"toserver\"", "--emit-schema: @ToServer export event on one line is remote too")
|
||||
schema_case("networking/net_export", "\"owned\": true, \"properties\": [{\"name\": \"Spot\", \"sync\": true}]", "--emit-schema: @Owned export model and @Sync export property keep theirs")
|
||||
schema_case("networking/net_roles", "\"phase\": \"Update\", \"hook\": null, \"target\": null, \"public\": false, \"net\": \"server\"", "--emit-schema: code.handlers - an @Server handler's phase and net role")
|
||||
schema_case("lang/annotations", "\"queries\": {\"these\": [{\"property\": \"Transform\", \"filter\": \"scale > 0\"}, {\"property\": \"Velocity\", \"filter\": \"dx > 0 or dy > 0\"}], \"on\": \"Actor\"}", "--emit-schema: code.handlers - @Queries' terms, filters and model")
|
||||
schema_case("lang/annotations", "\"phase\": null, \"hook\": \"OnSpawn\", \"target\": \"Actor\"", "--emit-schema: code.handlers - a lifecycle hook and the model it names")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue