schema: every function a fn value can name, not only zero-argument ones - fn_type is the type a field sees with the states stripped, spelled as a field's type is (fn(A,B)->R), with params beside states; reseeded

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-29 18:15:04 +03:00
parent 863b9712f9
commit b647964839
7 changed files with 18325 additions and 18333 deletions

View file

@ -661,8 +661,10 @@ object with `"schema_version": 1`:
"index": 0, "file": ..., "line": ..., "col": ..., "fields": [{"name", "value", "file", "line",
"col"}]}`), with `contributors`: which resource file or file of `def`s brought which keys in;
- `consts` - every const: its type, its value as written, its module and doc;
- `functions` - every function a `fn` value can name with no arguments (the states it takes are the
runtime's to supply): its return type, `fn_type` and signature.
- `functions` - every function a `fn` value can name, exported or not: its module, return type,
`params` (the arguments a caller passes), `states` (what the runtime supplies), `signature`, and
`fn_type` - the type a `fn` field sees, the states stripped, spelled as a field's type is
(`fn(NpcPerson,float)->bool`), so matching a function to a field is comparing two strings.
Each list is sorted by name (then file and line), a registry's entries are in index order, and the
paths are the ones the compiler read, so two runs over the same source write the same file. The

View file

@ -5,7 +5,7 @@ schema [file] [-o FILE]`) writes what the compiler resolved once the program typ
record with its fields' types, defaults, doc comments and places; every registry with its record,
prefix, resource file, openness and its entries in their final order after the open-registry merge
(key, constant, index, file:line:col of the entry and of each field value, and which file brought
which entries in); every const; and the functions a `fn` value can name with no arguments.
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;

View file

@ -32,6 +32,8 @@ program Attributes {
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 }
# what a pack of n of a tool weighs - a fn(Tool,int)->float an editor can offer a field of that type
function pack_weight(t: Tool, n: int) -> float { return t.weight * float(n) }
entry {
print(`{TL_COUNT} {Tools[TL_LANTERN].seller} {Tools[TL_LANTERN].name} {CAMP_TOOLS / 2}`)
}

View file

@ -8,8 +8,8 @@
# entries in their final order - key, constant, index, where each entry and each of its
# field values is written - with which file contributed which entries
# consts every const: its type and its value as written
# functions every function a `fn` value can name with no arguments (the states it takes are the
# runtime's to supply)
# functions every function a `fn` value can name: fn_type is the type a field sees, the states
# stripped (the runtime supplies them), written as a field's type is ("fn(A,B)->R")
#
# Deterministic: each list sorted by name (then file and line), an entry list in index order, the
# paths as the compiler read them, and nothing about the run. The engine's runtime is left out.
@ -529,13 +529,6 @@ function sc_functions(b: Buf) -> void {
let d = prog[i]
var ok = d.kind == N_FN and d.s != null and d.tps == null and sc_written(d)
if ok { ok = not sc_generic_fn(gens, d.s) }
if ok {
var k = 0
while k < len(d.kids) {
if d.kids[k].kind == N_PARAM and not sc_is_state_param(d.kids[k]) { ok = false }
k += 1
}
}
if ok { push(xs, i); push(keys, sc_key(sc_name(d.s), d.file, d.line)) }
else { push(keys, "") }
i += 1
@ -550,6 +543,8 @@ function sc_functions(b: Buf) -> void {
function sc_function(b: Buf, d: Node, first: bool) -> void {
let ret = sc_ty(d.ty)
var ps: pointer = ""
var args: pointer = ""
var params: pointer = ""
var states: pointer = ""
var k = 0
while k < len(d.kids) {
@ -561,8 +556,15 @@ function sc_function(b: Buf, d: Node, first: bool) -> void {
ps = ps + p.s + ": " + mt + sc_ty(p.ty)
let bq = buf_new()
jq_put(bq, sc_ty(p.ty))
if len(states) > 0 { states = states + ", " }
states = states + buf_str(bq)
if sc_is_state_param(p) {
if len(states) > 0 { states = states + ", " }
states = states + buf_str(bq)
} else {
if len(args) > 0 { args = args + "," }
args = args + sc_ty(p.ty)
if len(params) > 0 { params = params + ", " }
params = params + buf_str(bq)
}
}
k += 1
}
@ -578,7 +580,10 @@ function sc_function(b: Buf, d: Node, first: bool) -> void {
buf_puts(b, ", \"returns\": ")
jq_put(b, ret)
buf_puts(b, ", \"fn_type\": ")
jq_put(b, `fn() -> {ret}`)
jq_put(b, "fn(" + args + ")->" + ret)
buf_puts(b, ", \"params\": [")
buf_puts(b, params)
buf_puts(b, "]")
buf_puts(b, ", \"signature\": ")
jq_put(b, `function {sc_name(d.s)}({ps}) -> {ret}`)
buf_puts(b, ", \"states\": [")

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

View file

@ -1234,6 +1234,7 @@ function cmd_dev_test() -> int {
reject_case("rejected/ref_unknown", "field Tool.seller: @Ref(Vendorz) names no registry", "@Ref names a registry that exists")
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")
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)")