138 lines
3.8 KiB
TypeScript
138 lines
3.8 KiB
TypeScript
import * as ast from "./ast.ts";
|
|
|
|
export class Ty {
|
|
private static idCounter = 0;
|
|
private static internedTys = new Map<string, Ty>();
|
|
|
|
static create<Tag extends TyKind["tag"]>(
|
|
tag: Tag,
|
|
kind: Omit<TyKind & { tag: Tag }, "tag">,
|
|
): Ty {
|
|
const ty = new Ty(
|
|
this.idCounter,
|
|
{ tag, ...kind } as TyKind & { tag: Tag },
|
|
);
|
|
const hash = ty.internHash();
|
|
if (this.internedTys.has(hash)) {
|
|
return this.internedTys.get(hash)!;
|
|
}
|
|
this.internedTys.set(hash, ty);
|
|
this.idCounter += 1;
|
|
return ty;
|
|
}
|
|
|
|
static Error = Ty.create("Error", {});
|
|
static Void = Ty.create("Void", {});
|
|
static Int = Ty.create("Int", {});
|
|
static Bool = Ty.create("Bool", {});
|
|
|
|
private internHash(): string {
|
|
return JSON.stringify(this.kind);
|
|
}
|
|
|
|
private constructor(
|
|
public id: number,
|
|
public kind: TyKind,
|
|
) {}
|
|
|
|
is<
|
|
Tag extends TyKind["tag"],
|
|
>(tag: Tag): this is Ty & { kind: { tag: Tag } } {
|
|
return this.kind.tag === tag;
|
|
}
|
|
|
|
compatibleWith(other: Ty): boolean {
|
|
// types are interned--we can just do this
|
|
if (this === other) {
|
|
return true;
|
|
}
|
|
|
|
if (this.is("Error")) {
|
|
return false;
|
|
}
|
|
if (this.is("Void")) {
|
|
return other.is("Void");
|
|
}
|
|
if (this.is("Int")) {
|
|
return other.is("Int");
|
|
}
|
|
if (this.is("Bool")) {
|
|
return other.is("Bool");
|
|
}
|
|
if (this.is("Fn")) {
|
|
if (!other.is("Fn")) {
|
|
return false;
|
|
}
|
|
for (const i of this.kind.params.keys()) {
|
|
if (!this.kind.params[i].compatibleWith(other.kind.params[i])) {
|
|
return false;
|
|
}
|
|
}
|
|
if (!this.kind.retTy.compatibleWith(other.kind.retTy)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
if (this.is("FnStmt")) {
|
|
if (!other.is("FnStmt")) {
|
|
return false;
|
|
}
|
|
if (!this.kind.ty.compatibleWith(other.kind.ty)) {
|
|
return false;
|
|
}
|
|
// redundant; sanity check
|
|
if (this.kind.stmt.id !== other.kind.stmt.id) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
throw new Error(`'${this.kind.tag}' not handled`);
|
|
}
|
|
|
|
pretty(): string {
|
|
if (this.is("Error")) {
|
|
return "<error>";
|
|
}
|
|
if (this.is("Void")) {
|
|
return "void";
|
|
}
|
|
if (this.is("Int")) {
|
|
return "int";
|
|
}
|
|
if (this.is("Bool")) {
|
|
return "bool";
|
|
}
|
|
if (this.is("Ptr")) {
|
|
return `*${this.kind.ty.pretty()}`;
|
|
}
|
|
if (this.is("PtrMut")) {
|
|
return `*mut ${this.kind.ty.pretty()}`;
|
|
}
|
|
if (this.is("Fn")) {
|
|
return `fn (${
|
|
this.kind.params.map((param) => param.pretty()).join(", ")
|
|
}) -> ${this.kind.retTy.pretty()}`;
|
|
}
|
|
if (this.is("FnStmt")) {
|
|
if (!this.kind.ty.is("Fn")) throw new Error();
|
|
return `fn ${this.kind.stmt.kind.ident}(${
|
|
this.kind.ty.kind.params.map((param) => param.pretty()).join(
|
|
", ",
|
|
)
|
|
}) -> ${this.kind.ty.kind.retTy.pretty()}`;
|
|
}
|
|
throw new Error("unhandled");
|
|
}
|
|
}
|
|
|
|
export type TyKind =
|
|
| { tag: "Error" }
|
|
| { tag: "Void" }
|
|
| { tag: "Int" }
|
|
| { tag: "Bool" }
|
|
| { tag: "Ptr"; ty: Ty }
|
|
| { tag: "PtrMut"; ty: Ty }
|
|
| { tag: "Array"; ty: Ty; length: number }
|
|
| { tag: "Fn"; params: Ty[]; retTy: Ty }
|
|
| { tag: "FnStmt"; ty: Ty; stmt: ast.NodeWithKind<"FnStmt"> };
|