From 5150090d2de4139dddda2d3ccda0ea77e3618b10 Mon Sep 17 00:00:00 2001 From: sfja Date: Thu, 26 Dec 2024 02:38:32 +0100 Subject: [PATCH] karlkodet generics --- compiler/ast.ts | 2 +- compiler/checker.ts | 21 +- compiler/compiler.ts | 12 +- compiler/lowerer.ts | 260 ++++++++++------ compiler/mono_lower.ts | 618 ------------------------------------- compiler/resolver.ts | 1 + examples/generic_array.slg | 19 ++ 7 files changed, 200 insertions(+), 733 deletions(-) delete mode 100644 compiler/mono_lower.ts create mode 100644 examples/generic_array.slg diff --git a/compiler/ast.ts b/compiler/ast.ts index 1bfdbd3..8a342e7 100644 --- a/compiler/ast.ts +++ b/compiler/ast.ts @@ -101,7 +101,7 @@ export type SymKind = | { type: "fn"; stmt: Stmt } | { type: "fn_param"; param: Param } | { type: "closure"; inner: Sym } - | { type: "generic"; genericParam: GenericParam }; + | { type: "generic"; stmt: Stmt; genericParam: GenericParam }; export type EType = { kind: ETypeKind; diff --git a/compiler/checker.ts b/compiler/checker.ts index b86312f..07fe346 100644 --- a/compiler/checker.ts +++ b/compiler/checker.ts @@ -15,6 +15,8 @@ export class Checker { private fnReturnStack: VType[] = []; private loopBreakStack: VType[][] = []; + private globalIdToGenericParamMap = new Map(); + public constructor(private reporter: Reporter) {} public check(stmts: Stmt[]) { @@ -29,15 +31,15 @@ export class Checker { if (stmt.kind.type !== "fn") { continue; } - const returnType: VType = stmt.kind.returnType - ? this.checkEType(stmt.kind.returnType) - : { type: "null" }; let genericParams: VTypeGenericParam[] | undefined; if (stmt.kind.genericParams !== undefined) { genericParams = []; for (const etypeParam of stmt.kind.genericParams) { const id = genericParams.length; - genericParams.push({ id, ident: etypeParam.ident }); + const globalId = etypeParam.id; + const param = { id, ident: etypeParam.ident }; + genericParams.push(param); + this.globalIdToGenericParamMap.set(globalId, param); } } const params: VTypeParam[] = []; @@ -50,6 +52,9 @@ export class Checker { param.vtype = vtype; params.push({ ident: param.ident, vtype }); } + const returnType: VType = stmt.kind.returnType + ? this.checkEType(stmt.kind.returnType) + : { type: "null" }; stmt.kind.vtype = { type: "fn", genericParams, @@ -499,7 +504,7 @@ export class Checker { return { type: "error" }; } const args = expr.kind.etypeArgs; - if (args.length !== subject.params.length) { + if (args.length !== subject.genericParams.length) { this.report( `incorrect number of arguments` + `, expected ${subject.params.length}`, @@ -687,7 +692,11 @@ export class Checker { } if (etype.kind.type === "sym") { if (etype.kind.sym.type === "generic") { - const { id, ident } = etype.kind.sym.genericParam; + const { id: globalId, ident } = etype.kind.sym.genericParam; + if (!this.globalIdToGenericParamMap.has(globalId)) { + throw new Error(); + } + const { id } = this.globalIdToGenericParamMap.get(globalId)!; return { type: "generic", param: { id, ident } }; } this.report(`sym type '${etype.kind.sym.type}' used as type`, pos); diff --git a/compiler/compiler.ts b/compiler/compiler.ts index 827d227..026a173 100644 --- a/compiler/compiler.ts +++ b/compiler/compiler.ts @@ -4,9 +4,8 @@ import { CompoundAssignDesugarer } from "./desugar/compound_assign.ts"; import { SpecialLoopDesugarer } from "./desugar/special_loop.ts"; import { Reporter } from "./info.ts"; import { Lexer } from "./lexer.ts"; -import { FnNamesMap, Lowerer } from "./lowerer.ts"; import { Monomorphizer } from "./mono.ts"; -import { MonoLowerer } from "./mono_lower.ts"; +import { FnNamesMap, Lowerer } from "./lowerer.ts"; import { Parser } from "./parser.ts"; import { Resolver } from "./resolver.ts"; @@ -49,14 +48,9 @@ export class Compiler { const { monoFns, callMap } = new Monomorphizer(ast).monomorphize(); - //const lowerer = new Lowerer(lexer.currentPos()); - //lowerer.lower(ast); - //// lowerer.printProgram(); - //const { program, fnNames } = lowerer.finish(); - - const lowerer = new MonoLowerer(monoFns, callMap, lexer.currentPos()); + const lowerer = new Lowerer(monoFns, callMap, lexer.currentPos()); const { program, fnNames } = lowerer.lower(); - lowerer.printProgram(); + //lowerer.printProgram(); return { program, fnNames }; } diff --git a/compiler/lowerer.ts b/compiler/lowerer.ts index 0b8b536..67fc3e0 100644 --- a/compiler/lowerer.ts +++ b/compiler/lowerer.ts @@ -1,48 +1,65 @@ import { Builtins, Ops } from "./arch.ts"; +import { Assembler, Label } from "./assembler.ts"; import { Expr, Stmt } from "./ast.ts"; import { LocalLeaf, Locals, LocalsFnRoot } from "./lowerer_locals.ts"; -import { Assembler, Label } from "./assembler.ts"; -import { vtypeToString } from "./vtype.ts"; +import { MonoCallNameGenMap, MonoFn, MonoFnsMap } from "./mono.ts"; import { Pos } from "./token.ts"; +import { vtypeToString } from "./vtype.ts"; export type FnNamesMap = { [pc: number]: string }; export class Lowerer { private program = Assembler.newRoot(); - private locals: Locals = new LocalsFnRoot(); - private fnStmtIdLabelMap: { [stmtId: number]: string } = {}; - private fnLabelNameMap: { [name: string]: string } = {}; - private returnStack: Label[] = []; - private breakStack: Label[] = []; - public constructor(private lastPos: Pos) {} + public constructor( + private monoFns: MonoFnsMap, + private callMap: MonoCallNameGenMap, + private lastPos: Pos, + ) {} - public lower(stmts: Stmt[]) { + public lower(): { program: number[]; fnNames: FnNamesMap } { + const fnLabelNameMap: FnLabelMap = {}; + for (const nameGen in this.monoFns) { + fnLabelNameMap[nameGen] = nameGen; + } + + this.addPrelimiary(); + + for (const fn of Object.values(this.monoFns)) { + const fnProgram = new MonoFnLowerer( + fn, + this.program.fork(), + this.callMap, + ).lower(); + this.program.join(fnProgram); + } + + this.addConcluding(); + + const { program, locs } = this.program.assemble(); + const fnNames: FnNamesMap = {}; + for (const label in locs) { + if (label in fnLabelNameMap) { + fnNames[locs[label]] = fnLabelNameMap[label]; + } + } + return { program, fnNames }; + } + + private addPrelimiary() { this.addClearingSourceMap(); this.program.add(Ops.PushPtr, { label: "main" }); this.program.add(Ops.Call, 0); this.program.add(Ops.PushPtr, { label: "_exit" }); this.program.add(Ops.Jump); - this.scoutFnHeaders(stmts); - for (const stmt of stmts) { - this.lowerStaticStmt(stmt); - } + } + + private addConcluding() { this.program.setLabel({ label: "_exit" }); this.addSourceMap(this.lastPos); this.program.add(Ops.Pop); } - public finish(): { program: number[]; fnNames: FnNamesMap } { - const { program, locs } = this.program.assemble(); - const fnNames: FnNamesMap = {}; - for (const label in locs) { - if (label in this.fnLabelNameMap) { - fnNames[locs[label]] = this.fnLabelNameMap[label]; - } - } - return { program, fnNames }; - } - private addSourceMap({ index, line, col }: Pos) { this.program.add(Ops.SourceMap, index, line, col); } @@ -51,30 +68,78 @@ export class Lowerer { this.program.add(Ops.SourceMap, 0, 1, 1); } - private scoutFnHeaders(stmts: Stmt[]) { - for (const stmt of stmts) { - if (stmt.kind.type !== "fn") { - continue; - } - const label = stmt.kind.ident === "main" - ? "main" - : `${stmt.kind.ident}_${stmt.id}`; - this.fnStmtIdLabelMap[stmt.id] = label; - } + public printProgram() { + this.program.printProgram(); + } +} + +type FnLabelMap = { [nameGen: string]: string }; + +class MonoFnLowerer { + private locals: Locals = new LocalsFnRoot(); + private returnStack: Label[] = []; + private breakStack: Label[] = []; + + public constructor( + private fn: MonoFn, + private program: Assembler, + private callMap: MonoCallNameGenMap, + ) {} + + public lower(): Assembler { + this.lowerFnStmt(this.fn.stmt); + return this.program; } - private lowerStaticStmt(stmt: Stmt) { - switch (stmt.kind.type) { - case "fn": - return this.lowerFnStmt(stmt); - case "error": - case "break": - case "return": - case "let": - case "assign": - case "expr": + private lowerFnStmt(stmt: Stmt) { + if (stmt.kind.type !== "fn") { + throw new Error(); } - throw new Error(`unhandled static statement '${stmt.kind.type}'`); + const label = this.fn.nameGen; + this.program.setLabel({ label }); + this.addSourceMap(stmt.pos); + + const outerLocals = this.locals; + const fnRoot = new LocalsFnRoot(outerLocals); + const outerProgram = this.program; + + const returnLabel = this.program.makeLabel(); + this.returnStack.push(returnLabel); + + this.program = outerProgram.fork(); + this.locals = fnRoot; + for (const { ident } of stmt.kind.params) { + this.locals.allocSym(ident); + } + if (stmt.kind.anno?.ident === "builtin") { + this.lowerFnBuiltinBody(stmt.kind.anno.values); + } else if (stmt.kind.anno?.ident === "remainder") { + this.program.add(Ops.Remainder); + } else { + this.lowerExpr(stmt.kind.body); + } + this.locals = outerLocals; + + const localAmount = fnRoot.stackReserved() - + stmt.kind.params.length; + for (let i = 0; i < localAmount; ++i) { + outerProgram.add(Ops.PushNull); + } + + this.returnStack.pop(); + this.program.setLabel(returnLabel); + this.program.add(Ops.Return); + + outerProgram.join(this.program); + this.program = outerProgram; + } + + private addSourceMap({ index, line, col }: Pos) { + this.program.add(Ops.SourceMap, index, line, col); + } + + private addClearingSourceMap() { + this.program.add(Ops.SourceMap, 0, 1, 1); } private lowerStmt(stmt: Stmt) { @@ -153,52 +218,6 @@ export class Lowerer { this.program.add(Ops.Jump); } - private lowerFnStmt(stmt: Stmt) { - if (stmt.kind.type !== "fn") { - throw new Error(); - } - const label = stmt.kind.ident === "main" - ? "main" - : `${stmt.kind.ident}_${stmt.id}`; - this.program.setLabel({ label }); - this.fnLabelNameMap[label] = stmt.kind.ident; - this.addSourceMap(stmt.pos); - - const outerLocals = this.locals; - const fnRoot = new LocalsFnRoot(outerLocals); - const outerProgram = this.program; - - const returnLabel = this.program.makeLabel(); - this.returnStack.push(returnLabel); - - this.program = outerProgram.fork(); - this.locals = fnRoot; - for (const { ident } of stmt.kind.params) { - this.locals.allocSym(ident); - } - if (stmt.kind.anno?.ident === "builtin") { - this.lowerFnBuiltinBody(stmt.kind.anno.values); - } else if (stmt.kind.anno?.ident === "remainder") { - this.program.add(Ops.Remainder); - } else { - this.lowerExpr(stmt.kind.body); - } - this.locals = outerLocals; - - const localAmount = fnRoot.stackReserved() - - stmt.kind.params.length; - for (let i = 0; i < localAmount; ++i) { - outerProgram.add(Ops.PushNull); - } - - this.returnStack.pop(); - this.program.setLabel(returnLabel); - this.program.add(Ops.Return); - - outerProgram.join(this.program); - this.program = outerProgram; - } - private lowerFnBuiltinBody(annoArgs: Expr[]) { if (annoArgs.length !== 1) { throw new Error("invalid # of arguments to builtin annotation"); @@ -308,8 +327,42 @@ export class Lowerer { return; } if (expr.kind.sym.type === "fn") { - const label = this.fnStmtIdLabelMap[expr.kind.sym.stmt.id]; - this.program.add(Ops.PushPtr, { label }); + // Is this smart? Well, my presumption is + // that it isn't. The underlying problem, which + // this solutions raison d'être is to solve, is + // that the compiler, as it d'être's currently + // doesn't support checking and infering generic + // fn args all the way down to the sym. Therefore, + // when a sym is checked in a call expr, we can't + // really do anything useful. Instead the actual + // function pointer pointing to the actual + // monomorphized function is emplaced when + // lowering the call expression itself. But what + // should we do then, if the user decides to + // assign a function to a local? You might ask. + // You see, that's where the problem lies. + // My current, very thought out solution, as + // you can read below, is to push a null pointer, + // for it to then be replaced later. This will + // probably cause many hastles in the future + // for myself in particular, when trying to + // decipher the lowerer's output. So if you're + // the unlucky girl, who has tried for ages to + // decipher why a zero value is pushed and then + // later replaced, and then you finally + // stumbled upon this here implementation, + // let me first say, I'm so sorry. At the time + // of writing, I really haven't thought out + // very well, how the generic call system should + // work, and it's therefore a bit flaky, and the + // implementation kinda looks like it was + // implementated by a girl who didn't really + // understand very well what they were + // implementing at the time that they were + // implementing it. Anyway, I just wanted to + // apologize. Happy coding. + // -Your favorite compiler girl. + this.program.add(Ops.PushPtr, 0); return; } throw new Error(`unhandled sym type '${expr.kind.sym.type}'`); @@ -432,6 +485,18 @@ export class Lowerer { default: } } + if (vtype.type === "bool") { + switch (expr.kind.binaryType) { + case "==": + this.program.add(Ops.And); + return; + case "!=": + this.program.add(Ops.And); + this.program.add(Ops.Not); + return; + default: + } + } if (vtype.type === "string") { if (expr.kind.binaryType === "+") { this.program.add(Ops.Builtin, Builtins.StringConcat); @@ -464,6 +529,8 @@ export class Lowerer { this.lowerExpr(arg); } this.lowerExpr(expr.kind.subject); + this.program.add(Ops.Pop); + this.program.add(Ops.PushPtr, { label: this.callMap[expr.id] }); this.program.add(Ops.Call, expr.kind.args.length); } @@ -471,7 +538,7 @@ export class Lowerer { if (expr.kind.type !== "etype_args") { throw new Error(); } - throw new Error("not implemented"); + this.lowerExpr(expr.kind.subject); } private lowerIfExpr(expr: Expr) { @@ -537,7 +604,6 @@ export class Lowerer { } const outerLocals = this.locals; this.locals = new LocalLeaf(this.locals); - this.scoutFnHeaders(expr.kind.stmts); for (const stmt of expr.kind.stmts) { this.addSourceMap(stmt.pos); this.lowerStmt(stmt); @@ -550,8 +616,4 @@ export class Lowerer { } this.locals = outerLocals; } - - public printProgram() { - this.program.printProgram(); - } } diff --git a/compiler/mono_lower.ts b/compiler/mono_lower.ts deleted file mode 100644 index 40dca45..0000000 --- a/compiler/mono_lower.ts +++ /dev/null @@ -1,618 +0,0 @@ -import { Builtins, Ops } from "./arch.ts"; -import { Assembler, Label } from "./assembler.ts"; -import { Expr, Stmt } from "./ast.ts"; -import { FnNamesMap } from "./lowerer.ts"; -import { LocalLeaf, Locals, LocalsFnRoot } from "./lowerer_locals.ts"; -import { MonoCallNameGenMap, MonoFn, MonoFnsMap } from "./mono.ts"; -import { Pos } from "./token.ts"; -import { vtypeToString } from "./vtype.ts"; - -export class MonoLowerer { - private program = Assembler.newRoot(); - - public constructor( - private monoFns: MonoFnsMap, - private callMap: MonoCallNameGenMap, - private lastPos: Pos, - ) {} - - public lower(): { program: number[]; fnNames: FnNamesMap } { - const fnLabelNameMap: FnLabelMap = {}; - for (const nameGen in this.monoFns) { - fnLabelNameMap[nameGen] = nameGen; - } - - this.addPrelimiary(); - - for (const fn of Object.values(this.monoFns)) { - const fnProgram = new MonoFnLowerer( - fn, - this.program.fork(), - this.callMap, - ).lower(); - this.program.join(fnProgram); - } - - this.addConcluding(); - - const { program, locs } = this.program.assemble(); - const fnNames: FnNamesMap = {}; - for (const label in locs) { - if (label in fnLabelNameMap) { - fnNames[locs[label]] = fnLabelNameMap[label]; - } - } - return { program, fnNames }; - } - - private addPrelimiary() { - this.addClearingSourceMap(); - this.program.add(Ops.PushPtr, { label: "main" }); - this.program.add(Ops.Call, 0); - this.program.add(Ops.PushPtr, { label: "_exit" }); - this.program.add(Ops.Jump); - } - - private addConcluding() { - this.program.setLabel({ label: "_exit" }); - this.addSourceMap(this.lastPos); - this.program.add(Ops.Pop); - } - - private addSourceMap({ index, line, col }: Pos) { - this.program.add(Ops.SourceMap, index, line, col); - } - - private addClearingSourceMap() { - this.program.add(Ops.SourceMap, 0, 1, 1); - } - - public printProgram() { - this.program.printProgram(); - } -} - -type FnLabelMap = { [nameGen: string]: string }; - -class MonoFnLowerer { - private locals: Locals = new LocalsFnRoot(); - private returnStack: Label[] = []; - private breakStack: Label[] = []; - - public constructor( - private fn: MonoFn, - private program: Assembler, - private callMap: MonoCallNameGenMap, - ) {} - - public lower(): Assembler { - this.lowerFnStmt(this.fn.stmt); - return this.program; - } - - private lowerFnStmt(stmt: Stmt) { - if (stmt.kind.type !== "fn") { - throw new Error(); - } - const label = this.fn.nameGen; - this.program.setLabel({ label }); - this.addSourceMap(stmt.pos); - - const outerLocals = this.locals; - const fnRoot = new LocalsFnRoot(outerLocals); - const outerProgram = this.program; - - const returnLabel = this.program.makeLabel(); - this.returnStack.push(returnLabel); - - this.program = outerProgram.fork(); - this.locals = fnRoot; - for (const { ident } of stmt.kind.params) { - this.locals.allocSym(ident); - } - if (stmt.kind.anno?.ident === "builtin") { - this.lowerFnBuiltinBody(stmt.kind.anno.values); - } else if (stmt.kind.anno?.ident === "remainder") { - this.program.add(Ops.Remainder); - } else { - this.lowerExpr(stmt.kind.body); - } - this.locals = outerLocals; - - const localAmount = fnRoot.stackReserved() - - stmt.kind.params.length; - for (let i = 0; i < localAmount; ++i) { - outerProgram.add(Ops.PushNull); - } - - this.returnStack.pop(); - this.program.setLabel(returnLabel); - this.program.add(Ops.Return); - - outerProgram.join(this.program); - this.program = outerProgram; - } - - private addSourceMap({ index, line, col }: Pos) { - this.program.add(Ops.SourceMap, index, line, col); - } - - private addClearingSourceMap() { - this.program.add(Ops.SourceMap, 0, 1, 1); - } - - private lowerStmt(stmt: Stmt) { - switch (stmt.kind.type) { - case "error": - break; - case "break": - return this.lowerBreakStmt(stmt); - case "return": - return this.lowerReturnStmt(stmt); - case "fn": - return this.lowerFnStmt(stmt); - case "let": - return this.lowerLetStmt(stmt); - case "assign": - return this.lowerAssignStmt(stmt); - case "expr": - this.lowerExpr(stmt.kind.expr); - this.program.add(Ops.Pop); - return; - } - throw new Error(`unhandled stmt '${stmt.kind.type}'`); - } - - private lowerAssignStmt(stmt: Stmt) { - if (stmt.kind.type !== "assign") { - throw new Error(); - } - this.lowerExpr(stmt.kind.value); - switch (stmt.kind.subject.kind.type) { - case "field": { - this.lowerExpr(stmt.kind.subject.kind.subject); - this.program.add(Ops.PushString, stmt.kind.subject.kind.ident); - this.program.add(Ops.Builtin, Builtins.StructSet); - return; - } - case "index": { - this.lowerExpr(stmt.kind.subject.kind.subject); - this.lowerExpr(stmt.kind.subject.kind.value); - this.program.add(Ops.Builtin, Builtins.ArraySet); - return; - } - case "sym": { - this.program.add( - Ops.StoreLocal, - this.locals.symId(stmt.kind.subject.kind.sym.ident), - ); - return; - } - default: - throw new Error(); - } - } - - private lowerReturnStmt(stmt: Stmt) { - if (stmt.kind.type !== "return") { - throw new Error(); - } - if (stmt.kind.expr) { - this.lowerExpr(stmt.kind.expr); - } - this.addClearingSourceMap(); - this.program.add(Ops.PushPtr, this.returnStack.at(-1)!); - this.program.add(Ops.Jump); - } - - private lowerBreakStmt(stmt: Stmt) { - if (stmt.kind.type !== "break") { - throw new Error(); - } - if (stmt.kind.expr) { - this.lowerExpr(stmt.kind.expr); - } - this.addClearingSourceMap(); - this.program.add(Ops.PushPtr, this.breakStack.at(-1)!); - this.program.add(Ops.Jump); - } - - private lowerFnBuiltinBody(annoArgs: Expr[]) { - if (annoArgs.length !== 1) { - throw new Error("invalid # of arguments to builtin annotation"); - } - const anno = annoArgs[0]; - if (anno.kind.type !== "ident") { - throw new Error( - `unexpected argument type '${anno.kind.type}' expected 'ident'`, - ); - } - const value = anno.kind.ident; - const builtin = Object.entries(Builtins).find((entry) => - entry[0] === value - )?.[1]; - if (builtin === undefined) { - throw new Error( - `unrecognized builtin '${value}'`, - ); - } - this.program.add(Ops.Builtin, builtin); - } - - private lowerLetStmt(stmt: Stmt) { - if (stmt.kind.type !== "let") { - throw new Error(); - } - this.lowerExpr(stmt.kind.value); - this.locals.allocSym(stmt.kind.param.ident); - this.program.add( - Ops.StoreLocal, - this.locals.symId(stmt.kind.param.ident), - ); - } - - private lowerExpr(expr: Expr) { - switch (expr.kind.type) { - case "error": - break; - case "sym": - return this.lowerSymExpr(expr); - case "null": - break; - case "int": - return this.lowerIntExpr(expr); - case "bool": - return this.lowerBoolExpr(expr); - case "string": - return this.lowerStringExpr(expr); - case "ident": - break; - case "group": - return void this.lowerExpr(expr.kind.expr); - case "field": - break; - case "index": - return this.lowerIndexExpr(expr); - case "call": - return this.lowerCallExpr(expr); - case "etype_args": - return this.lowerETypeArgsExpr(expr); - case "unary": - return this.lowerUnaryExpr(expr); - case "binary": - return this.lowerBinaryExpr(expr); - case "if": - return this.lowerIfExpr(expr); - case "loop": - return this.lowerLoopExpr(expr); - case "block": - return this.lowerBlockExpr(expr); - } - throw new Error(`unhandled expr '${expr.kind.type}'`); - } - - private lowerIndexExpr(expr: Expr) { - if (expr.kind.type !== "index") { - throw new Error(); - } - this.lowerExpr(expr.kind.subject); - this.lowerExpr(expr.kind.value); - - if (expr.kind.subject.vtype?.type == "array") { - this.program.add(Ops.Builtin, Builtins.ArrayAt); - return; - } - if (expr.kind.subject.vtype?.type == "string") { - this.program.add(Ops.Builtin, Builtins.StringCharAt); - return; - } - throw new Error(`unhandled index subject type '${expr.kind.subject}'`); - } - - private lowerSymExpr(expr: Expr) { - if (expr.kind.type !== "sym") { - throw new Error(); - } - if (expr.kind.sym.type === "let") { - const symId = this.locals.symId(expr.kind.ident); - this.program.add(Ops.LoadLocal, symId); - return; - } - if (expr.kind.sym.type === "fn_param") { - this.program.add( - Ops.LoadLocal, - this.locals.symId(expr.kind.ident), - ); - return; - } - if (expr.kind.sym.type === "fn") { - // Is this smart? Well, my presumption is - // that it isn't. The underlying problem, which - // this solutions raison d'être is to solve, is - // that the compiler, as it d'être's currently - // doesn't support checking and infering generic - // fn args all the way down to the sym. Therefore, - // when a sym is checked in a call expr, we can't - // really do anything useful. Instead the actual - // function pointer pointing to the actual - // monomorphized function is emplaced when - // lowering the call expression itself. But what - // should we do then, if the user decides to - // assign a function to a local? You might ask. - // You see, that's where the problem lies. - // My current, very thought out solution, as - // you can read below, is to push a null pointer, - // for it to then be replaced later. This will - // probably cause many hastles in the future - // for myself in particular, when trying to - // decipher the lowerer's output. So if you're - // the unlucky girl, who has tried for ages to - // decipher why a zero value is pushed and then - // later replaced, and then you finally - // stumbled upon this here implementation, - // let me first say, I'm so sorry. At the time - // of writing, I really haven't thought out - // very well, how the generic call system should - // work, and it's therefore a bit flaky, and the - // implementation kinda looks like it was - // implementated by a girl who didn't really - // understand very well what they were - // implementing at the time that they were - // implementing it. Anyway, I just wanted to - // apologize. Happy coding. - // -Your favorite compiler girl. - this.program.add(Ops.PushPtr, 0); - return; - } - throw new Error(`unhandled sym type '${expr.kind.sym.type}'`); - } - - private lowerIntExpr(expr: Expr) { - if (expr.kind.type !== "int") { - throw new Error(); - } - this.program.add(Ops.PushInt, expr.kind.value); - } - - private lowerBoolExpr(expr: Expr) { - if (expr.kind.type !== "bool") { - throw new Error(); - } - this.program.add(Ops.PushBool, expr.kind.value); - } - - private lowerStringExpr(expr: Expr) { - if (expr.kind.type !== "string") { - throw new Error(); - } - this.program.add(Ops.PushString, expr.kind.value); - } - - private lowerUnaryExpr(expr: Expr) { - if (expr.kind.type !== "unary") { - throw new Error(); - } - this.lowerExpr(expr.kind.subject); - const vtype = expr.kind.subject.vtype!; - if (vtype.type === "bool") { - switch (expr.kind.unaryType) { - case "not": - this.program.add(Ops.Not); - return; - default: - } - } - if (vtype.type === "int") { - switch (expr.kind.unaryType) { - case "-": { - this.program.add(Ops.PushInt, 0); - this.program.add(Ops.Swap); - this.program.add(Ops.Subtract); - return; - } - default: - } - } - throw new Error( - `unhandled unary` + - ` '${vtypeToString(expr.vtype!)}' aka. ` + - ` ${expr.kind.unaryType}` + - ` '${vtypeToString(expr.kind.subject.vtype!)}'`, - ); - } - - private lowerBinaryExpr(expr: Expr) { - if (expr.kind.type !== "binary") { - throw new Error(); - } - const vtype = expr.kind.left.vtype!; - if (vtype.type === "bool") { - if (["or", "and"].includes(expr.kind.binaryType)) { - const shortCircuitLabel = this.program.makeLabel(); - this.lowerExpr(expr.kind.left); - this.program.add(Ops.Duplicate); - if (expr.kind.binaryType === "and") { - this.program.add(Ops.Not); - } - this.program.add(Ops.PushPtr, shortCircuitLabel); - this.program.add(Ops.JumpIfTrue); - this.program.add(Ops.Pop); - this.lowerExpr(expr.kind.right); - this.program.setLabel(shortCircuitLabel); - return; - } - } - this.lowerExpr(expr.kind.left); - this.lowerExpr(expr.kind.right); - if (vtype.type === "int") { - switch (expr.kind.binaryType) { - case "+": - this.program.add(Ops.Add); - return; - case "-": - this.program.add(Ops.Subtract); - return; - case "*": - this.program.add(Ops.Multiply); - return; - case "/": - this.program.add(Ops.Multiply); - return; - case "==": - this.program.add(Ops.Equal); - return; - case "!=": - this.program.add(Ops.Equal); - this.program.add(Ops.Not); - return; - case "<": - this.program.add(Ops.LessThan); - return; - case ">": - this.program.add(Ops.Swap); - this.program.add(Ops.LessThan); - return; - case "<=": - this.program.add(Ops.Swap); - this.program.add(Ops.LessThan); - this.program.add(Ops.Not); - return; - case ">=": - this.program.add(Ops.LessThan); - this.program.add(Ops.Not); - return; - default: - } - } - if (vtype.type === "bool") { - switch (expr.kind.binaryType) { - case "==": - this.program.add(Ops.And); - return; - case "!=": - this.program.add(Ops.And); - this.program.add(Ops.Not); - return; - default: - } - } - if (vtype.type === "string") { - if (expr.kind.binaryType === "+") { - this.program.add(Ops.Builtin, Builtins.StringConcat); - return; - } - if (expr.kind.binaryType === "==") { - this.program.add(Ops.Builtin, Builtins.StringEqual); - return; - } - if (expr.kind.binaryType === "!=") { - this.program.add(Ops.Builtin, Builtins.StringEqual); - this.program.add(Ops.Not); - return; - } - } - throw new Error( - `unhandled binaryType` + - ` '${vtypeToString(expr.vtype!)}' aka. ` + - ` '${vtypeToString(expr.kind.left.vtype!)}'` + - ` ${expr.kind.binaryType}` + - ` '${vtypeToString(expr.kind.left.vtype!)}'`, - ); - } - - private lowerCallExpr(expr: Expr) { - if (expr.kind.type !== "call") { - throw new Error(); - } - for (const arg of expr.kind.args) { - this.lowerExpr(arg); - } - this.lowerExpr(expr.kind.subject); - this.program.add(Ops.Pop); - this.program.add(Ops.PushPtr, { label: this.callMap[expr.id] }); - this.program.add(Ops.Call, expr.kind.args.length); - } - - private lowerETypeArgsExpr(expr: Expr) { - if (expr.kind.type !== "etype_args") { - throw new Error(); - } - this.lowerExpr(expr.kind.subject); - } - - private lowerIfExpr(expr: Expr) { - if (expr.kind.type !== "if") { - throw new Error(); - } - - const falseLabel = this.program.makeLabel(); - const doneLabel = this.program.makeLabel(); - - this.lowerExpr(expr.kind.cond); - - this.program.add(Ops.Not); - this.addClearingSourceMap(); - this.program.add(Ops.PushPtr, falseLabel); - this.program.add(Ops.JumpIfTrue); - - this.addSourceMap(expr.kind.truthy.pos); - this.lowerExpr(expr.kind.truthy); - - this.addClearingSourceMap(); - this.program.add(Ops.PushPtr, doneLabel); - this.program.add(Ops.Jump); - - this.program.setLabel(falseLabel); - - if (expr.kind.falsy) { - this.addSourceMap(expr.kind.elsePos!); - this.lowerExpr(expr.kind.falsy); - } else { - this.program.add(Ops.PushNull); - } - - this.program.setLabel(doneLabel); - } - - private lowerLoopExpr(expr: Expr) { - if (expr.kind.type !== "loop") { - throw new Error(); - } - const continueLabel = this.program.makeLabel(); - const breakLabel = this.program.makeLabel(); - - this.breakStack.push(breakLabel); - - this.program.setLabel(continueLabel); - this.addSourceMap(expr.kind.body.pos); - this.lowerExpr(expr.kind.body); - this.program.add(Ops.Pop); - this.addClearingSourceMap(); - this.program.add(Ops.PushPtr, continueLabel); - this.program.add(Ops.Jump); - this.program.setLabel(breakLabel); - if (expr.vtype!.type === "null") { - this.program.add(Ops.PushNull); - } - this.breakStack.pop(); - } - - private lowerBlockExpr(expr: Expr) { - if (expr.kind.type !== "block") { - throw new Error(); - } - const outerLocals = this.locals; - this.locals = new LocalLeaf(this.locals); - for (const stmt of expr.kind.stmts) { - this.addSourceMap(stmt.pos); - this.lowerStmt(stmt); - } - if (expr.kind.expr) { - this.addSourceMap(expr.kind.expr.pos); - this.lowerExpr(expr.kind.expr); - } else { - this.program.add(Ops.PushNull); - } - this.locals = outerLocals; - } -} diff --git a/compiler/resolver.ts b/compiler/resolver.ts index b6b067c..5f639a6 100644 --- a/compiler/resolver.ts +++ b/compiler/resolver.ts @@ -84,6 +84,7 @@ export class Resolver implements AstVisitor<[Syms]> { ident: param.ident, type: "generic", pos: param.pos, + stmt, genericParam: param, }); } diff --git a/examples/generic_array.slg b/examples/generic_array.slg new file mode 100644 index 0000000..11cb0bf --- /dev/null +++ b/examples/generic_array.slg @@ -0,0 +1,19 @@ + + +fn array_new() -> [T] #[builtin(ArrayNew)] {} +fn array_push(array: [T], value: T) #[builtin(ArrayPush)] {} +fn array_length(array: [T]) -> int #[builtin(ArrayLength)] {} +fn array_at(array: [T], index: int) -> string #[builtin(ArrayAt)] {} + + +fn main() { + let strings = array_new::(); + array_push::(strings, "hello"); + array_push::(strings, "world"); + + let ints = array_new::(); + array_push::(ints, 1); + array_push::(ints, 2); +} + +