이름 하나당 사본 하나와 그것을 대신하는 숫자. 컴파일러는 이름을 끊임없이
비교하고 사방에 저장하는데, StrId 둘을 비교하는 것은 정수 둘을 비교하는
것이고 하나를 저장하는 것은 4바이트에 소유권 없음이다.
str 을 꺼내는 API 를 일부러 두지 않았다. 텍스트를 빌리는 것은 interner 를
빌리는 것인데, interner 는 이름을 든 채로 계속 더 넣고 싶은 바로 그 물건이다
-- 파서는 식별자를 읽으면서 같은 숨에 다음 것을 등록한다. 텍스트를 열어서
하려던 일은 전부 여기 있다: eq, len_of, hash_of, find, copy_into.
그리고 이것이 제네릭 인스턴스를 필드로 담는 구조체를 통째로 깨뜨리던 버그를
드러냈다.
Holder{ bytes: ^[]mut u8, used: usize, seen: map.Map(u32) }
36 바이트여야 하는데 12 로 잡혔다.
Map(u32) 를 짓는 중에 그 안의 Slot(u32) 를 인스턴스화하면 거기서 배치 패스가
다시 돈다. 그때 Map(u32) 는 field_count 는 4 인데 필드 배열이 아직 아무것도
말하지 않는 상태라, 크기 0 으로 확정되고 굳었다. 이미 크기가 있는 타입은
아무도 다시 계산하지 않으니 Holder 는 그 0 을 읽었다.
셋을 고쳤다: 짓는 중인 인스턴스는 building 을 세워 배치를 거절하고, 멤버가
아직 자리를 못 잡은 집합 타입은 틀린 답으로 굳느니 물러나며, 배치 패스는
움직임이 없을 때까지 돈다.
245/245, 38/38.
132 lines
4.5 KiB
Plaintext
132 lines
4.5 KiB
Plaintext
unit std.intern;
|
|
|
|
import std.map;
|
|
import std.mem;
|
|
|
|
// One copy of every distinct name, and a number that stands for it.
|
|
//
|
|
// A compiler compares names constantly and stores them everywhere. Comparing
|
|
// two `StrId` is comparing two integers; storing one costs four bytes and no
|
|
// ownership. That is the whole point.
|
|
//
|
|
// There is deliberately no way to get a `str` back out. A borrow of the text
|
|
// would be a borrow of the interner, and the interner is exactly the thing you
|
|
// want to keep adding to while holding names -- the parser reads an identifier
|
|
// and registers the next one in the same breath. Everything you would open the
|
|
// text for is here instead: compare it, measure it, hash it, write it.
|
|
|
|
/// A name, as a number. Copy, four bytes, and meaningless to any other
|
|
/// interner -- which is fine, because a program has one.
|
|
pub struct StrId {
|
|
pub raw: u32,
|
|
|
|
pub fn same(self: &Self, other: StrId) -> bool {
|
|
return self.raw == other.raw;
|
|
}
|
|
}
|
|
|
|
/// No name.
|
|
pub const NONE: u32 = 4294967295;
|
|
|
|
struct Entry {
|
|
at: usize,
|
|
len: usize,
|
|
}
|
|
|
|
pub struct Interner {
|
|
/// Every name end to end. Nothing is ever removed, so an offset stays
|
|
/// good for as long as the interner does.
|
|
bytes: ^[]mut u8,
|
|
used: usize,
|
|
names: ^[]mut Entry,
|
|
count: usize,
|
|
/// Text to id, so interning the same name twice gives the same number.
|
|
seen: map.Map(u32),
|
|
|
|
pub fn with_capacity(n: usize) -> !Self {
|
|
let text: ^[]mut u8 = try mem.alloc_slice(u8, n * 8);
|
|
let table: ^[]mut Entry = try mem.alloc_slice(Entry, n);
|
|
let index: map.Map(u32) = try map.Map(u32).with_capacity(n);
|
|
return Self{ bytes: text, used: 0, names: table, count: 0,
|
|
seen: index };
|
|
}
|
|
|
|
pub fn count_of(self: &Self) -> usize { return self.count; }
|
|
|
|
/// The number for this name, making one if it is new.
|
|
pub fn intern(self: &mut Self, text: []u8) -> !StrId {
|
|
let found: u32 = self.seen.get(text, NONE);
|
|
if found != NONE { return StrId{ raw: found }; }
|
|
if self.count == self.names.^.n { return error.OutOfMemory; }
|
|
let at: usize = self.used;
|
|
if at + text.n > self.bytes.^.n { return error.OutOfMemory; }
|
|
var i: usize = 0;
|
|
while i < text.n {
|
|
self.bytes.^[at + i] = text[i];
|
|
i = i + 1;
|
|
}
|
|
let id: usize = self.count;
|
|
self.names.^[id].at = at;
|
|
self.names.^[id].len = text.n;
|
|
self.used = at + text.n;
|
|
self.count = id + 1;
|
|
try self.seen.put(text, id as u32);
|
|
return StrId{ raw: id as u32 };
|
|
}
|
|
|
|
/// Is this a name it has seen? `NONE` when not.
|
|
pub fn find(self: &Self, text: []u8) -> u32 {
|
|
return self.seen.get(text, NONE);
|
|
}
|
|
|
|
pub fn len_of(self: &Self, id: StrId) -> usize {
|
|
if (id.raw as usize) >= self.count { return 0; }
|
|
return self.names.^[id.raw as usize].len;
|
|
}
|
|
|
|
/// Does this id spell this text? The comparison every `if name == "fn"`
|
|
/// in a parser turns into.
|
|
pub fn eq(self: &Self, id: StrId, text: []u8) -> bool {
|
|
if (id.raw as usize) >= self.count { return false; }
|
|
let e: usize = id.raw as usize;
|
|
if self.names.^[e].len != text.n { return false; }
|
|
var i: usize = 0;
|
|
while i < text.n {
|
|
if self.bytes.^[self.names.^[e].at + i] != text[i] { return false; }
|
|
i = i + 1;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// FNV-1a over the stored bytes, for anything that wants to bucket names
|
|
/// without opening them.
|
|
pub fn hash_of(self: &Self, id: StrId) -> u32 {
|
|
if (id.raw as usize) >= self.count { return 0; }
|
|
let e: usize = id.raw as usize;
|
|
var h: u32 = 2166136261;
|
|
var i: usize = 0;
|
|
while i < self.names.^[e].len {
|
|
h = h ^ (self.bytes.^[self.names.^[e].at + i] as u32);
|
|
h = h * 16777619;
|
|
i = i + 1;
|
|
}
|
|
return h;
|
|
}
|
|
|
|
/// Copy the name into `out` and say how many bytes it took. This is how a
|
|
/// name reaches a diagnostic without the interner being borrowed past the
|
|
/// statement.
|
|
pub fn copy_into(self: &Self, id: StrId, out: []mut u8) -> usize {
|
|
if (id.raw as usize) >= self.count { return 0; }
|
|
let e: usize = id.raw as usize;
|
|
var n: usize = self.names.^[e].len;
|
|
if n > out.n { n = out.n; }
|
|
var i: usize = 0;
|
|
while i < n {
|
|
out[i] = self.bytes.^[self.names.^[e].at + i];
|
|
i = i + 1;
|
|
}
|
|
return n;
|
|
}
|
|
}
|