mirror of
https://github.com/Mercantec-GHC/h4-projekt-gruppe-0-sm.git
synced 2025-04-28 08:44:06 +02:00
402 lines
12 KiB
TypeScript
402 lines
12 KiB
TypeScript
import * as ast from "@slige/ast";
|
|
import {
|
|
AstId,
|
|
Ctx,
|
|
exhausted,
|
|
File,
|
|
IdMap,
|
|
IdSet,
|
|
Ok,
|
|
Res,
|
|
Span,
|
|
todo,
|
|
} from "@slige/common";
|
|
import * as resolve from "@slige/resolve";
|
|
import { Ty, tyToString } from "@slige/ty";
|
|
|
|
export class Checker {
|
|
private stmtChecked = new IdSet<AstId>();
|
|
private itemTys = new IdMap<AstId, Ty>();
|
|
private exprTys = new IdMap<AstId, Ty>();
|
|
private tyTys = new IdMap<AstId, Ty>();
|
|
private patTys = new IdMap<AstId, Ty>();
|
|
|
|
private currentFile: File;
|
|
|
|
public constructor(
|
|
private ctx: Ctx,
|
|
private entryFileAst: ast.File,
|
|
private re: resolve.Resols,
|
|
) {
|
|
this.currentFile = ctx.entryFile();
|
|
}
|
|
|
|
private checkBlock(block: ast.Block, expected: Ty): Ty {
|
|
this.checkStmts(block.stmts);
|
|
return block.expr &&
|
|
this.checkExpr(block.expr, expected) ||
|
|
Ty({ tag: "null" });
|
|
}
|
|
|
|
private checkStmts(stmts: ast.Stmt[]) {
|
|
}
|
|
|
|
private checkLetStmtTy(stmt: ast.Stmt, kind: ast.LetStmt) {
|
|
if (this.stmtChecked.has(stmt.id)) {
|
|
return;
|
|
}
|
|
|
|
const exprTy = kind.expr && Ok(this.exprTy(kind.expr));
|
|
const tyTy = kind.ty && Ok(this.tyTy(kind.ty));
|
|
const ty = exprTy !== undefined && tyTy !== undefined
|
|
? this.resolveTys(exprTy.val, tyTy.val)
|
|
: exprTy || tyTy;
|
|
|
|
this.stmtChecked.add(stmt.id);
|
|
|
|
if (ty === undefined) {
|
|
this.assignPatTy(kind.pat, Ty({ tag: "error" }));
|
|
this.report("type amfibious, specify type or value", stmt.span);
|
|
return Ty({ tag: "error" });
|
|
}
|
|
|
|
if (!ty.ok) {
|
|
this.assignPatTy(kind.pat, Ty({ tag: "error" }));
|
|
this.report(ty.val, stmt.span);
|
|
return Ty({ tag: "error" });
|
|
}
|
|
|
|
const res = this.assignPatTy(kind.pat, ty.val);
|
|
if (!res.ok) {
|
|
this.report(res.val, stmt.span);
|
|
return Ty({ tag: "error" });
|
|
}
|
|
}
|
|
|
|
private assignPatTy(pat: ast.Pat, ty: Ty): Res<void, string> {
|
|
const k = pat.kind;
|
|
switch (k.tag) {
|
|
case "error":
|
|
// don't report, already reported
|
|
return Res.Ok(undefined);
|
|
case "bind":
|
|
this.patTys.set(pat.id, ty);
|
|
return Ok(undefined);
|
|
case "path":
|
|
return todo();
|
|
}
|
|
exhausted(k);
|
|
}
|
|
|
|
public fnItemTy(item: ast.Item, kind: ast.FnItem): Ty {
|
|
return this.itemTys.get(item.id) ?? this.checkFnItem(item, kind);
|
|
}
|
|
|
|
private checkFnItem(item: ast.Item, kind: ast.FnItem): Ty {
|
|
const params = kind.params.map((param) => this.tyTy(param.ty));
|
|
const returnTy = kind.returnTy && this.tyTy(kind.returnTy) ||
|
|
Ty({ tag: "null" });
|
|
return Ty({ tag: "fn", item, kind, params, returnTy });
|
|
}
|
|
|
|
public exprTy(expr: ast.Expr): Ty {
|
|
return this.exprTys.get(expr.id) ||
|
|
this.checkExpr(expr, Ty({ tag: "unknown" }));
|
|
}
|
|
|
|
private checkExpr(expr: ast.Expr, expected: Ty): Ty {
|
|
const k = expr.kind;
|
|
switch (k.tag) {
|
|
case "error":
|
|
return Ty({ tag: "error" });
|
|
case "path":
|
|
return this.checkPathExpr(expr, k, expected);
|
|
case "null":
|
|
return Ty({ tag: "null" });
|
|
case "int":
|
|
return Ty({ tag: "int" });
|
|
case "bool":
|
|
return Ty({ tag: "bool" });
|
|
case "str":
|
|
return todo();
|
|
case "group":
|
|
return todo();
|
|
case "array":
|
|
return todo();
|
|
case "repeat":
|
|
return todo();
|
|
case "struct":
|
|
return todo();
|
|
case "ref":
|
|
return todo();
|
|
case "deref":
|
|
return todo();
|
|
case "elem":
|
|
return todo();
|
|
case "field":
|
|
return todo();
|
|
case "index":
|
|
return todo();
|
|
case "call":
|
|
return this.checkCallExpr(expr, k, expected);
|
|
case "unary":
|
|
return todo();
|
|
case "binary": {
|
|
const res = this.resolveTys(
|
|
this.exprTy(k.left),
|
|
this.exprTy(k.right),
|
|
);
|
|
if (!res.ok) {
|
|
this.exprTys.set(expr.id, Ty({ tag: "error" }));
|
|
this.report(res.val, expr.span);
|
|
return Ty({ tag: "error" });
|
|
}
|
|
this.exprTys.set(expr.id, res.val);
|
|
return res.val;
|
|
}
|
|
case "block": {
|
|
const ty = this.checkBlock(k.block, expected);
|
|
return ty;
|
|
}
|
|
case "if": {
|
|
const cond = this.exprTy(k.cond);
|
|
const condRes = this.resolveTys(cond, Ty({ tag: "bool" }));
|
|
if (!condRes.ok) {
|
|
this.exprTys.set(expr.id, Ty({ tag: "error" }));
|
|
this.report("if-condition must be a boolean", k.cond.span);
|
|
return Ty({ tag: "error" });
|
|
}
|
|
const truthy = this.exprTy(k.truthy);
|
|
if (!k.falsy) {
|
|
const truthyRes = this.resolveTys(
|
|
truthy,
|
|
Ty({ tag: "null" }),
|
|
);
|
|
if (!truthyRes.ok) {
|
|
this.exprTys.set(expr.id, Ty({ tag: "error" }));
|
|
this.report(
|
|
"if there isn't a falsy-clause, then the truthy clause must evaluate to null",
|
|
k.truthy.span,
|
|
);
|
|
return Ty({ tag: "error" });
|
|
}
|
|
this.exprTys.set(expr.id, Ty({ tag: "null" }));
|
|
return Ty({ tag: "null" });
|
|
}
|
|
const falsy = this.exprTy(k.falsy);
|
|
const bothRes = this.resolveTys(truthy, falsy);
|
|
if (!bothRes.ok) {
|
|
this.exprTys.set(expr.id, Ty({ tag: "error" }));
|
|
this.report(bothRes.val, k.truthy.span);
|
|
return Ty({ tag: "error" });
|
|
}
|
|
this.exprTys.set(expr.id, bothRes.val);
|
|
return bothRes.val;
|
|
}
|
|
case "loop":
|
|
return todo();
|
|
case "while":
|
|
return todo();
|
|
case "for":
|
|
return todo();
|
|
case "c_for":
|
|
return todo();
|
|
}
|
|
exhausted(k);
|
|
}
|
|
|
|
private checkPathExpr(
|
|
expr: ast.Expr,
|
|
kind: ast.PathExpr,
|
|
expected: Ty,
|
|
): Ty {
|
|
const res = this.re.exprRes(expr.id);
|
|
switch (res.kind.tag) {
|
|
case "error":
|
|
return Ty({ tag: "error" });
|
|
case "fn": {
|
|
const fn = res.kind.item;
|
|
const ty = this.fnItemTy(fn, res.kind.kind);
|
|
const resu = this.resolveTys(ty, expected);
|
|
if (!resu.ok) {
|
|
this.report(resu.val, expr.span);
|
|
return Ty({ tag: "error" });
|
|
}
|
|
return resu.val;
|
|
}
|
|
case "local": {
|
|
const patRes = this.re.patRes(res.kind.id);
|
|
const ty = this.patTy(patRes.pat);
|
|
const resu = this.resolveTys(ty, expected);
|
|
if (!resu.ok) {
|
|
this.report(resu.val, expr.span);
|
|
return Ty({ tag: "error" });
|
|
}
|
|
return resu.val;
|
|
}
|
|
}
|
|
exhausted(res.kind);
|
|
}
|
|
|
|
private checkCallExpr(
|
|
expr: ast.Expr,
|
|
kind: ast.CallExpr,
|
|
expected: Ty,
|
|
): Ty {
|
|
const fnTy = this.exprTy(kind.expr);
|
|
if (fnTy.kind.tag !== "fn") {
|
|
if (fnTy.kind.tag === "error") {
|
|
return fnTy;
|
|
}
|
|
const ty = tyToString(this.ctx, fnTy);
|
|
console.log(kind.expr.span);
|
|
this.report(`type '${ty}' not fucking callable`, kind.expr.span);
|
|
return Ty({ tag: "error" });
|
|
}
|
|
const paramTys = fnTy.kind.params;
|
|
if (paramTys.length !== kind.args.length) {
|
|
this.report(
|
|
"not enough/too many fucking arguments",
|
|
kind.expr.span,
|
|
);
|
|
return Ty({ tag: "error" });
|
|
}
|
|
const _args = kind.args.map((arg, i) =>
|
|
this.checkExpr(arg, paramTys[i])
|
|
);
|
|
|
|
const ty = fnTy.kind.returnTy;
|
|
this.exprTys.set(expr.id, ty);
|
|
return ty;
|
|
}
|
|
|
|
private tyTy(ty: ast.Ty): Ty {
|
|
return this.tyTys.get(ty.id) ||
|
|
this.checkTy(ty);
|
|
}
|
|
|
|
private checkTy(ty: ast.Ty): Ty {
|
|
const k = ty.kind;
|
|
switch (k.tag) {
|
|
case "error":
|
|
return Ty({ tag: "error" });
|
|
case "null":
|
|
case "int":
|
|
return Ty({ tag: "int" });
|
|
case "bool":
|
|
case "str":
|
|
case "path":
|
|
case "ref":
|
|
case "ptr":
|
|
case "slice":
|
|
case "array":
|
|
case "anon_struct":
|
|
return todo(k);
|
|
}
|
|
exhausted(k);
|
|
}
|
|
|
|
public patTy(pat: ast.Pat): Ty {
|
|
return this.patTys.get(pat.id) ||
|
|
this.checkPat(pat);
|
|
}
|
|
|
|
private checkPat(pat: ast.Pat): Ty {
|
|
const patRes = this.re.patRes(pat.id);
|
|
const k = pat.kind;
|
|
switch (k.tag) {
|
|
case "error":
|
|
return todo();
|
|
case "bind": {
|
|
switch (patRes.kind.tag) {
|
|
case "fn_param": {
|
|
const fnTy = this.fnItemTy(
|
|
patRes.kind.item,
|
|
patRes.kind.kind,
|
|
);
|
|
if (fnTy.kind.tag !== "fn") {
|
|
throw new Error();
|
|
}
|
|
const paramTy = fnTy.kind.params[patRes.kind.paramIdx];
|
|
this.assignPatTy(
|
|
patRes.kind.kind.params[patRes.kind.paramIdx].pat,
|
|
paramTy,
|
|
);
|
|
const ty = this.patTy(pat);
|
|
this.patTys.set(pat.id, ty);
|
|
return ty;
|
|
}
|
|
case "let": {
|
|
this.checkLetStmtTy(patRes.kind.stmt, patRes.kind.kind);
|
|
const ty = this.patTy(pat);
|
|
this.patTys.set(pat.id, ty);
|
|
return ty;
|
|
}
|
|
}
|
|
exhausted(patRes.kind);
|
|
return todo();
|
|
}
|
|
case "path":
|
|
return todo();
|
|
}
|
|
exhausted(k);
|
|
}
|
|
|
|
private resolveTys(a: Ty, b: Ty): Res<Ty, string> {
|
|
if (a.kind.tag === "error" || b.kind.tag === "error") {
|
|
return Res.Ok(a);
|
|
}
|
|
if (b.kind.tag === "unknown") {
|
|
return Res.Ok(a);
|
|
}
|
|
const incompat = () => {
|
|
const as = tyToString(this.ctx, a);
|
|
const bs = tyToString(this.ctx, b);
|
|
return Res.Err(
|
|
`type '${as}' not compatible with type '${bs}'`,
|
|
);
|
|
};
|
|
switch (a.kind.tag) {
|
|
case "unknown":
|
|
return this.resolveTys(b, a);
|
|
case "null": {
|
|
if (b.kind.tag !== "null") {
|
|
return incompat();
|
|
}
|
|
return Res.Ok(a);
|
|
}
|
|
case "int": {
|
|
if (b.kind.tag !== "int") {
|
|
return incompat();
|
|
}
|
|
return Res.Ok(a);
|
|
}
|
|
case "bool": {
|
|
if (b.kind.tag !== "bool") {
|
|
return incompat();
|
|
}
|
|
return Res.Ok(a);
|
|
}
|
|
case "fn": {
|
|
if (b.kind.tag !== "fn") {
|
|
return incompat();
|
|
}
|
|
if (b.kind.item.id === a.kind.item.id) {
|
|
return incompat();
|
|
}
|
|
return Res.Ok(a);
|
|
}
|
|
}
|
|
exhausted(a.kind);
|
|
}
|
|
|
|
private report(msg: string, span: Span) {
|
|
this.ctx.report({
|
|
severity: "error",
|
|
file: this.currentFile,
|
|
span,
|
|
msg,
|
|
});
|
|
}
|
|
}
|