ludic/selfhost/frontend/diag.ludic
Orkuncakilkaya 42deb76c28 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>
2026-09-29 18:06:54 +03:00

95 lines
3 KiB
Text

# diag.ludic — `--diagnostics=json`: every error and warning the compiler reports, collected and
# written to stdout as one JSON array of {file, line, col, severity, message} when it exits, instead
# of the `file:line: error: message` lines on stderr. The type checker and the module rules (vis,
# uses) report everything they find; an error the parser or the emitter cannot go on from (a token
# it did not expect, an unknown name while lowering) is the last one in the array. A col of 0 is a
# place known only to its line.
var g_diag_json: bool = false
var g_dg_file: []pointer = new []pointer
var g_dg_line: []int = new []int
var g_dg_col: []int = new []int
var g_dg_sev: []pointer = new []pointer
var g_dg_msg: []pointer = new []pointer
var g_dg_errors: int = 0
# one diagnostic, kept for the array - once, though a pass may find it twice (a reference the
# checker and the emitter both resolve)
function diag_add(file: pointer, line: int, col: int, sev: pointer, msg: pointer) -> void {
var f = file
if f == null { f = "" }
let m = diag_trim(msg)
var i = 0
while i < len(g_dg_file) {
if g_dg_line[i] == line and g_dg_col[i] == col and (g_dg_msg[i] == m) and (g_dg_file[i] == f) and (g_dg_sev[i] == sev) { return }
i += 1
}
push(g_dg_file, f)
push(g_dg_line, line)
push(g_dg_col, col)
push(g_dg_sev, sev)
push(g_dg_msg, m)
if (sev == "error") { g_dg_errors += 1 }
}
# a message without its closing newline
function diag_trim(m: pointer) -> pointer {
var n = len(m)
while n > 0 and (m[n - 1] == '\n' or m[n - 1] == '\r') { n -= 1 }
return m[0 .. n]
}
# `s` as a JSON string, quotes included, into b
function jq_put(b: Buf, s: pointer) -> void {
buf_putc(b, '"')
if s != null {
var i = 0
while s[i] != 0 {
let c = s[i]
if c == '"' { buf_puts(b, "\\\"") }
else if c == CH_BACKSLASH { buf_puts(b, "\\\\") }
else if c == '\n' { buf_puts(b, "\\n") }
else if c == '\t' { buf_puts(b, "\\t") }
else if c == '\r' { buf_puts(b, "\\r") }
else if c >= 0 and c < 32 {
buf_puts(b, "\\u00")
buf_putc(b, jq_hex(c >> 4))
buf_putc(b, jq_hex(c & 15))
}
else { buf_putc(b, c) }
i += 1
}
}
buf_putc(b, '"')
}
function jq_hex(d: int) -> int {
if d < 10 { return 48 + d }
return 87 + d
}
# the array, on stdout
function diag_flush() -> void {
let b = buf_new()
buf_puts(b, "[")
var i = 0
while i < len(g_dg_file) {
if i > 0 { buf_puts(b, ",") }
buf_puts(b, "\n {\"file\": ")
jq_put(b, g_dg_file[i])
buf_puts(b, ", \"line\": ")
buf_puti(b, g_dg_line[i])
buf_puts(b, ", \"col\": ")
buf_puti(b, g_dg_col[i])
buf_puts(b, ", \"severity\": ")
jq_put(b, g_dg_sev[i])
buf_puts(b, ", \"message\": ")
jq_put(b, g_dg_msg[i])
buf_puts(b, "}")
i += 1
}
if len(g_dg_file) > 0 { buf_puts(b, "\n") }
buf_puts(b, "]\n")
let out = file_stdout()
file_write(out, buf_str(b), b.len)
}
# stop: with --diagnostics=json the array is written first
function diag_exit(code: int) -> void {
if g_diag_json { diag_flush() }
exit(code)
}