diff --git a/checked.ts b/checked.ts index f96e386..f2d4d31 100644 --- a/checked.ts +++ b/checked.ts @@ -4,6 +4,7 @@ import { Position } from "./token.ts"; export type CheckedExpr = & { pos: Position; + valueType: CheckedType; } & ({ exprType: "error"; message: string } | { exprType: "unit"; @@ -13,7 +14,6 @@ export type CheckedExpr = } | { exprType: "int"; value: number; - valueType: CheckedType; } | { exprType: "if"; condition: CheckedExpr; @@ -23,7 +23,6 @@ export type CheckedExpr = exprType: "block"; body: CheckedExpr[]; value: CheckedExpr | null; - valueType: CheckedType; } | { exprType: "call"; subject: CheckedExpr; diff --git a/checker.ts b/checker.ts index 353f3d1..6bb7842 100644 --- a/checker.ts +++ b/checker.ts @@ -60,25 +60,117 @@ export class Checker { private searchTopLevelFn(statement: ParsedExpr<"fn">) { } + 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" } }; + } + private checkExpr(expr: ParsedExpr): CheckedExpr { switch (expr.exprType) { case "error": return this.errorExpr(expr.message, expr.pos); case "unit": - return { pos: expr.pos, exprType: "unit" }; + return { + pos: expr.pos, + exprType: "unit", + valueType: { pos: expr.pos, typeType: "unit" }, + }; case "id": - throw new Error("not implemented"); + return this.checkId(expr); case "int": return this.checkInt(expr); case "if": + return this.checkIf(expr); case "block": + return this.checkBlock(expr); case "call": + return this.checkCall(expr); case "index": + return this.checkIndex(expr); case "increment": + return this.checkIncrement(expr as ParsedExpr<"increment">); case "decrement": + return this.checkDecrement(expr as ParsedExpr<"decrement">); case "unary": + return this.checkUnary(expr); case "binary": + return this.checkBinary(expr); case "assign": + return this.checkAssign(expr); default: return this.errorExpr( `expected expression, got '${expr.exprType}' statement`, @@ -87,7 +179,14 @@ export class Checker { } } + private checkId(_expr: ParsedExpr<"id">): CheckedExpr { + throw new Error("not implemented"); + } + private checkInt(expr: ParsedExpr<"int">): CheckedExpr { + if (expr.value >= 1 << 15) { + return this.errorExpr("16-bit literal too large", expr.pos); + } return { pos: expr.pos, exprType: "int", @@ -96,11 +195,118 @@ export class Checker { }; } + 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"); + } + } + private errorExpr(message: string, pos: Position): CheckedExpr { this.errors.push({ message, pos }); return { pos, exprType: "error", + valueType: { pos, typeType: "unit" }, message, }; }