attributes: a target the program does not have at all (an @Ref registry, an @Tint / @OneOf constant) is a warning and "unresolved" in the schema, not an error - a package names the game's registry without importing it; a name of another kind is still an error; reseeded
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
dacc0f7280
commit
d82dc3162f
10 changed files with 83635 additions and 82878 deletions
|
|
@ -37,6 +37,7 @@ program Attributes {
|
||||||
registry Tools of Tool as TL from "data/attr_tools.lres"
|
registry Tools of Tool as TL from "data/attr_tools.lres"
|
||||||
# a tool hung on the camp's rack: which, and from which node of ITS model
|
# a tool hung on the camp's rack: which, and from which node of ITS model
|
||||||
property RackSlot {
|
property RackSlot {
|
||||||
|
@Ref(Items) item: int = 0 # a registry this program lacks: a warning, unresolved
|
||||||
@Ref(Tools) tool: int = 0
|
@Ref(Tools) tool: int = 0
|
||||||
@Node(tool) hook: string = "grip"
|
@Node(tool) hook: string = "grip"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
# attributes: @Node / @Clip name a field of its record that names a glTF - `handle` names no field,
|
# attributes: @Node / @Clip name a field of their record that names a glTF - `handle` names no field,
|
||||||
# `tag` is a plain string, `kit` refers to a registry whose record has two models - and a listing
|
# `tag` is a plain string, and `kit` refers to a registry whose record has two models: four errors.
|
||||||
# @OneOf and @Tint name constants that exist, which KITS_NONE and TSLOT_NONE are not (all six are
|
# @OneOf's KITS_NONE and @Tint's TSLOT_NONE are in no part of the program: two warnings.
|
||||||
# reported)
|
|
||||||
program R {
|
program R {
|
||||||
property Kit {
|
property Kit {
|
||||||
key: string = ""
|
key: string = ""
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
# attributes: @Ref names a registry - there is no registry Vendorz, nor Shops (both are reported)
|
# attributes: @Ref names a registry - Vendor is a record, which is an error; Shops is in no part of
|
||||||
|
# this program, which is only a warning (a package may name the game's registry it cannot import)
|
||||||
program R {
|
program R {
|
||||||
property Vendor { key: string = "" }
|
property Vendor { key: string = "" }
|
||||||
registry Vendors of Vendor
|
registry Vendors of Vendor
|
||||||
property Tool {
|
property Tool {
|
||||||
@Ref(Vendorz) seller: int = 0
|
@Ref(Vendor) seller: int = 0
|
||||||
@Ref(Shops) shop: int = 0
|
@Ref(Shops) shop: int = 0
|
||||||
}
|
}
|
||||||
entry { print(VENDORS_COUNT) }
|
entry { print(VENDORS_COUNT) }
|
||||||
|
|
|
||||||
|
|
@ -181,6 +181,7 @@ function sc_attrs(b: Buf, g: int) -> void {
|
||||||
jq_put(b, g_at_name[j])
|
jq_put(b, g_at_name[j])
|
||||||
buf_puts(b, ", \"args\": ")
|
buf_puts(b, ", \"args\": ")
|
||||||
buf_puts(b, g_at_args[j])
|
buf_puts(b, g_at_args[j])
|
||||||
|
if g_at_unres[j] { buf_puts(b, ", \"unresolved\": true") }
|
||||||
buf_puts(b, "}")
|
buf_puts(b, "}")
|
||||||
j += 1
|
j += 1
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,8 @@
|
||||||
# }
|
# }
|
||||||
# @AppendOnly @ByKey registry Items of Item as IT from "items.lres"
|
# @AppendOnly @ByKey registry Items of Item as IT from "items.lres"
|
||||||
#
|
#
|
||||||
# They change nothing the program does. Four are checked: @Ref must name a registry that exists,
|
# They change nothing the program does. A target the program does not have at all is a warning and
|
||||||
|
# "unresolved" in the schema (a package names the game's registry it cannot import). Four are checked: @Ref must name a registry that exists,
|
||||||
# @Node and @Clip a field of its record that names a glTF (attrs_node.ludic), and a listing @OneOf
|
# @Node and @Clip a field of its record that names a glTF (attrs_node.ludic), and a listing @OneOf
|
||||||
# constants that exist, and @Tint a constant that exists (a slot's row in the game's registry). A
|
# constants that exist, and @Tint a constant that exists (a slot's row in the game's registry). A
|
||||||
# field's or a registry's attributes are a group (Node.at, g_rg_at): a run of names, each with its
|
# field's or a registry's attributes are a group (Node.at, g_rg_at): a run of names, each with its
|
||||||
|
|
@ -29,6 +30,7 @@ var g_at_name: []pointer = new []pointer
|
||||||
var g_at_args: []pointer = new []pointer # the arguments, a JSON array: ["Items"], [0, 1.5]
|
var g_at_args: []pointer = new []pointer # the arguments, a JSON array: ["Items"], [0, 1.5]
|
||||||
var g_at_first: []pointer = new []pointer # the first argument as written (@Ref's registry), or null
|
var g_at_first: []pointer = new []pointer # the first argument as written (@Ref's registry), or null
|
||||||
var g_at_nargs: []int = new []int
|
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_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
|
var g_at_keep: bool = false # `export` parses its declaration one call down
|
||||||
|
|
@ -44,6 +46,7 @@ function at_push(g: int, name: pointer, args: pointer, first: pointer, nargs: in
|
||||||
push(g_at_args, args)
|
push(g_at_args, args)
|
||||||
push(g_at_first, first)
|
push(g_at_first, first)
|
||||||
push(g_at_nargs, nargs)
|
push(g_at_nargs, nargs)
|
||||||
|
push(g_at_unres, false)
|
||||||
g_at_count[g] = g_at_count[g] + 1
|
g_at_count[g] = g_at_count[g] + 1
|
||||||
}
|
}
|
||||||
# `(a, "b", -1.5)` after an attribute's name, if it is there, as a JSON array; the first argument
|
# `(a, "b", -1.5)` after an attribute's name, if it is there, as a JSON array; the first argument
|
||||||
|
|
@ -176,14 +179,31 @@ function at_check_field(d: Node, f: Node) -> void {
|
||||||
let g = f.at
|
let g = f.at
|
||||||
var j = g_at_start[g]
|
var j = g_at_start[g]
|
||||||
while j < g_at_start[g] + g_at_count[g] {
|
while j < g_at_start[g] + g_at_count[g] {
|
||||||
if (g_at_name[j] == "Ref") and reg_find(g_at_first[j]) < 0 {
|
if (g_at_name[j] == "Ref") and reg_find(g_at_first[j]) < 0 { at_target(d, f, j, "a registry") }
|
||||||
perr_at(f.file, f.line, f.col, `field {d.s}.{f.s}: @Ref({g_at_first[j]}) names no registry - there is no registry {g_at_first[j]}`)
|
|
||||||
}
|
|
||||||
if (g_at_name[j] == "Node") or (g_at_name[j] == "Clip") { at_check_node(d, f, g_at_name[j], g_at_first[j]) }
|
if (g_at_name[j] == "Node") or (g_at_name[j] == "Clip") { at_check_node(d, f, g_at_name[j], g_at_first[j]) }
|
||||||
if g_at_name[j] == "OneOf" { at_check_oneof(d, f, j) }
|
if g_at_name[j] == "OneOf" { at_check_oneof(d, f, j) }
|
||||||
if (g_at_name[j] == "Tint") and not at_const_exists(g_at_first[j]) {
|
if (g_at_name[j] == "Tint") and not at_const_exists(g_at_first[j]) { at_const_target(d, f, j, g_at_first[j]) }
|
||||||
perr_at(f.file, f.line, f.col, `field {d.s}.{f.s}: @Tint({g_at_first[j]}) names no tint slot - there is no constant {g_at_first[j]}`)
|
|
||||||
}
|
|
||||||
j += 1
|
j += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
# a target this program does not have: a package can name the game's registry or constant without
|
||||||
|
# importing it, so that is a warning and the schema says unresolved; a name of another kind is an error
|
||||||
|
function at_target(d: Node, f: Node, j: int, want: pointer) -> void {
|
||||||
|
let x = g_at_first[j]
|
||||||
|
let where = `field {d.s}.{f.s}: @{g_at_name[j]}({x})`
|
||||||
|
if at_decl_named(x) {
|
||||||
|
perr_at(f.file, f.line, f.col, `{where} names {x}, which is not {want}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
g_at_unres[j] = true
|
||||||
|
pwarn_at(f.file, f.line, f.col, `{where}: this program has no {x} - left unresolved`)
|
||||||
|
}
|
||||||
|
function at_const_target(d: Node, f: Node, j: int, x: pointer) -> void {
|
||||||
|
let where = `field {d.s}.{f.s}: @{g_at_name[j]}`
|
||||||
|
if at_decl_named(x) {
|
||||||
|
perr_at(f.file, f.line, f.col, `{where} names {x}, which is not a constant`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
g_at_unres[j] = true
|
||||||
|
pwarn_at(f.file, f.line, f.col, `{where}: this program has no constant {x} - left unresolved`)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,9 +75,7 @@ function at_check_oneof(d: Node, f: Node, j: int) -> void {
|
||||||
let names = at_arg_names(g_at_args[j])
|
let names = at_arg_names(g_at_args[j])
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(names) {
|
while i < len(names) {
|
||||||
if not at_const_exists(names[i]) {
|
if not at_const_exists(names[i]) { at_const_target(d, f, j, names[i]) }
|
||||||
perr_at(f.file, f.line, f.col, `field {d.s}.{f.s}: @OneOf names {names[i]}, and there is no constant {names[i]}`)
|
|
||||||
}
|
|
||||||
i += 1
|
i += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -105,3 +103,12 @@ function at_const_exists(name: pointer) -> bool {
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
# a declaration of any kind (a record, a function, a const, a state ...) called name
|
||||||
|
function at_decl_named(name: pointer) -> bool {
|
||||||
|
var i = 0
|
||||||
|
while i < len(prog) {
|
||||||
|
if prog[i].s != null and vis_plain(prog[i].s) == name { return true }
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,16 @@ function perr_at(file: pointer, line: int, col: int, msg: pointer) -> void {
|
||||||
g_dg_errors += 1
|
g_dg_errors += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
# a warning: kept in the JSON array, or said on stderr; the build goes on
|
||||||
|
function pwarn_at(file: pointer, line: int, col: int, msg: pointer) -> void {
|
||||||
|
var f = file
|
||||||
|
if f == null { f = "" }
|
||||||
|
if g_diag_json { diag_add(f, line, col, "warning", msg) }
|
||||||
|
else {
|
||||||
|
let m = `{f}:{itoa(line)}: warning: {msg}\n`
|
||||||
|
file_write(file_stderr(), m, len(m))
|
||||||
|
}
|
||||||
|
}
|
||||||
function diag_stop_if_errors() -> void {
|
function diag_stop_if_errors() -> void {
|
||||||
if g_dg_errors > 0 { diag_exit(1) }
|
if g_dg_errors > 0 { diag_exit(1) }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
80470
selfhost/ludicc.seed.ll
80470
selfhost/ludicc.seed.ll
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load diff
|
|
@ -1199,8 +1199,8 @@ function cmd_dev_test() -> int {
|
||||||
reject_case("rejected/math_into_text", "field a1 of Note wants a string and this is an int", "a Math.* result has its type: an int from Math.max is not text")
|
reject_case("rejected/math_into_text", "field a1 of Note wants a string and this is an int", "a Math.* result has its type: an int from Math.max is not text")
|
||||||
reject_count("rejected/every_error", 3, "every type error in a program is reported at once, not only the first")
|
reject_count("rejected/every_error", 3, "every type error in a program is reported at once, not only the first")
|
||||||
diag_json_case("rejected/every_error", 3, "\"line\": 8, \"col\": 15, \"severity\": \"error\", \"message\": \"half takes 1 argument(s) and this call gives 2\"", "--diagnostics=json: every type error as {file, line, col, severity, message}")
|
diag_json_case("rejected/every_error", 3, "\"line\": 8, \"col\": 15, \"severity\": \"error\", \"message\": \"half takes 1 argument(s) and this call gives 2\"", "--diagnostics=json: every type error as {file, line, col, severity, message}")
|
||||||
diag_json_case("rejected/ref_unknown", 2, "@Ref(Shops) names no registry", "--diagnostics=json: every @Ref that names no registry")
|
diag_json_case("rejected/ref_unknown", 2, "@Ref(Shops): this program has no Shops - left unresolved", "--diagnostics=json: an @Ref to a registry the program lacks is a warning beside the error")
|
||||||
diag_json_case("rejected/node_bad", 6, "@OneOf names KITS_NONE, and there is no constant KITS_NONE", "--diagnostics=json: every @Node / @Clip that names no model field, and every @OneOf constant that does not exist")
|
diag_json_case("rejected/node_bad", 6, "this program has no constant KITS_NONE - left unresolved", "--diagnostics=json: every @Node / @Clip that names no model field, and every @OneOf constant that does not exist")
|
||||||
feat_case("lang/generics", "", "7 Crater Lake a 2 west 7 0.75 2.5 1", "generics.ludic (L5: generic records and functions, nested instances, fn-typed parameters and fields, inference from the result's slot)")
|
feat_case("lang/generics", "", "7 Crater Lake a 2 west 7 0.75 2.5 1", "generics.ludic (L5: generic records and functions, nested instances, fn-typed parameters and fields, inference from the result's slot)")
|
||||||
reject_case("rejected/generic_unbound", "cannot tell what T is in this call to pool_new", "a call whose type arguments nothing decides is refused")
|
reject_case("rejected/generic_unbound", "cannot tell what T is in this call to pool_new", "a call whose type arguments nothing decides is refused")
|
||||||
reject_case("rejected/generic_mismatch", "words wants a Pool<string> and this is a Pool<int>", "two instances of one generic are two types")
|
reject_case("rejected/generic_mismatch", "words wants a Pool<string> and this is a Pool<int>", "two instances of one generic are two types")
|
||||||
|
|
@ -1233,12 +1233,13 @@ function cmd_dev_test() -> int {
|
||||||
reject_case("rejected/registry_unknown", "there is no registry Tables", "a def names a registry that exists")
|
reject_case("rejected/registry_unknown", "there is no registry Tables", "a def names a registry that exists")
|
||||||
feat_case("lang/attributes", "", "2 1 Lantern 2", "attributes.ludic (editor attributes: @Ref, @OneOf, @Range, @Unit, @Asset, @Color, @AppendOnly, @ByKey - no effect on the program)")
|
feat_case("lang/attributes", "", "2 1 Lantern 2", "attributes.ludic (editor attributes: @Ref, @OneOf, @Range, @Unit, @Asset, @Color, @AppendOnly, @ByKey - no effect on the program)")
|
||||||
feat_case("lang/schema_hash", "", "true", "schema_hash.ludic (Build.schema_hash(): FNV-1a 64 of the program's own schema, 0 in a release)")
|
feat_case("lang/schema_hash", "", "true", "schema_hash.ludic (Build.schema_hash(): FNV-1a 64 of the program's own schema, 0 in a release)")
|
||||||
reject_case("rejected/ref_unknown", "field Tool.seller: @Ref(Vendorz) names no registry", "@Ref names a registry that exists")
|
reject_case("rejected/ref_unknown", "field Tool.seller: @Ref(Vendor) names Vendor, which is not a registry", "@Ref names a registry, not another kind of thing")
|
||||||
reject_case("rejected/node_bad", "field Tool.grip: @Node(handle) names no field", "@Node names a field of its record")
|
reject_case("rejected/node_bad", "field Tool.grip: @Node(handle) names no field", "@Node names a field of its record")
|
||||||
schema_case("lang/attributes", "{\"name\": \"weight\", \"type\": \"float\", \"default\": \"1.0\", \"file\": \"examples/lang/attributes.ludic\", \"line\": 24, \"col\": 33, \"attributes\": [{\"name\": \"Range\", \"args\": [0, 20.5]}, {\"name\": \"Unit\", \"args\": [\"kg\"]}]", "--emit-schema: a field's type, default, place and attributes")
|
schema_case("lang/attributes", "{\"name\": \"weight\", \"type\": \"float\", \"default\": \"1.0\", \"file\": \"examples/lang/attributes.ludic\", \"line\": 24, \"col\": 33, \"attributes\": [{\"name\": \"Range\", \"args\": [0, 20.5]}, {\"name\": \"Unit\", \"args\": [\"kg\"]}]", "--emit-schema: a field's type, default, place and attributes")
|
||||||
schema_case("lang/attributes", "{\"key\": \"lantern\", \"constant\": \"TL_LANTERN\", \"index\": 1, \"file\": \"examples/lang/data/attr_tools.lres\", \"line\": 3, \"col\": 1", "--emit-schema: a resource entry's constant, index and place")
|
schema_case("lang/attributes", "{\"key\": \"lantern\", \"constant\": \"TL_LANTERN\", \"index\": 1, \"file\": \"examples/lang/data/attr_tools.lres\", \"line\": 3, \"col\": 1", "--emit-schema: a resource entry's constant, index and place")
|
||||||
schema_case("lang/attributes", "\"fn_type\": \"fn(Tool,int)->float\", \"params\": [\"Tool\", \"int\"]", "--emit-schema: a function with arguments, its fn_type spelled as a field's")
|
schema_case("lang/attributes", "\"fn_type\": \"fn(Tool,int)->float\", \"params\": [\"Tool\", \"int\"]", "--emit-schema: a function with arguments, its fn_type spelled as a field's")
|
||||||
schema_case("lang/attributes", "{\"name\": \"Node\", \"args\": [\"tool\"]}", "--emit-schema: @Node and the field it reads its model from")
|
schema_case("lang/attributes", "{\"name\": \"Node\", \"args\": [\"tool\"]}", "--emit-schema: @Node and the field it reads its model from")
|
||||||
|
schema_case("lang/attributes", "{\"name\": \"Ref\", \"args\": [\"Items\"], \"unresolved\": true}", "--emit-schema: an attribute whose target the program lacks is marked unresolved, not refused")
|
||||||
reject_case("rejected/registry_twice", "def Furnishings crate is declared twice", "a registry's key is declared once")
|
reject_case("rejected/registry_twice", "def Furnishings crate is declared twice", "a registry's key is declared once")
|
||||||
reject_case("rejected/registry_field", "Furnishing has no field colour", "a def's fields are the record's")
|
reject_case("rejected/registry_field", "Furnishing has no field colour", "a def's fields are the record's")
|
||||||
feat_case("modules/open_registry", "", "clock animals weather / 2 2 3", "open_registry.ludic (L8+L3: an open registry takes other modules' defs; its own entries first, then by module name)")
|
feat_case("modules/open_registry", "", "clock animals weather / 2 2 3", "open_registry.ludic (L8+L3: an open registry takes other modules' defs; its own entries first, then by module name)")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue