리졸버를 위한 자리: Node.bind 와 Map.clear

ast.Node 가 이름이 무엇으로 해석됐는지 들고, Map 이 저장소를 유지한 채 키만
잊는다. 스코프가 끝날 때 표를 다음 스코프에 넘기는 것이 리졸버가 원하는
모양이다 -- 함수마다가 아니라 중첩 단계마다 표 하나.

bind 는 Name 노드의 남는 a 필드를 재활용할 수도 있었지만 명시적인 쪽을 골랐다.
노드가 32 에서 36 바이트가 되는 값으로 그 자리가 무엇인지 이름이 말한다.

clear 는 아무도 부르지 않는 채로 들어와 있었다. maps.fe 가 이제 부른다: 키가
사라지고, 방은 64 로 남고, 그 위에 다시 채워도 버퍼를 새로 잡지 않는다.

  cleared 0 room 64 gone 0 / refilled 3 / balanced

GOAL.md 를 더했다. 외부 감사와, 그 항목들을 실제로 빌드해서 확인한 결과를
합친 P0~P4 다.

228/228, 32/32.
This commit is contained in:
2026-08-17 15:50:58 +09:00
parent f7e667652e
commit 51f555a830
7 changed files with 145 additions and 0 deletions
+14
View File
@@ -41,6 +41,20 @@ pub struct Map(V) {
pub fn count_of(self: &Self) -> usize { return self.count; }
/// Forget every key but keep the storage. A scope that ends can hand its
/// table to the next one without going back to the allocator, which is
/// what a resolver wants: one table per nesting level, not per function.
pub fn clear(self: &mut Self) -> void {
var i: usize = 0;
while i < self.slots.^.n {
self.slots.^[i].used = false;
i = i + 1;
}
self.used_bytes = 0;
self.count = 0;
return;
}
pub fn room(self: &Self) -> usize { return self.slots.^.n; }
/// Where `key` sits in the table: the slot holding it, or the first free
+4
View File
@@ -31,6 +31,10 @@ pub struct Node {
pub a: usize, // handles into the same tree
pub b: usize,
pub next: usize, // the following statement, when there is one
/// What a `Name` resolved to: the handle of the `Let` or `Fn` that
/// declared it. The resolver writes this back so the tree carries its own
/// answers and nothing has to look the name up a second time.
pub bind: usize,
}
pub fn name_of(s: Shape) -> []u8 {
+2
View File
@@ -66,6 +66,8 @@ pub struct Parser {
let n: ast.Node = ast.Node{
shape: s, from: t.from, len: t.len, line: t.line,
a: a, b: b, next: ast.NONE,
// The parser does not resolve names; the resolver writes this.
bind: ast.NONE,
};
let i: usize = self.nodes.count();
try self.nodes.push(n);
+22
View File
@@ -3,6 +3,8 @@
// OUTPUT:fn 2 let 3 missing 0
// OUTPUT:grown 64
// OUTPUT:after 40
// OUTPUT:cleared 0 room 64 gone 0
// OUTPUT:refilled 3
// OUTPUT:balanced
unit maps;
import std.io;
@@ -37,6 +39,26 @@ fn run() -> !void {
i = i + 1;
}
@print("after {}\n", found);
// `clear` forgets every key and keeps the storage, which is what a scope
// that ends wants: hand the table to the next one without going back to
// the allocator. The room has to survive and the keys have to not.
let room: usize = seen.room();
seen.clear();
var gone: usize = 0;
i = 0;
while i < 40 {
let n: usize = fmt.fmt_i32(buf[..], i as i32);
if seen.has(buf[0..n]) { gone = gone + 1; }
i = i + 1;
}
@print("cleared {} room {} gone {}\n", seen.count_of(), room, gone);
// And it is usable again afterwards, reusing the same buffers.
try seen.put("a", 1);
try seen.put("b", 2);
try seen.put("c", 3);
@print("refilled {}\n", seen.count_of());
return;
}