work on arrays and slices
All checks were successful
Check / Explore-Gitea-Actions (push) Successful in 8s

This commit is contained in:
sfja 2026-03-16 01:04:35 +01:00
parent 284627e0d2
commit 1c92a3c077
9 changed files with 612 additions and 239 deletions

View File

@ -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 {
private static idCounter = 0;
@ -82,17 +90,25 @@ export class Node {
return visit();
case "ArrayExpr":
return visit(...k.values);
case "IndexExpr":
return visit(k.expr, k.arg);
case "CallExpr":
return visit(k.expr, ...k.args);
case "UnaryExpr":
return visit(k.expr);
case "BinaryExpr":
return visit(k.left, k.right);
case "RangeExpr":
return visit(k.begin, k.end);
case "IdentTy":
return visit();
case "PtrTy":
case "PtrMutTy":
return visit(k.ty);
case "ArrayTy":
return visit(k.ty, k.length);
case "SliceTy":
return visit(k.ty);
}
k satisfies never;
}
@ -118,11 +134,20 @@ export type NodeKind =
| { tag: "IdentExpr"; ident: string }
| { tag: "IntExpr"; value: number }
| { tag: "ArrayExpr"; values: Node[] }
| { tag: "IndexExpr"; expr: Node; arg: Node }
| { tag: "CallExpr"; expr: Node; args: Node[] }
| { tag: "UnaryExpr"; op: UnaryOp; expr: 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: "PtrTy" | "PtrMutTy"; ty: Node };
| { tag: "PtrTy" | "PtrMutTy"; ty: Node }
| { tag: "ArrayTy"; ty: Node; length: Node }
| { tag: "SliceTy"; ty: Node };
export type UnaryOp =
| "Not"
@ -151,6 +176,8 @@ export type BinaryOp =
| "Divide"
| "Remainder";
export type RangeLimit = "Inclusive" | "Exclusive";
export interface Visitor {
visit(node: Node): void | "break";
}

View File

@ -32,11 +32,13 @@ export function printDiagnostics(
`${" ".repeat(lineNumberText.length)}%c|\n` +
`${lineNumberText}|%c${lineText}\n` +
`${" ".repeat(lineNumberText.length)}%c|` +
`%c${"~".repeat(lineText.length)}%c`,
`%c${"~".repeat(lineText.length)}\n` +
`${" ".repeat(lineNumberText.length)}%c|%c`,
"color: cyan;",
"color: lightwhite;",
"color: cyan;",
`color: ${severityColor};`,
"color: cyan;",
"",
);
}

View File

