work on arrays and slices
All checks were successful
Check / Explore-Gitea-Actions (push) Successful in 8s
All checks were successful
Check / Explore-Gitea-Actions (push) Successful in 8s
This commit is contained in:
parent
284627e0d2
commit
1c92a3c077
29
src/ast.ts
29
src/ast.ts
@ -1,3 +1,11 @@
|
|||||||
|
export function create<Tag extends NodeKind["tag"]>(
|
||||||
|
line: number,
|
||||||
|
tag: Tag,
|
||||||
|
kind: Omit<NodeKind & { tag: Tag }, "tag">,
|
||||||
|
): Node {
|
||||||
|
return Node.create(line, tag, kind);
|
||||||
|
}
|
||||||
|
|
||||||
export class Node {
|
export class Node {
|
||||||
private static idCounter = 0;
|
private static idCounter = 0;
|
||||||
|
|
||||||
@ -82,17 +90,25 @@ export class Node {
|
|||||||
return visit();
|
return visit();
|
||||||
case "ArrayExpr":
|
case "ArrayExpr":
|
||||||
return visit(...k.values);
|
return visit(...k.values);
|
||||||
|
case "IndexExpr":
|
||||||
|
return visit(k.expr, k.arg);
|
||||||
case "CallExpr":
|
case "CallExpr":
|
||||||
return visit(k.expr, ...k.args);
|
return visit(k.expr, ...k.args);
|
||||||
case "UnaryExpr":
|
case "UnaryExpr":
|
||||||
return visit(k.expr);
|
return visit(k.expr);
|
||||||
case "BinaryExpr":
|
case "BinaryExpr":
|
||||||
return visit(k.left, k.right);
|
return visit(k.left, k.right);
|
||||||
|
case "RangeExpr":
|
||||||
|
return visit(k.begin, k.end);
|
||||||
case "IdentTy":
|
case "IdentTy":
|
||||||
return visit();
|
return visit();
|
||||||
case "PtrTy":
|
case "PtrTy":
|
||||||
case "PtrMutTy":
|
case "PtrMutTy":
|
||||||
return visit(k.ty);
|
return visit(k.ty);
|
||||||
|
case "ArrayTy":
|
||||||
|
return visit(k.ty, k.length);
|
||||||
|
case "SliceTy":
|
||||||
|
return visit(k.ty);
|
||||||
}
|
}
|
||||||
k satisfies never;
|
k satisfies never;
|
||||||
}
|
}
|
||||||
@ -118,11 +134,20 @@ export type NodeKind =
|
|||||||
| { tag: "IdentExpr"; ident: string }
|
| { tag: "IdentExpr"; ident: string }
|
||||||
| { tag: "IntExpr"; value: number }
|
| { tag: "IntExpr"; value: number }
|
||||||
| { tag: "ArrayExpr"; values: Node[] }
|
| { tag: "ArrayExpr"; values: Node[] }
|
||||||
|
| { tag: "IndexExpr"; expr: Node; arg: Node }
|
||||||
| { tag: "CallExpr"; expr: Node; args: Node[] }
|
| { tag: "CallExpr"; expr: Node; args: Node[] }
|
||||||
| { tag: "UnaryExpr"; op: UnaryOp; expr: Node; tok: string }
|
| { tag: "UnaryExpr"; op: UnaryOp; expr: Node; tok: string }
|
||||||
| { tag: "BinaryExpr"; op: BinaryOp; left: Node; right: Node; tok: string }
|
| { tag: "BinaryExpr"; op: BinaryOp; left: Node; right: Node; tok: string }
|
||||||
|
| {
|
||||||
|
tag: "RangeExpr";
|
||||||
|
begin: Node | null;
|
||||||
|
end: Node | null;
|
||||||
|
limit: RangeLimit;
|
||||||
|
}
|
||||||
| { tag: "IdentTy"; ident: string }
|
| { tag: "IdentTy"; ident: string }
|
||||||
| { tag: "PtrTy" | "PtrMutTy"; ty: Node };
|
| { tag: "PtrTy" | "PtrMutTy"; ty: Node }
|
||||||
|
| { tag: "ArrayTy"; ty: Node; length: Node }
|
||||||
|
| { tag: "SliceTy"; ty: Node };
|
||||||
|
|
||||||
export type UnaryOp =
|
export type UnaryOp =
|
||||||
| "Not"
|
| "Not"
|
||||||
@ -151,6 +176,8 @@ export type BinaryOp =
|
|||||||
| "Divide"
|
| "Divide"
|
||||||
| "Remainder";
|
| "Remainder";
|
||||||
|
|
||||||
|
export type RangeLimit = "Inclusive" | "Exclusive";
|
||||||
|
|
||||||
export interface Visitor {
|
export interface Visitor {
|
||||||
visit(node: Node): void | "break";
|
visit(node: Node): void | "break";
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,11 +32,13 @@ export function printDiagnostics(
|
|||||||
`${" ".repeat(lineNumberText.length)}%c|\n` +
|
`${" ".repeat(lineNumberText.length)}%c|\n` +
|
||||||
`${lineNumberText}|%c${lineText}\n` +
|
`${lineNumberText}|%c${lineText}\n` +
|
||||||
`${" ".repeat(lineNumberText.length)}%c|` +
|
`${" ".repeat(lineNumberText.length)}%c|` +
|
||||||
`%c${"~".repeat(lineText.length)}%c`,
|
`%c${"~".repeat(lineText.length)}\n` +
|
||||||
|
`${" ".repeat(lineNumberText.length)}%c|%c`,
|
||||||
"color: cyan;",
|
"color: cyan;",
|
||||||
"color: lightwhite;",
|
"color: lightwhite;",
|
||||||
"color: cyan;",
|
"color: cyan;",
|
||||||
`color: ${severityColor};`,
|
`color: ${severityColor};`,
|
||||||
|
"color: cyan;",
|
||||||
"",
|
"",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -98,6 +98,28 @@ export class Checker {
|
|||||||
return Ty.create("Array", { ty, length });
|
return Ty.create("Array", { ty, length });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (node.is("IndexExpr")) {
|
||||||
|
const exprTy = this.check(node.kind.expr);
|
||||||
|
const argTy = this.check(node.kind.arg);
|
||||||
|
if (
|
||||||
|
(exprTy.is("Array") || exprTy.is("Slice")) &&
|
||||||
|
argTy.compatibleWith(Ty.Int)
|
||||||
|
) {
|
||||||
|
return exprTy.kind.ty;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
(exprTy.is("Array") || exprTy.is("Slice")) &&
|
||||||
|
argTy.compatibleWith(Ty.create("Range", {}))
|
||||||
|
) {
|
||||||
|
return Ty.create("Slice", { ty: exprTy.kind.ty });
|
||||||
|
}
|
||||||
|
this.error(
|
||||||
|
node.line,
|
||||||
|
`cannot use index operator on '${exprTy.pretty()}' with '${argTy.pretty()}'`,
|
||||||
|
);
|
||||||
|
this.fail();
|
||||||
|
}
|
||||||
|
|
||||||
if (node.is("CallExpr")) {
|
if (node.is("CallExpr")) {
|
||||||
return this.checkCall(node);
|
return this.checkCall(node);
|
||||||
}
|
}
|
||||||
@ -147,6 +169,20 @@ export class Checker {
|
|||||||
return binaryOp.result;
|
return binaryOp.result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (node.is("RangeExpr")) {
|
||||||
|
for (const operandExpr of [node.kind.begin, node.kind.end]) {
|
||||||
|
const operandTy = operandExpr && this.check(operandExpr);
|
||||||
|
if (operandTy && !operandTy.compatibleWith(Ty.Int)) {
|
||||||
|
this.error(
|
||||||
|
operandExpr.line,
|
||||||
|
`range operand must be '${Ty.Int.pretty()}', not '${operandTy.pretty()}'`,
|
||||||
|
);
|
||||||
|
this.fail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Ty.create("Range", {});
|
||||||
|
}
|
||||||
|
|
||||||
if (node.is("IdentTy")) {
|
if (node.is("IdentTy")) {
|
||||||
switch (node.kind.ident) {
|
switch (node.kind.ident) {
|
||||||
case "void":
|
case "void":
|
||||||
@ -169,6 +205,32 @@ export class Checker {
|
|||||||
return Ty.create("PtrMut", { ty });
|
return Ty.create("PtrMut", { ty });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (node.is("ArrayTy")) {
|
||||||
|
const ty = this.check(node.kind.ty);
|
||||||
|
const lengthTy = this.check(node.kind.length);
|
||||||
|
if (!lengthTy.compatibleWith(Ty.Int)) {
|
||||||
|
this.error(
|
||||||
|
node.kind.length.line,
|
||||||
|
`for array length, expected 'int', got '${lengthTy.pretty()}'`,
|
||||||
|
);
|
||||||
|
this.fail();
|
||||||
|
}
|
||||||
|
if (!node.kind.length.is("IntExpr")) {
|
||||||
|
this.error(
|
||||||
|
node.kind.length.line,
|
||||||
|
`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.check(node.kind.ty);
|
||||||
|
return Ty.create("Slice", { ty });
|
||||||
|
}
|
||||||
|
|
||||||
throw new Error(`'${k.tag}' not unhandled`);
|
throw new Error(`'${k.tag}' not unhandled`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -241,7 +303,7 @@ export class Checker {
|
|||||||
this.error(
|
this.error(
|
||||||
node.kind.args[i].line,
|
node.kind.args[i].line,
|
||||||
`type '${args[i].pretty()}' not compatible with type '${
|
`type '${args[i].pretty()}' not compatible with type '${
|
||||||
params[i]
|
params[i].pretty()
|
||||||
}', for argument ${i}`,
|
}', for argument ${i}`,
|
||||||
);
|
);
|
||||||
if (calleeTy.is("FnStmt")) {
|
if (calleeTy.is("FnStmt")) {
|
||||||
|
|||||||
@ -12,6 +12,7 @@ export class Parser {
|
|||||||
private toks: Tok[];
|
private toks: Tok[];
|
||||||
private idx = 0;
|
private idx = 0;
|
||||||
private currentLine = 1;
|
private currentLine = 1;
|
||||||
|
private prevTok: Tok | null = null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private filename: string,
|
private filename: string,
|
||||||
@ -130,7 +131,31 @@ export class Parser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
parseExpr(): ast.Node {
|
parseExpr(): ast.Node {
|
||||||
return this.parseBinary();
|
return this.parseRange();
|
||||||
|
}
|
||||||
|
|
||||||
|
parseRange(): ast.Node {
|
||||||
|
const loc = this.loc();
|
||||||
|
if (this.eat("..") || this.eat("..=")) {
|
||||||
|
return this.parseRangeTail(loc, null, this.prevTok!.type);
|
||||||
|
} else {
|
||||||
|
const begin = this.parseBinary();
|
||||||
|
if (this.eat("..") || this.eat("..=")) {
|
||||||
|
return this.parseRangeTail(loc, begin, this.prevTok!.type);
|
||||||
|
} else {
|
||||||
|
return begin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parseRangeTail(loc: number, begin: ast.Node | null, tok: string): ast.Node {
|
||||||
|
const limit: ast.RangeLimit = tok === ".." ? "Exclusive" : "Inclusive";
|
||||||
|
let end: ast.Node | null = null;
|
||||||
|
if (![";", ",", ")", "]"].some((tok) => this.test(tok))) {
|
||||||
|
end = this.parseBinary();
|
||||||
|
}
|
||||||
|
return ast
|
||||||
|
.create(loc, "RangeExpr", { begin, end, limit });
|
||||||
}
|
}
|
||||||
|
|
||||||
parseBinary(prec = 7): ast.Node {
|
parseBinary(prec = 7): ast.Node {
|
||||||
@ -206,7 +231,16 @@ export class Parser {
|
|||||||
let expr = this.parseOperand();
|
let expr = this.parseOperand();
|
||||||
while (true) {
|
while (true) {
|
||||||
const loc = this.loc();
|
const loc = this.loc();
|
||||||
if (this.eat("(")) {
|
if (this.eat(".*")) {
|
||||||
|
// use unary because it's already there
|
||||||
|
// TODO: consider making a separate node type
|
||||||
|
expr = ast.Node
|
||||||
|
.create(loc, "UnaryExpr", { expr, op: "Deref", tok: ".*" });
|
||||||
|
} else if (this.eat("[")) {
|
||||||
|
const arg = this.parseExpr();
|
||||||
|
this.mustEat("]");
|
||||||
|
expr = ast.Node.create(loc, "IndexExpr", { expr, arg });
|
||||||
|
} else if (this.eat("(")) {
|
||||||
const args: ast.Node[] = [];
|
const args: ast.Node[] = [];
|
||||||
if (!this.test(")")) {
|
if (!this.test(")")) {
|
||||||
args.push(this.parseExpr());
|
args.push(this.parseExpr());
|
||||||
@ -269,6 +303,16 @@ export class Parser {
|
|||||||
const mutable = this.eat("mut");
|
const mutable = this.eat("mut");
|
||||||
const ty = this.parseTy();
|
const ty = this.parseTy();
|
||||||
return ast.Node.create(loc, mutable ? "PtrMutTy" : "PtrTy", { ty });
|
return ast.Node.create(loc, mutable ? "PtrMutTy" : "PtrTy", { ty });
|
||||||
|
} else if (this.eat("[")) {
|
||||||
|
const ty = this.parseTy();
|
||||||
|
if (this.eat(";")) {
|
||||||
|
const length = this.parseExpr();
|
||||||
|
this.mustEat("]");
|
||||||
|
return ast.Node.create(loc, "ArrayTy", { ty, length });
|
||||||
|
} else {
|
||||||
|
this.mustEat("]");
|
||||||
|
return ast.Node.create(loc, "SliceTy", { ty });
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.mustEat("<type>");
|
this.mustEat("<type>");
|
||||||
throw new Error();
|
throw new Error();
|
||||||
@ -291,6 +335,7 @@ export class Parser {
|
|||||||
|
|
||||||
private error(message: string, loc: number): never {
|
private error(message: string, loc: number): never {
|
||||||
printDiagnostics(this.filename, loc, "error", message, this.text);
|
printDiagnostics(this.filename, loc, "error", message, this.text);
|
||||||
|
throw new Error();
|
||||||
Deno.exit(1);
|
Deno.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -303,6 +348,9 @@ export class Parser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private step() {
|
private step() {
|
||||||
|
if (!this.done) {
|
||||||
|
this.prevTok = this.current;
|
||||||
|
}
|
||||||
this.idx += 1;
|
this.idx += 1;
|
||||||
if (!this.done) {
|
if (!this.done) {
|
||||||
this.currentLine = this.current.line;
|
this.currentLine = this.current.line;
|
||||||
@ -331,7 +379,7 @@ export type Tok = { type: string; value: string; line: number };
|
|||||||
const keywordPattern =
|
const keywordPattern =
|
||||||
/^(?:fn)|(?:return)|(?:let)|(?:if)|(?:else)|(?:while)|(?:break)|(?:or)|(?:and)|(?:not)|(?:mut)$/;
|
/^(?:fn)|(?:return)|(?:let)|(?:if)|(?:else)|(?:while)|(?:break)|(?:or)|(?:and)|(?:not)|(?:mut)$/;
|
||||||
const operatorPattern =
|
const operatorPattern =
|
||||||
/((?:\->)|(?:==)|(?:!=)|(?:<=)|(?:>=)|(?:<<)|(?:>>)|[\n\(\)\{\}\[\]\,\.\;\:\!\=\<\>\&\^\|\+\-\*\/\%])/g;
|
/((?:\->)|(?:==)|(?:!=)|(?:<=)|(?:>=)|(?:<<)|(?:>>)|(?:\.\*)|(?:\.\.)|(?:\.\.=)|[\n\(\)\{\}\[\]\,\.\;\:\!\=\<\>\&\^\|\+\-\*\/\%])/g;
|
||||||
|
|
||||||
export function tokenize(text: string): Tok[] {
|
export function tokenize(text: string): Tok[] {
|
||||||
return text
|
return text
|
||||||
@ -359,7 +407,7 @@ export function tokenize(text: string): Tok[] {
|
|||||||
: tok
|
: tok
|
||||||
)
|
)
|
||||||
.map((tok) =>
|
.map((tok) =>
|
||||||
/^0|(?:[1-9][0-9]*)$/.test(tok.value)
|
/^(?:0|(?:[1-9][0-9]*))$/.test(tok.value)
|
||||||
? { ...tok, type: "int" }
|
? { ...tok, type: "int" }
|
||||||
: tok
|
: tok
|
||||||
);
|
);
|
||||||
|
|||||||
255
src/middle.ts
255
src/middle.ts
@ -1,6 +1,7 @@
|
|||||||
import * as ast from "./ast.ts";
|
import * as ast from "./ast.ts";
|
||||||
import { Checker, ResolveMap } from "./front/mod.ts";
|
import { Checker, ResolveMap } from "./front/mod.ts";
|
||||||
import { Ty } from "./ty.ts";
|
import { Ty } from "./ty.ts";
|
||||||
|
import { BasicBlock, BinaryOp, Fn, Inst, InstKind } from "./mir.ts";
|
||||||
|
|
||||||
export class MiddleLowerer {
|
export class MiddleLowerer {
|
||||||
private fns = new Map<number, Fn>();
|
private fns = new Map<number, Fn>();
|
||||||
@ -50,7 +51,7 @@ class FnLowerer {
|
|||||||
if (stmt.is("LetStmt")) {
|
if (stmt.is("LetStmt")) {
|
||||||
const ty = this.checker.check(stmt.kind.param);
|
const ty = this.checker.check(stmt.kind.param);
|
||||||
const expr = this.lowerExpr(stmt.kind.expr);
|
const expr = this.lowerExpr(stmt.kind.expr);
|
||||||
const local = new Inst(ty, { tag: "Alloca" });
|
const local = new Inst(Ty.create("Ptr", { ty }), { tag: "Alloca" });
|
||||||
this.allocs.push(local);
|
this.allocs.push(local);
|
||||||
this.pushInst(Ty.Void, "Store", {
|
this.pushInst(Ty.Void, "Store", {
|
||||||
target: local,
|
target: local,
|
||||||
@ -134,6 +135,43 @@ class FnLowerer {
|
|||||||
return this.lowerExpr(place.kind.expr);
|
return this.lowerExpr(place.kind.expr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (place.is("IndexExpr")) {
|
||||||
|
const exprTy = this.checker.check(place.kind.expr);
|
||||||
|
if (!exprTy.is("Array") && !exprTy.is("Slice")) {
|
||||||
|
throw new Error(exprTy.pretty());
|
||||||
|
}
|
||||||
|
const arg = place.kind.arg;
|
||||||
|
const argTy = this.checker.check(arg);
|
||||||
|
const exprInst = this.lowerExpr(place.kind.expr);
|
||||||
|
if (argTy.is("Int")) {
|
||||||
|
const argInst = this.lowerExpr(arg);
|
||||||
|
return this.pushInst(
|
||||||
|
Ty.create("PtrMut", { ty: exprTy.kind.ty }),
|
||||||
|
"Index",
|
||||||
|
{ value: exprInst, arg: argInst },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (argTy.is("Range")) {
|
||||||
|
if (!arg.is("RangeExpr")) {
|
||||||
|
throw new Error("not supported yet");
|
||||||
|
}
|
||||||
|
const begin = arg.kind.begin &&
|
||||||
|
this.lowerExpr(arg.kind.begin);
|
||||||
|
const end = arg.kind.end &&
|
||||||
|
this.lowerExpr(arg.kind.end);
|
||||||
|
return this.pushInst(
|
||||||
|
Ty.create("PtrMut", {
|
||||||
|
ty: Ty.create("Slice", { ty: exprTy.kind.ty }),
|
||||||
|
}),
|
||||||
|
"Slice",
|
||||||
|
{ value: exprInst, begin, end },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
throw new Error(
|
||||||
|
`${place.kind.tag} with arg ${argTy.pretty()} not handled`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
throw new Error(`'${place.kind.tag}' not handled`);
|
throw new Error(`'${place.kind.tag}' not handled`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,6 +210,12 @@ class FnLowerer {
|
|||||||
.map((value) => this.lowerExpr(value));
|
.map((value) => this.lowerExpr(value));
|
||||||
return this.pushInst(ty, "Array", { values });
|
return this.pushInst(ty, "Array", { values });
|
||||||
}
|
}
|
||||||
|
if (expr.is("IndexExpr")) {
|
||||||
|
const ty = this.checker.check(expr.kind.expr);
|
||||||
|
const arg = this.lowerExpr(expr.kind.arg);
|
||||||
|
const value = this.lowerExpr(expr.kind.expr);
|
||||||
|
return this.pushInst(ty, "Index", { value, arg });
|
||||||
|
}
|
||||||
if (expr.is("CallExpr")) {
|
if (expr.is("CallExpr")) {
|
||||||
const ty = this.checker.check(expr);
|
const ty = this.checker.check(expr);
|
||||||
const args = expr.kind.args
|
const args = expr.kind.args
|
||||||
@ -228,6 +272,16 @@ class FnLowerer {
|
|||||||
`${expr.kind.op} with sym ${sym.tag} not handled`,
|
`${expr.kind.op} with sym ${sym.tag} not handled`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (place.is("IndexExpr")) {
|
||||||
|
const placeTy = this.checker.check(place);
|
||||||
|
const placeInst = this.lowerPlace(place);
|
||||||
|
if (placeTy.is("Slice")) {
|
||||||
|
return placeInst;
|
||||||
|
}
|
||||||
|
return this.pushInst(placeTy, "Load", {
|
||||||
|
source: placeInst,
|
||||||
|
});
|
||||||
|
}
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`${expr.kind.op} with place ${place.kind.tag} not handled`,
|
`${expr.kind.op} with place ${place.kind.tag} not handled`,
|
||||||
);
|
);
|
||||||
@ -302,202 +356,3 @@ const binaryOpPatterns: BinaryOpPattern[] = [
|
|||||||
{ op: "Lte", tag: "Lte", 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 },
|
{ op: "Gte", tag: "Gte", result: Ty.Bool, left: Ty.Int },
|
||||||
];
|
];
|
||||||
|
|
||||||
export interface Visitor {
|
|
||||||
visitFn?(fn: Fn): void;
|
|
||||||
visitBasicBlock?(bb: BasicBlock): void;
|
|
||||||
visitInst?(inst: Inst): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Fn {
|
|
||||||
constructor(
|
|
||||||
public stmt: ast.FnStmt,
|
|
||||||
public ty: Ty,
|
|
||||||
public bbs: BasicBlock[],
|
|
||||||
) {}
|
|
||||||
|
|
||||||
visit(v: Visitor) {
|
|
||||||
v.visitFn?.(this);
|
|
||||||
for (const bb of this.bbs) {
|
|
||||||
bb.visit(v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pretty(): string {
|
|
||||||
const fnTy = this.ty.is("FnStmt") && this.ty.kind.ty.is("Fn")
|
|
||||||
? this.ty.kind.ty
|
|
||||||
: null;
|
|
||||||
if (!fnTy) {
|
|
||||||
throw new Error();
|
|
||||||
}
|
|
||||||
const cx = new PrettyCx();
|
|
||||||
return `fn ${this.stmt.kind.ident}(${
|
|
||||||
fnTy.kind.params
|
|
||||||
.map((ty, idx) => `${idx}: ${ty.pretty()}`)
|
|
||||||
.join(", ")
|
|
||||||
}) -> ${fnTy.kind.retTy.pretty()}\n{\n${
|
|
||||||
this.bbs
|
|
||||||
.map((bb) => bb.pretty(cx))
|
|
||||||
.join("\n")
|
|
||||||
}\n}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class IdMap<T> {
|
|
||||||
private map = new Map<T, number>();
|
|
||||||
private counter = 0;
|
|
||||||
|
|
||||||
id(val: T): number {
|
|
||||||
if (!this.map.has(val)) {
|
|
||||||
this.map.set(val, this.counter++);
|
|
||||||
}
|
|
||||||
return this.map.get(val)!;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class PrettyCx {
|
|
||||||
private bbIds = new IdMap<BasicBlock>();
|
|
||||||
private regIds = new IdMap<Inst>();
|
|
||||||
|
|
||||||
bbId(bb: BasicBlock): number {
|
|
||||||
return this.bbIds.id(bb);
|
|
||||||
}
|
|
||||||
regId(reg: Inst): number {
|
|
||||||
return this.regIds.id(reg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class BasicBlock {
|
|
||||||
public insts: Inst[] = [];
|
|
||||||
|
|
||||||
visit(v: Visitor) {
|
|
||||||
v.visitBasicBlock?.(this);
|
|
||||||
for (const inst of this.insts) {
|
|
||||||
inst.visit(v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pretty(cx: PrettyCx): string {
|
|
||||||
return `bb${cx.bbId(this)}:\n${
|
|
||||||
this.insts
|
|
||||||
.map((inst) => inst.pretty(cx))
|
|
||||||
.map((line) => ` ${line}`)
|
|
||||||
.join("\n")
|
|
||||||
}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Inst {
|
|
||||||
constructor(
|
|
||||||
public ty: Ty,
|
|
||||||
public kind: InstKind,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
visit(v: Visitor) {
|
|
||||||
v.visitInst?.(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
pretty(cx: PrettyCx): string {
|
|
||||||
const r = (v: Inst) => `%${cx.regId(v)}`;
|
|
||||||
|
|
||||||
return `${`${r(this)}:`.padEnd(4, " ")} ${
|
|
||||||
this.ty.pretty().padEnd(4, " ")
|
|
||||||
} = ${this.kind.tag}${
|
|
||||||
(() => {
|
|
||||||
const k = this.kind;
|
|
||||||
switch (k.tag) {
|
|
||||||
case "Error":
|
|
||||||
return "";
|
|
||||||
case "Void":
|
|
||||||
return "";
|
|
||||||
case "Int":
|
|
||||||
case "Bool":
|
|
||||||
return ` ${k.value}`;
|
|
||||||
case "Array":
|
|
||||||
return ` [${k.values.map(r).join(", ")}]`;
|
|
||||||
case "Fn":
|
|
||||||
return ` ${k.fn.stmt.kind.ident}`;
|
|
||||||
case "Param":
|
|
||||||
return ` ${k.idx}`;
|
|
||||||
case "Call":
|
|
||||||
return ` ${r(k.callee)} (${k.args.map(r).join(", ")})`;
|
|
||||||
case "Alloca":
|
|
||||||
return "";
|
|
||||||
case "Load":
|
|
||||||
return ` ${r(k.source)}`;
|
|
||||||
case "Store":
|
|
||||||
return ` ${r(k.target)} = ${r(k.source)}`;
|
|
||||||
case "Jump":
|
|
||||||
return ` bb${cx.bbId(k.target)}`;
|
|
||||||
case "Branch":
|
|
||||||
return ` if ${r(k.cond)} then bb${
|
|
||||||
cx.bbId(k.truthy)
|
|
||||||
} else bb${cx.bbId(k.falsy)}`;
|
|
||||||
case "Return":
|
|
||||||
return ` ${r(k.source)}`;
|
|
||||||
case "Not":
|
|
||||||
case "Negate":
|
|
||||||
return ` ${r(k.source)}`;
|
|
||||||
case "Eq":
|
|
||||||
case "Ne":
|
|
||||||
case "Lt":
|
|
||||||
case "Gt":
|
|
||||||
case "Lte":
|
|
||||||
case "Gte":
|
|
||||||
case "BitOr":
|
|
||||||
case "BitXor":
|
|
||||||
case "BitAnd":
|
|
||||||
case "Shl":
|
|
||||||
case "Shr":
|
|
||||||
case "Add":
|
|
||||||
case "Sub":
|
|
||||||
case "Mul":
|
|
||||||
case "Div":
|
|
||||||
case "Rem":
|
|
||||||
return ` ${r(k.left)} ${r(k.right)}`;
|
|
||||||
case "DebugPrint":
|
|
||||||
return ` ${k.args.map(r).join(", ")}`;
|
|
||||||
}
|
|
||||||
k satisfies never;
|
|
||||||
})()
|
|
||||||
}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type InstKind =
|
|
||||||
| { tag: "Error" }
|
|
||||||
| { tag: "Void" }
|
|
||||||
| { tag: "Int"; value: number }
|
|
||||||
| { tag: "Bool"; value: boolean }
|
|
||||||
| { tag: "Array"; values: Inst[] }
|
|
||||||
| { tag: "Fn"; fn: Fn }
|
|
||||||
| { tag: "Param"; idx: number }
|
|
||||||
| { tag: "Call"; callee: Inst; args: Inst[] }
|
|
||||||
| { tag: "Alloca" }
|
|
||||||
| { tag: "Load"; source: Inst }
|
|
||||||
| { tag: "Store"; target: Inst; source: Inst }
|
|
||||||
| { tag: "Jump"; target: BasicBlock }
|
|
||||||
| { tag: "Branch"; cond: Inst; truthy: BasicBlock; falsy: BasicBlock }
|
|
||||||
| { tag: "Return"; source: Inst }
|
|
||||||
| { tag: "Not"; source: Inst }
|
|
||||||
| { tag: "Negate"; source: Inst }
|
|
||||||
| { tag: BinaryOp; left: Inst; right: Inst }
|
|
||||||
| { tag: "DebugPrint"; args: Inst[] };
|
|
||||||
|
|
||||||
export type BinaryOp =
|
|
||||||
| "Eq"
|
|
||||||
| "Ne"
|
|
||||||
| "Lt"
|
|
||||||
| "Gt"
|
|
||||||
| "Lte"
|
|
||||||
| "Gte"
|
|
||||||
| "BitOr"
|
|
||||||
| "BitXor"
|
|
||||||
| "BitAnd"
|
|
||||||
| "Shl"
|
|
||||||
| "Shr"
|
|
||||||
| "Add"
|
|
||||||
| "Sub"
|
|
||||||
| "Mul"
|
|
||||||
| "Div"
|
|
||||||
| "Rem";
|
|
||||||
|
|||||||
217
src/mir.ts
Normal file
217
src/mir.ts
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
import * as ast from "./ast.ts";
|
||||||
|
import { Ty } from "./ty.ts";
|
||||||
|
|
||||||
|
export interface Visitor {
|
||||||
|
visitFn?(fn: Fn): void;
|
||||||
|
visitBasicBlock?(bb: BasicBlock): void;
|
||||||
|
visitInst?(inst: Inst): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Fn {
|
||||||
|
constructor(
|
||||||
|
public stmt: ast.FnStmt,
|
||||||
|
public ty: Ty,
|
||||||
|
public bbs: BasicBlock[],
|
||||||
|
) {}
|
||||||
|
|
||||||
|
visit(v: Visitor) {
|
||||||
|
v.visitFn?.(this);
|
||||||
|
for (const bb of this.bbs) {
|
||||||
|
bb.visit(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pretty(): string {
|
||||||
|
const fnTy = this.ty.is("FnStmt") && this.ty.kind.ty.is("Fn")
|
||||||
|
? this.ty.kind.ty
|
||||||
|
: null;
|
||||||
|
if (!fnTy) {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
const cx = new PrettyCx();
|
||||||
|
return `fn ${this.stmt.kind.ident}(${
|
||||||
|
fnTy.kind.params
|
||||||
|
.map((ty, idx) => `${idx}: ${ty.pretty()}`)
|
||||||
|
.join(", ")
|
||||||
|
}) -> ${fnTy.kind.retTy.pretty()}\n{\n${
|
||||||
|
this.bbs
|
||||||
|
.map((bb) => bb.pretty(cx))
|
||||||
|
.join("\n")
|
||||||
|
}\n}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class IdMap<T> {
|
||||||
|
private map = new Map<T, number>();
|
||||||
|
private counter = 0;
|
||||||
|
|
||||||
|
id(val: T): number {
|
||||||
|
if (!this.map.has(val)) {
|
||||||
|
this.map.set(val, this.counter++);
|
||||||
|
}
|
||||||
|
return this.map.get(val)!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PrettyCx {
|
||||||
|
private bbIds = new IdMap<BasicBlock>();
|
||||||
|
private regIds = new IdMap<Inst>();
|
||||||
|
|
||||||
|
bbId(bb: BasicBlock): number {
|
||||||
|
return this.bbIds.id(bb);
|
||||||
|
}
|
||||||
|
regId(reg: Inst): number {
|
||||||
|
return this.regIds.id(reg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BasicBlock {
|
||||||
|
public insts: Inst[] = [];
|
||||||
|
|
||||||
|
visit(v: Visitor) {
|
||||||
|
v.visitBasicBlock?.(this);
|
||||||
|
for (const inst of this.insts) {
|
||||||
|
inst.visit(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pretty(cx: PrettyCx): string {
|
||||||
|
const consts = ["Void", "Int", "Bool", "Array"];
|
||||||
|
|
||||||
|
return `bb${cx.bbId(this)}:\n${
|
||||||
|
this.insts
|
||||||
|
.filter((inst) => !consts.includes(inst.kind.tag))
|
||||||
|
.map((inst) => inst.pretty(cx))
|
||||||
|
.map((line) => ` ${line}`)
|
||||||
|
.join("\n")
|
||||||
|
}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Inst {
|
||||||
|
constructor(
|
||||||
|
public ty: Ty,
|
||||||
|
public kind: InstKind,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
visit(v: Visitor) {
|
||||||
|
v.visitInst?.(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
pretty(cx: PrettyCx): string {
|
||||||
|
const valueless = ["Store", "Jump", "Branch", "Return", "DebugPrint"];
|
||||||
|
const valueType = `%${cx.regId(this)} = ${this.ty.pretty()} `;
|
||||||
|
return `${
|
||||||
|
!valueless.includes(this.kind.tag) ? valueType : ""
|
||||||
|
}${this.kind.tag} ${this.prettyArgs(cx)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private prettyArgs(cx: PrettyCx): string {
|
||||||
|
const consts = ["Void", "Int", "Bool", "Array"];
|
||||||
|
|
||||||
|
const r = (v: Inst) =>
|
||||||
|
consts.includes(v.kind.tag) ? v.prettyArgs(cx) : `%${cx.regId(v)}`;
|
||||||
|
|
||||||
|
const k = this.kind;
|
||||||
|
switch (k.tag) {
|
||||||
|
case "Error":
|
||||||
|
return "";
|
||||||
|
case "Void":
|
||||||
|
return "";
|
||||||
|
case "Int":
|
||||||
|
case "Bool":
|
||||||
|
return `${k.value}`;
|
||||||
|
case "Array":
|
||||||
|
return `[${k.values.map(r).join(", ")}]`;
|
||||||
|
case "Fn":
|
||||||
|
return `${k.fn.stmt.kind.ident}`;
|
||||||
|
case "Param":
|
||||||
|
return `${k.idx}`;
|
||||||
|
case "Index":
|
||||||
|
return `${r(k.value)} [${r(k.arg)}]`;
|
||||||
|
case "Slice":
|
||||||
|
return `${r(k.value)} [${k.begin ? r(k.begin) : ""}..${
|
||||||
|
k.end ? r(k.end) : ""
|
||||||
|
}]`;
|
||||||
|
case "Call":
|
||||||
|
return `${r(k.callee)} (${k.args.map(r).join(", ")})`;
|
||||||
|
case "Alloca":
|
||||||
|
return "";
|
||||||
|
case "Load":
|
||||||
|
return `${r(k.source)}`;
|
||||||
|
case "Store":
|
||||||
|
return `${r(k.target)} = ${r(k.source)}`;
|
||||||
|
case "Jump":
|
||||||
|
return `bb${cx.bbId(k.target)}`;
|
||||||
|
case "Branch":
|
||||||
|
return `if ${r(k.cond)} then bb${cx.bbId(k.truthy)} else bb${
|
||||||
|
cx.bbId(k.falsy)
|
||||||
|
}`;
|
||||||
|
case "Return":
|
||||||
|
return `${r(k.source)}`;
|
||||||
|
case "Not":
|
||||||
|
case "Negate":
|
||||||
|
return `${r(k.source)}`;
|
||||||
|
case "Eq":
|
||||||
|
case "Ne":
|
||||||
|
case "Lt":
|
||||||
|
case "Gt":
|
||||||
|
case "Lte":
|
||||||
|
case "Gte":
|
||||||
|
case "BitOr":
|
||||||
|
case "BitXor":
|
||||||
|
case "BitAnd":
|
||||||
|
case "Shl":
|
||||||
|
case "Shr":
|
||||||
|
case "Add":
|
||||||
|
case "Sub":
|
||||||
|
case "Mul":
|
||||||
|
case "Div":
|
||||||
|
case "Rem":
|
||||||
|
return `${r(k.left)} ${r(k.right)}`;
|
||||||
|
case "DebugPrint":
|
||||||
|
return `${k.args.map(r).join(", ")}`;
|
||||||
|
}
|
||||||
|
k satisfies never;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type InstKind =
|
||||||
|
| { tag: "Error" }
|
||||||
|
| { tag: "Void" }
|
||||||
|
| { tag: "Int"; value: number }
|
||||||
|
| { tag: "Bool"; value: boolean }
|
||||||
|
| { tag: "Array"; values: Inst[] }
|
||||||
|
| { tag: "Fn"; fn: Fn }
|
||||||
|
| { tag: "Param"; idx: number }
|
||||||
|
| { tag: "Index"; value: Inst; arg: Inst }
|
||||||
|
| { tag: "Slice"; value: Inst; begin: Inst | null; end: Inst | null }
|
||||||
|
| { tag: "Call"; callee: Inst; args: Inst[] }
|
||||||
|
| { tag: "Alloca" }
|
||||||
|
| { tag: "Load"; source: Inst }
|
||||||
|
| { tag: "Store"; target: Inst; source: Inst }
|
||||||
|
| { tag: "Jump"; target: BasicBlock }
|
||||||
|
| { tag: "Branch"; cond: Inst; truthy: BasicBlock; falsy: BasicBlock }
|
||||||
|
| { tag: "Return"; source: Inst }
|
||||||
|
| { tag: "Not"; source: Inst }
|
||||||
|
| { tag: "Negate"; source: Inst }
|
||||||
|
| { tag: BinaryOp; left: Inst; right: Inst }
|
||||||
|
| { tag: "DebugPrint"; args: Inst[] };
|
||||||
|
|
||||||
|
export type BinaryOp =
|
||||||
|
| "Eq"
|
||||||
|
| "Ne"
|
||||||
|
| "Lt"
|
||||||
|
| "Gt"
|
||||||
|
| "Lte"
|
||||||
|
| "Gte"
|
||||||
|
| "BitOr"
|
||||||
|
| "BitXor"
|
||||||
|
| "BitAnd"
|
||||||
|
| "Shl"
|
||||||
|
| "Shr"
|
||||||
|
| "Add"
|
||||||
|
| "Sub"
|
||||||
|
| "Mul"
|
||||||
|
| "Div"
|
||||||
|
| "Rem";
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import * as mir from "./middle.ts";
|
import * as mir from "./mir.ts";
|
||||||
|
|
||||||
export class FnInterpreter {
|
export class FnInterpreter {
|
||||||
private regs = new Map<mir.Inst, Val>();
|
private regs = new Map<mir.Inst, Val>();
|
||||||
@ -17,6 +17,12 @@ export class FnInterpreter {
|
|||||||
const inst = this.bb.insts[this.instIdx];
|
const inst = this.bb.insts[this.instIdx];
|
||||||
this.instIdx += 1;
|
this.instIdx += 1;
|
||||||
|
|
||||||
|
// console.log(
|
||||||
|
// `[${this.instIdx.toString().padStart(2, " ")}] ${
|
||||||
|
// inst.pretty(new mir.PrettyCx())
|
||||||
|
// }`,
|
||||||
|
// );
|
||||||
|
|
||||||
const k = inst.kind;
|
const k = inst.kind;
|
||||||
switch (k.tag) {
|
switch (k.tag) {
|
||||||
case "Error":
|
case "Error":
|
||||||
@ -43,6 +49,84 @@ export class FnInterpreter {
|
|||||||
case "Param":
|
case "Param":
|
||||||
this.regs.set(inst, this.args[k.idx]);
|
this.regs.set(inst, this.args[k.idx]);
|
||||||
break;
|
break;
|
||||||
|
case "Index": {
|
||||||
|
const idx = this.regs.get(k.arg)!;
|
||||||
|
if (idx.kind.tag !== "Int") {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
const value = this.regs.get(k.value)!;
|
||||||
|
if (value.kind.tag === "Array") {
|
||||||
|
if (idx.kind.value >= value.kind.values.length) {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
this.regs.set(inst, value.kind.values[idx.kind.value]);
|
||||||
|
} else if (value.kind.tag === "Slice") {
|
||||||
|
if (value.kind.value.kind.tag !== "Array") {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
const values = value.kind.value.kind.values;
|
||||||
|
const begin = value.kind.value;
|
||||||
|
const end = value.kind.end;
|
||||||
|
if (
|
||||||
|
begin.kind.tag !== "Int" || end.kind.tag !== "Int"
|
||||||
|
) {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
begin.kind.value + idx.kind.value < 0 ||
|
||||||
|
end.kind.value + idx.kind.value >= values.length
|
||||||
|
) {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
this.regs.set(
|
||||||
|
inst,
|
||||||
|
values[begin.kind.value + idx.kind.value],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "Slice": {
|
||||||
|
const begin = k.begin && this.regs.get(k.begin)!;
|
||||||
|
const end = k.end && this.regs.get(k.end)!;
|
||||||
|
if (
|
||||||
|
begin && begin.kind.tag !== "Int" ||
|
||||||
|
end && end.kind.tag !== "Int"
|
||||||
|
) {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
const value = this.regs.get(k.value)!;
|
||||||
|
if (value.kind.tag !== "Array") {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
begin && begin.kind.tag === "Int" &&
|
||||||
|
begin.kind.value < 0
|
||||||
|
) {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
end && end.kind.tag === "Int" &&
|
||||||
|
end.kind.value >= value.kind.values.length
|
||||||
|
) {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
this.regs.set(
|
||||||
|
inst,
|
||||||
|
new Val({
|
||||||
|
tag: "Slice",
|
||||||
|
value,
|
||||||
|
begin: begin ?? new Val({ tag: "Int", value: 0 }),
|
||||||
|
end: end ??
|
||||||
|
new Val({
|
||||||
|
tag: "Int",
|
||||||
|
value: value.kind.values.length,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "Call": {
|
case "Call": {
|
||||||
const fn = this.regs.get(k.callee);
|
const fn = this.regs.get(k.callee);
|
||||||
if (!fn || fn.kind.tag !== "Fn") {
|
if (!fn || fn.kind.tag !== "Fn") {
|
||||||
@ -226,6 +310,19 @@ class Val {
|
|||||||
return `${k.value}`;
|
return `${k.value}`;
|
||||||
case "Ptr":
|
case "Ptr":
|
||||||
return `<pointer>`;
|
return `<pointer>`;
|
||||||
|
case "Slice":
|
||||||
|
if (k.value.kind.tag !== "Array") {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
if (k.begin.kind.tag !== "Int" || k.end.kind.tag !== "Int") {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
return `[${
|
||||||
|
k.value.kind.values.slice(
|
||||||
|
k.begin.kind.value,
|
||||||
|
k.end.kind.value,
|
||||||
|
).map((v) => v.pretty()).join(", ")
|
||||||
|
}]`;
|
||||||
case "Array":
|
case "Array":
|
||||||
return `[${k.values.map((v) => v.pretty()).join(", ")}]`;
|
return `[${k.values.map((v) => v.pretty()).join(", ")}]`;
|
||||||
case "Fn":
|
case "Fn":
|
||||||
@ -243,5 +340,6 @@ type ValKind =
|
|||||||
| { tag: "Int"; value: number }
|
| { tag: "Int"; value: number }
|
||||||
| { tag: "Bool"; value: boolean }
|
| { tag: "Bool"; value: boolean }
|
||||||
| { tag: "Ptr"; mutable: boolean; value: Val }
|
| { tag: "Ptr"; mutable: boolean; value: Val }
|
||||||
|
| { tag: "Slice"; value: Val; begin: Val; end: Val }
|
||||||
| { tag: "Array"; values: Val[] }
|
| { tag: "Array"; values: Val[] }
|
||||||
| { tag: "Fn"; fn: mir.Fn };
|
| { tag: "Fn"; fn: mir.Fn };
|
||||||
|
|||||||
72
src/ty.ts
72
src/ty.ts
@ -59,6 +59,39 @@ export class Ty {
|
|||||||
if (this.is("Bool")) {
|
if (this.is("Bool")) {
|
||||||
return other.is("Bool");
|
return other.is("Bool");
|
||||||
}
|
}
|
||||||
|
if (this.is("Ptr")) {
|
||||||
|
if (!other.is("Ptr")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!this.kind.ty.compatibleWith(other.kind.ty)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (this.is("Array")) {
|
||||||
|
if (!other.is("Array")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!this.kind.ty.compatibleWith(other.kind.ty)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.kind.length !== other.kind.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (this.is("Slice")) {
|
||||||
|
if (!other.is("Slice")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!this.kind.ty.compatibleWith(other.kind.ty)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (this.is("Range")) {
|
||||||
|
return other.is("Range");
|
||||||
|
}
|
||||||
if (this.is("Fn")) {
|
if (this.is("Fn")) {
|
||||||
if (!other.is("Fn")) {
|
if (!other.is("Fn")) {
|
||||||
return false;
|
return false;
|
||||||
@ -90,36 +123,41 @@ export class Ty {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pretty(): string {
|
pretty(): string {
|
||||||
if (this.is("Error")) {
|
switch (this.kind.tag) {
|
||||||
|
case "Error":
|
||||||
return "<error>";
|
return "<error>";
|
||||||
}
|
case "Void":
|
||||||
if (this.is("Void")) {
|
|
||||||
return "void";
|
return "void";
|
||||||
}
|
case "Int":
|
||||||
if (this.is("Int")) {
|
|
||||||
return "int";
|
return "int";
|
||||||
}
|
case "Bool":
|
||||||
if (this.is("Bool")) {
|
|
||||||
return "bool";
|
return "bool";
|
||||||
}
|
case "Ptr":
|
||||||
if (this.is("Ptr")) {
|
|
||||||
return `*${this.kind.ty.pretty()}`;
|
return `*${this.kind.ty.pretty()}`;
|
||||||
}
|
case "PtrMut":
|
||||||
if (this.is("PtrMut")) {
|
|
||||||
return `*mut ${this.kind.ty.pretty()}`;
|
return `*mut ${this.kind.ty.pretty()}`;
|
||||||
}
|
case "Array":
|
||||||
if (this.is("Fn")) {
|
return `[${this.kind.ty.pretty()}; ${this.kind.length}]`;
|
||||||
|
case "Slice":
|
||||||
|
return `[${this.kind.ty.pretty()}]`;
|
||||||
|
case "Range":
|
||||||
|
return `Range`;
|
||||||
|
case "Fn":
|
||||||
return `fn (${
|
return `fn (${
|
||||||
this.kind.params.map((param) => param.pretty()).join(", ")
|
this.kind.params.map((param) => param.pretty()).join(", ")
|
||||||
}) -> ${this.kind.retTy.pretty()}`;
|
}) -> ${this.kind.retTy.pretty()}`;
|
||||||
|
case "FnStmt":
|
||||||
|
if (!this.kind.ty.is("Fn")) {
|
||||||
|
throw new Error();
|
||||||
}
|
}
|
||||||
if (this.is("FnStmt")) {
|
|
||||||
if (!this.kind.ty.is("Fn")) throw new Error();
|
|
||||||
return `fn ${this.kind.stmt.kind.ident}(${
|
return `fn ${this.kind.stmt.kind.ident}(${
|
||||||
this.kind.ty.kind.params.map((param) => param.pretty()).join(
|
this.kind.ty.kind.params.map((param) => param.pretty())
|
||||||
|
.join(
|
||||||
", ",
|
", ",
|
||||||
)
|
)
|
||||||
}) -> ${this.kind.ty.kind.retTy.pretty()}`;
|
}) -> ${this.kind.ty.kind.retTy.pretty()}`;
|
||||||
|
default:
|
||||||
|
this.kind satisfies never;
|
||||||
}
|
}
|
||||||
throw new Error("unhandled");
|
throw new Error("unhandled");
|
||||||
}
|
}
|
||||||
@ -133,5 +171,7 @@ export type TyKind =
|
|||||||
| { tag: "Ptr"; ty: Ty }
|
| { tag: "Ptr"; ty: Ty }
|
||||||
| { tag: "PtrMut"; ty: Ty }
|
| { tag: "PtrMut"; ty: Ty }
|
||||||
| { tag: "Array"; ty: Ty; length: number }
|
| { tag: "Array"; ty: Ty; length: number }
|
||||||
|
| { tag: "Slice"; ty: Ty }
|
||||||
|
| { tag: "Range" }
|
||||||
| { tag: "Fn"; params: Ty[]; retTy: Ty }
|
| { tag: "Fn"; params: Ty[]; retTy: Ty }
|
||||||
| { tag: "FnStmt"; ty: Ty; stmt: ast.NodeWithKind<"FnStmt"> };
|
| { tag: "FnStmt"; ty: Ty; stmt: ast.NodeWithKind<"FnStmt"> };
|
||||||
|
|||||||
@ -1,6 +1,30 @@
|
|||||||
|
|
||||||
fn main()
|
fn main()
|
||||||
{
|
{
|
||||||
let array = [1, 2, 3];
|
let array: [int; 3] = [1, 2, 3];
|
||||||
|
|
||||||
|
// let a = 4;
|
||||||
|
// let b = a;
|
||||||
|
// array[0] = a;
|
||||||
|
// print_int(array[0]);
|
||||||
|
|
||||||
|
// let elem: int = array[0];
|
||||||
|
// // e xpect: 1
|
||||||
|
// print_int(elem);
|
||||||
|
//
|
||||||
|
// let ptr_to_array: *[int; 3] = &array;
|
||||||
|
// // e xpect: 2
|
||||||
|
// print_int(ptr_to_array.*[1]);
|
||||||
|
|
||||||
|
// let slice: *[int] = &array[..];
|
||||||
|
// e xpect: 2
|
||||||
|
// print_int(slice.*[2]);
|
||||||
|
|
||||||
|
// let slice_1: *mut [int] = &mut array[1..3];
|
||||||
|
// slice_1.*[0] = 4;
|
||||||
|
// // e xpect: 4
|
||||||
|
// print_int(array[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// vim: syntax=rust commentstring=//\ %s
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user