1
0
forked from Mirror/wren

For loops!

This commit is contained in:
Bob Nystrom
2013-12-24 21:04:11 -08:00
parent 1e0401bfc1
commit e6e7b2594c
18 changed files with 404 additions and 163 deletions

View File

@ -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

12
benchmark/for.lua Normal file
View File

@ -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))

10
benchmark/for.py Normal file
View File

@ -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))

7
benchmark/for.rb Normal file
View File

@ -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

17
benchmark/for.wren Normal file
View File

@ -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)

View File

@ -30,6 +30,8 @@ BENCHMARK("fib", r"""317811
317811
317811""")
BENCHMARK("for", r"""1999999000000""")
BENCHMARK("method_call", r"""true
false""")

View File

@ -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

View File

@ -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);
}

View File

@ -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);

View File

@ -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):

View File

@ -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

20
test/for.wren Normal file
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -22,3 +22,5 @@ while
// expect: 1
// expect: 2
// expect: 3
// TODO: Close over variable in body.