nandgame-compiler/checker.ts

314 lines
9.5 KiB
TypeScript
Raw Normal View History

2023-04-27 01:32:06 +01:00
import { CheckedExpr, CheckedType } from "./checked.ts";
2023-05-01 12:36:06 +01:00
import { ParsedExpr as AnyParsedExpr } from "./parsed.ts";
2023-04-27 13:48:08 +01:00
import { CompileError, Position } from "./token.ts";
2023-04-27 01:32:06 +01:00
2023-05-01 12:36:06 +01:00
type ParsedExpr<
Type extends AnyParsedExpr["exprType"] = AnyParsedExpr["exprType"],
> =
& AnyParsedExpr
& {
exprType: Type;
};
2023-04-27 01:32:06 +01:00
export type SymbolValue = {
id: number;
subject: string;
valueType: CheckedType;
};
export class SymbolTable {
private idCounter = 0;
private symbols: { [key: string]: SymbolValue } = {};
public define(subject: string, valueType: CheckedType) {
if (subject in this.symbols) {
throw new Error("redefinition not implemented");
}
const id = this.idCounter;
this.idCounter++;
this.symbols[subject] = { id, subject, valueType };
}
}
export class Checker {
private symbolTable = new SymbolTable();
public errors: CompileError[] = [];
public check(statements: ParsedExpr[]): CheckedExpr[] {
return statements as CheckedExpr[];
}
private searchTopLevelStatements(statements: ParsedExpr[]) {
for (const statement of statements) {
switch (statement.exprType) {
case "fn":
this.searchTopLevelFn(statement);
break;
case "let":
break;
default:
this.errors.push({
pos: statement.pos,
message:
`statement '${statement.exprType}' not supported in top level`,
});
break;
}
}
}
2023-05-01 12:36:06 +01:00
private searchTopLevelFn(statement: ParsedExpr<"fn">) {
2023-04-27 01:32:06 +01:00
}
2023-05-04 00:06:09 +01:00
private checkStatement(expr: ParsedExpr): CheckedExpr {
switch (expr.exprType) {
case "if":
return this.checkIfStatement(expr);
case "let":
return this.checkLetStatement(expr);
case "fn":
return this.checkFnStatement(expr);
case "return":
return this.checkReturnStatement(expr);
case "loop":
return this.checkLoopStatement(expr);
case "while":
return this.checkWhileStatement(expr);
case "break":
return this.checkBreakStatement(expr);
case "continue":
return this.checkContinueStatement(expr);
default:
return this.checkExpr(expr);
}
}
private checkIfStatement(expr: ParsedExpr<"if">): CheckedExpr {
const condition = this.checkExpr(expr.condition);
const truthy = this.checkExpr(expr.truthy);
const falsy = expr.falsy ? this.checkExpr(expr.falsy) : null;
return {
pos: expr.pos,
exprType: "if",
condition,
truthy,
falsy,
valueType: { pos: expr.pos, typeType: "unit" },
};
}
private checkLetStatement(expr: ParsedExpr<"let">): CheckedExpr {
throw new Error("not implemented");
}
private checkFnStatement(expr: ParsedExpr<"fn">): CheckedExpr {
throw new Error("not implemented");
}
private checkReturnStatement(expr: ParsedExpr<"return">): CheckedExpr {
throw new Error("not implemented");
}
private checkLoopStatement(expr: ParsedExpr<"loop">): CheckedExpr {
const body = this.checkExpr(expr.body);
return {
pos: expr.pos,
exprType: "loop",
body,
valueType: { pos: expr.pos, typeType: "unit" },
};
}
private checkWhileStatement(expr: ParsedExpr<"while">): CheckedExpr {
const condition = this.checkExpr(expr.condition);
const body = this.checkExpr(expr.body);
return {
pos: expr.pos,
exprType: "while",
condition,
body,
valueType: { pos: expr.pos, typeType: "unit" },
};
}
private checkBreakStatement(expr: ParsedExpr<"break">): CheckedExpr {
return { ...expr, valueType: { pos: expr.pos, typeType: "unit" } };
}
private checkContinueStatement(expr: ParsedExpr<"continue">): CheckedExpr {
return { ...expr, valueType: { pos: expr.pos, typeType: "unit" } };
}
2023-04-27 13:48:08 +01:00
private checkExpr(expr: ParsedExpr): CheckedExpr {
switch (expr.exprType) {
case "error":
return this.errorExpr(expr.message, expr.pos);
case "unit":
2023-05-04 00:06:09 +01:00
return {
pos: expr.pos,
exprType: "unit",
valueType: { pos: expr.pos, typeType: "unit" },
};
2023-04-27 13:48:08 +01:00
case "id":
2023-05-04 00:06:09 +01:00
return this.checkId(expr);
2023-04-27 13:48:08 +01:00
case "int":
2023-05-01 12:36:06 +01:00
return this.checkInt(expr);
2023-04-27 13:48:08 +01:00
case "if":
2023-05-04 00:06:09 +01:00
return this.checkIf(expr);
2023-04-27 13:48:08 +01:00
case "block":
2023-05-04 00:06:09 +01:00
return this.checkBlock(expr);
2023-04-27 13:48:08 +01:00
case "call":
2023-05-04 00:06:09 +01:00
return this.checkCall(expr);
2023-04-27 13:48:08 +01:00
case "index":
2023-05-04 00:06:09 +01:00
return this.checkIndex(expr);
2023-04-27 13:48:08 +01:00
case "increment":
2023-05-04 00:06:09 +01:00
return this.checkIncrement(expr as ParsedExpr<"increment">);
2023-04-27 13:48:08 +01:00
case "decrement":
2023-05-04 00:06:09 +01:00
return this.checkDecrement(expr as ParsedExpr<"decrement">);
2023-04-27 13:48:08 +01:00
case "unary":
2023-05-04 00:06:09 +01:00
return this.checkUnary(expr);
2023-04-27 13:48:08 +01:00
case "binary":
2023-05-04 00:06:09 +01:00
return this.checkBinary(expr);
2023-04-27 13:48:08 +01:00
case "assign":
2023-05-04 00:06:09 +01:00
return this.checkAssign(expr);
2023-04-27 13:48:08 +01:00
default:
return this.errorExpr(
`expected expression, got '${expr.exprType}' statement`,
expr.pos,
);
}
}
2023-05-04 00:06:09 +01:00
private checkId(_expr: ParsedExpr<"id">): CheckedExpr {
throw new Error("not implemented");
}
2023-05-01 12:36:06 +01:00
private checkInt(expr: ParsedExpr<"int">): CheckedExpr {
2023-05-04 00:06:09 +01:00
if (expr.value >= 1 << 15) {
return this.errorExpr("16-bit literal too large", expr.pos);
}
2023-05-01 12:36:06 +01:00
return {
pos: expr.pos,
exprType: "int",
value: expr.value,
valueType: { pos: expr.pos, typeType: "i16" },
};
}
2023-05-04 00:06:09 +01:00
private checkIf(expr: ParsedExpr<"if">): CheckedExpr {
const condition = this.checkExpr(expr.condition);
const truthy = this.checkExpr(expr.truthy);
if (!expr.falsy) {
this.errors.push({
pos: expr.pos,
message: "expected else-block in if expression",
});
return {
pos: expr.pos,
exprType: "if",
condition,
truthy,
falsy: null,
valueType: { pos: expr.pos, typeType: "unit" },
};
}
const falsy = this.checkExpr(expr.falsy);
const valueType = this.compatibleType(
truthy.valueType,
falsy.valueType,
);
return {
pos: expr.pos,
exprType: "if",
condition,
truthy,
falsy,
valueType,
};
}
private checkBlock(expr: ParsedExpr<"block">): CheckedExpr {
const body = expr.body.map((statement) =>
this.checkStatement(statement)
);
if (expr.value) {
const value = this.checkExpr(expr.value);
return {
pos: expr.pos,
exprType: "block",
body,
value,
valueType: value.valueType,
};
} else {
return {
pos: expr.pos,
exprType: "block",
body,
value: null,
valueType: { pos: expr.pos, typeType: "unit" },
};
}
}
private checkCall(expr: ParsedExpr<"call">): CheckedExpr {
throw new Error("not implemented");
}
private checkIndex(expr: ParsedExpr<"index">): CheckedExpr {
throw new Error("not implemented");
}
private checkIncrement(expr: ParsedExpr<"increment">): CheckedExpr {
throw new Error("not implemented");
}
private checkDecrement(expr: ParsedExpr<"decrement">): CheckedExpr {
throw new Error("not implemented");
}
private checkUnary(expr: ParsedExpr<"unary">): CheckedExpr {
throw new Error("not implemented");
}
private checkBinary(expr: ParsedExpr<"binary">): CheckedExpr {
throw new Error("not implemented");
}
private checkAssign(expr: ParsedExpr<"assign">): CheckedExpr {
throw new Error("not implemented");
}
private compatibleType(
a: CheckedType,
b: CheckedType,
reversed = false,
): CheckedType {
if (a.typeType === "error") {
return a;
} else if (b.typeType === "error") {
return b;
} else if (a.typeType === "unit" && b.typeType === "unit") {
return a;
} else if (a.typeType === "u16" && b.typeType === "u16") {
return a;
} else if (a.typeType === "u16" && b.typeType === "i16") {
return a;
} else if (!reversed) {
return this.compatibleType(b, a, true);
} else {
throw new Error("not implemented");
}
}
2023-04-27 13:48:08 +01:00
private errorExpr(message: string, pos: Position): CheckedExpr {
this.errors.push({ message, pos });
return {
pos,
exprType: "error",
2023-05-04 00:06:09 +01:00
valueType: { pos, typeType: "unit" },
2023-04-27 13:48:08 +01:00
message,
};
}
}