diff options
| author | mryouse | 2023-06-05 21:14:27 -0400 |
|---|---|---|
| committer | mryouse | 2023-06-05 21:14:27 -0400 |
| commit | 7393da674216aa3dd737db7ec4a3418ca025871c (patch) | |
| tree | 9f061b136e7b606757f1caab6e5b09e593f55c67 /compiler.d | |
| parent | 6c6afc8d594ad675290bda81a0a6bde3dfa590eb (diff) | |
reorganization
Diffstat (limited to 'compiler.d')
| -rw-r--r-- | compiler.d | 948 |
1 files changed, 443 insertions, 505 deletions
@@ -44,76 +44,12 @@ class Compiler { 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) { + // CONSTANTS + void compileAtom(Form form, const ValueType expecting) { + Atom atom = cast(Atom)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; @@ -123,214 +59,176 @@ class Compiler { 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; - } + // CONTROL FLOW + 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++; } - */ - 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 + // patch all the jumps + foreach (int jmp; jumps) { + this.patchJump(jmp); } - */ + } + + 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); - /* - 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 (if_.hasElse) { + this.resolve(if_.elseForm); } - */ - /* - 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); - } + this.patchJump(elseJump); - 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); + void compileOr(Form form) { + Or or_ = cast(Or)form; - // (+ n) always returns n - if (args.length == 1) { - return; + 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++; } - 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); + // patch all the jumps + foreach (int jmp; jumps) { + this.patchJump(jmp); } } - 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); + 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; } - 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 patchJump(int offset) { + // additional -2 to account for the 2 byte jump itself + int jmp = to!int(this.func.chunk.code.length) - offset - 2; - 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); - } + // 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 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); + 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); } - 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); + // DEFINITIONS + void compileDef(Form form, ValueType expecting) { + Def def = cast(Def)form; - ValueType vt2 = this.resolve(args[1], ValueType.NUMBER); - this.func.chunk.writeOp(OpCode.OP_TYPE_CHECK_NUMBER, args[1].line); + // resolve the value + this.resolve(def.val, expecting); - this.func.chunk.writeOp(OpCode.OP_LESS, args[1].line); + // add the variable name to the chunk (if applicable) + int addr = this.parseVariable(def.name); - // this should probably return a ValueType - return ValueType.BOOLEAN; + // 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); } - ValueType compileGreater(Form[] args) { - if (args.length != 2) { - this.error(format("'>': expected [2] arguments, received %d", to!int(args.length)), -1); - return ValueType.NIL; + 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); + } } - 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); + Block b = new Block(f.line); + b.blockBody = f.funcBody; + compiler.compileBlock(b); - this.func.chunk.writeOp(OpCode.OP_GREATER, args[1].line); + // write the function as a Value + Function outFunc = compiler.finish(); - // this should probably return a ValueType - return ValueType.BOOLEAN; - } + 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); - 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; + // capture the errors + foreach (CompileError err ; compiler.errors) { + this.errors ~= err; } - 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; + // define the global variable + this.defineVariable(global); } - void compilePrint(Form[] args) { - if (args.length != 1) { - this.error(format("'print': expected [1] argument, received %d", to!int(args.length)), -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); } - ValueType vt1 = this.resolve(args[0], ValueType.ANY); - this.func.chunk.writeOp(OpCode.OP_PRINT, args[0].line); + + // get the variable + this.func.chunk.writeOp(to!ubyte(arg), sym.line); } int parseVariable(Symbol sym) { @@ -379,24 +277,6 @@ class Compiler { 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]; @@ -408,134 +288,85 @@ class Compiler { 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); + void call(Form[] args) { + //ubyte argCount = argumentList(); + foreach (Form f ; args) { + this.resolve(f); } - - // 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); + this.func.chunk.writeOp(OpCode.OP_CALL, -1); + //func.chunk.writeOp(argCount, -1); + this.func.chunk.writeOp(to!ubyte(args.length), -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); + // HOF + 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 - int elseJump = this.jump(OpCode.OP_JUMP); + // 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 ] - this.patchJump(thenJump); - this.func.chunk.writeOp(OpCode.OP_POP, if_.line); + // 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); - if (if_.hasElse) { - this.resolve(if_.elseForm); - } + // 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 ] - this.patchJump(elseJump); + // 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 ] - void compileAnd(Form form) { - And and_ = cast(And)form; + // 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 ] - this.resolve(and_.clauses[0]); - this.func.chunk.writeOp(OpCode.OP_TYPE_CHECK_BOOLEAN, and_.clauses[0].line); + // 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) ] - 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++; - } + // 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 ] - // patch all the jumps - foreach (int jmp; jumps) { - this.patchJump(jmp); - } - } + // 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 ] - void compileOr(Form form) { - Or or_ = cast(Or)form; + // get the rest of the list + this.func.chunk.writeOp(OpCode.OP_REST, args[0].line); // | [ fn(item) ] [ 1 ] [ fn ] [ rest(list) ] - 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++; - } + // jump back to the top of the loop + this.jumpBackTo(OpCode.OP_JUMP_TO, jumpBack); - // patch all the jumps - foreach (int jmp; jumps) { - this.patchJump(jmp); - } - } + // jump here when we have a nil + this.patchJump(nilJump); // | [ fn(first) ] ... [ fn(last) ] [ length ] [ fn ] [ nil ] [ true ] - 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; - } + // 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 ] - 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; + // create a list from the stack, with the length as the top item + this.func.chunk.writeOp(OpCode.OP_LIST_N, args[0].line); } + // LISP-Y void compileBlock(Form form) { Block block = cast(Block)form; this.beginScope(); @@ -560,16 +391,40 @@ class Compiler { } } - void call(Form[] args) { - //ubyte argCount = argumentList(); - foreach (Form f ; args) { - this.resolve(f); + void compileLambda(Form form) { + Lambda lamb = cast(Lambda)form; + Compiler compiler = new Compiler(ObjType.FUNCTION, this.parser, "<lambda>"); + 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; } - this.func.chunk.writeOp(OpCode.OP_CALL, -1); - //func.chunk.writeOp(argCount, -1); - this.func.chunk.writeOp(to!ubyte(args.length), -1); } + // LISTS void compileList(Form[] args) { if (args.length == 0) { this.func.chunk.writeOp(OpCode.OP_LIST, -1); @@ -584,111 +439,151 @@ class Compiler { 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 compileNil(Form form) { + form.compile(this.func); } - void compileAppend(Form[] args) { + // LOGIC + ValueType compileEq(Form[] args) { if (args.length != 2) { - this.error(format("'append': expected [2] arguments, received %d", args.length), -1); - return; + this.error(format("'eq?': expected [1] argument, received %d", to!int(args.length)), -1); + return ValueType.NIL; } - - 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 vt1 = this.resolve(args[0], ValueType.ANY); ValueType vt2 = this.resolve(args[1], ValueType.ANY); - this.func.chunk.writeOp(OpCode.OP_APPEND, args[0].line); + this.func.chunk.writeOp(OpCode.OP_EQUAL, args[1].line); + + return ValueType.BOOLEAN; } - void compileIn(Form[] args) { - // TODO how do we identify/propagate errors? + ValueType compileGreater(Form[] args) { if (args.length != 2) { - this.error(format("'in?': expected [2] arguments, received %d", args.length), -1); - return; + this.error(format("'>': expected [2] arguments, 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.SEQ); - this.func.chunk.writeOp(OpCode.OP_TYPE_CHECK_SEQ, args[0].line); + ValueType vt1 = this.resolve(args[0], ValueType.NUMBER); + this.func.chunk.writeOp(OpCode.OP_TYPE_CHECK_NUMBER, args[0].line); - this.func.chunk.writeOp(OpCode.OP_MEMBER, 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; } - void compileMap(Form[] args) { - // TODO how do we identify/propagate errors? + ValueType compileLess(Form[] args, ValueType expected) { if (args.length != 2) { - this.error(format("'map': expected [2] arguments, received %d", args.length), -1); - return; + this.error(format("'<': expected [2] arguments, received %d", to!int(args.length)), -1); + return ValueType.NIL; } - ValueType vt = this.resolve(args[0], ValueType.ANY); // resolves the function - vt = this.resolve(args[1], ValueType.ANY); // resolves the list + ValueType vt1 = this.resolve(args[0], ValueType.NUMBER); + this.func.chunk.writeOp(OpCode.OP_TYPE_CHECK_NUMBER, args[0].line); - // 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 ] + ValueType vt2 = this.resolve(args[1], ValueType.NUMBER); + this.func.chunk.writeOp(OpCode.OP_TYPE_CHECK_NUMBER, args[1].line); - // 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); + this.func.chunk.writeOp(OpCode.OP_LESS, args[1].line); - // 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 ] + // this should probably return a ValueType + return ValueType.BOOLEAN; + } - // 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 + 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; + } - // get rid of the boolean on top - this.func.chunk.writeOp(OpCode.OP_POP, args[0].line); // | [ 0 ] [ fn ] [ list ] + // MATH + 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); - // 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 ] + // (+ n) always returns n + if (args.length == 1) { + return; + } - // 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) ] + 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); + } + } - // 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 ] + 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); + } - // 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 ] + 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); + } + } - // get the rest of the list - this.func.chunk.writeOp(OpCode.OP_REST, args[0].line); // | [ fn(item) ] [ 1 ] [ fn ] [ rest(list) ] + 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); + } - // jump back to the top of the loop - this.jumpBackTo(OpCode.OP_JUMP_TO, jumpBack); + 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; + } - // jump here when we have a nil - this.patchJump(nilJump); // | [ fn(first) ] ... [ fn(last) ] [ length ] [ fn ] [ nil ] [ true ] + // SEQUENCES + void compileAppend(Form[] args) { + if (args.length != 2) { + this.error(format("'append': expected [2] arguments, received %d", args.length), -1); + return; + } - // 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 ] + ValueType vt1 = this.resolve(args[0], ValueType.SEQ); + this.func.chunk.writeOp(OpCode.OP_TYPE_CHECK_SEQ, args[0].line); - // create a list from the stack, with the length as the top item - this.func.chunk.writeOp(OpCode.OP_LIST_N, 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); + } + + 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 compileFirst(Form[] args) { @@ -708,6 +603,31 @@ class Compiler { this.func.chunk.writeOp(OpCode.OP_FIRST, 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 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 compileRest(Form[] args) { // TODO how do we identify/propagate errors? if (args.length != 1) { @@ -720,80 +640,87 @@ class Compiler { 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, "<lambda>"); - 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); - } + // TERMINAL + 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); + } - 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); + // TYPES + 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); + } - // capture the errors - foreach (CompileError err ; compiler.errors) { - this.errors ~= err; + 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 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); + /* ~!~!~!~ BOTTOM ~!~!~!~ */ + 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; } - - 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; + /* + 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; + } } - - // define the global variable - this.defineVariable(global); + */ } + // top level ValueType compileCons(Form form, ValueType expected) { Cons cons = cast(Cons)form; Form head = cons.head; @@ -883,7 +810,6 @@ class Compiler { this.compileLength(cons.tail); break; case "list": - //return compileList(cons.tail); this.compileList(cons.tail); break; case "rest": @@ -952,6 +878,18 @@ class Compiler { return ValueType.NIL; } + // helper functions + + void advance() { + this.previous = this.current; + this.current = this.parser.parseForm(); + } + + void error(string message, int line) { + CompileError err = { message, line }; + this.errors ~= err; + } + Function compile() { this.advance(); while(current.type != FormType.EOF) { |
