slige/compiler/resolver.ts

167 lines
4.8 KiB
TypeScript

import { Expr, Stmt } from "./ast.ts";
import {
AstVisitor,
visitExpr,
VisitRes,
visitStmt,
visitStmts,
} from "./ast_visitor.ts";
import { printStackTrace, Reporter } from "./info.ts";
import {
FnSyms,
GlobalSyms,
LeafSyms,
StaticSyms,
Syms,
} from "./resolver_syms.ts";
import { Pos } from "./token.ts";
export class Resolver implements AstVisitor<[Syms]> {
private root = new GlobalSyms();
public constructor(private reporter: Reporter) {
}
public resolve(stmts: Stmt[]): VisitRes {
const scopeSyms = new StaticSyms(this.root);
this.scoutFnStmts(stmts, scopeSyms);
visitStmts(stmts, this, scopeSyms);
return "stop";
}
visitLetStmt(stmt: Stmt, syms: Syms): VisitRes {
if (stmt.kind.type !== "let") {
throw new Error("expected let statement");
}
visitExpr(stmt.kind.value, this, syms);
const ident = stmt.kind.param.ident;
if (syms.definedLocally(ident)) {
this.reportAlreadyDefined(ident, stmt.pos, syms);
return;
}
syms.define(ident, {
ident,
type: "let",
pos: stmt.kind.param.pos,
stmt,
param: stmt.kind.param,
});
return "stop";
}
private scoutFnStmts(stmts: Stmt[], syms: Syms) {
for (const stmt of stmts) {
if (stmt.kind.type !== "fn") {
continue;
}
if (syms.definedLocally(stmt.kind.ident)) {
this.reportAlreadyDefined(stmt.kind.ident, stmt.pos, syms);
return;
}
const ident = stmt.kind.ident;
syms.define(ident, {
ident: stmt.kind.ident,
type: "fn",
pos: stmt.pos,
stmt,
});
}
}
visitFnStmt(stmt: Stmt, syms: Syms): VisitRes {
if (stmt.kind.type !== "fn") {
throw new Error("expected fn statement");
}
const fnScopeSyms = new FnSyms(syms);
for (const param of stmt.kind.params) {
if (fnScopeSyms.definedLocally(param.ident)) {
this.reportAlreadyDefined(param.ident, param.pos, syms);
continue;
}
fnScopeSyms.define(param.ident, {
ident: param.ident,
type: "fn_param",
pos: param.pos,
param,
});
}
visitExpr(stmt.kind.body, this, fnScopeSyms);
return "stop";
}
visitIdentExpr(expr: Expr, syms: Syms): VisitRes {
if (expr.kind.type !== "ident") {
throw new Error("expected ident");
}
const ident = expr.kind;
const symResult = syms.get(ident.value);
if (!symResult.ok) {
this.reportUseOfUndefined(ident.value, expr.pos, syms);
return;
}
const sym = symResult.sym;
expr.kind = {
type: "sym",
ident: ident.value,
sym,
};
return "stop";
}
visitBlockExpr(expr: Expr, syms: Syms): VisitRes {
if (expr.kind.type !== "block") {
throw new Error();
}
const childSyms = new LeafSyms(syms);
this.scoutFnStmts(expr.kind.stmts, childSyms);
visitStmts(expr.kind.stmts, this, childSyms);
if (expr.kind.expr) {
visitExpr(expr.kind.expr, this, childSyms);
}
return "stop";
}
visitForExpr(expr: Expr, syms: Syms): VisitRes {
if (expr.kind.type !== "for") {
throw new Error();
}
const childSyms = new LeafSyms(syms);
if (expr.kind.decl) visitStmt(expr.kind.decl, this, syms);
if (expr.kind.cond) visitExpr(expr.kind.cond, this, syms);
if (expr.kind.incr) visitStmt(expr.kind.incr, this, syms);
visitExpr(expr.kind.body, this, childSyms);
return "stop";
}
private reportUseOfUndefined(ident: string, pos: Pos, _syms: Syms) {
this.reporter.reportError({
reporter: "Resolver",
msg: `use of undefined symbol '${ident}'`,
pos,
});
printStackTrace();
}
private reportAlreadyDefined(ident: string, pos: Pos, syms: Syms) {
this.reporter.reportError({
reporter: "Resolver",
msg: `symbol already defined '${ident}'`,
pos,
});
const prev = syms.get(ident);
if (!prev.ok) {
throw new Error("expected to be defined");
}
if (!prev.sym.pos) {
return;
}
this.reporter.addNote({
reporter: "Resolver",
msg: `previous definition of '${ident}'`,
pos: prev.sym.pos,
});
printStackTrace();
}
}