렉서 다음은 파서다. 노드는 한 배열에 살고 자식은 그 안의 인덱스다 -- 노드는 ^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.
108 lines
3.1 KiB
Plaintext
108 lines
3.1 KiB
Plaintext
// EXIT:0
|
|
// OUTPUT:unit demo
|
|
// OUTPUT:fn answer @2
|
|
// OUTPUT: let n = (+ 1 (* 2 3))
|
|
// OUTPUT: return n
|
|
// OUTPUT:fn greet @3
|
|
// OUTPUT: let s = "hi"
|
|
// OUTPUT: return
|
|
// OUTPUT:fn muddle @4
|
|
// OUTPUT: let x = <error>
|
|
// OUTPUT:nodes 17 errors 2
|
|
// OUTPUT:balanced
|
|
unit tree;
|
|
|
|
import std.io;
|
|
import std.sys;
|
|
import tok;
|
|
import ast;
|
|
import parse;
|
|
|
|
// The Ferro parser, written in Ferro.
|
|
//
|
|
// The lexer next to this file showed that a token can say where it came from
|
|
// instead of holding the text. A tree is the same idea one level up: a node
|
|
// cannot hold `^Node` children -- it has several and owns none of them once --
|
|
// and R4 keeps `&Node` out of aggregate storage. So the parser owns one array
|
|
// of nodes and every child is an index into it.
|
|
//
|
|
// Printing the expressions back in prefix form is the point of the test: it is
|
|
// the only way to see that `1 + 2 * 3` bound the way the grammar says.
|
|
|
|
const SOURCE: str = "unit demo;\nfn answer() { let n = 1 + 2 * 3; return n; }\nfn greet() { let s = \"hi\"; return; }\nfn muddle() { let x = ; }\n";
|
|
|
|
fn show_expr(p: &parse.Parser, src: []u8, i: usize) -> void {
|
|
if i == ast.NONE { return; }
|
|
let n: ast.Node = p.node(i);
|
|
match n.shape {
|
|
Binary => {
|
|
@print("({} ", src[n.from..n.from + n.len]);
|
|
show_expr(p, src, n.a);
|
|
@print(" ");
|
|
show_expr(p, src, n.b);
|
|
@print(")");
|
|
}
|
|
Error => { @print("<error>"); }
|
|
_ => { @print("{}", src[n.from..n.from + n.len]); }
|
|
}
|
|
return;
|
|
}
|
|
|
|
fn show_stmt(p: &parse.Parser, src: []u8, i: usize) -> void {
|
|
let n: ast.Node = p.node(i);
|
|
match n.shape {
|
|
Let => {
|
|
@print(" let {} = ", src[n.from..n.from + n.len]);
|
|
show_expr(p, src, n.b);
|
|
@print("\n");
|
|
}
|
|
Return => {
|
|
if n.a == ast.NONE { @print(" return\n"); }
|
|
else {
|
|
@print(" return ");
|
|
show_expr(p, src, n.a);
|
|
@print("\n");
|
|
}
|
|
}
|
|
_ => { @print(" <error>\n"); }
|
|
}
|
|
return;
|
|
}
|
|
|
|
fn show_item(p: &parse.Parser, src: []u8, i: usize) -> void {
|
|
let n: ast.Node = p.node(i);
|
|
match n.shape {
|
|
Fn => {
|
|
@print("fn {} @{}\n", src[n.from..n.from + n.len], n.line);
|
|
var s: usize = n.b;
|
|
while s != ast.NONE {
|
|
show_stmt(p, src, s);
|
|
s = p.node(s).next;
|
|
}
|
|
}
|
|
_ => { @print(" <error>\n"); }
|
|
}
|
|
return;
|
|
}
|
|
|
|
fn run() -> !void {
|
|
var p: parse.Parser = try parse.Parser.on(SOURCE);
|
|
let root: usize = try p.unit_decl(SOURCE);
|
|
let head: ast.Node = p.node(root);
|
|
@print("unit {}\n", SOURCE[head.from..head.from + head.len]);
|
|
var it: usize = head.a;
|
|
while it != ast.NONE {
|
|
show_item(&p, SOURCE, it);
|
|
it = p.node(it).next;
|
|
}
|
|
@print("nodes {} errors {}\n", p.count(), p.errors);
|
|
return;
|
|
}
|
|
|
|
fn main() -> i32 {
|
|
run() catch |e| { @print("out of memory\n"); return 1; };
|
|
if sys.allocs() == sys.frees() { @print("balanced\n"); }
|
|
else { @print("leaked {}\n", sys.allocs() - sys.frees()); }
|
|
return 0;
|
|
}
|