ethos/src/front/resolve.ts
sfja d2a49f3be0
All checks were successful
Check / Explore-Gitea-Actions (push) Successful in 17s
add while stmt
2026-03-17 12:44:35 +01:00

155 lines
4.4 KiB
TypeScript

import * as ast from "../ast.ts";
import { FileReporter } from "../diagnostics.ts";
import { builtins } from "./builtins.ts";
export class Syms {
constructor(
private symMap: Map<number, Sym>,
) {}
get(node: ast.Node): Sym {
if (!this.symMap.has(node.id)) {
throw new Error(`'${node.kind.tag}' not resolved`);
}
return this.symMap.get(node.id)!;
}
}
export type Sym =
| { tag: "Error" }
| { tag: "Bool"; value: boolean }
| { tag: "Builtin"; id: string }
| { tag: "Fn"; stmt: ast.NodeWithKind<"FnStmt"> }
| {
tag: "FnParam";
stmt: ast.NodeWithKind<"FnStmt">;
param: ast.NodeWithKind<"Param">;
idx: number;
}
| {
tag: "Let";
stmt: ast.NodeWithKind<"LetStmt">;
param: ast.NodeWithKind<"Param">;
}
| { tag: "Loop"; stmt: ast.Node };
export function resolve(
file: ast.Node,
reporter: FileReporter,
): Syms {
let syms = ResolverSyms.root();
const resols = new Map<number, Sym>();
const loopStack: ast.Node[] = [];
file.visit({
visit(node) {
const k = node.kind;
if (k.tag === "File" || k.tag === "Block") {
syms = ResolverSyms.forkFrom(syms);
for (const stmt of k.stmts) {
if (stmt.is("FnStmt")) {
syms.define(stmt.kind.ident, { tag: "Fn", stmt });
}
}
node.visitBelow(this);
syms = syms.parent!;
return "break";
}
if (k.tag === "FnStmt") {
ast.assertNodeWithKind(node, "FnStmt");
syms = ResolverSyms.forkFrom(syms);
for (const [idx, param] of k.params.entries()) {
ast.assertNodeWithKind(param, "Param");
const sym: Sym = { tag: "FnParam", stmt: node, param, idx };
syms.define(param.kind.ident, sym);
resols.set(param.id, sym);
}
node.visitBelow(this);
syms = syms.parent!;
return "break";
}
if (k.tag === "LetStmt") {
const stmt = node as ast.NodeWithKind<"LetStmt">;
const param = k.param as ast.NodeWithKind<"Param">;
const sym: Sym = { tag: "Let", stmt, param };
syms.define(param.kind.ident, sym);
resols.set(param.id, sym);
}
if (k.tag === "IdentExpr") {
const sym = syms.resolveExpr(k.ident);
if (sym === null) {
reporter.error(node.loc, `undefined symbol '${k.ident}'`);
reporter.abort();
}
resols.set(node.id, sym);
}
if (k.tag === "WhileStmt") {
loopStack.push(node);
node.visitBelow(this);
loopStack.pop();
return "break";
}
if (k.tag === "BreakStmt") {
const loopNode = loopStack.at(-1);
if (!loopNode) {
reporter.error(node.loc, `break outside loop`);
reporter.abort();
}
resols.set(node.id, { tag: "Loop", stmt: loopNode });
}
},
});
return new Syms(resols);
}
class ResolverSyms {
static root(): ResolverSyms {
return new ResolverSyms(
new Map(
builtins.map<[string, Sym]>((sym) => [
sym.id,
{ tag: "Builtin", id: sym.id },
]),
),
null,
);
}
static forkFrom(parent: ResolverSyms): ResolverSyms {
return new ResolverSyms(
new Map(),
parent,
);
}
private constructor(
private syms = new Map<string, Sym>(),
public parent: ResolverSyms | null,
) {}
define(ident: string, sym: Sym) {
this.syms.set(ident, sym);
}
resolveExpr(ident: string): Sym | null {
if (ident === "false" || ident === "true") {
return { tag: "Bool", value: ident === "true" };
}
if (this.syms.has(ident)) {
return this.syms.get(ident)!;
}
if (this.parent) {
return this.parent.resolveExpr(ident);
}
return null;
}
}