180 lines
4.6 KiB
Plaintext
180 lines
4.6 KiB
Plaintext
// lib/ww/sym.ww — port of cmd/wcc/sym.c.
|
|
//
|
|
// Per-scope hashtable, chained to the parent. Lookup walks up.
|
|
// Plan 9 / Hare flavoured. Duplicate definitions in the same scope
|
|
// return nil; the caller flags the error.
|
|
|
|
use mem;
|
|
use typ;
|
|
use ast;
|
|
|
|
// Symbol kinds — must stay numerically aligned with cmd/wcc/ww.h Skind.
|
|
type skind = enum i32 {
|
|
SK_NONE = 0,
|
|
SK_VAR = 1,
|
|
SK_PARAM = 2,
|
|
SK_DEF = 3,
|
|
SK_TYPE = 4,
|
|
SK_FN = 5,
|
|
SK_USE = 6,
|
|
SK_FIELD = 7,
|
|
};
|
|
|
|
type sym = struct {
|
|
name: str,
|
|
skind: skind,
|
|
type_: *tinfo,
|
|
decl: *node,
|
|
exported: i32,
|
|
is_const: i32, // const-bound (assignment rejected)
|
|
mod: str, // importing module's bareword for symbols
|
|
// from a `use`-imported module; "" for primary
|
|
// (root) compilation unit symbols. Used by
|
|
// scopelookupinmodule to disambiguate same-leaf-
|
|
// name types coming from different imports.
|
|
snext: *sym, // iteration order
|
|
hashnext: *sym, // hash bucket chain
|
|
scope: *scope,
|
|
};
|
|
|
|
def NBUCKETS: i32 = 16;
|
|
|
|
type scope = struct {
|
|
parent: *scope,
|
|
first: *sym,
|
|
last: *sym,
|
|
buckets: **sym, // length = NBUCKETS
|
|
nbuckets: i32,
|
|
a: *arena,
|
|
};
|
|
|
|
// FNV-1a 64 — same hash the C side uses, so bucket distribution is
|
|
// identical when both walk a scope in declaration order.
|
|
fn hashstr(s: str) u64 = {
|
|
let h: u64 = 14695981039346656037u64;
|
|
let i: i32 = 0;
|
|
for (i < s.len) {
|
|
let c: u8 = s[i];
|
|
h = h ^ (c: u64);
|
|
h = h * 1099511628211u64;
|
|
i += 1;
|
|
};
|
|
return h;
|
|
};
|
|
|
|
export fn newscope(a: *arena, parent: *scope) *scope = {
|
|
let s: *scope = amalloc(a, 64u64): *scope;
|
|
s.parent = parent;
|
|
s.a = a;
|
|
s.nbuckets = NBUCKETS;
|
|
s.buckets = amalloc(a, (NBUCKETS: u64) * 8u64): **sym;
|
|
return s;
|
|
};
|
|
|
|
export fn streq(a: str, b: str) bool = {
|
|
if (a.len != b.len) { return false; };
|
|
let i: i32 = 0;
|
|
for (i < a.len) {
|
|
if (a[i] != b[i]) { return false; };
|
|
i += 1;
|
|
};
|
|
return true;
|
|
};
|
|
|
|
export fn scopelookuplocal(s: *scope, name: str) *sym = {
|
|
if (s == nil) { return nil; };
|
|
let h: u64 = hashstr(name);
|
|
let bi: i32 = (h % (s.nbuckets: u64)): i32;
|
|
let b: *sym = s.buckets[bi];
|
|
for (b != nil) {
|
|
let bn: str = b.name;
|
|
if (streq(bn, name)) { return b; };
|
|
b = b.hashnext;
|
|
};
|
|
return nil;
|
|
};
|
|
|
|
export fn scopelookup(s: *scope, name: str) *sym = {
|
|
for (s != nil) {
|
|
let r: *sym = scopelookuplocal(s, name);
|
|
if (r != nil) { return r; };
|
|
s = s.parent;
|
|
};
|
|
return nil;
|
|
};
|
|
|
|
// scopelookupinmodule — module-filtered chain walk.
|
|
//
|
|
// Same FNV bucket + hashnext chain + parent walk as scopelookup, plus
|
|
// a `b.mod.len > 0 && streq(b.mod, mod)` filter. When `mod` is empty
|
|
// we fall back to unfiltered scopelookup semantics, so callers that
|
|
// don't care about disambiguation get the default.
|
|
//
|
|
// Used by the dot-prefixed type-name lookup in selfhost/cmd/wcc/
|
|
// check.ww to pick the right same-leaf-name type when two imports
|
|
// each export it (`bufio.stream` vs `io.stream`).
|
|
export fn scopelookupinmodule(s: *scope, mod: str, name: str) *sym = {
|
|
if (mod.len == 0) { return scopelookup(s, name); };
|
|
for (s != nil) {
|
|
let h: u64 = hashstr(name);
|
|
let bi: i32 = (h % (s.nbuckets: u64)): i32;
|
|
let b: *sym = s.buckets[bi];
|
|
for (b != nil) {
|
|
if (streq(b.name, name)) {
|
|
if (b.mod.len > 0) {
|
|
if (streq(b.mod, mod)) {
|
|
return b;
|
|
};
|
|
};
|
|
};
|
|
b = b.hashnext;
|
|
};
|
|
s = s.parent;
|
|
};
|
|
return nil;
|
|
};
|
|
|
|
export fn scopedefine(s: *scope, name: str, k: skind, t: *tinfo, decl: *node) *sym = {
|
|
let empty: str;
|
|
return scopedefineinmodule(s, name, empty, k, t, decl);
|
|
};
|
|
|
|
// scopedefineinmodule — bucket insert with per-mod dedup.
|
|
//
|
|
// Same insertion as scopedefine, but the duplicate-rejection key is
|
|
// (name, mod) rather than name alone. This lets two imports each
|
|
// register their own `stream` SK_TYPE in the flat scope, and lets the
|
|
// primary register `stream` (mod="") alongside imported `stream`s.
|
|
//
|
|
// Within a single (name, mod) pair the first registration wins; later
|
|
// attempts return nil and the caller can flag the error.
|
|
export fn scopedefineinmodule(s: *scope, name: str, mod: str, k: skind, t: *tinfo, decl: *node) *sym = {
|
|
let h: u64 = hashstr(name);
|
|
let bi: i32 = (h % (s.nbuckets: u64)): i32;
|
|
let b: *sym = s.buckets[bi];
|
|
for (b != nil) {
|
|
if (streq(b.name, name)) {
|
|
if (b.mod.len == 0) {
|
|
if (mod.len == 0) { return nil; };
|
|
} else {
|
|
if (mod.len > 0) {
|
|
if (streq(b.mod, mod)) { return nil; };
|
|
};
|
|
};
|
|
};
|
|
b = b.hashnext;
|
|
};
|
|
let sy: *sym = amalloc(s.a, 112u64): *sym;
|
|
sy.name = name;
|
|
sy.skind = k;
|
|
sy.type_ = t;
|
|
sy.decl = decl;
|
|
sy.mod = mod;
|
|
sy.scope = s;
|
|
sy.hashnext = s.buckets[bi];
|
|
s.buckets[bi] = sy;
|
|
if (s.first == nil) { s.first = sy; } else { s.last.snext = sy; };
|
|
s.last = sy;
|
|
return sy;
|
|
};
|