|
|
@@ -8,6 +8,10 @@
|
|
|
|
#include "wren_compiler.h"
|
|
|
|
#include "wren_compiler.h"
|
|
|
|
#include "wren_vm.h"
|
|
|
|
#include "wren_vm.h"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#if WREN_DUMP_COMPILED_CODE
|
|
|
|
|
|
|
|
#include "wren_debug.h"
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
// This is written in bottom-up order, so the tokenization comes first, then
|
|
|
|
// This is written in bottom-up order, so the tokenization comes first, then
|
|
|
|
// parsing/code generation. This minimizes the number of explicit forward
|
|
|
|
// parsing/code generation. This minimizes the number of explicit forward
|
|
|
|
// declarations needed.
|
|
|
|
// declarations needed.
|
|
|
@@ -172,6 +176,27 @@ typedef struct
|
|
|
|
int index;
|
|
|
|
int index;
|
|
|
|
} CompilerUpvalue;
|
|
|
|
} CompilerUpvalue;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Keeps track of bookkeeping information for the current loop being compiled.
|
|
|
|
|
|
|
|
typedef struct sLoop
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
// Index of the instruction that the loop should jump back to.
|
|
|
|
|
|
|
|
int start;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Index of the argument for the CODE_JUMP_IF instruction used to exit the
|
|
|
|
|
|
|
|
// loop. Stored so we can patch it once we know where the loop ends.
|
|
|
|
|
|
|
|
int exitJump;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Index of the first instruction of the body of the loop.
|
|
|
|
|
|
|
|
int body;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Depth of the scope(s) that need to be exited if a break is hit inside the
|
|
|
|
|
|
|
|
// loop.
|
|
|
|
|
|
|
|
int scopeDepth;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// The loop enclosing this one, or NULL if this is the outermost loop.
|
|
|
|
|
|
|
|
struct sLoop* enclosing;
|
|
|
|
|
|
|
|
} Loop;
|
|
|
|
|
|
|
|
|
|
|
|
struct sCompiler
|
|
|
|
struct sCompiler
|
|
|
|
{
|
|
|
|
{
|
|
|
|
Parser* parser;
|
|
|
|
Parser* parser;
|
|
|
@@ -200,9 +225,8 @@ struct sCompiler
|
|
|
|
// in effect at all. Any variables declared will be global.
|
|
|
|
// in effect at all. Any variables declared will be global.
|
|
|
|
int scopeDepth;
|
|
|
|
int scopeDepth;
|
|
|
|
|
|
|
|
|
|
|
|
// Index of the first instruction of the body of the innermost loop currently
|
|
|
|
// The current innermost loop being compiled, or NULL if not in a loop.
|
|
|
|
// being compiled. Will be -1 if not currently inside a loop.
|
|
|
|
Loop* loop;
|
|
|
|
int loopBody;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Symbol table for the fields of the nearest enclosing class, or NULL if not
|
|
|
|
// Symbol table for the fields of the nearest enclosing class, or NULL if not
|
|
|
|
// currently inside a class.
|
|
|
|
// currently inside a class.
|
|
|
@@ -304,7 +328,7 @@ static void initCompiler(Compiler* compiler, Parser* parser, Compiler* parent,
|
|
|
|
compiler->constants = NULL;
|
|
|
|
compiler->constants = NULL;
|
|
|
|
|
|
|
|
|
|
|
|
compiler->numUpvalues = 0;
|
|
|
|
compiler->numUpvalues = 0;
|
|
|
|
compiler->loopBody = -1;
|
|
|
|
compiler->loop = NULL;
|
|
|
|
compiler->methodName = methodName;
|
|
|
|
compiler->methodName = methodName;
|
|
|
|
compiler->methodLength = methodLength;
|
|
|
|
compiler->methodLength = methodLength;
|
|
|
|
|
|
|
|
|
|
|
@@ -964,22 +988,23 @@ static void pushScope(Compiler* compiler)
|
|
|
|
compiler->scopeDepth++;
|
|
|
|
compiler->scopeDepth++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Closes the last pushed block scope. This should only be called in a statement
|
|
|
|
// Generates code to discard local variables at [depth] or greater. Does *not*
|
|
|
|
// context where no temporaries are still on the stack.
|
|
|
|
// actually undeclare variables or pop any scopes, though. This is called
|
|
|
|
static void popScope(Compiler* compiler)
|
|
|
|
// directly when compiling "break" statements to ditch the local variables
|
|
|
|
|
|
|
|
// before jumping out of the loop even though they are still in scope *past*
|
|
|
|
|
|
|
|
// the break instruction.
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// Returns the number of local variables that were eliminated.
|
|
|
|
|
|
|
|
static int discardLocals(Compiler* compiler, int depth)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
ASSERT(compiler->scopeDepth > -1, "Cannot pop top-level scope.");
|
|
|
|
ASSERT(compiler->scopeDepth > -1, "Cannot exit top-level scope.");
|
|
|
|
|
|
|
|
|
|
|
|
// Pop locals off the stack.
|
|
|
|
int local = compiler->numLocals - 1;
|
|
|
|
while (compiler->numLocals > 0 &&
|
|
|
|
while (local >= 0 && compiler->locals[local].depth >= depth)
|
|
|
|
compiler->locals[compiler->numLocals - 1].depth ==
|
|
|
|
|
|
|
|
compiler->scopeDepth)
|
|
|
|
|
|
|
|
{
|
|
|
|
{
|
|
|
|
compiler->numLocals--;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// If the local was closed over, make sure the upvalue gets closed when it
|
|
|
|
// If the local was closed over, make sure the upvalue gets closed when it
|
|
|
|
// goes out of scope on the stack.
|
|
|
|
// goes out of scope on the stack.
|
|
|
|
if (compiler->locals[compiler->numLocals].isUpvalue)
|
|
|
|
if (compiler->locals[local].isUpvalue)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
emit(compiler, CODE_CLOSE_UPVALUE);
|
|
|
|
emit(compiler, CODE_CLOSE_UPVALUE);
|
|
|
|
}
|
|
|
|
}
|
|
|
@@ -987,8 +1012,19 @@ static void popScope(Compiler* compiler)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
emit(compiler, CODE_POP);
|
|
|
|
emit(compiler, CODE_POP);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
local--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return compiler->numLocals - local - 1;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Closes the last pushed block scope and discards any local variables declared
|
|
|
|
|
|
|
|
// in that scope. This should only be called in a statement context where no
|
|
|
|
|
|
|
|
// temporaries are still on the stack.
|
|
|
|
|
|
|
|
static void popScope(Compiler* compiler)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
compiler->numLocals -= discardLocals(compiler, compiler->scopeDepth);
|
|
|
|
compiler->scopeDepth--;
|
|
|
|
compiler->scopeDepth--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@@ -1185,7 +1221,9 @@ static ObjFn* endCompiler(Compiler* compiler,
|
|
|
|
|
|
|
|
|
|
|
|
unpinObj(compiler->parser->vm);
|
|
|
|
unpinObj(compiler->parser->vm);
|
|
|
|
|
|
|
|
|
|
|
|
// wrenDebugPrintCode(compiler->parser->vm, fn);
|
|
|
|
#if WREN_DUMP_COMPILED_CODE
|
|
|
|
|
|
|
|
wrenDebugPrintCode(compiler->parser->vm, fn);
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
return fn;
|
|
|
|
return fn;
|
|
|
|
}
|
|
|
|
}
|
|
|
@@ -2025,18 +2063,46 @@ static int getNumArguments(const uint8_t* bytecode, const Value* constants,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int startLoopBody(Compiler* compiler)
|
|
|
|
// Marks the beginning of a loop. Keeps track of the current instruction so we
|
|
|
|
|
|
|
|
// know what to loop back to at the end of the body.
|
|
|
|
|
|
|
|
static void startLoop(Compiler* compiler, Loop* loop)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
int outerLoopBody = compiler->loopBody;
|
|
|
|
loop->enclosing = compiler->loop;
|
|
|
|
compiler->loopBody = compiler->bytecode.count;
|
|
|
|
loop->start = compiler->bytecode.count - 1;
|
|
|
|
return outerLoopBody;
|
|
|
|
loop->scopeDepth = compiler->scopeDepth;
|
|
|
|
|
|
|
|
compiler->loop = loop;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void endLoopBody(Compiler* compiler, int outerLoopBody)
|
|
|
|
// Emits the [CODE_JUMP_IF] instruction used to test the loop condition and
|
|
|
|
|
|
|
|
// potentially exit the loop. Keeps track of the instruction so we can patch it
|
|
|
|
|
|
|
|
// later once we know where the end of the body is.
|
|
|
|
|
|
|
|
static void testExitLoop(Compiler* compiler)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
|
|
|
|
// TODO: Support longer jumps.
|
|
|
|
|
|
|
|
compiler->loop->exitJump = emitByte(compiler, CODE_JUMP_IF, 255);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Compiles the body of the loop and tracks its extent so that contained "break"
|
|
|
|
|
|
|
|
// statements can be handled correctly.
|
|
|
|
|
|
|
|
static void loopBody(Compiler* compiler)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
compiler->loop->body = compiler->bytecode.count;
|
|
|
|
|
|
|
|
block(compiler);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Ends the current innermost loop. Patches up all jumps and breaks now that
|
|
|
|
|
|
|
|
// we know where the end of the loop is.
|
|
|
|
|
|
|
|
static void endLoop(Compiler* compiler)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
emit(compiler, CODE_LOOP);
|
|
|
|
|
|
|
|
int loopOffset = compiler->bytecode.count - compiler->loop->start;
|
|
|
|
|
|
|
|
emit(compiler, loopOffset);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
patchJump(compiler, compiler->loop->exitJump);
|
|
|
|
|
|
|
|
|
|
|
|
// Find any break placeholder instructions (which will be CODE_END in the
|
|
|
|
// Find any break placeholder instructions (which will be CODE_END in the
|
|
|
|
// bytecode) and replace them with real jumps.
|
|
|
|
// bytecode) and replace them with real jumps.
|
|
|
|
int i = compiler->loopBody;
|
|
|
|
int i = compiler->loop->body;
|
|
|
|
while (i < compiler->bytecode.count)
|
|
|
|
while (i < compiler->bytecode.count)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
if (compiler->bytecode.data[i] == CODE_END)
|
|
|
|
if (compiler->bytecode.data[i] == CODE_END)
|
|
|
@@ -2053,7 +2119,7 @@ static void endLoopBody(Compiler* compiler, int outerLoopBody)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
compiler->loopBody = outerLoopBody;
|
|
|
|
compiler->loop = compiler->loop->enclosing;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void forStatement(Compiler* compiler)
|
|
|
|
static void forStatement(Compiler* compiler)
|
|
|
@@ -2113,8 +2179,8 @@ static void forStatement(Compiler* compiler)
|
|
|
|
|
|
|
|
|
|
|
|
consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after loop expression.");
|
|
|
|
consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after loop expression.");
|
|
|
|
|
|
|
|
|
|
|
|
// Remember what instruction to loop back to.
|
|
|
|
Loop loop;
|
|
|
|
int loopStart = compiler->bytecode.count - 1;
|
|
|
|
startLoop(compiler, &loop);
|
|
|
|
|
|
|
|
|
|
|
|
// Advance the iterator by calling the ".iterate" method on the sequence.
|
|
|
|
// Advance the iterator by calling the ".iterate" method on the sequence.
|
|
|
|
emitByte(compiler, CODE_LOAD_LOCAL, seqSlot);
|
|
|
|
emitByte(compiler, CODE_LOAD_LOCAL, seqSlot);
|
|
|
@@ -2126,12 +2192,7 @@ static void forStatement(Compiler* compiler)
|
|
|
|
emitByte(compiler, CODE_STORE_LOCAL, iterSlot);
|
|
|
|
emitByte(compiler, CODE_STORE_LOCAL, iterSlot);
|
|
|
|
// TODO: We can probably get this working with a bit less stack juggling.
|
|
|
|
// TODO: We can probably get this working with a bit less stack juggling.
|
|
|
|
|
|
|
|
|
|
|
|
// If it returned something falsy, jump out of the loop.
|
|
|
|
testExitLoop(compiler);
|
|
|
|
// TODO: Support longer jumps.
|
|
|
|
|
|
|
|
int exitJump = emitByte(compiler, CODE_JUMP_IF, 255);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Create a scope for the loop body.
|
|
|
|
|
|
|
|
pushScope(compiler);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Get the current value in the sequence by calling ".iteratorValue".
|
|
|
|
// Get the current value in the sequence by calling ".iteratorValue".
|
|
|
|
emitByte(compiler, CODE_LOAD_LOCAL, seqSlot);
|
|
|
|
emitByte(compiler, CODE_LOAD_LOCAL, seqSlot);
|
|
|
@@ -2140,51 +2201,35 @@ static void forStatement(Compiler* compiler)
|
|
|
|
emitShort(compiler, CODE_CALL_1,
|
|
|
|
emitShort(compiler, CODE_CALL_1,
|
|
|
|
methodSymbol(compiler, "iteratorValue ", 14));
|
|
|
|
methodSymbol(compiler, "iteratorValue ", 14));
|
|
|
|
|
|
|
|
|
|
|
|
// Bind it to the loop variable.
|
|
|
|
// Bind the loop variable in its own scope. This ensures we get a fresh
|
|
|
|
|
|
|
|
// variable each iteration so that closures for it don't all see the same one.
|
|
|
|
|
|
|
|
pushScope(compiler);
|
|
|
|
defineLocal(compiler, name, length);
|
|
|
|
defineLocal(compiler, name, length);
|
|
|
|
|
|
|
|
|
|
|
|
// Compile the body.
|
|
|
|
loopBody(compiler);
|
|
|
|
int outerLoopBody = startLoopBody(compiler);
|
|
|
|
|
|
|
|
block(compiler);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Loop variable.
|
|
|
|
popScope(compiler);
|
|
|
|
popScope(compiler);
|
|
|
|
|
|
|
|
|
|
|
|
// Loop back to the top.
|
|
|
|
endLoop(compiler);
|
|
|
|
emit(compiler, CODE_LOOP);
|
|
|
|
|
|
|
|
int loopOffset = compiler->bytecode.count - loopStart;
|
|
|
|
|
|
|
|
emit(compiler, loopOffset);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
patchJump(compiler, exitJump);
|
|
|
|
|
|
|
|
endLoopBody(compiler, outerLoopBody);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Hidden variables.
|
|
|
|
popScope(compiler);
|
|
|
|
popScope(compiler);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void whileStatement(Compiler* compiler)
|
|
|
|
static void whileStatement(Compiler* compiler)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
// Remember what instruction to loop back to.
|
|
|
|
Loop loop;
|
|
|
|
int loopStart = compiler->bytecode.count - 1;
|
|
|
|
startLoop(compiler, &loop);
|
|
|
|
|
|
|
|
|
|
|
|
// Compile the condition.
|
|
|
|
// Compile the condition.
|
|
|
|
consume(compiler, TOKEN_LEFT_PAREN, "Expect '(' after 'while'.");
|
|
|
|
consume(compiler, TOKEN_LEFT_PAREN, "Expect '(' after 'while'.");
|
|
|
|
expression(compiler);
|
|
|
|
expression(compiler);
|
|
|
|
consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after while condition.");
|
|
|
|
consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after while condition.");
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: Support longer jumps.
|
|
|
|
testExitLoop(compiler);
|
|
|
|
int exitJump = emitByte(compiler, CODE_JUMP_IF, 255);
|
|
|
|
loopBody(compiler);
|
|
|
|
|
|
|
|
endLoop(compiler);
|
|
|
|
// Compile the body.
|
|
|
|
|
|
|
|
int outerLoopBody = startLoopBody(compiler);
|
|
|
|
|
|
|
|
block(compiler);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Loop back to the top.
|
|
|
|
|
|
|
|
emit(compiler, CODE_LOOP);
|
|
|
|
|
|
|
|
int loopOffset = compiler->bytecode.count - loopStart;
|
|
|
|
|
|
|
|
// TODO: Support longer jumps.
|
|
|
|
|
|
|
|
emit(compiler, loopOffset);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
patchJump(compiler, exitJump);
|
|
|
|
|
|
|
|
endLoopBody(compiler, outerLoopBody);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Compiles a statement. These can only appear at the top-level or within
|
|
|
|
// Compiles a statement. These can only appear at the top-level or within
|
|
|
@@ -2193,11 +2238,16 @@ void statement(Compiler* compiler)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
if (match(compiler, TOKEN_BREAK))
|
|
|
|
if (match(compiler, TOKEN_BREAK))
|
|
|
|
{
|
|
|
|
{
|
|
|
|
if (compiler->loopBody == -1)
|
|
|
|
if (compiler->loop == NULL)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
error(compiler, "Cannot use 'break' outside of a loop.");
|
|
|
|
error(compiler, "Cannot use 'break' outside of a loop.");
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Since we will be jumping out of the scope, make sure any locals in it
|
|
|
|
|
|
|
|
// are discarded first.
|
|
|
|
|
|
|
|
discardLocals(compiler, compiler->loop->scopeDepth + 1);
|
|
|
|
|
|
|
|
|
|
|
|
// Emit a placeholder instruction for the jump to the end of the body. When
|
|
|
|
// Emit a placeholder instruction for the jump to the end of the body. When
|
|
|
|
// we're done compiling the loop body and know where the end is, we'll
|
|
|
|
// we're done compiling the loop body and know where the end is, we'll
|
|
|
|
// replace these with `CODE_JUMP` instructions with appropriate offsets.
|
|
|
|
// replace these with `CODE_JUMP` instructions with appropriate offsets.
|
|
|
|