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; } class Compiler { Function func; Parser* parser; Local[] locals; int localCount; int scopeDepth; Form current; Form previous; int currentLine; 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(); } 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 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 = typeCheck(atom.value.type, expecting); form.compile(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 } */ advance(); /* 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 compileAdd(Form[] args, ValueType expecting) { int line = args[0].line; resolve(args[0], expecting); 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++) { resolve(args[i], expecting); func.chunk.writeOp(OpCode.OP_TYPE_CHECK_NUMBER, line); func.chunk.writeOp(OpCode.OP_ADD, line); } } void compileNegate(Form arg) { resolve(arg); func.chunk.writeOp(OpCode.OP_TYPE_CHECK_NUMBER, currentLine); func.chunk.writeOp(OpCode.OP_NEGATE, currentLine); } ValueType compileSubtract(Form[] args, ValueType expected) { ValueType vt; vt = resolve(args[0], expected); func.chunk.writeOp(OpCode.OP_TYPE_CHECK_NUMBER, currentLine); for (int i = 1; i < args.length; i++) { vt = resolve(args[i], expected); func.chunk.writeOp(OpCode.OP_TYPE_CHECK_NUMBER, currentLine); func.chunk.writeOp(OpCode.OP_SUBTRACT, currentLine); } return ValueType.NUMBER; } //ValueType compileLess(Form[] args, ValueType expected = ValueType.ANY) { ValueType compileLess(Form[] args, ValueType expected) { if (args.length != 2) { writeln("'<' requires 2 arguments"); advance(); return ValueType.NIL; } ValueType vt1 = resolve(args[0], ValueType.NUMBER); /* TC tc1 = typeCheck(vt1, expected); if (tc1.maybe) { func.chunk.writeOp(to!ubyte(tc1.op), currentLine); } else if (!tc1.exact) { writeln("COMPILE ERROR: bad type (less)"); } */ func.chunk.writeOp(OpCode.OP_TYPE_CHECK_NUMBER, currentLine); ValueType vt2 = resolve(args[1], ValueType.NUMBER); /* TC tc2 = typeCheck(vt2, expected); if (tc2.maybe) { func.chunk.writeOp(to!ubyte(tc2.op), currentLine); } else if (!tc2.exact) { writeln("COMPILE ERROR: bad type (less)"); } */ func.chunk.writeOp(OpCode.OP_TYPE_CHECK_NUMBER, currentLine); //ValueType vt1 = resolve(args[0], expected); //func.chunk.writeOp(OpCode.OP_TYPE_CHECK_NUMBER, currentLine); //ValueType vt2 = resolve(args[1], expected); //func.chunk.writeOp(OpCode.OP_TYPE_CHECK_NUMBER, currentLine); func.chunk.writeOp(OpCode.OP_LESS, currentLine); // this should probably return a ValueType return ValueType.BOOLEAN; } ValueType compileGreater(Form[] args) { if (args.length != 2) { writeln("'>' requires 2 arguments"); advance(); return ValueType.NIL; } ValueType vt1 = resolve(args[0], ValueType.NUMBER); func.chunk.writeOp(OpCode.OP_TYPE_CHECK_NUMBER, currentLine); ValueType vt2 = resolve(args[1], ValueType.NUMBER); func.chunk.writeOp(OpCode.OP_TYPE_CHECK_NUMBER, currentLine); func.chunk.writeOp(OpCode.OP_GREATER, currentLine); // this should probably return a ValueType return ValueType.BOOLEAN; } //int parseVariable(Def def) { int parseVariable(Symbol sym) { declareVariable(sym); if (this.scopeDepth > 0) { return 0; } int addr = func.chunk.addConstant(makeStringValue(sym.name)); return addr; } void defineVariable(int addr) { if (this.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 (this.scopeDepth == 0) { return; } for (int i = localCount - 1; i >= 0; i--) { Local local = locals[i]; if (local.depth != -1 && local.depth < this.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, this.scopeDepth); if (localCount == locals.length) { locals ~= loc; } else { locals[localCount] = loc; } localCount++; } void compileDef(Form form, ValueType expecting) { Def def = cast(Def)form; // resolve the value resolve(def.val, expecting); // add the variable name to the chunk (if applicable) int addr = parseVariable(def.name); // are we setting a local? if (this.scopeDepth > 0) { func.chunk.writeOp(OpCode.OP_DEF_LOCAL, def.line); } // define the variable defineVariable(addr); } /* struct LV { int i; bool needsTypeCheck; } */ 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, ValueType expecting = ValueType.ANY) { 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); func.chunk.writeOp(OpCode.OP_TYPE_CHECK_BOOLEAN, if_.line); 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]); func.chunk.writeOp(OpCode.OP_TYPE_CHECK_BOOLEAN, currentLine); int[] jumps; jumps ~= jump(OpCode.OP_JUMP_IF_FALSE); int count = 1; while (count < and_.clauses.length) { this.func.chunk.writeOp(OpCode.OP_POP, currentLine); resolve(and_.clauses[count]); func.chunk.writeOp(OpCode.OP_TYPE_CHECK_BOOLEAN, currentLine); 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]); func.chunk.writeOp(OpCode.OP_TYPE_CHECK_BOOLEAN, currentLine); 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]); func.chunk.writeOp(OpCode.OP_TYPE_CHECK_BOOLEAN, currentLine); 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() { this.scopeDepth++; } void endScope() { this.scopeDepth--; while (localCount > 0 && locals[localCount - 1].depth > this.scopeDepth) { func.chunk.writeOp(OpCode.OP_POPB, -1); localCount--; } } void call(Form[] args) { //ubyte argCount = argumentList(); foreach (Form f ; args) { resolve(f); } func.chunk.writeOp(OpCode.OP_CALL, -1); //func.chunk.writeOp(argCount, -1); func.chunk.writeOp(to!ubyte(args.length), -1); } void compileFunc(Form form) { Func f = cast(Func)form; // name the function int global = parseVariable(f.name); Compiler compiler = new Compiler(ObjType.FUNCTION, 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); } } /* // compile each inner Form foreach (Form inner; f.funcBody) { compiler.resolve(inner); } */ advance(); // ?? Block b = new Block(f.line); b.blockBody = f.funcBody; compiler.compileBlock(b); // write the function as a Value Function outFunc = compiler.finish(); func.chunk.writeOp(OpCode.OP_CONSTANT, f.line); int funcAddr = func.chunk.addConstant(makeObjValue(outFunc)); func.chunk.writeOp(to!ubyte(funcAddr), f.line); // define the global variable defineVariable(global); } ValueType compileCons(Form form, ValueType expected) { Cons cons = cast(Cons)form; Form head = cons.head; if (head.type != FormType.SYMBOL) { writeln("cons must start with a symbol"); advance(); return ValueType.NIL; } Symbol sym = cast(Symbol)head; switch (sym.name) { case "+": compileAdd(cons.tail, ValueType.NUMBER); break; case "-": if (cons.tail.length == 1) { compileNegate(cons.tail[0]); } else { return compileSubtract(cons.tail, ValueType.NUMBER); } break; case "<": return compileLess(cons.tail, ValueType.NUMBER); break; case "<=": ValueType vt = compileGreater(cons.tail); func.chunk.writeOp(OpCode.OP_NOT, cons.line); return vt; case ">": return compileGreater(cons.tail); break; case ">=": ValueType vt = compileLess(cons.tail, ValueType.NUMBER); func.chunk.writeOp(OpCode.OP_NOT, cons.line); return vt; default: /* writefln("unsure how to compile %s", sym.name); advance(); */ resolve(head); call(cons.tail); advance(); break; } return ValueType.NIL; } ValueType resolve(Form form, const ValueType expecting = ValueType.ANY) { //printForm(form); currentLine = form.line; switch(form.type) { case FormType.ATOM: this.compileAtom(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; default: write("not sure how to resolve: "); printForm(form); advance(); break; } return ValueType.NIL; } 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 this.func; } this(ObjType type, Parser* parser, string name = "") { this.parser = parser; this.func = new Function(type, name); //localCount = 0; locals ~= Local(new Symbol("", -1), 0); localCount++; } }