@ -98,6 +98,28 @@ export class Checker {
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")) {
return this.checkCall(node);
}
@ -147,6 +169,20 @@ export class Checker {
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")) {
switch (node.kind.ident) {
case "void":
@ -169,6 +205,32 @@ export class Checker {
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`);
}
@ -241,7 +303,7 @@ export class Checker {
this.error(
node.kind.args[i].line,
`type '${args[i].pretty()}' not compatible with type '${
params[i]
params[i].pretty()
}', for argument ${i}`,
);
if (calleeTy.is("FnStmt")) {

View File

@ -12,6 +12,7 @@ export class Parser {
private toks: Tok[];
private idx = 0;
private currentLine = 1;
private prevTok: Tok | null = null;
constructor(
private filename: string,
@ -130,7 +131,31 @@ export class Parser {
}
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 {
@ -206,7 +231,16 @@ export class Parser {
let expr = this.parseOperand();
while (true) {
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[] = [];
if (!this.test(")")) {
args.push(this.parseExpr());
@ -269,6 +303,16 @@ export class Parser {
const mutable = this.eat("mut");
const ty = this.parseTy();
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 {
this.mustEat("<type>");
throw new Error();
@ -291,6 +335,7 @@ export class Parser {
private error(message: string, loc: number): never {
printDiagnostics(this.filename, loc, "error", message, this.text);
throw new Error();
Deno.exit(1);
}
@ -303,6 +348,9 @@ export class Parser {
}
private step() {
if (!this.done) {
this.prevTok = this.current;
}
this.idx += 1;
if (!this.done) {
this.currentLine = this.current.line;
@ -331,7 +379,7 @@ export type Tok = { type: string; value: string; line: number };
const keywordPattern =
/^(?:fn)|(?:return)|(?:let)|(?:if)|(?:else)|(?:while)|(?:break)|(?:or)|(?:and)|(?:not)|(?:mut)$/;
const operatorPattern =
/((?:\->)|(?:==)|(?:!=)|(?:<=)|(?:>=)|(?:<<)|(?:>>)|[\n\(\)\{\}\[\]\,\.\;\:\!\=\<\>\&\^\|\+\-\*\/\%])/g;
/((?:\->)|(?:==)|(?:!=)|(?:<=)|(?:>=)|(?:<<)|(?:>>)|(?:\.\*)|(?:\.\.)|(?:\.\.=)|[\n\(\)\{\}\[\]\,\.\;\:\!\=\<\>\&\^\|\+\-\*\/\%])/g;
export function tokenize(text: string): Tok[] {
return text
@ -359,7 +407,7 @@ export function tokenize(text: string): Tok[] {
: tok
)
.map((tok) =>
/^0|(?:[1-9][0-9]*)$/.test(tok.value)
/^(?:0|(?:[1-9][0-9]*))$/.test(tok.value)
? { ...tok, type: "int" }
: tok
);

View File

@ -1,6 +1,7 @@
import * as ast from "./ast.ts";
import { Checker, ResolveMap } from "./front/mod.ts";
import { Ty } from "./ty.ts";
import { BasicBlock, BinaryOp, Fn, Inst, InstKind } from "./mir.ts";
export class MiddleLowerer {
private fns = new Map<number, Fn>();
@ -50,7 +51,7 @@ class FnLowerer {
if (stmt.is("LetStmt")) {
const ty = this.checker.check(stmt.kind.param);
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.pushInst(Ty.Void, "Store", {
target: local,
@ -134,6 +135,43 @@ class FnLowerer {
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`);
}
@ -172,6 +210,12 @@ class FnLowerer {
.map((value) => this.lowerExpr(value));
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")) {
const ty = this.checker.check(expr);
const args = expr.kind.args
@ -228,6 +272,16 @@ class FnLowerer {
`${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(
`${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: "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
View 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";

View File

@ -1,4 +1,4 @@
import * as mir from "./middle.ts";
import * as mir from "./mir.ts";
export class FnInterpreter {
private regs = new Map<mir.Inst, Val>();
@ -17,6 +17,12 @@ export class FnInterpreter {
const inst = this.bb.insts[this.instIdx];
this.instIdx += 1;
// console.log(
// `[${this.instIdx.toString().padStart(2, " ")}] ${
// inst.pretty(new mir.PrettyCx())
// }`,
// );
const k = inst.kind;
switch (k.tag) {
case "Error":
@ -43,6 +49,84 @@ export class FnInterpreter {
case "Param":
this.regs.set(inst, this.args[k.idx]);
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": {
const fn = this.regs.get(k.callee);
if (!fn || fn.kind.tag !== "Fn") {
@ -226,6 +310,19 @@ class Val {
return `${k.value}`;
case "Ptr":
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":
return `[${k.values.map((v) => v.pretty()).join(", ")}]`;
case "Fn":
@ -243,5 +340,6 @@ type ValKind =
| { tag: "Int"; value: number }
| { tag: "Bool"; value: boolean }
| { tag: "Ptr"; mutable: boolean; value: Val }
| { tag: "Slice"; value: Val; begin: Val; end: Val }
| { tag: "Array"; values: Val[] }
| { tag: "Fn"; fn: mir.Fn };

100
src/ty.ts
View File

@ -59,6 +59,39 @@ export class Ty {
if (this.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 (!other.is("Fn")) {
return false;
@ -90,36 +123,41 @@ export class Ty {
}
pretty(): string {
if (this.is("Error")) {
return "<error>";
}
if (this.is("Void")) {
return "void";
}
if (this.is("Int")) {
return "int";
}
if (this.is("Bool")) {
return "bool";
}
if (this.is("Ptr")) {
return `*${this.kind.ty.pretty()}`;
}
if (this.is("PtrMut")) {
return `*mut ${this.kind.ty.pretty()}`;
}
if (this.is("Fn")) {
return `fn (${
this.kind.params.map((param) => param.pretty()).join(", ")
}) -> ${this.kind.retTy.pretty()}`;
}
if (this.is("FnStmt")) {
if (!this.kind.ty.is("Fn")) throw new Error();
return `fn ${this.kind.stmt.kind.ident}(${
this.kind.ty.kind.params.map((param) => param.pretty()).join(
", ",
)
}) -> ${this.kind.ty.kind.retTy.pretty()}`;
switch (this.kind.tag) {
case "Error":
return "<error>";
case "Void":
return "void";
case "Int":
return "int";
case "Bool":
return "bool";
case "Ptr":
return `*${this.kind.ty.pretty()}`;
case "PtrMut":
return `*mut ${this.kind.ty.pretty()}`;
case "Array":
return `[${this.kind.ty.pretty()}; ${this.kind.length}]`;
case "Slice":
return `[${this.kind.ty.pretty()}]`;
case "Range":
return `Range`;
case "Fn":
return `fn (${
this.kind.params.map((param) => param.pretty()).join(", ")
}) -> ${this.kind.retTy.pretty()}`;
case "FnStmt":
if (!this.kind.ty.is("Fn")) {
throw new Error();
}
return `fn ${this.kind.stmt.kind.ident}(${
this.kind.ty.kind.params.map((param) => param.pretty())
.join(
", ",
)
}) -> ${this.kind.ty.kind.retTy.pretty()}`;
default:
this.kind satisfies never;
}
throw new Error("unhandled");
}
@ -133,5 +171,7 @@ export type TyKind =
| { tag: "Ptr"; ty: Ty }
| { tag: "PtrMut"; ty: Ty }
| { tag: "Array"; ty: Ty; length: number }
| { tag: "Slice"; ty: Ty }
| { tag: "Range" }
| { tag: "Fn"; params: Ty[]; retTy: Ty }
| { tag: "FnStmt"; ty: Ty; stmt: ast.NodeWithKind<"FnStmt"> };

View File

@ -1,6 +1,30 @@
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