aboutsummaryrefslogtreecommitdiff
path: root/compiler.d
diff options
context:
space:
mode:
authormryouse2023-06-05 21:14:27 -0400
committermryouse2023-06-05 21:14:27 -0400
commit7393da674216aa3dd737db7ec4a3418ca025871c (patch)
tree9f061b136e7b606757f1caab6e5b09e593f55c67 /compiler.d
parent6c6afc8d594ad675290bda81a0a6bde3dfa590eb (diff)
reorganization
Diffstat (limited to 'compiler.d')
-rw-r--r--compiler.d948
1 files changed, 443 insertions, 505 deletions
diff --git a/compiler.d b/compiler.d
index 08b916b..0c90a45 100644
--- a/compiler.d
+++ b/compiler.d
@@ -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) {