slige/compiler/Checker.ts

243 lines
8.6 KiB
TypeScript
Raw Normal View History

2024-12-06 14:17:52 +01:00
import { EType, Expr } from "./ast.ts";
import { Pos } from "./Token.ts";
import { VType, VTypeParam, vtypesEqual, vtypeToString } from "./vtypes.ts";
export class Checker {
public report(msg: string, pos: Pos) {
console.error(`${msg} at ${pos.line}:${pos.col}`);
}
public checkEType(etype: EType): VType {
const pos = etype.pos;
if (etype.kind.type === "ident") {
if (etype.kind.value === "null") {
return { type: "null" };
}
if (etype.kind.value === "int") {
return { type: "int" };
}
if (etype.kind.value === "bool") {
return { type: "bool" };
}
if (etype.kind.value === "string") {
return { type: "string" };
}
this.report(`undefined type '${etype.kind.value}'`, pos);
return { type: "error" };
}
if (etype.kind.type === "array") {
const inner = this.checkEType(etype.kind.inner);
return { type: "array", inner };
}
if (etype.kind.type === "struct") {
const noTypeTest = etype.kind.fields.reduce(
(acc, param) => [acc[0] || !param.etype, param.ident],
[false, ""],
);
if (noTypeTest[0]) {
this.report(
`field '${noTypeTest[1]}' declared without type`,
pos,
);
return { type: "error" };
}
const declaredTwiceTest = etype.kind.fields.reduce<
[boolean, string[], string]
>(
(acc, curr) => {
if (acc[0]) {
return acc;
}
if (acc[1].includes(curr.ident)) {
return [true, acc[1], curr.ident];
}
return [false, [...acc[1], curr.ident], ""];
},
[false, [], ""],
);
if (
declaredTwiceTest[0]
) {
this.report(`field ${declaredTwiceTest[2]} defined twice`, pos);
return { type: "error" };
}
const fields = etype.kind.fields.map((param): VTypeParam => ({
ident: param.ident,
vtype: this.checkEType(param.etype!),
}));
return { type: "struct", fields };
}
throw new Error(`unknown explicit type ${etype.kind.type}`);
}
public checkExpr(expr: Expr): VType {
const pos = expr.pos;
const vtype = ((): VType => {
switch (expr.kind.type) {
case "error":
throw new Error("error in AST");
case "ident":
throw new Error("ident expr in AST");
case "sym":
return this.checkSymExpr(expr);
case "null":
return { type: "null" };
case "int":
return { type: "int" };
case "bool":
return { type: "bool" };
case "string":
return { type: "string" };
case "binary":
return this.checkBinaryExpr(expr);
case "group":
return this.checkExpr(expr.kind.expr);
2024-12-09 10:52:05 +01:00
case "field":
return this.checkFieldExpr(expr);
case "index":
return this.checkIndexExpr(expr);
case "call":
return this.checkCallExpr(expr);
2024-12-06 14:17:52 +01:00
case "unary":
case "if":
case "loop":
case "block":
}
throw new Error(`unhandled type ${expr.kind.type}`);
})();
expr.vtype = vtype;
throw new Error(`unknown expression ${expr.kind.type}`);
}
public checkSymExpr(expr: Expr): VType {
const pos = expr.pos;
if (expr.kind.type !== "sym") {
throw new Error();
}
}
public checkBinaryExpr(expr: Expr): VType {
if (expr.kind.type !== "binary") {
throw new Error();
}
2024-12-09 10:52:05 +01:00
const pos = expr.pos;
2024-12-06 14:17:52 +01:00
const left = this.checkExpr(expr.kind.left);
const right = this.checkExpr(expr.kind.right);
for (const operation of simpleBinaryOperations) {
if (operation.binaryType !== expr.kind.binaryType) {
continue;
}
if (!vtypesEqual(operation.operand, left)) {
continue;
}
if (!vtypesEqual(left, right)) {
continue;
}
return operation.result ?? operation.operand;
}
this.report(
`cannot apply binary operation '${expr.kind.binaryType}' ` +
`on types '${vtypeToString(left)}' and '${
vtypeToString(right)
}'`,
pos,
);
return { type: "error" };
}
2024-12-09 10:52:05 +01:00
public checkFieldExpr(expr: Expr): VType {
if (expr.kind.type !== "field") {
throw new Error();
}
const pos = expr.pos;
const subject = this.checkExpr(expr.kind.subject);
if (subject.type !== "struct") {
this.report("cannot use field on non-struct", pos);
return { type: "error" };
}
const value = expr.kind.value;
const found = subject.fields.find((param) => param.ident === value);
if (!found) {
this.report(
`no field named '${expr.kind.value}' on struct`,
pos,
);
return { type: "error" };
}
return found.vtype;
}
public checkIndexExpr(expr: Expr): VType {
if (expr.kind.type !== "index") {
throw new Error();
}
const pos = expr.pos;
const subject = this.checkExpr(expr.kind.subject);
if (subject.type !== "array") {
this.report("cannot index on non-array", pos);
return { type: "error" };
}
return subject.inner;
}
public checkCallExpr(expr: Expr): VType {
if (expr.kind.type !== "call") {
throw new Error();
}
const pos = expr.pos;
const subject = this.checkExpr(expr.kind.subject);
if (subject.type !== "fn") {
this.report("cannot call non-fn", pos);
return { type: "error" };
}
const args = expr.kind.args.map((arg) => this.checkExpr(arg));
if (args.length !== subject.params.length) {
this.report(
`incorrect number of arguments` +
`, expected ${subject.params.length}`,
pos,
);
}
for (let i = 0; i < args.length; ++i) {
if (!vtypesEqual(args[i], subject.params[i].vtype)) {
this.report(
`incorrect argument ${i} '${subject.params[i].ident}'` +
`, expected ${vtypeToString(subject.params[i].vtype)}` +
`, got ${vtypeToString(args[i])}`,
pos,
);
break;
}
}
return subject.returnType;
}
2024-12-06 14:17:52 +01:00
}
const simpleBinaryOperations: {
binaryType: string;
operand: VType;
result?: VType;
}[] = [
// arithmetic
{ binaryType: "+", operand: { type: "int" } },
{ binaryType: "-", operand: { type: "int" } },
{ binaryType: "*", operand: { type: "int" } },
{ binaryType: "/", operand: { type: "int" } },
// logical
{ binaryType: "and", operand: { type: "bool" } },
{ binaryType: "or", operand: { type: "bool" } },
// equality
{ binaryType: "==", operand: { type: "null" }, result: { type: "bool" } },
{ binaryType: "==", operand: { type: "int" }, result: { type: "bool" } },
{ binaryType: "==", operand: { type: "string" }, result: { type: "bool" } },
{ binaryType: "==", operand: { type: "bool" }, result: { type: "bool" } },
{ binaryType: "!=", operand: { type: "null" }, result: { type: "bool" } },
{ binaryType: "!=", operand: { type: "int" }, result: { type: "bool" } },
{ binaryType: "!=", operand: { type: "string" }, result: { type: "bool" } },
{ binaryType: "!=", operand: { type: "bool" }, result: { type: "bool" } },
// comparison
{ binaryType: "<", operand: { type: "int" }, result: { type: "bool" } },
{ binaryType: ">", operand: { type: "int" }, result: { type: "bool" } },
{ binaryType: "<=", operand: { type: "int" }, result: { type: "bool" } },
{ binaryType: ">=", operand: { type: "int" }, result: { type: "bool" } },
];