2024-12-06 13:17:52 +00: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 09:52:05 +00:00
|
|
|
case "field":
|
|
|
|
return this.checkFieldExpr(expr);
|
|
|
|
case "index":
|
|
|
|
return this.checkIndexExpr(expr);
|
|
|
|
case "call":
|
|
|
|
return this.checkCallExpr(expr);
|
2024-12-06 13:17:52 +00: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 09:52:05 +00:00
|
|
|
const pos = expr.pos;
|
2024-12-06 13:17:52 +00: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 09:52:05 +00: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 13:17:52 +00: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" } },
|
|
|
|
];
|