@Asset(kind, map): a path under each map's directory - ludicc --check looks for it in every map (a @PerMap row's in its own, a game-wide row's in all), unless @Asset(kind, map, optional); the schema marks it scope map; reseeded
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
9cc67fee74
commit
af90a1a334
10 changed files with 79959 additions and 77965 deletions
|
|
@ -731,6 +731,7 @@ property Tool {
|
||||||
@OneOf(GR_GOLD, GR_SILVER) medal: int = 0 # or one of these constants
|
@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)
|
||||||
|
@Asset("png", map) density: string = "" # a path under EACH map's directory (assets/maps/<key>/...)
|
||||||
@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
|
@Clip(model) swing: string = "" # a clip inside it
|
||||||
|
|
@ -759,7 +760,11 @@ Everything else is an error, every one reported: a target that exists but is ano
|
||||||
arguments are words (`@OneOf("box", "hull")`) and every registry row's value must be one of them, on
|
arguments are words (`@OneOf("box", "hull")`) and every registry row's value must be one of them, on
|
||||||
any other field a prefix ending in `_` or constants; and `@Node(f)`, `@Clip(f)` or `@Material(f)`
|
any other field a prefix ending in `_` or constants; and `@Node(f)`, `@Clip(f)` or `@Material(f)`
|
||||||
naming anything but a field `f` of the same record that is `@Asset("gltf")`, or an `@Ref` to a
|
naming anything but a field `f` of the same record that is `@Asset("gltf")`, or an `@Ref` to a
|
||||||
registry whose record has exactly one `@Asset("gltf")` field (the model is then the row's).
|
registry whose record has exactly one `@Asset("gltf")` field (the model is then the row's). `@Asset(kind, map)` names a file under each map's
|
||||||
|
directory, not the game's root: `ludicc --check` looks for it in every map - a @PerMap row's in its own
|
||||||
|
map, a game-wide row's in all of them - and refuses a map that lacks it, unless the field says
|
||||||
|
`@Asset(kind, map, optional)`; the schema marks it `"scope": "map"`. A plain `@Asset(kind)` is not
|
||||||
|
looked for.
|
||||||
|
|
||||||
`ludicc app.ludic --emit-schema out.json` (or `ludic schema [file] [-o out.json]`) writes what the
|
`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
|
compiler resolved, once the types are checked and every open registry has its entries, as one JSON
|
||||||
|
|
|
||||||
6
changes/asset-map.md
Normal file
6
changes/asset-map.md
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
bump: minor
|
||||||
|
type: feature
|
||||||
|
**`@Asset(kind, map)`: a path under each map's directory.** A field whose file lives in the map's own folder
|
||||||
|
(a grass kind's density picture, `ground/blades.png`) says so, and `ludicc --check` looks for it in every
|
||||||
|
map - a @PerMap row's in its map, a game-wide row's in all of them - refusing a map that lacks it unless the
|
||||||
|
field is `@Asset(kind, map, optional)`. The schema marks the attribute `"scope": "map"`.
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
# permap_maps.ludic - `ludicc --check` reads every map directory and checks its tables against their
|
# permap_maps.ludic - `ludicc --check` reads every map directory and checks its tables against their
|
||||||
# records. maps/m1 holds five mistakes, each refused at its file, line and column: a field the
|
# records. maps/m1 holds six mistakes, each refused at its file, line and column: a field the record
|
||||||
# record lacks, a value of the wrong type, a constant nobody declared, a key naming no row, and a
|
# lacks, a value of the wrong type, a constant nobody declared, a key naming no row, a chunked table's
|
||||||
# chunked table's key written in two of the map's chunk files (t1, in trees/-1_0 and trees/0_0).
|
# key written in two chunk files (t1, in trees/-1_0 and trees/0_0), and a game-wide row's
|
||||||
|
# @Asset(..., map) file the map lacks (blades.png; the optional one, wild.png, may be missing).
|
||||||
program PerMapMaps {
|
program PerMapMaps {
|
||||||
numbers float
|
numbers float
|
||||||
const MDL_TENT: int = 7
|
const MDL_TENT: int = 7
|
||||||
|
|
@ -15,6 +16,13 @@ program PerMapMaps {
|
||||||
}
|
}
|
||||||
@PerMap @ByKey registry Props of PropRow from "props.lres"
|
@PerMap @ByKey registry Props of PropRow from "props.lres"
|
||||||
property Tree { key: string = "", x: float = 0.0 }
|
property Tree { key: string = "", x: float = 0.0 }
|
||||||
|
property Grass {
|
||||||
|
key: string = ""
|
||||||
|
@Asset("png", map) density: string = ""
|
||||||
|
@Asset("png", map, optional) wild: string = ""
|
||||||
|
}
|
||||||
|
registry Grasses of Grass
|
||||||
|
def Grasses meadow { density: "blades.png", wild: "wild.png" }
|
||||||
@PerMap @Chunked(64) registry Trees of Tree from "trees/{cx}_{cz}.lres"
|
@PerMap @Chunked(64) registry Trees of Tree from "trees/{cx}_{cz}.lres"
|
||||||
entry (props: mut Props) {
|
entry (props: mut Props) {
|
||||||
print(`{props_load(props, "m1")}`)
|
print(`{props_load(props, "m1")}`)
|
||||||
|
|
|
||||||
|
|
@ -191,6 +191,7 @@ function sc_attrs(b: Buf, g: int) -> void {
|
||||||
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") }
|
if g_at_unres[j] { buf_puts(b, ", \"unresolved\": true") }
|
||||||
|
if (g_at_name[j] == "Asset") and len(at_arg_names(g_at_args[j])) >= 2 { buf_puts(b, ", \"scope\": \"map\"") }
|
||||||
if (g_at_name[j] == "Ref") and g_at_first[j] != null and pm_named(g_at_first[j]) { buf_puts(b, ", \"scope\": \"map\"") }
|
if (g_at_name[j] == "Ref") and g_at_first[j] != null and pm_named(g_at_first[j]) { buf_puts(b, ", \"scope\": \"map\"") }
|
||||||
buf_puts(b, "}")
|
buf_puts(b, "}")
|
||||||
j += 1
|
j += 1
|
||||||
|
|
|
||||||
|
|
@ -135,11 +135,21 @@ function at_field(g: int, a: pointer) -> void {
|
||||||
g_at_strs_next = (a == "OneOf") and g_at_allstr
|
g_at_strs_next = (a == "OneOf") and g_at_allstr
|
||||||
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") and (n != 1 or toks[pi - 2].kind != TK_STR) { perr(`@{a} takes one string: @{a}("m/s")`) }
|
||||||
|
if (a == "Asset") and not at_asset_ok(args, n) { perr("@Asset takes a kind, and `map` for a path under each map's directory (then `optional`): @Asset(\"png\"), @Asset(\"png\", map), @Asset(\"png\", map, optional)") }
|
||||||
if a == "Unit" { at_unit_check(g_at_arg0, toks[pi - 2]) }
|
if a == "Unit" { at_unit_check(g_at_arg0, toks[pi - 2]) }
|
||||||
if at_flag(a) and n != 0 { perr(`@{a} takes nothing`) }
|
if at_flag(a) and n != 0 { perr(`@{a} takes nothing`) }
|
||||||
at_push(g, a, args, g_at_arg0, n)
|
at_push(g, a, args, g_at_arg0, n)
|
||||||
}
|
}
|
||||||
|
# @Asset("png"), @Asset("png", map) or @Asset("png", map, optional)
|
||||||
|
function at_asset_ok(args: pointer, n: int) -> bool {
|
||||||
|
if n < 1 or n > 3 { return false }
|
||||||
|
let w = at_arg_names(args)
|
||||||
|
if len(w) != n { return false }
|
||||||
|
if n >= 2 and not (w[1] == "map") { return false }
|
||||||
|
if n == 3 and not (w[2] == "optional") { return false }
|
||||||
|
return true
|
||||||
|
}
|
||||||
# a field's group, made the first time it needs one
|
# a field's group, made the first time it needs one
|
||||||
function at_field_group(f: Node) -> int {
|
function at_field_group(f: Node) -> int {
|
||||||
if f.at < 0 { f.at = at_group() }
|
if f.at < 0 { f.at = at_group() }
|
||||||
|
|
|
||||||
|
|
@ -167,3 +167,18 @@ function at_decl_named(name: pointer) -> bool {
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
# a field's @Asset: 0 none or a game path, 1 a path under each map's directory, 2 the same and optional
|
||||||
|
function at_asset_map(f: Node) -> int {
|
||||||
|
if f.at < 0 { return 0 }
|
||||||
|
var j = g_at_start[f.at]
|
||||||
|
while j < g_at_start[f.at] + g_at_count[f.at] {
|
||||||
|
if g_at_name[j] == "Asset" {
|
||||||
|
let w = at_arg_names(g_at_args[j])
|
||||||
|
if len(w) >= 3 { return 2 }
|
||||||
|
if len(w) == 2 { return 1 }
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
j += 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -141,6 +141,7 @@ var g_pmk_ref_reg: []pointer = new []pointer # per cross-row @Ref met: the tabl
|
||||||
var g_pmk_ref_key: []pointer = new []pointer
|
var g_pmk_ref_key: []pointer = new []pointer
|
||||||
var g_pmk_ref_at: []Node = new []Node
|
var g_pmk_ref_at: []Node = new []Node
|
||||||
var g_pmk_mod: pointer = "" # the module of the registry being checked
|
var g_pmk_mod: pointer = "" # the module of the registry being checked
|
||||||
|
var g_pmk_md: pointer = "" # the map directory being checked
|
||||||
|
|
||||||
function pmk_read(r: int, path: pointer) -> void {
|
function pmk_read(r: int, path: pointer) -> void {
|
||||||
let src = read_file(path)
|
let src = read_file(path)
|
||||||
|
|
@ -186,6 +187,7 @@ function pm_check_maps() -> void {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function pmk_map(md: pointer) -> void {
|
function pmk_map(md: pointer) -> void {
|
||||||
|
g_pmk_md = md
|
||||||
g_pmk_reg = new []int
|
g_pmk_reg = new []int
|
||||||
g_pmk_key = new []pointer
|
g_pmk_key = new []pointer
|
||||||
g_pmk_rec = new []Node
|
g_pmk_rec = new []Node
|
||||||
|
|
@ -217,6 +219,7 @@ function pmk_map(md: pointer) -> void {
|
||||||
e += 1
|
e += 1
|
||||||
}
|
}
|
||||||
pmk_refs()
|
pmk_refs()
|
||||||
|
pmk_game_assets()
|
||||||
}
|
}
|
||||||
# a key written twice in one file
|
# a key written twice in one file
|
||||||
function pmk_dups() -> void {
|
function pmk_dups() -> void {
|
||||||
|
|
@ -414,6 +417,57 @@ function pmk_has_dot(t: pointer) -> bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ---- map-relative assets: @Asset("png", map) -----------------------------------------------------
|
||||||
|
function pmk_exists(p: pointer) -> bool {
|
||||||
|
let fh = file_open(p, "rb")
|
||||||
|
if fh == null { return false }
|
||||||
|
file_close(fh)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
# a value of an @Asset(kind, map) field, at node at: the file is in this map's directory, unless optional
|
||||||
|
function pmk_asset(at: Node, f: Node, v: pointer) -> void {
|
||||||
|
let m = at_asset_map(f)
|
||||||
|
if m != 1 { return }
|
||||||
|
let p = `{g_pmk_md}/{v}`
|
||||||
|
if not pmk_exists(p) { pmk_err_at(at, `{f.s} is "{v}", and this map has no {p} - @Asset(..., map) is a path under each map's directory`) }
|
||||||
|
}
|
||||||
|
# a game-wide registry's rows whose @Asset(kind, map) field names a file: that file in every map
|
||||||
|
function pmk_game_assets() -> void {
|
||||||
|
var r = 0
|
||||||
|
while r < len(g_rg_name) {
|
||||||
|
if not pm_is(r) { pmk_game_asset_reg(r) }
|
||||||
|
r += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function pmk_game_asset_reg(r: int) -> void {
|
||||||
|
let comp = find_comp(g_rg_type[r])
|
||||||
|
if comp == null { return }
|
||||||
|
var d = 0
|
||||||
|
while d < len(g_df_rec) {
|
||||||
|
if (g_df_reg[d] == g_rg_name[r]) { pmk_game_asset_row(comp, g_df_rec[d]) }
|
||||||
|
d += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function pmk_game_asset_row(comp: Node, rec: Node) -> void {
|
||||||
|
var k = 0
|
||||||
|
while k < len(rec.kids) {
|
||||||
|
let fi = rec.kids[k]
|
||||||
|
if fi.kind == E_FINIT and fi.a != null and fi.a.kind == E_STR and len(fi.a.s) > 0 {
|
||||||
|
let f = pmk_field_of(comp, fi.s)
|
||||||
|
if f != null { pmk_asset(fi, f, fi.a.s) }
|
||||||
|
}
|
||||||
|
k += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function pmk_field_of(comp: Node, name: pointer) -> Node {
|
||||||
|
var i = 0
|
||||||
|
while i < len(comp.kids) {
|
||||||
|
if comp.kids[i].s != null and (comp.kids[i].s == name) { return comp.kids[i] }
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
# ---- attributes ---------------------------------------------------------------------------------
|
# ---- attributes ---------------------------------------------------------------------------------
|
||||||
function pmk_attrs_str(e: Node, f: Node) -> void {
|
function pmk_attrs_str(e: Node, f: Node) -> void {
|
||||||
if f.at < 0 { return }
|
if f.at < 0 { return }
|
||||||
|
|
@ -424,6 +478,7 @@ function pmk_attrs_str(e: Node, f: Node) -> void {
|
||||||
let words = at_arg_names(g_at_args[j])
|
let words = at_arg_names(g_at_args[j])
|
||||||
if not at_word_in(e.s, words) { pmk_err_at(e, `{f.s} is "{e.s}", and it is @OneOf({at_words_text(words)})`) }
|
if not at_word_in(e.s, words) { pmk_err_at(e, `{f.s} is "{e.s}", and it is @OneOf({at_words_text(words)})`) }
|
||||||
}
|
}
|
||||||
|
if (a == "Asset") and len(e.s) > 0 { pmk_asset(e, f, e.s) }
|
||||||
if (a == "Ref") and pm_named(g_at_first[j]) and len(e.s) > 0 {
|
if (a == "Ref") and pm_named(g_at_first[j]) and len(e.s) > 0 {
|
||||||
push(g_pmk_ref_reg, g_at_first[j])
|
push(g_pmk_ref_reg, g_at_first[j])
|
||||||
push(g_pmk_ref_key, e.s)
|
push(g_pmk_ref_key, e.s)
|
||||||
|
|
|
||||||
76189
selfhost/ludicc.seed.ll
76189
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
|
|
@ -1278,11 +1278,12 @@ function cmd_dev_test() -> int {
|
||||||
schema_case("lang/permap/permap", "\"units\": [\"m\", \"m/s\", \"m/s2\"", "--emit-schema: the canonical @Unit spellings")
|
schema_case("lang/permap/permap", "\"units\": [\"m\", \"m/s\", \"m/s2\"", "--emit-schema: the canonical @Unit spellings")
|
||||||
reject_case("rejected/permap_ref_int", "names a @PerMap registry, whose rows have no index a program can hold - use a string key", "@Ref into a @PerMap registry is on a string field")
|
reject_case("rejected/permap_ref_int", "names a @PerMap registry, whose rows have no index a program can hold - use a string key", "@Ref into a @PerMap registry is on a string field")
|
||||||
reject_case("rejected/permap_prefix", "a @PerMap registry has no 'as PREFIX'", "a @PerMap registry has no prefix and no constants")
|
reject_case("rejected/permap_prefix", "a @PerMap registry has no 'as PREFIX'", "a @PerMap registry has no prefix and no constants")
|
||||||
diag_json_case("rejected/permap_maps/permap_maps", 5, "\"line\": 2, \"col\": 18, \"severity\": \"error\", \"message\": \"PropRow has no field colour\"", "--check reads every map: a field its record lacks, at the map file's line and column")
|
diag_json_case("rejected/permap_maps/permap_maps", 6, "\"line\": 2, \"col\": 18, \"severity\": \"error\", \"message\": \"PropRow has no field colour\"", "--check reads every map: a field its record lacks, at the map file's line and column")
|
||||||
diag_json_case("rejected/permap_maps/permap_maps", 5, "model wants an int, not a string", "--check reads every map: a value of the wrong type")
|
diag_json_case("rejected/permap_maps/permap_maps", 6, "model wants an int, not a string", "--check reads every map: a value of the wrong type")
|
||||||
diag_json_case("rejected/permap_maps/permap_maps", 5, "unknown constant MDL_BENCH", "--check reads every map: a constant nobody declared")
|
diag_json_case("rejected/permap_maps/permap_maps", 6, "unknown constant MDL_BENCH", "--check reads every map: a constant nobody declared")
|
||||||
diag_json_case("rejected/permap_maps/permap_maps", 5, "there is no Spots row nowhere in this map", "--check reads every map: a cross-row @Ref names a row of the same map")
|
diag_json_case("rejected/permap_maps/permap_maps", 6, "there is no Spots row nowhere in this map", "--check reads every map: a cross-row @Ref names a row of the same map")
|
||||||
diag_json_case("rejected/permap_maps/permap_maps", 5, "the key t1 is also written at", "--check reads every map: a chunked table's key is unique across the map's chunk files, both places named")
|
diag_json_case("rejected/permap_maps/permap_maps", 6, "the key t1 is also written at", "--check reads every map: a chunked table's key is unique across the map's chunk files, both places named")
|
||||||
|
diag_json_case("rejected/permap_maps/permap_maps", 6, "this map has no examples/rejected/permap_maps/maps/m1/blades.png", "--check reads every map: a game-wide row's @Asset(..., map) file is in each map's directory, unless optional")
|
||||||
feat_case("lang/defaults", "", "a 300.0 8.0 / b 120.0 8.0 / c! 300.0 8.0 / d 300.0 2.0 / x / y", "defaults.ludic (L11: default parameters, positional then named arguments, in a generic too)")
|
feat_case("lang/defaults", "", "a 300.0 8.0 / b 120.0 8.0 / c! 300.0 8.0 / d 300.0 2.0 / x / y", "defaults.ludic (L11: default parameters, positional then named arguments, in a generic too)")
|
||||||
reject_case("rejected/default_missing", "this call to f leaves out a, which has no default", "a call leaves out only what has a default")
|
reject_case("rejected/default_missing", "this call to f leaves out a, which has no default", "a call leaves out only what has a default")
|
||||||
reject_case("rejected/default_unknown", "f has no parameter c", "a named argument names a parameter the function has")
|
reject_case("rejected/default_unknown", "f has no parameter c", "a named argument names a parameter the function has")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue