resolve
This commit is contained in:
parent
181f237f4d
commit
9a7e263bed
@ -5,7 +5,12 @@ fn main() -> int {
|
||||
let ch = 'c';
|
||||
let s = "hello\ world";
|
||||
|
||||
print_int(v);
|
||||
inner();
|
||||
|
||||
fn inner() {
|
||||
print_int(v);
|
||||
}
|
||||
|
||||
print_str(v);
|
||||
}
|
||||
|
||||
|
||||
@ -173,6 +173,10 @@ export class Ty {
|
||||
|
||||
export type TyKind =
|
||||
| { tag: "error" }
|
||||
| { tag: "int" }
|
||||
| { tag: "bool" }
|
||||
| { tag: "char" }
|
||||
| { tag: "str" }
|
||||
| { tag: "ident"; ident: string };
|
||||
|
||||
export const VisitorBreak = Symbol();
|
||||
|
||||
13
src/main.ts
13
src/main.ts
@ -10,11 +10,22 @@ async function main() {
|
||||
|
||||
const parser = new Parser(toks);
|
||||
const file = parser.parseFile();
|
||||
if (parser.errorOccured) {
|
||||
console.error("parsing failed");
|
||||
Deno.exit(1);
|
||||
}
|
||||
|
||||
// console.log(yaml.stringify({ file }, { skipInvalid: true, indent: 2 }));
|
||||
// console.log(JSON.stringify({ file }, null, 2));
|
||||
|
||||
file.visit(new Resolver());
|
||||
const resolver = new Resolver();
|
||||
const syms = resolver.resolveFile(file);
|
||||
if (resolver.errorOccured) {
|
||||
console.error("resolving failed");
|
||||
Deno.exit(1);
|
||||
}
|
||||
|
||||
console.log(syms);
|
||||
|
||||
// console.log(cx);
|
||||
}
|
||||
|
||||
12
src/parse.ts
12
src/parse.ts
@ -190,7 +190,15 @@ export class Parser {
|
||||
|
||||
parseTy(): Ty {
|
||||
const line = this.line();
|
||||
if (this.eat("ident")) {
|
||||
if (this.eat("int")) {
|
||||
return t.ty(line, "int", {});
|
||||
} else if (this.eat("bool")) {
|
||||
return t.ty(line, "bool", {});
|
||||
} else if (this.eat("char")) {
|
||||
return t.ty(line, "char", {});
|
||||
} else if (this.eat("str")) {
|
||||
return t.ty(line, "str", {});
|
||||
} else if (this.eat("ident")) {
|
||||
const ident = this.eaten!.value!;
|
||||
return t.ty(line, "ident", { ident });
|
||||
} else {
|
||||
@ -242,7 +250,9 @@ export class Parser {
|
||||
return this.i >= this.toks.length;
|
||||
}
|
||||
|
||||
public errorOccured = false;
|
||||
private error(line: number, message: string) {
|
||||
this.errorOccured = true;
|
||||
console.error(
|
||||
`%cerror%c: ${message}\n %c--> line ${line}%c`,
|
||||
"font-weight: bold; color: red",
|
||||
|
||||
276
src/resolve.ts
276
src/resolve.ts
@ -1,9 +1,56 @@
|
||||
import { Pat, Stmt, Visitor, VisitorBreak } from "./ast.ts";
|
||||
import {
|
||||
Block,
|
||||
Expr,
|
||||
File,
|
||||
Pat,
|
||||
Stmt,
|
||||
Ty,
|
||||
Visitor,
|
||||
VisitorBreak,
|
||||
} from "./ast.ts";
|
||||
|
||||
class Res {
|
||||
constructor(
|
||||
private kind: ResKind,
|
||||
) {}
|
||||
|
||||
is(tag: ResKind["tag"]): boolean {
|
||||
return this.kind.tag === tag;
|
||||
}
|
||||
}
|
||||
|
||||
type ResKind =
|
||||
| { tag: "unresolved" }
|
||||
| { tag: "fn"; def: Def }
|
||||
| { tag: "local"; def: Def };
|
||||
|
||||
export class Def {
|
||||
constructor(
|
||||
kind: DefKind,
|
||||
private kind: DefKind,
|
||||
) {}
|
||||
|
||||
resolve(): Res {
|
||||
const k = this.kind;
|
||||
switch (k.tag) {
|
||||
case "fn":
|
||||
return new Res({ tag: "fn", def: this });
|
||||
case "param":
|
||||
case "let":
|
||||
return new Res({ tag: "local", def: this });
|
||||
}
|
||||
}
|
||||
|
||||
line(): number {
|
||||
const k = this.kind;
|
||||
switch (k.tag) {
|
||||
case "fn":
|
||||
return k.stmt.line;
|
||||
case "param":
|
||||
return k.pat.line;
|
||||
case "let":
|
||||
return k.pat.line;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type DefKind =
|
||||
@ -11,6 +58,10 @@ export type DefKind =
|
||||
| { tag: "param"; stmt: Stmt; pat: Pat }
|
||||
| { tag: "let"; stmt: Stmt; pat: Pat };
|
||||
|
||||
type DefineResult =
|
||||
| { ok: true }
|
||||
| { ok: false; error: "redefinition"; originalDef: Def };
|
||||
|
||||
export class Rib {
|
||||
private syms = new Map<string, Def>();
|
||||
|
||||
@ -18,64 +69,231 @@ export class Rib {
|
||||
private kind: RibKind,
|
||||
) {}
|
||||
|
||||
define(ident: string, def: Def) {
|
||||
define(ident: string, def: Def): DefineResult {
|
||||
if (this.syms.has(ident)) {
|
||||
return {
|
||||
ok: false,
|
||||
error: "redefinition",
|
||||
originalDef: this.syms.get(ident)!,
|
||||
};
|
||||
}
|
||||
this.syms.set(ident, def);
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
resolve(ident: string): Res {
|
||||
const k = this.kind;
|
||||
const sym = this.syms.get(ident);
|
||||
if (!sym) {
|
||||
if (k.tag === "root") {
|
||||
return new Res({ tag: "unresolved" });
|
||||
}
|
||||
return k.parent.resolve(ident);
|
||||
}
|
||||
const res = sym.resolve();
|
||||
if (k.tag === "fn" && res.is("local")) {
|
||||
return new Res({ tag: "unresolved" });
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
export type RibKind =
|
||||
| { tag: "root" }
|
||||
| { tag: "fn"; parent: Rib }
|
||||
| { tag: "param"; parent: Rib }
|
||||
| { tag: "block"; parent: Rib }
|
||||
| { tag: "let"; parent: Rib };
|
||||
|
||||
export class Syms {
|
||||
constructor(
|
||||
private astNodeDefs: Map<number, Res>,
|
||||
) {}
|
||||
|
||||
exprRes(expr: Expr): Res {
|
||||
const res = this.astNodeDefs.get(expr.id);
|
||||
if (!res) throw new Error();
|
||||
return res;
|
||||
}
|
||||
|
||||
patRes(pat: Pat): Res {
|
||||
const res = this.astNodeDefs.get(pat.id);
|
||||
if (!res) throw new Error();
|
||||
return res;
|
||||
}
|
||||
|
||||
tyRes(ty: Ty): Res {
|
||||
const res = this.astNodeDefs.get(ty.id);
|
||||
if (!res) throw new Error();
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
export class Resolver implements Visitor {
|
||||
private rib = new Rib({ tag: "root" });
|
||||
private ribStateStack: Rib[] = [];
|
||||
private scopeStack: Rib[] = [];
|
||||
private astNodeDefs = new Map<number, Res>();
|
||||
|
||||
pushRib<Tag extends RibKind["tag"]>(
|
||||
tag: Tag,
|
||||
kind: Omit<RibKind & { tag: Tag }, "tag" | "parent">,
|
||||
) {
|
||||
this.rib = new Rib({ tag, parent: this.rib, ...kind } as RibKind);
|
||||
}
|
||||
resolveFile(file: File): Syms {
|
||||
file.visit({
|
||||
visitStmt: (stmt): void | VisitorBreak => {
|
||||
const k = stmt.kind;
|
||||
if (k.tag === "fn") {
|
||||
this.tryDefine(
|
||||
stmt.line,
|
||||
k.ident,
|
||||
new Def({ tag: "fn", stmt }),
|
||||
);
|
||||
}
|
||||
return VisitorBreak;
|
||||
},
|
||||
});
|
||||
|
||||
saveRibState() {
|
||||
this.ribStateStack.push(this.rib);
|
||||
}
|
||||
|
||||
restoreRibState() {
|
||||
this.rib = this.ribStateStack.pop()!;
|
||||
}
|
||||
|
||||
define(ident: string, def: Def) {
|
||||
this.rib.define(ident, def);
|
||||
file.visit(this);
|
||||
return new Syms(this.astNodeDefs);
|
||||
}
|
||||
|
||||
visitStmt(stmt: Stmt): void | VisitorBreak {
|
||||
const k = stmt.kind;
|
||||
if (k.tag === "fn") {
|
||||
this.define(k.ident, new Def({ tag: "fn", stmt }));
|
||||
this.saveRibState();
|
||||
this.saveScope();
|
||||
this.pushRib("fn", {});
|
||||
k.params.forEach((param) => param.visit(this));
|
||||
|
||||
k.body.visit({
|
||||
visitStmt: (stmt): void | VisitorBreak => {
|
||||
const k = stmt.kind;
|
||||
if (k.tag === "fn") {
|
||||
this.tryDefine(
|
||||
stmt.line,
|
||||
k.ident,
|
||||
new Def({ tag: "fn", stmt }),
|
||||
);
|
||||
}
|
||||
return VisitorBreak;
|
||||
},
|
||||
});
|
||||
|
||||
this.pushRib("param", {});
|
||||
for (const param of k.params) {
|
||||
param.pat.visit({
|
||||
visitPat: (pat: Pat) => {
|
||||
const k = pat.kind;
|
||||
if (k.tag !== "ident") {
|
||||
return;
|
||||
}
|
||||
this.tryDefine(
|
||||
pat.line,
|
||||
k.ident,
|
||||
new Def({ tag: "param", stmt, pat }),
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
k.retTy?.visit(this);
|
||||
k.body.visit(this);
|
||||
this.restoreRibState();
|
||||
|
||||
this.restoreScope();
|
||||
return VisitorBreak;
|
||||
} else if (k.tag === "let") {
|
||||
this.pushRib("let", {});
|
||||
k.param.pat.visit({
|
||||
visitPat: (pat: Pat) => {
|
||||
const k = pat.kind;
|
||||
if (k.tag === "ident") {
|
||||
this.define(
|
||||
k.ident,
|
||||
new Def({ tag: "let", stmt, pat }),
|
||||
);
|
||||
if (k.tag !== "ident") {
|
||||
return;
|
||||
}
|
||||
this.tryDefine(
|
||||
pat.line,
|
||||
k.ident,
|
||||
new Def({ tag: "let", stmt, pat }),
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
visitBlock(block: Block): void | VisitorBreak {
|
||||
this.saveScope();
|
||||
this.pushRib("block", {});
|
||||
block.stmts.forEach((stmt) => stmt.visit(this));
|
||||
block.expr?.visit(this);
|
||||
this.restoreScope();
|
||||
return VisitorBreak;
|
||||
}
|
||||
|
||||
visitExpr(expr: Expr): void | VisitorBreak {
|
||||
const k = expr.kind;
|
||||
if (k.tag !== "ident") {
|
||||
return;
|
||||
}
|
||||
const res = this.resolve(expr.line, k.ident);
|
||||
this.astNodeDefs.set(expr.id, res);
|
||||
}
|
||||
|
||||
visitTy(ty: Ty): void | VisitorBreak {
|
||||
const k = ty.kind;
|
||||
if (k.tag !== "ident") {
|
||||
return;
|
||||
}
|
||||
const res = this.resolve(ty.line, k.ident);
|
||||
this.astNodeDefs.set(ty.id, res);
|
||||
}
|
||||
|
||||
private pushRib<Tag extends RibKind["tag"]>(
|
||||
tag: Tag,
|
||||
kind: Omit<RibKind & { tag: Tag }, "tag" | "parent">,
|
||||
) {
|
||||
this.rib = new Rib({ tag, parent: this.rib, ...kind } as RibKind);
|
||||
}
|
||||
|
||||
private saveScope() {
|
||||
this.scopeStack.push(this.rib);
|
||||
}
|
||||
|
||||
private restoreScope() {
|
||||
this.rib = this.scopeStack.pop()!;
|
||||
}
|
||||
|
||||
private tryDefine(line: number, ident: string, def: Def) {
|
||||
const result = this.define(ident, def);
|
||||
if (!result.ok) {
|
||||
this.error(line, `redefinition of '${ident}'`);
|
||||
this.info(result.originalDef.line(), `'${ident}' defined here`);
|
||||
}
|
||||
}
|
||||
|
||||
private define(ident: string, def: Def): DefineResult {
|
||||
return this.rib.define(ident, def);
|
||||
}
|
||||
|
||||
private resolve(line: number, ident: string): Res {
|
||||
const res = this.rib.resolve(ident);
|
||||
if (res.is("unresolved")) {
|
||||
this.error(line, `unresolved ident '${ident}'`);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
public errorOccured = false;
|
||||
private error(line: number, message: string) {
|
||||
this.errorOccured = true;
|
||||
console.error(
|
||||
`%cerror%c: ${message}\n %c--> line ${line}%c`,
|
||||
"font-weight: bold; color: red",
|
||||
"font-weight: bold; color: while",
|
||||
"color: cyan",
|
||||
"",
|
||||
);
|
||||
}
|
||||
|
||||
private info(line: number, message: string) {
|
||||
console.error(
|
||||
`%cinfo%c: ${message}\n %c--> line ${line}%c`,
|
||||
"font-weight: bold; color: blue",
|
||||
"font-weight: bold; color: while",
|
||||
"color: cyan",
|
||||
"",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,10 +5,14 @@ export type Tok = {
|
||||
};
|
||||
|
||||
const keywords = new Set([
|
||||
"fn",
|
||||
"let",
|
||||
"true",
|
||||
"false",
|
||||
"bool",
|
||||
"int",
|
||||
"char",
|
||||
"str",
|
||||
"fn",
|
||||
"let",
|
||||
]);
|
||||
|
||||
type OpTree = Map<string, OpTree | null>;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user