diff --git a/src/front/check.ts b/src/front/check.ts index be4b08a..ae7e67e 100644 --- a/src/front/check.ts +++ b/src/front/check.ts @@ -28,7 +28,7 @@ export class Tys { if (this.nodeTys.has(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); return ty; } @@ -82,6 +82,14 @@ class Checker { } 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); } @@ -157,7 +165,7 @@ class Checker { } 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); if ( (exprTy.is("Array") || exprTy.is("Slice")) && @@ -198,6 +206,13 @@ class Checker { } if (node.kind.op === "Deref") { 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; } } diff --git a/src/front/parse.ts b/src/front/parse.ts index 22a28cd..d2896ae 100644 --- a/src/front/parse.ts +++ b/src/front/parse.ts @@ -18,7 +18,7 @@ export class Parser { private text: string, private reporter: FileReporter, ) { - this.toks = tokenize(text); + this.toks = tokenize(this.text, this.reporter); } parseFile(): ast.Node { @@ -402,7 +402,7 @@ const keywordPattern = const operatorPattern2 = /((?:\->)|(?:==)|(?:!=)|(?:<=)|(?:>=)|(?:<<)|(?:>>)|(?:\.\*)|(?:\.\.)|(?:\.\.=)|[\n\(\)\{\}\[\]\,\.\;\:\!\=\<\>\&\^\|\+\-\*\/\%])/g; -export function tokenize(text: string): Tok[] { +export function tokenize(text: string, reporter: FileReporter): Tok[] { return new Lexer() .add(/[ \t\r\n]+/, (_) => null) .add(/\/\/[^\n]*/, (_) => null) @@ -415,6 +415,8 @@ export function tokenize(text: string): Tok[] { return { type: "int", value, loc }; }) .add(/./, (loc, value) => { + const escapedChar = JSON.stringify(value[0]).slice(1, -1); + reporter.error(loc, `illegal character '${escapedChar}'`); return null; }) .lex(text); diff --git a/src/main.ts b/src/main.ts index 918627a..ecbbb17 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,6 +2,7 @@ import * as ast from "./ast.ts"; import { Reporter } from "./diagnostics.ts"; import * as front from "./front/mod.ts"; import * as middle from "./middle.ts"; +import * as mir from "./mir.ts"; import { FnInterpreter } from "./mir_interpreter.ts"; const reporter = new Reporter(); @@ -39,8 +40,56 @@ if (!mainFn) { const m = new middle.MiddleLowerer(syms, tys); const mainMiddleFn = m.lowerFn(mainFn); -if (!Deno.args.includes("--test")) { - console.log(mainMiddleFn.pretty()); +function printMirFn(fn: mir.Fn) { + 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(); diff --git a/src/middle.ts b/src/middle.ts index b08df4e..5001ac6 100644 --- a/src/middle.ts +++ b/src/middle.ts @@ -19,6 +19,10 @@ export class MiddleLowerer { this.fns.set(stmt.id, fn); return fn; } + + allFns(): Fn[] { + return this.fns.values().toArray(); + } } class FnLowerer { @@ -50,19 +54,7 @@ class FnLowerer { private lowerStmt(stmt: ast.Node) { 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("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; + return this.lowerLetStmt(stmt); } if (stmt.is("ReturnStmt")) { const source = stmt.kind.expr @@ -159,10 +151,7 @@ class FnLowerer { return; } if (stmt.is("AssignStmt")) { - const source = this.lowerExpr(stmt.kind.expr); - const target = this.lowerPlace(stmt.kind.place); - this.pushInst(Ty.Void, "Store", { target, source }); - return; + return this.lowerAssignStmt(stmt); } if (stmt.is("ExprStmt")) { this.lowerExpr(stmt.kind.expr); @@ -171,7 +160,30 @@ class FnLowerer { 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 { + // evaluate to most direct pointer + const ty = this.tys.place(place); if (place.is("IdentExpr")) { const sym = this.syms.get(place); @@ -189,17 +201,18 @@ class FnLowerer { } if (place.is("UnaryExpr") && place.kind.op === "Deref") { - const source = this.lowerPlace(place.kind.expr); - return this.pushInst( - Ty.create("PtrMut", { ty }), - "Load", - { source }, - ); + const source = this.lowerExpr(place.kind.expr); + // return this.pushInst( + // Ty.create("PtrMut", { ty }), + // "Load", + // { source }, + // ); + return source; } if (place.is("IndexExpr")) { const value = place.kind.value; - const valueTy = this.tys.expr(value); + const valueTy = this.tys.place(value); const arg = place.kind.arg; const argTy = this.tys.expr(arg); if (!valueTy.is("Array") && !valueTy.is("Slice")) { @@ -210,7 +223,7 @@ class FnLowerer { const argInst = this.lowerExpr(arg); return this.pushInst( Ty.create("PtrMut", { ty: valueTy.kind.ty }), - "Index", + "GetElemPtr", { base: valueInst, offset: argInst }, ); } diff --git a/src/mir.ts b/src/mir.ts index 1ee4f5b..63d193e 100644 --- a/src/mir.ts +++ b/src/mir.ts @@ -100,7 +100,7 @@ export class Inst { pretty(cx: PrettyCx): string { const valueless = ["Store", "Jump", "Branch", "Return", "DebugPrint"]; - const valueType = `%${cx.regId(this)} = ${this.ty.pretty()} `; + const valueType = `%${cx.regId(this)} (${this.ty.pretty()}) = `; return `${ !valueless.includes(this.kind.tag) ? valueType : "" }${this.kind.tag} ${this.prettyArgs(cx)}`; @@ -127,10 +127,10 @@ export class Inst { return `${k.fn.stmt.kind.ident}`; case "Param": return `${k.idx}`; - case "Index": - return `${r(k.base)} &[${r(k.offset)}]`; + case "GetElemPtr": + return `&[ptr ${r(k.base)}][${r(k.offset)}]`; 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) : "" }]`; case "Call": @@ -138,9 +138,9 @@ export class Inst { case "Alloca": return ""; case "Load": - return `${r(k.source)}`; + return `[ptr ${r(k.source)}]`; case "Store": - return `${r(k.target)} = ${r(k.source)}`; + return `[ptr ${r(k.target)}] = ${r(k.source)}`; case "Jump": return `bb${cx.bbId(k.target)}`; case "Branch": @@ -184,7 +184,7 @@ export type InstKind = | { tag: "Array"; values: Inst[] } | { tag: "Fn"; fn: Fn } | { 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: "Call"; callee: Inst; args: Inst[] } | { tag: "Alloca" } diff --git a/src/mir_interpreter.ts b/src/mir_interpreter.ts index 9dbfadd..f54337c 100644 --- a/src/mir_interpreter.ts +++ b/src/mir_interpreter.ts @@ -13,12 +13,12 @@ export class FnInterpreter { } eval(): Val { - const cx = new mir.PrettyCx(); + // const cx = new mir.PrettyCx(); while (this.instIdx < this.bb.insts.length) { const inst = this.bb.insts[this.instIdx]; this.instIdx += 1; - // console.log(poin + // console.log( // `[${this.instIdx.toString().padStart(2, " ")}] ${ // inst.pretty(cx) // }`, @@ -50,45 +50,7 @@ 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": { + case "GetElemPtr": { const offset = this.regs.get(k.offset)!; if (offset.kind.tag !== "Int") { throw new Error(); @@ -100,7 +62,10 @@ export class FnInterpreter { console.log({ array }); 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(); } this.regs.set( @@ -112,8 +77,20 @@ export class FnInterpreter { mutable: base.kind.mutable, }), ); - } else if (base.kind.tag === "Slice") { - throw new Error(); + } else if (base.kind.tag === "ArraySlice") { + 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 { throw new Error(); } @@ -136,29 +113,31 @@ export class FnInterpreter { if (value.kind.tag !== "Array") { throw new Error(); } - if ( - begin && begin.kind.tag === "Int" && - begin.kind.value < 0 - ) { - throw new Error(); + let beginIdx = 0; + if (begin) { + if (begin.kind.tag !== "Int") { + throw new Error(); + } + beginIdx = begin.kind.value; } - if ( - end && end.kind.tag === "Int" && - end.kind.value >= value.kind.values.length - ) { + let endIdx = value.kind.values.length; + if (end) { + if (end.kind.tag !== "Int") { + throw new Error(); + } + endIdx = end.kind.value; + } + if (beginIdx < 0 || endIdx > 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, - }), + tag: "ArraySlice", + mutable: false, + values: value.kind.values, + begin: beginIdx, + end: endIdx, }), ); break; @@ -194,6 +173,7 @@ export class FnInterpreter { source.kind.values[source.kind.idx], ); } else { + console.log({ source }); throw new Error(); } break; @@ -207,6 +187,7 @@ export class FnInterpreter { const source = this.regs.get(k.source)!; target.kind.values[target.kind.idx] = source; } else { + console.log({ target }); throw new Error(); } break; @@ -357,18 +338,12 @@ class Val { case "Ptr": case "ArrayElemPtr": 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(); - } + case "ArraySlice": return `[${ - k.value.kind.values.slice( - k.begin.kind.value, - k.end.kind.value, - ).map((v) => v.pretty()).join(", ") + k.values + .slice(k.begin, k.end) + .map((v) => v.pretty()) + .join(", ") }]`; case "Array": return `[${k.values.map((v) => v.pretty()).join(", ")}]`; @@ -388,6 +363,12 @@ type ValKind = | { 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: "ArraySlice"; + mutable: boolean; + values: Val[]; + begin: number; + end: number; + } | { tag: "Array"; values: Val[] } | { tag: "Fn"; fn: mir.Fn }; diff --git a/src/ty.ts b/src/ty.ts index 05ab78e..8f912d5 100644 --- a/src/ty.ts +++ b/src/ty.ts @@ -122,6 +122,13 @@ export class Ty { throw new Error(`'${this.kind.tag}' not handled`); } + isSized(): boolean { + if (this.is("Slice")) { + return false; + } + return true; + } + pretty(): string { switch (this.kind.tag) { case "Error": diff --git a/tests/array.ethlang b/tests/array.ethlang index 8261ff4..61bbf9d 100644 --- a/tests/array.ethlang +++ b/tests/array.ethlang @@ -12,13 +12,13 @@ fn main() print_int(ptr_to_array.*[1]); let slice: *[int] = &array[..]; - // // e xpect: 2 - // print_int(slice.*[2]); + // expect: 3 + print_int(slice.*[2]); - // let slice_mut: *mut [int] = &mut array[1..3]; - // slice_mut.*[0] = 4; - // // e xpect: 4 - // print_int(array[1]); + let slice_mut: *mut [int] = &mut array[1..3]; + slice_mut.*[0] = 4; + // expect: 4 + print_int(array[1]); } // vim: syntax=rust commentstring=//\ %s diff --git a/tests/_pointer.ethlang b/tests/pointer.ethlang similarity index 97% rename from tests/_pointer.ethlang rename to tests/pointer.ethlang index a693c72..7c19ba7 100644 --- a/tests/_pointer.ethlang +++ b/tests/pointer.ethlang @@ -8,25 +8,21 @@ fn main() { let a = 1; let b: *int = &a; - // expect: 1 print_int(*b); a = 2; - // expect: 2 print_int(*b); let c: *mut int = &mut a; *c = 3; - // expect: 3 print_int(a); // expect: 3 print_int(*c); change_to(&mut a, 4); - // expect: 4 print_int(a); }