test checker
This commit is contained in:
parent
5b4057160d
commit
9e6e92b520
@ -1,11 +1,513 @@
|
||||
import { EType, Expr } from "./ast.ts";
|
||||
import { StmtKind } from "./ast.ts";
|
||||
import { EType, Expr, Stmt } 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}`);
|
||||
private fnReturnStack: VType[] = [];
|
||||
private loopBreakStack: VType[][] = [];
|
||||
|
||||
public check(stmts: Stmt[]) {
|
||||
for (const stmt of stmts) {
|
||||
this.checkStmt(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public checkBreakStmt(stmt: Stmt) {
|
||||
if (stmt.kind.type !== "break") {
|
||||
throw new Error();
|
||||
}
|
||||
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` +
|
||||
`, got ${exprType}` +
|
||||
` incompatible with ${prevBreakType}`,
|
||||
pos,
|
||||
);
|
||||
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` +
|
||||
`, got ${exprType}` +
|
||||
`, expected ${returnType}`,
|
||||
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" };
|
||||
return;
|
||||
}
|
||||
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` +
|
||||
`, got ${body}` +
|
||||
`, expected ${returnType}`,
|
||||
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)) {
|
||||
this.report(
|
||||
`incompatible value type` +
|
||||
`, got '${value}'` +
|
||||
`, expected '${paramVtype}'`,
|
||||
pos,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
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}'` +
|
||||
`, got '${vtypeToString(value)}'` +
|
||||
`, expected '${vtypeToString(found.vtype)}'`,
|
||||
pos,
|
||||
);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
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 ` +
|
||||
`'${vtypeToString(subject)}'` +
|
||||
`, got '${vtypeToString(value)}'`,
|
||||
pos,
|
||||
);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
case "sym": {
|
||||
if (stmt.kind.subject.kind.defType !== "let") {
|
||||
this.report("cannot only assign to let-symbol", pos);
|
||||
return { type: "error" };
|
||||
}
|
||||
if (!vtypesEqual(stmt.kind.subject.kind.param!.vtype!, value)) {
|
||||
this.report(
|
||||
`cannot assign to incompatible type` +
|
||||
`, got '${vtypeToString(value)}'` +
|
||||
`, expected '${
|
||||
vtypeToString(
|
||||
stmt.kind.subject.kind.param!.vtype!,
|
||||
)
|
||||
}'`,
|
||||
pos,
|
||||
);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
default:
|
||||
this.report("unassignable expression", pos);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
case "field":
|
||||
return this.checkFieldExpr(expr);
|
||||
case "index":
|
||||
return this.checkIndexExpr(expr);
|
||||
case "call":
|
||||
return this.checkCallExpr(expr);
|
||||
case "unary":
|
||||
return this.checkUnaryExpr(expr);
|
||||
case "binary":
|
||||
return this.checkBinaryExpr(expr);
|
||||
case "if":
|
||||
return this.checkIfExpr(expr);
|
||||
case "loop":
|
||||
return this.checkLoopExpr(expr);
|
||||
case "block":
|
||||
return this.checkBlockExpr(expr);
|
||||
}
|
||||
// throw new Error(`unhandled type ${expr.kind.type}`);
|
||||
})();
|
||||
return expr.vtype = vtype;
|
||||
}
|
||||
|
||||
public checkSymExpr(expr: Expr): VType {
|
||||
if (expr.kind.type !== "sym") {
|
||||
throw new Error();
|
||||
}
|
||||
const pos = expr.pos;
|
||||
switch (expr.kind.defType) {
|
||||
case "let":
|
||||
return expr.kind.param?.vtype!;
|
||||
case "fn": {
|
||||
const fnStmt = expr.kind.stmt!;
|
||||
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 };
|
||||
}
|
||||
case "fn_param":
|
||||
return expr.kind.param!.vtype!;
|
||||
case "builtin":
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
|
||||
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" };
|
||||
}
|
||||
const value = this.checkExpr(expr.kind.value);
|
||||
if (value.type !== "int") {
|
||||
this.report("cannot index on array with non-int", 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;
|
||||
}
|
||||
|
||||
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}' ` +
|
||||
`on type '${vtypeToString(subject)}'`,
|
||||
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}' ` +
|
||||
`on types '${vtypeToString(left)}' and '${
|
||||
vtypeToString(right)
|
||||
}'`,
|
||||
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'` +
|
||||
`, got '${vtypeToString(truthy)}'`,
|
||||
pos,
|
||||
);
|
||||
return { type: "error" };
|
||||
}
|
||||
if (falsy !== undefined && !vtypesEqual(truthy, falsy)) {
|
||||
this.report(
|
||||
`if cases must be compatible, got incompatible types` +
|
||||
` '${vtypeToString(truthy)}'` +
|
||||
` and '${vtypeToString(falsy)}'`,
|
||||
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'` +
|
||||
`, got '${vtypeToString(body)}'`,
|
||||
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` +
|
||||
`, got '${vtypeToString(breakType[2])}'` +
|
||||
` incompatible with ${vtypeToString(breakType[0])}`,
|
||||
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") {
|
||||
@ -69,149 +571,19 @@ export class Checker {
|
||||
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);
|
||||
case "field":
|
||||
return this.checkFieldExpr(expr);
|
||||
case "index":
|
||||
return this.checkIndexExpr(expr);
|
||||
case "call":
|
||||
return this.checkCallExpr(expr);
|
||||
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();
|
||||
}
|
||||
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}' ` +
|
||||
`on types '${vtypeToString(left)}' and '${
|
||||
vtypeToString(right)
|
||||
}'`,
|
||||
pos,
|
||||
);
|
||||
return { type: "error" };
|
||||
}
|
||||
|
||||
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;
|
||||
private report(msg: string, pos: Pos) {
|
||||
console.error(`${msg} at ${pos.line}:${pos.col}`);
|
||||
}
|
||||
}
|
||||
|
||||
const simpleUnaryOperations: {
|
||||
unaryType: string;
|
||||
operand: VType;
|
||||
result?: VType;
|
||||
}[] = [
|
||||
{ unaryType: "not", operand: { type: "bool" } },
|
||||
];
|
||||
|
||||
const simpleBinaryOperations: {
|
||||
binaryType: string;
|
||||
operand: VType;
|
||||
|
@ -26,7 +26,6 @@ export type Param = {
|
||||
export type Stmt = {
|
||||
kind: StmtKind;
|
||||
pos: Pos;
|
||||
vtype?: VType;
|
||||
id: number;
|
||||
};
|
||||
|
||||
@ -40,6 +39,7 @@ export type StmtKind =
|
||||
params: Param[];
|
||||
returnType?: EType;
|
||||
body: Expr;
|
||||
vtype?: VType;
|
||||
}
|
||||
| { type: "let"; param: Param; value: Expr }
|
||||
| { type: "assign"; subject: Expr; value: Expr }
|
||||
|
@ -1,5 +1,4 @@
|
||||
export type VType =
|
||||
| { type: "" }
|
||||
| { type: "error" }
|
||||
| { type: "unknown" }
|
||||
| { type: "null" }
|
||||
|
Loading…
Reference in New Issue
Block a user