mirror of
https://git.sfja.dk/Mikkel/slige.git
synced 2025-01-18 13:06:30 +00:00
karlkodet generics
This commit is contained in:
parent
cab2c9baa3
commit
5150090d2d
@ -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;
|
||||||
|
@ -15,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[]) {
|
||||||
@ -29,15 +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) {
|
||||||
const id = genericParams.length;
|
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[] = [];
|
const params: VTypeParam[] = [];
|
||||||
@ -50,6 +52,9 @@ export class Checker {
|
|||||||
param.vtype = vtype;
|
param.vtype = vtype;
|
||||||
params.push({ ident: param.ident, vtype });
|
params.push({ ident: param.ident, vtype });
|
||||||
}
|
}
|
||||||
|
const returnType: VType = stmt.kind.returnType
|
||||||
|
? this.checkEType(stmt.kind.returnType)
|
||||||
|
: { type: "null" };
|
||||||
stmt.kind.vtype = {
|
stmt.kind.vtype = {
|
||||||
type: "fn",
|
type: "fn",
|
||||||
genericParams,
|
genericParams,
|
||||||
@ -499,7 +504,7 @@ export class Checker {
|
|||||||
return { type: "error" };
|
return { type: "error" };
|
||||||
}
|
}
|
||||||
const args = expr.kind.etypeArgs;
|
const args = expr.kind.etypeArgs;
|
||||||
if (args.length !== subject.params.length) {
|
if (args.length !== subject.genericParams.length) {
|
||||||
this.report(
|
this.report(
|
||||||
`incorrect number of arguments` +
|
`incorrect number of arguments` +
|
||||||
`, expected ${subject.params.length}`,
|
`, expected ${subject.params.length}`,
|
||||||
@ -687,7 +692,11 @@ 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") {
|
||||||
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 } };
|
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);
|
||||||
|
@ -4,9 +4,8 @@ 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 { FnNamesMap, Lowerer } from "./lowerer.ts";
|
|
||||||
import { Monomorphizer } from "./mono.ts";
|
import { Monomorphizer } from "./mono.ts";
|
||||||
import { MonoLowerer } from "./mono_lower.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";
|
||||||
|
|
||||||
@ -49,14 +48,9 @@ export class Compiler {
|
|||||||
|
|
||||||
const { monoFns, callMap } = new Monomorphizer(ast).monomorphize();
|
const { monoFns, callMap } = new Monomorphizer(ast).monomorphize();
|
||||||
|
|
||||||
//const lowerer = new Lowerer(lexer.currentPos());
|
const lowerer = new Lowerer(monoFns, callMap, lexer.currentPos());
|
||||||
//lowerer.lower(ast);
|
|
||||||
//// lowerer.printProgram();
|
|
||||||
//const { program, fnNames } = lowerer.finish();
|
|
||||||
|
|
||||||
const lowerer = new MonoLowerer(monoFns, callMap, lexer.currentPos());
|
|
||||||
const { program, fnNames } = lowerer.lower();
|
const { program, fnNames } = lowerer.lower();
|
||||||
lowerer.printProgram();
|
//lowerer.printProgram();
|
||||||
|
|
||||||
return { program, fnNames };
|
return { program, fnNames };
|
||||||
}
|
}
|
||||||
|
@ -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"
|
type FnLabelMap = { [nameGen: string]: string };
|
||||||
? "main"
|
|
||||||
: `${stmt.kind.ident}_${stmt.id}`;
|
class MonoFnLowerer {
|
||||||
this.fnStmtIdLabelMap[stmt.id] = label;
|
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) {
|
private lowerFnStmt(stmt: Stmt) {
|
||||||
switch (stmt.kind.type) {
|
if (stmt.kind.type !== "fn") {
|
||||||
case "fn":
|
throw new Error();
|
||||||
return this.lowerFnStmt(stmt);
|
|
||||||
case "error":
|
|
||||||
case "break":
|
|
||||||
case "return":
|
|
||||||
case "let":
|
|
||||||
case "assign":
|
|
||||||
case "expr":
|
|
||||||
}
|
}
|
||||||
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) {
|
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");
|
||||||
@ -308,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}'`);
|
||||||
@ -432,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);
|
||||||
@ -464,6 +529,8 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -471,7 +538,7 @@ export class Lowerer {
|
|||||||
if (expr.kind.type !== "etype_args") {
|
if (expr.kind.type !== "etype_args") {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
throw new Error("not implemented");
|
this.lowerExpr(expr.kind.subject);
|
||||||
}
|
}
|
||||||
|
|
||||||
private lowerIfExpr(expr: Expr) {
|
private lowerIfExpr(expr: Expr) {
|
||||||
@ -537,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);
|
||||||
@ -550,8 +616,4 @@ export class Lowerer {
|
|||||||
}
|
}
|
||||||
this.locals = outerLocals;
|
this.locals = outerLocals;
|
||||||
}
|
}
|
||||||
|
|
||||||
public printProgram() {
|
|
||||||
this.program.printProgram();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user