Files
doslang-mirror/fec/std/map.fe
T
coolguy e84f892147 Ferro 파서를 Ferro 로, 그리고 그것이 드러낸 네 가지
렉서 다음은 파서다. 노드는 한 배열에 살고 자식은 그 안의 인덱스다 -- 노드는
^Node 를 들 수 없고(여럿이며 한 번씩 소유하지 않는다) &Node 도 들 수 없다(R4).
인덱스는 둘 다 아니다. 소스도 필드가 아니라 매 단계에 같이 다닌다.

  unit demo / fn answer @2 / let n = (+ 1 (* 2 3)) / return n / balanced

전위 표기로 다시 찍는 것이 시험의 요점이다. 1 + 2 * 3 이 어떻게 묶였는지는
그렇게만 보인다.

쓰면서 나온 컴파일러 버그 넷:

1. 다른 유닛의 타입을 필드로 쓰면 그 필드 타입이 영영 UNKNOWN 이었다. 필드
   해석이 유닛마다 선언 직후에 돌아서, 아직 선언되지 않은 유닛의 타입을 찾다
   실패하고 그 답을 굳혔다. 이제 모든 유닛이 선언을 마친 뒤에 한 번 푼다.

2. 그리고 그 해석은 타입을 선언한 유닛에서 해야 한다. 필드 타입은 그 유닛의
   import 로 쓰였는데 아무 유닛에서나 풀고 있었다. 타입 계층에 enter/leave
   콜백을 두고 체커가 그 자리로 데려간다.

3. cycle_state 를 재귀 검사와 크기 계산이 같이 썼다. 첫 번째가 보는 중인 구조체
   가 두 번째에게는 다 끝난 것으로 보여서, 필드가 하나뿐인 것처럼 1 바이트로
   자리를 잡았다 -- Parser 가 그래서 자기 토큰을 밟았다. layout_state 로 나눴다.

4. 다른 유닛의 상수(ast.NONE)를 lowering 이 필드 접근으로 봤다. 체커가 이미
   링크 이름을 붙여두었으니 그것이 있으면 전역이다.

그리고 R1 을 실제로 지키게 했다: 소유자를 놓으면 그것이 가진 것도 놓는다.
전에는 자기 drop 이 있거나 자기가 owned 일 때만이어서, drop 을 가진 타입을
필드로 담은 구조체는 그것을 놓을 방법이 없었다(drop 은 손으로 못 부른다).
이제 release_at 이 drop 을 부르고 필드로 내려간다. 그 덕에 List/Arena/Map 의
drop 이 전부 필요 없어져서 지웠다 -- 버퍼가 owned 이니 R1 이 알아서 한다.

221/221, 29/29.
2026-08-17 12:49:53 +09:00

173 lines
5.7 KiB
Plaintext

