resolve
This commit is contained in:
parent
181f237f4d
commit
9a7e263bed
@ -4,8 +4,13 @@ fn main() -> int {
|
|||||||
|
|
||||||
let ch = 'c';
|
let ch = 'c';
|
||||||
let s = "hello\ world";
|
let s = "hello\ world";
|
||||||
|
|
||||||
|
inner();
|
||||||
|
|
||||||
|
fn inner() {
|
||||||
|
print_int(v);
|
||||||
|
}
|
||||||
|
|
||||||
print_int(v);
|
|
||||||
print_str(v);
|
print_str(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -173,6 +173,10 @@ export class Ty {
|
|||||||
|
|
||||||
export type TyKind =
|
export type TyKind =
|
||||||
| { tag: "error" }
|
| { tag: "error" }
|
||||||
|
| { tag: "int" }
|
||||||
|
| { tag: "bool" }
|
||||||
|
| { tag: "char" }
|
||||||
|
| { tag: "str" }
|
||||||
| { tag: "ident"; ident: string };
|
| { tag: "ident"; ident: string };
|
||||||
|
|
||||||
export const VisitorBreak = Symbol();
|
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 parser = new Parser(toks);
|
||||||
const file = parser.parseFile();
|
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(yaml.stringify({ file }, { skipInvalid: true, indent: 2 }));
|
||||||
// console.log(JSON.stringify({ file }, null, 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);
|
// console.log(cx);
|
||||||
}
|
}
|
||||||
|
|||||||
12
src/parse.ts
12
src/parse.ts
@ -190,7 +190,15 @@ export class Parser {
|
|||||||
|
|
||||||
parseTy(): Ty {
|
parseTy(): Ty {
|
||||||
const line = this.line();
|
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!;
|
const ident = this.eaten!.value!;
|
||||||
return t.ty(line, "ident", { ident });
|
return t.ty(line, "ident", { ident });
|
||||||
} else {
|
} else {
|
||||||
@ -242,7 +250,9 @@ export class Parser {
|
|||||||
return this.i >= this.toks.length;
|
return this.i >= this.toks.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public errorOccured = false;
|
||||||
private error(line: number, message: string) {
|
private error(line: number, message: string) {
|
||||||
|
this.errorOccured = true;
|
||||||
console.error(
|
console.error(
|
||||||
`%cerror%c: ${message}\n %c--> line ${line}%c`,
|
`%cerror%c: ${message}\n %c--> line ${line}%c`,
|
||||||
"font-weight: bold; color: red",
|
"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 {
|
export class Def {
|
||||||
constructor(
|
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 =
|
export type DefKind =
|
||||||
@ -11,6 +58,10 @@ export type DefKind =
|
|||||||
| { tag: "param"; stmt: Stmt; pat: Pat }
|
| { tag: "param"; stmt: Stmt; pat: Pat }
|
||||||
| { tag: "let"; stmt: Stmt; pat: Pat };
|
| { tag: "let"; stmt: Stmt; pat: Pat };
|
||||||
|
|
||||||
|
type DefineResult =
|
||||||
|
| { ok: true }
|
||||||
|
| { ok: false; error: "redefinition"; originalDef: Def };
|
||||||
|
|
||||||
export class Rib {
|
export class Rib {
|
||||||
private syms = new Map<string, Def>();
|
private syms = new Map<string, Def>();
|
||||||
|
|
||||||
@ -18,64 +69,231 @@ export class Rib {
|
|||||||
private kind: RibKind,
|
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);
|
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 =
|
export type RibKind =
|
||||||
| { tag: "root" }
|
| { tag: "root" }
|
||||||
| { tag: "fn"; parent: Rib }
|
| { tag: "fn"; parent: Rib }
|
||||||
|
| { tag: "param"; parent: Rib }
|
||||||
| { tag: "block"; parent: Rib }
|
| { tag: "block"; parent: Rib }
|
||||||
| { tag: "let"; 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 {
|
export class Resolver implements Visitor {
|
||||||
private rib = new Rib({ tag: "root" });
|
private rib = new Rib({ tag: "root" });
|
||||||
private ribStateStack: Rib[] = [];
|
private scopeStack: Rib[] = [];
|
||||||
|
private astNodeDefs = new Map<number, Res>();
|
||||||
|
|
||||||
pushRib<Tag extends RibKind["tag"]>(
|
resolveFile(file: File): Syms {
|
||||||
tag: Tag,
|
file.visit({
|
||||||
kind: Omit<RibKind & { tag: Tag }, "tag" | "parent">,
|
visitStmt: (stmt): void | VisitorBreak => {
|
||||||
) {
|
const k = stmt.kind;
|
||||||
this.rib = new Rib({ tag, parent: this.rib, ...kind } as RibKind);
|
if (k.tag === "fn") {
|
||||||
}
|
this.tryDefine(
|
||||||
|
stmt.line,
|
||||||
|
k.ident,
|
||||||
|
new Def({ tag: "fn", stmt }),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return VisitorBreak;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
saveRibState() {
|
file.visit(this);
|
||||||
this.ribStateStack.push(this.rib);
|
return new Syms(this.astNodeDefs);
|
||||||
}
|
|
||||||
|
|
||||||
restoreRibState() {
|
|
||||||
this.rib = this.ribStateStack.pop()!;
|
|
||||||
}
|
|
||||||
|
|
||||||
define(ident: string, def: Def) {
|
|
||||||
this.rib.define(ident, def);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
visitStmt(stmt: Stmt): void | VisitorBreak {
|
visitStmt(stmt: Stmt): void | VisitorBreak {
|
||||||
const k = stmt.kind;
|
const k = stmt.kind;
|
||||||
if (k.tag === "fn") {
|
if (k.tag === "fn") {
|
||||||
this.define(k.ident, new Def({ tag: "fn", stmt }));
|
this.saveScope();
|
||||||
this.saveRibState();
|
|
||||||
this.pushRib("fn", {});
|
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.retTy?.visit(this);
|
||||||
k.body.visit(this);
|
k.body.visit(this);
|
||||||
this.restoreRibState();
|
|
||||||
|
this.restoreScope();
|
||||||
return VisitorBreak;
|
return VisitorBreak;
|
||||||
} else if (k.tag === "let") {
|
} else if (k.tag === "let") {
|
||||||
this.pushRib("let", {});
|
this.pushRib("let", {});
|
||||||
k.param.pat.visit({
|
k.param.pat.visit({
|
||||||
visitPat: (pat: Pat) => {
|
visitPat: (pat: Pat) => {
|
||||||
const k = pat.kind;
|
const k = pat.kind;
|
||||||
if (k.tag === "ident") {
|
if (k.tag !== "ident") {
|
||||||
this.define(
|
return;
|
||||||
k.ident,
|
|
||||||
new Def({ tag: "let", stmt, pat }),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
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([
|
const keywords = new Set([
|
||||||
"fn",
|
|
||||||
"let",
|
|
||||||
"true",
|
"true",
|
||||||
"false",
|
"false",
|
||||||
|
"bool",
|
||||||
|
"int",
|
||||||
|
"char",
|
||||||
|
"str",
|
||||||
|
"fn",
|
||||||
|
"let",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
type OpTree = Map<string, OpTree | null>;
|
type OpTree = Map<string, OpTree | null>;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user