diff --git a/benchmark/fib.txt b/benchmark/fib.txt deleted file mode 100644 index 3bb6d4f1..00000000 --- a/benchmark/fib.txt +++ /dev/null @@ -1,37 +0,0 @@ -Value is just Obj* and all values are boxed. -→ time ../build/Release/wren fib.wren -9.22746e+06 - -real 0m12.688s -user 0m12.076s -sys 0m0.601s - ---- - -Unboxed singletons and numbers. Value is a struct of ValueType, double, Obj* obj. -→ time ../build/Release/wren fib.wren -9.22746e+06 - -real 0m3.233s -user 0m3.229s -sys 0m0.003s - ---- - -NaN tagged values. -→ time ../build/Release/wren fib.wren -9.22746e+06 - -real 0m3.100s -user 0m3.097s -sys 0m0.002s - ---- - -Hoist bytecode and IP into locals in interpret loop. -→ time ../build/Release/wren fib.wren -9.22746e+06 - -real 0m2.490s -user 0m2.487s -sys 0m0.002s diff --git a/benchmark/for.lua b/benchmark/for.lua new file mode 100644 index 00000000..173ff300 --- /dev/null +++ b/benchmark/for.lua @@ -0,0 +1,12 @@ +local list = {} +for i = 0, 1999999 do + list[i] = i +end + +local start = os.clock() +local sum = 0 +for k, i in pairs(list) do + sum = sum + i +end +io.write(sum .. "\n") +io.write(string.format("elapsed: %.8f\n", os.clock() - start)) diff --git a/benchmark/for.py b/benchmark/for.py new file mode 100644 index 00000000..fd03f18d --- /dev/null +++ b/benchmark/for.py @@ -0,0 +1,10 @@ +import time + +list = range(0, 2000000) + +start = time.clock() +sum = 0 +for i in list: + sum += i +print(sum) +print("elapsed: " + str(time.clock() - start)) \ No newline at end of file diff --git a/benchmark/for.rb b/benchmark/for.rb new file mode 100644 index 00000000..77183acb --- /dev/null +++ b/benchmark/for.rb @@ -0,0 +1,7 @@ +list = Array.new(2000000) {|i| i} + +start = Time.now +sum = 0 +list.each {|i| sum += i} +puts sum +puts "elapsed: " + (Time.now - start).to_s diff --git a/benchmark/for.wren b/benchmark/for.wren new file mode 100644 index 00000000..8466350b --- /dev/null +++ b/benchmark/for.wren @@ -0,0 +1,17 @@ +var list = [] + +{ + var i = 0 + while (i < 2000000) { + list.add(i) + i = i + 1 + } +} + +var start = OS.clock +var sum = 0 +for (i in list) { + sum = sum + i +} +IO.write(sum) +IO.write("elapsed: " + (OS.clock - start).toString) diff --git a/benchmark/run_bench b/benchmark/run_bench index eb7c3736..23454b64 100755 --- a/benchmark/run_bench +++ b/benchmark/run_bench @@ -30,6 +30,8 @@ BENCHMARK("fib", r"""317811 317811 317811""") +BENCHMARK("for", r"""1999999000000""") + BENCHMARK("method_call", r"""true false""") diff --git a/doc/site/syntax.markdown b/doc/site/syntax.markdown index b51a027b..10a64b19 100644 --- a/doc/site/syntax.markdown +++ b/doc/site/syntax.markdown @@ -26,8 +26,8 @@ Some people like to see all of the reserved words in a programming language in one lump. If you're one of those folks, here you go: :::wren - class else false fn for if is null - return static this true var while + break class else false fn for if in is + null return static this true var while ## Statement terminators diff --git a/src/wren_compiler.c b/src/wren_compiler.c index 545a41bb..69738d6b 100644 --- a/src/wren_compiler.c +++ b/src/wren_compiler.c @@ -83,6 +83,7 @@ typedef enum TOKEN_FN, TOKEN_FOR, TOKEN_IF, + TOKEN_IN, TOKEN_IS, TOKEN_NEW, TOKEN_NULL, @@ -499,6 +500,7 @@ static void readName(Parser* parser, TokenType type) if (isKeyword(parser, "fn")) type = TOKEN_FN; if (isKeyword(parser, "for")) type = TOKEN_FOR; if (isKeyword(parser, "if")) type = TOKEN_IF; + if (isKeyword(parser, "in")) type = TOKEN_IN; if (isKeyword(parser, "is")) type = TOKEN_IS; if (isKeyword(parser, "new")) type = TOKEN_NEW; if (isKeyword(parser, "null")) type = TOKEN_NULL; @@ -718,6 +720,7 @@ static void nextToken(Parser* parser) case TOKEN_ELSE: case TOKEN_FOR: case TOKEN_IF: + case TOKEN_IN: case TOKEN_IS: case TOKEN_NEW: case TOKEN_STATIC: @@ -771,13 +774,33 @@ static Token* consume(Compiler* compiler, TokenType expected, // Variables and scopes -------------------------------------------------------- -// Emits one bytecode instruction or argument. +// Emits one bytecode instruction or argument. Returns its index. static int emit(Compiler* compiler, Code code) { compiler->fn->bytecode[compiler->numCodes++] = code; return compiler->numCodes - 1; } +// Emits one bytecode instruction followed by an argument. Returns the index of +// the argument in the bytecode. +static int emit1(Compiler* compiler, Code instruction, uint8_t arg) +{ + emit(compiler, instruction); + return emit(compiler, arg); +} + +// Create a new local variable with [name]. Assumes the current scope is local +// and the name is unique. +static int defineLocal(Compiler* compiler, const char* name, int length) +{ + Local* local = &compiler->locals[compiler->numLocals]; + local->name = name; + local->length = length; + local->depth = compiler->scopeDepth; + local->isUpvalue = false; + return compiler->numLocals++; +} + // Parses a name token and declares a variable in the current scope with that // name. Returns its symbol. static int declareVariable(Compiler* compiler) @@ -822,13 +845,7 @@ static int declareVariable(Compiler* compiler) return -1; } - // Define a new local variable in the current scope. - Local* local = &compiler->locals[compiler->numLocals]; - local->name = token->start; - local->length = token->length; - local->depth = compiler->scopeDepth; - local->isUpvalue = false; - return compiler->numLocals++; + return defineLocal(compiler, token->start, token->length); } // Stores a variable with the previously defined symbol in the current scope. @@ -840,8 +857,7 @@ static void defineVariable(Compiler* compiler, int symbol) // It's a global variable, so store the value in the global slot and then // discard the temporary for the initializer. - emit(compiler, CODE_STORE_GLOBAL); - emit(compiler, symbol); + emit1(compiler, CODE_STORE_GLOBAL, symbol); emit(compiler, CODE_POP); } @@ -1020,22 +1036,20 @@ static void endCompiler(Compiler* compiler, int constant) // We can just load and run the function directly. if (compiler->fn->numUpvalues == 0) { - emit(compiler->parent, CODE_CONSTANT); - emit(compiler->parent, constant); + emit1(compiler->parent, CODE_CONSTANT, constant); } else { // Capture the upvalues in the new closure object. - emit(compiler->parent, CODE_CLOSURE); - emit(compiler->parent, constant); + emit1(compiler->parent, CODE_CLOSURE, constant); // Emit arguments for each upvalue to know whether to capture a local or // an upvalue. // TODO: Do something more efficient here? for (int i = 0; i < compiler->fn->numUpvalues; i++) { - emit(compiler->parent, compiler->upvalues[i].isLocal ? 1 : 0); - emit(compiler->parent, compiler->upvalues[i].index); + emit1(compiler->parent, compiler->upvalues[i].isLocal ? 1 : 0, + compiler->upvalues[i].index); } } } @@ -1167,8 +1181,7 @@ static void methodCall(Compiler* compiler, Code instruction, int symbol = ensureSymbol(&compiler->parser->vm->methods, name, length); - emit(compiler, instruction + numArgs); - emit(compiler, symbol); + emit1(compiler, instruction + numArgs, symbol); } // Compiles an expression that starts with ".name". That includes getters, @@ -1192,8 +1205,7 @@ static void namedCall(Compiler* compiler, bool allowAssignment, expression(compiler); int symbol = ensureSymbol(&compiler->parser->vm->methods, name, length); - emit(compiler, instruction + 1); - emit(compiler, symbol); + emit1(compiler, instruction + 1, symbol); } else { @@ -1207,8 +1219,7 @@ static void loadThis(Compiler* compiler) { Code loadInstruction; int index = resolveName(compiler, "this", 4, &loadInstruction); - emit(compiler, loadInstruction); - emit(compiler, index); + emit1(compiler, loadInstruction, index); } static void grouping(Compiler* compiler, bool allowAssignment) @@ -1236,9 +1247,8 @@ static void list(Compiler* compiler, bool allowAssignment) consume(compiler, TOKEN_RIGHT_BRACKET, "Expect ']' after list elements."); // Create the list. - emit(compiler, CODE_LIST); // TODO: Handle lists >255 elements. - emit(compiler, numElements); + emit1(compiler, CODE_LIST, numElements); } // Unary operators like `-foo`. @@ -1251,20 +1261,13 @@ static void unaryOp(Compiler* compiler, bool allowAssignment) // Call the operator method on the left-hand side. int symbol = ensureSymbol(&compiler->parser->vm->methods, rule->name, 1); - emit(compiler, CODE_CALL_0); - emit(compiler, symbol); + emit1(compiler, CODE_CALL_0, symbol); } static void boolean(Compiler* compiler, bool allowAssignment) { - if (compiler->parser->previous.type == TOKEN_FALSE) - { - emit(compiler, CODE_FALSE); - } - else - { - emit(compiler, CODE_TRUE); - } + emit(compiler, + compiler->parser->previous.type == TOKEN_FALSE ? CODE_FALSE : CODE_TRUE); } static void function(Compiler* compiler, bool allowAssignment) @@ -1323,12 +1326,12 @@ static void field(Compiler* compiler, bool allowAssignment) // If we're directly inside a method, use a more optimal instruction. if (compiler->methodName != NULL) { - emit(compiler, CODE_STORE_FIELD_THIS); + emit1(compiler, CODE_STORE_FIELD_THIS, field); } else { loadThis(compiler); - emit(compiler, CODE_STORE_FIELD); + emit1(compiler, CODE_STORE_FIELD, field); } } else @@ -1336,16 +1339,14 @@ static void field(Compiler* compiler, bool allowAssignment) // If we're directly inside a method, use a more optimal instruction. if (compiler->methodName != NULL) { - emit(compiler, CODE_LOAD_FIELD_THIS); + emit1(compiler, CODE_LOAD_FIELD_THIS, field); } else { loadThis(compiler); - emit(compiler, CODE_LOAD_FIELD); + emit1(compiler, CODE_LOAD_FIELD, field); } } - - emit(compiler, field); } static void name(Compiler* compiler, bool allowAssignment) @@ -1369,19 +1370,17 @@ static void name(Compiler* compiler, bool allowAssignment) // Emit the store instruction. switch (loadInstruction) { - case CODE_LOAD_LOCAL: emit(compiler, CODE_STORE_LOCAL); break; - case CODE_LOAD_UPVALUE: emit(compiler, CODE_STORE_UPVALUE); break; - case CODE_LOAD_GLOBAL: emit(compiler, CODE_STORE_GLOBAL); break; + case CODE_LOAD_LOCAL: emit1(compiler, CODE_STORE_LOCAL, index); break; + case CODE_LOAD_UPVALUE: emit1(compiler, CODE_STORE_UPVALUE, index); break; + case CODE_LOAD_GLOBAL: emit1(compiler, CODE_STORE_GLOBAL, index); break; default: UNREACHABLE(); } } else { - emit(compiler, loadInstruction); + emit1(compiler, loadInstruction, index); } - - emit(compiler, index); } static void null(Compiler* compiler, bool allowAssignment) @@ -1406,8 +1405,7 @@ static void number(Compiler* compiler, bool allowAssignment) int constant = addConstant(compiler, NUM_VAL(value)); // Compile the code to load the constant. - emit(compiler, CODE_CONSTANT); - emit(compiler, constant); + emit1(compiler, CODE_CONSTANT, constant); } static void string(Compiler* compiler, bool allowAssignment) @@ -1417,8 +1415,7 @@ static void string(Compiler* compiler, bool allowAssignment) compiler->parser->currentString, compiler->parser->currentStringLength)); // Compile the code to load the constant. - emit(compiler, CODE_CONSTANT); - emit(compiler, constant); + emit1(compiler, CODE_CONSTANT, constant); } // Returns true if [compiler] is compiling a chunk of code that is either @@ -1543,8 +1540,7 @@ static void subscript(Compiler* compiler, bool allowAssignment) int symbol = ensureSymbol(&compiler->parser->vm->methods, name, length); // Compile the method call. - emit(compiler, CODE_CALL_0 + numArgs); - emit(compiler, symbol); + emit1(compiler, CODE_CALL_0 + numArgs, symbol); } void call(Compiler* compiler, bool allowAssignment) @@ -1563,22 +1559,16 @@ void is(Compiler* compiler, bool allowAssignment) void and(Compiler* compiler, bool allowAssignment) { // Skip the right argument if the left is false. - emit(compiler, CODE_AND); - int jump = emit(compiler, 255); - + int jump = emit1(compiler, CODE_AND, 255); parsePrecedence(compiler, false, PREC_LOGIC); - patchJump(compiler, jump); } void or(Compiler* compiler, bool allowAssignment) { // Skip the right argument if the left is true. - emit(compiler, CODE_OR); - int jump = emit(compiler, 255); - + int jump = emit1(compiler, CODE_OR, 255); parsePrecedence(compiler, false, PREC_LOGIC); - patchJump(compiler, jump); } @@ -1592,8 +1582,7 @@ void infixOp(Compiler* compiler, bool allowAssignment) // Call the operator method on the left-hand side. int symbol = ensureSymbol(&compiler->parser->vm->methods, rule->name, strlen(rule->name)); - emit(compiler, CODE_CALL_1); - emit(compiler, symbol); + emit1(compiler, CODE_CALL_1, symbol); } // Compiles a method signature for an infix operator. @@ -1701,6 +1690,7 @@ GrammarRule rules[] = /* TOKEN_FN */ PREFIX(function), /* TOKEN_FOR */ UNUSED, /* TOKEN_IF */ UNUSED, + /* TOKEN_IN */ UNUSED, /* TOKEN_IS */ INFIX(PREC_IS, is), /* TOKEN_NEW */ { new_, NULL, constructorSignature, PREC_NONE, NULL }, /* TOKEN_NULL */ PREFIX(null), @@ -1776,8 +1766,7 @@ void method(Compiler* compiler, Code instruction, bool isConstructor, if (isConstructor) { // The receiver is always stored in the first local slot. - emit(&methodCompiler, CODE_LOAD_LOCAL); - emit(&methodCompiler, 0); + emit1(&methodCompiler, CODE_LOAD_LOCAL, 0); } else { @@ -1790,8 +1779,7 @@ void method(Compiler* compiler, Code instruction, bool isConstructor, endCompiler(&methodCompiler, constant); // Compile the code to define the method. - emit(compiler, instruction); - emit(compiler, symbol); + emit1(compiler, instruction, symbol); } // Parses a curly block or an expression statement. Used in places like the @@ -1849,32 +1837,15 @@ static int getNumArguments(ObjFn* fn, int ip) } } -static void whileStatement(Compiler* compiler) +static int startLoopBody(Compiler* compiler) { - // Remember what instruction to loop back to. - int loopStart = compiler->numCodes - 1; - - // Compile the condition. - consume(compiler, TOKEN_LEFT_PAREN, "Expect '(' after 'while'."); - expression(compiler); - consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after while condition."); - - emit(compiler, CODE_JUMP_IF); - int exitJump = emit(compiler, 255); - - // Compile the body. int outerLoopBody = compiler->loopBody; compiler->loopBody = compiler->numCodes; + return outerLoopBody; +} - block(compiler); - - // Loop back to the top. - emit(compiler, CODE_LOOP); - int loopOffset = compiler->numCodes - loopStart; - emit(compiler, loopOffset); - - patchJump(compiler, exitJump); - +static void endLoopBody(Compiler* compiler, int outerLoopBody) +{ // Find any break placeholder instructions (which will be CODE_END in the // bytecode) and replace them with real jumps. int i = compiler->loopBody; @@ -1896,6 +1867,137 @@ static void whileStatement(Compiler* compiler) compiler->loopBody = outerLoopBody; } +static void forStatement(Compiler* compiler) +{ + // A for statement like: + // + // for (i in sequence.expression) { + // IO.write(i) + // } + // + // Is compiled to bytecode almost as if the source looked like this: + // + // { + // var seq_ = sequence.expression + // var iter_ + // while (true) { + // iter_ = seq_.iterate(iter_) + // if (!iter_) break + // var i = set_.iteratorValue(iter_) + // IO.write(i) + // } + // } + // + // It's not exactly this, because the synthetic variables `seq_` and `iter_` + // actually get names that aren't valid Wren identfiers. Also, the `while` + // and `break` are just the bytecode for explicit loops and jumps. But that's + // the basic idea. + // + // The important parts are: + // - The sequence expression is only evaluated once. + // - The .iterate() method is used to advance the iterator and determine if + // it should exit the loop. + // - The .iteratorValue() method is used to get the value at the current + // iterator position. + + // Create a scope for the hidden local variables used for the iterator. + pushScope(compiler); + + consume(compiler, TOKEN_LEFT_PAREN, "Expect '(' after 'for'."); + consume(compiler, TOKEN_NAME, "Expect for loop variable name."); + + // Remember the name of the loop variable. + const char* name = compiler->parser->previous.start; + int length = compiler->parser->previous.length; + + consume(compiler, TOKEN_IN, "Expect 'in' after loop variable."); + + // Evaluate the sequence expression and store it in a hidden local variable. + // The space in the variable name ensures it won't collide with a user-defined + // variable. + expression(compiler); + int seqSlot = defineLocal(compiler, "seq ", 4); + + // Create another hidden local for the iterator object. + null(compiler, false); + int iterSlot = defineLocal(compiler, "iter ", 5); + + consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after loop expression."); + + // Remember what instruction to loop back to. + int loopStart = compiler->numCodes - 1; + + // Advance the iterator by calling the ".iterate" method on the sequence. + emit1(compiler, CODE_LOAD_LOCAL, seqSlot); + emit1(compiler, CODE_LOAD_LOCAL, iterSlot); + + int iterateSymbol = ensureSymbol(&compiler->parser->vm->methods, + "iterate ", 8); + emit1(compiler, CODE_CALL_1, iterateSymbol); + + // Store the iterator back in its local for the next iteration. + emit1(compiler, CODE_STORE_LOCAL, iterSlot); + // TODO: We can probably get this working with a bit less stack juggling. + + // If it returned something falsy, jump out of the loop. + int exitJump = emit1(compiler, CODE_JUMP_IF, 255); + + // Create a scope for the loop body. + pushScope(compiler); + + // Get the current value in the sequence by calling ".iteratorValue". + emit1(compiler, CODE_LOAD_LOCAL, seqSlot); + emit1(compiler, CODE_LOAD_LOCAL, iterSlot); + + int iteratorValueSymbol = ensureSymbol(&compiler->parser->vm->methods, + "iteratorValue ", 14); + emit1(compiler, CODE_CALL_1, iteratorValueSymbol); + + // Bind it to the loop variable. + defineLocal(compiler, name, length); + + // Compile the body. + int outerLoopBody = startLoopBody(compiler); + block(compiler); + + popScope(compiler); + + // Loop back to the top. + emit(compiler, CODE_LOOP); + int loopOffset = compiler->numCodes - loopStart; + emit(compiler, loopOffset); + + patchJump(compiler, exitJump); + endLoopBody(compiler, outerLoopBody); + + popScope(compiler); +} + +static void whileStatement(Compiler* compiler) +{ + // Remember what instruction to loop back to. + int loopStart = compiler->numCodes - 1; + + // Compile the condition. + consume(compiler, TOKEN_LEFT_PAREN, "Expect '(' after 'while'."); + expression(compiler); + consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after while condition."); + + int exitJump = emit1(compiler, CODE_JUMP_IF, 255); + + // Compile the body. + int outerLoopBody = startLoopBody(compiler); + block(compiler); + + // Loop back to the top. + emit(compiler, CODE_LOOP); + int loopOffset = compiler->numCodes - loopStart; + emit(compiler, loopOffset); + + patchJump(compiler, exitJump); + endLoopBody(compiler, outerLoopBody); +} + // Compiles a statement. These can only appear at the top-level or within // curly blocks. Unlike expressions, these do not leave a value on the stack. void statement(Compiler* compiler) @@ -1912,11 +2014,12 @@ void statement(Compiler* compiler) // replace these with `CODE_JUMP` instructions with appropriate offsets. // We use `CODE_END` here because that can't occur in the middle of // bytecode. - emit(compiler, CODE_END); - emit(compiler, 0); + emit1(compiler, CODE_END, 0); return; } + if (match(compiler, TOKEN_FOR)) return forStatement(compiler); + if (match(compiler, TOKEN_IF)) { // Compile the condition. @@ -1925,8 +2028,7 @@ void statement(Compiler* compiler) consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after if condition."); // Jump to the else branch if the condition is false. - emit(compiler, CODE_JUMP_IF); - int ifJump = emit(compiler, 255); + int ifJump = emit1(compiler, CODE_JUMP_IF, 255); // Compile the then branch. block(compiler); @@ -1935,8 +2037,7 @@ void statement(Compiler* compiler) if (match(compiler, TOKEN_ELSE)) { // Jump over the else branch when the if branch is taken. - emit(compiler, CODE_JUMP); - int elseJump = emit(compiler, 255); + int elseJump = emit1(compiler, CODE_JUMP, 255); patchJump(compiler, ifJump); @@ -1964,11 +2065,7 @@ void statement(Compiler* compiler) return; } - if (match(compiler, TOKEN_WHILE)) - { - whileStatement(compiler); - return; - } + if (match(compiler, TOKEN_WHILE)) return whileStatement(compiler); // Expression statement. expression(compiler); @@ -2052,36 +2149,32 @@ static void classDefinition(Compiler* compiler) defineVariable(compiler, symbol); } +static void variableDefinition(Compiler* compiler) +{ + // TODO: Variable should not be in scope until after initializer. + int symbol = declareVariable(compiler); + + // Compile the initializer. + if (match(compiler, TOKEN_EQ)) + { + expression(compiler); + } + else + { + // Default initialize it to null. + null(compiler, false); + } + + defineVariable(compiler, symbol); +} + // Compiles a "definition". These are the statements that bind new variables. // They can only appear at the top level of a block and are prohibited in places // like the non-curly body of an if or while. void definition(Compiler* compiler) { - if (match(compiler, TOKEN_CLASS)) - { - classDefinition(compiler); - return; - } - - if (match(compiler, TOKEN_VAR)) - { - // TODO: Variable should not be in scope until after initializer. - int symbol = declareVariable(compiler); - - // Compile the initializer. - if (match(compiler, TOKEN_EQ)) - { - expression(compiler); - } - else - { - // Default initialize it to null. - null(compiler, false); - } - - defineVariable(compiler, symbol); - return; - } + if (match(compiler, TOKEN_CLASS)) return classDefinition(compiler); + if (match(compiler, TOKEN_VAR)) return variableDefinition(compiler); block(compiler); } diff --git a/src/wren_core.c b/src/wren_core.c index 94db3818..b32c7fae 100644 --- a/src/wren_core.c +++ b/src/wren_core.c @@ -197,6 +197,30 @@ DEF_NATIVE(list_insert) return args[1]; } +DEF_NATIVE(list_iterate) +{ + // If we're starting the iteration, return the first index. + if (IS_NULL(args[1])) return NUM_VAL(0); + + ObjList* list = AS_LIST(args[0]); + double index = AS_NUM(args[1]); + // TODO: Handle arg not a number or not an integer. + + // Stop if we're out of elements. + if (index >= list->count - 1) return FALSE_VAL; + + // Otherwise, move to the next index. + return NUM_VAL(index + 1); +} + +DEF_NATIVE(list_iteratorValue) +{ + ObjList* list = AS_LIST(args[0]); + double index = AS_NUM(args[1]); + // TODO: Handle index out of bounds or not integer. + return list->elements[(int)index]; +} + DEF_NATIVE(list_removeAt) { ObjList* list = AS_LIST(args[0]); @@ -599,6 +623,8 @@ void wrenInitializeCore(WrenVM* vm) NATIVE(vm->listClass, "clear", list_clear); NATIVE(vm->listClass, "count", list_count); NATIVE(vm->listClass, "insert ", list_insert); + NATIVE(vm->listClass, "iterate ", list_iterate); + NATIVE(vm->listClass, "iteratorValue ", list_iteratorValue); NATIVE(vm->listClass, "removeAt ", list_removeAt); NATIVE(vm->listClass, "[ ]", list_subscript); NATIVE(vm->listClass, "[ ]=", list_subscriptSetter); diff --git a/src/wren_vm.c b/src/wren_vm.c index bd4050de..f0c5ccba 100644 --- a/src/wren_vm.c +++ b/src/wren_vm.c @@ -689,6 +689,19 @@ static Value interpret(WrenVM* vm, ObjFiber* fiber) #define CASE_CODE(name) code_##name #define DISPATCH() goto *dispatchTable[instruction = *ip++] + // If you want to debug the VM and see the stack as each instruction is + // executed, uncomment this. + // TODO: Use a #define to enable/disable this. + /* + #define DISPATCH() \ + { \ + wrenDebugDumpStack(fiber); \ + wrenDebugDumpInstruction(vm, fn, (int)(ip - fn->bytecode)); \ + instruction = *ip++; \ + goto *dispatchTable[instruction]; \ + } + */ + #else #define INTERPRET_LOOP for (;;) switch (instruction = *ip++) @@ -1015,6 +1028,7 @@ static Value interpret(WrenVM* vm, ObjFiber* fiber) CASE_CODE(CLOSE_UPVALUE): closeUpvalue(fiber); + POP(); DISPATCH(); CASE_CODE(NEW): diff --git a/test/break/in_for_loop.wren b/test/break/in_for_loop.wren new file mode 100644 index 00000000..df89195a --- /dev/null +++ b/test/break/in_for_loop.wren @@ -0,0 +1,10 @@ +for (i in [1, 2, 3, 4, 5]) { + IO.write(i) + if (i > 2) break + IO.write(i) +} +// expect: 1 +// expect: 1 +// expect: 2 +// expect: 2 +// expect: 3 diff --git a/test/for.wren b/test/for.wren new file mode 100644 index 00000000..af0278f8 --- /dev/null +++ b/test/for.wren @@ -0,0 +1,20 @@ +// Single-expression body. +for (i in [1, 2, 3]) IO.write(i) +// expect: 1 +// expect: 2 +// expect: 3 + +// Block body. +for (i in [1, 2, 3]) { + IO.write(i) +} +// expect: 1 +// expect: 2 +// expect: 3 + +// Newline after "for". +for +(i in [1, 2, 3]) IO.write(i) +// expect: 1 +// expect: 2 +// expect: 3 diff --git a/test/for/close_over_loop_variable.wren b/test/for/close_over_loop_variable.wren new file mode 100644 index 00000000..f3a3a975 --- /dev/null +++ b/test/for/close_over_loop_variable.wren @@ -0,0 +1,11 @@ +var list = [] + +for (i in [1, 2, 3]) { + list.add(fn IO.write(i)) + //list.add(fn IO.write(i)) +} + +for (f in list) f.call +// expect: 1 +// expect: 2 +// expect: 3 diff --git a/test/for/closure_in_body.wren b/test/for/closure_in_body.wren new file mode 100644 index 00000000..c0d5af94 --- /dev/null +++ b/test/for/closure_in_body.wren @@ -0,0 +1,11 @@ +var list = [] + +for (i in [1, 2, 3]) { + var j = i + 1 + list.add(fn IO.write(j)) +} + +for (f in list) f.call +// expect: 2 +// expect: 3 +// expect: 4 diff --git a/test/for/for_in_syntax.wren b/test/for/for_in_syntax.wren new file mode 100644 index 00000000..af0278f8 --- /dev/null +++ b/test/for/for_in_syntax.wren @@ -0,0 +1,20 @@ +// Single-expression body. +for (i in [1, 2, 3]) IO.write(i) +// expect: 1 +// expect: 2 +// expect: 3 + +// Block body. +for (i in [1, 2, 3]) { + IO.write(i) +} +// expect: 1 +// expect: 2 +// expect: 3 + +// Newline after "for". +for +(i in [1, 2, 3]) IO.write(i) +// expect: 1 +// expect: 2 +// expect: 3 diff --git a/test/for/only_evaluate_sequence_once.wren b/test/for/only_evaluate_sequence_once.wren new file mode 100644 index 00000000..d7ddadcb --- /dev/null +++ b/test/for/only_evaluate_sequence_once.wren @@ -0,0 +1,10 @@ +var f = fn { + IO.write("evaluate sequence") + return [1, 2, 3] +} + +for (i in f.call) IO.write(i) +// expect: evaluate sequence +// expect: 1 +// expect: 2 +// expect: 3 diff --git a/test/while/closure_in_body.wren b/test/while/closure_in_body.wren new file mode 100644 index 00000000..1a61641f --- /dev/null +++ b/test/while/closure_in_body.wren @@ -0,0 +1,13 @@ +var list = [] + +var i = 1 +while (i < 4) { + var j = i + 1 + list.add(fn IO.write(j)) + i = i + 1 +} + +for (f in list) f.call +// expect: 2 +// expect: 3 +// expect: 4 diff --git a/test/while.wren b/test/while/syntax.wren similarity index 89% rename from test/while.wren rename to test/while/syntax.wren index 933d6d70..7138239a 100644 --- a/test/while.wren +++ b/test/while/syntax.wren @@ -22,3 +22,5 @@ while // expect: 1 // expect: 2 // expect: 3 + +// TODO: Close over variable in body. \ No newline at end of file