add i32, etc. and some other stuff
All checks were successful
Check / Explore-Gitea-Actions (push) Successful in 13s
All checks were successful
Check / Explore-Gitea-Actions (push) Successful in 13s
This commit is contained in:
parent
5faabe93c2
commit
0d57cd7e3c
14
src/ast.ts
14
src/ast.ts
@ -142,7 +142,7 @@ export type NodeKind =
|
||||
| { tag: "BreakStmt" }
|
||||
| { tag: "Param"; ident: string; ty: Node | null }
|
||||
| { tag: "IdentExpr"; ident: string }
|
||||
| { tag: "IntExpr"; value: number }
|
||||
| { tag: "IntExpr"; value: number; intTy: IntTy }
|
||||
| { tag: "StrExpr"; value: string }
|
||||
| { tag: "ArrayExpr"; values: Node[] }
|
||||
| { tag: "IndexExpr"; value: Node; arg: Node }
|
||||
@ -160,6 +160,18 @@ export type NodeKind =
|
||||
| { tag: "ArrayTy"; ty: Node; length: Node }
|
||||
| { tag: "SliceTy"; ty: Node };
|
||||
|
||||
export type IntTy =
|
||||
| "i8"
|
||||
| "i16"
|
||||
| "i32"
|
||||
| "i64"
|
||||
| "isize"
|
||||
| "u8"
|
||||
| "u16"
|
||||
| "u32"
|
||||
| "u64"
|
||||
| "usize";
|
||||
|
||||
export type UnaryOp =
|
||||
| "Not"
|
||||
| "Negate"
|
||||
|
||||
@ -1,76 +1,137 @@
|
||||
import * as ast from "../ast.ts";
|
||||
import { FileReporter, Loc } from "../diagnostics.ts";
|
||||
import { Ty } from "../ty.ts";
|
||||
import { Syms } from "./resolve.ts";
|
||||
import { Sym, Syms } from "./resolve.ts";
|
||||
|
||||
export class Tys {
|
||||
private nodeTys = new Map<number, Ty>();
|
||||
private checker: Checker;
|
||||
|
||||
constructor(
|
||||
private syms: Syms,
|
||||
private reporter: FileReporter,
|
||||
) {
|
||||
this.checker = new Checker(this, this.syms, this.reporter);
|
||||
this.cx = new CheckerCx(this.syms, this.reporter);
|
||||
}
|
||||
|
||||
private cx: CheckerCx;
|
||||
|
||||
fnStmt(node: ast.NodeWithKind<"FnStmt">): Ty {
|
||||
if (this.nodeTys.has(node.id)) {
|
||||
return this.nodeTys.get(node.id)!;
|
||||
return this.cx.fnStmt(node);
|
||||
}
|
||||
const ty = this.checker.checkFnStmt(node);
|
||||
this.nodeTys.set(node.id, ty);
|
||||
return ty;
|
||||
|
||||
param(node: ast.NodeWithKind<"Param">): Ty {
|
||||
return this.cx.param(node);
|
||||
}
|
||||
|
||||
place(node: ast.Node): Ty {
|
||||
if (this.nodeTys.has(node.id)) {
|
||||
return this.nodeTys.get(node.id)!;
|
||||
}
|
||||
const ty = this.checker.checkPlace(node);
|
||||
this.nodeTys.set(node.id, ty);
|
||||
return ty;
|
||||
return this.cx.place(node);
|
||||
}
|
||||
|
||||
expr(node: ast.Node): Ty {
|
||||
if (this.nodeTys.has(node.id)) {
|
||||
return this.nodeTys.get(node.id)!;
|
||||
return this.cx.expr(node);
|
||||
}
|
||||
const ty = this.checker.checkExpr(node);
|
||||
this.nodeTys.set(node.id, ty);
|
||||
return ty;
|
||||
|
||||
ty(node: ast.Node): Ty {
|
||||
return this.cx.ty(node);
|
||||
}
|
||||
}
|
||||
|
||||
class Checker {
|
||||
class CheckerCx {
|
||||
constructor(
|
||||
private tys: Tys,
|
||||
private syms: Syms,
|
||||
private reporter: FileReporter,
|
||||
) {}
|
||||
|
||||
private nodeTys = new Map<number, Ty>();
|
||||
|
||||
private stmtChecker = new StmtChecker(this);
|
||||
private paramChecker = new ParamChecker(this);
|
||||
private placeChecker = new PlaceChecker(this);
|
||||
private exprChecker = new ExprChecker(this);
|
||||
private tyChecker = new TyChecker(this);
|
||||
|
||||
fnStmt(node: ast.NodeWithKind<"FnStmt">): Ty {
|
||||
return this.cache(node, () => this.stmtChecker.checkFnStmt(node));
|
||||
}
|
||||
|
||||
param(node: ast.NodeWithKind<"Param">): Ty {
|
||||
return this.cache(node, () => this.paramChecker.checkParam(node));
|
||||
}
|
||||
|
||||
place(node: ast.Node): Ty {
|
||||
return this.cache(node, () => this.placeChecker.checkPlace(node));
|
||||
}
|
||||
|
||||
expr(node: ast.Node): Ty {
|
||||
return this.cache(node, () => this.exprChecker.checkExpr(node));
|
||||
}
|
||||
|
||||
ty(node: ast.Node): Ty {
|
||||
return this.cache(node, () => this.tyChecker.checkTy(node));
|
||||
}
|
||||
|
||||
private cache(node: ast.Node, action: () => Ty): Ty {
|
||||
if (this.nodeTys.has(node.id)) {
|
||||
return this.nodeTys.get(node.id)!;
|
||||
}
|
||||
const ty = action();
|
||||
this.nodeTys.set(node.id, ty);
|
||||
return ty;
|
||||
}
|
||||
|
||||
error(loc: Loc, message: string) {
|
||||
this.reporter.error(loc, message);
|
||||
}
|
||||
|
||||
info(loc: Loc, message: string) {
|
||||
this.reporter.info(loc, message);
|
||||
}
|
||||
|
||||
fail(): never {
|
||||
this.reporter.abort();
|
||||
}
|
||||
|
||||
sym(node: ast.Node): Sym {
|
||||
return this.syms.get(node);
|
||||
}
|
||||
|
||||
assertCompatible(left: Ty, right: Ty, loc: Loc): void {
|
||||
if (!left.compatibleWith(right)) {
|
||||
this.error(
|
||||
loc,
|
||||
`type '${left.pretty()}' not compatible with type '${right.pretty()}'`,
|
||||
);
|
||||
this.fail();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class StmtChecker {
|
||||
constructor(
|
||||
private cx: CheckerCx,
|
||||
) {}
|
||||
|
||||
checkFnStmt(stmt: ast.NodeWithKind<"FnStmt">): Ty {
|
||||
const k = stmt.kind;
|
||||
|
||||
const params = k.params.map((param) => this.tys.expr(param));
|
||||
const retTy = k.retTy ? this.tys.expr(k.retTy) : Ty.Void;
|
||||
const params = k.params
|
||||
.map((param) => this.cx.param(param.as("Param")));
|
||||
const retTy = k.retTy ? this.cx.ty(k.retTy) : Ty.Void;
|
||||
|
||||
k.body.visit({
|
||||
visit: (node) => {
|
||||
if (node.is("ReturnStmt")) {
|
||||
const ty = node.kind.expr
|
||||
? this.tys.expr(node.kind.expr)
|
||||
? this.cx.expr(node.kind.expr)
|
||||
: Ty.Void;
|
||||
if (!ty.compatibleWith(retTy)) {
|
||||
this.error(
|
||||
this.cx.error(
|
||||
node.loc,
|
||||
`type '${ty.pretty()}' not compatible with return type '${retTy.pretty()}'`,
|
||||
);
|
||||
this.info(
|
||||
this.cx.info(
|
||||
stmt.kind.retTy?.loc ?? stmt.loc,
|
||||
`return type '${retTy}' defined here`,
|
||||
);
|
||||
this.fail();
|
||||
this.cx.fail();
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -79,6 +140,44 @@ class Checker {
|
||||
const ty = Ty.create("Fn", { params, retTy });
|
||||
return Ty.create("FnStmt", { stmt, ty });
|
||||
}
|
||||
}
|
||||
|
||||
class ParamChecker {
|
||||
constructor(
|
||||
private cx: CheckerCx,
|
||||
) {}
|
||||
|
||||
checkParam(node: ast.NodeWithKind<"Param">): Ty {
|
||||
const sym = this.cx.sym(node);
|
||||
|
||||
if (sym.tag === "Let") {
|
||||
const exprTy = this.cx.expr(sym.stmt.kind.expr);
|
||||
if (node.kind.ty) {
|
||||
const explicitTy = this.cx.ty(node.kind.ty);
|
||||
this.cx.assertCompatible(
|
||||
exprTy,
|
||||
explicitTy,
|
||||
sym.stmt.kind.expr.loc,
|
||||
);
|
||||
}
|
||||
return exprTy;
|
||||
}
|
||||
if (sym.tag === "FnParam") {
|
||||
if (!node.kind.ty) {
|
||||
this.cx.error(node.loc, `parameter must have a type`);
|
||||
this.cx.fail();
|
||||
}
|
||||
return this.cx.ty(node.kind.ty);
|
||||
}
|
||||
|
||||
throw new Error(`'${sym.tag}' not handled`);
|
||||
}
|
||||
}
|
||||
|
||||
class PlaceChecker {
|
||||
constructor(
|
||||
private cx: CheckerCx,
|
||||
) {}
|
||||
|
||||
checkPlace(node: ast.Node): Ty {
|
||||
if (node.is("UnaryExpr")) {
|
||||
@ -89,91 +188,109 @@ class Checker {
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.checkExpr(node);
|
||||
return this.cx.expr(node);
|
||||
}
|
||||
}
|
||||
|
||||
class ExprChecker {
|
||||
constructor(
|
||||
private cx: CheckerCx,
|
||||
) {}
|
||||
|
||||
checkExpr(node: ast.Node): Ty {
|
||||
const k = node.kind;
|
||||
|
||||
if (node.is("Param")) {
|
||||
const sym = this.syms.get(node);
|
||||
|
||||
if (sym.tag === "Let") {
|
||||
const exprTy = this.tys.expr(sym.stmt.kind.expr);
|
||||
if (node.kind.ty) {
|
||||
const explicitTy = this.tys.expr(node.kind.ty);
|
||||
this.assertCompatible(
|
||||
exprTy,
|
||||
explicitTy,
|
||||
sym.stmt.kind.expr.loc,
|
||||
);
|
||||
const tag = node.kind.tag;
|
||||
switch (tag) {
|
||||
case "IdentExpr":
|
||||
return this.checkIdentExpr(node.as(tag));
|
||||
case "IntExpr":
|
||||
return this.checkIntExpr(node.as(tag));
|
||||
case "StrExpr":
|
||||
return Ty.create("Ptr", { ty: Ty.Str });
|
||||
case "ArrayExpr":
|
||||
return this.checkArrayExpr(node.as(tag));
|
||||
case "IndexExpr":
|
||||
return this.checkIndexExpr(node.as(tag));
|
||||
case "CallExpr":
|
||||
return this.checkCallExpr(node.as(tag));
|
||||
case "UnaryExpr":
|
||||
return this.checkUnaryExpr(node.as(tag));
|
||||
case "BinaryExpr":
|
||||
return this.checkBinaryExpr(node.as(tag));
|
||||
case "RangeExpr":
|
||||
return this.checkRangeExpr(node.as(tag));
|
||||
default:
|
||||
throw new Error(`'${node.kind.tag}' not unhandled`);
|
||||
}
|
||||
return exprTy;
|
||||
}
|
||||
if (sym.tag === "FnParam") {
|
||||
if (!node.kind.ty) {
|
||||
this.error(node.loc, `parameter must have a type`);
|
||||
this.fail();
|
||||
}
|
||||
return this.tys.expr(node.kind.ty);
|
||||
}
|
||||
|
||||
throw new Error(`'${sym.tag}' not handled`);
|
||||
}
|
||||
|
||||
if (node.is("IdentExpr")) {
|
||||
const sym = this.syms.get(node);
|
||||
private checkIdentExpr(node: ast.NodeWithKind<"IdentExpr">): Ty {
|
||||
const sym = this.cx.sym(node);
|
||||
if (sym.tag === "Fn") {
|
||||
return this.tys.fnStmt(sym.stmt);
|
||||
return this.cx.fnStmt(sym.stmt);
|
||||
}
|
||||
if (sym.tag === "Bool") {
|
||||
return Ty.Bool;
|
||||
}
|
||||
if (sym.tag === "Builtin") {
|
||||
this.error(node.loc, `invalid use of builtin '${sym.id}'`);
|
||||
this.fail();
|
||||
this.cx.error(node.loc, `invalid use of builtin '${sym.id}'`);
|
||||
this.cx.fail();
|
||||
}
|
||||
if (sym.tag === "FnParam") {
|
||||
return this.tys.expr(sym.param);
|
||||
return this.cx.expr(sym.param);
|
||||
}
|
||||
if (sym.tag === "Let") {
|
||||
return this.tys.expr(sym.param);
|
||||
return this.cx.expr(sym.param);
|
||||
}
|
||||
throw new Error(`'${sym.tag}' not handled`);
|
||||
}
|
||||
|
||||
if (node.is("IntExpr")) {
|
||||
return Ty.Int;
|
||||
private checkIntExpr(node: ast.NodeWithKind<"IntExpr">): Ty {
|
||||
switch (node.kind.intTy) {
|
||||
case "u8":
|
||||
return Ty.U8;
|
||||
case "u16":
|
||||
return Ty.U16;
|
||||
case "u32":
|
||||
return Ty.U32;
|
||||
case "u64":
|
||||
return Ty.U64;
|
||||
case "i8":
|
||||
return Ty.U8;
|
||||
case "i16":
|
||||
return Ty.U16;
|
||||
case "i32":
|
||||
return Ty.I32;
|
||||
case "i64":
|
||||
return Ty.U64;
|
||||
default:
|
||||
throw new Error(`intType '${node.kind.intTy}' not handled`);
|
||||
}
|
||||
}
|
||||
|
||||
if (node.is("StrExpr")) {
|
||||
return Ty.create("Ptr", { ty: Ty.Str });
|
||||
}
|
||||
|
||||
if (node.is("ArrayExpr")) {
|
||||
private checkArrayExpr(node: ast.NodeWithKind<"ArrayExpr">): Ty {
|
||||
let ty: Ty | null = null;
|
||||
for (const value of node.kind.values) {
|
||||
const valueTy = this.tys.expr(value);
|
||||
const valueTy = this.cx.expr(value);
|
||||
if (ty) {
|
||||
this.assertCompatible(ty, valueTy, value.loc);
|
||||
this.cx.assertCompatible(ty, valueTy, value.loc);
|
||||
} else {
|
||||
ty = valueTy;
|
||||
}
|
||||
}
|
||||
if (!ty) {
|
||||
this.error(node.loc, `could not infer type of empty array`);
|
||||
this.fail();
|
||||
this.cx.error(node.loc, `could not infer type of empty array`);
|
||||
this.cx.fail();
|
||||
}
|
||||
const length = node.kind.values.length;
|
||||
return Ty.create("Array", { ty, length });
|
||||
}
|
||||
|
||||
if (node.is("IndexExpr")) {
|
||||
const exprTy = this.tys.place(node.kind.value);
|
||||
const argTy = this.tys.expr(node.kind.arg);
|
||||
private checkIndexExpr(node: ast.NodeWithKind<"IndexExpr">): Ty {
|
||||
const exprTy = this.cx.place(node.kind.value);
|
||||
const argTy = this.cx.expr(node.kind.arg);
|
||||
if (
|
||||
(exprTy.is("Array") || exprTy.is("Slice")) &&
|
||||
argTy.compatibleWith(Ty.Int)
|
||||
argTy.compatibleWith(Ty.I32)
|
||||
) {
|
||||
return exprTy.kind.ty;
|
||||
}
|
||||
@ -185,189 +302,26 @@ class Checker {
|
||||
}
|
||||
if (
|
||||
exprTy.is("Str") &&
|
||||
argTy.compatibleWith(Ty.Int)
|
||||
argTy.compatibleWith(Ty.I32)
|
||||
) {
|
||||
return Ty.Int;
|
||||
return Ty.I32;
|
||||
}
|
||||
this.error(
|
||||
this.cx.error(
|
||||
node.loc,
|
||||
`cannot use index operator on '${exprTy.pretty()}' with '${argTy.pretty()}'`,
|
||||
);
|
||||
this.fail();
|
||||
this.cx.fail();
|
||||
}
|
||||
|
||||
if (node.is("CallExpr")) {
|
||||
return this.checkCall(node);
|
||||
}
|
||||
|
||||
if (node.is("UnaryExpr")) {
|
||||
const exprTy = this.tys.expr(node.kind.expr);
|
||||
if (node.kind.op === "Negate" && exprTy.compatibleWith(Ty.Int)) {
|
||||
return Ty.Int;
|
||||
}
|
||||
if (node.kind.op === "Not" && exprTy.compatibleWith(Ty.Bool)) {
|
||||
return Ty.Bool;
|
||||
}
|
||||
if (node.kind.op === "Ref") {
|
||||
return Ty.create("Ptr", { ty: exprTy });
|
||||
}
|
||||
if (node.kind.op === "RefMut") {
|
||||
return Ty.create("PtrMut", { ty: exprTy });
|
||||
}
|
||||
if (node.kind.op === "Deref") {
|
||||
if (exprTy.is("Ptr") || exprTy.is("PtrMut")) {
|
||||
if (!exprTy.kind.ty.isSized()) {
|
||||
this.error(
|
||||
node.loc,
|
||||
`cannot dereference unsized type '${exprTy.kind.ty.pretty()}' in an expression`,
|
||||
);
|
||||
this.fail();
|
||||
}
|
||||
return exprTy.kind.ty;
|
||||
}
|
||||
}
|
||||
this.error(
|
||||
node.loc,
|
||||
`operator '${node.kind.tok}' cannot be applied to type '${exprTy.pretty()}'`,
|
||||
);
|
||||
this.fail();
|
||||
}
|
||||
|
||||
if (node.is("BinaryExpr")) {
|
||||
const left = this.tys.expr(node.kind.left);
|
||||
const right = this.tys.expr(node.kind.right);
|
||||
const binaryOp = binaryOpPatterns
|
||||
.find((pat) =>
|
||||
pat.op === node.kind.op &&
|
||||
left.compatibleWith(pat.left) &&
|
||||
right.compatibleWith(pat.right)
|
||||
);
|
||||
if (!binaryOp) {
|
||||
this.error(
|
||||
node.loc,
|
||||
`operator '${node.kind.tok}' cannot be applied to types '${left.pretty()}' and '${right.pretty()}'`,
|
||||
);
|
||||
this.fail();
|
||||
}
|
||||
return binaryOp.result;
|
||||
}
|
||||
|
||||
if (node.is("RangeExpr")) {
|
||||
for (const operandExpr of [node.kind.begin, node.kind.end]) {
|
||||
const operandTy = operandExpr && this.tys.expr(operandExpr);
|
||||
if (operandTy && !operandTy.compatibleWith(Ty.Int)) {
|
||||
this.error(
|
||||
operandExpr.loc,
|
||||
`range operand must be '${Ty.Int.pretty()}', not '${operandTy.pretty()}'`,
|
||||
);
|
||||
this.fail();
|
||||
}
|
||||
}
|
||||
return Ty.create("Range", {});
|
||||
}
|
||||
|
||||
if (node.is("IdentTy")) {
|
||||
const sym = this.syms.get(node);
|
||||
if (sym.tag === "BuiltinTy") {
|
||||
switch (sym.ident) {
|
||||
case "void":
|
||||
return Ty.Void;
|
||||
case "int":
|
||||
return Ty.Int;
|
||||
case "bool":
|
||||
return Ty.Bool;
|
||||
case "str":
|
||||
return Ty.Str;
|
||||
case "u8":
|
||||
return Ty.U8;
|
||||
default:
|
||||
throw new Error(
|
||||
`unknown type '${node.kind.ident}'`,
|
||||
);
|
||||
}
|
||||
}
|
||||
this.error(node.loc, `symbol is not a type`);
|
||||
this.fail();
|
||||
}
|
||||
|
||||
if (node.is("PtrTy")) {
|
||||
const ty = this.tys.expr(node.kind.ty);
|
||||
return Ty.create("Ptr", { ty });
|
||||
}
|
||||
if (node.is("PtrMutTy")) {
|
||||
const ty = this.tys.expr(node.kind.ty);
|
||||
return Ty.create("PtrMut", { ty });
|
||||
}
|
||||
|
||||
if (node.is("ArrayTy")) {
|
||||
const ty = this.tys.expr(node.kind.ty);
|
||||
const lengthTy = this.tys.expr(node.kind.length);
|
||||
if (!lengthTy.compatibleWith(Ty.Int)) {
|
||||
this.error(
|
||||
node.kind.length.loc,
|
||||
`for array length, expected 'int', got '${lengthTy.pretty()}'`,
|
||||
);
|
||||
this.fail();
|
||||
}
|
||||
if (!node.kind.length.is("IntExpr")) {
|
||||
this.error(
|
||||
node.kind.length.loc,
|
||||
`array length must be an 'int' expression`,
|
||||
);
|
||||
this.fail();
|
||||
}
|
||||
const length = node.kind.length.kind.value;
|
||||
return Ty.create("Array", { ty, length });
|
||||
}
|
||||
|
||||
if (node.is("SliceTy")) {
|
||||
const ty = this.tys.expr(node.kind.ty);
|
||||
return Ty.create("Slice", { ty });
|
||||
}
|
||||
|
||||
throw new Error(`'${k.tag}' not unhandled`);
|
||||
}
|
||||
|
||||
private checkCall(node: ast.NodeWithKind<"CallExpr">): Ty {
|
||||
private checkCallExpr(node: ast.NodeWithKind<"CallExpr">): Ty {
|
||||
if (node.kind.value.is("IdentExpr")) {
|
||||
const sym = this.syms.get(node.kind.value);
|
||||
const sym = this.cx.sym(node.kind.value);
|
||||
if (sym.tag === "Builtin") {
|
||||
if (sym.id === "len") {
|
||||
if (node.kind.args.length !== 1) {
|
||||
this.reportArgsIncorrectAmount(
|
||||
node,
|
||||
node.kind.args.length,
|
||||
0,
|
||||
null,
|
||||
);
|
||||
}
|
||||
const argTy = this.tys.expr(node.kind.args[0]);
|
||||
if (
|
||||
!(argTy.is("Array") ||
|
||||
argTy.is("Ptr") &&
|
||||
(argTy.kind.ty.is("Array") ||
|
||||
argTy.kind.ty.is("Slice") ||
|
||||
argTy.kind.ty.is("Str")))
|
||||
) {
|
||||
this.reportArgTypeNotCompatible(
|
||||
node,
|
||||
[argTy],
|
||||
[Ty.Error],
|
||||
null,
|
||||
0,
|
||||
);
|
||||
}
|
||||
return Ty.Int;
|
||||
}
|
||||
if (sym.id === "print") {
|
||||
void node.kind.args
|
||||
.map((arg) => this.tys.expr(arg));
|
||||
return Ty.Void;
|
||||
}
|
||||
return this.checkCallExprBuiltin(node, sym);
|
||||
}
|
||||
}
|
||||
|
||||
const calleeTy = this.tys.expr(node.kind.value);
|
||||
const calleeTy = this.cx.expr(node.kind.value);
|
||||
|
||||
const callableTy = calleeTy.is("Fn")
|
||||
? calleeTy
|
||||
@ -376,15 +330,15 @@ class Checker {
|
||||
: null;
|
||||
|
||||
if (!callableTy) {
|
||||
this.error(
|
||||
this.cx.error(
|
||||
node.loc,
|
||||
`type '${calleeTy.pretty()}' not callable`,
|
||||
);
|
||||
this.fail();
|
||||
this.cx.fail();
|
||||
}
|
||||
|
||||
const args = node.kind.args
|
||||
.map((arg) => this.tys.expr(arg));
|
||||
.map((arg) => this.cx.expr(arg));
|
||||
const params = callableTy.kind.params;
|
||||
if (args.length !== params.length) {
|
||||
this.reportArgsIncorrectAmount(
|
||||
@ -408,23 +362,132 @@ class Checker {
|
||||
return callableTy.kind.retTy;
|
||||
}
|
||||
|
||||
private checkCallExprBuiltin(
|
||||
node: ast.NodeWithKind<"CallExpr">,
|
||||
sym: Sym,
|
||||
): Ty {
|
||||
if (!node.kind.value.is("IdentExpr")) {
|
||||
throw new Error();
|
||||
}
|
||||
if (sym.tag !== "Builtin") {
|
||||
throw new Error();
|
||||
}
|
||||
if (sym.id === "len") {
|
||||
if (node.kind.args.length !== 1) {
|
||||
this.reportArgsIncorrectAmount(
|
||||
node,
|
||||
node.kind.args.length,
|
||||
0,
|
||||
null,
|
||||
);
|
||||
}
|
||||
const argTy = this.cx.expr(node.kind.args[0]);
|
||||
if (
|
||||
!(argTy.is("Array") ||
|
||||
argTy.is("Ptr") &&
|
||||
(argTy.kind.ty.is("Array") ||
|
||||
argTy.kind.ty.is("Slice") ||
|
||||
argTy.kind.ty.is("Str")))
|
||||
) {
|
||||
this.reportArgTypeNotCompatible(
|
||||
node,
|
||||
[argTy],
|
||||
[Ty.Error],
|
||||
null,
|
||||
0,
|
||||
);
|
||||
}
|
||||
return Ty.I32;
|
||||
}
|
||||
if (sym.id === "print") {
|
||||
void node.kind.args
|
||||
.map((arg) => this.cx.expr(arg));
|
||||
return Ty.Void;
|
||||
}
|
||||
throw new Error(`builtin '${sym.id}' not handled`);
|
||||
}
|
||||
|
||||
private checkUnaryExpr(node: ast.NodeWithKind<"UnaryExpr">): Ty {
|
||||
const exprTy = this.cx.expr(node.kind.expr);
|
||||
if (node.kind.op === "Negate" && exprTy.compatibleWith(Ty.I32)) {
|
||||
return Ty.I32;
|
||||
}
|
||||
if (node.kind.op === "Not" && exprTy.compatibleWith(Ty.Bool)) {
|
||||
return Ty.Bool;
|
||||
}
|
||||
if (node.kind.op === "Ref") {
|
||||
return Ty.create("Ptr", { ty: exprTy });
|
||||
}
|
||||
if (node.kind.op === "RefMut") {
|
||||
return Ty.create("PtrMut", { ty: exprTy });
|
||||
}
|
||||
if (node.kind.op === "Deref") {
|
||||
if (exprTy.is("Ptr") || exprTy.is("PtrMut")) {
|
||||
if (!exprTy.kind.ty.isSized()) {
|
||||
this.cx.error(
|
||||
node.loc,
|
||||
`cannot dereference unsized type '${exprTy.kind.ty.pretty()}' in an expression`,
|
||||
);
|
||||
this.cx.fail();
|
||||
}
|
||||
return exprTy.kind.ty;
|
||||
}
|
||||
}
|
||||
this.cx.error(
|
||||
node.loc,
|
||||
`operator '${node.kind.tok}' cannot be applied to type '${exprTy.pretty()}'`,
|
||||
);
|
||||
this.cx.fail();
|
||||
}
|
||||
|
||||
private checkBinaryExpr(node: ast.NodeWithKind<"BinaryExpr">): Ty {
|
||||
const left = this.cx.expr(node.kind.left);
|
||||
const right = this.cx.expr(node.kind.right);
|
||||
const result = binaryOpTests
|
||||
.map((test) => test(node.kind.op, left, right))
|
||||
.filter((result) => result)
|
||||
.at(0);
|
||||
if (!result) {
|
||||
this.cx.error(
|
||||
node.loc,
|
||||
`operator '${node.kind.tok}' cannot be applied to types '${left.pretty()}' and '${right.pretty()}'`,
|
||||
);
|
||||
this.cx.fail();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private checkRangeExpr(node: ast.NodeWithKind<"RangeExpr">): Ty {
|
||||
for (const operandExpr of [node.kind.begin, node.kind.end]) {
|
||||
const operandTy = operandExpr && this.cx.expr(operandExpr);
|
||||
if (operandTy && !operandTy.compatibleWith(Ty.I32)) {
|
||||
this.cx.error(
|
||||
operandExpr.loc,
|
||||
`range operand must be '${Ty.I32.pretty()}', not '${operandTy.pretty()}'`,
|
||||
);
|
||||
this.cx.fail();
|
||||
}
|
||||
}
|
||||
return Ty.create("Range", {});
|
||||
}
|
||||
|
||||
private reportArgsIncorrectAmount(
|
||||
node: ast.NodeWithKind<"CallExpr">,
|
||||
argsLength: number,
|
||||
paramsLength: number,
|
||||
calleeTy: Ty | null,
|
||||
): never {
|
||||
this.error(
|
||||
this.cx.error(
|
||||
node.loc,
|
||||
`incorrect amount of arguments. got ${argsLength} expected ${paramsLength}`,
|
||||
);
|
||||
if (calleeTy?.is("FnStmt")) {
|
||||
this.info(
|
||||
this.cx.info(
|
||||
calleeTy.kind.stmt.loc,
|
||||
"function defined here",
|
||||
);
|
||||
}
|
||||
this.fail();
|
||||
this.cx.fail();
|
||||
}
|
||||
|
||||
private reportArgTypeNotCompatible(
|
||||
@ -434,14 +497,14 @@ class Checker {
|
||||
calleeTy: Ty | null,
|
||||
i: number,
|
||||
): never {
|
||||
this.error(
|
||||
this.cx.error(
|
||||
node.kind.args[i].loc,
|
||||
`type '${args[i].pretty()}' not compatible with type '${
|
||||
params[i].pretty()
|
||||
}', for argument ${i}`,
|
||||
);
|
||||
if (calleeTy?.is("FnStmt")) {
|
||||
this.info(
|
||||
this.cx.info(
|
||||
calleeTy.kind.stmt.kind.params[i].loc,
|
||||
`parameter '${
|
||||
calleeTy.kind.stmt.kind.params[i]
|
||||
@ -449,50 +512,114 @@ class Checker {
|
||||
}' defined here`,
|
||||
);
|
||||
}
|
||||
this.fail();
|
||||
}
|
||||
|
||||
private assertCompatible(left: Ty, right: Ty, loc: Loc): void {
|
||||
if (!left.compatibleWith(right)) {
|
||||
this.error(
|
||||
loc,
|
||||
`type '${left.pretty()}' not compatible with type '${right.pretty()}'`,
|
||||
);
|
||||
this.fail();
|
||||
}
|
||||
}
|
||||
|
||||
private error(loc: Loc, message: string) {
|
||||
this.reporter.error(loc, message);
|
||||
}
|
||||
|
||||
private info(loc: Loc, message: string) {
|
||||
this.reporter.info(loc, message);
|
||||
}
|
||||
|
||||
private fail(): never {
|
||||
this.reporter.abort();
|
||||
this.cx.fail();
|
||||
}
|
||||
}
|
||||
|
||||
type BinaryOpPattern = {
|
||||
op: ast.BinaryOp;
|
||||
left: Ty;
|
||||
right: Ty;
|
||||
result: Ty;
|
||||
};
|
||||
class TyChecker {
|
||||
constructor(
|
||||
private cx: CheckerCx,
|
||||
) {}
|
||||
|
||||
const binaryOpPatterns: BinaryOpPattern[] = [
|
||||
{ op: "Add", left: Ty.Int, right: Ty.Int, result: Ty.Int },
|
||||
{ op: "Subtract", left: Ty.Int, right: Ty.Int, result: Ty.Int },
|
||||
{ op: "Multiply", left: Ty.Int, right: Ty.Int, result: Ty.Int },
|
||||
{ op: "Divide", left: Ty.Int, right: Ty.Int, result: Ty.Int },
|
||||
{ op: "Remainder", left: Ty.Int, right: Ty.Int, result: Ty.Int },
|
||||
checkTy(node: ast.Node): Ty {
|
||||
if (node.is("IdentTy")) {
|
||||
const sym = this.cx.sym(node);
|
||||
if (sym.tag === "BuiltinTy") {
|
||||
switch (sym.ident) {
|
||||
case "void":
|
||||
return Ty.Void;
|
||||
case "bool":
|
||||
return Ty.Bool;
|
||||
case "str":
|
||||
return Ty.Str;
|
||||
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 '${node.kind.ident}'`,
|
||||
);
|
||||
}
|
||||
}
|
||||
this.cx.error(node.loc, `symbol is not a type`);
|
||||
this.cx.fail();
|
||||
}
|
||||
|
||||
{ op: "Eq", left: Ty.Int, right: Ty.Int, result: Ty.Bool },
|
||||
{ op: "Ne", left: Ty.Int, right: Ty.Int, result: Ty.Bool },
|
||||
{ op: "Lt", left: Ty.Int, right: Ty.Int, result: Ty.Bool },
|
||||
{ op: "Gt", left: Ty.Int, right: Ty.Int, result: Ty.Bool },
|
||||
{ op: "Lte", left: Ty.Int, right: Ty.Int, result: Ty.Bool },
|
||||
{ op: "Gte", left: Ty.Int, right: Ty.Int, result: Ty.Bool },
|
||||
if (node.is("PtrTy")) {
|
||||
const ty = this.cx.ty(node.kind.ty);
|
||||
return Ty.create("Ptr", { ty });
|
||||
}
|
||||
if (node.is("PtrMutTy")) {
|
||||
const ty = this.cx.ty(node.kind.ty);
|
||||
return Ty.create("PtrMut", { ty });
|
||||
}
|
||||
|
||||
if (node.is("ArrayTy")) {
|
||||
const ty = this.cx.ty(node.kind.ty);
|
||||
const lengthTy = this.cx.expr(node.kind.length);
|
||||
if (!lengthTy.compatibleWith(Ty.I32)) {
|
||||
this.cx.error(
|
||||
node.kind.length.loc,
|
||||
`for array length, expected 'int', got '${lengthTy.pretty()}'`,
|
||||
);
|
||||
this.cx.fail();
|
||||
}
|
||||
if (!node.kind.length.is("IntExpr")) {
|
||||
this.cx.error(
|
||||
node.kind.length.loc,
|
||||
`array length must be an 'int' expression`,
|
||||
);
|
||||
this.cx.fail();
|
||||
}
|
||||
const length = node.kind.length.kind.value;
|
||||
return Ty.create("Array", { ty, length });
|
||||
}
|
||||
|
||||
if (node.is("SliceTy")) {
|
||||
const ty = this.cx.ty(node.kind.ty);
|
||||
return Ty.create("Slice", { ty });
|
||||
}
|
||||
|
||||
throw new Error(`'${node.kind.tag}' not unhandled`);
|
||||
}
|
||||
}
|
||||
|
||||
type BinaryOpTest = (op: ast.BinaryOp, left: Ty, right: Ty) => Ty | null;
|
||||
|
||||
const binaryOpTests: BinaryOpTest[] = [
|
||||
(op, left, right) => {
|
||||
const ops = ["Add", "Subtract", "Multiply", "Divide", "Remainder"];
|
||||
if (
|
||||
ops.includes(op) && left.is("Int") && left.compatibleWith(right)
|
||||
) {
|
||||
return left;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
(op, left, right) => {
|
||||
const ops = ["Eq", "Ne", "Lt", "Gt", "Lte", "Gte"];
|
||||
if (
|
||||
ops.includes(op) && left.is("Int") && left.compatibleWith(right)
|
||||
) {
|
||||
return Ty.Bool;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
];
|
||||
|
||||
@ -286,9 +286,28 @@ export class Parser {
|
||||
this.step();
|
||||
return ast.Node.create(loc, "IdentExpr", { ident });
|
||||
} else if (this.test("int")) {
|
||||
const value = Number(this.current.value);
|
||||
const match = this.current.value
|
||||
.match(/(0|(?:[1-9][0-9]*))([iu](?:8|16|32|64|size))?$/);
|
||||
if (!match) {
|
||||
throw new Error();
|
||||
}
|
||||
const value = Number(match[1]);
|
||||
const intTy = match[2] ?? "i32";
|
||||
if (
|
||||
intTy &&
|
||||
!["8", "16", "32", "64", "size"].includes(intTy.slice(1))
|
||||
) {
|
||||
this.reporter.error(
|
||||
loc,
|
||||
`invalid integer size '${intTy[1]}'`,
|
||||
);
|
||||
this.reporter.abort();
|
||||
}
|
||||
this.step();
|
||||
return ast.Node.create(loc, "IntExpr", { value });
|
||||
return ast.Node.create(loc, "IntExpr", {
|
||||
value,
|
||||
intTy: intTy as ast.IntTy ?? "i32",
|
||||
});
|
||||
} else if (this.test("str")) {
|
||||
const value = this.current.value;
|
||||
this.step();
|
||||
@ -415,9 +434,12 @@ export function tokenize(text: string, reporter: FileReporter): Tok[] {
|
||||
const type = keywordPattern.test(value) ? value : "ident";
|
||||
return { type, value, loc };
|
||||
})
|
||||
.add(/0|(?:[1-9][0-9]*)/, (loc, value) => {
|
||||
.add(
|
||||
/(?:0|(?:[1-9][0-9]*))(?:[iu](?:8|16|32|64|size))?/,
|
||||
(loc, value) => {
|
||||
return { type: "int", value, loc };
|
||||
})
|
||||
},
|
||||
)
|
||||
.add(/"(?:[^\\"]|\\.)*"/, (loc, literal) => {
|
||||
let i = 1;
|
||||
let value = "";
|
||||
|
||||
@ -160,7 +160,21 @@ class ResolverSyms {
|
||||
}
|
||||
|
||||
resolveTy(ident: string): Sym | null {
|
||||
const builtins: string[] = ["void", "int", "bool", "str", "u8"];
|
||||
const builtins: string[] = [
|
||||
"void",
|
||||
"bool",
|
||||
"str",
|
||||
"i8",
|
||||
"i16",
|
||||
"i32",
|
||||
"i64",
|
||||
"isize",
|
||||
"u8",
|
||||
"u16",
|
||||
"u32",
|
||||
"u64",
|
||||
"usize",
|
||||
];
|
||||
if (builtins.includes(ident)) {
|
||||
return { tag: "BuiltinTy", ident } as Sym;
|
||||
}
|
||||
|
||||
@ -172,7 +172,7 @@ class FnLowerer {
|
||||
}
|
||||
|
||||
private lowerLetStmt(stmt: ast.NodeWithKind<"LetStmt">) {
|
||||
const ty = this.tys.expr(stmt.kind.param);
|
||||
const ty = this.tys.param(stmt.kind.param.as("Param"));
|
||||
const expr = this.lowerExpr(stmt.kind.expr);
|
||||
const local = new Inst(
|
||||
Ty.create("PtrMut", { ty }),
|
||||
@ -253,7 +253,7 @@ class FnLowerer {
|
||||
if (argTy.is("Int")) {
|
||||
const argInst = this.lowerExpr(arg);
|
||||
return this.pushInst(
|
||||
Ty.create("Ptr", { ty: Ty.Int }),
|
||||
Ty.create("Ptr", { ty: Ty.I32 }),
|
||||
"GetElemPtr",
|
||||
{ base: valueInst, offset: argInst },
|
||||
);
|
||||
@ -292,7 +292,10 @@ class FnLowerer {
|
||||
throw new Error(`'${sym.tag}' not handled`);
|
||||
}
|
||||
if (expr.is("IntExpr")) {
|
||||
return this.pushInst(ty, "Int", { value: expr.kind.value });
|
||||
return this.pushInst(ty, "Int", {
|
||||
value: expr.kind.value,
|
||||
intTy: expr.kind.intTy,
|
||||
});
|
||||
}
|
||||
if (expr.is("StrExpr")) {
|
||||
return this.pushInst(ty, "Str", { value: expr.kind.value });
|
||||
@ -333,13 +336,10 @@ class FnLowerer {
|
||||
if (expr.is("BinaryExpr")) {
|
||||
const leftTy = this.tys.expr(expr.kind.left);
|
||||
const rightTy = this.tys.expr(expr.kind.right);
|
||||
const binaryOp = binaryOpPatterns
|
||||
.find((pat) =>
|
||||
expr.kind.op === pat.op &&
|
||||
ty.compatibleWith(pat.result) &&
|
||||
leftTy.compatibleWith(pat.left ?? pat.result) &&
|
||||
rightTy.compatibleWith(pat.right ?? pat.left ?? pat.result)
|
||||
);
|
||||
const binaryOp = binaryOpTests
|
||||
.map((test) => test(expr.kind.op, leftTy, rightTy, ty))
|
||||
.filter((tested) => tested)
|
||||
.at(0);
|
||||
if (!binaryOp) {
|
||||
throw new Error(
|
||||
`'${expr.kind.op}' with '${ty.pretty()}' not handled`,
|
||||
@ -347,7 +347,7 @@ class FnLowerer {
|
||||
}
|
||||
const left = this.lowerExpr(expr.kind.left);
|
||||
const right = this.lowerExpr(expr.kind.right);
|
||||
return this.pushInst(ty, binaryOp.tag, { left, right });
|
||||
return this.pushInst(ty, binaryOp, { left, right });
|
||||
}
|
||||
throw new Error(`'${expr.kind.tag}' not handled`);
|
||||
}
|
||||
@ -357,11 +357,11 @@ class FnLowerer {
|
||||
const operandTy = this.tys.expr(expr.kind.expr);
|
||||
if (
|
||||
expr.kind.op === "Negate" &&
|
||||
operandTy.compatibleWith(Ty.Int) &&
|
||||
resultTy.compatibleWith(Ty.Int)
|
||||
operandTy.compatibleWith(Ty.I32) &&
|
||||
resultTy.compatibleWith(Ty.I32)
|
||||
) {
|
||||
const operand = this.lowerExpr(expr.kind.expr);
|
||||
return this.pushInst(Ty.Int, "Negate", { source: operand });
|
||||
return this.pushInst(Ty.I32, "Negate", { source: operand });
|
||||
}
|
||||
if (
|
||||
expr.kind.op === "Not" &&
|
||||
@ -426,25 +426,43 @@ class FnLowerer {
|
||||
}
|
||||
}
|
||||
|
||||
type BinaryOpPattern = {
|
||||
op: ast.BinaryOp;
|
||||
tag: BinaryOp;
|
||||
result: Ty;
|
||||
left?: Ty;
|
||||
right?: Ty;
|
||||
};
|
||||
type BinaryOpTest = (
|
||||
op: ast.BinaryOp,
|
||||
left: Ty,
|
||||
right: Ty,
|
||||
result: Ty,
|
||||
) => BinaryOp | null;
|
||||
|
||||
const binaryOpPatterns: BinaryOpPattern[] = [
|
||||
{ op: "Add", tag: "Add", result: Ty.Int, left: Ty.Int },
|
||||
{ op: "Subtract", tag: "Sub", result: Ty.Int, left: Ty.Int },
|
||||
{ op: "Multiply", tag: "Mul", result: Ty.Int, left: Ty.Int },
|
||||
{ op: "Divide", tag: "Div", result: Ty.Int, left: Ty.Int },
|
||||
{ op: "Remainder", tag: "Rem", result: Ty.Int },
|
||||
|
||||
{ op: "Eq", tag: "Eq", result: Ty.Bool, left: Ty.Int },
|
||||
{ op: "Ne", tag: "Ne", result: Ty.Bool, left: Ty.Int },
|
||||
{ op: "Lt", tag: "Lt", result: Ty.Bool, left: Ty.Int },
|
||||
{ op: "Gt", tag: "Gt", result: Ty.Bool, left: Ty.Int },
|
||||
{ op: "Lte", tag: "Lte", result: Ty.Bool, left: Ty.Int },
|
||||
{ op: "Gte", tag: "Gte", result: Ty.Bool, left: Ty.Int },
|
||||
const binaryOpTests: BinaryOpTest[] = [
|
||||
(op, left, right, result) => {
|
||||
const ops = ["Add", "Subtract", "Multiply", "Divide", "Remainder"];
|
||||
const tags: Record<string, BinaryOp> = {
|
||||
"Add": "Add",
|
||||
"Subtract": "Sub",
|
||||
"Multiply": "Mul",
|
||||
"Divide": "Div",
|
||||
"Remainder": "Rem",
|
||||
};
|
||||
if (
|
||||
ops.includes(op) &&
|
||||
left.is("Int") &&
|
||||
left.compatibleWith(right) &&
|
||||
result.compatibleWith(left)
|
||||
) {
|
||||
return tags[op];
|
||||
}
|
||||
return null;
|
||||
},
|
||||
(op, left, right, result) => {
|
||||
const ops = ["Eq", "Ne", "Lt", "Gt", "Lte", "Gte"];
|
||||
if (
|
||||
ops.includes(op) &&
|
||||
left.is("Int") &&
|
||||
left.compatibleWith(right) &&
|
||||
result.is("Bool")
|
||||
) {
|
||||
return op as BinaryOp;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
];
|
||||
|
||||
@ -119,6 +119,7 @@ export class Inst {
|
||||
case "Void":
|
||||
return "";
|
||||
case "Int":
|
||||
return `${k.value}${k.intTy}`;
|
||||
case "Bool":
|
||||
return `${k.value}`;
|
||||
case "Str":
|
||||
@ -183,7 +184,7 @@ export class Inst {
|
||||
export type InstKind =
|
||||
| { tag: "Error" }
|
||||
| { tag: "Void" }
|
||||
| { tag: "Int"; value: number }
|
||||
| { tag: "Int"; value: number; intTy: ast.IntTy }
|
||||
| { tag: "Bool"; value: boolean }
|
||||
| { tag: "Str"; value: string }
|
||||
| { tag: "Array"; values: Inst[] }
|
||||
|
||||
@ -316,7 +316,35 @@ export class FnInterpreter {
|
||||
const r = right.kind.value;
|
||||
|
||||
const value = (() => {
|
||||
const Int = (value: number) => new Val({ tag: "Int", value });
|
||||
const Int = (value: number) =>
|
||||
new Val({
|
||||
tag: "Int",
|
||||
value: ((value: number) => {
|
||||
if (!inst.ty.is("Int")) {
|
||||
throw new Error();
|
||||
}
|
||||
switch (inst.ty.kind.intTy) {
|
||||
case "i8":
|
||||
return value & 0xff;
|
||||
case "i16":
|
||||
return value & 0xffff;
|
||||
case "i32":
|
||||
return value & 0xffffffff;
|
||||
case "i64":
|
||||
case "isize":
|
||||
return value;
|
||||
case "u8":
|
||||
return value & 0xff;
|
||||
case "u16":
|
||||
return value & 0xffff;
|
||||
case "u32":
|
||||
return value & 0xffffffff;
|
||||
case "u64":
|
||||
case "usize":
|
||||
return value;
|
||||
}
|
||||
})(value),
|
||||
});
|
||||
const Bool = (value: boolean) =>
|
||||
new Val({ tag: "Bool", value });
|
||||
|
||||
|
||||
34
src/ty.ts
34
src/ty.ts
@ -23,9 +23,17 @@ export class Ty {
|
||||
|
||||
static Error = Ty.create("Error", {});
|
||||
static Void = Ty.create("Void", {});
|
||||
static Int = Ty.create("Int", {});
|
||||
static IntLiteral = Ty.create("IntLiteral", {});
|
||||
static U8 = Ty.create("UInt", { size: 8 });
|
||||
static I8 = Ty.create("Int", { intTy: "i8" });
|
||||
static I16 = Ty.create("Int", { intTy: "i16" });
|
||||
static I32 = Ty.create("Int", { intTy: "i32" });
|
||||
static I64 = Ty.create("Int", { intTy: "i64" });
|
||||
static ISize = Ty.create("Int", { intTy: "isize" });
|
||||
static U8 = Ty.create("Int", { intTy: "u8" });
|
||||
static U16 = Ty.create("Int", { intTy: "u16" });
|
||||
static U32 = Ty.create("Int", { intTy: "u32" });
|
||||
static U64 = Ty.create("Int", { intTy: "u64" });
|
||||
static USize = Ty.create("Int", { intTy: "usize" });
|
||||
static Bool = Ty.create("Bool", {});
|
||||
static Str = Ty.create("Str", {});
|
||||
|
||||
@ -56,13 +64,14 @@ export class Ty {
|
||||
if (this.is("Void")) {
|
||||
return other.is("Void");
|
||||
}
|
||||
if (this.is("Int")) {
|
||||
return other.is("Int");
|
||||
}
|
||||
if (this.is("IntLiteral")) {
|
||||
// return other.is("Int") || other.is("UInt");
|
||||
return false;
|
||||
}
|
||||
if (this.is("Int")) {
|
||||
return other.is("Int") && this.kind.intTy == other.kind.intTy;
|
||||
}
|
||||
|
||||
if (this.is("Bool")) {
|
||||
return other.is("Bool");
|
||||
}
|
||||
@ -125,15 +134,13 @@ export class Ty {
|
||||
}
|
||||
// redundant; sanity check
|
||||
if (this.kind.stmt.id !== other.kind.stmt.id) {
|
||||
return false;
|
||||
throw new Error();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
throw new Error(`'${this.kind.tag}' not handled`);
|
||||
}
|
||||
|
||||
// convertibleTo(other: Ty): boolean {}
|
||||
|
||||
isSized(): boolean {
|
||||
if (this.is("Slice") || this.is("Str")) {
|
||||
return false;
|
||||
@ -147,12 +154,10 @@ export class Ty {
|
||||
return "<error>";
|
||||
case "Void":
|
||||
return "void";
|
||||
case "Int":
|
||||
return "int";
|
||||
case "IntLiteral":
|
||||
return "{integer}";
|
||||
case "UInt":
|
||||
return `u${this.kind.size}`;
|
||||
case "Int":
|
||||
return `${this.kind.intTy}`;
|
||||
case "Bool":
|
||||
return "bool";
|
||||
case "Str":
|
||||
@ -191,9 +196,8 @@ export class Ty {
|
||||
export type TyKind =
|
||||
| { tag: "Error" }
|
||||
| { tag: "Void" }
|
||||
| { tag: "Int" }
|
||||
| { tag: "IntLiteral" }
|
||||
| { tag: "UInt"; size: IntSize }
|
||||
| { tag: "Int"; intTy: ast.IntTy }
|
||||
| { tag: "Bool" }
|
||||
| { tag: "Str" }
|
||||
| { tag: "Ptr"; ty: Ty }
|
||||
@ -203,5 +207,3 @@ export type TyKind =
|
||||
| { tag: "Range" }
|
||||
| { tag: "Fn"; params: Ty[]; retTy: Ty }
|
||||
| { tag: "FnStmt"; ty: Ty; stmt: ast.NodeWithKind<"FnStmt"> };
|
||||
|
||||
export type IntSize = 8 | 16 | 32 | 64;
|
||||
|
||||
@ -1,21 +1,21 @@
|
||||
|
||||
fn main()
|
||||
{
|
||||
let array: [int; 3] = [1, 2, 3];
|
||||
let array: [i32; 3] = [1, 2, 3];
|
||||
|
||||
let elem: int = array[0];
|
||||
let elem: i32 = array[0];
|
||||
// expect: 1
|
||||
print(elem);
|
||||
|
||||
let ptr_to_array: *[int; 3] = &array;
|
||||
let ptr_to_array: *[i32; 3] = &array;
|
||||
// expect: 2
|
||||
print(ptr_to_array.*[1]);
|
||||
|
||||
let slice: *[int] = &array[..];
|
||||
let slice: *[i32] = &array[..];
|
||||
// expect: 3
|
||||
print(slice.*[2]);
|
||||
|
||||
let slice_mut: *mut [int] = &mut array[1..3];
|
||||
let slice_mut: *mut [i32] = &mut array[1..3];
|
||||
slice_mut.*[0] = 4;
|
||||
// expect: 4
|
||||
print(array[1]);
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
fn main()
|
||||
{
|
||||
let v: int = 123;
|
||||
let v: i32 = 123;
|
||||
v = 456;
|
||||
print(v);
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
|
||||
fn my_int_fn() -> int {
|
||||
fn my_int_fn() -> i32 {
|
||||
return 123;
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,17 @@
|
||||
|
||||
fn main()
|
||||
{
|
||||
// let a: u8 = 0;
|
||||
let a: u8 = 255u8;
|
||||
// expect: 255
|
||||
print(a);
|
||||
|
||||
a = a + 1u8;
|
||||
// expect: 0
|
||||
print(a);
|
||||
|
||||
let b = 256;
|
||||
// expect: 256
|
||||
print(b);
|
||||
}
|
||||
|
||||
// vim: syntax=rust commentstring=//\ %s
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
fn main()
|
||||
{
|
||||
let a = 123;
|
||||
let b: int = 321;
|
||||
let b: i32 = 321;
|
||||
let c = b;
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
|
||||
fn change_to(place: *mut int, value: int)
|
||||
fn change_to(place: *mut i32, value: i32)
|
||||
{
|
||||
*place = value;
|
||||
}
|
||||
@ -7,7 +7,7 @@ fn change_to(place: *mut int, value: int)
|
||||
fn main()
|
||||
{
|
||||
let a = 1;
|
||||
let b: *int = &a;
|
||||
let b: *i32 = &a;
|
||||
// expect: 1
|
||||
print(*b);
|
||||
|
||||
@ -15,7 +15,7 @@ fn main()
|
||||
// expect: 2
|
||||
print(*b);
|
||||
|
||||
let c: *mut int = &mut a;
|
||||
let c: *mut i32 = &mut a;
|
||||
*c = 3;
|
||||
// expect: 3
|
||||
print(a);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user