schema: ludicc --emit-schema / ludic schema, --check --diagnostics=json, and editor attributes

--emit-schema FILE writes the compiler's resolved view once the program type-checks: every
record (fields, types, defaults as written, docs, places, attributes), every registry with 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 contributed which keys), every const, and
the zero-argument functions a fn value can name. Deterministic, schema_version 1; the runtime is
left out. `ludic schema [file] [-o FILE]` wraps it.

--check --diagnostics=json prints every error as one JSON array on stdout: the checker's and the
module rules' all, a parse or lowering error as the last. Tokens and nodes now carry a column.

Fields take several @attributes; @Ref(Registry), @OneOf(PREFIX_), @Range(lo, hi), @Unit("..."),
@Asset("..."), @Color on a field and @AppendOnly / @ByKey on a registry change nothing but go into
the schema, and @Ref naming no registry is an error (every one reported). Fixtures:
examples/lang/attributes.ludic, examples/rejected/ref_unknown.ludic, cases in ludic-dev test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-29 18:06:54 +03:00
parent f8d811aa97
commit 42deb76c28
27 changed files with 1220 additions and 23 deletions

View file

@ -625,6 +625,49 @@ int and this is a string`). Its entries are defs of the module that wrote the li
must be open (and exported) to it, and they take that module's place in the order: the declaring
module's entries, then each other module's by module name, and within a file, file order.
### Editor attributes and the schema (`@Ref`, `@Range`, ..., `ludic schema`)
A field can say what an editor of the data should offer for it, and a registry how its entries may
change, on the same `@` a field's `@max(64)` is written with - one or several, on the field's line or
the lines above it:
```ludic
# doc-check: skip — the registries it names are declared elsewhere
property Tool {
@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_
@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
}
@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).
`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
object with `"schema_version": 1`:
- `records` - every `property`, `state` and `event`: its module, file, line and column, its doc
comment (the comment lines above it, else the one ending its line), and its fields, each with its
type as text, its default as written (or null), its doc, its place and its attributes
(`[{"name": "Range", "args": [0, 20.5]}]`);
- `registries` - every registry: its record, prefix, resource file, whether it is open, its own
attributes, and its entries in their final index order (`{"key": "axe", "constant": "TL_AXE",
"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.
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
engine's runtime is left out.
### Default parameters, and calls that name what they change
A parameter can have a default, and a call leaves out what it does not change - the last ones when
@ -2055,6 +2098,8 @@ ludic test tests/math.ludic --test adds # just the test named "adds" (-v: every
ludic test -j 4 # four tests at once (default: one per CPU)
ludic test packages/ludic.base # the test programs under a directory (a package's)
ludic build --check # every check a build makes, nothing written
ludic build --check --diagnostics=json # the same, every error as a JSON array on stdout
ludic schema -o build/schema.json # records, registries, entries and consts, for editors
ludic deps # how tangled the modules are, as the compiler resolved them
ludic deps --check tests/deps-baseline.txt # fail when a number rose (--baseline FILE writes them)
@ -2068,6 +2113,14 @@ parse, the types, `export`, `uses` and layers, ports and binds, registries, and
unknown name) - and writes nothing: no IR, no link. On Maroon Lake it takes about four seconds, for
iterating on `uses` lines.
With `--diagnostics=json` (`ludicc --check --diagnostics=json`) nothing goes to stderr: stdout is one
JSON array of `{"file", "line", "col", "severity", "message"}` (`[]` when the program is clean; a
`col` of 0 is a place known only by its line), and the exit status is 1 when any is an error. Every
type error is in it, every `@Ref` naming no registry, and every module rule broken (`export`,
`uses`); an error the parser or the code writer cannot go on from - a token it did not expect, an
unknown name while lowering, a def into a registry that does not exist - ends the array, and type
errors end it before the module rules are looked at.
`ludic deps` compiles the program (the package's entry, or a file) with the compiler recording every
reference its visibility pass resolves - from the module it is written in to the module of what it
names - and every assignment to another module's global. It prints five numbers: `modules` (the