feat(lang): open registry - other modules' defs, in an order imports cannot change
A def from another module into a registry that is not open is refused, and defs go through visibility (the registry exported, its module in the definer's uses). Index order: the declaring module's entries, then the other modules' by module name, each in reading order. A registry entry is emitted as its own file's code, so what it names is seen from its own module. Reseed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
05b09b4c98
commit
6cfaaf0bf9
19 changed files with 49801 additions and 47689 deletions
|
|
@ -139,14 +139,14 @@ function emit_push(e: Node) -> Val {
|
|||
# is the first element's; an empty literal has none, so it is spelled `new []T`.
|
||||
function emit_list(e: Node) -> Val {
|
||||
if len(e.kids) == 0 { perr("an empty list literal has no element type: write `new []T` instead") }
|
||||
let first = emit_expr(e.kids[0])
|
||||
let first = emit_list_elem(e.kids[0])
|
||||
let ty = "[]" + first.ty
|
||||
let elt = llty(first.ty)
|
||||
let s = emit_new_slice(ty)
|
||||
emit_push_into(s.code, elt, first)
|
||||
var i = 1
|
||||
while i < len(e.kids) {
|
||||
let v = emit_expr(e.kids[i])
|
||||
let v = emit_list_elem(e.kids[i])
|
||||
if not (v.ty == first.ty) { perr(`list literal mixes element types {first.ty} and {v.ty}`) }
|
||||
emit_push_into(s.code, elt, v)
|
||||
i += 1
|
||||
|
|
@ -154,6 +154,15 @@ function emit_list(e: Node) -> Val {
|
|||
return s
|
||||
}
|
||||
|
||||
# an element is its own file's code: a registry's entries come from the files their defs are in
|
||||
# (L8), and what an entry may see (L3) is its own module's view, not the registry's
|
||||
function emit_list_elem(k: Node) -> Val {
|
||||
let saved = g_err_file
|
||||
if k.file != null { g_err_file = k.file }
|
||||
let v = emit_expr(k)
|
||||
g_err_file = saved
|
||||
return v
|
||||
}
|
||||
# append the already-emitted value `v` (of LLVM element type `elt`) to the slice
|
||||
# whose header is `h`, growing the storage when it is full
|
||||
function emit_push_into(h: pointer, elt: pointer, v: Val) -> void {
|
||||
|
|
|
|||
|
|
@ -1004,6 +1004,12 @@ function parse_one_decl() -> void {
|
|||
if is_id("namespace") { parse_namespace(); return } # #76 namespace block
|
||||
if is_id("port") and (toks[pi + 1].kind == TK_ID) and (toks[pi + 2].text == "{") { parse_port(); return } # L3 ports
|
||||
if is_id("bind") and (toks[pi + 1].kind == TK_ID) and (toks[pi + 2].text == "{") { parse_bind(); return }
|
||||
if is_id("open") and (toks[pi + 1].text == "registry") { # L8: open to other modules' defs
|
||||
pi += 1
|
||||
parse_registry()
|
||||
g_rg_open[len(g_rg_open) - 1] = 1
|
||||
return
|
||||
}
|
||||
if is_id("registry") and (toks[pi + 1].kind == TK_ID) and (toks[pi + 2].text == "of") { parse_registry(); return } # L8
|
||||
if is_id("def") and (toks[pi + 1].kind == TK_ID) and (toks[pi + 2].kind == TK_ID) { parse_def(); return } # L8
|
||||
if is_id("view") and (toks[pi + 1].kind == TK_ID) and (toks[pi + 2].text == "{") { parse_view(); return } # L11
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ var g_rg_name: []pointer = new []pointer
|
|||
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_df_reg: []pointer = new []pointer
|
||||
var g_df_key: []pointer = new []pointer
|
||||
var g_df_rec: []Node = new []Node
|
||||
|
|
@ -70,6 +71,7 @@ function parse_registry() -> void {
|
|||
push(g_rg_type, t)
|
||||
push(g_rg_prefix, prefix)
|
||||
push(g_rg_var, v)
|
||||
push(g_rg_open, 0)
|
||||
if from != null { res_read(v, from) }
|
||||
}
|
||||
# def REGISTRY key { field: value, ... }
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ function registries_finish() -> void {
|
|||
g_parsing = false
|
||||
perr(`def {g_df_reg[d]} {g_df_key[d]}: there is no registry {g_df_reg[d]}`)
|
||||
}
|
||||
reg_def_check(d) # registry_open.ludic: open, and visible
|
||||
d += 1
|
||||
}
|
||||
var r = 0
|
||||
|
|
@ -26,41 +27,41 @@ function reg_fill(r: int) -> void {
|
|||
lst.line = v.line
|
||||
let seen = new []pointer
|
||||
var n = 0
|
||||
var d = 0
|
||||
while d < len(g_df_reg) {
|
||||
if (g_df_reg[d] == g_rg_name[r]) {
|
||||
let key = g_df_key[d]
|
||||
let rec = g_df_rec[d]
|
||||
var j = 0
|
||||
while j < len(seen) {
|
||||
if (seen[j] == key) {
|
||||
g_err_file = rec.file
|
||||
g_err_line = rec.line
|
||||
g_parsing = false
|
||||
perr(`def {g_rg_name[r]} {key} is declared twice`)
|
||||
}
|
||||
j += 1
|
||||
let order = reg_order(r)
|
||||
var o = 0
|
||||
while o < len(order) {
|
||||
let d = order[o]
|
||||
let key = g_df_key[d]
|
||||
let rec = g_df_rec[d]
|
||||
var j = 0
|
||||
while j < len(seen) {
|
||||
if (seen[j] == key) {
|
||||
g_err_file = rec.file
|
||||
g_err_line = rec.line
|
||||
g_parsing = false
|
||||
perr(`def {g_rg_name[r]} {key} is declared twice`)
|
||||
}
|
||||
push(seen, key)
|
||||
if keyed and not reg_rec_has(rec, "key") {
|
||||
let fi = node(E_FINIT)
|
||||
fi.s = "key"
|
||||
let ks = node(E_STR)
|
||||
ks.s = key
|
||||
fi.a = ks
|
||||
push(rec.kids, fi)
|
||||
}
|
||||
if comp != null { res_type_fields(rec, comp) } # L9: nested records take their field's type
|
||||
let nw = node(E_NEW)
|
||||
nw.s = t
|
||||
nw.a = rec
|
||||
nw.file = rec.file
|
||||
nw.line = rec.line
|
||||
push(lst.kids, nw)
|
||||
reg_const(v, `{g_rg_prefix[r]}_{reg_upper(key)}`, n)
|
||||
n += 1
|
||||
j += 1
|
||||
}
|
||||
d += 1
|
||||
push(seen, key)
|
||||
if keyed and not reg_rec_has(rec, "key") {
|
||||
let fi = node(E_FINIT)
|
||||
fi.s = "key"
|
||||
let ks = node(E_STR)
|
||||
ks.s = key
|
||||
fi.a = ks
|
||||
push(rec.kids, fi)
|
||||
}
|
||||
if comp != null { res_type_fields(rec, comp) } # L9: nested records take their field's type
|
||||
let nw = node(E_NEW)
|
||||
nw.s = t
|
||||
nw.a = rec
|
||||
nw.file = rec.file
|
||||
nw.line = rec.line
|
||||
push(lst.kids, nw)
|
||||
reg_const(v, `{g_rg_prefix[r]}_{reg_upper(key)}`, n)
|
||||
n += 1
|
||||
o += 1
|
||||
}
|
||||
reg_const(v, `{g_rg_prefix[r]}_COUNT`, n)
|
||||
if n == 0 {
|
||||
|
|
|
|||
80
selfhost/frontend/registry_open.ludic
Normal file
80
selfhost/frontend/registry_open.ludic
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
# registry_open.ludic — L8 + L3: which defs a registry takes, and in what order.
|
||||
#
|
||||
# A def written in another module than its registry's is refused unless the registry says `open
|
||||
# registry`, and then it goes through the same visibility as any reference: the registry must be
|
||||
# exported, and a module that says `uses` names the registry's module. The index order is the
|
||||
# declaring module's own entries first, in the order they were read, then each other module's, the
|
||||
# modules in the order of their names and each one's entries in the order they were read - so an
|
||||
# entry's index does not depend on the order a barrel happens to import things in. A registry and
|
||||
# its defs in one module, or in no module, keep exactly the order they always had.
|
||||
function reg_def_check(d: int) -> void {
|
||||
let r = reg_find(g_df_reg[d])
|
||||
let v = g_rg_var[r]
|
||||
let rec = g_df_rec[d]
|
||||
let to = module_of(v.file)
|
||||
let from = module_of(rec.file)
|
||||
if (to == "") or (from == to) { return }
|
||||
g_err_file = rec.file
|
||||
g_err_line = rec.line
|
||||
let saved = g_parsing
|
||||
g_parsing = false
|
||||
if g_rg_open[r] == 0 {
|
||||
perr(`def {v.s} {g_df_key[d]}: registry {v.s} is not open to other modules; declare it 'open registry {v.s}' in module {to}, or write the def there`)
|
||||
}
|
||||
vis_check(v, v.s)
|
||||
g_parsing = saved
|
||||
}
|
||||
# a < b, byte by byte
|
||||
function reg_str_less(a: pointer, b: pointer) -> bool {
|
||||
var i = 0
|
||||
while i < len(a) and i < len(b) {
|
||||
if a[i] != b[i] { return a[i] < b[i] }
|
||||
i += 1
|
||||
}
|
||||
return len(a) < len(b)
|
||||
}
|
||||
# the defs of registry r, as indices into g_df_*, in index order
|
||||
function reg_order(r: int) -> []int {
|
||||
let out = new []int
|
||||
let own = module_of(g_rg_var[r].file)
|
||||
let others = new []pointer
|
||||
var d = 0
|
||||
while d < len(g_df_reg) {
|
||||
if (g_df_reg[d] == g_rg_name[r]) {
|
||||
let m = module_of(g_df_rec[d].file)
|
||||
if (m == own) { push(out, d) }
|
||||
else {
|
||||
var seen = false
|
||||
var j = 0
|
||||
while j < len(others) {
|
||||
if (others[j] == m) { seen = true }
|
||||
j += 1
|
||||
}
|
||||
if not seen { push(others, m) }
|
||||
}
|
||||
}
|
||||
d += 1
|
||||
}
|
||||
# a handful of modules: an insertion sort by name
|
||||
var i = 1
|
||||
while i < len(others) {
|
||||
let x = others[i]
|
||||
var j = i - 1
|
||||
while j >= 0 and reg_str_less(x, others[j]) {
|
||||
others[j + 1] = others[j]
|
||||
j -= 1
|
||||
}
|
||||
others[j + 1] = x
|
||||
i += 1
|
||||
}
|
||||
i = 0
|
||||
while i < len(others) {
|
||||
d = 0
|
||||
while d < len(g_df_reg) {
|
||||
if (g_df_reg[d] == g_rg_name[r]) and (module_of(g_df_rec[d].file) == others[i]) { push(out, d) }
|
||||
d += 1
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
return out
|
||||
}
|
||||
48602
selfhost/ludicc.seed.ll
48602
selfhost/ludicc.seed.ll
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue