import std.stdio; import std.string; import std.conv; import std.algorithm : canFind; import parser; import chunk; import dbg; struct Local { Symbol sym; int depth; } struct TC { bool exact; bool maybe; OpCode op; } struct CompileError { string message; int line; } string printableCompilerError(CompileError err) { return format("[line %d] %s", err.line, err.message); } class Compiler { Function func; Parser* parser; Local[] locals; int localCount; int scopeDepth; Form current; Form previous; int currentLine; CompileError[] errors; Form outer; int outerIdx; void advance() { this.previous = this.current; /* if (outer.type != FormType.EOF && outerIdx < outer) { current = parser.parseForm(); } else { } */ this.current = this.parser.parseForm(); } void error(string message, int line) { CompileError err = { message, line }; this.errors ~= err; } void compileNil(Form form) { form.compile(this.func); } TC typeCheck(ValueType actual, ValueType expecting) { TC ret = { false, false, OpCode.OP_NIL }; if (actual == expecting) { //writeln("good types"); ret.exact = true; return ret; } else if (actual == ValueType.ANY) { OpCode op; switch (expecting) { case ValueType.NUMBER: op = OpCode.OP_TYPE_CHECK_NUMBER; break; case ValueType.BOOLEAN: op = OpCode.OP_TYPE_CHECK_BOOLEAN; break; default: writeln("not sure what type to check :("); break; } //func.chunk.writeOp(to!ubyte(op), atom.line); //tc = { false, true, op }; ret.maybe = true; ret.op = op; return ret; } else { return ret; } /* writeln("in typecheck"); ValueType[] types = [actual, expecting]; foreach (ValueType t ; types) { switch (t) { case ValueType.NUMBER: writeln("number"); break; case ValueType.BOOLEAN: writeln("boolean"); break; case ValueType.ANY: writeln("any"); break; default: writeln("something else"); break; } } */ } void compileString(Form form, ValueType expecting) { LiteralString ls = cast(LiteralString)form; this.func.chunk.writeOp(OpCode.OP_CONSTANT, ls.line); String str = new String(ls.value); int idx = this.func.chunk.addConstant(makeSeqValue(str)); this.func.chunk.writeOp(to!ubyte(idx), ls.line); } void compileAtom(Form form, const ValueType expecting) { Atom atom = cast(Atom)form; /* ValueType[] types = [expecting, atom.value.type]; foreach (ValueType t ; types) { switch (t) { case ValueType.NUMBER: writeln("number"); break; case ValueType.BOOLEAN: writeln("boolean"); break; case ValueType.ANY: writeln("any"); break; default: writeln("something else"); break; } } */ TC tc = this.typeCheck(atom.value.type, expecting); form.compile(this.func); //if (tc.exact) { //} else if (tc.maybe) { /* if (tc.maybe) { form.compile(func); func.chunk.writeOp(to!ubyte(tc.op), atom.line); } else if (!tc.exact) { writefln("COMPILE ERROR: typecheck on line %d", atom.line); //form.compile(func); // don't do this later } */ /* ValueType typ = atom.value.type; switch (typ) { case ValueType.ANY: writeln("IN ANY"); OpCode op; switch (expecting) { case ValueType.NUMBER: op = OpCode.OP_TYPE_CHECK_NUMBER; break; case ValueType.BOOLEAN: op = OpCode.OP_TYPE_CHECK_BOOLEAN; break; default: writeln("not sure what type to check :("); break; } func.chunk.writeOp(to!ubyte(op), atom.line); break; case expecting: form.compile(func); break; default: writefln("COMPILE ERROR: typecheck on line %d", atom.line); form.compile(func); break; } */ /* if (atom.value.type == expecting) { writefln("COMPILE ERROR: typecheck on line %d", atom.line); } else if (atom.value */ //form.compile(func); //advance(); } void compileIsAny(Form[] args) { if (args.length != 1) { this.error(format("'any?': expecting [1] argument, received %d", args.length), -1); return; } this.compileAtom(new Atom(true, args[0].line), ValueType.ANY); } void compileIsNil(Form[] args) { if (args.length != 1) { this.error(format("'nil?': expecting [1] argument, received %d", args.length), -1); return; } ValueType vt = this.resolve(args[0], ValueType.ANY); this.func.chunk.writeOp(OpCode.OP_IS_NIL, args[0].line); } void compileAdd(Form[] args, ValueType expecting) { int line = args[0].line; this.resolve(args[0], expecting); this.func.chunk.writeOp(OpCode.OP_TYPE_CHECK_NUMBER, line); // (+ n) always returns n if (args.length == 1) { return; } for (int i = 1; i < args.length; i++) { this.resolve(args[i], expecting); this.func.chunk.writeOp(OpCode.OP_TYPE_CHECK_NUMBER, line); this.func.chunk.writeOp(OpCode.OP_ADD, line); } } void compileNegate(Form arg) { this.resolve(arg); this.func.chunk.writeOp(OpCode.OP_TYPE_CHECK_NUMBER, arg.line); this.func.chunk.writeOp(OpCode.OP_NEGATE, arg.line); } ValueType compileSubtract(Form[] args, ValueType expected) { ValueType vt; vt = this.resolve(args[0], expected); this.func.chunk.writeOp(OpCode.OP_TYPE_CHECK_NUMBER, args[0].line); for (int i = 1; i < args.length; i++) { vt = this.resolve(args[i], expected); this.func.chunk.writeOp(OpCode.OP_TYPE_CHECK_NUMBER, args[i].line); this.func.chunk.writeOp(OpCode.OP_SUBTRACT, args[i].line); } return ValueType.NUMBER; } void compileMultiply(Form[] args) { if (args.length < 2) { this.error(format("'*': expected [2+] arguments, received %d", args.length), -1); return; } ValueType vt = this.resolve(args[0], ValueType.NUMBER); this.func.chunk.writeOp(OpCode.OP_TYPE_CHECK_NUMBER, args[0].line); for (int i = 1; i < args.length; i++) { vt = this.resolve(args[i], ValueType.NUMBER); this.func.chunk.writeOp(OpCode.OP_TYPE_CHECK_NUMBER, args[i].line); this.func.chunk.writeOp(OpCode.OP_MULTIPLY, args[i].line); } } void compileDivide(Form[] args) { if (args.length != 2) { this.error(format("'/': expected [2] arguments, received %d", args.length), -1); return; } ValueType vt = this.resolve(args[0], ValueType.NUMBER); this.func.chunk.writeOp(OpCode.OP_TYPE_CHECK_NUMBER, args[0].line); vt = this.resolve(args[1], ValueType.NUMBER); this.func.chunk.writeOp(OpCode.OP_TYPE_CHECK_NUMBER, args[1].line); this.func.chunk.writeOp(OpCode.OP_DIVIDE, args[1].line); } ValueType compileLess(Form[] args, ValueType expected) { if (args.length != 2) { this.error(format("'<': expected [2] arguments, received %d", to!int(args.length)), -1); return ValueType.NIL; } ValueType vt1 = this.resolve(args[0], ValueType.NUMBER); this.func.chunk.writeOp(OpCode.OP_TYPE_CHECK_NUMBER, args[0].line); ValueType vt2 = this.resolve(args[1], ValueType.NUMBER); this.func.chunk.writeOp(OpCode.OP_TYPE_CHECK_NUMBER, args[1].line); this.func.chunk.writeOp(OpCode.OP_LESS, args[1].line); // this should probably return a ValueType return ValueType.BOOLEAN; } ValueType compileGreater(Form[] args) { if (args.length != 2) { this.error(format("'>': expected [2] arguments, received %d", to!int(args.length)), -1); return ValueType.NIL; } ValueType vt1 = this.resolve(args[0], ValueType.NUMBER); this.func.chunk.writeOp(OpCode.OP_TYPE_CHECK_NUMBER, args[0].line); ValueType vt2 = this.resolve(args[1], ValueType.NUMBER); this.func.chunk.writeOp(OpCode.OP_TYPE_CHECK_NUMBER, args[1].line); this.func.chunk.writeOp(OpCode.OP_GREATER, args[1].line); // this should probably return a ValueType return ValueType.BOOLEAN; } ValueType compileEq(Form[] args) { if (args.length != 2) { this.error(format("'eq?': expected [1] argument, received %d", to!int(args.length)), -1); return ValueType.NIL; } ValueType vt1 = this.resolve(args[0], ValueType.ANY); ValueType vt2 = this.resolve(args[1], ValueType.ANY); this.func.chunk.writeOp(OpCode.OP_EQUAL, args[1].line); return ValueType.BOOLEAN; } void compilePrint(Form[] args) { if (args.length != 1) { this.error(format("'print': expected [1] argument, received %d", to!int(args.length)), -1); } ValueType vt1 = this.resolve(args[0], ValueType.ANY); this.func.chunk.writeOp(OpCode.OP_PRINT, args[0].line); } int parseVariable(Symbol sym) { this.declareVariable(sym); if (this.scopeDepth > 0) { return 0; } int addr = this.func.chunk.addConstant(makeStringValue(sym.name)); return addr; } void defineVariable(int addr) { if (this.scopeDepth > 0) { return; } this.func.chunk.writeOp(OpCode.OP_DEF_GLOBAL, -1); this.func.chunk.writeOp(to!ubyte(addr), -1); } void declareVariable(Symbol sym) { if (this.scopeDepth == 0) { return; } for (int i = this.localCount - 1; i >= 0; i--) { Local local = this.locals[i]; if (local.depth != -1 && local.depth < this.scopeDepth) { break; } if (sym.name == local.sym.name) { this.error(format("already a variable named '%s' in this scope", sym.name), sym.line); return; } } Local loc = Local(sym, this.scopeDepth); if (this.localCount == this.locals.length) { this.locals ~= loc; } else { this.locals[this.localCount] = loc; } this.localCount++; } void compileDef(Form form, ValueType expecting) { Def def = cast(Def)form; // resolve the value this.resolve(def.val, expecting); // add the variable name to the chunk (if applicable) int addr = this.parseVariable(def.name); // are we setting a local? if (this.scopeDepth > 0) { this.func.chunk.writeOp(OpCode.OP_DEF_LOCAL, def.line); } // define the variable this.defineVariable(addr); } int resolveLocal(Symbol sym) { for (int i = this.localCount - 1; i >= 0; i--) { Local local = this.locals[i]; if (local.sym.name == sym.name) { return i; } } return -1; } void compileSymbol(Form form, ValueType expecting = ValueType.ANY) { Symbol sym = cast(Symbol)form; int arg = this.resolveLocal(sym); if (arg != -1) { this.func.chunk.writeOp(OpCode.OP_GET_LOCAL, sym.line); } else { arg = this.func.chunk.addConstant(makeStringValue(sym.name)); this.func.chunk.writeOp(OpCode.OP_GET_GLOBAL, sym.line); } // get the variable this.func.chunk.writeOp(to!ubyte(arg), sym.line); } int jump(OpCode type) { this.func.chunk.writeOp(to!ubyte(type), -1); this.func.chunk.writeOp(0xff, -1); this.func.chunk.writeOp(0xff, -1); return to!int(this.func.chunk.code.length) - 2; } void patchJump(int offset) { // additional -2 to account for the 2 byte jump itself int jmp = to!int(this.func.chunk.code.length) - offset - 2; // TODO check to make sure we didn't jump too far? this.func.chunk.code[offset] = (jmp >> 8) & 0xff; this.func.chunk.code[offset + 1] = jmp & 0xff; } void jumpBackTo(OpCode type, int dest) { this.func.chunk.writeOp(to!ubyte(type), -1); int lower = (dest >> 8) & 0xff; int higher = dest & 0xff; this.func.chunk.writeOp(to!ubyte(lower), -1); this.func.chunk.writeOp(to!ubyte(higher), -1); } void compileIf(Form form) { If if_ = cast(If)form; this.resolve(if_.cond); this.func.chunk.writeOp(OpCode.OP_TYPE_CHECK_BOOLEAN, if_.line); int thenJump = this.jump(OpCode.OP_JUMP_IF_FALSE); this.func.chunk.writeOp(OpCode.OP_POP, if_.line); this.resolve(if_.thenForm); int elseJump = this.jump(OpCode.OP_JUMP); this.patchJump(thenJump); this.func.chunk.writeOp(OpCode.OP_POP, if_.line); if (if_.hasElse) { this.resolve(if_.elseForm); } this.patchJump(elseJump); } void compileAnd(Form form) { And and_ = cast(And)form; this.resolve(and_.clauses[0]); this.func.chunk.writeOp(OpCode.OP_TYPE_CHECK_BOOLEAN, and_.clauses[0].line); int[] jumps; jumps ~= this.jump(OpCode.OP_JUMP_IF_FALSE); int count = 1; while (count < and_.clauses.length) { this.func.chunk.writeOp(OpCode.OP_POP, and_.clauses[count].line); this.resolve(and_.clauses[count]); this.func.chunk.writeOp(OpCode.OP_TYPE_CHECK_BOOLEAN, and_.clauses[count].line); jumps ~= this.jump(OpCode.OP_JUMP_IF_FALSE); count++; } // patch all the jumps foreach (int jmp; jumps) { this.patchJump(jmp); } } void compileOr(Form form) { Or or_ = cast(Or)form; this.resolve(or_.clauses[0]); this.func.chunk.writeOp(OpCode.OP_TYPE_CHECK_BOOLEAN, or_.clauses[0].line); int[] jumps; jumps ~= this.jump(OpCode.OP_JUMP_IF_TRUE); int count = 1; while (count < or_.clauses.length) { this.func.chunk.writeOp(OpCode.OP_POP, or_.clauses[count].line); this.resolve(or_.clauses[count]); this.func.chunk.writeOp(OpCode.OP_TYPE_CHECK_BOOLEAN, or_.clauses[count].line); jumps ~= this.jump(OpCode.OP_JUMP_IF_TRUE); count++; } // patch all the jumps foreach (int jmp; jumps) { this.patchJump(jmp); } } ValueType compileNot(Form[] args) { ValueType vt = this.resolve(args[0], ValueType.BOOLEAN); this.func.chunk.writeOp(OpCode.OP_TYPE_CHECK_BOOLEAN, args[0].line); this.func.chunk.writeOp(OpCode.OP_NOT, args[0].line); return vt; } ValueType compileConcat(Form[] args) { ValueType vt = this.resolve(args[0], ValueType.STRING); for (int i = 1; i < args.length; i++) { vt = this.resolve(args[i], ValueType.STRING); this.func.chunk.writeOp(OpCode.OP_TYPE_CHECK_SEQ, args[i].line); // TODO wrong this.func.chunk.writeOp(OpCode.OP_CONCAT, args[i].line); } return ValueType.STRING; } void compileBlock(Form form) { Block block = cast(Block)form; this.beginScope(); foreach (Form inner; block.blockBody) { this.resolve(inner); } this.endScope(); } void beginScope() { this.scopeDepth++; } void endScope() { this.scopeDepth--; while (this.localCount > 0 && this.locals[this.localCount - 1].depth > this.scopeDepth) { this.func.chunk.writeOp(OpCode.OP_POPB, -1); this.localCount--; } } void call(Form[] args) { //ubyte argCount = argumentList(); foreach (Form f ; args) { this.resolve(f); } this.func.chunk.writeOp(OpCode.OP_CALL, -1); //func.chunk.writeOp(argCount, -1); this.func.chunk.writeOp(to!ubyte(args.length), -1); } void compileList(Form[] args) { if (args.length == 0) { this.func.chunk.writeOp(OpCode.OP_LIST, -1); this.func.chunk.writeOp(to!ubyte(0), -1); return; } int length = to!int(args.length); foreach (Form arg ; args) { this.resolve(arg, ValueType.ANY); // resolve everything onto the stack } this.func.chunk.writeOp(OpCode.OP_LIST, args[0].line); this.func.chunk.writeOp(to!ubyte(length), args[0].line); } void compileLength(Form[] args) { // TODO how do we identify/propagate errors? if (args.length != 1) { this.error("'length': expected [1] argument, received 0", -1); return; } ValueType vt = this.resolve(args[0], ValueType.SEQ); // TODO need a new type this.func.chunk.writeOp(OpCode.OP_TYPE_CHECK_SEQ, args[0].line); this.func.chunk.writeOp(OpCode.OP_LENGTH, args[0].line); } void compileAppend(Form[] args) { if (args.length != 2) { this.error(format("'append': expected [2] arguments, received %d", args.length), -1); return; } ValueType vt1 = this.resolve(args[0], ValueType.SEQ); this.func.chunk.writeOp(OpCode.OP_TYPE_CHECK_SEQ, args[0].line); // TODO if this is used for both string/list, should be able to typecheck second arg here ValueType vt2 = this.resolve(args[1], ValueType.ANY); this.func.chunk.writeOp(OpCode.OP_APPEND, args[0].line); } void compileIn(Form[] args) { // TODO how do we identify/propagate errors? if (args.length != 2) { this.error(format("'in?': expected [2] arguments, received %d", args.length), -1); return; } ValueType vt1 = this.resolve(args[0], ValueType.ANY); ValueType vt2 = this.resolve(args[1], ValueType.SEQ); this.func.chunk.writeOp(OpCode.OP_TYPE_CHECK_SEQ, args[0].line); this.func.chunk.writeOp(OpCode.OP_MEMBER, args[0].line); } void compileMap(Form[] args) { // TODO how do we identify/propagate errors? if (args.length != 2) { this.error(format("'map': expected [2] arguments, received %d", args.length), -1); return; } ValueType vt = this.resolve(args[0], ValueType.ANY); // resolves the function vt = this.resolve(args[1], ValueType.ANY); // resolves the list // we want to keep track of the length of the generated list (starts at zero) // TODO this could probably be cleaner, as we should know the length of the resulting list already this.func.chunk.writeOp(OpCode.OP_ZERO, args[0].line); // | [ fn ] [ list ] [ 0 ] this.func.chunk.writeOp(OpCode.OP_ROTATE_N, args[0].line); this.func.chunk.writeOp(to!ubyte(3), args[0].line); // | [ 0 ] [ fn ] [ list ] // we're going to jump back to this point, so let's keep track of our location int jumpBack = to!int(this.func.chunk.code.length); // with our list on top of the stack, let's check for nil to see if we're done this.func.chunk.writeOp(OpCode.OP_DUPLICATE, args[0].line); // | [ 0 ] [ fn ] [ list ] [ list ] this.func.chunk.writeOp(OpCode.OP_IS_NIL, args[0].line); // | [ 0 ] [ fn ] [ list ] [ bool ] // we'll jump from here to the end if we have nil int nilJump = this.jump(OpCode.OP_JUMP_IF_TRUE); // TODO if we're using "falsey", we could cut an instruction here // get rid of the boolean on top this.func.chunk.writeOp(OpCode.OP_POP, args[0].line); // | [ 0 ] [ fn ] [ list ] // duplicate the function and list, and rotate it below the length (we'll need it later) this.func.chunk.writeOp(OpCode.OP_DUPLICATE_2, args[0].line); // | [ 0 ] [ fn ] [ list ] [ fn ] [ list ] this.func.chunk.writeOp(OpCode.OP_ROTATE_N, args[0].line); this.func.chunk.writeOp(to!ubyte(5), args[0].line); // | [ list ] [ 0 ] [ fn ] [ list ] [ fn ] this.func.chunk.writeOp(OpCode.OP_ROTATE_N, args[0].line); this.func.chunk.writeOp(to!ubyte(5), args[0].line); // | [ fn ] [ list ] [ 0 ] [ fn ] [ list ] // get the first item from the top list, then run the function on it (we know there's always 1 argument) this.func.chunk.writeOp(OpCode.OP_FIRST, args[0].line); // | [ fn ] [ list ] [ 0 ] [ fn ] [ item ] this.func.chunk.writeOp(OpCode.OP_CALL, args[0].line); this.func.chunk.writeOp(to!ubyte(1), args[0].line); // | [ fn ] [ list ] [ 0 ] [ fn(item) ] // preserve the value we just generated this.func.chunk.writeOp(OpCode.OP_ROTATE_N, args[0].line); this.func.chunk.writeOp(to!ubyte(4), args[0].line); // | [ fn(item) ] [ fn ] [ list ] [ 0 ] // increment the counter, and save it *after* the value this.func.chunk.writeOp(OpCode.OP_INCREMENT, args[0].line); // | [ fn(item) ] [ fn ] [ list ] [ 1 ] this.func.chunk.writeOp(OpCode.OP_ROTATE_N, args[0].line); this.func.chunk.writeOp(to!ubyte(3), args[0].line); // | [ fn(item) ] [ 1 ] [ fn ] [ list ] // get the rest of the list this.func.chunk.writeOp(OpCode.OP_REST, args[0].line); // | [ fn(item) ] [ 1 ] [ fn ] [ rest(list) ] // jump back to the top of the loop this.jumpBackTo(OpCode.OP_JUMP_TO, jumpBack); // jump here when we have a nil this.patchJump(nilJump); // | [ fn(first) ] ... [ fn(last) ] [ length ] [ fn ] [ nil ] [ true ] // pop the unneeded state at the top this.func.chunk.writeOp(OpCode.OP_POP, args[0].line); this.func.chunk.writeOp(OpCode.OP_POP, args[0].line); this.func.chunk.writeOp(OpCode.OP_POP, args[0].line); // | [ fn(first) ] ... [ fn(last) ] [ length ] // create a list from the stack, with the length as the top item this.func.chunk.writeOp(OpCode.OP_LIST_N, args[0].line); } void compileFirst(Form[] args) { // TODO how do we identify/propagate errors? if (args.length != 1) { this.error("'first': expected [1] argument, received 0", -1); return; } ValueType vt = this.resolve(args[0], ValueType.SEQ); // TODO need a new type this.func.chunk.writeOp(OpCode.OP_TYPE_CHECK_SEQ, args[0].line); // pop the value off the stack // get the address of the first item in the list // push that item onto the stack // or, use a special instruction this.func.chunk.writeOp(OpCode.OP_FIRST, args[0].line); } void compileRest(Form[] args) { // TODO how do we identify/propagate errors? if (args.length != 1) { this.error("'rest': expected [1] argument, received ?", -1); return; } ValueType vt = this.resolve(args[0], ValueType.OBJ); // TODO need a new type this.func.chunk.writeOp(OpCode.OP_TYPE_CHECK_SEQ, args[0].line); this.func.chunk.writeOp(OpCode.OP_REST, args[0].line); } void compileLambda(Form form) { Lambda lamb = cast(Lambda)form; Compiler compiler = new Compiler(ObjType.FUNCTION, this.parser, ""); compiler.beginScope(); if (lamb.args.type != FormType.NIL) { Symbol sym = cast(Symbol)lamb.args.head; int constant = compiler.parseVariable(sym); compiler.defineVariable(constant); foreach (Form inner ; lamb.args.tail) { sym = cast(Symbol)inner; constant = compiler.parseVariable(sym); compiler.defineVariable(constant); } } Block b = new Block(lamb.line); b.blockBody = lamb.lambdaBody; compiler.compileBlock(b); // write the function as a Value Function outFunc = compiler.finish(); this.func.chunk.writeOp(OpCode.OP_CONSTANT, lamb.line); int funcAddr = this.func.chunk.addConstant(makeObjValue(outFunc)); this.func.chunk.writeOp(to!ubyte(funcAddr), lamb.line); // capture the errors foreach (CompileError err ; compiler.errors) { this.errors ~= err; } } void compileFunc(Form form) { Func f = cast(Func)form; // name the function int global = this.parseVariable(f.name); Compiler compiler = new Compiler(ObjType.FUNCTION, this.parser, f.name.name); compiler.beginScope(); if (f.args.type != FormType.NIL) { Symbol sym = cast(Symbol)f.args.head; int constant = compiler.parseVariable(sym); compiler.defineVariable(constant); foreach (Form inner ; f.args.tail) { sym = cast(Symbol)inner; constant = compiler.parseVariable(sym); compiler.defineVariable(constant); } } Block b = new Block(f.line); b.blockBody = f.funcBody; compiler.compileBlock(b); // write the function as a Value Function outFunc = compiler.finish(); this.func.chunk.writeOp(OpCode.OP_CONSTANT, f.line); int funcAddr = this.func.chunk.addConstant(makeObjValue(outFunc)); this.func.chunk.writeOp(to!ubyte(funcAddr), f.line); // capture the errors foreach (CompileError err ; compiler.errors) { this.errors ~= err; } // define the global variable this.defineVariable(global); } ValueType compileCons(Form form, ValueType expected) { Cons cons = cast(Cons)form; Form head = cons.head; if (head.type == FormType.LAMBDA) { this.resolve(head); this.call(cons.tail); return ValueType.NIL; } if (head.type != FormType.SYMBOL) { this.error("can't evaluate without a symbol or lambda", head.line); return ValueType.NIL; } Symbol sym = cast(Symbol)head; switch (sym.name) { case "+": this.compileAdd(cons.tail, ValueType.NUMBER); break; case "-": if (cons.tail.length == 1) { this.compileNegate(cons.tail[0]); } else { return this.compileSubtract(cons.tail, ValueType.NUMBER); } break; case "*": this.compileMultiply(cons.tail); break; case "/": this.compileDivide(cons.tail); break; // BOOLEAN case "<": return this.compileLess(cons.tail, ValueType.NUMBER); break; case "<=": ValueType vt = this.compileGreater(cons.tail); this.func.chunk.writeOp(OpCode.OP_NOT, cons.line); return vt; case ">": return this.compileGreater(cons.tail); break; case ">=": ValueType vt = this.compileLess(cons.tail, ValueType.NUMBER); this.func.chunk.writeOp(OpCode.OP_NOT, cons.line); return vt; case "not": return this.compileNot(cons.tail); case "eq?": return this.compileEq(cons.tail); // HOF case "map": this.compileMap(cons.tail); break; // STRINGS case "concat": return this.compileConcat(cons.tail); // TYPES case "any?": this.compileIsAny(cons.tail); break; case "nil?": this.compileIsNil(cons.tail); break; case "->string": // does nothing at the moment, as primarily used for printing // and everything can be printed break; // LISTS case "append": this.compileAppend(cons.tail); break; case "first": this.compileFirst(cons.tail); break; case "in?": this.compileIn(cons.tail); break; case "length": this.compileLength(cons.tail); break; case "list": //return compileList(cons.tail); this.compileList(cons.tail); break; case "rest": this.compileRest(cons.tail); break; // OTHER case "print": this.compilePrint(cons.tail); break; default: this.resolve(head); this.call(cons.tail); break; } return ValueType.NIL; } ValueType resolve(Form form, const ValueType expecting = ValueType.ANY) { this.currentLine = form.line; switch(form.type) { case FormType.ATOM: this.compileAtom(form, expecting); break; case FormType.LITERALSTRING: this.compileString(form, expecting); break; case FormType.CONS: return this.compileCons(form, expecting); break; case FormType.NIL: this.compileNil(form); break; case FormType.DEF: this.compileDef(form, expecting); break; case FormType.SYMBOL: this.compileSymbol(form, expecting); break; case FormType.BLOCK: this.compileBlock(form); break; case FormType.IF: this.compileIf(form); break; case FormType.AND: this.compileAnd(form); break; case FormType.OR: this.compileOr(form); break; case FormType.FUNC: this.compileFunc(form); break; case FormType.LAMBDA: this.compileLambda(form); break; default: write("not sure how to resolve: "); printForm(form); break; } return ValueType.NIL; } Function compile() { this.advance(); while(current.type != FormType.EOF) { this.resolve(current); this.advance(); } return this.finish(); } Function finish() { this.func.chunk.writeOp(OpCode.OP_RETURN, -1); if (DEBUG) { disassembleChunk(this.func.chunk, to!string(func)); } return this.func; } this(ObjType type, Parser* parser, string name = "") { this.parser = parser; this.func = new Function(type, name); //localCount = 0; this.locals ~= Local(new Symbol("", -1), 0); this.localCount++; } }