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

This commit is contained in:
sfja 2026-03-17 00:06:43 +01:00
parent 8b10163805
commit 54ee879b45
9 changed files with 196 additions and 126 deletions

View File

@ -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 }
| {

View File

@ -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

View File

@ -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;
}

View File

@ -4,14 +4,14 @@ import { builtins } from "./builtins.ts";
export class Syms {
constructor(
private resols: Map<number, Sym>,
private symMap: Map<number, Sym>,
) {}
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)!;
}
}

View File

@ -7,7 +7,7 @@ export class MiddleLowerer {
private fns = new Map<number, Fn>();
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) {

View File

@ -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" }

View File

@ -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") {
throw new Error();
}
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();
}
break;
}
case "Jump": {
@ -309,6 +355,7 @@ class Val {
case "Bool":
return `${k.value}`;
case "Ptr":
case "ArrayElemPtr":
return `<pointer>`;
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 };

View File

@ -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]);
}