Compare commits
No commits in common. "87f90c67008b03a9f853e5de62b8b0fbd33246a7" and "aab3fa77ad428cc26370e7e382507cf61c10b11a" have entirely different histories.
87f90c6700
...
aab3fa77ad
1313
src/front/check.ts
1313
src/front/check.ts
File diff suppressed because it is too large
Load Diff
793
src/front/check2.ts
Normal file
793
src/front/check2.ts
Normal file
@ -0,0 +1,793 @@
|
|||||||
|
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,3 +1,4 @@
|
|||||||
export * from "./parse.ts";
|
export * from "./parse.ts";
|
||||||
export * from "./resolve.ts";
|
export * from "./resolve.ts";
|
||||||
export * from "./check.ts";
|
export * from "./check.ts";
|
||||||
|
export * from "./check2.ts";
|
||||||
|
|||||||
32
src/main.ts
32
src/main.ts
@ -37,10 +37,40 @@ if (!mainFn) {
|
|||||||
Deno.exit(1);
|
Deno.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tys = new front.Tys(syms, fileRep);
|
||||||
|
|
||||||
const checker = new front.Checker(syms, fileRep);
|
const checker = new front.Checker(syms, fileRep);
|
||||||
const fnTys = checker.checkFn(mainFn);
|
const fnTys = checker.checkFn(mainFn);
|
||||||
|
|
||||||
const m = new middle.MiddleLowerer(syms, checker);
|
// 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 mainMiddleFn = m.lowerFn(mainFn);
|
const mainMiddleFn = m.lowerFn(mainFn);
|
||||||
|
|
||||||
if (Deno.args.includes("--print-mir")) {
|
if (Deno.args.includes("--print-mir")) {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import * as ast from "./ast.ts";
|
import * as ast from "./ast.ts";
|
||||||
import { CheckedFn, Checker, Syms } from "./front/mod.ts";
|
import { Syms, Tys } from "./front/mod.ts";
|
||||||
import { Ty } from "./ty.ts";
|
import { Ty } from "./ty.ts";
|
||||||
import { BasicBlock, BinaryOp, Fn, Inst, InstKind } from "./mir.ts";
|
import { BasicBlock, BinaryOp, Fn, Inst, InstKind } from "./mir.ts";
|
||||||
|
|
||||||
@ -8,19 +8,14 @@ export class MiddleLowerer {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private syms: Syms,
|
private syms: Syms,
|
||||||
private checker: Checker,
|
private tys: Tys,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
lowerFn(stmt: ast.FnStmt): Fn {
|
lowerFn(stmt: ast.FnStmt): Fn {
|
||||||
if (this.fns.has(stmt.id)) {
|
if (this.fns.has(stmt.id)) {
|
||||||
return this.fns.get(stmt.id)!;
|
return this.fns.get(stmt.id)!;
|
||||||
}
|
}
|
||||||
const fn = new FnLowerer(
|
const fn = new FnLowerer(this, this.syms, this.tys, stmt).lower();
|
||||||
this,
|
|
||||||
this.syms,
|
|
||||||
this.checker.checkFn(stmt),
|
|
||||||
stmt,
|
|
||||||
).lower();
|
|
||||||
this.fns.set(stmt.id, fn);
|
this.fns.set(stmt.id, fn);
|
||||||
return fn;
|
return fn;
|
||||||
}
|
}
|
||||||
@ -39,12 +34,12 @@ class FnLowerer {
|
|||||||
constructor(
|
constructor(
|
||||||
private lowerer: MiddleLowerer,
|
private lowerer: MiddleLowerer,
|
||||||
private syms: Syms,
|
private syms: Syms,
|
||||||
private tys: CheckedFn,
|
private tys: Tys,
|
||||||
private stmt: ast.FnStmt,
|
private stmt: ast.FnStmt,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
lower(): Fn {
|
lower(): Fn {
|
||||||
const ty = this.tys.ty();
|
const ty = this.tys.fnStmt(this.stmt);
|
||||||
this.lowerBlock(this.stmt.kind.body.as("Block"));
|
this.lowerBlock(this.stmt.kind.body.as("Block"));
|
||||||
this.pushInst(Ty.Void, "Return", { source: this.makeVoid() });
|
this.pushInst(Ty.Void, "Return", { source: this.makeVoid() });
|
||||||
this.bbs[0].insts.unshift(...this.allocs);
|
this.bbs[0].insts.unshift(...this.allocs);
|
||||||
@ -177,7 +172,7 @@ class FnLowerer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private lowerLetStmt(stmt: ast.NodeWithKind<"LetStmt">) {
|
private lowerLetStmt(stmt: ast.NodeWithKind<"LetStmt">) {
|
||||||
const ty = this.tys.exprTy(stmt.kind.expr);
|
const ty = this.tys.param(stmt.kind.param.as("Param"));
|
||||||
const expr = this.lowerExpr(stmt.kind.expr);
|
const expr = this.lowerExpr(stmt.kind.expr);
|
||||||
const local = new Inst(
|
const local = new Inst(
|
||||||
Ty.create("PtrMut", { ty }),
|
Ty.create("PtrMut", { ty }),
|
||||||
@ -200,7 +195,7 @@ class FnLowerer {
|
|||||||
private lowerPlace(place: ast.Node): Inst {
|
private lowerPlace(place: ast.Node): Inst {
|
||||||
// evaluate to most direct pointer
|
// evaluate to most direct pointer
|
||||||
|
|
||||||
const _ty = this.tys.exprTy(place);
|
const _ty = this.tys.place(place);
|
||||||
|
|
||||||
if (place.is("IdentExpr")) {
|
if (place.is("IdentExpr")) {
|
||||||
const sym = this.syms.get(place);
|
const sym = this.syms.get(place);
|
||||||
@ -223,9 +218,9 @@ class FnLowerer {
|
|||||||
|
|
||||||
if (place.is("IndexExpr")) {
|
if (place.is("IndexExpr")) {
|
||||||
const value = place.kind.value;
|
const value = place.kind.value;
|
||||||
const valueTy = this.tys.exprTy(value);
|
const valueTy = this.tys.place(value);
|
||||||
const arg = place.kind.arg;
|
const arg = place.kind.arg;
|
||||||
const argTy = this.tys.exprTy(arg);
|
const argTy = this.tys.expr(arg);
|
||||||
if (valueTy.is("Array") || valueTy.is("Slice")) {
|
if (valueTy.is("Array") || valueTy.is("Slice")) {
|
||||||
const valueInst = this.lowerPlace(place.kind.value);
|
const valueInst = this.lowerPlace(place.kind.value);
|
||||||
if (argTy.is("Int")) {
|
if (argTy.is("Int")) {
|
||||||
@ -262,7 +257,7 @@ class FnLowerer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private lowerExpr(expr: ast.Node): Inst {
|
private lowerExpr(expr: ast.Node): Inst {
|
||||||
const ty = this.tys.exprTy(expr);
|
const ty = this.tys.expr(expr);
|
||||||
if (expr.is("IdentExpr")) {
|
if (expr.is("IdentExpr")) {
|
||||||
const sym = this.syms.get(expr);
|
const sym = this.syms.get(expr);
|
||||||
if (sym.tag === "Fn") {
|
if (sym.tag === "Fn") {
|
||||||
@ -270,9 +265,7 @@ class FnLowerer {
|
|||||||
return this.pushInst(fn.ty, "Fn", { fn });
|
return this.pushInst(fn.ty, "Fn", { fn });
|
||||||
}
|
}
|
||||||
if (sym.tag === "FnParam") {
|
if (sym.tag === "FnParam") {
|
||||||
const ty = this.tys.ty().as("FnStmt")
|
const ty = this.tys.expr(sym.param);
|
||||||
.kind.ty.as("Fn")
|
|
||||||
.kind.params[sym.idx];
|
|
||||||
return this.pushInst(ty, "Param", { idx: sym.idx });
|
return this.pushInst(ty, "Param", { idx: sym.idx });
|
||||||
}
|
}
|
||||||
if (sym.tag === "Builtin") {
|
if (sym.tag === "Builtin") {
|
||||||
@ -297,7 +290,7 @@ class FnLowerer {
|
|||||||
return this.pushInst(ty, "Str", { value: expr.kind.value });
|
return this.pushInst(ty, "Str", { value: expr.kind.value });
|
||||||
}
|
}
|
||||||
if (expr.is("ArrayExpr")) {
|
if (expr.is("ArrayExpr")) {
|
||||||
const ty = this.tys.exprTy(expr);
|
const ty = this.tys.expr(expr);
|
||||||
const values = expr.kind.values
|
const values = expr.kind.values
|
||||||
.map((value) => this.lowerExpr(value));
|
.map((value) => this.lowerExpr(value));
|
||||||
return this.pushInst(ty, "Array", { values });
|
return this.pushInst(ty, "Array", { values });
|
||||||
@ -330,8 +323,8 @@ class FnLowerer {
|
|||||||
return this.lowerUnaryExpr(expr);
|
return this.lowerUnaryExpr(expr);
|
||||||
}
|
}
|
||||||
if (expr.is("BinaryExpr")) {
|
if (expr.is("BinaryExpr")) {
|
||||||
const leftTy = this.tys.exprTy(expr.kind.left);
|
const leftTy = this.tys.expr(expr.kind.left);
|
||||||
const rightTy = this.tys.exprTy(expr.kind.right);
|
const rightTy = this.tys.expr(expr.kind.right);
|
||||||
const binaryOp = binaryOpTests
|
const binaryOp = binaryOpTests
|
||||||
.map((test) => test(expr.kind.op, leftTy, rightTy, ty))
|
.map((test) => test(expr.kind.op, leftTy, rightTy, ty))
|
||||||
.filter((tested) => tested)
|
.filter((tested) => tested)
|
||||||
@ -349,8 +342,8 @@ class FnLowerer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private lowerUnaryExpr(expr: ast.NodeWithKind<"UnaryExpr">) {
|
private lowerUnaryExpr(expr: ast.NodeWithKind<"UnaryExpr">) {
|
||||||
const resultTy = this.tys.exprTy(expr);
|
const resultTy = this.tys.expr(expr);
|
||||||
const operandTy = this.tys.exprTy(expr.kind.expr);
|
const operandTy = this.tys.expr(expr.kind.expr);
|
||||||
if (
|
if (
|
||||||
expr.kind.op === "Neg" &&
|
expr.kind.op === "Neg" &&
|
||||||
operandTy.resolvableWith(Ty.I32) &&
|
operandTy.resolvableWith(Ty.I32) &&
|
||||||
@ -383,7 +376,7 @@ class FnLowerer {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (place.is("IndexExpr")) {
|
if (place.is("IndexExpr")) {
|
||||||
const placeTy = this.tys.exprTy(place);
|
const placeTy = this.tys.expr(place);
|
||||||
const placeInst = this.lowerPlace(place);
|
const placeInst = this.lowerPlace(place);
|
||||||
if (placeTy.is("Slice")) {
|
if (placeTy.is("Slice")) {
|
||||||
return placeInst;
|
return placeInst;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user