aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormryouse2023-06-08 20:03:38 -0400
committermryouse2023-06-08 20:03:38 -0400
commit2990e709e410f4f41f80802cf31e930950cf499c (patch)
treeda8513086079f7c6a1690ecd2d80212a98af910b
parenta1a7eb31c2ded8e3f1f6ed205fc3c4c2fd5d67b5 (diff)
initial commit of reduce
-rw-r--r--README.md1
-rw-r--r--compiler.d69
2 files changed, 70 insertions, 0 deletions
diff --git a/README.md b/README.md
index 6230bbd..df76593 100644
--- a/README.md
+++ b/README.md
@@ -27,6 +27,7 @@ now in bytecode
- [ ] bounds issues (`first`/`last` on empty sequences) crashes entirely
- [ ] constants get duplicated in chunks
- [ ] a space at the end of (definitions? lists?) crashes
+ - [ ] HOF can't take in builtins (they're not actually in the global environment)
- [ ] i mean, nearly nothing works
## things that hopefully work
diff --git a/compiler.d b/compiler.d
index 8cb65ee..9ae009d 100644
--- a/compiler.d
+++ b/compiler.d
@@ -401,6 +401,72 @@ class Compiler {
this.func.chunk.writeOp(OpCode.OP_LIST_N, args[0].line);
}
+ void compileReduce(Form[] args) {
+ // TODO how do we identify/propagate errors?
+ if (args.length != 3) {
+ this.error(format("'reduce': expected [3] 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
+
+ vt = this.resolve(args[2], ValueType.ANY); // resolves the accumulator
+
+ this.func.chunk.writeOp(OpCode.OP_ROTATE_N, args[0].line);
+ this.func.chunk.writeOp(to!ubyte(3), args[0].line); // | [ acc ] [ fn ] [ list ]
+
+ // we're going to jump back to this point, so let's keep track of our location
+ int jumpBack = to!int(this.func.chunk.code.length);
+
+ // with our list on top of the stack, let's check for nil to see if we're done
+ this.func.chunk.writeOp(OpCode.OP_DUPLICATE, args[0].line); // | [ acc ] [ fn ] [ list ] [ list ]
+ this.func.chunk.writeOp(OpCode.OP_IS_NIL, args[0].line); // | [ acc ] [ fn ] [ list ] [ bool ]
+
+ // we'll jump from here to the end if we have nil
+ int nilJump = this.jump(OpCode.OP_JUMP_IF_TRUE); // TODO if we're using "falsey", we could cut an instruction here
+
+ // get rid of the boolean on top
+ this.func.chunk.writeOp(OpCode.OP_POP, args[0].line); // | [ acc ] [ fn ] [ list ]
+
+ // duplicate the function and list, and rotate it below the length (we'll need it later)
+ this.func.chunk.writeOp(OpCode.OP_DUPLICATE_2, args[0].line); // | [ acc ] [ 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 ] [ acc ] [ 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 ] [ acc ] [ fn ] [ list ]
+
+ // get the first item from the top list, then run the function on it (we know there's always 1 argument)
+ this.func.chunk.writeOp(OpCode.OP_FIRST, args[0].line); // | [ fn ] [ list ] [ acc ] [ fn ] [ item ]
+
+ // rotate the accumulator and item to the top
+ this.func.chunk.writeOp(OpCode.OP_ROTATE_N, args[0].line);
+ this.func.chunk.writeOp(to!ubyte(3), args[0].line); // | [ fn ] [ list ] [ item ] [ acc ] [ fn ]
+ this.func.chunk.writeOp(OpCode.OP_ROTATE_N, args[0].line);
+ this.func.chunk.writeOp(to!ubyte(3), args[0].line); // | [ fn ] [ list ] [ fn ] [ item ] [ acc ]
+
+ // call the function
+ this.func.chunk.writeOp(OpCode.OP_CALL, args[0].line);
+ this.func.chunk.writeOp(to!ubyte(2), args[0].line); // | [ fn ] [ list ] [ fn(item, acc) ]
+
+ // rotate the accumulator behind the fn/list
+ this.func.chunk.writeOp(OpCode.OP_ROTATE_N, args[0].line);
+ this.func.chunk.writeOp(to!ubyte(3), args[0].line); // | [ fn(item, acc) ] [ fn ] [ list ]
+
+ // get the rest of the list
+ this.func.chunk.writeOp(OpCode.OP_REST, args[0].line); // | [ fn(item, acc) ] [ fn ] [ rest(list) ]
+
+ // jump back to the top of the loop
+ this.jumpBackTo(OpCode.OP_JUMP_TO, jumpBack);
+
+ // jump here when we have a nil
+ this.patchJump(nilJump); // | [ fn(first) ] ... [ fn(last) ] [ length ] [ fn ] [ nil ] [ true ]
+
+ // pop the unneeded state at the top, leaving the result 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); // | [ acc ]
+ }
+
// LISP-Y
void compileBlock(Form form) {
Block block = cast(Block)form;
@@ -854,6 +920,9 @@ class Compiler {
case "map":
this.compileMap(cons.tail);
break;
+ case "reduce":
+ this.compileReduce(cons.tail);
+ break;
// STRINGS
case "concat":