Merge lang/builtinnames d4df925b (attrs before export 26b986de + the builtin-name refusal); reseeded, fixpoint 300381
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
3bf07ce606
17 changed files with 91731 additions and 89751 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.
|
||||||
9
changes/builtin-names.md
Normal file
9
changes/builtin-names.md
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
bump: patch
|
||||||
|
type: fix
|
||||||
|
**A function named like a built-in a call always takes is refused.** `function words(st, k)` compiled,
|
||||||
|
and every call to it became the built-in `words(n)` - n zeroed ints, with a pointer for n - and LLVM
|
||||||
|
refused the IR far from the cause. A top-level function whose name a call always takes as the
|
||||||
|
compiler's own (`words`, `keep`, `print`, `save`, `load`, `key`, ...; the table is
|
||||||
|
`selfhost/check/check_builtins.ludic`, held to `emit_call` by `ludic-dev syntax --check`) is now an
|
||||||
|
error at its declaration, as `run` already was. Every other built-in (`buffer`, `floats`, `double`,
|
||||||
|
...) yields to a function the program declares, in the checker as it already did in codegen.
|
||||||
11
examples/functions/builtin_yield.ludic
Normal file
11
examples/functions/builtin_yield.ludic
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
# builtin_yield.ludic - a built-in that a declared function may take (buffer, floats, double, ...)
|
||||||
|
# yields to it in the checker as in codegen: this `buffer` is the program's own, returning text.
|
||||||
|
#
|
||||||
|
# Prints: ab 4
|
||||||
|
program BuiltinYield {
|
||||||
|
function buffer(a: string, b: string) -> string { return a + b }
|
||||||
|
entry {
|
||||||
|
let s = buffer("a", "b")
|
||||||
|
print(`{s} {len(floats(4))}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
# has been used, and one that IS held - in a variable, handed to a function, a function's result -
|
# has been used, and one that IS held - in a variable, handed to a function, a function's result -
|
||||||
# is not. Prints 1 2 3 4 5 6 7 and `grew 0` (the heap is read on the Mac; 0 elsewhere).
|
# is not. Prints 1 2 3 4 5 6 7 and `grew 0` (the heap is read on the Mac; 0 elsewhere).
|
||||||
program StringTemps {
|
program StringTemps {
|
||||||
function keep(s: string) -> string { return s }
|
function hand(s: string) -> string { return s }
|
||||||
function tag(i: int) -> string { return `t{i}` }
|
function tag(i: int) -> string { return `t{i}` }
|
||||||
|
|
||||||
# every shape of throwaway text, compared against itself built another way: nothing survives
|
# every shape of throwaway text, compared against itself built another way: nothing survives
|
||||||
|
|
@ -29,7 +29,7 @@ program StringTemps {
|
||||||
let n = string(-42)
|
let n = string(-42)
|
||||||
let t = n + "!" + n
|
let t = n + "!" + n
|
||||||
if n == "-42" and t == "-42!-42" { print(2) }
|
if n == "-42" and t == "-42!-42" { print(2) }
|
||||||
let k = keep(a + "+" + b)
|
let k = hand(a + "+" + b)
|
||||||
let k2 = k + "=" + k
|
let k2 = k + "=" + k
|
||||||
if k == "dir+file" and k2 == "dir+file=dir+file" { print(3) }
|
if k == "dir+file" and k2 == "dir+file=dir+file" { print(3) }
|
||||||
var acc = ""
|
var acc = ""
|
||||||
|
|
@ -37,7 +37,7 @@ program StringTemps {
|
||||||
if acc == "0,1,2,3," { print(4) }
|
if acc == "0,1,2,3," { print(4) }
|
||||||
if string(0) == "0" and string(2147483647) == "2147483647" and `{long(9000000000)}` == "9000000000" { print(5) }
|
if string(0) == "0" and string(2147483647) == "2147483647" and `{long(9000000000)}` == "9000000000" { print(5) }
|
||||||
let nested = `a{`b{1 + 1}c`}d`
|
let nested = `a{`b{1 + 1}c`}d`
|
||||||
# a function's result is its caller's to keep (keep() hands back its own argument), so only
|
# a function's result is its caller's to keep (hand() hands back its own argument), so only
|
||||||
# the text built round it goes
|
# the text built round it goes
|
||||||
if nested == "ab2cd" and "[" + tag(3) + "]" == "[t3]" { print(6) }
|
if nested == "ab2cd" and "[" + tag(3) + "]" == "[t3]" { print(6) }
|
||||||
var bad = 0
|
var bad = 0
|
||||||
|
|
|
||||||
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
8
examples/rejected/builtin_call_name.ludic
Normal file
8
examples/rejected/builtin_call_name.ludic
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
# builtin_call_name.ludic - a function named like a built-in a call always takes is refused at its
|
||||||
|
# declaration: every call to `words` is words(n), n zeroed ints, so this helper would never run (a
|
||||||
|
# pointer became its size). (Rejected on purpose: tools/ludic-cli/test.ludic.)
|
||||||
|
program BuiltinCallName {
|
||||||
|
function words(n: int, k: string) -> string { return k }
|
||||||
|
function keep(s: string) -> string { return s }
|
||||||
|
entry { print(words(1, keep("x"))) }
|
||||||
|
}
|
||||||
|
|
@ -44,7 +44,7 @@ program CrossHeapTest {
|
||||||
cs.on[c] = false
|
cs.on[c] = false
|
||||||
}
|
}
|
||||||
# the ring kept round chunk (px, pz): those past it out, those in it in, and the broad phase rebuilt
|
# the ring kept round chunk (px, pz): those past it out, those in it in, and the broad phase rebuilt
|
||||||
function keep(physics_st: mut PhysicsState, cs: mut CrossState, px: int, pz: int) -> void {
|
function keep_cell(physics_st: mut PhysicsState, cs: mut CrossState, px: int, pz: int) -> void {
|
||||||
for c in 0 .. N * N {
|
for c in 0 .. N * N {
|
||||||
let near = Math.abs(c % N - px) <= RING and Math.abs(c / N - pz) <= RING
|
let near = Math.abs(c % N - px) <= RING and Math.abs(c / N - pz) <= RING
|
||||||
if cs.on[c] and not near { chunk_out(physics_st, cs, c) }
|
if cs.on[c] and not near { chunk_out(physics_st, cs, c) }
|
||||||
|
|
@ -54,8 +54,8 @@ program CrossHeapTest {
|
||||||
phys_step(physics_st)
|
phys_step(physics_st)
|
||||||
}
|
}
|
||||||
function crossing(physics_st: mut PhysicsState, cs: mut CrossState) -> void {
|
function crossing(physics_st: mut PhysicsState, cs: mut CrossState) -> void {
|
||||||
for k in 0 .. N { keep(physics_st, cs, k, k) }
|
for k in 0 .. N { keep_cell(physics_st, cs, k, k) }
|
||||||
for k in 0 .. N { keep(physics_st, cs, N - 1 - k, N - 1 - k) }
|
for k in 0 .. N { keep_cell(physics_st, cs, N - 1 - k, N - 1 - k) }
|
||||||
}
|
}
|
||||||
|
|
||||||
test "crossing the map and back six times holds the bodies, the shapes and Jolt's bytes of the first crossing" (physics_st: mut PhysicsState, cross_st: mut CrossState) {
|
test "crossing the map and back six times holds the bodies, the shapes and Jolt's bytes of the first crossing" (physics_st: mut PhysicsState, cross_st: mut CrossState) {
|
||||||
|
|
|
||||||
23
selfhost/check/check_builtins.ludic
Normal file
23
selfhost/check/check_builtins.ludic
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
# check_builtins.ludic — the names a call always takes as the compiler's own, whatever the program
|
||||||
|
# declares: emit_call's built-ins that no `find_fn` guard lets a declared function take. A function
|
||||||
|
# so named is refused (ck_runtime_shadows) - a call would never reach it: a `words(st, k)` helper
|
||||||
|
# once compiled as words(n) with a pointer for its size. Every other built-in yields to a declared
|
||||||
|
# function, in the checker as in codegen; `ludic-dev syntax --check` holds this table to emit_call.
|
||||||
|
function is_call_builtin(name: pointer) -> bool {
|
||||||
|
if (name == "apply") or (name == "assert") or (name == "bytes") or (name == "data_of") { return true }
|
||||||
|
if (name == "expect") or (name == "expect_eq") or (name == "expect_near") or (name == "fixed") { return true }
|
||||||
|
if (name == "floor") or (name == "intern") or (name == "is_owner") or (name == "is_server") { return true }
|
||||||
|
if (name == "keep") or (name == "key") or (name == "len") or (name == "load") { return true }
|
||||||
|
if (name == "local_id") or (name == "net_poll") or (name == "net_pump") or (name == "net_send") { return true }
|
||||||
|
if (name == "owner") or (name == "panic") or (name == "print") or (name == "push") { return true }
|
||||||
|
if (name == "quit") or (name == "save") or (name == "self") or (name == "serialize") { return true }
|
||||||
|
if (name == "set_local_id") or (name == "set_owner") or (name == "set_role") or (name == "string") { return true }
|
||||||
|
if (name == "sync_size") or (name == "tick_fixed") or (name == "tick_render") or (name == "view") { return true }
|
||||||
|
if (name == "words") or (name == "world_attach_dyn") or (name == "world_count") or (name == "world_detach_dyn") { return true }
|
||||||
|
if (name == "world_field_count") or (name == "world_field_id") or (name == "world_field_name") or (name == "world_field_type") { return true }
|
||||||
|
if (name == "world_get") or (name == "world_has") or (name == "world_kind") or (name == "world_load") { return true }
|
||||||
|
if (name == "world_model_id") or (name == "world_prop_count") or (name == "world_prop_id") or (name == "world_prop_name") { return true }
|
||||||
|
if (name == "world_query_next") or (name == "world_register_prop") or (name == "world_save") or (name == "world_set") { return true }
|
||||||
|
if (name == "world_size") or (name == "world_spawn") { return true }
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
@ -231,6 +231,15 @@ function ck_call_named(e: Node, name: pointer) -> pointer {
|
||||||
return "?"
|
return "?"
|
||||||
}
|
}
|
||||||
if ck_raw_builtin(name) { ck_raw(e, `{name}()`) }
|
if ck_raw_builtin(name) { ck_raw(e, `{name}()`) }
|
||||||
|
# a built-in no call always takes (not is_call_builtin) yields to a function the program declares,
|
||||||
|
# as codegen's does: the checker once typed a `buffer` helper as buffer(n) while codegen called it
|
||||||
|
if not is_call_builtin(name) {
|
||||||
|
let uf = ck_fn(name)
|
||||||
|
if uf != null {
|
||||||
|
ck_vis(uf, name, e)
|
||||||
|
return ck_call_fn(e, name, uf)
|
||||||
|
}
|
||||||
|
}
|
||||||
let bt = ck_builtin(e, name)
|
let bt = ck_builtin(e, name)
|
||||||
if bt != null { return bt }
|
if bt != null { return bt }
|
||||||
let f = ck_fn(name)
|
let f = ck_fn(name)
|
||||||
|
|
|
||||||
|
|
@ -212,7 +212,7 @@ function ck_runtime_shadows() -> void {
|
||||||
var u = 0
|
var u = 0
|
||||||
while u < g_prog_user_end {
|
while u < g_prog_user_end {
|
||||||
let d = prog[u]
|
let d = prog[u]
|
||||||
if d.kind == N_FN and (is_intrinsic(d.s) or is_intrinsic2(d.s)) { ck_err("shadow", d, `{d.s} is a built-in of the compiler, and a call to it would never reach this function; choose another name`) }
|
if d.kind == N_FN and (is_intrinsic(d.s) or is_intrinsic2(d.s) or is_call_builtin(d.s)) { ck_err("shadow", d, `{d.s} is a built-in of the compiler, and a call to it would never reach this function; choose another name`) }
|
||||||
u += 1
|
u += 1
|
||||||
}
|
}
|
||||||
var r = g_prog_user_end
|
var r = g_prog_user_end
|
||||||
|
|
|
||||||
|
|
@ -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_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_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_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
|
# a new group, empty; its index
|
||||||
function at_group() -> int {
|
function at_group() -> int {
|
||||||
|
|
@ -165,10 +164,6 @@ function at_decl_with(a: pointer, args: pointer) -> void {
|
||||||
push(g_at_decl_args, args)
|
push(g_at_decl_args, args)
|
||||||
}
|
}
|
||||||
function at_decl_reset() -> void {
|
function at_decl_reset() -> void {
|
||||||
if g_at_keep {
|
|
||||||
g_at_keep = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(g_at_decl) > 0 { at_decl_clear() }
|
if len(g_at_decl) > 0 { at_decl_clear() }
|
||||||
}
|
}
|
||||||
function at_decl_clear() -> void {
|
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`.
|
# parse one top-level declaration (or resolve an import) into `prog`.
|
||||||
# Modifiers are `@annotations` in front of the declaration: `@export`, `@edge`,
|
# Modifiers are `@annotations` in front of the declaration: `@export`, `@edge`,
|
||||||
# `@pure`, `@deterministic`, … — one channel, not a zoo of prefix keywords.
|
# `@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 {
|
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
|
at_decl_reset() # attrs.ludic: @AppendOnly / @ByKey belong to one declaration
|
||||||
var is_export = false
|
var is_export = false
|
||||||
var qspec: Node = null
|
var qspec: Node = null
|
||||||
|
|
@ -1005,7 +1034,14 @@ function parse_one_decl() -> void {
|
||||||
var esys_phase: pointer = null
|
var esys_phase: pointer = null
|
||||||
var ns_name: pointer = null # @Namespace(Name) — a package-provided Foo.* namespace (#62)
|
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)
|
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
|
pi += 1; let a = eat_id() # collect a leading @annotation
|
||||||
if a == "export" { is_export = true }
|
if a == "export" { is_export = true }
|
||||||
else if a == "Public" { is_public = true } # @Public hook promotion
|
else if a == "Public" { is_public = true } # @Public hook promotion
|
||||||
|
|
@ -1045,7 +1081,7 @@ function parse_one_decl() -> void {
|
||||||
let why = expr()
|
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\")") }
|
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(")")
|
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)
|
else if a == "creates" or a == "releases" { # 25.5e: @creates(PhysShape) / @releases(PhysShape)
|
||||||
eat_op("(")
|
eat_op("(")
|
||||||
|
|
@ -1073,31 +1109,6 @@ function parse_one_decl() -> void {
|
||||||
mod_parse_line() # module NAME [uses A, B]
|
mod_parse_line() # module NAME [uses A, B]
|
||||||
return
|
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") {
|
if is_id("numbers") and (toks[pi + 1].text == "float") {
|
||||||
pi += 2
|
pi += 2
|
||||||
if not is_float_file(g_parse_file) { float_file_add(g_parse_file) }
|
if not is_float_file(g_parse_file) { float_file_add(g_parse_file) }
|
||||||
|
|
|
||||||
79060
selfhost/ludicc.seed.ll
79060
selfhost/ludicc.seed.ll
File diff suppressed because one or more lines are too long
102199
selfhost/ludicc.win.seed.ll
102199
selfhost/ludicc.win.seed.ll
File diff suppressed because it is too large
Load diff
|
|
@ -112,6 +112,7 @@ function selfhost_frags() -> []pointer {
|
||||||
push(f, "selfhost/check/check_ops.ludic")
|
push(f, "selfhost/check/check_ops.ludic")
|
||||||
push(f, "selfhost/check/check_call.ludic")
|
push(f, "selfhost/check/check_call.ludic")
|
||||||
push(f, "selfhost/check/check_stmt.ludic")
|
push(f, "selfhost/check/check_stmt.ludic")
|
||||||
|
push(f, "selfhost/check/check_builtins.ludic")
|
||||||
push(f, "selfhost/check/check_gen.ludic")
|
push(f, "selfhost/check/check_gen.ludic")
|
||||||
push(f, "selfhost/check/check_gen_call.ludic")
|
push(f, "selfhost/check/check_gen_call.ludic")
|
||||||
push(f, "selfhost/check/check_tynames.ludic")
|
push(f, "selfhost/check/check_tynames.ludic")
|
||||||
|
|
|
||||||
|
|
@ -422,6 +422,53 @@ function sx_parser() -> int {
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ---- the built-ins a call always takes, held to emit_call ----
|
||||||
|
# every `name == "x"` on one of a function's top-level `if` lines (after head, to the next function);
|
||||||
|
# guarded skips the lines a declared function can take (`find_fn(`)
|
||||||
|
function sx_name_tests(text: pointer, head: pointer, guarded: bool) -> []pointer {
|
||||||
|
let out = new []pointer
|
||||||
|
let n = slen(text)
|
||||||
|
var inside = false
|
||||||
|
var i = 0
|
||||||
|
while i < n {
|
||||||
|
let line = line_at(text, i)
|
||||||
|
i = i + slen(line) + 1
|
||||||
|
if s_starts(line, "function ") { inside = s_starts(line, head) }
|
||||||
|
else if inside and s_starts(line, " if (") and not (guarded and s_contains(line, "find_fn(")) {
|
||||||
|
var at = s_index(line, "name == \"", 0)
|
||||||
|
while at >= 0 {
|
||||||
|
let a = at + 9
|
||||||
|
var b = a
|
||||||
|
while b < slen(line) and line[b] != '"' { b += 1 }
|
||||||
|
let w = line[a .. b]
|
||||||
|
if not set_has(out, w) { push(out, w) }
|
||||||
|
at = s_index(line, "name == \"", b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
# selfhost/check/check_builtins.ludic's table against emit_call's unguarded built-ins, both ways
|
||||||
|
function sx_builtins() -> int {
|
||||||
|
let ec = read_file("selfhost/backend/emit_call.ludic")
|
||||||
|
let tb = read_file("selfhost/check/check_builtins.ludic")
|
||||||
|
if ec == null or tb == null { push(g_sx_prob, "cannot read emit_call.ludic or check_builtins.ludic"); return 1 }
|
||||||
|
let want = sx_name_tests(ec, "function emit_call(", true)
|
||||||
|
let have = sx_name_tests(tb, "function is_call_builtin(", false)
|
||||||
|
var n = 0
|
||||||
|
var i = 0
|
||||||
|
while i < len(want) {
|
||||||
|
if not set_has(have, want[i]) { push(g_sx_prob, `emit_call always takes {want[i]}(...), and check_builtins.ludic does not refuse a function of that name - add it`); n += 1 }
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
i = 0
|
||||||
|
while i < len(have) {
|
||||||
|
if not set_has(want, have[i]) { push(g_sx_prob, `check_builtins.ludic refuses {have[i]}, which emit_call no longer always takes - drop it`); n += 1 }
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
# ---- the command ----
|
# ---- the command ----
|
||||||
function sx_report(what: pointer) -> int {
|
function sx_report(what: pointer) -> int {
|
||||||
if len(g_sx_prob) == 0 { return 0 }
|
if len(g_sx_prob) == 0 { return 0 }
|
||||||
|
|
@ -457,6 +504,7 @@ function cmd_syntax_gen() -> int {
|
||||||
while i < len(ts) { sx_carries(ts[i]); i += 1 }
|
while i < len(ts) { sx_carries(ts[i]); i += 1 }
|
||||||
sx_docs()
|
sx_docs()
|
||||||
sx_parser()
|
sx_parser()
|
||||||
|
sx_builtins()
|
||||||
return sx_report("the vocabulary and what is written from it disagree")
|
return sx_report("the vocabulary and what is written from it disagree")
|
||||||
}
|
}
|
||||||
if changed == 0 { print(" every grammar already says what ludicc --emit-syntax does") }
|
if changed == 0 { print(" every grammar already says what ludicc --emit-syntax does") }
|
||||||
|
|
|
||||||
|
|
@ -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("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\": \"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_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("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", "\"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")
|
schema_case("lang/annotations", "\"phase\": null, \"hook\": \"OnSpawn\", \"target\": \"Actor\"", "--emit-schema: code.handlers - a lifecycle hook and the model it names")
|
||||||
|
|
@ -1372,6 +1375,8 @@ function cmd_dev_test() -> int {
|
||||||
schema_case("lang/scenes", "\"scene\": \"Play\", \"layer\": \"Hud\"", "--emit-schema: code.handlers - a scene's handler, its scene and layer")
|
schema_case("lang/scenes", "\"scene\": \"Play\", \"layer\": \"Hud\"", "--emit-schema: code.handlers - a scene's handler, its scene and layer")
|
||||||
schema_case("state/write_reach", "{\"fn\": \"log_note\", \"module\": null, \"at\": \"examples/state/write_reach.ludic:21:37\", \"slot_kind\": \"registry\", \"slot\": \"Kinds.note\", \"entry\": \"box\"}", "--emit-schema: code.fn_refs - a fn in a registry row, the field and row it fills")
|
schema_case("state/write_reach", "{\"fn\": \"log_note\", \"module\": null, \"at\": \"examples/state/write_reach.ludic:21:37\", \"slot_kind\": \"registry\", \"slot\": \"Kinds.note\", \"entry\": \"box\"}", "--emit-schema: code.fn_refs - a fn in a registry row, the field and row it fills")
|
||||||
schema_case("modules/ports", "\"slot_kind\": \"port_default\", \"slot\": \"Clock.day\"", "--emit-schema: code.fn_refs - a port member's default")
|
schema_case("modules/ports", "\"slot_kind\": \"port_default\", \"slot\": \"Clock.day\"", "--emit-schema: code.fn_refs - a port member's default")
|
||||||
|
diag_json_case("rejected/builtin_call_name", 2, "words is a built-in of the compiler, and a call to it would never reach this function; choose another name", "a function named like a built-in a call always takes (words, keep) is refused at its declaration")
|
||||||
|
feat_case("functions/builtin_yield", "", "ab 4", "builtin_yield.ludic (a built-in a declared function may take - buffer - yields to it in the checker as in codegen)")
|
||||||
diag_json_case("rejected/i18n_keys/key_missing", 2, "the key pause.gone is not in examples/rejected/i18n_keys/lang/en.po", "a key literal en.po does not have is refused, at its line and column")
|
diag_json_case("rejected/i18n_keys/key_missing", 2, "the key pause.gone is not in examples/rejected/i18n_keys/lang/en.po", "a key literal en.po does not have is refused, at its line and column")
|
||||||
diag_json_case("rejected/i18n_keys/key_missing", 2, "is a plural key, and examples/rejected/i18n_keys/lang/en.po:7 gives pause.resume no msgid_plural", "kn\"...\" is a plural key: en.po's entry has a msgid_plural")
|
diag_json_case("rejected/i18n_keys/key_missing", 2, "is a plural key, and examples/rejected/i18n_keys/lang/en.po:7 gives pause.resume no msgid_plural", "kn\"...\" is a plural key: en.po's entry has a msgid_plural")
|
||||||
diag_json_case("rejected/i18n_keys/key_missing", 2, "trf: hud.day is given 2 value(s), and its English in examples/rejected/i18n_keys/lang/en.po has 1 hole(s)", "trf given more values than the key's English has holes: a warning")
|
diag_json_case("rejected/i18n_keys/key_missing", 2, "trf: hud.day is given 2 value(s), and its English in examples/rejected/i18n_keys/lang/en.po has 1 hole(s)", "trf given more values than the key's English has holes: a warning")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue