attributes: @Node(field), @Derived, @Text, @Multiline and @Key on a field, into the schema - @Node's field must be @Asset("gltf") or an @Ref to a registry whose record has exactly one, every failure reported; reseeded
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
b647964839
commit
ebfbabdfd8
10 changed files with 73439 additions and 71415 deletions
11
LANGUAGE.md
11
LANGUAGE.md
|
|
@ -639,14 +639,19 @@ property Tool {
|
|||
@Range(0, 20.5) @Unit("kg") weight: float = 1.0
|
||||
@Asset("gltf") model: string = "" # a file of that kind (any string)
|
||||
@Color tint: int = 0
|
||||
@Node(model) grip: string = "" # a node inside the glTF that `model` names
|
||||
@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
|
||||
@Key bind: int = 0 # a key code
|
||||
}
|
||||
@AppendOnly @ByKey
|
||||
registry Tools of Tool as TL from "data/tools.lres"
|
||||
```
|
||||
|
||||
They change nothing the program does. One is checked: `@Ref` must name a registry the program
|
||||
declares, and a field whose `@Ref` names none is an error naming the field and the registry (every
|
||||
such field is reported, not only the first).
|
||||
They change nothing the program does. Two 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
|
||||
`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).
|
||||
|
||||
`ludicc app.ludic --emit-schema out.json` (or `ludic schema [file] [-o out.json]`) writes what the
|
||||
compiler resolved, once the types are checked and every open registry has its entries, as one JSON
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ 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).
|
||||
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")`,
|
||||
`@Color`, and `@AppendOnly` / `@ByKey` on a registry - which change nothing but go into the schema;
|
||||
`@Ref` naming no registry is an error. A field may now carry several attributes. `ludicc --check
|
||||
`@Color`, `@Node(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
|
||||
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
|
||||
--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.
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
# attributes.ludic — editor metadata on the @-syntax: which registry a field indexes, which constants
|
||||
# it takes, its range, its unit, the kind of file it names, that it is a colour, and a registry's own
|
||||
# rules (append only, saved by key). None of them changes what the program does: `ludicc
|
||||
# --emit-schema` carries them to an editor with every record, registry entry and const. One is
|
||||
# checked - @Ref must name a registry that exists.
|
||||
# --emit-schema` carries them to an editor with every record, registry entry and const. Two are
|
||||
# checked - @Ref must name a registry that exists, and @Node a field of its record naming a glTF.
|
||||
#
|
||||
# Running it prints: 2 1 Lantern 2
|
||||
program Attributes {
|
||||
|
|
@ -26,9 +26,18 @@ program Attributes {
|
|||
model: string = "" # its model, under assets/kit
|
||||
@Color tint: int = 16777215
|
||||
@OneOf(VD_) stocked_at: int = VD_STORE
|
||||
@Node(model) grip: string = "grip" # where a hand holds it, a node in that model
|
||||
@Derived reach: float = 0.0 # worked out at boot from the model
|
||||
@Text @Multiline blurb: string = "" # what the shop says about it
|
||||
@Key bind: int = 0 # the key that takes it in hand
|
||||
}
|
||||
@AppendOnly
|
||||
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
|
||||
property RackSlot {
|
||||
@Ref(Tools) tool: int = 0
|
||||
@Node(tool) hook: string = "grip"
|
||||
}
|
||||
const CAMP_TOOLS: int = 2 * VD_COUNT # how many a camp can carry
|
||||
# the tool a new camp starts with
|
||||
function first_tool() -> string { return Tools[0].name }
|
||||
|
|
|
|||
18
examples/rejected/node_bad.ludic
Normal file
18
examples/rejected/node_bad.ludic
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# attributes: @Node names a field of its record that names a glTF - `handle` names no field, `tag`
|
||||
# is a plain string, and `kit` refers to a registry whose record has two models (all three are reported)
|
||||
program R {
|
||||
property Kit {
|
||||
key: string = ""
|
||||
@Asset("gltf") body: string = ""
|
||||
@Asset("gltf") lod: string = ""
|
||||
}
|
||||
registry Kits of Kit
|
||||
property Tool {
|
||||
tag: string = ""
|
||||
@Ref(Kits) kit: int = 0
|
||||
@Node(handle) grip: string = ""
|
||||
@Node(tag) tip: string = ""
|
||||
@Node(kit) hook: string = ""
|
||||
}
|
||||
entry { print(KITS_COUNT) }
|
||||
}
|
||||
|
|
@ -8,10 +8,15 @@
|
|||
# @Unit("m/s") speed: float = 0.0
|
||||
# @Asset("gltf") model: string = "" # a file of that kind
|
||||
# @Color tint: int = 0
|
||||
# @Node(model) grip: string = "" # a node inside the glTF the field `model` names
|
||||
# @Derived reach: float = 0.0 # worked out at boot: anything written is overwritten
|
||||
# @Text @Multiline blurb: string = "" # read by the player, so translated; prose
|
||||
# @Key bind: int = 0 # a key code
|
||||
# }
|
||||
# @AppendOnly @ByKey registry Items of Item as IT from "items.lres"
|
||||
#
|
||||
# They change nothing the program does. One is checked: @Ref must name a registry that exists. A
|
||||
# They change nothing the program does. Two are checked: @Ref must name a registry that exists, and
|
||||
# @Node a field of its record that names a glTF (attrs_node.ludic). A
|
||||
# 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.
|
||||
var g_at_start: []int = new []int # a group's first attribute ...
|
||||
|
|
@ -92,19 +97,24 @@ function at_lead0(raw: pointer) -> pointer {
|
|||
}
|
||||
# the editor attributes a field may carry, and what each takes
|
||||
function at_field_known(a: pointer) -> bool {
|
||||
return (a == "Ref") or (a == "OneOf") or (a == "Range") or (a == "Unit") or (a == "Asset") or (a == "Color")
|
||||
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)
|
||||
}
|
||||
# the attributes that take nothing: they say what a field is
|
||||
function at_flag(a: pointer) -> bool {
|
||||
return (a == "Color") or (a == "Derived") or (a == "Text") or (a == "Multiline") or (a == "Key")
|
||||
}
|
||||
# one field attribute, its '@' and name already read: its arguments, checked for shape, into group g
|
||||
function at_field(g: int, a: pointer) -> void {
|
||||
let args = at_args()
|
||||
let n = g_at_argn
|
||||
if (a == "Ref") or (a == "OneOf") {
|
||||
if (a == "Ref") or (a == "OneOf") or (a == "Node") {
|
||||
if n != 1 or toks[pi - 2].kind != TK_ID { perr(`@{a} takes one name: @{a}(Items)`) }
|
||||
}
|
||||
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 == "Unit") or (a == "Asset")) and (n != 1 or toks[pi - 2].kind != TK_STR) { perr(`@{a} takes one string: @{a}("m/s")`) }
|
||||
if (a == "Color") and n != 0 { perr("@Color takes nothing") }
|
||||
if at_flag(a) and n != 0 { perr(`@{a} takes nothing`) }
|
||||
at_push(g, a, args, g_at_arg0, n)
|
||||
}
|
||||
# a field's group, made the first time it needs one
|
||||
|
|
@ -135,7 +145,8 @@ function at_registry_take() -> int {
|
|||
g_at_decl = new []pointer
|
||||
return g
|
||||
}
|
||||
# once every registry is declared: each @Ref names one, or the build stops with every one that does not
|
||||
# once every registry is declared: each @Ref names one and each @Node a glTF field, or the build
|
||||
# stops with every one that does not
|
||||
function at_check_refs() -> void {
|
||||
var i = 0
|
||||
while i < len(prog) {
|
||||
|
|
@ -159,6 +170,7 @@ function at_check_field(d: Node, f: Node) -> void {
|
|||
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]}`)
|
||||
}
|
||||
if g_at_name[j] == "Node" { at_check_node(d, f, g_at_first[j]) }
|
||||
j += 1
|
||||
}
|
||||
}
|
||||
|
|
|
|||
69
selfhost/frontend/attrs_node.ludic
Normal file
69
selfhost/frontend/attrs_node.ludic
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
# attrs_node.ludic — @Node(model): a string naming a node inside the glTF 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.
|
||||
|
||||
# the field of record d called name, or null
|
||||
function at_field_of(d: Node, name: pointer) -> Node {
|
||||
var k = 0
|
||||
while k < len(d.kids) {
|
||||
if d.kids[k].s != null and vis_plain(d.kids[k].s) == name { return d.kids[k] }
|
||||
k += 1
|
||||
}
|
||||
return null
|
||||
}
|
||||
# field f's argument to attribute a as written, or null when f does not carry it
|
||||
function at_arg_of(f: Node, a: pointer) -> pointer {
|
||||
if f.at < 0 { return null }
|
||||
var j = g_at_start[f.at]
|
||||
while j < g_at_start[f.at] + g_at_count[f.at] {
|
||||
if g_at_name[j] == a { return g_at_first[j] }
|
||||
j += 1
|
||||
}
|
||||
return null
|
||||
}
|
||||
function at_is_gltf(f: Node) -> bool {
|
||||
let a = at_arg_of(f, "Asset")
|
||||
return a != null and a == "gltf"
|
||||
}
|
||||
# the record called name (a property or record), or null
|
||||
function at_record(name: pointer) -> Node {
|
||||
let plain = vis_plain(name)
|
||||
var i = 0
|
||||
while i < len(prog) {
|
||||
if prog[i].kind == N_COMP and prog[i].s != null and vis_plain(prog[i].s) == plain { return prog[i] }
|
||||
i += 1
|
||||
}
|
||||
return null
|
||||
}
|
||||
# how many @Asset("gltf") fields record r carries
|
||||
function at_gltf_count(r: Node) -> int {
|
||||
var n = 0
|
||||
var k = 0
|
||||
while k < len(r.kids) {
|
||||
if at_is_gltf(r.kids[k]) { n += 1 }
|
||||
k += 1
|
||||
}
|
||||
return n
|
||||
}
|
||||
function at_check_node(d: Node, f: Node, target: pointer) -> void {
|
||||
let where = `field {d.s}.{f.s}: @Node({target})`
|
||||
let t = at_field_of(d, target)
|
||||
if t == null {
|
||||
perr_at(f.file, f.line, f.col, `{where} names no field - {d.s} has no field {target}`)
|
||||
return
|
||||
}
|
||||
if at_is_gltf(t) { return }
|
||||
let reg = at_arg_of(t, "Ref")
|
||||
if reg == null {
|
||||
perr_at(f.file, f.line, f.col, `{where}: {target} is neither @Asset("gltf") nor an @Ref to a registry of models`)
|
||||
return
|
||||
}
|
||||
let r = reg_find(reg)
|
||||
if r < 0 { return } # @Ref's own check says so
|
||||
let rec = at_record(g_rg_type[r])
|
||||
var n = 0
|
||||
if rec != null { n = at_gltf_count(rec) }
|
||||
if n != 1 {
|
||||
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`)
|
||||
}
|
||||
}
|
||||
70632
selfhost/ludicc.seed.ll
70632
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
|
|
@ -33,6 +33,7 @@ function selfhost_frags() -> []pointer {
|
|||
push(f, "selfhost/frontend/registry_finish.ludic")
|
||||
push(f, "selfhost/frontend/registry_open.ludic")
|
||||
push(f, "selfhost/frontend/attrs.ludic")
|
||||
push(f, "selfhost/frontend/attrs_node.ludic")
|
||||
push(f, "selfhost/frontend/resource.ludic")
|
||||
push(f, "selfhost/frontend/defaults.ludic")
|
||||
push(f, "selfhost/frontend/view.ludic")
|
||||
|
|
|
|||
|
|
@ -1200,6 +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")
|
||||
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/node_bad", 3, "its record Kit has 2 @Asset(", "--diagnostics=json: every @Node that names no model field")
|
||||
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_mismatch", "words wants a Pool<string> and this is a Pool<int>", "two instances of one generic are two types")
|
||||
|
|
@ -1232,9 +1233,11 @@ function cmd_dev_test() -> int {
|
|||
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)")
|
||||
reject_case("rejected/ref_unknown", "field Tool.seller: @Ref(Vendorz) names no registry", "@Ref names a registry that exists")
|
||||
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", "{\"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", "{\"name\": \"Node\", \"args\": [\"tool\"]}", "--emit-schema: @Node and the field it reads its model from")
|
||||
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")
|
||||
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