From 1c92a3c0778793d16fbad09aba1efca09ab5c0ec Mon Sep 17 00:00:00 2001 From: sfja Date: Mon, 16 Mar 2026 01:04:35 +0100 Subject: [PATCH] work on arrays and slices --- src/ast.ts | 29 ++++- src/diagnostics.ts | 4 +- src/front/check.ts | 64 ++++++++++- src/front/parse.ts | 56 ++++++++- src/middle.ts | 255 +++++++++-------------------------------- src/mir.ts | 217 +++++++++++++++++++++++++++++++++++ src/mir_interpreter.ts | 100 +++++++++++++++- src/ty.ts | 100 +++++++++++----- tests/array.ethlang | 26 ++++- 9 files changed, 612 insertions(+), 239 deletions(-) create mode 100644 src/mir.ts diff --git a/src/ast.ts b/src/ast.ts index 4ece706..9b6e15e 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -1,3 +1,11 @@ +export function create( + line: number, + tag: Tag, + kind: Omit, +): 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"; } diff --git a/src/diagnostics.ts b/src/diagnostics.ts index 4c5bf22..aa1769c 100644 --- a/src/diagnostics.ts +++ b/src/diagnostics.ts @@ -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;", "", ); } diff --git a/src/front/check.ts b/src/front/check.ts index 113eb85..3a4d00e 100644 --- a/src/front/check.ts +++ b/src/front/check.ts @@ -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")) { diff --git a/src/front/parse.ts b/src/front/parse.ts index 575b4f5..6d47653 100644 --- a/src/front/parse.ts +++ b/src/front/parse.ts @@ -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(""); 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 ); diff --git a/src/middle.ts b/src/middle.ts index 1152fa2..144272a 100644 --- a/src/middle.ts +++ b/src/middle.ts @@ -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(); @@ -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 { - private map = new Map(); - 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(); - private regIds = new IdMap(); - - 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"; diff --git a/src/mir.ts b/src/mir.ts new file mode 100644 index 0000000..995335e --- /dev/null +++ b/src/mir.ts @@ -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 { + private map = new Map(); + 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(); + private regIds = new IdMap(); + + 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"; diff --git a/src/mir_interpreter.ts b/src/mir_interpreter.ts index bb74c4a..ac2d16e 100644 --- a/src/mir_interpreter.ts +++ b/src/mir_interpreter.ts @@ -1,4 +1,4 @@ -import * as mir from "./middle.ts"; +import * as mir from "./mir.ts"; export class FnInterpreter { private regs = new Map(); @@ -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 ``; + 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 }; diff --git a/src/ty.ts b/src/ty.ts index 55ee356..05ab78e 100644 --- a/src/ty.ts +++ b/src/ty.ts @@ -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 ""; - } - 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 ""; + 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"> }; diff --git a/tests/array.ethlang b/tests/array.ethlang index ffe0489..ac8eaed 100644 --- a/tests/array.ethlang +++ b/tests/array.ethlang @@ -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 +