Compare commits
3 Commits
aab3fa77ad
...
87f90c6700
| Author | SHA1 | Date | |
|---|---|---|---|
| 87f90c6700 | |||
| cb7c887558 | |||
| a22ced4c24 |
1287
src/front/check.ts
1287
src/front/check.ts
File diff suppressed because it is too large
Load Diff
@ -1,793 +0,0 @@
|
||||
import { Syms } from "./resolve.ts";
|
||||
import * as ast from "../ast.ts";
|
||||
import { Ty } from "../ty.ts";
|
||||
import { FileReporter, Loc } from "../diagnostics.ts";
|
||||
import * as stringify from "../stringify.ts";
|
||||
|
||||
export class Checker {
|
||||
private checkedFns = new Map<ast.FnStmt, CheckedFn>();
|
||||
|
||||
constructor(
|
||||
private syms: Syms,
|
||||
private reporter: FileReporter,
|
||||
) {}
|
||||
|
||||
checkFn(fn: ast.FnStmt): CheckedFn {
|
||||
const existing = this.checkedFns.get(fn);
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
const checkedFn = new TypeChecker(this, fn, this.syms, this.reporter)
|
||||
.check();
|
||||
|
||||
checkedFn.checkForCheckerInternalTys(this.reporter);
|
||||
|
||||
this.checkedFns.set(fn, checkedFn);
|
||||
return checkedFn;
|
||||
}
|
||||
}
|
||||
|
||||
export class CheckedFn {
|
||||
constructor(
|
||||
private fnTy: Ty,
|
||||
private nodeTys: Map<ast.Node, Ty>,
|
||||
) {}
|
||||
|
||||
ty(): Ty {
|
||||
return this.fnTy;
|
||||
}
|
||||
|
||||
exprTy(expr: ast.Node): Ty {
|
||||
const ty = this.nodeTys.get(expr);
|
||||
if (ty === undefined) {
|
||||
throw new Error(`no type for '${expr.kind.tag}'`);
|
||||
}
|
||||
return ty;
|
||||
}
|
||||
|
||||
checkForCheckerInternalTys(reporter: FileReporter) {
|
||||
for (const [node, ty] of this.nodeTys) {
|
||||
if (ty.isCheckerInternal()) {
|
||||
reporter.error(
|
||||
node.loc,
|
||||
`concrete type must be known at this point, got temporary type '${ty.pretty()}'`,
|
||||
);
|
||||
reporter.abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TypeChecker {
|
||||
private nodeTys = new Map<ast.Node, Ty>();
|
||||
private params!: Ty[];
|
||||
|
||||
constructor(
|
||||
private cx: Checker,
|
||||
private fn: ast.FnStmt,
|
||||
private syms: Syms,
|
||||
private reporter: FileReporter,
|
||||
) {}
|
||||
|
||||
check(): CheckedFn {
|
||||
const params = this.fn.kind.params
|
||||
.map((node) => {
|
||||
const param = node.as("Param");
|
||||
if (!param.kind.ty) {
|
||||
this.reporter
|
||||
.error(param.loc, `parameter must have a type`);
|
||||
this.reporter.abort();
|
||||
}
|
||||
return this.ty(param.kind.ty);
|
||||
});
|
||||
this.params = params;
|
||||
|
||||
const retTy = this.fn.kind.retTy
|
||||
? this.ty(this.fn.kind.retTy)
|
||||
: Ty.Void;
|
||||
|
||||
this.fn.kind.body.visit({
|
||||
visit: (node) => {
|
||||
const k = node.kind;
|
||||
switch (k.tag) {
|
||||
case "ReturnStmt": {
|
||||
const ty = k.expr ? this.expr(k.expr, retTy) : Ty.Void;
|
||||
if (!ty.resolvableWith(retTy)) {
|
||||
this.reporter.error(
|
||||
node.loc,
|
||||
`type '${ty.pretty()}' not compatible with return type '${retTy.pretty()}'`,
|
||||
);
|
||||
this.reporter.info(
|
||||
this.fn.kind.retTy?.loc ?? this.fn.loc,
|
||||
`return type '${retTy}' defined here`,
|
||||
);
|
||||
this.reporter.abort();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "LetStmt": {
|
||||
let ty: Ty;
|
||||
const paramTy = k.param.as("Param").kind.ty;
|
||||
if (paramTy) {
|
||||
const explicitTy = this.ty(paramTy);
|
||||
const exprTy = this.expr(k.expr, explicitTy);
|
||||
const res = this.resolve(
|
||||
exprTy,
|
||||
explicitTy,
|
||||
k.expr.loc,
|
||||
);
|
||||
if (res.rewriteSubtree) {
|
||||
this.rewriteTree(k.expr, res.ty);
|
||||
}
|
||||
ty = res.ty;
|
||||
} else {
|
||||
ty = this.expr(k.expr, Ty.Any);
|
||||
if (ty.is("AnyInt")) {
|
||||
ty = Ty.I32;
|
||||
this.rewriteTree(k.expr, ty);
|
||||
}
|
||||
}
|
||||
|
||||
this.nodeTys.set(node, ty);
|
||||
break;
|
||||
}
|
||||
case "ExprStmt": {
|
||||
this.expr(k.expr, Ty.Any);
|
||||
break;
|
||||
}
|
||||
case "AssignStmt": {
|
||||
const placeTy = this.expr(k.place, Ty.Any);
|
||||
const exprTy = this.expr(k.expr, placeTy);
|
||||
if (!placeTy.resolvableWith(exprTy)) {
|
||||
this.reporter.error(
|
||||
k.expr.loc,
|
||||
`type '${exprTy.pretty()}' not assignable to type '${placeTy.pretty()}'`,
|
||||
);
|
||||
this.reporter.abort();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "IfStmt": {
|
||||
const condTy = this.expr(k.cond, Ty.Bool);
|
||||
if (!condTy.is("Bool")) {
|
||||
this.reporter.error(
|
||||
k.cond.loc,
|
||||
`expected condition to be 'bool', got '${condTy.pretty()}'`,
|
||||
);
|
||||
this.reporter.abort();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "WhileStmt": {
|
||||
const condTy = this.expr(k.cond, Ty.Bool);
|
||||
if (!condTy.is("Bool")) {
|
||||
this.reporter.error(
|
||||
k.cond.loc,
|
||||
`expected condition to be 'bool', got '${condTy.pretty()}'`,
|
||||
);
|
||||
this.reporter.abort();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "BreakStmt": {
|
||||
break;
|
||||
}
|
||||
case "FnStmt": {
|
||||
break;
|
||||
}
|
||||
case "Error":
|
||||
case "File":
|
||||
case "Block":
|
||||
case "Param":
|
||||
case "IdentExpr":
|
||||
case "IntExpr":
|
||||
case "StrExpr":
|
||||
case "ArrayExpr":
|
||||
case "IndexExpr":
|
||||
case "CallExpr":
|
||||
case "UnaryExpr":
|
||||
case "BinaryExpr":
|
||||
case "RangeExpr":
|
||||
case "IdentTy":
|
||||
case "PtrTy":
|
||||
case "PtrMutTy":
|
||||
case "ArrayTy":
|
||||
case "SliceTy":
|
||||
break;
|
||||
default:
|
||||
k satisfies never;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
this.convertAnyIntToI32();
|
||||
|
||||
const ty = Ty.create("FnStmt", {
|
||||
stmt: this.fn,
|
||||
ty: Ty.create("Fn", { params, retTy }),
|
||||
});
|
||||
|
||||
return new CheckedFn(ty, this.nodeTys);
|
||||
}
|
||||
|
||||
private convertAnyIntToI32() {
|
||||
for (const [node, ty] of this.nodeTys) {
|
||||
if (ty.is("AnyInt")) {
|
||||
this.rewriteTree(node, Ty.I64);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private place(expr: ast.Node, expected: Ty): Ty {
|
||||
return this.cachedCheck(expr, () => this.checkPlace(expr, expected));
|
||||
}
|
||||
|
||||
private checkPlace(expr: ast.Node, expected: Ty): Ty {
|
||||
const k = expr.kind;
|
||||
switch (k.tag) {
|
||||
case "UnaryExpr": {
|
||||
switch (k.op) {
|
||||
case "Deref": {
|
||||
const innerTy = this.checkPlace(
|
||||
k.expr,
|
||||
Ty.AnyDerefable(expected),
|
||||
);
|
||||
if (innerTy.is("Ptr") || innerTy.is("PtrMut")) {
|
||||
return innerTy.kind.ty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.expr(expr, expected);
|
||||
}
|
||||
|
||||
private expr(expr: ast.Node, expected: Ty): Ty {
|
||||
return this.cachedCheck(expr, () => this.checkExpr(expr, expected));
|
||||
}
|
||||
|
||||
private checkExpr(expr: ast.Node, expected: Ty): Ty {
|
||||
const k = expr.kind;
|
||||
switch (k.tag) {
|
||||
case "Error":
|
||||
case "File":
|
||||
case "Block":
|
||||
case "ExprStmt":
|
||||
case "AssignStmt":
|
||||
case "FnStmt":
|
||||
case "ReturnStmt":
|
||||
case "LetStmt":
|
||||
case "IfStmt":
|
||||
case "WhileStmt":
|
||||
case "BreakStmt":
|
||||
case "Param":
|
||||
case "IdentTy":
|
||||
case "PtrTy":
|
||||
case "PtrMutTy":
|
||||
case "ArrayTy":
|
||||
case "SliceTy":
|
||||
throw new Error(`node '${k.tag}' not an expression`);
|
||||
case "IdentExpr": {
|
||||
const sym = this.syms.get(expr);
|
||||
if (sym.tag === "Fn") {
|
||||
const fn = this.cx.checkFn(sym.stmt);
|
||||
return fn.ty();
|
||||
}
|
||||
if (sym.tag === "Bool") {
|
||||
return Ty.Bool;
|
||||
}
|
||||
if (sym.tag === "Builtin") {
|
||||
this.reporter.error(
|
||||
expr.loc,
|
||||
`invalid use of builtin '${sym.id}'`,
|
||||
);
|
||||
this.reporter.abort();
|
||||
}
|
||||
if (sym.tag === "FnParam") {
|
||||
return this.params[sym.idx];
|
||||
}
|
||||
if (sym.tag === "Let") {
|
||||
const ty = this.nodeTys.get(sym.stmt);
|
||||
if (!ty) {
|
||||
throw new Error();
|
||||
}
|
||||
return ty;
|
||||
}
|
||||
throw new Error(`'${sym.tag}' not handled`);
|
||||
}
|
||||
case "IntExpr": {
|
||||
const intTy = (() => {
|
||||
switch (k.intTy) {
|
||||
case "u8":
|
||||
return Ty.U8;
|
||||
case "u16":
|
||||
return Ty.U16;
|
||||
case "u32":
|
||||
return Ty.U32;
|
||||
case "u64":
|
||||
return Ty.U64;
|
||||
case "usize":
|
||||
return Ty.ISize;
|
||||
case "i8":
|
||||
return Ty.U8;
|
||||
case "i16":
|
||||
return Ty.U16;
|
||||
case "i32":
|
||||
return Ty.I32;
|
||||
case "i64":
|
||||
return Ty.U64;
|
||||
case "isize":
|
||||
return Ty.ISize;
|
||||
case "any":
|
||||
return Ty.AnyInt;
|
||||
default:
|
||||
throw new Error(
|
||||
`intType '${k.intTy}' not handled`,
|
||||
);
|
||||
}
|
||||
})();
|
||||
return this.resolve(intTy, expected, expr.loc).ty;
|
||||
}
|
||||
case "StrExpr": {
|
||||
return this.resolve(
|
||||
Ty.Ptr(Ty.Slice(Ty.U8)),
|
||||
expected,
|
||||
expr.loc,
|
||||
).ty;
|
||||
}
|
||||
case "ArrayExpr": {
|
||||
if (k.values.length === 0) {
|
||||
if (expected.is("Any")) {
|
||||
return Ty.Array(Ty.Any, 0);
|
||||
} else {
|
||||
return this.resolve(
|
||||
Ty.Array(Ty.Any, 0),
|
||||
expected,
|
||||
expr.loc,
|
||||
).ty;
|
||||
}
|
||||
}
|
||||
|
||||
const expectedInner = expected.isIndexable()
|
||||
? expected.indexableTy()!
|
||||
: Ty.Any;
|
||||
|
||||
let res = this.resolve(
|
||||
this.expr(k.values[0], expectedInner),
|
||||
expectedInner,
|
||||
k.values[0].loc,
|
||||
);
|
||||
while (true) {
|
||||
for (const val of k.values.slice(1)) {
|
||||
res = this.resolve(
|
||||
this.expr(val, expectedInner),
|
||||
expectedInner,
|
||||
k.values[0].loc,
|
||||
);
|
||||
if (res.rewriteSubtree) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!res.rewriteSubtree) {
|
||||
break;
|
||||
}
|
||||
|
||||
for (const val of k.values) {
|
||||
this.rewriteTree(val, res.ty);
|
||||
}
|
||||
}
|
||||
|
||||
return Ty.Array(res.ty, k.values.length);
|
||||
}
|
||||
case "IndexExpr": {
|
||||
const innerTy = this.place(
|
||||
k.value,
|
||||
Ty.AnyIndexable(expected),
|
||||
);
|
||||
if (!innerTy.isIndexable()) {
|
||||
this.reporter.error(
|
||||
expr.loc,
|
||||
`expected indexable type, got '${expected.pretty()}'`,
|
||||
);
|
||||
this.reporter.abort();
|
||||
}
|
||||
|
||||
let argTy = this.expr(k.arg, Ty.Any);
|
||||
|
||||
if (argTy.is("AnyInt")) {
|
||||
argTy = Ty.USize;
|
||||
this.rewriteTree(k.arg, argTy);
|
||||
return innerTy.indexableTy()!;
|
||||
} else if (argTy.is("Range")) {
|
||||
return Ty.Slice(innerTy.indexableTy()!);
|
||||
} else {
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
case "CallExpr": {
|
||||
const checkArgs = (
|
||||
callableTy: Ty & { kind: { tag: "Fn" } },
|
||||
) => {
|
||||
const params = callableTy.kind.params;
|
||||
if (k.args.length !== params.length) {
|
||||
this.reporter.error(
|
||||
expr.loc,
|
||||
`incorrect amount of arguments. got ${k.args.length} expected ${params.length}`,
|
||||
);
|
||||
if (calleeTy?.is("FnStmt")) {
|
||||
this.reporter.info(
|
||||
calleeTy.kind.stmt.loc,
|
||||
"function defined here",
|
||||
);
|
||||
}
|
||||
this.reporter.abort();
|
||||
}
|
||||
const args = k.args
|
||||
.map((arg, i) => this.expr(arg, params[i]));
|
||||
for (const i of args.keys()) {
|
||||
this.resolve(args[i], params[i], k.args[i].loc, () => {
|
||||
if (calleeTy?.is("FnStmt")) {
|
||||
this.reporter.info(
|
||||
calleeTy.kind.stmt.kind.params[i].loc,
|
||||
`parameter '${
|
||||
calleeTy.kind.stmt.kind.params[i]
|
||||
.as("Param").kind.ident
|
||||
}' defined here`,
|
||||
);
|
||||
}
|
||||
this.reporter.error(
|
||||
k.args[i].loc,
|
||||
`type '${
|
||||
args[i].pretty()
|
||||
}' not compatible with type '${
|
||||
params[i].pretty()
|
||||
}', for argument ${i}`,
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const sym = k.value.is("IdentExpr")
|
||||
? this.syms.get(k.value)
|
||||
: null;
|
||||
if (sym?.tag === "Builtin") {
|
||||
if (sym.id === "len") {
|
||||
checkArgs(
|
||||
Ty.create("Fn", {
|
||||
params: [Ty.Any],
|
||||
retTy: Ty.USize,
|
||||
}).as("Fn"),
|
||||
);
|
||||
return Ty.USize;
|
||||
}
|
||||
if (sym.id === "print") {
|
||||
for (const arg of k.args) {
|
||||
const ty = this.expr(arg, Ty.Any);
|
||||
if (ty.is("AnyInt")) {
|
||||
this.rewriteTree(arg, Ty.I32);
|
||||
}
|
||||
}
|
||||
return Ty.Void;
|
||||
}
|
||||
throw new Error(`builtin '${sym.id}' not handled`);
|
||||
}
|
||||
|
||||
const calleeTy = this.expr(k.value, Ty.AnyCallable(expected));
|
||||
if (!calleeTy.isCallable()) {
|
||||
this.reporter.error(
|
||||
expr.loc,
|
||||
`expected callable type, got '${expected.pretty()}'`,
|
||||
);
|
||||
this.reporter.abort();
|
||||
}
|
||||
|
||||
const callableTy = calleeTy.callableTy();
|
||||
checkArgs(callableTy);
|
||||
return callableTy.kind.retTy;
|
||||
}
|
||||
case "UnaryExpr": {
|
||||
switch (k.op) {
|
||||
case "Not": {
|
||||
const ty = this.expr(k.expr, Ty.Bool);
|
||||
if (!ty.is("Bool")) {
|
||||
this.reporter.error(
|
||||
expr.loc,
|
||||
`expected 'bool', got '${expected.pretty()}'`,
|
||||
);
|
||||
this.reporter.abort();
|
||||
}
|
||||
return ty;
|
||||
}
|
||||
case "Neg": {
|
||||
const ty = this.expr(k.expr, expected);
|
||||
if (!ty.is("Int")) {
|
||||
this.reporter.error(
|
||||
expr.loc,
|
||||
`cannot apply ! to '${expected.pretty()}'`,
|
||||
);
|
||||
this.reporter.abort();
|
||||
}
|
||||
return ty;
|
||||
}
|
||||
case "Ref": {
|
||||
const ty = this.expr(k.expr, expected);
|
||||
return Ty.Ptr(ty);
|
||||
}
|
||||
case "RefMut": {
|
||||
const ty = this.expr(k.expr, expected);
|
||||
return Ty.PtrMut(ty);
|
||||
}
|
||||
case "Deref": {
|
||||
const ty = this.expr(k.expr, expected);
|
||||
if (!ty.is("Ptr") && !ty.is("PtrMut")) {
|
||||
this.reporter.error(
|
||||
expr.loc,
|
||||
`cannot dereference type '${expected.pretty()}'`,
|
||||
);
|
||||
this.reporter.abort();
|
||||
}
|
||||
if (!ty.kind.ty.isSized()) {
|
||||
this.reporter.error(
|
||||
expr.loc,
|
||||
`cannot dereference unsized type '${ty.kind.ty.pretty()}' in an expression`,
|
||||
);
|
||||
this.reporter.abort();
|
||||
}
|
||||
return ty.kind.ty;
|
||||
}
|
||||
default:
|
||||
k satisfies never;
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
case "BinaryExpr": {
|
||||
const op = new BinaryOp(k.op);
|
||||
const expectedInner = op.isPropagating() ? expected : Ty.Any;
|
||||
const left = this.expr(k.left, expectedInner);
|
||||
const right = this.expr(k.right, expectedInner);
|
||||
|
||||
const res = this.resolve(left, right, expr.loc);
|
||||
|
||||
const result = binaryOpTests
|
||||
.map((test) => test(k.op, left, right))
|
||||
.filter((result) => result)
|
||||
.at(0);
|
||||
if (!result) {
|
||||
this.reporter.error(
|
||||
expr.loc,
|
||||
`operator '${k.tok}' cannot be applied to types '${left.pretty()}' and '${right.pretty()}'`,
|
||||
);
|
||||
this.reporter.abort();
|
||||
}
|
||||
this.rewriteTree(k.left, res.ty);
|
||||
this.rewriteTree(k.right, res.ty);
|
||||
return result;
|
||||
}
|
||||
case "RangeExpr": {
|
||||
for (const operandExpr of [k.begin, k.end]) {
|
||||
const operandTy = operandExpr &&
|
||||
this.expr(operandExpr, Ty.USize);
|
||||
if (operandTy && !operandTy.resolvableWith(Ty.USize)) {
|
||||
this.reporter.error(
|
||||
operandExpr.loc,
|
||||
`range operand must be '${Ty.USize.pretty()}', not '${operandTy.pretty()}'`,
|
||||
);
|
||||
this.reporter.abort();
|
||||
}
|
||||
}
|
||||
return Ty.create("Range", {});
|
||||
}
|
||||
default:
|
||||
k satisfies never;
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
|
||||
private ty(ty: ast.Node): Ty {
|
||||
return this.cachedCheck(ty, () => this.checkTy(ty));
|
||||
}
|
||||
|
||||
private checkTy(ty: ast.Node): Ty {
|
||||
const k = ty.kind;
|
||||
switch (k.tag) {
|
||||
case "Error":
|
||||
case "File":
|
||||
case "Block":
|
||||
case "ExprStmt":
|
||||
case "AssignStmt":
|
||||
case "FnStmt":
|
||||
case "ReturnStmt":
|
||||
case "LetStmt":
|
||||
case "IfStmt":
|
||||
case "WhileStmt":
|
||||
case "BreakStmt":
|
||||
case "Param":
|
||||
case "IdentExpr":
|
||||
case "IntExpr":
|
||||
case "StrExpr":
|
||||
case "ArrayExpr":
|
||||
case "IndexExpr":
|
||||
case "CallExpr":
|
||||
case "UnaryExpr":
|
||||
case "BinaryExpr":
|
||||
case "RangeExpr":
|
||||
throw new Error(`node '${k.tag}' not a type`);
|
||||
case "IdentTy": {
|
||||
const sym = this.syms.get(ty);
|
||||
if (sym.tag === "BuiltinTy") {
|
||||
switch (sym.ident) {
|
||||
case "void":
|
||||
return Ty.Void;
|
||||
case "bool":
|
||||
return Ty.Bool;
|
||||
case "i8":
|
||||
return Ty.I8;
|
||||
case "i16":
|
||||
return Ty.I16;
|
||||
case "i32":
|
||||
return Ty.I32;
|
||||
case "i64":
|
||||
return Ty.I64;
|
||||
case "isize":
|
||||
return Ty.ISize;
|
||||
case "u8":
|
||||
return Ty.U8;
|
||||
case "u16":
|
||||
return Ty.U16;
|
||||
case "u32":
|
||||
return Ty.U32;
|
||||
case "u64":
|
||||
return Ty.U64;
|
||||
case "usize":
|
||||
return Ty.USize;
|
||||
default:
|
||||
throw new Error(
|
||||
`unknown type '${k.ident}'`,
|
||||
);
|
||||
}
|
||||
}
|
||||
this.reporter.error(ty.loc, `symbol is not a type`);
|
||||
return this.reporter.abort();
|
||||
}
|
||||
case "PtrTy": {
|
||||
const ty = this.ty(k.ty);
|
||||
return Ty.create("Ptr", { ty });
|
||||
}
|
||||
case "PtrMutTy": {
|
||||
const ty = this.ty(k.ty);
|
||||
return Ty.create("PtrMut", { ty });
|
||||
}
|
||||
case "ArrayTy": {
|
||||
const ty = this.ty(k.ty);
|
||||
const lengthTy = this.expr(k.length, Ty.USize);
|
||||
if (!lengthTy.resolvableWith(Ty.USize)) {
|
||||
this.reporter.error(
|
||||
k.length.loc,
|
||||
`for array length, expected 'int', got '${lengthTy.pretty()}'`,
|
||||
);
|
||||
this.reporter.abort();
|
||||
}
|
||||
if (!k.length.is("IntExpr")) {
|
||||
this.reporter.error(
|
||||
k.length.loc,
|
||||
`array length must be an 'int' expression`,
|
||||
);
|
||||
this.reporter.abort();
|
||||
}
|
||||
const length = k.length.kind.value;
|
||||
return Ty.create("Array", { ty, length });
|
||||
}
|
||||
case "SliceTy": {
|
||||
const ty = this.ty(k.ty);
|
||||
return Ty.create("Slice", { ty });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private cachedCheck(node: ast.Node, func: () => Ty): Ty {
|
||||
const existing = this.nodeTys.get(node);
|
||||
if (existing !== undefined) {
|
||||
return existing;
|
||||
}
|
||||
const ty = func();
|
||||
this.nodeTys.set(node, ty);
|
||||
return ty;
|
||||
}
|
||||
|
||||
private resolve(
|
||||
ty: Ty,
|
||||
expected: Ty,
|
||||
loc: Loc,
|
||||
inCaseOfError?: () => void,
|
||||
): TyRes {
|
||||
if (ty == expected) {
|
||||
return { ty, rewriteSubtree: false };
|
||||
}
|
||||
if (expected.is("Any")) {
|
||||
return { ty, rewriteSubtree: false };
|
||||
}
|
||||
if (expected.is("Int") && ty.is("AnyInt")) {
|
||||
return { ty: expected, rewriteSubtree: true };
|
||||
}
|
||||
if (expected.is("AnyInt") && ty.is("Int")) {
|
||||
return { ty, rewriteSubtree: true };
|
||||
}
|
||||
if (!ty.resolvableWith(expected)) {
|
||||
if (inCaseOfError) {
|
||||
inCaseOfError();
|
||||
} else {
|
||||
this.reporter.error(
|
||||
loc,
|
||||
`expected type '${expected.pretty()}', got '${ty.pretty()}'`,
|
||||
);
|
||||
}
|
||||
this.reporter.abort();
|
||||
}
|
||||
throw new Error(
|
||||
`resolving '${ty.pretty()}' with '${expected.pretty()}' not implemented`,
|
||||
);
|
||||
}
|
||||
|
||||
private rewriteTree(node: ast.Node, ty: Ty) {
|
||||
this.nodeTys.set(node, ty);
|
||||
const k = node.kind;
|
||||
switch (k.tag) {
|
||||
case "IdentExpr":
|
||||
break;
|
||||
case "IntExpr":
|
||||
break;
|
||||
case "BinaryExpr": {
|
||||
const op = new BinaryOp(k.op);
|
||||
if (op.isPropagating()) {
|
||||
this.rewriteTree(k.left, ty);
|
||||
this.rewriteTree(k.right, ty);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Error(`not implemented for '${k.tag}'`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type TyRes = {
|
||||
ty: Ty;
|
||||
rewriteSubtree: boolean;
|
||||
};
|
||||
|
||||
type BinaryOpTest = (op: ast.BinaryOp, left: Ty, right: Ty) => Ty | null;
|
||||
|
||||
const binaryOpTests: BinaryOpTest[] = [
|
||||
(op, left, right) => {
|
||||
const ops: ast.BinaryOp[] = ["Add", "Sub", "Mul", "Div", "Rem"];
|
||||
if (
|
||||
ops.includes(op) && (left.is("Int") || left.is("AnyInt")) &&
|
||||
left.resolvableWith(right)
|
||||
) {
|
||||
return left;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
(op, left, right) => {
|
||||
const ops: ast.BinaryOp[] = ["Eq", "Ne", "Lt", "Gt", "Lte", "Gte"];
|
||||
if (
|
||||
ops.includes(op) && (left.is("Int") || left.is("AnyInt")) &&
|
||||
left.resolvableWith(right)
|
||||
) {
|
||||
return Ty.Bool;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
];
|
||||
|
||||
class BinaryOp {
|
||||
constructor(
|
||||
public op: ast.BinaryOp,
|
||||
) {}
|
||||
|
||||
isPropagating(): boolean {
|
||||
return (["Add", "Sub", "Mul", "Div", "Rem"] as ast.BinaryOp[])
|
||||
.includes(this.op);
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,3 @@
|
||||
export * from "./parse.ts";
|
||||
export * from "./resolve.ts";
|
||||
export * from "./check.ts";
|
||||
export * from "./check2.ts";
|
||||
|
||||
32
src/main.ts
32
src/main.ts
@ -37,40 +37,10 @@ if (!mainFn) {
|
||||
Deno.exit(1);
|
||||
}
|
||||
|
||||
const tys = new front.Tys(syms, fileRep);
|
||||
|
||||
const checker = new front.Checker(syms, fileRep);
|
||||
const fnTys = checker.checkFn(mainFn);
|
||||
|
||||
// fileAst.visit({
|
||||
// visit(node) {
|
||||
// switch (node.kind.tag) {
|
||||
// // case "IdentExpr":
|
||||
// case "IntExpr": // case "StrExpr":
|
||||
// // case "ArrayExpr":
|
||||
// // case "IndexExpr":
|
||||
// // case "CallExpr":
|
||||
// // case "UnaryExpr":
|
||||
// // case "BinaryExpr":
|
||||
// // case "RangeExpr":
|
||||
// {
|
||||
// const oldTy = tys.expr(node);
|
||||
// const newTy = fnTys.exprTy(node);
|
||||
// if (oldTy !== newTy) {
|
||||
// if (newTy.is("AnyInt") && oldTy.is("Int")) {
|
||||
// break;
|
||||
// }
|
||||
// throw new Error(
|
||||
// `'${newTy.pretty()}' != '${oldTy.pretty()}'`,
|
||||
// );
|
||||
// }
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// });
|
||||
|
||||
const m = new middle.MiddleLowerer(syms, tys);
|
||||
const m = new middle.MiddleLowerer(syms, checker);
|
||||
const mainMiddleFn = m.lowerFn(mainFn);
|
||||
|
||||
if (Deno.args.includes("--print-mir")) {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import * as ast from "./ast.ts";
|
||||
import { Syms, Tys } from "./front/mod.ts";
|
||||
import { CheckedFn, Checker, Syms } from "./front/mod.ts";
|
||||
import { Ty } from "./ty.ts";
|
||||
import { BasicBlock, BinaryOp, Fn, Inst, InstKind } from "./mir.ts";
|
||||
|
||||
@ -8,14 +8,19 @@ export class MiddleLowerer {
|
||||
|
||||
constructor(
|
||||
private syms: Syms,
|
||||
private tys: Tys,
|
||||
private checker: Checker,
|
||||
) {}
|
||||
|
||||
lowerFn(stmt: ast.FnStmt): Fn {
|
||||
if (this.fns.has(stmt.id)) {
|
||||
return this.fns.get(stmt.id)!;
|
||||
}
|
||||
const fn = new FnLowerer(this, this.syms, this.tys, stmt).lower();
|
||||
const fn = new FnLowerer(
|
||||
this,
|
||||
this.syms,
|
||||
this.checker.checkFn(stmt),
|
||||
stmt,
|
||||
).lower();
|
||||
this.fns.set(stmt.id, fn);
|
||||
return fn;
|
||||
}
|
||||
@ -34,12 +39,12 @@ class FnLowerer {
|
||||
constructor(
|
||||
private lowerer: MiddleLowerer,
|
||||
private syms: Syms,
|
||||
private tys: Tys,
|
||||
private tys: CheckedFn,
|
||||
private stmt: ast.FnStmt,
|
||||
) {}
|
||||
|
||||
lower(): Fn {
|
||||
const ty = this.tys.fnStmt(this.stmt);
|
||||
const ty = this.tys.ty();
|
||||
this.lowerBlock(this.stmt.kind.body.as("Block"));
|
||||
this.pushInst(Ty.Void, "Return", { source: this.makeVoid() });
|
||||
this.bbs[0].insts.unshift(...this.allocs);
|
||||
@ -172,7 +177,7 @@ class FnLowerer {
|
||||
}
|
||||
|
||||
private lowerLetStmt(stmt: ast.NodeWithKind<"LetStmt">) {
|
||||
const ty = this.tys.param(stmt.kind.param.as("Param"));
|
||||
const ty = this.tys.exprTy(stmt.kind.expr);
|
||||
const expr = this.lowerExpr(stmt.kind.expr);
|
||||
const local = new Inst(
|
||||
Ty.create("PtrMut", { ty }),
|
||||
@ -195,7 +200,7 @@ class FnLowerer {
|
||||
private lowerPlace(place: ast.Node): Inst {
|
||||
// evaluate to most direct pointer
|
||||
|
||||
const _ty = this.tys.place(place);
|
||||
const _ty = this.tys.exprTy(place);
|
||||
|
||||
if (place.is("IdentExpr")) {
|
||||
const sym = this.syms.get(place);
|
||||
@ -218,9 +223,9 @@ class FnLowerer {
|
||||
|
||||
if (place.is("IndexExpr")) {
|
||||
const value = place.kind.value;
|
||||
const valueTy = this.tys.place(value);
|
||||
const valueTy = this.tys.exprTy(value);
|
||||
const arg = place.kind.arg;
|
||||
const argTy = this.tys.expr(arg);
|
||||
const argTy = this.tys.exprTy(arg);
|
||||
if (valueTy.is("Array") || valueTy.is("Slice")) {
|
||||
const valueInst = this.lowerPlace(place.kind.value);
|
||||
if (argTy.is("Int")) {
|
||||
@ -257,7 +262,7 @@ class FnLowerer {
|
||||
}
|
||||
|
||||
private lowerExpr(expr: ast.Node): Inst {
|
||||
const ty = this.tys.expr(expr);
|
||||
const ty = this.tys.exprTy(expr);
|
||||
if (expr.is("IdentExpr")) {
|
||||
const sym = this.syms.get(expr);
|
||||
if (sym.tag === "Fn") {
|
||||
@ -265,7 +270,9 @@ class FnLowerer {
|
||||
return this.pushInst(fn.ty, "Fn", { fn });
|
||||
}
|
||||
if (sym.tag === "FnParam") {
|
||||
const ty = this.tys.expr(sym.param);
|
||||
const ty = this.tys.ty().as("FnStmt")
|
||||
.kind.ty.as("Fn")
|
||||
.kind.params[sym.idx];
|
||||
return this.pushInst(ty, "Param", { idx: sym.idx });
|
||||
}
|
||||
if (sym.tag === "Builtin") {
|
||||
@ -290,7 +297,7 @@ class FnLowerer {
|
||||
return this.pushInst(ty, "Str", { value: expr.kind.value });
|
||||
}
|
||||
if (expr.is("ArrayExpr")) {
|
||||
const ty = this.tys.expr(expr);
|
||||
const ty = this.tys.exprTy(expr);
|
||||
const values = expr.kind.values
|
||||
.map((value) => this.lowerExpr(value));
|
||||
return this.pushInst(ty, "Array", { values });
|
||||
@ -323,8 +330,8 @@ class FnLowerer {
|
||||
return this.lowerUnaryExpr(expr);
|
||||
}
|
||||
if (expr.is("BinaryExpr")) {
|
||||
const leftTy = this.tys.expr(expr.kind.left);
|
||||
const rightTy = this.tys.expr(expr.kind.right);
|
||||
const leftTy = this.tys.exprTy(expr.kind.left);
|
||||
const rightTy = this.tys.exprTy(expr.kind.right);
|
||||
const binaryOp = binaryOpTests
|
||||
.map((test) => test(expr.kind.op, leftTy, rightTy, ty))
|
||||
.filter((tested) => tested)
|
||||
@ -342,8 +349,8 @@ class FnLowerer {
|
||||
}
|
||||
|
||||
private lowerUnaryExpr(expr: ast.NodeWithKind<"UnaryExpr">) {
|
||||
const resultTy = this.tys.expr(expr);
|
||||
const operandTy = this.tys.expr(expr.kind.expr);
|
||||
const resultTy = this.tys.exprTy(expr);
|
||||
const operandTy = this.tys.exprTy(expr.kind.expr);
|
||||
if (
|
||||
expr.kind.op === "Neg" &&
|
||||
operandTy.resolvableWith(Ty.I32) &&
|
||||
@ -376,7 +383,7 @@ class FnLowerer {
|
||||
);
|
||||
}
|
||||
if (place.is("IndexExpr")) {
|
||||
const placeTy = this.tys.expr(place);
|
||||
const placeTy = this.tys.exprTy(place);
|
||||
const placeInst = this.lowerPlace(place);
|
||||
if (placeTy.is("Slice")) {
|
||||
return placeInst;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user