From 54ee879b4547eb61ce61df8243d549f14c975ca7 Mon Sep 17 00:00:00 2001 From: sfja Date: Tue, 17 Mar 2026 00:06:43 +0100 Subject: [PATCH] progress on arrays and slices --- src/ast.ts | 8 +- src/front/check.ts | 94 ++++++++++------- src/front/parse.ts | 4 +- src/front/resolve.ts | 6 +- src/middle.ts | 69 ++++++------ src/mir.ts | 6 +- src/mir_interpreter.ts | 110 ++++++++++++++------ tests/{pointer.ethlang => _pointer.ethlang} | 0 tests/array.ethlang | 25 ++--- 9 files changed, 196 insertions(+), 126 deletions(-) rename tests/{pointer.ethlang => _pointer.ethlang} (100%) diff --git a/src/ast.ts b/src/ast.ts index 05f6b9d..5ff0085 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -93,9 +93,9 @@ export class Node { case "ArrayExpr": return visit(...k.values); case "IndexExpr": - return visit(k.expr, k.arg); + return visit(k.value, k.arg); case "CallExpr": - return visit(k.expr, ...k.args); + return visit(k.value, ...k.args); case "UnaryExpr": return visit(k.expr); case "BinaryExpr": @@ -136,8 +136,8 @@ 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: "IndexExpr"; value: Node; arg: Node } + | { tag: "CallExpr"; value: Node; args: Node[] } | { tag: "UnaryExpr"; op: UnaryOp; expr: Node; tok: string } | { tag: "BinaryExpr"; op: BinaryOp; left: Node; right: Node; tok: string } | { diff --git a/src/front/check.ts b/src/front/check.ts index 9019811..be4b08a 100644 --- a/src/front/check.ts +++ b/src/front/check.ts @@ -15,6 +15,24 @@ export class Tys { this.checker = new Checker(this, this.syms, this.reporter); } + fnStmt(node: ast.NodeWithKind<"FnStmt">): Ty { + if (this.nodeTys.has(node.id)) { + return this.nodeTys.get(node.id)!; + } + const ty = this.checker.checkFnStmt(node); + this.nodeTys.set(node.id, ty); + return ty; + } + + place(node: ast.Node): Ty { + if (this.nodeTys.has(node.id)) { + return this.nodeTys.get(node.id)!; + } + const ty = this.checker.checkExpr(node); + this.nodeTys.set(node.id, ty); + return ty; + } + expr(node: ast.Node): Ty { if (this.nodeTys.has(node.id)) { return this.nodeTys.get(node.id)!; @@ -32,13 +50,44 @@ class Checker { private reporter: FileReporter, ) {} + 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; + + k.body.visit({ + visit: (node) => { + if (node.is("ReturnStmt")) { + const ty = node.kind.expr + ? this.tys.expr(node.kind.expr) + : Ty.Void; + if (!ty.compatibleWith(retTy)) { + this.error( + node.loc, + `type '${ty.pretty()}' not compatible with return type '${retTy.pretty()}'`, + ); + this.info( + stmt.kind.retTy?.loc ?? stmt.loc, + `return type '${retTy}' defined here`, + ); + this.fail(); + } + } + }, + }); + + const ty = Ty.create("Fn", { params, retTy }); + return Ty.create("FnStmt", { stmt, ty }); + } + + checkPlace(node: ast.Node): Ty { + return this.checkExpr(node); + } + checkExpr(node: ast.Node): Ty { const k = node.kind; - if (node.is("FnStmt")) { - return this.checkFnStmt(node); - } - if (node.is("Param")) { const sym = this.syms.get(node); @@ -68,7 +117,7 @@ class Checker { if (node.is("IdentExpr")) { const sym = this.syms.get(node); if (sym.tag === "Fn") { - return this.tys.expr(sym.stmt); + return this.tys.fnStmt(sym.stmt); } if (sym.tag === "Bool") { return Ty.Bool; @@ -108,7 +157,7 @@ class Checker { } if (node.is("IndexExpr")) { - const exprTy = this.tys.expr(node.kind.expr); + const exprTy = this.tys.expr(node.kind.value); const argTy = this.tys.expr(node.kind.arg); if ( (exprTy.is("Array") || exprTy.is("Slice")) && @@ -243,39 +292,8 @@ class Checker { throw new Error(`'${k.tag}' not unhandled`); } - private 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; - - k.body.visit({ - visit: (node) => { - if (node.is("ReturnStmt")) { - const ty = node.kind.expr - ? this.tys.expr(node.kind.expr) - : Ty.Void; - if (!ty.compatibleWith(retTy)) { - this.error( - node.loc, - `type '${ty.pretty()}' not compatible with return type '${retTy.pretty()}'`, - ); - this.info( - stmt.kind.retTy?.loc ?? stmt.loc, - `return type '${retTy}' defined here`, - ); - this.fail(); - } - } - }, - }); - - const ty = Ty.create("Fn", { params, retTy }); - return Ty.create("FnStmt", { stmt, ty }); - } - private checkCall(node: ast.NodeWithKind<"CallExpr">): Ty { - const calleeTy = this.tys.expr(node.kind.expr); + const calleeTy = this.tys.expr(node.kind.value); const callableTy = calleeTy.is("Fn") ? calleeTy diff --git a/src/front/parse.ts b/src/front/parse.ts index b29c21e..413475f 100644 --- a/src/front/parse.ts +++ b/src/front/parse.ts @@ -239,7 +239,7 @@ export class Parser { } else if (this.eat("[")) { const arg = this.parseExpr(); this.mustEat("]"); - expr = ast.Node.create(loc, "IndexExpr", { expr, arg }); + expr = ast.Node.create(loc, "IndexExpr", { value: expr, arg }); } else if (this.eat("(")) { const args: ast.Node[] = []; if (!this.test(")")) { @@ -252,7 +252,7 @@ export class Parser { } } this.mustEat(")"); - expr = ast.Node.create(loc, "CallExpr", { expr, args }); + expr = ast.Node.create(loc, "CallExpr", { value: expr, args }); } else { break; } diff --git a/src/front/resolve.ts b/src/front/resolve.ts index dad8c15..3cec817 100644 --- a/src/front/resolve.ts +++ b/src/front/resolve.ts @@ -4,14 +4,14 @@ import { builtins } from "./builtins.ts"; export class Syms { constructor( - private resols: Map, + private symMap: Map, ) {} get(node: ast.Node): Sym { - if (!this.resols.has(node.id)) { + if (!this.symMap.has(node.id)) { throw new Error(`'${node.kind.tag}' not resolved`); } - return this.resols.get(node.id)!; + return this.symMap.get(node.id)!; } } diff --git a/src/middle.ts b/src/middle.ts index ea9dbe2..cf3d4fd 100644 --- a/src/middle.ts +++ b/src/middle.ts @@ -7,7 +7,7 @@ export class MiddleLowerer { private fns = new Map(); constructor( - private resols: Syms, + private syms: Syms, private tys: Tys, ) {} @@ -15,7 +15,7 @@ export class MiddleLowerer { if (this.fns.has(stmt.id)) { return this.fns.get(stmt.id)!; } - const fn = new FnLowerer(this, this.resols, this.tys, stmt).lower(); + const fn = new FnLowerer(this, this.syms, this.tys, stmt).lower(); this.fns.set(stmt.id, fn); return fn; } @@ -28,13 +28,13 @@ class FnLowerer { constructor( private lowerer: MiddleLowerer, - private resols: Syms, + private syms: Syms, private tys: Tys, private stmt: ast.FnStmt, ) {} lower(): Fn { - const ty = this.tys.expr(this.stmt); + const ty = this.tys.fnStmt(this.stmt); this.lowerBlock(this.stmt.kind.body.as("Block")); this.pushInst(Ty.Void, "Return", { source: this.makeVoid() }); this.bbs[0].insts.unshift(...this.allocs); @@ -51,7 +51,10 @@ class FnLowerer { if (stmt.is("LetStmt")) { const ty = this.tys.expr(stmt.kind.param); const expr = this.lowerExpr(stmt.kind.expr); - const local = new Inst(Ty.create("Ptr", { ty }), { tag: "Alloca" }); + const local = new Inst( + Ty.create("PtrMut", { ty }), + { tag: "Alloca" }, + ); this.allocs.push(local); this.pushInst(Ty.Void, "Store", { target: local, @@ -119,8 +122,9 @@ class FnLowerer { } private lowerPlace(place: ast.Node): Inst { + const ty = this.tys.place(place); if (place.is("IdentExpr")) { - const sym = this.resols.get(place); + const sym = this.syms.get(place); if (sym.tag === "Let") { const local = this.localMap.get(sym.param.id); if (!local) { @@ -128,27 +132,36 @@ class FnLowerer { } return local; } + if (sym.tag === "FnParam") { + return this.lowerExpr(place); + } throw new Error(`'${sym.tag}' not handled`); } if (place.is("UnaryExpr") && place.kind.op === "Deref") { - return this.lowerExpr(place.kind.expr); + const source = this.lowerPlace(place.kind.expr); + return this.pushInst( + Ty.create("PtrMut", { ty }), + "Load", + { source }, + ); } if (place.is("IndexExpr")) { - const exprTy = this.tys.expr(place.kind.expr); - if (!exprTy.is("Array") && !exprTy.is("Slice")) { - throw new Error(exprTy.pretty()); - } + const value = place.kind.value; + const valueTy = this.tys.expr(value); const arg = place.kind.arg; const argTy = this.tys.expr(arg); - const exprInst = this.lowerExpr(place.kind.expr); + if (!valueTy.is("Array") && !valueTy.is("Slice")) { + throw new Error(); + } + const valueInst = this.lowerPlace(place.kind.value); if (argTy.is("Int")) { const argInst = this.lowerExpr(arg); return this.pushInst( - Ty.create("PtrMut", { ty: exprTy.kind.ty }), + Ty.create("PtrMut", { ty: valueTy.kind.ty }), "Index", - { value: exprInst, arg: argInst }, + { base: valueInst, offset: argInst }, ); } if (argTy.is("Range")) { @@ -161,10 +174,10 @@ class FnLowerer { this.lowerExpr(arg.kind.end); return this.pushInst( Ty.create("PtrMut", { - ty: Ty.create("Slice", { ty: exprTy.kind.ty }), + ty: Ty.create("Slice", { ty: valueTy.kind.ty }), }), "Slice", - { value: exprInst, begin, end }, + { value: valueInst, begin, end }, ); } throw new Error( @@ -176,8 +189,9 @@ class FnLowerer { } private lowerExpr(expr: ast.Node): Inst { + const ty = this.tys.expr(expr); if (expr.is("IdentExpr")) { - const sym = this.resols.get(expr); + const sym = this.syms.get(expr); if (sym.tag === "Fn") { const fn = this.lowerer.lowerFn(sym.stmt); return this.pushInst(fn.ty, "Fn", { fn }); @@ -190,11 +204,8 @@ class FnLowerer { throw new Error("handle elsewhere"); } if (sym.tag === "Let") { - const local = this.localMap.get(sym.param.id); - if (!local) { - throw new Error(); - } - return this.pushInst(local.ty, "Load", { source: local }); + const source = this.lowerPlace(expr); + return this.pushInst(ty, "Load", { source }); } if (sym.tag === "Bool") { return this.pushInst(Ty.Bool, "Bool", { value: sym.value }); @@ -211,18 +222,16 @@ class FnLowerer { return this.pushInst(ty, "Array", { values }); } if (expr.is("IndexExpr")) { - const ty = this.tys.expr(expr.kind.expr); - const arg = this.lowerExpr(expr.kind.arg); - const value = this.lowerExpr(expr.kind.expr); - return this.pushInst(ty, "Index", { value, arg }); + const source = this.lowerPlace(expr); + return this.pushInst(ty, "Load", { source }); } if (expr.is("CallExpr")) { const ty = this.tys.expr(expr); const args = expr.kind.args .map((arg) => this.lowerExpr(arg)); - if (expr.kind.expr.is("IdentExpr")) { - const sym = this.resols.get(expr.kind.expr); + if (expr.kind.value.is("IdentExpr")) { + const sym = this.syms.get(expr.kind.value); if (sym.tag === "Builtin") { if (sym.id === "__add") { const [left, right] = args; @@ -235,7 +244,7 @@ class FnLowerer { } } - const callee = this.lowerExpr(expr.kind.expr); + const callee = this.lowerExpr(expr.kind.value); return this.pushInst(ty, "Call", { callee, args }); } if (expr.is("UnaryExpr")) { @@ -260,7 +269,7 @@ class FnLowerer { if (expr.kind.op === "Ref" || expr.kind.op === "RefMut") { const place = expr.kind.expr; if (place.is("IdentExpr")) { - const sym = this.resols.get(place); + const sym = this.syms.get(place); if (sym.tag === "Let") { const local = this.localMap.get(sym.param.id); if (!local) { diff --git a/src/mir.ts b/src/mir.ts index 995335e..1ee4f5b 100644 --- a/src/mir.ts +++ b/src/mir.ts @@ -128,7 +128,7 @@ export class Inst { case "Param": return `${k.idx}`; case "Index": - return `${r(k.value)} [${r(k.arg)}]`; + return `${r(k.base)} &[${r(k.offset)}]`; case "Slice": return `${r(k.value)} [${k.begin ? r(k.begin) : ""}..${ k.end ? r(k.end) : "" @@ -144,7 +144,7 @@ export class Inst { case "Jump": return `bb${cx.bbId(k.target)}`; case "Branch": - return `if ${r(k.cond)} then bb${cx.bbId(k.truthy)} else bb${ + return `if ${r(k.cond)}: bb${cx.bbId(k.truthy)}, else: bb${ cx.bbId(k.falsy) }`; case "Return": @@ -184,7 +184,7 @@ export type InstKind = | { tag: "Array"; values: Inst[] } | { tag: "Fn"; fn: Fn } | { tag: "Param"; idx: number } - | { tag: "Index"; value: Inst; arg: Inst } + | { tag: "Index"; base: Inst; offset: Inst } | { tag: "Slice"; value: Inst; begin: Inst | null; end: Inst | null } | { tag: "Call"; callee: Inst; args: Inst[] } | { tag: "Alloca" } diff --git a/src/mir_interpreter.ts b/src/mir_interpreter.ts index ac2d16e..9dbfadd 100644 --- a/src/mir_interpreter.ts +++ b/src/mir_interpreter.ts @@ -13,13 +13,14 @@ export class FnInterpreter { } eval(): Val { + const cx = new mir.PrettyCx(); while (this.instIdx < this.bb.insts.length) { const inst = this.bb.insts[this.instIdx]; this.instIdx += 1; - // console.log( + // console.log(poin // `[${this.instIdx.toString().padStart(2, " ")}] ${ - // inst.pretty(new mir.PrettyCx()) + // inst.pretty(cx) // }`, // ); @@ -49,39 +50,70 @@ 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 "Index": { - const idx = this.regs.get(k.arg)!; - if (idx.kind.tag !== "Int") { + const offset = this.regs.get(k.offset)!; + if (offset.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) { + const base = this.regs.get(k.base)!; + if (base.kind.tag === "Ptr") { + const array = base.kind.value; + if (array.kind.tag !== "Array") { + console.log({ array }); 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 - ) { + if (offset.kind.value >= array.kind.values.length) { throw new Error(); } this.regs.set( inst, - values[begin.kind.value + idx.kind.value], + new Val({ + tag: "ArrayElemPtr", + values: array.kind.values, + idx: offset.kind.value, + mutable: base.kind.mutable, + }), ); + } else if (base.kind.tag === "Slice") { + throw new Error(); } else { throw new Error(); } @@ -96,7 +128,11 @@ export class FnInterpreter { ) { throw new Error(); } - const value = this.regs.get(k.value)!; + const ptr = this.regs.get(k.value)!; + if (ptr.kind.tag !== "Ptr") { + throw new Error(); + } + const value = ptr.kind.value; if (value.kind.tag !== "Array") { throw new Error(); } @@ -150,19 +186,29 @@ export class FnInterpreter { } case "Load": { const source = this.regs.get(k.source)!; - if (source.kind.tag !== "Ptr") { + if (source.kind.tag === "Ptr") { + this.regs.set(inst, source.kind.value); + } else if (source.kind.tag === "ArrayElemPtr") { + this.regs.set( + inst, + source.kind.values[source.kind.idx], + ); + } else { throw new Error(); } - this.regs.set(inst, source.kind.value); break; } case "Store": { const target = this.regs.get(k.target)!; - if (target.kind.tag !== "Ptr") { + if (target.kind.tag === "Ptr") { + const source = this.regs.get(k.source)!; + target.kind.value = source; + } else if (target.kind.tag === "ArrayElemPtr") { + const source = this.regs.get(k.source)!; + target.kind.values[target.kind.idx] = source; + } else { throw new Error(); } - const source = this.regs.get(k.source)!; - target.kind.value = source; break; } case "Jump": { @@ -309,6 +355,7 @@ class Val { case "Bool": return `${k.value}`; case "Ptr": + case "ArrayElemPtr": return ``; case "Slice": if (k.value.kind.tag !== "Array") { @@ -340,6 +387,7 @@ type ValKind = | { tag: "Int"; value: number } | { tag: "Bool"; value: boolean } | { tag: "Ptr"; mutable: boolean; value: Val } + | { tag: "ArrayElemPtr"; mutable: boolean; values: Val[]; idx: number } | { tag: "Slice"; value: Val; begin: Val; end: Val } | { tag: "Array"; values: Val[] } | { tag: "Fn"; fn: mir.Fn }; diff --git a/tests/pointer.ethlang b/tests/_pointer.ethlang similarity index 100% rename from tests/pointer.ethlang rename to tests/_pointer.ethlang diff --git a/tests/array.ethlang b/tests/array.ethlang index ac8eaed..8261ff4 100644 --- a/tests/array.ethlang +++ b/tests/array.ethlang @@ -3,25 +3,20 @@ fn main() { 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]; + // expect: 1 + print_int(elem); - // let elem: int = array[0]; - // // e xpect: 1 - // print_int(elem); - // - // let ptr_to_array: *[int; 3] = &array; + let ptr_to_array: *[int; 3] = &array; + // expect: 2 + print_int(ptr_to_array.*[1]); + + let slice: *[int] = &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; + // let slice_mut: *mut [int] = &mut array[1..3]; + // slice_mut.*[0] = 4; // // e xpect: 4 // print_int(array[1]); }