mirror of
https://github.com/Mercantec-GHC/h4-projekt-gruppe-0-sm.git
synced 2025-04-28 00:34:06 +02:00
464 lines
14 KiB
TypeScript
464 lines
14 KiB
TypeScript
import * as ast from "@slige/ast";
|
|
import {
|
|
AstId,
|
|
Ctx,
|
|
exhausted,
|
|
File,
|
|
IdMap,
|
|
Ids,
|
|
Res,
|
|
Span,
|
|
todo,
|
|
} from "@slige/common";
|
|
import {
|
|
FnSyms,
|
|
ItemSyms,
|
|
LocalSyms,
|
|
LoopBreakResolve,
|
|
LoopResolve,
|
|
PatResolve,
|
|
PatResolveKind,
|
|
Redef,
|
|
Resolve,
|
|
ResolveError,
|
|
RootSyms,
|
|
Syms,
|
|
} from "./cx.ts";
|
|
|
|
export class Resols {
|
|
public constructor(
|
|
private exprResols: IdMap<AstId, Resolve>,
|
|
private tyResols: IdMap<AstId, Resolve>,
|
|
private pathResols: IdMap<AstId, Resolve>,
|
|
private patResols: IdMap<AstId, PatResolve>,
|
|
private loopsResols: IdMap<AstId, LoopResolve>,
|
|
private loopBreakResols: IdMap<AstId, LoopBreakResolve[]>,
|
|
) {}
|
|
|
|
public exprRes(id: AstId): Resolve {
|
|
if (!this.exprResols.has(id)) {
|
|
throw new Error();
|
|
}
|
|
return this.exprResols.get(id)!;
|
|
}
|
|
|
|
public tyRes(id: AstId): Resolve {
|
|
if (!this.tyResols.has(id)) {
|
|
throw new Error();
|
|
}
|
|
return this.tyResols.get(id)!;
|
|
}
|
|
|
|
public pathRes(id: AstId): Resolve {
|
|
if (!this.pathResols.has(id)) {
|
|
throw new Error();
|
|
}
|
|
return this.pathResols.get(id)!;
|
|
}
|
|
|
|
public patRes(id: AstId): PatResolve {
|
|
if (!this.patResols.has(id)) {
|
|
throw new Error();
|
|
}
|
|
return this.patResols.get(id)!;
|
|
}
|
|
|
|
public loopRes(id: AstId): LoopResolve {
|
|
if (!this.loopsResols.has(id)) {
|
|
throw new Error();
|
|
}
|
|
return this.loopsResols.get(id)!;
|
|
}
|
|
|
|
public loopBreaks(id: AstId): LoopBreakResolve[] {
|
|
if (!this.loopBreakResols.has(id)) {
|
|
throw new Error();
|
|
}
|
|
return this.loopBreakResols.get(id)!;
|
|
}
|
|
}
|
|
|
|
export class Resolver implements ast.Visitor {
|
|
private currentFile!: File;
|
|
private rootSyms = new RootSyms();
|
|
private syms: Syms = this.rootSyms;
|
|
|
|
private exprResols = new IdMap<AstId, Resolve>();
|
|
private tyResols = new IdMap<AstId, Resolve>();
|
|
private pathResols = new IdMap<AstId, Resolve>();
|
|
|
|
private patResols = new IdMap<AstId, PatResolve>();
|
|
private patResolveStack: PatResolveKind[] = [];
|
|
|
|
private loopsResols = new IdMap<AstId, LoopResolve>();
|
|
private loopBreakResols = new IdMap<AstId, LoopBreakResolve[]>();
|
|
private loopResolveStack: LoopResolve[] = [{ tag: "error" }];
|
|
|
|
public constructor(
|
|
private ctx: Ctx,
|
|
private entryFileAst: ast.File,
|
|
) {}
|
|
|
|
public resolve(): Resols {
|
|
ast.visitFile(this, this.entryFileAst);
|
|
return new Resols(
|
|
this.exprResols,
|
|
this.tyResols,
|
|
this.pathResols,
|
|
this.patResols,
|
|
this.loopsResols,
|
|
this.loopBreakResols,
|
|
);
|
|
}
|
|
|
|
visitFile(file: ast.File): ast.VisitRes {
|
|
this.currentFile = file.file;
|
|
this.fnBodiesToCheck.push([]);
|
|
ast.visitStmts(this, file.stmts);
|
|
this.popAndVisitFnBodies();
|
|
return "stop";
|
|
}
|
|
|
|
visitBlock(block: ast.Block): ast.VisitRes {
|
|
this.fnBodiesToCheck.push([]);
|
|
const outerSyms = this.syms;
|
|
this.syms = new LocalSyms(this.syms);
|
|
ast.visitStmts(this, block.stmts);
|
|
this.popAndVisitFnBodies();
|
|
block.expr && ast.visitExpr(this, block.expr);
|
|
this.syms = outerSyms;
|
|
return "stop";
|
|
}
|
|
|
|
visitLetStmt(stmt: ast.Stmt, kind: ast.LetStmt): ast.VisitRes {
|
|
kind.ty && ast.visitTy(this, kind.ty);
|
|
kind.expr && ast.visitExpr(this, kind.expr);
|
|
this.syms = new LocalSyms(this.syms);
|
|
this.patResolveStack.push({ tag: "let", stmt, kind });
|
|
ast.visitPat(this, kind.pat);
|
|
this.patResolveStack.pop();
|
|
return "stop";
|
|
}
|
|
|
|
visitModBlockItem(item: ast.Item, kind: ast.ModBlockItem): ast.VisitRes {
|
|
todo();
|
|
}
|
|
|
|
visitModFileItem(item: ast.Item, kind: ast.ModFileItem): ast.VisitRes {
|
|
ast.visitFile(this, kind.ast!);
|
|
todo();
|
|
}
|
|
|
|
visitBreakStmt(stmt: ast.Stmt, kind: ast.BreakStmt): ast.VisitRes {
|
|
const res = this.loopResolveStack.at(-1)!;
|
|
if (res.tag === "error") {
|
|
this.report("no loop to break", stmt.span);
|
|
return;
|
|
}
|
|
this.loopsResols.set(stmt.id, res);
|
|
this.loopBreakResols.get(res.expr.id)!.push({ stmt, kind });
|
|
}
|
|
|
|
visitContinueStmt(stmt: ast.Stmt): ast.VisitRes {
|
|
const res = this.loopResolveStack.at(-1)!;
|
|
if (res.tag === "error") {
|
|
this.report("no loop to continue", stmt.span);
|
|
return;
|
|
}
|
|
this.loopsResols.set(stmt.id, res);
|
|
}
|
|
|
|
visitEnumItem(item: ast.Item, kind: ast.EnumItem): ast.VisitRes {
|
|
this.syms.defTy(item.ident, { tag: "enum", item, kind });
|
|
const outerSyms = this.syms;
|
|
this.syms = new ItemSyms(this.syms);
|
|
for (const variant of kind.variants) {
|
|
ast.visitVariant(this, variant);
|
|
}
|
|
this.syms = outerSyms;
|
|
return "stop";
|
|
}
|
|
|
|
visitStructItem(item: ast.Item, kind: ast.StructItem): ast.VisitRes {
|
|
this.syms.defTy(item.ident, { tag: "struct", item, kind });
|
|
this.syms.defVal(item.ident, { tag: "struct", item, kind });
|
|
const outerSyms = this.syms;
|
|
this.syms = new ItemSyms(this.syms);
|
|
ast.visitVariantData(this, kind.data);
|
|
this.syms = outerSyms;
|
|
return "stop";
|
|
}
|
|
|
|
visitVariant(variant: ast.Variant): ast.VisitRes {
|
|
ast.visitVariantData(this, variant.data);
|
|
return "stop";
|
|
}
|
|
|
|
visitFieldDef(field: ast.FieldDef): ast.VisitRes {
|
|
ast.visitTy(this, field.ty);
|
|
return "stop";
|
|
}
|
|
|
|
private fnBodiesToCheck: [ast.Item, ast.FnItem][][] = [];
|
|
|
|
visitFnItem(item: ast.Item, kind: ast.FnItem): ast.VisitRes {
|
|
this.syms.defVal(item.ident, { tag: "fn", item, kind });
|
|
this.fnBodiesToCheck.at(-1)!.push([item, kind]);
|
|
return "stop";
|
|
}
|
|
|
|
visitUseItem(item: ast.Item, kind: ast.UseItem): ast.VisitRes {
|
|
todo();
|
|
}
|
|
|
|
visitTypeAliasItem(item: ast.Item, kind: ast.TypeAliasItem): ast.VisitRes {
|
|
todo();
|
|
}
|
|
|
|
private popAndVisitFnBodies() {
|
|
for (const [item, kind] of this.fnBodiesToCheck.at(-1)!) {
|
|
const outerSyms = this.syms;
|
|
this.syms = new FnSyms(this.syms);
|
|
this.syms = new LocalSyms(this.syms);
|
|
for (const [paramIdx, param] of kind.params.entries()) {
|
|
this.patResolveStack.push({
|
|
tag: "fn_param",
|
|
item,
|
|
kind,
|
|
paramIdx,
|
|
});
|
|
ast.visitParam(this, param);
|
|
this.patResolveStack.pop();
|
|
}
|
|
ast.visitBlock(this, kind.body!);
|
|
this.syms = outerSyms;
|
|
}
|
|
this.fnBodiesToCheck.pop();
|
|
}
|
|
|
|
visitPathExpr(expr: ast.Expr, kind: ast.PathExpr): ast.VisitRes {
|
|
if (kind.qty) {
|
|
return todo();
|
|
}
|
|
const res = this.resolveValPath(kind.path);
|
|
this.exprResols.set(expr.id, res);
|
|
return "stop";
|
|
}
|
|
|
|
visitStructExpr(expr: ast.Expr, kind: ast.StructExpr): ast.VisitRes {
|
|
if (!kind.path) {
|
|
return todo();
|
|
}
|
|
this.resolveTyPath(kind.path);
|
|
for (const field of kind.fields) {
|
|
ast.visitExpr(this, field.expr);
|
|
}
|
|
return "stop";
|
|
}
|
|
|
|
visitLoopExpr(expr: ast.Expr, kind: ast.LoopExpr): ast.VisitRes {
|
|
this.genericVisitLoop(expr, kind.body, { tag: "loop", expr, kind });
|
|
return "stop";
|
|
}
|
|
|
|
visitWhileExpr(expr: ast.Expr, kind: ast.WhileExpr): ast.VisitRes {
|
|
ast.visitExpr(this, kind.cond);
|
|
this.genericVisitLoop(expr, kind.body, { tag: "while", expr, kind });
|
|
return "stop";
|
|
}
|
|
|
|
visitForExpr(expr: ast.Expr, kind: ast.ForExpr): ast.VisitRes {
|
|
todo();
|
|
return "stop";
|
|
}
|
|
|
|
visitCForExpr(expr: ast.Expr, kind: ast.CForExpr): ast.VisitRes {
|
|
const outerSyms = this.syms;
|
|
this.syms = new LocalSyms(this.syms);
|
|
|
|
kind.decl && ast.visitStmt(this, kind.decl);
|
|
kind.cond && ast.visitExpr(this, kind.cond);
|
|
kind.incr && ast.visitStmt(this, kind.incr);
|
|
this.genericVisitLoop(expr, kind.body, { tag: "cfor", expr, kind });
|
|
this.syms = outerSyms;
|
|
return "stop";
|
|
}
|
|
|
|
private genericVisitLoop(expr: ast.Expr, body: ast.Expr, res: LoopResolve) {
|
|
this.loopResolveStack.push(res);
|
|
this.loopBreakResols.set(expr.id, []);
|
|
ast.visitExpr(this, body);
|
|
this.loopResolveStack.pop();
|
|
return "stop";
|
|
}
|
|
|
|
visitMatchExpr(expr: ast.Expr, kind: ast.MatchExpr): ast.VisitRes {
|
|
ast.visitExpr(this, kind.expr);
|
|
this.patResolveStack.push({ tag: "match", expr, kind });
|
|
for (const arm of kind.arms) {
|
|
ast.visitMatchArm(this, arm);
|
|
}
|
|
this.patResolveStack.pop();
|
|
return "stop";
|
|
}
|
|
|
|
visitMatchArm(arm: ast.MatchArm): ast.VisitRes {
|
|
const outerSyms = this.syms;
|
|
this.syms = new LocalSyms(this.syms);
|
|
ast.visitPat(this, arm.pat);
|
|
ast.visitExpr(this, arm.expr);
|
|
this.syms = outerSyms;
|
|
return "stop";
|
|
}
|
|
|
|
visitBindPat(pat: ast.Pat, kind: ast.BindPat): ast.VisitRes {
|
|
this.patResols.set(pat.id, { pat, kind: this.patResolveStack.at(-1)! });
|
|
const res = this.syms.defVal(kind.ident, {
|
|
tag: "local",
|
|
id: pat.id,
|
|
});
|
|
if (!res.ok) {
|
|
const text = this.ctx.identText(kind.ident.id);
|
|
this.report(`redefinition of value '${text}'`, kind.ident.span);
|
|
}
|
|
return "stop";
|
|
}
|
|
|
|
visitPathPat(pat: ast.Pat, kind: ast.PathPat): ast.VisitRes {
|
|
this.resolveTyPath(kind.path);
|
|
return "stop";
|
|
}
|
|
|
|
visitTuplePat(pat: ast.Pat, kind: ast.TuplePat): ast.VisitRes {
|
|
if (!kind.path) {
|
|
return todo();
|
|
}
|
|
this.resolveTyPath(kind.path);
|
|
for (const elem of kind.elems) {
|
|
ast.visitPat(this, elem);
|
|
}
|
|
return "stop";
|
|
}
|
|
|
|
visitStructPat(pat: ast.Pat, kind: ast.StructPat): ast.VisitRes {
|
|
if (!kind.path) {
|
|
return todo();
|
|
}
|
|
this.resolveTyPath(kind.path);
|
|
for (const field of kind.fields) {
|
|
ast.visitPat(this, field.pat);
|
|
}
|
|
return "stop";
|
|
}
|
|
|
|
visitPathTy(ty: ast.Ty, kind: ast.PathTy): ast.VisitRes {
|
|
if (kind.qty) {
|
|
return todo();
|
|
}
|
|
const res = this.resolveTyPath(kind.path);
|
|
this.tyResols.set(ty.id, res);
|
|
return "stop";
|
|
}
|
|
|
|
visitPath(_path: ast.Path): ast.VisitRes {
|
|
throw new Error("should not be reached");
|
|
}
|
|
|
|
visitIdent(_ident: ast.Ident): ast.VisitRes {
|
|
throw new Error("should not be reached");
|
|
}
|
|
|
|
private resolveValPath(path: ast.Path): Resolve {
|
|
let res: Resolve;
|
|
if (path.segments.length === 1) {
|
|
res = this.syms.getVal(path.segments[0].ident);
|
|
} else {
|
|
res = path.segments
|
|
.slice(1)
|
|
.reduce((inner, seg): Resolve => {
|
|
const k = inner.kind;
|
|
switch (k.tag) {
|
|
case "error":
|
|
return inner;
|
|
case "enum":
|
|
return this.resolveEnumVariant(k.item, k.kind, seg);
|
|
case "struct":
|
|
case "fn":
|
|
case "variant":
|
|
case "field":
|
|
case "local":
|
|
return todo();
|
|
}
|
|
exhausted();
|
|
}, this.syms.getTy(path.segments[0].ident));
|
|
}
|
|
if (res.kind.tag === "error") {
|
|
if (path.segments.length === 1) {
|
|
this.report(
|
|
`could not resolve symbol '${path.segments[0].ident.text}'`,
|
|
path.span,
|
|
);
|
|
} else {
|
|
this.report(`could not path`, path.span);
|
|
}
|
|
}
|
|
this.pathResols.set(path.id, res);
|
|
return res;
|
|
}
|
|
|
|
private resolveTyPath(path: ast.Path): Resolve {
|
|
const res = path.segments
|
|
.slice(1)
|
|
.reduce((inner, seg): Resolve => {
|
|
const k = inner.kind;
|
|
switch (k.tag) {
|
|
case "error":
|
|
return inner;
|
|
case "enum":
|
|
return this.resolveEnumVariant(k.item, k.kind, seg);
|
|
case "struct":
|
|
case "fn":
|
|
case "variant":
|
|
case "field":
|
|
case "local":
|
|
return todo();
|
|
}
|
|
exhausted();
|
|
}, this.syms.getTy(path.segments[0].ident));
|
|
this.pathResols.set(path.id, res);
|
|
return res;
|
|
}
|
|
|
|
private resolveEnumVariant(
|
|
item: ast.Item,
|
|
kind: ast.EnumItem,
|
|
seg: ast.PathSegment,
|
|
): Resolve {
|
|
const { ident } = seg;
|
|
const found = kind.variants
|
|
.map((v, idx) => [v, idx] as const)
|
|
.find(([variant]) => variant.ident.id === ident.id);
|
|
if (!found) {
|
|
this.report(
|
|
`enum ${item.ident.text} has no member '${ident.text}'`,
|
|
seg.span,
|
|
);
|
|
return ResolveError(ident);
|
|
}
|
|
const [variant, variantIdx] = found;
|
|
return {
|
|
ident,
|
|
kind: { tag: "variant", item, kind, variant, variantIdx },
|
|
};
|
|
}
|
|
|
|
private report(msg: string, span: Span) {
|
|
this.ctx.report({
|
|
severity: "error",
|
|
file: this.currentFile,
|
|
span,
|
|
msg,
|
|
});
|
|
}
|
|
}
|