unit std.map;
// A table from a run of bytes to a value.
//
// A compiler looks names up constantly and a linear scan over a list is the
// wrong shape for that. Keys are copied into one buffer the map owns and each
// slot records where in it the key sits -- the arena-and-handle shape R11 asks
// for, which also means letting go of the map is two frees and not one per
// entry. Both buffers are owned, so R1 does the letting go and no `drop` is
// written here.
//
// Open addressing with linear probing. The table is a power of two so the
// index is a mask rather than a division, and it grows at three quarters full
// because probing gets long well before the table gets full.
pub struct Slot(V) {
at: usize,
len: usize,
used: bool,
value: V,
}
pub struct Map(V) {
slots: ^[]mut Slot(V),
bytes: ^[]mut u8,
used_bytes: usize,
count: usize,
pub fn with_capacity(n: usize) -> !Self {
var room: usize = 8;
while room < n * 2 { room = room * 2; }
let table: ^[]mut Slot(V) = try mem.alloc_slice(Slot(V), room);
let text: ^[]mut u8 = try mem.alloc_slice(u8, 64);
var i: usize = 0;
while i < room {
table.^[i].used = false;
i = i + 1;
}
return Self{ slots: table, bytes: text, used_bytes: 0, count: 0 };
}
pub fn count_of(self: &Self) -> usize { return self.count; }
pub fn room(self: &Self) -> usize { return self.slots.^.n; }
/// Where `key` sits in the table: the slot holding it, or the first free
/// slot it could go in. Probing stops at a free slot, which is why a slot
/// is never cleared -- only ever filled.
fn find(self: &Self, key: []u8) -> usize {
let mask: usize = self.slots.^.n - 1;
var at: usize = hash(key) & mask;
while true {
if not self.slots.^[at].used { return at; }
if self.same(at, key) { return at; }
at = (at + 1) & mask;
}
return 0;
}
fn same(self: &Self, slot: usize, key: []u8) -> bool {
if self.slots.^[slot].len != key.n { return false; }
let from: usize = self.slots.^[slot].at;
var i: usize = 0;
while i < key.n {
if self.bytes.^[from + i] != key[i] { return false; }
i = i + 1;
}
return true;
}
pub fn has(self: &Self, key: []u8) -> bool {
return self.slots.^[self.find(key)].used;
}
pub fn get(self: &Self, key: []u8, missing: V) -> V {
let at: usize = self.find(key);
if self.slots.^[at].used { return self.slots.^[at].value; }
return missing;
}
pub fn put(self: &mut Self, key: []u8, value: V) -> !void {
if self.count * 4 >= self.slots.^.n * 3 { try self.regrow(); }
let at: usize = self.find(key);
if self.slots.^[at].used {
self.slots.^[at].value = value;
return;
}
try self.keep(key, at);
self.slots.^[at].value = value;
self.slots.^[at].used = true;
self.count = self.count + 1;
return;
}
/// Make sure `need` bytes fit, moving to a bigger buffer if they do not.
///
/// Kept apart from `keep` because the borrow that swaps the buffer in is
/// a borrow of the whole of `self` -- borrowing is tracked at the root --
/// and it has to be over before any field is read again.
fn ensure_room(self: &mut Self, need: usize) -> !void {
let have: usize = self.bytes.^.n;
if need <= have { return; }
var room: usize = have;
while room < need { room = room * 2; }
let bigger: ^[]mut u8 = try mem.alloc_slice(u8, room);
var k: usize = 0;
let filled: usize = self.used_bytes;
while k < filled {
bigger.^[k] = self.bytes.^[k];
k = k + 1;
}
let old: ^[]mut u8 = mem.replace(&mut self.bytes, bigger);
mem.destroy(old);
return;
}
/// Copy `key` into the byte buffer and point the slot at it.
fn keep(self: &mut Self, key: []u8, slot: usize) -> !void {
let at: usize = self.used_bytes;
let need: usize = at + key.n;
try self.ensure_room(need);
var i: usize = 0;
while i < key.n {
self.bytes.^[at + i] = key[i];
i = i + 1;
}
self.slots.^[slot].at = at;
self.slots.^[slot].len = key.n;
self.used_bytes = at + key.n;
return;
}
/// Twice the slots, everything placed again. The keys do not move: they
/// live in the byte buffer and the slots only point at them.
fn regrow(self: &mut Self) -> !void {
let bigger: ^[]mut Slot(V) = try mem.alloc_slice(Slot(V),
self.slots.^.n * 2);
let mask: usize = bigger.^.n - 1;
var i: usize = 0;
while i < bigger.^.n {
bigger.^[i].used = false;
i = i + 1;
}
i = 0;
while i < self.slots.^.n {
if self.slots.^[i].used {
let from: usize = self.slots.^[i].at;
let len: usize = self.slots.^[i].len;
var at: usize = hash(self.bytes.^[from..from + len]) & mask;
while bigger.^[at].used { at = (at + 1) & mask; }
bigger.^[at] = self.slots.^[i];
}
i = i + 1;
}
let old: ^[]mut Slot(V) = mem.replace(&mut self.slots, bigger);
mem.destroy(old);
return;
}
}
/// FNV-1a. Small, fast, and good enough for identifiers; nothing here has to
/// resist an adversary choosing the keys.
pub fn hash(key: []u8) -> usize {
var h: u32 = 2166136261;
var i: usize = 0;
while i < key.n {
h = h ^ (key[i] as u32);
h = h * 16777619;
i = i + 1;
}
return h as usize;
}