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

This commit is contained in:
sfja 2026-03-17 16:00:49 +01:00
parent d2a49f3be0
commit 9691fc38cb
9 changed files with 184 additions and 121 deletions

View File

@ -28,7 +28,7 @@ export class Tys {
if (this.nodeTys.has(node.id)) { if (this.nodeTys.has(node.id)) {
return this.nodeTys.get(node.id)!; return this.nodeTys.get(node.id)!;
} }
const ty = this.checker.checkExpr(node); const ty = this.checker.checkPlace(node);
this.nodeTys.set(node.id, ty); this.nodeTys.set(node.id, ty);
return ty; return ty;
} }
@ -82,6 +82,14 @@ class Checker {
} }
checkPlace(node: ast.Node): Ty { checkPlace(node: ast.Node): Ty {
if (node.is("UnaryExpr")) {
if (node.kind.op === "Deref") {
const exprTy = this.checkPlace(node.kind.expr);
if (exprTy.is("Ptr") || exprTy.is("PtrMut")) {
return exprTy.kind.ty;
}
}
}
return this.checkExpr(node); return this.checkExpr(node);
} }
@ -157,7 +165,7 @@ class Checker {
} }
if (node.is("IndexExpr")) { if (node.is("IndexExpr")) {
const exprTy = this.tys.expr(node.kind.value); const exprTy = this.tys.place(node.kind.value);
const argTy = this.tys.expr(node.kind.arg); const argTy = this.tys.expr(node.kind.arg);
if ( if (
(exprTy.is("Array") || exprTy.is("Slice")) && (exprTy.is("Array") || exprTy.is("Slice")) &&
@ -198,6 +206,13 @@ class Checker {
} }
if (node.kind.op === "Deref") { if (node.kind.op === "Deref") {
if (exprTy.is("Ptr") || exprTy.is("PtrMut")) { if (exprTy.is("Ptr") || exprTy.is("PtrMut")) {
if (!exprTy.kind.ty.isSized()) {
this.error(
node.loc,
`cannot dereference unsized type '${exprTy.kind.ty.pretty()}' in an expression`,
);
this.fail();
}
return exprTy.kind.ty; return exprTy.kind.ty;
} }
} }

View File

@ -18,7 +18,7 @@ export class Parser {
private text: string, private text: string,
private reporter: FileReporter, private reporter: FileReporter,
) { ) {
this.toks = tokenize(text); this.toks = tokenize(this.text, this.reporter);
} }
parseFile(): ast.Node { parseFile(): ast.Node {
@ -402,7 +402,7 @@ const keywordPattern =
const operatorPattern2 = const operatorPattern2 =
/((?:\->)|(?:==)|(?:!=)|(?:<=)|(?:>=)|(?:<<)|(?:>>)|(?:\.\*)|(?:\.\.)|(?:\.\.=)|[\n\(\)\{\}\[\]\,\.\;\:\!\=\<\>\&\^\|\+\-\*\/\%])/g; /((?:\->)|(?:==)|(?:!=)|(?:<=)|(?:>=)|(?:<<)|(?:>>)|(?:\.\*)|(?:\.\.)|(?:\.\.=)|[\n\(\)\{\}\[\]\,\.\;\:\!\=\<\>\&\^\|\+\-\*\/\%])/g;
export function tokenize(text: string): Tok[] { export function tokenize(text: string, reporter: FileReporter): Tok[] {
return new Lexer() return new Lexer()
.add(/[ \t\r\n]+/, (_) => null) .add(/[ \t\r\n]+/, (_) => null)
.add(/\/\/[^\n]*/, (_) => null) .add(/\/\/[^\n]*/, (_) => null)
@ -415,6 +415,8 @@ export function tokenize(text: string): Tok[] {
return { type: "int", value, loc }; return { type: "int", value, loc };
}) })
.add(/./, (loc, value) => { .add(/./, (loc, value) => {
const escapedChar = JSON.stringify(value[0]).slice(1, -1);
reporter.error(loc, `illegal character '${escapedChar}'`);
return null; return null;
}) })
.lex(text); .lex(text);

View File

@ -2,6 +2,7 @@ import * as ast from "./ast.ts";
import { Reporter } from "./diagnostics.ts"; import { Reporter } from "./diagnostics.ts";
import * as front from "./front/mod.ts"; import * as front from "./front/mod.ts";
import * as middle from "./middle.ts"; import * as middle from "./middle.ts";
import * as mir from "./mir.ts";
import { FnInterpreter } from "./mir_interpreter.ts"; import { FnInterpreter } from "./mir_interpreter.ts";
const reporter = new Reporter(); const reporter = new Reporter();
@ -39,8 +40,56 @@ if (!mainFn) {
const m = new middle.MiddleLowerer(syms, tys); const m = new middle.MiddleLowerer(syms, tys);
const mainMiddleFn = m.lowerFn(mainFn); const mainMiddleFn = m.lowerFn(mainFn);
if (!Deno.args.includes("--test")) { function printMirFn(fn: mir.Fn) {
console.log(mainMiddleFn.pretty()); const pretty = fn.pretty();
const [lines, colors] = pretty
.split("\n")
.map<[string, string[]]>((line) => [line, []])
.map<[string, string[]]>(([line, colors]) => {
line = line.replace(/%(\d+)/g, "__%$1__");
if (/^\s*__%\d+__ \(.*?\) =/.test(line)) {
line = line.replace(
/__%(\d+)__ \((.*?)\) =/g,
"%c%$1 %c($2)%c =",
);
colors.push("color: lightblue;", "color: gray;", "");
}
if (/[A-Z][a-zA-Z]+/.test(line)) {
line = line.replace(/([A-Z][a-zA-Z]+)/, "%c$1%c");
colors.push("color: red;", "");
}
if (/\bptr\b/.test(line)) {
line = line.replace(/\b(ptr)\b/, "%c$1%c");
colors.push("color: gray;", "");
}
while (/__%\d+__/.test(line)) {
line = line.replace(/__%(\d+)__/, "%c%$1%c");
colors.push("color: lightblue;", "");
}
return [line, colors];
})
.reduce<[string[], string[]]>(
([linesAcc, colorsAcc], [line, colors]) => {
linesAcc.push(line);
colorsAcc.push(...colors);
return [linesAcc, colorsAcc];
},
[[], []],
);
const text = lines.join("\n");
console.log(text + "%c", ...colors, "");
}
if (Deno.args.includes("--print-mir")) {
for (const fn of m.allFns()) {
printMirFn(fn);
}
}
if (!reporter.ok()) {
reporter.printAll();
Deno.exit(1);
} }
new FnInterpreter(mainMiddleFn, []).eval(); new FnInterpreter(mainMiddleFn, []).eval();

View File

@ -19,6 +19,10 @@ export class MiddleLowerer {
this.fns.set(stmt.id, fn); this.fns.set(stmt.id, fn);
return fn; return fn;
} }
allFns(): Fn[] {
return this.fns.values().toArray();
}
} }
class FnLowerer { class FnLowerer {
@ -50,19 +54,7 @@ class FnLowerer {
private lowerStmt(stmt: ast.Node) { private lowerStmt(stmt: ast.Node) {
if (stmt.is("LetStmt")) { if (stmt.is("LetStmt")) {
const ty = this.tys.expr(stmt.kind.param); return this.lowerLetStmt(stmt);
const expr = this.lowerExpr(stmt.kind.expr);
const local = new Inst(
Ty.create("PtrMut", { ty }),
{ tag: "Alloca" },
);
this.allocs.push(local);
this.pushInst(Ty.Void, "Store", {
target: local,
source: expr,
});
this.localMap.set(stmt.kind.param.id, local);
return;
} }
if (stmt.is("ReturnStmt")) { if (stmt.is("ReturnStmt")) {
const source = stmt.kind.expr const source = stmt.kind.expr
@ -159,10 +151,7 @@ class FnLowerer {
return; return;
} }
if (stmt.is("AssignStmt")) { if (stmt.is("AssignStmt")) {
const source = this.lowerExpr(stmt.kind.expr); return this.lowerAssignStmt(stmt);
const target = this.lowerPlace(stmt.kind.place);
this.pushInst(Ty.Void, "Store", { target, source });
return;
} }
if (stmt.is("ExprStmt")) { if (stmt.is("ExprStmt")) {
this.lowerExpr(stmt.kind.expr); this.lowerExpr(stmt.kind.expr);
@ -171,7 +160,30 @@ class FnLowerer {
throw new Error(`'${stmt.kind.tag}' not handled`); throw new Error(`'${stmt.kind.tag}' not handled`);
} }
private lowerLetStmt(stmt: ast.NodeWithKind<"LetStmt">) {
const ty = this.tys.expr(stmt.kind.param);
const expr = this.lowerExpr(stmt.kind.expr);
const local = new Inst(
Ty.create("PtrMut", { ty }),
{ tag: "Alloca" },
);
this.allocs.push(local);
this.pushInst(Ty.Void, "Store", {
target: local,
source: expr,
});
this.localMap.set(stmt.kind.param.id, local);
}
private lowerAssignStmt(stmt: ast.NodeWithKind<"AssignStmt">) {
const source = this.lowerExpr(stmt.kind.expr);
const target = this.lowerPlace(stmt.kind.place);
this.pushInst(Ty.Void, "Store", { target, source });
}
private lowerPlace(place: ast.Node): Inst { private lowerPlace(place: ast.Node): Inst {
// evaluate to most direct pointer
const ty = this.tys.place(place); const ty = this.tys.place(place);
if (place.is("IdentExpr")) { if (place.is("IdentExpr")) {
const sym = this.syms.get(place); const sym = this.syms.get(place);
@ -189,17 +201,18 @@ class FnLowerer {
} }
if (place.is("UnaryExpr") && place.kind.op === "Deref") { if (place.is("UnaryExpr") && place.kind.op === "Deref") {
const source = this.lowerPlace(place.kind.expr); const source = this.lowerExpr(place.kind.expr);
return this.pushInst( // return this.pushInst(
Ty.create("PtrMut", { ty }), // Ty.create("PtrMut", { ty }),
"Load", // "Load",
{ source }, // { source },
); // );
return source;
} }
if (place.is("IndexExpr")) { if (place.is("IndexExpr")) {
const value = place.kind.value; const value = place.kind.value;
const valueTy = this.tys.expr(value); const valueTy = this.tys.place(value);
const arg = place.kind.arg; const arg = place.kind.arg;
const argTy = this.tys.expr(arg); const argTy = this.tys.expr(arg);
if (!valueTy.is("Array") && !valueTy.is("Slice")) { if (!valueTy.is("Array") && !valueTy.is("Slice")) {
@ -210,7 +223,7 @@ class FnLowerer {
const argInst = this.lowerExpr(arg); const argInst = this.lowerExpr(arg);
return this.pushInst( return this.pushInst(
Ty.create("PtrMut", { ty: valueTy.kind.ty }), Ty.create("PtrMut", { ty: valueTy.kind.ty }),
"Index", "GetElemPtr",
{ base: valueInst, offset: argInst }, { base: valueInst, offset: argInst },
); );
} }

View File

@ -100,7 +100,7 @@ export class Inst {
pretty(cx: PrettyCx): string { pretty(cx: PrettyCx): string {
const valueless = ["Store", "Jump", "Branch", "Return", "DebugPrint"]; const valueless = ["Store", "Jump", "Branch", "Return", "DebugPrint"];
const valueType = `%${cx.regId(this)} = ${this.ty.pretty()} `; const valueType = `%${cx.regId(this)} (${this.ty.pretty()}) = `;
return `${ return `${
!valueless.includes(this.kind.tag) ? valueType : "" !valueless.includes(this.kind.tag) ? valueType : ""
}${this.kind.tag} ${this.prettyArgs(cx)}`; }${this.kind.tag} ${this.prettyArgs(cx)}`;
@ -127,10 +127,10 @@ export class Inst {
return `${k.fn.stmt.kind.ident}`; return `${k.fn.stmt.kind.ident}`;
case "Param": case "Param":
return `${k.idx}`; return `${k.idx}`;
case "Index": case "GetElemPtr":
return `${r(k.base)} &[${r(k.offset)}]`; return `&[ptr ${r(k.base)}][${r(k.offset)}]`;
case "Slice": case "Slice":
return `${r(k.value)} [${k.begin ? r(k.begin) : ""}..${ return `&[ptr ${r(k.value)}][${k.begin ? r(k.begin) : ""}..${
k.end ? r(k.end) : "" k.end ? r(k.end) : ""
}]`; }]`;
case "Call": case "Call":
@ -138,9 +138,9 @@ export class Inst {
case "Alloca": case "Alloca":
return ""; return "";
case "Load": case "Load":
return `${r(k.source)}`; return `[ptr ${r(k.source)}]`;
case "Store": case "Store":
return `${r(k.target)} = ${r(k.source)}`; return `[ptr ${r(k.target)}] = ${r(k.source)}`;
case "Jump": case "Jump":
return `bb${cx.bbId(k.target)}`; return `bb${cx.bbId(k.target)}`;
case "Branch": case "Branch":
@ -184,7 +184,7 @@ export type InstKind =
| { tag: "Array"; values: Inst[] } | { tag: "Array"; values: Inst[] }
| { tag: "Fn"; fn: Fn } | { tag: "Fn"; fn: Fn }
| { tag: "Param"; idx: number } | { tag: "Param"; idx: number }
| { tag: "Index"; base: Inst; offset: Inst } | { tag: "GetElemPtr"; base: Inst; offset: Inst }
| { tag: "Slice"; value: Inst; begin: Inst | null; end: Inst | null } | { tag: "Slice"; value: Inst; begin: Inst | null; end: Inst | null }
| { tag: "Call"; callee: Inst; args: Inst[] } | { tag: "Call"; callee: Inst; args: Inst[] }
| { tag: "Alloca" } | { tag: "Alloca" }

View File

@ -13,12 +13,12 @@ export class FnInterpreter {
} }
eval(): Val { eval(): Val {
const cx = new mir.PrettyCx(); // const cx = new mir.PrettyCx();
while (this.instIdx < this.bb.insts.length) { while (this.instIdx < this.bb.insts.length) {
const inst = this.bb.insts[this.instIdx]; const inst = this.bb.insts[this.instIdx];
this.instIdx += 1; this.instIdx += 1;
// console.log(poin // console.log(
// `[${this.instIdx.toString().padStart(2, " ")}] ${ // `[${this.instIdx.toString().padStart(2, " ")}] ${
// inst.pretty(cx) // inst.pretty(cx)
// }`, // }`,
@ -50,45 +50,7 @@ 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": { case "GetElemPtr": {
// 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 offset = this.regs.get(k.offset)!; const offset = this.regs.get(k.offset)!;
if (offset.kind.tag !== "Int") { if (offset.kind.tag !== "Int") {
throw new Error(); throw new Error();
@ -100,7 +62,10 @@ export class FnInterpreter {
console.log({ array }); console.log({ array });
throw new Error(); throw new Error();
} }
if (offset.kind.value >= array.kind.values.length) { if (
offset.kind.value < 0 ||
offset.kind.value >= array.kind.values.length
) {
throw new Error(); throw new Error();
} }
this.regs.set( this.regs.set(
@ -112,8 +77,20 @@ export class FnInterpreter {
mutable: base.kind.mutable, mutable: base.kind.mutable,
}), }),
); );
} else if (base.kind.tag === "Slice") { } else if (base.kind.tag === "ArraySlice") {
throw new Error(); const idx = offset.kind.value + base.kind.begin;
if (idx < 0 || idx >= base.kind.end) {
throw new Error();
}
this.regs.set(
inst,
new Val({
tag: "ArrayElemPtr",
values: base.kind.values,
idx,
mutable: base.kind.mutable,
}),
);
} else { } else {
throw new Error(); throw new Error();
} }
@ -136,29 +113,31 @@ export class FnInterpreter {
if (value.kind.tag !== "Array") { if (value.kind.tag !== "Array") {
throw new Error(); throw new Error();
} }
if ( let beginIdx = 0;
begin && begin.kind.tag === "Int" && if (begin) {
begin.kind.value < 0 if (begin.kind.tag !== "Int") {
) { throw new Error();
throw new Error(); }
beginIdx = begin.kind.value;
} }
if ( let endIdx = value.kind.values.length;
end && end.kind.tag === "Int" && if (end) {
end.kind.value >= value.kind.values.length if (end.kind.tag !== "Int") {
) { throw new Error();
}
endIdx = end.kind.value;
}
if (beginIdx < 0 || endIdx > value.kind.values.length) {
throw new Error(); throw new Error();
} }
this.regs.set( this.regs.set(
inst, inst,
new Val({ new Val({
tag: "Slice", tag: "ArraySlice",
value, mutable: false,
begin: begin ?? new Val({ tag: "Int", value: 0 }), values: value.kind.values,
end: end ?? begin: beginIdx,
new Val({ end: endIdx,
tag: "Int",
value: value.kind.values.length,
}),
}), }),
); );
break; break;
@ -194,6 +173,7 @@ export class FnInterpreter {
source.kind.values[source.kind.idx], source.kind.values[source.kind.idx],
); );
} else { } else {
console.log({ source });
throw new Error(); throw new Error();
} }
break; break;
@ -207,6 +187,7 @@ export class FnInterpreter {
const source = this.regs.get(k.source)!; const source = this.regs.get(k.source)!;
target.kind.values[target.kind.idx] = source; target.kind.values[target.kind.idx] = source;
} else { } else {
console.log({ target });
throw new Error(); throw new Error();
} }
break; break;
@ -357,18 +338,12 @@ class Val {
case "Ptr": case "Ptr":
case "ArrayElemPtr": case "ArrayElemPtr":
return `<pointer>`; return `<pointer>`;
case "Slice": case "ArraySlice":
if (k.value.kind.tag !== "Array") {
throw new Error();
}
if (k.begin.kind.tag !== "Int" || k.end.kind.tag !== "Int") {
throw new Error();
}
return `[${ return `[${
k.value.kind.values.slice( k.values
k.begin.kind.value, .slice(k.begin, k.end)
k.end.kind.value, .map((v) => v.pretty())
).map((v) => v.pretty()).join(", ") .join(", ")
}]`; }]`;
case "Array": case "Array":
return `[${k.values.map((v) => v.pretty()).join(", ")}]`; return `[${k.values.map((v) => v.pretty()).join(", ")}]`;
@ -388,6 +363,12 @@ type ValKind =
| { tag: "Bool"; value: boolean } | { tag: "Bool"; value: boolean }
| { tag: "Ptr"; mutable: boolean; value: Val } | { tag: "Ptr"; mutable: boolean; value: Val }
| { tag: "ArrayElemPtr"; mutable: boolean; values: Val[]; idx: number } | { tag: "ArrayElemPtr"; mutable: boolean; values: Val[]; idx: number }
| { tag: "Slice"; value: Val; begin: Val; end: Val } | {
tag: "ArraySlice";
mutable: boolean;
values: Val[];
begin: number;
end: number;
}
| { tag: "Array"; values: Val[] } | { tag: "Array"; values: Val[] }
| { tag: "Fn"; fn: mir.Fn }; | { tag: "Fn"; fn: mir.Fn };

View File

@ -122,6 +122,13 @@ export class Ty {
throw new Error(`'${this.kind.tag}' not handled`); throw new Error(`'${this.kind.tag}' not handled`);
} }
isSized(): boolean {
if (this.is("Slice")) {
return false;
}
return true;
}
pretty(): string { pretty(): string {
switch (this.kind.tag) { switch (this.kind.tag) {
case "Error": case "Error":

View File

@ -12,13 +12,13 @@ fn main()
print_int(ptr_to_array.*[1]); print_int(ptr_to_array.*[1]);
let slice: *[int] = &array[..]; let slice: *[int] = &array[..];
// // e xpect: 2 // expect: 3
// print_int(slice.*[2]); print_int(slice.*[2]);
// let slice_mut: *mut [int] = &mut array[1..3]; let slice_mut: *mut [int] = &mut array[1..3];
// slice_mut.*[0] = 4; slice_mut.*[0] = 4;
// // e xpect: 4 // expect: 4
// print_int(array[1]); print_int(array[1]);
} }
// vim: syntax=rust commentstring=//\ %s // vim: syntax=rust commentstring=//\ %s

View File

@ -8,25 +8,21 @@ fn main()
{ {
let a = 1; let a = 1;
let b: *int = &a; let b: *int = &a;
// expect: 1 // expect: 1
print_int(*b); print_int(*b);
a = 2; a = 2;
// expect: 2 // expect: 2
print_int(*b); print_int(*b);
let c: *mut int = &mut a; let c: *mut int = &mut a;
*c = 3; *c = 3;
// expect: 3 // expect: 3
print_int(a); print_int(a);
// expect: 3 // expect: 3
print_int(*c); print_int(*c);
change_to(&mut a, 4); change_to(&mut a, 4);
// expect: 4 // expect: 4
print_int(a); print_int(a);
} }