렉서 다음은 파서다. 노드는 한 배열에 살고 자식은 그 안의 인덱스다 -- 노드는 ^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.
61 lines
2.2 KiB
Plaintext
61 lines
2.2 KiB
Plaintext
unit std.mem;
|
|
import std.sys;
|
|
|
|
// `create`, `destroy`, `alloc_slice` and `replace` are compiler intrinsics:
|
|
// they need to know the type they are handed, which no signature can say.
|
|
// What is written here is what can be written in Ferro.
|
|
|
|
/// A block of storage handed out in pieces, released all at once.
|
|
///
|
|
/// SPEC R11 answers recursive and graph-shaped data with an arena that owns
|
|
/// the values and integer handles that reference them. This is that arena. A
|
|
/// handle is an offset, so it stays valid while the arena does, and comparing
|
|
/// two handles is comparing two numbers. The block itself is owned, so nothing
|
|
/// here says how to let go of it -- R1 already does.
|
|
pub struct Arena {
|
|
bytes: ^[]mut u8,
|
|
used: usize,
|
|
|
|
pub fn with_capacity(n: usize) -> !Self {
|
|
let room: ^[]mut u8 = try mem.alloc_slice(u8, n);
|
|
return Self{ bytes: room, used: 0 };
|
|
}
|
|
|
|
pub fn size(self: &Self) -> usize { return self.used; }
|
|
|
|
pub fn room(self: &Self) -> usize { return self.bytes.^.n; }
|
|
|
|
/// Reserve `n` bytes aligned to `align` and give back where they start.
|
|
/// Failure is running out of room, which the caller decides what to do
|
|
/// about; the arena never grows behind your back, because a handle that
|
|
/// moved would no longer mean anything.
|
|
pub fn alloc(self: &mut Self, n: usize, align: usize) -> !usize {
|
|
var at: usize = self.used;
|
|
if align > 1 {
|
|
let over: usize = at % align;
|
|
if over != 0 { at = at + align - over; }
|
|
}
|
|
if at + n > self.bytes.^.n { return error.ArenaFull; }
|
|
self.used = at + n;
|
|
return at;
|
|
}
|
|
|
|
/// One byte at a handle. Reading and writing go through here so that a
|
|
/// handle can be checked once, in one place.
|
|
pub fn at(self: &Self, handle: usize) -> !u8 {
|
|
if handle >= self.used { return error.BadHandle; }
|
|
return self.bytes.^[handle];
|
|
}
|
|
|
|
pub fn put(self: &mut Self, handle: usize, value: u8) -> !void {
|
|
if handle >= self.used { return error.BadHandle; }
|
|
self.bytes.^[handle] = value;
|
|
return;
|
|
}
|
|
|
|
/// Forget everything handed out so far. Every handle from before is stale;
|
|
/// that is the trade an arena makes.
|
|
pub fn reset(self: &mut Self) -> void { self.used = 0; }
|
|
|
|
}
|