feat(lang): new Type { field: value } record initialisers
`new` accepted only a bare `new T` (every field its declared default) or
`new []T`, but the docs (kw-new) document `new Record { field: value, … }`
as the way to construct a record with non-default fields — a documented,
intended form the parser never accepted (`let o = new Point { x: 3 }` failed
with "expected newline or ';'").
Parse an optional `{ … }` override record after the type in a `new`
expression (reusing the existing `record()` parser that `spawn` uses), and
seed each field in emit_new_struct from that record when present, else from
the field's declared default. `new []T` and bare `new T` are unchanged.
Also mark the illustrative kw-import fence `# doc-check: skip` (its imports
are example paths that can't resolve in isolation), which makes `x check-docs`
fully green (398 fences, 0 drifted) — so it is now wired as a gate in
`x test-tools` and CI, guarding against future doc/compiler drift.
Reseed is a clean fixpoint (x bootstrap-cfree holds); x test (56),
x selfhost-test (29, golden renders unchanged) and x test-tools (30) green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
e65e862244
commit
f5e9b5d6c2
7 changed files with 13209 additions and 13078 deletions
|
|
@ -80,5 +80,6 @@ jobs:
|
|||
# site generator (gen.py) is still Python; its port is tracked.
|
||||
bin/x check-impl
|
||||
bin/x check-vocabulary
|
||||
bin/x check-docs
|
||||
python3 tools/docgen/gen.py --out build/pages
|
||||
python3 tools/docgen/check.py build/pages
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ order: 10
|
|||
An <code>import</code> pulls the declarations of another Ludic file into this program, letting you split a game across many files instead of one giant block. The imported file is a <b>fragment</b> — bare declarations with no `program` wrapper — and its contents are spliced in as if written here. Paths resolve relative to the importing file, imports may nest, and each resolved path is include-guarded, so importing the same file twice (even through different chains) pulls it in exactly once. Diagnostics still point at the real source file, so errors in an imported fragment report that file's name and line.
|
||||
|
||||
```ludic
|
||||
# doc-check: skip — illustrative; the imported paths are example files, not
|
||||
# resolvable when this snippet is checked in isolation.
|
||||
program ChronoRift {
|
||||
import "chronorift/world.ludic" # properties and models
|
||||
import "chronorift/combat.ludic" # the battle handlers
|
||||
|
|
|
|||
|
|
@ -407,7 +407,7 @@ function emit_expr(e: Node) -> Val {
|
|||
if e.kind == E_STR { return val(emit_str_const(e.s), "string") }
|
||||
if e.kind == E_NEW {
|
||||
if is_slice_ty(e.s) { return emit_new_slice(e.s) }
|
||||
return emit_new_struct(e.s)
|
||||
return emit_new_struct(e.s, e.a)
|
||||
}
|
||||
if e.kind == E_ID {
|
||||
let li = loc_find(e.s)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,23 @@ function emit_sizeof(llt: pointer) -> pointer {
|
|||
return emit_bind(`ptrtoint ptr {p} to i64`)
|
||||
}
|
||||
|
||||
function emit_new_struct(name: pointer) -> Val {
|
||||
# the value expression a `new T { ... }` record supplies for field `fname`,
|
||||
# or null when the record omits it (so the field keeps its declared default).
|
||||
function rec_field(rec: Node, fname: pointer) -> Node {
|
||||
if (rec == null) { return null }
|
||||
var i = 0
|
||||
while i < len(rec.kids) {
|
||||
let fi = rec.kids[i]
|
||||
if (fi.s == fname) { return fi.a }
|
||||
i = i + 1
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
# `new T` / `new T { field: value, ... }` — allocate a record and seed each
|
||||
# field: a value from the override record if given, else the field's declared
|
||||
# default. `rec` is the E_REC of overrides (or null for the bare `new T`).
|
||||
function emit_new_struct(name: pointer, rec: Node) -> Val {
|
||||
let s = layout_node(name) # a struct or a property — same shape
|
||||
if (s == null) { perr("unknown record type in new") }
|
||||
let lty = layout_ty(name)
|
||||
|
|
@ -23,6 +39,8 @@ function emit_new_struct(name: pointer) -> Val {
|
|||
var v = "0"
|
||||
if (lt == "ptr") { v = "null" }
|
||||
if (fd.a != null) { let dv = emit_expr(fd.a); v = dv.code }
|
||||
let ov = rec_field(rec, fd.s) # explicit override wins over the default
|
||||
if (ov != null) { let dv = emit_expr(ov); v = dv.code }
|
||||
emit(" store "); emit(lt); emit(" "); emit(v); emit(", ptr "); emit(addr); emit("\n")
|
||||
f = f + 1
|
||||
}
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@ function p_primary() -> Node {
|
|||
if (t.text == "true") { let n = node(E_BOOL); n.ival = 1; pi = pi + 1; return n }
|
||||
if (t.text == "false") { let n = node(E_BOOL); n.ival = 0; pi = pi + 1; return n }
|
||||
if (t.text == "null") { pi = pi + 1; return node(E_NULL) }
|
||||
if (t.text == "new") { pi = pi + 1; let n = node(E_NEW); n.s = ptype(); return n }
|
||||
if (t.text == "new") { pi = pi + 1; let n = node(E_NEW); n.s = ptype(); if is_op("{") { n.a = record() }; return n }
|
||||
let n = node(E_ID); n.s = t.text; pi = pi + 1; return n
|
||||
}
|
||||
if is_op("(") { pi = pi + 1; skipnl(); let e = expr(); skipnl(); eat_op(")"); return e }
|
||||
|
|
|
|||
26256
selfhost/ludicc.seed.ll
26256
selfhost/ludicc.seed.ll
File diff suppressed because it is too large
Load diff
|
|
@ -178,6 +178,10 @@ function cmd_test_tools() -> int {
|
|||
# Ported to Ludic; runs through x.
|
||||
if shq("bin/x check-impl > /dev/null 2>&1") { ok("docs cover every implemented feature") } else { bad("docs drifted from the implementation") }
|
||||
|
||||
# every ```ludic fence in the docs must parse (or be marked skip/expect-error),
|
||||
# so a documented snippet can't drift from what the compiler accepts.
|
||||
if shq("bin/x check-docs > /dev/null 2>&1") { ok("every doc fence parses") } else { bad2("a doc fence drifted", "run: x check-docs") }
|
||||
|
||||
return report()
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue