attributes: @Clip(field) resolved as @Node is, and @OneOf takes constants as well as a prefix - one argument ending in _ is a prefix, otherwise each is a constant that must exist; reseeded
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
ebfbabdfd8
commit
34c2ac2cb9
9 changed files with 73220 additions and 71912 deletions
|
|
@ -636,10 +636,12 @@ the lines above it:
|
||||||
property Tool {
|
property Tool {
|
||||||
@Ref(Vendors) seller: int = 0 # an index into that registry: its entries are offered
|
@Ref(Vendors) seller: int = 0 # an index into that registry: its entries are offered
|
||||||
@OneOf(GR_) grade: int = 0 # one of the constants whose names start GR_
|
@OneOf(GR_) grade: int = 0 # one of the constants whose names start GR_
|
||||||
|
@OneOf(GR_GOLD, GR_SILVER) medal: int = 0 # or one of these constants
|
||||||
@Range(0, 20.5) @Unit("kg") weight: float = 1.0
|
@Range(0, 20.5) @Unit("kg") weight: float = 1.0
|
||||||
@Asset("gltf") model: string = "" # a file of that kind (any string)
|
@Asset("gltf") model: string = "" # a file of that kind (any string)
|
||||||
@Color tint: int = 0
|
@Color tint: int = 0
|
||||||
@Node(model) grip: string = "" # a node inside the glTF that `model` names
|
@Node(model) grip: string = "" # a node inside the glTF that `model` names
|
||||||
|
@Clip(model) swing: string = "" # a clip inside it
|
||||||
@Derived reach: float = 0.0 # worked out at boot: anything written is overwritten
|
@Derived reach: float = 0.0 # worked out at boot: anything written is overwritten
|
||||||
@Text @Multiline blurb: string = "" # read by the player (so translated), and prose
|
@Text @Multiline blurb: string = "" # read by the player (so translated), and prose
|
||||||
@Key bind: int = 0 # a key code
|
@Key bind: int = 0 # a key code
|
||||||
|
|
@ -648,8 +650,10 @@ property Tool {
|
||||||
registry Tools of Tool as TL from "data/tools.lres"
|
registry Tools of Tool as TL from "data/tools.lres"
|
||||||
```
|
```
|
||||||
|
|
||||||
They change nothing the program does. Two are checked, and every field that fails is reported, not
|
They change nothing the program does. Three are checked, and every field that fails is reported, not
|
||||||
only the first: `@Ref` must name a registry the program declares, and `@Node(f)` must name a field
|
only the first: `@Ref` must name a registry the program declares; `@OneOf` with one argument ending
|
||||||
|
in `_` is a prefix, and otherwise each argument is a constant that must exist; and `@Node(f)` or
|
||||||
|
`@Clip(f)` must name a field
|
||||||
`f` of the same record that is either `@Asset("gltf")` or an `@Ref` to a registry whose record has
|
`f` of the same record that is either `@Asset("gltf")` or an `@Ref` to a registry whose record has
|
||||||
exactly one `@Asset("gltf")` field (the model is then the referenced row's).
|
exactly one `@Asset("gltf")` field (the model is then the referenced row's).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,9 @@ prefix, resource file, openness and its entries in their final order after the o
|
||||||
which entries in); every const; and every function a `fn` value can name, with the `fn_type` a field sees (its states stripped).
|
which entries in); every const; and every function a `fn` value can name, with the `fn_type` a field sees (its states stripped).
|
||||||
Deterministic, `"schema_version": 1`. Fields and registries carry editor attributes on the existing
|
Deterministic, `"schema_version": 1`. Fields and registries carry editor attributes on the existing
|
||||||
`@` syntax - `@Ref(Registry)`, `@OneOf(PREFIX_)`, `@Range(lo, hi)`, `@Unit("m/s")`, `@Asset("gltf")`,
|
`@` syntax - `@Ref(Registry)`, `@OneOf(PREFIX_)`, `@Range(lo, hi)`, `@Unit("m/s")`, `@Asset("gltf")`,
|
||||||
`@Color`, `@Node(field)`, `@Derived`, `@Text`, `@Multiline`, `@Key`, and `@AppendOnly` / `@ByKey` on
|
`@Color`, `@Node(field)`, `@Clip(field)`, `@Derived`, `@Text`, `@Multiline`, `@Key`, and `@AppendOnly` / `@ByKey` on
|
||||||
a registry - which change nothing but go into the schema; `@Ref` naming no registry is an error, and
|
a registry - which change nothing but go into the schema; `@Ref` naming no registry is an error, and
|
||||||
so is `@Node` naming a field that is not a glTF (`@Asset("gltf")`, or an `@Ref` to one). A field may now carry several attributes. `ludicc --check
|
so is `@Node` / `@Clip` naming a field that is not a glTF (`@Asset("gltf")`, or an `@Ref` to one), and
|
||||||
|
a listing `@OneOf` (`@OneOf(A, B)`, not a prefix `@OneOf(P_)`) naming a constant that does not exist. A field may now carry several attributes. `ludicc --check
|
||||||
--diagnostics=json` (`ludic build --check --diagnostics=json`) prints every error as one JSON array
|
--diagnostics=json` (`ludic build --check --diagnostics=json`) prints every error as one JSON array
|
||||||
of `{file, line, col, severity, message}` on stdout; tokens and nodes now know their column.
|
of `{file, line, col, severity, message}` on stdout; tokens and nodes now know their column.
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,8 @@ program Attributes {
|
||||||
@Derived reach: float = 0.0 # worked out at boot from the model
|
@Derived reach: float = 0.0 # worked out at boot from the model
|
||||||
@Text @Multiline blurb: string = "" # what the shop says about it
|
@Text @Multiline blurb: string = "" # what the shop says about it
|
||||||
@Key bind: int = 0 # the key that takes it in hand
|
@Key bind: int = 0 # the key that takes it in hand
|
||||||
|
@Clip(model) swing: string = "swing" # the clip it plays in use
|
||||||
|
@OneOf(VD_STORE, VD_MARINA) sold_at: int = VD_MARINA
|
||||||
}
|
}
|
||||||
@AppendOnly
|
@AppendOnly
|
||||||
registry Tools of Tool as TL from "data/attr_tools.lres"
|
registry Tools of Tool as TL from "data/attr_tools.lres"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
# attributes: @Node names a field of its record that names a glTF - `handle` names no field, `tag`
|
# attributes: @Node / @Clip name a field of its record that names a glTF - `handle` names no field,
|
||||||
# is a plain string, and `kit` refers to a registry whose record has two models (all three are reported)
|
# `tag` is a plain string, `kit` refers to a registry whose record has two models - and a listing
|
||||||
|
# @OneOf names constants that exist, which KITS_NONE is not (all five are reported)
|
||||||
program R {
|
program R {
|
||||||
property Kit {
|
property Kit {
|
||||||
key: string = ""
|
key: string = ""
|
||||||
|
|
@ -13,6 +14,8 @@ program R {
|
||||||
@Node(handle) grip: string = ""
|
@Node(handle) grip: string = ""
|
||||||
@Node(tag) tip: string = ""
|
@Node(tag) tip: string = ""
|
||||||
@Node(kit) hook: string = ""
|
@Node(kit) hook: string = ""
|
||||||
|
@Clip(tag) swing: string = ""
|
||||||
|
@OneOf(KITS_COUNT, KITS_NONE) which: int = 0
|
||||||
}
|
}
|
||||||
entry { print(KITS_COUNT) }
|
entry { print(KITS_COUNT) }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,20 +3,23 @@
|
||||||
#
|
#
|
||||||
# property Item {
|
# property Item {
|
||||||
# @Ref(Vendors) seller: int = 0 # an index into that registry (its entries are offered)
|
# @Ref(Vendors) seller: int = 0 # an index into that registry (its entries are offered)
|
||||||
# @OneOf(GR_) grade: int = 0 # one of the constants that start GR_
|
# @OneOf(GR_) grade: int = 0 # one of the constants that start GR_ ...
|
||||||
|
# @OneOf(WILD_MEADOW, WILD_SHORE) hab: int = 0 # ... or one of these (each must exist)
|
||||||
# @Range(0, 1.5) weight: float = 0.4
|
# @Range(0, 1.5) weight: float = 0.4
|
||||||
# @Unit("m/s") speed: float = 0.0
|
# @Unit("m/s") speed: float = 0.0
|
||||||
# @Asset("gltf") model: string = "" # a file of that kind
|
# @Asset("gltf") model: string = "" # a file of that kind
|
||||||
# @Color tint: int = 0
|
# @Color tint: int = 0
|
||||||
# @Node(model) grip: string = "" # a node inside the glTF the field `model` names
|
# @Node(model) grip: string = "" # a node inside the glTF the field `model` names
|
||||||
|
# @Clip(model) idle: string = "" # a clip inside that glTF
|
||||||
# @Derived reach: float = 0.0 # worked out at boot: anything written is overwritten
|
# @Derived reach: float = 0.0 # worked out at boot: anything written is overwritten
|
||||||
# @Text @Multiline blurb: string = "" # read by the player, so translated; prose
|
# @Text @Multiline blurb: string = "" # read by the player, so translated; prose
|
||||||
# @Key bind: int = 0 # a key code
|
# @Key bind: int = 0 # a key code
|
||||||
# }
|
# }
|
||||||
# @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. Two are checked: @Ref must name a registry that exists, and
|
# They change nothing the program does. Three are checked: @Ref must name a registry that exists,
|
||||||
# @Node a field of its record that names a glTF (attrs_node.ludic). A
|
# @Node and @Clip a field of its record that names a glTF (attrs_node.ludic), and a listing @OneOf
|
||||||
|
# constants that exist. 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
|
||||||
# arguments as JSON values - an identifier or a string as a string, a number as written.
|
# arguments as JSON values - an identifier or a string as a string, a number as written.
|
||||||
var g_at_start: []int = new []int # a group's first attribute ...
|
var g_at_start: []int = new []int # a group's first attribute ...
|
||||||
|
|
@ -47,10 +50,12 @@ function at_push(g: int, name: pointer, args: pointer, first: pointer, nargs: in
|
||||||
var g_at_arg0: pointer = null
|
var g_at_arg0: pointer = null
|
||||||
var g_at_argn: int = 0
|
var g_at_argn: int = 0
|
||||||
var g_at_allnum: bool = true # every argument a number
|
var g_at_allnum: bool = true # every argument a number
|
||||||
|
var g_at_allid: bool = true # every argument a name
|
||||||
function at_args() -> pointer {
|
function at_args() -> pointer {
|
||||||
g_at_arg0 = null
|
g_at_arg0 = null
|
||||||
g_at_argn = 0
|
g_at_argn = 0
|
||||||
g_at_allnum = true
|
g_at_allnum = true
|
||||||
|
g_at_allid = true
|
||||||
if not is_op("(") { return "[]" }
|
if not is_op("(") { return "[]" }
|
||||||
pi += 1
|
pi += 1
|
||||||
var out: pointer = "["
|
var out: pointer = "["
|
||||||
|
|
@ -59,6 +64,7 @@ function at_args() -> pointer {
|
||||||
var neg = false
|
var neg = false
|
||||||
if is_op("-") { neg = true; pi += 1 }
|
if is_op("-") { neg = true; pi += 1 }
|
||||||
let t = toks[pi]
|
let t = toks[pi]
|
||||||
|
if t.kind != TK_ID { g_at_allid = false }
|
||||||
var v: pointer = null
|
var v: pointer = null
|
||||||
var raw: pointer = null
|
var raw: pointer = null
|
||||||
if t.kind == TK_INT {
|
if t.kind == TK_INT {
|
||||||
|
|
@ -98,7 +104,7 @@ function at_lead0(raw: pointer) -> pointer {
|
||||||
# the editor attributes a field may carry, and what each takes
|
# the editor attributes a field may carry, and what each takes
|
||||||
function at_field_known(a: pointer) -> bool {
|
function at_field_known(a: pointer) -> bool {
|
||||||
if (a == "Ref") or (a == "OneOf") or (a == "Range") or (a == "Unit") or (a == "Asset") or (a == "Color") { return true }
|
if (a == "Ref") or (a == "OneOf") or (a == "Range") or (a == "Unit") or (a == "Asset") or (a == "Color") { return true }
|
||||||
return (a == "Node") or at_flag(a)
|
return (a == "Node") or (a == "Clip") or at_flag(a)
|
||||||
}
|
}
|
||||||
# the attributes that take nothing: they say what a field is
|
# the attributes that take nothing: they say what a field is
|
||||||
function at_flag(a: pointer) -> bool {
|
function at_flag(a: pointer) -> bool {
|
||||||
|
|
@ -108,9 +114,10 @@ function at_flag(a: pointer) -> bool {
|
||||||
function at_field(g: int, a: pointer) -> void {
|
function at_field(g: int, a: pointer) -> void {
|
||||||
let args = at_args()
|
let args = at_args()
|
||||||
let n = g_at_argn
|
let n = g_at_argn
|
||||||
if (a == "Ref") or (a == "OneOf") or (a == "Node") {
|
if (a == "Ref") or (a == "Node") or (a == "Clip") {
|
||||||
if n != 1 or toks[pi - 2].kind != TK_ID { perr(`@{a} takes one name: @{a}(Items)`) }
|
if n != 1 or toks[pi - 2].kind != TK_ID { perr(`@{a} takes one name: @{a}(Items)`) }
|
||||||
}
|
}
|
||||||
|
if (a == "OneOf") and (n < 1 or not g_at_allid) { perr("@OneOf takes a prefix or constants: @OneOf(GR_) or @OneOf(GR_GOLD, GR_SILVER)") }
|
||||||
if (a == "Range") and n != 2 { perr("@Range takes the least and the most: @Range(0, 1.5)") }
|
if (a == "Range") and n != 2 { perr("@Range takes the least and the most: @Range(0, 1.5)") }
|
||||||
if (a == "Range") and not g_at_allnum { perr("@Range takes two numbers: @Range(0, 1.5)") }
|
if (a == "Range") and not g_at_allnum { perr("@Range takes two numbers: @Range(0, 1.5)") }
|
||||||
if ((a == "Unit") or (a == "Asset")) and (n != 1 or toks[pi - 2].kind != TK_STR) { perr(`@{a} takes one string: @{a}("m/s")`) }
|
if ((a == "Unit") or (a == "Asset")) and (n != 1 or toks[pi - 2].kind != TK_STR) { perr(`@{a} takes one string: @{a}("m/s")`) }
|
||||||
|
|
@ -145,7 +152,8 @@ function at_registry_take() -> int {
|
||||||
g_at_decl = new []pointer
|
g_at_decl = new []pointer
|
||||||
return g
|
return g
|
||||||
}
|
}
|
||||||
# once every registry is declared: each @Ref names one and each @Node a glTF field, or the build
|
# once every registry is declared: each @Ref names one, each @Node / @Clip a glTF field and a
|
||||||
|
# listing @OneOf constants that exist, or the build
|
||||||
# stops with every one that does not
|
# stops with every one that does not
|
||||||
function at_check_refs() -> void {
|
function at_check_refs() -> void {
|
||||||
var i = 0
|
var i = 0
|
||||||
|
|
@ -170,7 +178,8 @@ function at_check_field(d: Node, f: Node) -> void {
|
||||||
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 {
|
||||||
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]}`)
|
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" { at_check_node(d, f, 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) }
|
||||||
j += 1
|
j += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# attrs_node.ludic — @Node(model): a string naming a node inside the glTF another field of the same
|
# attrs_node.ludic — @Node(model) / @Clip(model): a string naming a node or a clip inside the glTF
|
||||||
# record names. That field is @Asset("gltf") itself, or an @Ref(R) whose record has exactly one
|
# another field of the same record names. That field is @Asset("gltf") itself, or an @Ref(R) whose record has exactly one
|
||||||
# @Asset("gltf") field (the model is found through the row). Anything else is an error naming it.
|
# @Asset("gltf") field (the model is found through the row). Anything else is an error naming it.
|
||||||
|
|
||||||
# the field of record d called name, or null
|
# the field of record d called name, or null
|
||||||
|
|
@ -45,8 +45,8 @@ function at_gltf_count(r: Node) -> int {
|
||||||
}
|
}
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
function at_check_node(d: Node, f: Node, target: pointer) -> void {
|
function at_check_node(d: Node, f: Node, a: pointer, target: pointer) -> void {
|
||||||
let where = `field {d.s}.{f.s}: @Node({target})`
|
let where = `field {d.s}.{f.s}: @{a}({target})`
|
||||||
let t = at_field_of(d, target)
|
let t = at_field_of(d, target)
|
||||||
if t == null {
|
if t == null {
|
||||||
perr_at(f.file, f.line, f.col, `{where} names no field - {d.s} has no field {target}`)
|
perr_at(f.file, f.line, f.col, `{where} names no field - {d.s} has no field {target}`)
|
||||||
|
|
@ -67,3 +67,41 @@ function at_check_node(d: Node, f: Node, target: pointer) -> void {
|
||||||
perr_at(f.file, f.line, f.col, `{where}: {target} is an @Ref({reg}), and its record {g_rg_type[r]} has {n} @Asset("gltf") fields, not one`)
|
perr_at(f.file, f.line, f.col, `{where}: {target} is an @Ref({reg}), and its record {g_rg_type[r]} has {n} @Asset("gltf") fields, not one`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# @OneOf: one argument ending in _ is a prefix; otherwise every argument is a constant that must exist
|
||||||
|
function at_check_oneof(d: Node, f: Node, j: int) -> void {
|
||||||
|
let first = g_at_first[j]
|
||||||
|
if g_at_nargs[j] == 1 and first[len(first) - 1] == '_' { return }
|
||||||
|
let names = at_arg_names(g_at_args[j])
|
||||||
|
var i = 0
|
||||||
|
while i < len(names) {
|
||||||
|
if not at_const_exists(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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# the names in an attribute's JSON arguments (["A", "B"]): each a quoted identifier
|
||||||
|
function at_arg_names(args: pointer) -> []pointer {
|
||||||
|
let out = new []pointer
|
||||||
|
var i = 0
|
||||||
|
var at = -1
|
||||||
|
while i < len(args) {
|
||||||
|
if args[i] == '"' {
|
||||||
|
if at < 0 { at = i + 1 } else {
|
||||||
|
push(out, args[at .. i])
|
||||||
|
at = -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
function at_const_exists(name: pointer) -> bool {
|
||||||
|
var i = 0
|
||||||
|
while i < len(prog) {
|
||||||
|
if prog[i].kind == N_CONST and prog[i].s != null and vis_plain(prog[i].s) == name { return true }
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
|
||||||
70649
selfhost/ludicc.seed.ll
70649
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
|
|
@ -1200,7 +1200,7 @@ function cmd_dev_test() -> int {
|
||||||
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) names no registry", "--diagnostics=json: every @Ref that names no registry")
|
||||||
diag_json_case("rejected/node_bad", 3, "its record Kit has 2 @Asset(", "--diagnostics=json: every @Node that names no model field")
|
diag_json_case("rejected/node_bad", 5, "@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")
|
||||||
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")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue