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,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|