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

@ -16,9 +16,25 @@ var g_rg_type: []pointer = new []pointer
var g_rg_prefix: []pointer = new []pointer
var g_rg_var: []Node = new []Node
var g_rg_open: []int = new []int # 1: `open registry` - other modules may def into it
var g_rg_from: []pointer = new []pointer # its resource file as written, or null
var g_rg_at: []int = new []int # its attributes (@AppendOnly, @ByKey), a group or -1
var g_df_reg: []pointer = new []pointer
var g_df_key: []pointer = new []pointer
var g_df_rec: []Node = new []Node
var g_df_kline: []int = new []int # where its key is written (the schema's)
var g_df_kcol: []int = new []int
var g_df_src: []pointer = new []pointer # the resource file it came from, or "" for a `def`
var g_df_via: []pointer = new []pointer # the declaration that brought it in: file:line
# one def, and where it came from
function df_push(reg: pointer, key: pointer, rec: Node, kline: int, kcol: int, src: pointer, via: pointer) -> void {
push(g_df_reg, reg)
push(g_df_key, key)
push(g_df_rec, rec)
push(g_df_kline, kline)
push(g_df_kcol, kcol)
push(g_df_src, src)
push(g_df_via, via)
}
function reg_upper(s: pointer) -> pointer {
let n = len(s)
@ -72,6 +88,8 @@ function parse_registry() -> void {
push(g_rg_prefix, prefix)
push(g_rg_var, v)
push(g_rg_open, 0)
push(g_rg_from, from)
push(g_rg_at, at_registry_take()) # attrs.ludic
if from != null { res_read(v, from) }
}
# def REGISTRY key { field: value, ... }
@ -85,12 +103,11 @@ function parse_def() -> void {
res_read_into(reg, from, `def {reg} from`)
return
}
let kt = toks[pi]
let key = eat_id()
skipnl()
let rec = record()
push(g_df_reg, reg)
push(g_df_key, key)
push(g_df_rec, rec)
df_push(reg, key, rec, kt.line, kt.col, "", `{g_parse_file}:{itoa(kt.line)}`)
}
function reg_find(name: pointer) -> int {
var i = 0