2024-12-09 13:33:33 +01:00
|
|
|
import { StmtKind } from "./ast.ts";
|
|
|
|
import { EType, Expr, Stmt } from "./ast.ts";
|
2024-12-10 21:42:15 +01:00
|
|
|
import { Pos } from "./token.ts";
|
|
|
|
import { VType, VTypeParam, vtypesEqual, vtypeToString } from "./vtype.ts";
|
2024-12-06 14:17:52 +01:00
|
|
|
|
|
|
|
export class Checker {
|
2024-12-09 13:33:33 +01:00
|
|
|
private fnReturnStack: VType[] = [];
|
|
|
|
private loopBreakStack: VType[][] = [];
|
|
|
|
|
|
|
|
public check(stmts: Stmt[]) {
|
|
|
|
for (const stmt of stmts) {
|
|
|
|
this.checkStmt(stmt);
|
|
|
|
}
|
2024-12-06 14:17:52 +01:00
|
|
|
}
|
2024-12-09 13:33:33 +01:00
|
|
|
|
|
|
|
public checkStmt(stmt: Stmt) {
|
|
|
|
switch (stmt.kind.type) {
|
|
|
|
case "error":
|
|
|
|
return { type: "error" };
|
|
|
|
case "break":
|
|
|
|
return this.checkBreakStmt(stmt);
|
|
|
|
case "return":
|
|
|
|
return this.checkReturnStmt(stmt);
|
|
|
|
case "fn":
|
|
|
|
return this.checkFnStmt(stmt);
|
|
|
|
case "let":
|
|
|
|
return this.checkLetStmt(stmt);
|
|
|
|
case "assign":
|
|
|
|
return this.checkAssignStmt(stmt);
|
|
|
|
case "expr":
|
|
|
|
return this.checkExpr(stmt.kind.expr);
|
2024-12-06 14:17:52 +01:00
|
|
|
}
|
2024-12-09 13:33:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public checkBreakStmt(stmt: Stmt) {
|
|
|
|
if (stmt.kind.type !== "break") {
|
|
|
|
throw new Error();
|
2024-12-06 14:17:52 +01:00
|
|
|
}
|
2024-12-09 13:33:33 +01:00
|
|
|
const pos = stmt.pos;
|
|
|
|
if (this.loopBreakStack.length === 0) {
|
|
|
|
this.report("cannot break outside loop context", pos);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const exprType: VType = stmt.kind.expr
|
|
|
|
? this.checkExpr(stmt.kind.expr)
|
|
|
|
: { type: "null" };
|
|
|
|
const breakTypes = this.loopBreakStack.at(-1)!;
|
|
|
|
if (breakTypes.length === 0) {
|
|
|
|
breakTypes.push(exprType);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const prevBreakType = breakTypes.at(-1)!;
|
|
|
|
if (!vtypesEqual(prevBreakType, exprType)) {
|
|
|
|
this.report(
|
|
|
|
`incompatible types for break` +
|
2024-12-10 21:42:15 +01:00
|
|
|
`, got ${exprType}` +
|
|
|
|
` incompatible with ${prevBreakType}`,
|
2024-12-09 13:33:33 +01:00
|
|
|
pos,
|
2024-12-06 14:17:52 +01:00
|
|
|
);
|
2024-12-09 13:33:33 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
breakTypes.push(exprType);
|
|
|
|
}
|
|
|
|
|
|
|
|
public checkReturnStmt(stmt: Stmt) {
|
|
|
|
if (stmt.kind.type !== "return") {
|
|
|
|
throw new Error();
|
|
|
|
}
|
|
|
|
const pos = stmt.pos;
|
|
|
|
if (this.fnReturnStack.length === 0) {
|
|
|
|
this.report("cannot return outside fn context", pos);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const exprType: VType = stmt.kind.expr
|
|
|
|
? this.checkExpr(stmt.kind.expr)
|
|
|
|
: { type: "null" };
|
|
|
|
const returnType = this.fnReturnStack.at(-1)!;
|
|
|
|
if (!vtypesEqual(exprType, returnType)) {
|
|
|
|
this.report(
|
|
|
|
`incompatible return type` +
|
2024-12-10 21:42:15 +01:00
|
|
|
`, got ${exprType}` +
|
|
|
|
`, expected ${returnType}`,
|
2024-12-09 13:33:33 +01:00
|
|
|
pos,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public checkFnStmt(stmt: Stmt) {
|
|
|
|
if (stmt.kind.type !== "fn") {
|
|
|
|
throw new Error();
|
|
|
|
}
|
|
|
|
const pos = stmt.pos;
|
|
|
|
const returnType: VType = stmt.kind.returnType
|
|
|
|
? this.checkEType(stmt.kind.returnType)
|
|
|
|
: { type: "null" };
|
|
|
|
const params: VTypeParam[] = [];
|
|
|
|
for (const param of stmt.kind.params) {
|
|
|
|
if (param.etype === undefined) {
|
|
|
|
this.report("parameter types must be defined", param.pos);
|
|
|
|
stmt.kind.vtype = { type: "error" };
|
|
|
|
}
|
|
|
|
const vtype = this.checkEType(param.etype!);
|
|
|
|
param.vtype = vtype;
|
|
|
|
params.push({ ident: param.ident, vtype });
|
|
|
|
}
|
|
|
|
stmt.kind.vtype = { type: "fn", params, returnType };
|
|
|
|
this.fnReturnStack.push(returnType);
|
|
|
|
const body = this.checkExpr(stmt.kind.body);
|
|
|
|
this.fnReturnStack.pop();
|
|
|
|
if (!vtypesEqual(returnType, body)) {
|
|
|
|
this.report(
|
|
|
|
`incompatible return type` +
|
2024-12-10 21:42:15 +01:00
|
|
|
`, got ${body}` +
|
|
|
|
`, expected ${returnType}`,
|
2024-12-09 13:33:33 +01:00
|
|
|
pos,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public checkLetStmt(stmt: Stmt) {
|
|
|
|
if (stmt.kind.type !== "let") {
|
|
|
|
throw new Error();
|
|
|
|
}
|
|
|
|
const pos = stmt.pos;
|
|
|
|
const value = this.checkExpr(stmt.kind.value);
|
|
|
|
if (stmt.kind.param.etype) {
|
|
|
|
const paramVtype = this.checkEType(stmt.kind.param.etype);
|
|
|
|
if (!vtypesEqual(value, paramVtype)) {
|
2024-12-06 14:17:52 +01:00
|
|
|
this.report(
|
2024-12-09 13:33:33 +01:00
|
|
|
`incompatible value type` +
|
2024-12-10 21:42:15 +01:00
|
|
|
`, got '${vtypeToString(value)}'` +
|
|
|
|
`, expected '${vtypeToString(paramVtype)}'`,
|
2024-12-06 14:17:52 +01:00
|
|
|
pos,
|
|
|
|
);
|
2024-12-09 13:33:33 +01:00
|
|
|
return;
|
2024-12-06 14:17:52 +01:00
|
|
|
}
|
2024-12-09 13:33:33 +01:00
|
|
|
}
|
|
|
|
stmt.kind.param.vtype = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
public checkAssignStmt(stmt: Stmt) {
|
|
|
|
if (stmt.kind.type !== "assign") {
|
|
|
|
throw new Error();
|
|
|
|
}
|
|
|
|
const pos = stmt.pos;
|
|
|
|
const value = this.checkExpr(stmt.kind.value);
|
|
|
|
switch (stmt.kind.subject.kind.type) {
|
|
|
|
case "field": {
|
|
|
|
const subject = this.checkExpr(stmt.kind.subject.kind.subject);
|
|
|
|
if (subject.type !== "struct") {
|
|
|
|
this.report("cannot use field on non-struct", pos);
|
|
|
|
return { type: "error" };
|
|
|
|
}
|
|
|
|
const fieldValue = stmt.kind.subject.kind.value;
|
|
|
|
const found = subject.fields.find((param) =>
|
|
|
|
param.ident === fieldValue
|
|
|
|
);
|
|
|
|
if (!found) {
|
|
|
|
this.report(
|
|
|
|
`no field named '${stmt.kind.subject.kind.value}' on struct`,
|
|
|
|
pos,
|
|
|
|
);
|
|
|
|
return { type: "error" };
|
|
|
|
}
|
|
|
|
if (!vtypesEqual(found.vtype, value)) {
|
|
|
|
this.report(
|
|
|
|
`cannot assign incompatible type to field '${found.ident}'` +
|
2024-12-10 21:42:15 +01:00
|
|
|
`, got '${vtypeToString(value)}'` +
|
|
|
|
`, expected '${vtypeToString(found.vtype)}'`,
|
2024-12-09 13:33:33 +01:00
|
|
|
pos,
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
return;
|
2024-12-06 14:17:52 +01:00
|
|
|
}
|
2024-12-09 13:33:33 +01:00
|
|
|
case "index": {
|
|
|
|
const subject = this.checkExpr(stmt.kind.subject.kind.subject);
|
|
|
|
if (subject.type !== "array") {
|
|
|
|
this.report("cannot index on non-array", pos);
|
|
|
|
return { type: "error" };
|
|
|
|
}
|
|
|
|
const indexValue = this.checkExpr(stmt.kind.subject.kind.value);
|
|
|
|
if (indexValue.type !== "int") {
|
|
|
|
this.report("cannot index on array with non-int", pos);
|
|
|
|
return { type: "error" };
|
|
|
|
}
|
|
|
|
if (!vtypesEqual(subject.inner, value)) {
|
|
|
|
this.report(
|
|
|
|
`cannot assign incompatible type to array ` +
|
2024-12-10 21:42:15 +01:00
|
|
|
`'${vtypeToString(subject)}'` +
|
|
|
|
`, got '${vtypeToString(value)}'`,
|
2024-12-09 13:33:33 +01:00
|
|
|
pos,
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
case "sym": {
|
2024-12-10 14:36:41 +01:00
|
|
|
if (stmt.kind.subject.kind.sym.type !== "let") {
|
2024-12-09 13:33:33 +01:00
|
|
|
this.report("cannot only assign to let-symbol", pos);
|
|
|
|
return { type: "error" };
|
|
|
|
}
|
2024-12-10 14:36:41 +01:00
|
|
|
if (
|
|
|
|
!vtypesEqual(stmt.kind.subject.kind.sym.param.vtype!, value)
|
|
|
|
) {
|
2024-12-09 13:33:33 +01:00
|
|
|
this.report(
|
|
|
|
`cannot assign to incompatible type` +
|
2024-12-10 21:42:15 +01:00
|
|
|
`, got '${vtypeToString(value)}'` +
|
|
|
|
`, expected '${vtypeToString(
|
|
|
|
stmt.kind.subject.kind.sym.param.vtype!,
|
|
|
|
)
|
|
|
|
}'`,
|
2024-12-09 13:33:33 +01:00
|
|
|
pos,
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
this.report("unassignable expression", pos);
|
|
|
|
return;
|
2024-12-06 14:17:52 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public checkExpr(expr: Expr): VType {
|
|
|
|
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 "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":
|
2024-12-09 13:33:33 +01:00
|
|
|
return this.checkUnaryExpr(expr);
|
|
|
|
case "binary":
|
|
|
|
return this.checkBinaryExpr(expr);
|
2024-12-06 14:17:52 +01:00
|
|
|
case "if":
|
2024-12-09 13:33:33 +01:00
|
|
|
return this.checkIfExpr(expr);
|
2024-12-06 14:17:52 +01:00
|
|
|
case "loop":
|
2024-12-09 13:33:33 +01:00
|
|
|
return this.checkLoopExpr(expr);
|
2024-12-06 14:17:52 +01:00
|
|
|
case "block":
|
2024-12-09 13:33:33 +01:00
|
|
|
return this.checkBlockExpr(expr);
|
2024-12-06 14:17:52 +01:00
|
|
|
}
|
2024-12-09 13:33:33 +01:00
|
|
|
// throw new Error(`unhandled type ${expr.kind.type}`);
|
2024-12-06 14:17:52 +01:00
|
|
|
})();
|
2024-12-09 13:33:33 +01:00
|
|
|
return expr.vtype = vtype;
|
2024-12-06 14:17:52 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public checkSymExpr(expr: Expr): VType {
|
|
|
|
if (expr.kind.type !== "sym") {
|
|
|
|
throw new Error();
|
|
|
|
}
|
2024-12-10 14:36:41 +01:00
|
|
|
switch (expr.kind.sym.type) {
|
2024-12-09 13:33:33 +01:00
|
|
|
case "let":
|
2024-12-10 14:36:41 +01:00
|
|
|
return expr.kind.sym.param.vtype!;
|
2024-12-09 13:33:33 +01:00
|
|
|
case "fn": {
|
2024-12-10 14:36:41 +01:00
|
|
|
const fnStmt = expr.kind.sym.stmt!;
|
2024-12-09 13:33:33 +01:00
|
|
|
if (fnStmt.kind.type !== "fn") {
|
|
|
|
throw new Error();
|
|
|
|
}
|
|
|
|
const vtype = fnStmt.kind.vtype!;
|
|
|
|
if (vtype.type !== "fn") {
|
|
|
|
throw new Error();
|
|
|
|
}
|
|
|
|
const { params, returnType } = vtype;
|
|
|
|
return { type: "fn", params, returnType };
|
2024-12-06 14:17:52 +01:00
|
|
|
}
|
2024-12-09 13:33:33 +01:00
|
|
|
case "fn_param":
|
2024-12-10 14:36:41 +01:00
|
|
|
return expr.kind.sym.param.vtype!;
|
2024-12-09 13:33:33 +01:00
|
|
|
case "builtin":
|
2024-12-10 14:36:41 +01:00
|
|
|
case "let_static":
|
|
|
|
case "closure":
|
|
|
|
throw new Error(
|
|
|
|
`not implemented, sym type '${expr.kind.sym.type}'`,
|
|
|
|
);
|
2024-12-06 14:17:52 +01:00
|
|
|
}
|
|
|
|
}
|
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" };
|
|
|
|
}
|
2024-12-09 13:33:33 +01:00
|
|
|
const value = this.checkExpr(expr.kind.value);
|
|
|
|
if (value.type !== "int") {
|
|
|
|
this.report("cannot index on array with non-int", pos);
|
|
|
|
return { type: "error" };
|
|
|
|
}
|
2024-12-09 10:52:05 +01:00
|
|
|
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` +
|
2024-12-10 21:42:15 +01:00
|
|
|
`, expected ${subject.params.length}`,
|
2024-12-09 10:52:05 +01:00
|
|
|
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}'` +
|
2024-12-10 21:42:15 +01:00
|
|
|
`, expected ${vtypeToString(subject.params[i].vtype)}` +
|
|
|
|
`, got ${vtypeToString(args[i])}`,
|
2024-12-09 10:52:05 +01:00
|
|
|
pos,
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return subject.returnType;
|
|
|
|
}
|
2024-12-09 13:33:33 +01:00
|
|
|
|
|
|
|
public checkUnaryExpr(expr: Expr): VType {
|
|
|
|
if (expr.kind.type !== "unary") {
|
|
|
|
throw new Error();
|
|
|
|
}
|
|
|
|
const pos = expr.pos;
|
|
|
|
const subject = this.checkExpr(expr.kind.subject);
|
|
|
|
for (const operation of simpleUnaryOperations) {
|
|
|
|
if (operation.unaryType !== expr.kind.unaryType) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (!vtypesEqual(operation.operand, subject)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
return operation.result ?? operation.operand;
|
|
|
|
}
|
|
|
|
this.report(
|
|
|
|
`cannot apply unary operation '${expr.kind.unaryType}' ` +
|
2024-12-10 21:42:15 +01:00
|
|
|
`on type '${vtypeToString(subject)}'`,
|
2024-12-09 13:33:33 +01:00
|
|
|
pos,
|
|
|
|
);
|
|
|
|
return { type: "error" };
|
|
|
|
}
|
|
|
|
|
|
|
|
public checkBinaryExpr(expr: Expr): VType {
|
|
|
|
if (expr.kind.type !== "binary") {
|
|
|
|
throw new Error();
|
|
|
|
}
|
|
|
|
const pos = expr.pos;
|
|
|
|
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}' ` +
|
2024-12-10 21:42:15 +01:00
|
|
|
`on types '${vtypeToString(left)}' and '${vtypeToString(right)
|
|
|
|
}'`,
|
2024-12-09 13:33:33 +01:00
|
|
|
pos,
|
|
|
|
);
|
|
|
|
return { type: "error" };
|
|
|
|
}
|
|
|
|
|
|
|
|
public checkIfExpr(expr: Expr): VType {
|
|
|
|
if (expr.kind.type !== "if") {
|
|
|
|
throw new Error();
|
|
|
|
}
|
|
|
|
const pos = expr.pos;
|
|
|
|
const cond = this.checkExpr(expr.kind.cond);
|
|
|
|
const truthy = this.checkExpr(expr.kind.truthy);
|
|
|
|
const falsy = expr.kind.falsy
|
|
|
|
? this.checkExpr(expr.kind.falsy)
|
|
|
|
: undefined;
|
|
|
|
if (cond.type !== "bool") {
|
|
|
|
this.report(
|
|
|
|
`if condition should be 'bool', got '${vtypeToString(cond)}'`,
|
|
|
|
pos,
|
|
|
|
);
|
|
|
|
return { type: "error" };
|
|
|
|
}
|
|
|
|
if (falsy === undefined && truthy.type !== "null") {
|
|
|
|
this.report(
|
|
|
|
`if expressions without false-case must result in type 'null'` +
|
2024-12-10 21:42:15 +01:00
|
|
|
`, got '${vtypeToString(truthy)}'`,
|
2024-12-09 13:33:33 +01:00
|
|
|
pos,
|
|
|
|
);
|
|
|
|
return { type: "error" };
|
|
|
|
}
|
|
|
|
if (falsy !== undefined && !vtypesEqual(truthy, falsy)) {
|
|
|
|
this.report(
|
|
|
|
`if cases must be compatible, got incompatible types` +
|
2024-12-10 21:42:15 +01:00
|
|
|
` '${vtypeToString(truthy)}'` +
|
|
|
|
` and '${vtypeToString(falsy)}'`,
|
2024-12-09 13:33:33 +01:00
|
|
|
pos,
|
|
|
|
);
|
|
|
|
return { type: "error" };
|
|
|
|
}
|
|
|
|
return truthy;
|
|
|
|
}
|
|
|
|
|
|
|
|
public checkLoopExpr(expr: Expr): VType {
|
|
|
|
if (expr.kind.type !== "loop") {
|
|
|
|
throw new Error();
|
|
|
|
}
|
|
|
|
const pos = expr.pos;
|
|
|
|
this.loopBreakStack.push([]);
|
|
|
|
const body = this.checkExpr(expr.kind.body);
|
|
|
|
if (body.type !== "null") {
|
|
|
|
this.report(
|
|
|
|
`loop body must result in type 'null'` +
|
2024-12-10 21:42:15 +01:00
|
|
|
`, got '${vtypeToString(body)}'`,
|
2024-12-09 13:33:33 +01:00
|
|
|
pos,
|
|
|
|
);
|
|
|
|
return { type: "error" };
|
|
|
|
}
|
|
|
|
const loopBreakTypes = this.loopBreakStack.pop()!;
|
|
|
|
if (loopBreakTypes.length === 0) {
|
|
|
|
return { type: "null" };
|
|
|
|
}
|
|
|
|
const breakType = loopBreakTypes.reduce<[VType, boolean, VType]>(
|
|
|
|
(acc, curr) => {
|
|
|
|
const [resulting, isIncompatible, outlier] = acc;
|
|
|
|
if (isIncompatible) {
|
|
|
|
return acc;
|
|
|
|
}
|
|
|
|
if (!vtypesEqual(resulting, curr)) {
|
|
|
|
return [resulting, true, curr];
|
|
|
|
}
|
|
|
|
return [resulting, false, outlier];
|
|
|
|
},
|
|
|
|
[{ type: "null" }, false, { type: "null" }],
|
|
|
|
);
|
|
|
|
if (breakType[1]) {
|
|
|
|
this.report(
|
|
|
|
`incompatible types in break statements` +
|
2024-12-10 21:42:15 +01:00
|
|
|
`, got '${vtypeToString(breakType[2])}'` +
|
|
|
|
` incompatible with ${vtypeToString(breakType[0])}`,
|
2024-12-09 13:33:33 +01:00
|
|
|
pos,
|
|
|
|
);
|
|
|
|
return { type: "error" };
|
|
|
|
}
|
|
|
|
return breakType[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
public checkBlockExpr(expr: Expr): VType {
|
|
|
|
if (expr.kind.type !== "block") {
|
|
|
|
throw new Error();
|
|
|
|
}
|
|
|
|
const pos = expr.pos;
|
|
|
|
for (const stmt of expr.kind.stmts) {
|
|
|
|
this.checkStmt(stmt);
|
|
|
|
}
|
|
|
|
return expr.kind.expr
|
|
|
|
? this.checkExpr(expr.kind.expr)
|
|
|
|
: { type: "null" };
|
|
|
|
}
|
|
|
|
|
|
|
|
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}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
private report(msg: string, pos: Pos) {
|
2024-12-09 13:57:48 +01:00
|
|
|
console.error(`Checker: ${msg} at ${pos.line}:${pos.col}`);
|
|
|
|
class ReportNotAnError extends Error {
|
|
|
|
constructor() {
|
|
|
|
super("ReportNotAnError");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
throw new ReportNotAnError();
|
|
|
|
} catch (error) {
|
|
|
|
if (!(error instanceof ReportNotAnError)) {
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
console.log(error);
|
|
|
|
}
|
2024-12-09 13:33:33 +01:00
|
|
|
}
|
2024-12-06 14:17:52 +01:00
|
|
|
}
|
|
|
|
|
2024-12-09 13:33:33 +01:00
|
|
|
const simpleUnaryOperations: {
|
|
|
|
unaryType: string;
|
|
|
|
operand: VType;
|
|
|
|
result?: VType;
|
|
|
|
}[] = [
|
2024-12-10 21:42:15 +01:00
|
|
|
{ unaryType: "not", operand: { type: "bool" } },
|
|
|
|
];
|
2024-12-09 13:33:33 +01:00
|
|
|
|
2024-12-06 14:17:52 +01:00
|
|
|
const simpleBinaryOperations: {
|
|
|
|
binaryType: string;
|
|
|
|
operand: VType;
|
|
|
|
result?: VType;
|
|
|
|
}[] = [
|
2024-12-10 21:42:15 +01:00
|
|
|
// arithmetic
|
|
|
|
{ binaryType: "+", operand: { type: "int" } },
|
|
|
|
{ binaryType: "+", operand: { type: "string" } },
|
|
|
|
{ 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" } },
|
|
|
|
];
|