203 lines
6.0 KiB
TypeScript
203 lines
6.0 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 IntLiteral = Ty.create("IntLiteral", {});
|
|
static I8 = Ty.create("Int", { intTy: "i8" });
|
|
static I16 = Ty.create("Int", { intTy: "i16" });
|
|
static I32 = Ty.create("Int", { intTy: "i32" });
|
|
static I64 = Ty.create("Int", { intTy: "i64" });
|
|
static ISize = Ty.create("Int", { intTy: "isize" });
|
|
static U8 = Ty.create("Int", { intTy: "u8" });
|
|
static U16 = Ty.create("Int", { intTy: "u16" });
|
|
static U32 = Ty.create("Int", { intTy: "u32" });
|
|
static U64 = Ty.create("Int", { intTy: "u64" });
|
|
static USize = Ty.create("Int", { intTy: "usize" });
|
|
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("IntLiteral")) {
|
|
// return other.is("Int") || other.is("UInt");
|
|
return false;
|
|
}
|
|
if (this.is("Int")) {
|
|
return other.is("Int") && this.kind.intTy == other.kind.intTy;
|
|
}
|
|
|
|
if (this.is("Bool")) {
|
|
return other.is("Bool");
|
|
}
|
|
if (this.is("Ptr")) {
|
|
if (!other.is("Ptr")) {
|
|
return false;
|
|
}
|
|
if (!this.kind.ty.compatibleWith(other.kind.ty)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
if (this.is("Array")) {
|
|
if (!other.is("Array")) {
|
|
return false;
|
|
}
|
|
if (!this.kind.ty.compatibleWith(other.kind.ty)) {
|
|
return false;
|
|
}
|
|
if (this.kind.length !== other.kind.length) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
if (this.is("Slice")) {
|
|
if (!other.is("Slice")) {
|
|
return false;
|
|
}
|
|
if (!this.kind.ty.compatibleWith(other.kind.ty)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
if (this.is("Range")) {
|
|
return other.is("Range");
|
|
}
|
|
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) {
|
|
throw new Error();
|
|
}
|
|
return true;
|
|
}
|
|
throw new Error(`'${this.kind.tag}' not handled`);
|
|
}
|
|
|
|
isSized(): boolean {
|
|
if (this.is("Slice")) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
pretty(): string {
|
|
switch (this.kind.tag) {
|
|
case "Error":
|
|
return "<error>";
|
|
case "Void":
|
|
return "void";
|
|
case "IntLiteral":
|
|
return "{integer}";
|
|
case "Int":
|
|
return `${this.kind.intTy}`;
|
|
case "Bool":
|
|
return "bool";
|
|
case "Ptr":
|
|
return `*${this.kind.ty.pretty()}`;
|
|
case "PtrMut":
|
|
return `*mut ${this.kind.ty.pretty()}`;
|
|
case "Array":
|
|
return `[${this.kind.ty.pretty()}; ${this.kind.length}]`;
|
|
case "Slice":
|
|
return `[${this.kind.ty.pretty()}]`;
|
|
case "Range":
|
|
return `Range`;
|
|
case "Fn":
|
|
return `fn (${
|
|
this.kind.params.map((param) => param.pretty()).join(", ")
|
|
}) -> ${this.kind.retTy.pretty()}`;
|
|
case "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()}`;
|
|
default:
|
|
this.kind satisfies never;
|
|
}
|
|
throw new Error("unhandled");
|
|
}
|
|
}
|
|
|
|
export type TyKind =
|
|
| { tag: "Error" }
|
|
| { tag: "Void" }
|
|
| { tag: "IntLiteral" }
|
|
| { tag: "Int"; intTy: ast.IntTy }
|
|
| { tag: "Bool" }
|
|
| { tag: "Ptr"; ty: Ty }
|
|
| { tag: "PtrMut"; ty: Ty }
|
|
| { tag: "Array"; ty: Ty; length: number }
|
|
| { tag: "Slice"; ty: Ty }
|
|
| { tag: "Range" }
|
|
| { tag: "Fn"; params: Ty[]; retTy: Ty }
|
|
| { tag: "FnStmt"; ty: Ty; stmt: ast.NodeWithKind<"FnStmt"> };
|