/* * sym.c — symbol table. Plan 9-flavoured: a per-scope hashtable * chained to the parent scope. Lookup walks up. Duplicate definitions * within the same scope are flagged by the caller (we just refuse the * insert and return the first one). */ #include "ww.h" #include #define INIT_BUCKETS 16 static u64 hashstr(const char *s) { /* FNV-1a 64-bit; small fixed footprint, decent distribution */ u64 h = 0xcbf29ce484222325ULL; for (; *s; s++) { h ^= (unsigned char)*s; h *= 0x100000001b3ULL; } return h; } Scope * newscope(Arena *a, Scope *parent) { Scope *s = amalloc(a, sizeof *s); s->parent = parent; s->a = a; s->nbuckets = INIT_BUCKETS; s->buckets = amalloc(a, s->nbuckets * sizeof(Sym *)); return s; } Sym * scope_lookup_local(Scope *s, const char *name) { if (s == NULL) return NULL; u64 h = hashstr(name) % s->nbuckets; for (Sym *b = s->buckets[h]; b; b = b->hashnext) if (strcmp(b->name, name) == 0) return b; return NULL; } Sym * scope_lookup(Scope *s, const char *name) { for (; s; s = s->parent) { Sym *r = scope_lookup_local(s, name); if (r) return r; } return NULL; } /* * scope_lookup_in_module — module-filtered chain walk. * * Same FNV bucket + hashnext chain + parent walk as scope_lookup, * plus a (b->mod != NULL && strcmp(b->mod, mod) == 0) filter. When * `mod` is NULL we fall back to unfiltered scope_lookup semantics, * so callers that don't care about disambiguation get the default. * * Used by resolve_typename and the cexpr N_DOT branch to pick the * right same-leaf-name type when two imports each export it * (`bufio.stream` vs `io.stream`). */ Sym * scope_lookup_in_module(Scope *s, const char *mod, const char *name) { if (mod == NULL) return scope_lookup(s, name); for (; s; s = s->parent) { u64 h = hashstr(name) % s->nbuckets; for (Sym *b = s->buckets[h]; b; b = b->hashnext) { if (strcmp(b->name, name) != 0) continue; if (b->mod && strcmp(b->mod, mod) == 0) return b; } } return NULL; } /* * scope_lookup_prefer — bare-leaf lookup with same-module preference. * * Walks the same FNV bucket + hashnext chain + parent walk scope_lookup * uses. Within each scope's bucket: Pass 1 prefers entries whose * `sym.mod` matches the caller's `mod`; Pass 2 falls back to the first * match regardless of mod (the existing scope_lookup semantics). We * only descend to the parent scope when the current scope has no * matching entry at all — so a local binding in a closer scope still * shadows a same-name fn from a parent scope, even when the parent * entry mod-matches. * * When `mod` is NULL we just call scope_lookup — there's no module * identity to prefer. * * Used at bare-leaf lookup sites inside a known current module so that * a bare `read` inside lib/os resolves to os.read rather than the * io.read that happens to hash earlier into the flat scope. * Qualified-lookup paths (`mod.name`) stay on scope_lookup_in_module. */ Sym * scope_lookup_prefer(Scope *s, const char *mod, const char *name) { if (mod == NULL) return scope_lookup(s, name); for (Scope *p = s; p; p = p->parent) { u64 h = hashstr(name) % p->nbuckets; Sym *fallback = NULL; for (Sym *b = p->buckets[h]; b; b = b->hashnext) { if (strcmp(b->name, name) != 0) continue; if (b->mod && strcmp(b->mod, mod) == 0) return b; if (fallback == NULL) fallback = b; } if (fallback) return fallback; } return NULL; } Sym * scope_define(Scope *s, const char *name, Skind k, Type *t, Node *decl) { return scope_define_in_module(s, name, NULL, k, t, decl); } /* * scope_define_in_module — bucket insert with per-mod dedup. * * Same insertion as scope_define, 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=NULL) alongside imported `stream`s. * * Within a single (name, mod) pair the first registration wins; later * attempts return NULL and the caller emits a duplicate-type error. */ Sym * scope_define_in_module(Scope *s, const char *name, const char *mod, Skind k, Type *t, Node *decl) { u64 h = hashstr(name) % s->nbuckets; for (Sym *b = s->buckets[h]; b; b = b->hashnext) { if (strcmp(b->name, name) != 0) continue; if (b->mod == NULL && mod == NULL) return NULL; if (b->mod && mod && strcmp(b->mod, mod) == 0) return NULL; } Sym *sy = amalloc(s->a, sizeof *sy); sy->name = name; sy->mod = mod; sy->kind = k; sy->type = t; sy->decl = decl; sy->scope = s; sy->hashnext = s->buckets[h]; s->buckets[h] = sy; if (s->first == NULL) s->first = sy; else s->last->next = sy; s->last = sy; return sy; }