Compare commits
	
		
			2 Commits
		
	
	
		
			34529d9cbb
			...
			0449a232c3
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 0449a232c3 | |||
| 87d46b6647 | 
| @ -101,7 +101,7 @@ export type SymKind = | |||||||
|     | { type: "fn"; stmt: Stmt } |     | { type: "fn"; stmt: Stmt } | ||||||
|     | { type: "fn_param"; param: Param } |     | { type: "fn_param"; param: Param } | ||||||
|     | { type: "closure"; inner: Sym } |     | { type: "closure"; inner: Sym } | ||||||
|     | { type: "generic"; genericParam: GenericParam }; |     | { type: "generic"; stmt: Stmt; genericParam: GenericParam }; | ||||||
| 
 | 
 | ||||||
| export type EType = { | export type EType = { | ||||||
|     kind: ETypeKind; |     kind: ETypeKind; | ||||||
| @ -125,6 +125,7 @@ export type ETypeKind = | |||||||
|     | { type: "struct"; fields: Param[] }; |     | { type: "struct"; fields: Param[] }; | ||||||
| 
 | 
 | ||||||
| export type GenericParam = { | export type GenericParam = { | ||||||
|  |     id: number; | ||||||
|     ident: string; |     ident: string; | ||||||
|     pos: Pos; |     pos: Pos; | ||||||
|     vtype?: VType; |     vtype?: VType; | ||||||
|  | |||||||
| @ -2,6 +2,8 @@ import { EType, Expr, Stmt } from "./ast.ts"; | |||||||
| import { printStackTrace, Reporter } from "./info.ts"; | import { printStackTrace, Reporter } from "./info.ts"; | ||||||
| import { Pos } from "./token.ts"; | import { Pos } from "./token.ts"; | ||||||
| import { | import { | ||||||
|  |     extractGenericType, | ||||||
|  |     GenericArgsMap, | ||||||
|     VType, |     VType, | ||||||
|     VTypeGenericParam, |     VTypeGenericParam, | ||||||
|     VTypeParam, |     VTypeParam, | ||||||
| @ -13,6 +15,8 @@ export class Checker { | |||||||
|     private fnReturnStack: VType[] = []; |     private fnReturnStack: VType[] = []; | ||||||
|     private loopBreakStack: VType[][] = []; |     private loopBreakStack: VType[][] = []; | ||||||
| 
 | 
 | ||||||
|  |     private globalIdToGenericParamMap = new Map<number, VTypeGenericParam>(); | ||||||
|  | 
 | ||||||
|     public constructor(private reporter: Reporter) {} |     public constructor(private reporter: Reporter) {} | ||||||
| 
 | 
 | ||||||
|     public check(stmts: Stmt[]) { |     public check(stmts: Stmt[]) { | ||||||
| @ -27,14 +31,15 @@ export class Checker { | |||||||
|             if (stmt.kind.type !== "fn") { |             if (stmt.kind.type !== "fn") { | ||||||
|                 continue; |                 continue; | ||||||
|             } |             } | ||||||
|             const returnType: VType = stmt.kind.returnType |  | ||||||
|                 ? this.checkEType(stmt.kind.returnType) |  | ||||||
|                 : { type: "null" }; |  | ||||||
|             let genericParams: VTypeGenericParam[] | undefined; |             let genericParams: VTypeGenericParam[] | undefined; | ||||||
|             if (stmt.kind.genericParams !== undefined) { |             if (stmt.kind.genericParams !== undefined) { | ||||||
|                 genericParams = []; |                 genericParams = []; | ||||||
|                 for (const etypeParam of stmt.kind.genericParams) { |                 for (const etypeParam of stmt.kind.genericParams) { | ||||||
|                     genericParams.push({ ident: etypeParam.ident }); |                     const id = genericParams.length; | ||||||
|  |                     const globalId = etypeParam.id; | ||||||
|  |                     const param = { id, ident: etypeParam.ident }; | ||||||
|  |                     genericParams.push(param); | ||||||
|  |                     this.globalIdToGenericParamMap.set(globalId, param); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             const params: VTypeParam[] = []; |             const params: VTypeParam[] = []; | ||||||
| @ -47,7 +52,16 @@ export class Checker { | |||||||
|                 param.vtype = vtype; |                 param.vtype = vtype; | ||||||
|                 params.push({ ident: param.ident, vtype }); |                 params.push({ ident: param.ident, vtype }); | ||||||
|             } |             } | ||||||
|             stmt.kind.vtype = { type: "fn", genericParams, params, returnType }; |             const returnType: VType = stmt.kind.returnType | ||||||
|  |                 ? this.checkEType(stmt.kind.returnType) | ||||||
|  |                 : { type: "null" }; | ||||||
|  |             stmt.kind.vtype = { | ||||||
|  |                 type: "fn", | ||||||
|  |                 genericParams, | ||||||
|  |                 params, | ||||||
|  |                 returnType, | ||||||
|  |                 stmtId: stmt.id, | ||||||
|  |             }; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -336,8 +350,7 @@ export class Checker { | |||||||
|                 if (vtype.type !== "fn") { |                 if (vtype.type !== "fn") { | ||||||
|                     throw new Error(); |                     throw new Error(); | ||||||
|                 } |                 } | ||||||
|                 const { params, returnType } = vtype; |                 return vtype; | ||||||
|                 return { type: "fn", params, returnType }; |  | ||||||
|             } |             } | ||||||
|             case "fn_param": |             case "fn_param": | ||||||
|                 return expr.kind.sym.param.vtype!; |                 return expr.kind.sym.param.vtype!; | ||||||
| @ -399,9 +412,10 @@ export class Checker { | |||||||
|         } |         } | ||||||
|         const pos = expr.pos; |         const pos = expr.pos; | ||||||
|         const subject = this.checkExpr(expr.kind.subject); |         const subject = this.checkExpr(expr.kind.subject); | ||||||
|         if (subject.type !== "fn") { |         if (subject.type === "error") return subject; | ||||||
|             this.report("cannot call non-fn", pos); |         if (subject.type === "fn") { | ||||||
|             return { type: "error" }; |             if (subject.genericParams !== undefined) { | ||||||
|  |                 throw new Error("😭😭😭"); | ||||||
|             } |             } | ||||||
|             const args = expr.kind.args.map((arg) => this.checkExpr(arg)); |             const args = expr.kind.args.map((arg) => this.checkExpr(arg)); | ||||||
|             if (args.length !== subject.params.length) { |             if (args.length !== subject.params.length) { | ||||||
| @ -415,7 +429,9 @@ export class Checker { | |||||||
|                 if (!vtypesEqual(args[i], subject.params[i].vtype)) { |                 if (!vtypesEqual(args[i], subject.params[i].vtype)) { | ||||||
|                     this.report( |                     this.report( | ||||||
|                         `incorrect argument ${i} '${subject.params[i].ident}'` + |                         `incorrect argument ${i} '${subject.params[i].ident}'` + | ||||||
|                         `, expected ${vtypeToString(subject.params[i].vtype)}` + |                             `, expected ${ | ||||||
|  |                                 vtypeToString(subject.params[i].vtype) | ||||||
|  |                             }` +
 | ||||||
|                             `, got ${vtypeToString(args[i])}`, |                             `, got ${vtypeToString(args[i])}`, | ||||||
|                         pos, |                         pos, | ||||||
|                     ); |                     ); | ||||||
| @ -424,6 +440,48 @@ export class Checker { | |||||||
|             } |             } | ||||||
|             return subject.returnType; |             return subject.returnType; | ||||||
|         } |         } | ||||||
|  |         if (subject.type === "generic_spec" && subject.subject.type === "fn") { | ||||||
|  |             const inner = subject.subject; | ||||||
|  |             const params = inner.params; | ||||||
|  |             const args = expr.kind.args.map((arg) => this.checkExpr(arg)); | ||||||
|  |             if (args.length !== params.length) { | ||||||
|  |                 this.report( | ||||||
|  |                     `incorrect number of arguments` + | ||||||
|  |                         `, expected ${params.length}`, | ||||||
|  |                     pos, | ||||||
|  |                 ); | ||||||
|  |             } | ||||||
|  |             for (let i = 0; i < args.length; ++i) { | ||||||
|  |                 const vtypeCompatible = vtypesEqual( | ||||||
|  |                     args[i], | ||||||
|  |                     params[i].vtype, | ||||||
|  |                     subject.genericArgs, | ||||||
|  |                 ); | ||||||
|  |                 if (!vtypeCompatible) { | ||||||
|  |                     this.report( | ||||||
|  |                         `incorrect argument ${i} '${inner.params[i].ident}'` + | ||||||
|  |                             `, expected ${ | ||||||
|  |                                 vtypeToString( | ||||||
|  |                                     extractGenericType( | ||||||
|  |                                         params[i].vtype, | ||||||
|  |                                         subject.genericArgs, | ||||||
|  |                                     ), | ||||||
|  |                                 ) | ||||||
|  |                             }` +
 | ||||||
|  |                             `, got ${vtypeToString(args[i])}`, | ||||||
|  |                         pos, | ||||||
|  |                     ); | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             return extractGenericType( | ||||||
|  |                 subject.subject.returnType, | ||||||
|  |                 subject.genericArgs, | ||||||
|  |             ); | ||||||
|  |         } | ||||||
|  |         this.report("cannot call non-fn", pos); | ||||||
|  |         return { type: "error" }; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     public checkPathExpr(expr: Expr): VType { |     public checkPathExpr(expr: Expr): VType { | ||||||
|         if (expr.kind.type !== "path") { |         if (expr.kind.type !== "path") { | ||||||
| @ -445,20 +503,23 @@ export class Checker { | |||||||
|             ); |             ); | ||||||
|             return { type: "error" }; |             return { type: "error" }; | ||||||
|         } |         } | ||||||
|         const genericParams = expr.kind.etypeArgs.map((arg) => |         const args = expr.kind.etypeArgs; | ||||||
|             this.checkEType(arg) |         if (args.length !== subject.genericParams.length) { | ||||||
|         ); |  | ||||||
|         if (genericParams.length !== subject.params.length) { |  | ||||||
|             this.report( |             this.report( | ||||||
|                 `incorrect number of arguments` + |                 `incorrect number of arguments` + | ||||||
|                     `, expected ${subject.params.length}`, |                     `, expected ${subject.params.length}`, | ||||||
|                 pos, |                 pos, | ||||||
|             ); |             ); | ||||||
|         } |         } | ||||||
|  |         const genericArgs: GenericArgsMap = {}; | ||||||
|  |         for (let i = 0; i < args.length; ++i) { | ||||||
|  |             const etype = this.checkEType(args[i]); | ||||||
|  |             genericArgs[subject.genericParams[i].id] = etype; | ||||||
|  |         } | ||||||
|         return { |         return { | ||||||
|             type: "generic_spec", |             type: "generic_spec", | ||||||
|             subject, |             subject, | ||||||
|             genericParams, |             genericArgs, | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -491,7 +552,9 @@ export class Checker { | |||||||
|         } |         } | ||||||
|         const pos = expr.pos; |         const pos = expr.pos; | ||||||
|         const left = this.checkExpr(expr.kind.left); |         const left = this.checkExpr(expr.kind.left); | ||||||
|  |         if (left.type === "error") return left; | ||||||
|         const right = this.checkExpr(expr.kind.right); |         const right = this.checkExpr(expr.kind.right); | ||||||
|  |         if (right.type === "error") return right; | ||||||
|         for (const operation of simpleBinaryOperations) { |         for (const operation of simpleBinaryOperations) { | ||||||
|             if (operation.binaryType !== expr.kind.binaryType) { |             if (operation.binaryType !== expr.kind.binaryType) { | ||||||
|                 continue; |                 continue; | ||||||
| @ -520,10 +583,13 @@ export class Checker { | |||||||
|         } |         } | ||||||
|         const pos = expr.pos; |         const pos = expr.pos; | ||||||
|         const cond = this.checkExpr(expr.kind.cond); |         const cond = this.checkExpr(expr.kind.cond); | ||||||
|  |         if (cond.type === "error") return cond; | ||||||
|         const truthy = this.checkExpr(expr.kind.truthy); |         const truthy = this.checkExpr(expr.kind.truthy); | ||||||
|  |         if (truthy.type === "error") return truthy; | ||||||
|         const falsy = expr.kind.falsy |         const falsy = expr.kind.falsy | ||||||
|             ? this.checkExpr(expr.kind.falsy) |             ? this.checkExpr(expr.kind.falsy) | ||||||
|             : undefined; |             : undefined; | ||||||
|  |         if (falsy?.type === "error") return falsy; | ||||||
|         if (cond.type !== "bool") { |         if (cond.type !== "bool") { | ||||||
|             this.report( |             this.report( | ||||||
|                 `if condition should be 'bool', got '${vtypeToString(cond)}'`, |                 `if condition should be 'bool', got '${vtypeToString(cond)}'`, | ||||||
| @ -626,7 +692,12 @@ export class Checker { | |||||||
|         } |         } | ||||||
|         if (etype.kind.type === "sym") { |         if (etype.kind.type === "sym") { | ||||||
|             if (etype.kind.sym.type === "generic") { |             if (etype.kind.sym.type === "generic") { | ||||||
|                 return { type: "generic" }; |                 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); |             this.report(`sym type '${etype.kind.sym.type}' used as type`, pos); | ||||||
|             return { type: "error" }; |             return { type: "error" }; | ||||||
|  | |||||||
| @ -4,6 +4,7 @@ import { CompoundAssignDesugarer } from "./desugar/compound_assign.ts"; | |||||||
| import { SpecialLoopDesugarer } from "./desugar/special_loop.ts"; | import { SpecialLoopDesugarer } from "./desugar/special_loop.ts"; | ||||||
| import { Reporter } from "./info.ts"; | import { Reporter } from "./info.ts"; | ||||||
| import { Lexer } from "./lexer.ts"; | import { Lexer } from "./lexer.ts"; | ||||||
|  | import { Monomorphizer } from "./mono.ts"; | ||||||
| import { FnNamesMap, Lowerer } from "./lowerer.ts"; | import { FnNamesMap, Lowerer } from "./lowerer.ts"; | ||||||
| import { Parser } from "./parser.ts"; | import { Parser } from "./parser.ts"; | ||||||
| import { Resolver } from "./resolver.ts"; | import { Resolver } from "./resolver.ts"; | ||||||
| @ -45,10 +46,11 @@ export class Compiler { | |||||||
|             Deno.exit(1); |             Deno.exit(1); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const lowerer = new Lowerer(lexer.currentPos()); |         const { monoFns, callMap } = new Monomorphizer(ast).monomorphize(); | ||||||
|         lowerer.lower(ast); | 
 | ||||||
|  |         const lowerer = new Lowerer(monoFns, callMap, lexer.currentPos()); | ||||||
|  |         const { program, fnNames } = lowerer.lower(); | ||||||
|         //lowerer.printProgram();
 |         //lowerer.printProgram();
 | ||||||
|         const { program, fnNames } = lowerer.finish(); |  | ||||||
| 
 | 
 | ||||||
|         return { program, fnNames }; |         return { program, fnNames }; | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -42,7 +42,7 @@ export function printStackTrace() { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     try { |     try { | ||||||
|         //throw new ReportNotAnError();
 |         throw new ReportNotAnError(); | ||||||
|     } catch (error) { |     } catch (error) { | ||||||
|         if (!(error instanceof ReportNotAnError)) { |         if (!(error instanceof ReportNotAnError)) { | ||||||
|             throw error; |             throw error; | ||||||
|  | |||||||
| @ -1,48 +1,65 @@ | |||||||
| import { Builtins, Ops } from "./arch.ts"; | import { Builtins, Ops } from "./arch.ts"; | ||||||
|  | import { Assembler, Label } from "./assembler.ts"; | ||||||
| import { Expr, Stmt } from "./ast.ts"; | import { Expr, Stmt } from "./ast.ts"; | ||||||
| import { LocalLeaf, Locals, LocalsFnRoot } from "./lowerer_locals.ts"; | import { LocalLeaf, Locals, LocalsFnRoot } from "./lowerer_locals.ts"; | ||||||
| import { Assembler, Label } from "./assembler.ts"; | import { MonoCallNameGenMap, MonoFn, MonoFnsMap } from "./mono.ts"; | ||||||
| import { vtypeToString } from "./vtype.ts"; |  | ||||||
| import { Pos } from "./token.ts"; | import { Pos } from "./token.ts"; | ||||||
|  | import { vtypeToString } from "./vtype.ts"; | ||||||
| 
 | 
 | ||||||
| export type FnNamesMap = { [pc: number]: string }; | export type FnNamesMap = { [pc: number]: string }; | ||||||
| 
 | 
 | ||||||
| export class Lowerer { | export class Lowerer { | ||||||
|     private program = Assembler.newRoot(); |     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.addClearingSourceMap(); | ||||||
|         this.program.add(Ops.PushPtr, { label: "main" }); |         this.program.add(Ops.PushPtr, { label: "main" }); | ||||||
|         this.program.add(Ops.Call, 0); |         this.program.add(Ops.Call, 0); | ||||||
|         this.program.add(Ops.PushPtr, { label: "_exit" }); |         this.program.add(Ops.PushPtr, { label: "_exit" }); | ||||||
|         this.program.add(Ops.Jump); |         this.program.add(Ops.Jump); | ||||||
|         this.scoutFnHeaders(stmts); |  | ||||||
|         for (const stmt of stmts) { |  | ||||||
|             this.lowerStaticStmt(stmt); |  | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     private addConcluding() { | ||||||
|         this.program.setLabel({ label: "_exit" }); |         this.program.setLabel({ label: "_exit" }); | ||||||
|         this.addSourceMap(this.lastPos); |         this.addSourceMap(this.lastPos); | ||||||
|         this.program.add(Ops.Pop); |         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) { |     private addSourceMap({ index, line, col }: Pos) { | ||||||
|         this.program.add(Ops.SourceMap, index, line, col); |         this.program.add(Ops.SourceMap, index, line, col); | ||||||
|     } |     } | ||||||
| @ -51,30 +68,78 @@ export class Lowerer { | |||||||
|         this.program.add(Ops.SourceMap, 0, 1, 1); |         this.program.add(Ops.SourceMap, 0, 1, 1); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private scoutFnHeaders(stmts: Stmt[]) { |     public printProgram() { | ||||||
|         for (const stmt of stmts) { |         this.program.printProgram(); | ||||||
|             if (stmt.kind.type !== "fn") { |  | ||||||
|                 continue; |  | ||||||
|             } |  | ||||||
|             const label = stmt.kind.ident === "main" |  | ||||||
|                 ? "main" |  | ||||||
|                 : `${stmt.kind.ident}_${stmt.id}`; |  | ||||||
|             this.fnStmtIdLabelMap[stmt.id] = label; |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|     private lowerStaticStmt(stmt: Stmt) { | type FnLabelMap = { [nameGen: string]: string }; | ||||||
|         switch (stmt.kind.type) { | 
 | ||||||
|             case "fn": | class MonoFnLowerer { | ||||||
|                 return this.lowerFnStmt(stmt); |     private locals: Locals = new LocalsFnRoot(); | ||||||
|             case "error": |     private returnStack: Label[] = []; | ||||||
|             case "break": |     private breakStack: Label[] = []; | ||||||
|             case "return": | 
 | ||||||
|             case "let": |     public constructor( | ||||||
|             case "assign": |         private fn: MonoFn, | ||||||
|             case "expr": |         private program: Assembler, | ||||||
|  |         private callMap: MonoCallNameGenMap, | ||||||
|  |     ) {} | ||||||
|  | 
 | ||||||
|  |     public lower(): Assembler { | ||||||
|  |         this.lowerFnStmt(this.fn.stmt); | ||||||
|  |         return this.program; | ||||||
|     } |     } | ||||||
|         throw new Error(`unhandled static statement '${stmt.kind.type}'`); | 
 | ||||||
|  |     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) { |     private lowerStmt(stmt: Stmt) { | ||||||
| @ -153,52 +218,6 @@ export class Lowerer { | |||||||
|         this.program.add(Ops.Jump); |         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[]) { |     private lowerFnBuiltinBody(annoArgs: Expr[]) { | ||||||
|         if (annoArgs.length !== 1) { |         if (annoArgs.length !== 1) { | ||||||
|             throw new Error("invalid # of arguments to builtin annotation"); |             throw new Error("invalid # of arguments to builtin annotation"); | ||||||
| @ -257,6 +276,8 @@ export class Lowerer { | |||||||
|                 return this.lowerIndexExpr(expr); |                 return this.lowerIndexExpr(expr); | ||||||
|             case "call": |             case "call": | ||||||
|                 return this.lowerCallExpr(expr); |                 return this.lowerCallExpr(expr); | ||||||
|  |             case "etype_args": | ||||||
|  |                 return this.lowerETypeArgsExpr(expr); | ||||||
|             case "unary": |             case "unary": | ||||||
|                 return this.lowerUnaryExpr(expr); |                 return this.lowerUnaryExpr(expr); | ||||||
|             case "binary": |             case "binary": | ||||||
| @ -306,8 +327,42 @@ export class Lowerer { | |||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|         if (expr.kind.sym.type === "fn") { |         if (expr.kind.sym.type === "fn") { | ||||||
|             const label = this.fnStmtIdLabelMap[expr.kind.sym.stmt.id]; |             // Is this smart? Well, my presumption is
 | ||||||
|             this.program.add(Ops.PushPtr, { label }); |             // 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; |             return; | ||||||
|         } |         } | ||||||
|         throw new Error(`unhandled sym type '${expr.kind.sym.type}'`); |         throw new Error(`unhandled sym type '${expr.kind.sym.type}'`); | ||||||
| @ -430,6 +485,18 @@ export class Lowerer { | |||||||
|                 default: |                 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 (vtype.type === "string") { | ||||||
|             if (expr.kind.binaryType === "+") { |             if (expr.kind.binaryType === "+") { | ||||||
|                 this.program.add(Ops.Builtin, Builtins.StringConcat); |                 this.program.add(Ops.Builtin, Builtins.StringConcat); | ||||||
| @ -462,9 +529,18 @@ export class Lowerer { | |||||||
|             this.lowerExpr(arg); |             this.lowerExpr(arg); | ||||||
|         } |         } | ||||||
|         this.lowerExpr(expr.kind.subject); |         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); |         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) { |     private lowerIfExpr(expr: Expr) { | ||||||
|         if (expr.kind.type !== "if") { |         if (expr.kind.type !== "if") { | ||||||
|             throw new Error(); |             throw new Error(); | ||||||
| @ -528,7 +604,6 @@ export class Lowerer { | |||||||
|         } |         } | ||||||
|         const outerLocals = this.locals; |         const outerLocals = this.locals; | ||||||
|         this.locals = new LocalLeaf(this.locals); |         this.locals = new LocalLeaf(this.locals); | ||||||
|         this.scoutFnHeaders(expr.kind.stmts); |  | ||||||
|         for (const stmt of expr.kind.stmts) { |         for (const stmt of expr.kind.stmts) { | ||||||
|             this.addSourceMap(stmt.pos); |             this.addSourceMap(stmt.pos); | ||||||
|             this.lowerStmt(stmt); |             this.lowerStmt(stmt); | ||||||
| @ -541,8 +616,4 @@ export class Lowerer { | |||||||
|         } |         } | ||||||
|         this.locals = outerLocals; |         this.locals = outerLocals; | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     public printProgram() { |  | ||||||
|         this.program.printProgram(); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										262
									
								
								compiler/mono.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										262
									
								
								compiler/mono.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,262 @@ | |||||||
|  | import { Expr, Stmt } from "./ast.ts"; | ||||||
|  | import { AstVisitor, visitExpr, VisitRes, visitStmts } from "./ast_visitor.ts"; | ||||||
|  | import { GenericArgsMap, VType } from "./vtype.ts"; | ||||||
|  | 
 | ||||||
|  | export class Monomorphizer { | ||||||
|  |     private fns: MonoFnsMap = {}; | ||||||
|  |     private callMap: MonoCallNameGenMap = {}; | ||||||
|  |     private allFns: Map<number, Stmt>; | ||||||
|  |     private entryFn: Stmt; | ||||||
|  | 
 | ||||||
|  |     constructor(private ast: Stmt[]) { | ||||||
|  |         this.allFns = new AllFnsCollector().collect(this.ast); | ||||||
|  |         this.entryFn = findMain(this.allFns); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public monomorphize(): MonoResult { | ||||||
|  |         this.monomorphizeFn(this.entryFn); | ||||||
|  |         return { monoFns: this.fns, callMap: this.callMap }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private monomorphizeFn( | ||||||
|  |         stmt: Stmt, | ||||||
|  |         genericArgs?: GenericArgsMap, | ||||||
|  |     ): MonoFn { | ||||||
|  |         const nameGen = monoFnNameGen(stmt, genericArgs); | ||||||
|  |         if (nameGen in this.fns) { | ||||||
|  |             return this.fns[nameGen]; | ||||||
|  |         } | ||||||
|  |         const monoFn = { nameGen, stmt, genericArgs }; | ||||||
|  |         this.fns[nameGen] = monoFn; | ||||||
|  |         const calls = new CallCollector().collect(stmt); | ||||||
|  |         for (const call of calls) { | ||||||
|  |             this.callMap[call.id] = nameGen; | ||||||
|  |             if (call.kind.type !== "call") { | ||||||
|  |                 throw new Error(); | ||||||
|  |             } | ||||||
|  |             if (call.kind.subject.vtype?.type === "fn") { | ||||||
|  |                 const fn = this.allFns.get(call.kind.subject.vtype.stmtId); | ||||||
|  |                 if (fn === undefined) { | ||||||
|  |                     throw new Error(); | ||||||
|  |                 } | ||||||
|  |                 const monoFn = this.monomorphizeFn(fn); | ||||||
|  |                 this.callMap[call.id] = monoFn.nameGen; | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  |             if (call.kind.subject.vtype?.type === "generic_spec") { | ||||||
|  |                 const genericSpecType = call.kind.subject.vtype!; | ||||||
|  |                 if (genericSpecType.subject.type !== "fn") { | ||||||
|  |                     throw new Error(); | ||||||
|  |                 } | ||||||
|  |                 const fnType = genericSpecType.subject; | ||||||
|  | 
 | ||||||
|  |                 const monoArgs: GenericArgsMap = {}; | ||||||
|  |                 for (const key in genericSpecType.genericArgs) { | ||||||
|  |                     const vtype = genericSpecType.genericArgs[key]; | ||||||
|  |                     if (vtype.type === "generic") { | ||||||
|  |                         if (genericArgs === undefined) { | ||||||
|  |                             throw new Error(); | ||||||
|  |                         } | ||||||
|  |                         monoArgs[key] = genericArgs[vtype.param.id]; | ||||||
|  |                     } else { | ||||||
|  |                         monoArgs[key] = vtype; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 const fn = this.allFns.get(fnType.stmtId); | ||||||
|  |                 if (fn === undefined) { | ||||||
|  |                     throw new Error(); | ||||||
|  |                 } | ||||||
|  |                 const monoFn = this.monomorphizeFn(fn, monoArgs); | ||||||
|  |                 this.callMap[call.id] = monoFn.nameGen; | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  |             throw new Error(); | ||||||
|  |         } | ||||||
|  |         return monoFn; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export type MonoResult = { | ||||||
|  |     monoFns: MonoFnsMap; | ||||||
|  |     callMap: MonoCallNameGenMap; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export type MonoFnsMap = { [nameGen: string]: MonoFn }; | ||||||
|  | 
 | ||||||
|  | export type MonoFn = { | ||||||
|  |     nameGen: string; | ||||||
|  |     stmt: Stmt; | ||||||
|  |     genericArgs?: GenericArgsMap; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export type MonoCallNameGenMap = { [exprId: number]: string }; | ||||||
|  | 
 | ||||||
|  | function monoFnNameGen(stmt: Stmt, genericArgs?: GenericArgsMap): string { | ||||||
|  |     if (stmt.kind.type !== "fn") { | ||||||
|  |         throw new Error(); | ||||||
|  |     } | ||||||
|  |     if (stmt.kind.ident === "main") { | ||||||
|  |         return "main"; | ||||||
|  |     } | ||||||
|  |     if (genericArgs === undefined) { | ||||||
|  |         return `${stmt.kind.ident}_${stmt.id}`; | ||||||
|  |     } | ||||||
|  |     const args = Object.values(genericArgs) | ||||||
|  |         .map((arg) => vtypeNameGenPart(arg)) | ||||||
|  |         .join("_"); | ||||||
|  |     return `${stmt.kind.ident}_${stmt.id}_${args}`; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function vtypeNameGenPart(vtype: VType): string { | ||||||
|  |     switch (vtype.type) { | ||||||
|  |         case "error": | ||||||
|  |             throw new Error("error in type"); | ||||||
|  |         case "string": | ||||||
|  |         case "int": | ||||||
|  |         case "bool": | ||||||
|  |         case "null": | ||||||
|  |         case "unknown": | ||||||
|  |             return vtype.type; | ||||||
|  |         case "array": | ||||||
|  |             return `[${vtypeNameGenPart(vtype.inner)}]`; | ||||||
|  |         case "struct": { | ||||||
|  |             const fields = vtype.fields | ||||||
|  |                 .map((field) => | ||||||
|  |                     `${field.ident}, ${vtypeNameGenPart(field.vtype)}` | ||||||
|  |                 ) | ||||||
|  |                 .join(", "); | ||||||
|  |             return `struct { ${fields} }`; | ||||||
|  |         } | ||||||
|  |         case "fn": | ||||||
|  |             return `fn(${vtype.stmtId})`; | ||||||
|  |         case "generic": | ||||||
|  |         case "generic_spec": | ||||||
|  |             throw new Error("cannot be monomorphized"); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class AllFnsCollector implements AstVisitor { | ||||||
|  |     private allFns = new Map<number, Stmt>(); | ||||||
|  | 
 | ||||||
|  |     public collect(ast: Stmt[]): Map<number, Stmt> { | ||||||
|  |         visitStmts(ast, this); | ||||||
|  |         return this.allFns; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     visitFnStmt(stmt: Stmt): VisitRes { | ||||||
|  |         if (stmt.kind.type !== "fn") { | ||||||
|  |             throw new Error(); | ||||||
|  |         } | ||||||
|  |         this.allFns.set(stmt.id, stmt); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function findMain(fns: Map<number, Stmt>): Stmt { | ||||||
|  |     const mainId = fns.values().find((stmt) => | ||||||
|  |         stmt.kind.type === "fn" && stmt.kind.ident === "main" | ||||||
|  |     ); | ||||||
|  |     if (mainId === undefined) { | ||||||
|  |         console.error("error: cannot find function 'main'"); | ||||||
|  |         console.error(apology); | ||||||
|  |         throw new Error("cannot find function 'main'"); | ||||||
|  |     } | ||||||
|  |     return mainId; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class CallCollector implements AstVisitor { | ||||||
|  |     private calls: Expr[] = []; | ||||||
|  | 
 | ||||||
|  |     public collect(fn: Stmt): Expr[] { | ||||||
|  |         if (fn.kind.type !== "fn") { | ||||||
|  |             throw new Error(); | ||||||
|  |         } | ||||||
|  |         visitExpr(fn.kind.body, this); | ||||||
|  |         return this.calls; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     visitFnStmt(_stmt: Stmt): VisitRes { | ||||||
|  |         return "stop"; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     visitCallExpr(expr: Expr): VisitRes { | ||||||
|  |         if (expr.kind.type !== "call") { | ||||||
|  |             throw new Error(); | ||||||
|  |         } | ||||||
|  |         this.calls.push(expr); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const apology = ` | ||||||
|  |     Hear me out. Monomorphization, meaning the process | ||||||
|  |     inwich generic functions are stamped out into seperate | ||||||
|  |     specialized functions is actually really hard, and I | ||||||
|  |     have a really hard time right now, figuring out, how | ||||||
|  |     to do it in a smart way. To really explain it, let's | ||||||
|  |     imagine you have a function, you defined as a<T>(). | ||||||
|  |     For each call with seperate generics arguments given, | ||||||
|  |     such as a::<int>() and a::<string>(), a specialized | ||||||
|  |     function has to be 'stamped out', ie. created and put | ||||||
|  |     into the compilation with the rest of the program. Now | ||||||
|  |     to the reason as to why 'main' is needed. To do the | ||||||
|  |     monomorphization, we have to do it recursively. To | ||||||
|  |     explain this, imagine you have a generic function a<T> | ||||||
|  |     and inside the body of a<T>, you call another generic | ||||||
|  |     function such as b<T> with the same generic type. This | ||||||
|  |     means that the monomorphization process of b<T> depends | ||||||
|  |     on the monomorphization of a<T>. What this essentially | ||||||
|  |     means, is that the monomorphization process works on | ||||||
|  |     the program as a call graph, meaning a graph or tree | ||||||
|  |     structure where each represents a function call to | ||||||
|  |     either another function or a recursive call to the | ||||||
|  |     function itself. But a problem arises from doing it | ||||||
|  |     this way, which is that a call graph will need an | ||||||
|  |     entrypoint. The language, as it is currently, does | ||||||
|  |     not really require a 'main'-function. Or maybe it | ||||||
|  |     does, but that's beside the point. The point is that | ||||||
|  |     we need a main function, to be the entry point for | ||||||
|  |     the call graph. The monomorphization process then | ||||||
|  |     runs through the program from that entry point. This | ||||||
|  |     means that each function we call, will itself be | ||||||
|  |     monomorphized and added to the compilation. It also | ||||||
|  |     means that functions that are not called, will also | ||||||
|  |     not be added to the compilation. This essentially | ||||||
|  |     eliminates uncalled/dead functions. Is this | ||||||
|  |     particularly smart to do in such a high level part | ||||||
|  |     of the compilation process? I don't know. It's | ||||||
|  |     obvious that we can't just use every function as | ||||||
|  |     an entry point in the call graph, because we're | ||||||
|  |     actively added new functions. Additionally, with | ||||||
|  |     generic functions, we don't know, if they're the | ||||||
|  |     entry point, what generic arguments, they should | ||||||
|  |     be monomorphized with. We could do monomorphization | ||||||
|  |     the same way C++ does it, where all non-generic | ||||||
|  |     functions before monomorphization are treated as | ||||||
|  |     entry points in the call graph. But this has the | ||||||
|  |     drawback that generic and non-generic functions | ||||||
|  |     are treated differently, which has many underlying | ||||||
|  |     drawbacks, especially pertaining to the amount of | ||||||
|  |     work needed to handle both in all proceeding steps | ||||||
|  |     of the compiler. Anyways, I just wanted to yap and | ||||||
|  |     complain about the way generics and monomorphization | ||||||
|  |     has made the compiler 100x more complicated, and | ||||||
|  |     that I find it really hard to implement in a way, | ||||||
|  |     that is not either too simplistic or so complicated | ||||||
|  |     and advanced I'm too dumb to implement it. So if | ||||||
|  |     you would be so kind as to make it clear to the | ||||||
|  |     compiler, what function it should designate as | ||||||
|  |     the entry point to the call graph, it will use | ||||||
|  |     for monomorphization, that would be very kind of | ||||||
|  |     you. The way you do this, is by added or selecting | ||||||
|  |     one of your current functions and giving it the | ||||||
|  |     name of 'main'. This is spelled m-a-i-n. The word | ||||||
|  |     is synonemous with the words primary and principle. | ||||||
|  |     The name is meant to designate the entry point into | ||||||
|  |     the program, which is why the monomorphization | ||||||
|  |     process uses this specific function as the entry | ||||||
|  |     point into the call graph, it generates. So if you | ||||||
|  |     would be so kind as to do that, that would really | ||||||
|  |     make my day. In any case, keep hacking ferociously | ||||||
|  |     on whatever you're working on. I have monomorphizer | ||||||
|  |     to implement. See ya. -Your favorite compiler girl <3 | ||||||
|  | `.replaceAll("    ", "").trim();
 | ||||||
| @ -269,12 +269,16 @@ export class Parser { | |||||||
|         return this.parseDelimitedList(this.parseETypeParam, ">", ","); |         return this.parseDelimitedList(this.parseETypeParam, ">", ","); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     private veryTemporaryETypeParamIdCounter = 0; | ||||||
|  | 
 | ||||||
|     private parseETypeParam(): Res<GenericParam> { |     private parseETypeParam(): Res<GenericParam> { | ||||||
|         const pos = this.pos(); |         const pos = this.pos(); | ||||||
|         if (this.test("ident")) { |         if (this.test("ident")) { | ||||||
|             const ident = this.current().identValue!; |             const ident = this.current().identValue!; | ||||||
|             this.step(); |             this.step(); | ||||||
|             return { ok: true, value: { ident, pos } }; |             const id = this.veryTemporaryETypeParamIdCounter; | ||||||
|  |             this.veryTemporaryETypeParamIdCounter += 1; | ||||||
|  |             return { ok: true, value: { id, ident, pos } }; | ||||||
|         } |         } | ||||||
|         this.report("expected generic parameter"); |         this.report("expected generic parameter"); | ||||||
|         return { ok: false }; |         return { ok: false }; | ||||||
|  | |||||||
| @ -84,6 +84,7 @@ export class Resolver implements AstVisitor<[Syms]> { | |||||||
|                 ident: param.ident, |                 ident: param.ident, | ||||||
|                 type: "generic", |                 type: "generic", | ||||||
|                 pos: param.pos, |                 pos: param.pos, | ||||||
|  |                 stmt, | ||||||
|                 genericParam: param, |                 genericParam: param, | ||||||
|             }); |             }); | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -12,12 +12,13 @@ export type VType = | |||||||
|         genericParams?: VTypeGenericParam[]; |         genericParams?: VTypeGenericParam[]; | ||||||
|         params: VTypeParam[]; |         params: VTypeParam[]; | ||||||
|         returnType: VType; |         returnType: VType; | ||||||
|  |         stmtId: number; | ||||||
|     } |     } | ||||||
|     | { type: "generic" } |     | { type: "generic"; param: VTypeGenericParam } | ||||||
|     | { |     | { | ||||||
|         type: "generic_spec"; |         type: "generic_spec"; | ||||||
|         subject: VType; |         subject: VType; | ||||||
|         genericParams: VType[]; |         genericArgs: GenericArgsMap; | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
| export type VTypeParam = { | export type VTypeParam = { | ||||||
| @ -26,21 +27,25 @@ export type VTypeParam = { | |||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export type VTypeGenericParam = { | export type VTypeGenericParam = { | ||||||
|  |     id: number; | ||||||
|     ident: string; |     ident: string; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export function vtypesEqual(a: VType, b: VType): boolean { | export type GenericArgsMap = { [id: number]: VType }; | ||||||
|     if (a.type !== b.type) { | 
 | ||||||
|         return false; | export function vtypesEqual( | ||||||
|     } |     a: VType, | ||||||
|  |     b: VType, | ||||||
|  |     generics?: GenericArgsMap, | ||||||
|  | ): boolean { | ||||||
|     if ( |     if ( | ||||||
|         ["error", "unknown", "null", "int", "string", "bool"] |         ["error", "unknown", "null", "int", "string", "bool"] | ||||||
|             .includes(a.type) |             .includes(a.type) && a.type === b.type | ||||||
|     ) { |     ) { | ||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
|     if (a.type === "array" && b.type === "array") { |     if (a.type === "array" && b.type === "array") { | ||||||
|         return vtypesEqual(a.inner, b.inner); |         return vtypesEqual(a.inner, b.inner, generics); | ||||||
|     } |     } | ||||||
|     if (a.type === "fn" && b.type === "fn") { |     if (a.type === "fn" && b.type === "fn") { | ||||||
|         if (a.params.length !== b.params.length) { |         if (a.params.length !== b.params.length) { | ||||||
| @ -51,11 +56,41 @@ export function vtypesEqual(a: VType, b: VType): boolean { | |||||||
|                 return false; |                 return false; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         return vtypesEqual(a.returnType, b.returnType); |         return vtypesEqual(a.returnType, b.returnType, generics); | ||||||
|  |     } | ||||||
|  |     if (a.type === "generic" && b.type === "generic") { | ||||||
|  |         return a.param.id === b.param.id; | ||||||
|  |     } | ||||||
|  |     if ( | ||||||
|  |         (a.type === "generic" || b.type === "generic") && | ||||||
|  |         generics !== undefined | ||||||
|  |     ) { | ||||||
|  |         if (generics === undefined) { | ||||||
|  |             throw new Error(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const generic = a.type === "generic" ? a : b; | ||||||
|  |         const concrete = a.type === "generic" ? b : a; | ||||||
|  | 
 | ||||||
|  |         const genericType = extractGenericType(generic, generics); | ||||||
|  |         return vtypesEqual(genericType, concrete, generics); | ||||||
|     } |     } | ||||||
|     return false; |     return false; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | export function extractGenericType( | ||||||
|  |     generic: VType, | ||||||
|  |     generics: GenericArgsMap, | ||||||
|  | ): VType { | ||||||
|  |     if (generic.type !== "generic") { | ||||||
|  |         return generic; | ||||||
|  |     } | ||||||
|  |     if (!(generic.param.id in generics)) { | ||||||
|  |         throw new Error("generic not found (not supposed to happen)"); | ||||||
|  |     } | ||||||
|  |     return generics[generic.param.id]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| export function vtypeToString(vtype: VType): string { | export function vtypeToString(vtype: VType): string { | ||||||
|     if ( |     if ( | ||||||
|         ["error", "unknown", "null", "int", "string", "bool"] |         ["error", "unknown", "null", "int", "string", "bool"] | ||||||
|  | |||||||
							
								
								
									
										19
									
								
								examples/generic_array.slg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								examples/generic_array.slg
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,19 @@ | |||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | fn array_new<T>() -> [T] #[builtin(ArrayNew)] {} | ||||||
|  | fn array_push<T>(array: [T], value: T) #[builtin(ArrayPush)] {} | ||||||
|  | fn array_length<T>(array: [T]) -> int #[builtin(ArrayLength)] {} | ||||||
|  | fn array_at<T>(array: [T], index: int) -> string #[builtin(ArrayAt)] {} | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | fn main() { | ||||||
|  |     let strings = array_new::<string>(); | ||||||
|  |     array_push::<string>(strings, "hello"); | ||||||
|  |     array_push::<string>(strings, "world"); | ||||||
|  | 
 | ||||||
|  |     let ints = array_new::<int>(); | ||||||
|  |     array_push::<int>(ints, 1); | ||||||
|  |     array_push::<int>(ints, 2); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| @ -1,4 +1,6 @@ | |||||||
| 
 | 
 | ||||||
|  | fn exit(status_code: int) #[builtin(Exit)] {} | ||||||
|  | 
 | ||||||
| fn print(msg: string) #[builtin(Print)] {} | fn print(msg: string) #[builtin(Print)] {} | ||||||
| fn println(msg: string) { print(msg + "\n") } | fn println(msg: string) { print(msg + "\n") } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,17 +1,23 @@ | |||||||
| 
 | 
 | ||||||
| fn exit(status_code: int) #[builtin(Exit)] {} | fn exit(status_code: int) #[builtin(Exit)] {} | ||||||
| 
 | 
 | ||||||
|  | fn print(msg: string) #[builtin(Print)] {} | ||||||
|  | fn println(msg: string) { print(msg + "\n") } | ||||||
|  | 
 | ||||||
| fn id<T>(v: T) -> T { | fn id<T>(v: T) -> T { | ||||||
|     v |     v | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fn main() { | fn main() { | ||||||
|  |     println("calling with int"); | ||||||
|     if id::<int>(123) != 123 { |     if id::<int>(123) != 123 { | ||||||
|         exit(1); |         exit(1); | ||||||
|     } |     } | ||||||
|  |     println("calling with bool"); | ||||||
|     if id::<bool>(true) != true { |     if id::<bool>(true) != true { | ||||||
|         exit(1); |         exit(1); | ||||||
|     } |     } | ||||||
|  |     println("all tests ran successfully"); | ||||||
|     exit(0); |     exit(0); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user