forked from Mirror/wren
For loops!
This commit is contained in:
@ -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
12
benchmark/for.lua
Normal 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
10
benchmark/for.py
Normal 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
7
benchmark/for.rb
Normal 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
17
benchmark/for.wren
Normal 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)
|
||||
@ -30,6 +30,8 @@ BENCHMARK("fib", r"""317811
|
||||
317811
|
||||
317811""")
|
||||
|
||||
BENCHMARK("for", r"""1999999000000""")
|
||||
|
||||
BENCHMARK("method_call", r"""true
|
||||
false""")
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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):
|
||||
|
||||
10
test/break/in_for_loop.wren
Normal file
10
test/break/in_for_loop.wren
Normal 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
20
test/for.wren
Normal 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
|
||||
11
test/for/close_over_loop_variable.wren
Normal file
11
test/for/close_over_loop_variable.wren
Normal 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
|
||||
11
test/for/closure_in_body.wren
Normal file
11
test/for/closure_in_body.wren
Normal 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
|
||||
20
test/for/for_in_syntax.wren
Normal file
20
test/for/for_in_syntax.wren
Normal 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
|
||||
10
test/for/only_evaluate_sequence_once.wren
Normal file
10
test/for/only_evaluate_sequence_once.wren
Normal 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
|
||||
13
test/while/closure_in_body.wren
Normal file
13
test/while/closure_in_body.wren
Normal 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
|
||||
@ -22,3 +22,5 @@ while
|
||||
// expect: 1
|
||||
// expect: 2
|
||||
// expect: 3
|
||||
|
||||
// TODO: Close over variable in body.
|
||||
Reference in New Issue
Block a user