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; } class Compiler { Function func; Parser* parser; Local[] locals; int localCount; int scopeDepth; Form current; Form previous; Form outer; int outerIdx; void advance() { previous = current; /* if (outer.type != FormType.EOF && outerIdx < outer) { current = parser.parseForm(); } else { } */ current = parser.parseForm(); } void compileNil(Form form) { form.compile(func); advance(); } void compileAtom(Form form) { form.compile(func); advance(); } void compileAdd(Form[] args) { resolve(args[0]); // (+ n) always returns n if (args.length == 1) { return; } for (int i = 1; i < args.length; i++) { resolve(args[i]); func.chunk.writeOp(OpCode.OP_ADD, current.line); } } void compileNegate(Form arg) { resolve(arg); func.chunk.writeOp(OpCode.OP_NEGATE, current.line); } void compileSubtract(Form[] args) { resolve(args[0]); for (int i = 1; i < args.length; i++) { resolve(args[i]); func.chunk.writeOp(OpCode.OP_SUBTRACT, current.line); } } void compileLess(Form[] args) { if (args.length != 2) { writeln("'<' requires 2 arguments"); return; } resolve(args[0]); resolve(args[1]); func.chunk.writeOp(OpCode.OP_LESS, current.line); } int parseVariable(Def def) { declareVariable(def.name); if (scopeDepth > 0) { return 0; } int addr = func.chunk.addConstant(makeStringValue(def.name.name)); return addr; } void defineVariable(int addr) { if (scopeDepth > 0) { return; } func.chunk.writeOp(OpCode.OP_DEF_GLOBAL, current.line); func.chunk.writeOp(to!ubyte(addr), current.line); } void declareVariable(Symbol sym) { if (scopeDepth == 0) { return; } for (int i = localCount - 1; i >= 0; i--) { Local local = locals[i]; if (local.depth != -1 && local.depth < scopeDepth) { break; } if (sym.name == local.sym.name) { writefln("ERROR: already a variable named '%s' in this scope", sym.name); return; } } Local loc = Local(sym, scopeDepth); if (localCount == locals.length) { locals ~= loc; } else { locals[localCount] = loc; } localCount++; } void compileDef(Form form) { Def def = cast(Def)form; // resolve the value resolve(def.val); // add the variable name to the chunk (if applicable) int addr = parseVariable(def); // are we setting a local? if (scopeDepth > 0) { func.chunk.writeOp(OpCode.OP_DEF_LOCAL, current.line); } // define the variable defineVariable(addr); } int resolveLocal(Symbol sym) { for (int i = localCount - 1; i >= 0; i--) { Local local = locals[i]; if (local.sym.name == sym.name) { return i; } } return -1; } void compileSymbol(Form form) { Symbol sym = cast(Symbol)form; int arg = resolveLocal(sym); if (arg != -1) { func.chunk.writeOp(OpCode.OP_GET_LOCAL, sym.line); } else { arg = func.chunk.addConstant(makeStringValue(sym.name)); func.chunk.writeOp(OpCode.OP_GET_GLOBAL, sym.line); } // get the variable func.chunk.writeOp(to!ubyte(arg), sym.line); //advance(); } int jump(OpCode type) { func.chunk.writeOp(to!ubyte(type), current.line); func.chunk.writeOp(0xff, current.line); func.chunk.writeOp(0xff, current.line); return to!int(func.chunk.code.length) - 2; } void patchJump(int offset) { // additional -2 to account for the 2 byte jump itself int jmp = to!int(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 compileIf(Form form) { If if_ = cast(If)form; resolve(if_.cond); int thenJump = jump(OpCode.OP_JUMP_IF_FALSE); this.func.chunk.writeOp(OpCode.OP_POP, if_.line); resolve(if_.thenForm); int elseJump = jump(OpCode.OP_JUMP); patchJump(thenJump); this.func.chunk.writeOp(OpCode.OP_POP, if_.line); if (if_.hasElse) { resolve(if_.elseForm); } patchJump(elseJump); } void compileAnd(Form form) { And and_ = cast(And)form; resolve(and_.clauses[0]); int[] jumps; jumps ~= jump(OpCode.OP_JUMP_IF_FALSE); int count = 1; while (count < and_.clauses.length) { this.func.chunk.writeOp(OpCode.OP_POP, and_.line); resolve(and_.clauses[count]); jumps ~= jump(OpCode.OP_JUMP_IF_FALSE); count++; } // patch all the jumps foreach (int jmp; jumps) { patchJump(jmp); } } void compileOr(Form form) { Or or_ = cast(Or)form; resolve(or_.clauses[0]); int[] jumps; jumps ~= jump(OpCode.OP_JUMP_IF_TRUE); int count = 1; while (count < or_.clauses.length) { this.func.chunk.writeOp(OpCode.OP_POP, or_.line); resolve(or_.clauses[count]); jumps ~= jump(OpCode.OP_JUMP_IF_TRUE); count++; } // patch all the jumps foreach (int jmp; jumps) { patchJump(jmp); } } void compileBlock(Form form) { Block block = cast(Block)form; beginScope(); foreach (Form inner; block.blockBody) { resolve(inner); } endScope(); } void beginScope() { scopeDepth++; } void endScope() { scopeDepth--; while (localCount > 0 && locals[localCount - 1].depth > scopeDepth) { func.chunk.writeOp(OpCode.OP_POPB, -1); localCount--; } } void compileCons(Form form) { Cons cons = cast(Cons)form; Form head = cons.head; if (head.type != FormType.SYMBOL) { writeln("cons must start with a symbol"); return; } Symbol sym = cast(Symbol)head; switch (sym.name) { case "+": compileAdd(cons.tail); break; case "-": if (cons.tail.length == 1) { compileNegate(cons.tail[0]); } else { compileSubtract(cons.tail); } break; case "<": compileLess(cons.tail); break; default: /* writefln("unsure how to compile %s", sym.name); advance(); */ resolve(head); break; } } void resolve(Form form) { //printForm(form); switch(form.type) { case FormType.ATOM: this.compileAtom(form); break; case FormType.CONS: this.compileCons(form); break; case FormType.NIL: this.compileNil(form); break; case FormType.DEF: this.compileDef(form); break; case FormType.SYMBOL: this.compileSymbol(form); 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; default: write("not sure how to resolve: "); printForm(form); advance(); break; } } Function compile() { advance(); while(current.type != FormType.EOF) { resolve(current); } return finish(); } Function finish() { this.func.chunk.writeOp(OpCode.OP_RETURN, current.line); disassembleChunk(func.chunk, to!string(func)); return func; } this(ObjType type, Parser* parser) { this.parser = parser; this.func = new Function(type); //localCount = 0; locals ~= Local(new Symbol("", -1), 0); localCount++; } }