Get closures working!

In the process, I had to change the grammar. There is now a strong
separation between statements and expressions. The code was just wrong
before when it popped locals at the end of a block scope because there
could be temporaries on the stack if the block was in expression
position. This fixes that.

Still need to implement closing over `this`.
This commit is contained in:
Bob Nystrom
2013-12-04 07:43:50 -08:00
parent c14b115c02
commit 157944aa27
32 changed files with 1063 additions and 416 deletions
+342 -243
View File
@@ -27,10 +27,14 @@
// maximum number of variables in scope at one time, and spans block scopes.
//
// Note that this limitation is also explicit in the bytecode. Since
// [CODE_LOAD_LOCAL] and [CODE_STORE_LOCAL] use a single argument byte to
// `CODE_LOAD_LOCAL` and `CODE_STORE_LOCAL` use a single argument byte to
// identify the local, only 256 can be in scope at one time.
#define MAX_LOCALS (256)
// The maximum number of upvalues (i.e. variables from enclosing functions)
// that a function can close over.
#define MAX_UPVALUES (256)
typedef enum
{
TOKEN_LEFT_PAREN,
@@ -67,6 +71,7 @@ typedef enum
TOKEN_IF,
TOKEN_IS,
TOKEN_NULL,
TOKEN_RETURN,
TOKEN_STATIC,
TOKEN_THIS,
TOKEN_TRUE,
@@ -146,13 +151,26 @@ typedef struct
// the outermost scope--parameters for a method, or the first local block in
// top level code. One is the scope within that, etc.
int depth;
// Non-zero if this local variable is being used as an upvalue.
int isUpvalue;
} Local;
typedef struct
{
// Non-zero if this upvalue is capturing a local variable from the enclosing
// function. Zero if it's capturing an upvalue.
int isLocal;
// The index of the local or upvalue being captured in the enclosing function.
int index;
} CompilerUpvalue;
typedef struct sCompiler
{
Parser* parser;
// The compiler for the block enclosing this one, or NULL if it's the
// The compiler for the function enclosing this one, or NULL if it's the
// top level.
struct sCompiler* parent;
@@ -177,11 +195,17 @@ typedef struct sCompiler
// The currently in scope local variables.
Local locals[MAX_LOCALS];
// The upvalues that this function has captured from outer scopes. The count
// of them is stored in `fn->numUpvalues`.
CompilerUpvalue upvalues[MAX_UPVALUES];
} Compiler;
// Adds [constant] to the constant pool and returns its index.
static int addConstant(Compiler* compiler, Value constant)
{
// TODO(bob): Look for existing equal constant.
// TODO(bob): Check for overflow.
compiler->fn->constants[compiler->fn->numConstants++] = constant;
return compiler->fn->numConstants - 1;
}
@@ -209,14 +233,9 @@ static int initCompiler(Compiler* compiler, Parser* parser,
compiler->locals[0].name = NULL;
compiler->locals[0].length = 0;
compiler->locals[0].depth = -1;
compiler->locals[0].isUpvalue = 0;
// The initial scope for function or method is a local scope.
// TODO(bob): Need to explicitly pop this scope at end of fn/method so
// that we can correctly close over locals declared at top level of member.
// also, when done compiling fn/method, need to count total number of
// upvalues and store in fnobj. note: have to make sure we include upvalues
// added because a fn within this one closed over something outside of this
// one and we had to add upvalue here to flatten the closure.
compiler->scopeDepth = 0;
}
@@ -224,7 +243,6 @@ static int initCompiler(Compiler* compiler, Parser* parser,
compiler->fields = parent != NULL ? parent->fields : NULL;
compiler->fn = wrenNewFunction(parser->vm);
compiler->fn->numConstants = 0;
if (parent == NULL) return -1;
@@ -233,31 +251,6 @@ static int initCompiler(Compiler* compiler, Parser* parser,
return addConstant(parent, OBJ_VAL(compiler->fn));
}
// Emits one bytecode instruction or argument.
static int emit(Compiler* compiler, Code code)
{
compiler->fn->bytecode[compiler->numCodes++] = code;
return compiler->numCodes - 1;
}
// Finishes [compiler], which is compiling a function, method, or chunk of top
// level code. If there is a parent compiler, then this emits code in the
// parent compiler to load the resulting function.
static void endCompiler(Compiler* compiler, int constant)
{
// End the function's code.
emit(compiler, CODE_END);
// TODO(bob): will need to compile different code to capture upvalues if fn
// has them.
if (compiler->parent != NULL)
{
// In the function that contains this one, load the resulting function object.
emit(compiler->parent, CODE_CONSTANT);
emit(compiler->parent, constant);
}
}
// Outputs a compile or syntax error.
static void error(Compiler* compiler, const char* format, ...)
{
@@ -417,6 +410,7 @@ static void readName(Parser* parser, TokenType type)
if (isKeyword(parser, "if")) type = TOKEN_IF;
if (isKeyword(parser, "is")) type = TOKEN_IS;
if (isKeyword(parser, "null")) type = TOKEN_NULL;
if (isKeyword(parser, "return")) type = TOKEN_RETURN;
if (isKeyword(parser, "static")) type = TOKEN_STATIC;
if (isKeyword(parser, "this")) type = TOKEN_THIS;
if (isKeyword(parser, "true")) type = TOKEN_TRUE;
@@ -680,6 +674,13 @@ static Token* consume(Compiler* compiler, TokenType expected,
// Variables and scopes --------------------------------------------------------
// Emits one bytecode instruction or argument.
static int emit(Compiler* compiler, Code code)
{
compiler->fn->bytecode[compiler->numCodes++] = code;
return compiler->numCodes - 1;
}
// Parses a name token and declares a variable in the current scope with that
// name. Returns its symbol.
static int declareVariable(Compiler* compiler)
@@ -729,41 +730,22 @@ static int declareVariable(Compiler* compiler)
local->name = token->start;
local->length = token->length;
local->depth = compiler->scopeDepth;
local->isUpvalue = 0;
return compiler->numLocals++;
}
// Stores a variable with the previously defined symbol in the current scope.
static void defineVariable(Compiler* compiler, int symbol)
{
// Handle top-level global scope.
if (compiler->scopeDepth == -1)
{
// It's a global variable, so store the value in the global slot.
emit(compiler, CODE_STORE_GLOBAL);
emit(compiler, symbol);
}
else
{
// It's a local variable. The value is already in the right slot to store
// the local, but later code will pop and discard that. To cancel that out
// duplicate it now, so that the temporary value will be discarded and
// leave the local still on the stack.
// TODO(bob): Since variables are declared in statement position, this
// generates a lot of code like:
//
// var a = "value"
// io.write(a)
//
// CODE_CONSTANT "value" // put constant into local slot
// CODE_DUP // dup it so the top is a temporary
// CODE_POP // discard previous result in sequence
// <code for io.write...>
//
// Would be good to either peephole optimize this or be smarter about
// generating code for defining local variables to not emit the DUP
// sometimes.
emit(compiler, CODE_DUP);
}
// Store the variable. If it's a local, the result of the initializer is
// in the correct slot on the stack already so we're done.
if (compiler->scopeDepth >= 0) return;
// 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);
emit(compiler, CODE_POP);
}
// Starts a new local block scope.
@@ -772,39 +754,42 @@ static void pushScope(Compiler* compiler)
compiler->scopeDepth++;
}
// Closes the last pushed block scope.
// Closes the last pushed block scope. This should only be called in a statement
// context where no temporaries are still on the stack.
static void popScope(Compiler* compiler)
{
ASSERT(compiler->scopeDepth > -1, "Cannot pop top-level scope.");
// Pop locals off the stack.
// TODO(bob): Could make a single instruction that pops multiple values if
// this is a bottleneck.
while (compiler->numLocals > 0 &&
compiler->locals[compiler->numLocals - 1].depth ==
compiler->scopeDepth)
{
compiler->numLocals--;
emit(compiler, CODE_POP);
// If the local was closed over, make sure the upvalue gets closed when it
// goes out of scope on the stack.
if (compiler->locals[compiler->numLocals].isUpvalue)
{
emit(compiler, CODE_CLOSE_UPVALUE);
}
else
{
emit(compiler, CODE_POP);
}
}
// TODO(bob): Need to emit code to capture upvalue for any local going out of
// scope now that is closed over.
compiler->scopeDepth--;
}
// Look up the previously consumed token, which is presumed to be a TOKEN_NAME
// in the current scope to see what name it is bound to. Returns the index of
// the name either in global or local scope. Returns -1 if not found. Sets
// [isGlobal] to non-zero if the name is in global scope, or 0 if in local.
static int resolveName(Compiler* compiler, int* isGlobal)
// Attempts to look up the previously consumed name token in the local variables
// of [compiler]. If found, returns its index, otherwise returns -1.
static int resolveLocal(Compiler* compiler)
{
Token* token = &compiler->parser->previous;
// Look it up in the local scopes. Look in reverse order so that the most
// nested variable is found first and shadows outer ones.
*isGlobal = 0;
for (int i = compiler->numLocals - 1; i >= 0; i--)
{
if (compiler->locals[i].length == token->length &&
@@ -814,17 +799,103 @@ static int resolveName(Compiler* compiler, int* isGlobal)
}
}
// TODO(bob): Closures!
// look in current upvalues to see if we've already closed over it
// if so, just use that
// walk up parent chain looking in their local scopes for variable
// if we find it, need to close over it here
// add upvalue to fn being compiled
// return index of upvalue
// instead of isGlobal, should be some local/upvalue/global enum
return -1;
}
// Adds an upvalue to [compiler]'s function with the given properties. Does not
// add one if an upvalue for that variable is already in the list. Returns the
// index of the uvpalue.
static int addUpvalue(Compiler* compiler, int isLocal, int index)
{
// Look for an existing one.
for (int i = 0; i < compiler->fn->numUpvalues; i++)
{
CompilerUpvalue* upvalue = &compiler->upvalues[i];
if (upvalue->index == index && upvalue->isLocal == isLocal) return i;
}
// If we got here, it's a new upvalue.
compiler->upvalues[compiler->fn->numUpvalues].isLocal = isLocal;
compiler->upvalues[compiler->fn->numUpvalues].index = index;
return compiler->fn->numUpvalues++;
}
// Attempts to look up the previously consumed name token in the functions
// enclosing the one being compiled by [compiler]. If found, it adds an upvalue
// for it to this compiler's list of upvalues (unless it's already in there)
// and returns its index. If not found, returns -1.
//
// If the name is found outside of the immediately enclosing function, this
// will flatten the closure and add upvalues to all of the intermediate
// functions so that it gets walked down to this one.
static int findUpvalue(Compiler* compiler)
{
// If we are out of enclosing functions, it can't be an upvalue.
if (compiler->parent == NULL)
{
return -1;
}
// See if it's a local variable in the immediately enclosing function.
int local = resolveLocal(compiler->parent);
if (local != -1)
{
// Mark the local as an upvalue so we know to close it when it goes out of
// scope.
compiler->parent->locals[local].isUpvalue = 1;
return addUpvalue(compiler, 1, local);
}
// See if it's an upvalue in the immediately enclosing function. In other
// words, if its a local variable in a non-immediately enclosing function.
// This will "flatten" closures automatically: it will add upvalues to all
// of the intermediate functions to get from the function where a local is
// declared all the way into the possibly deeply nested function that is
// closing over it.
int upvalue = findUpvalue(compiler->parent);
if (upvalue != -1)
{
return addUpvalue(compiler, 0, upvalue);
}
// If we got here, we walked all the way up the parent chain and couldn't
// find it.
return -1;
}
// A name may resolve to refer to a variable in a few different places: local
// scope in the current function, an upvalue for variables being closed over
// from enclosing functions, or a top-level global variable.
typedef enum
{
NAME_LOCAL,
NAME_UPVALUE,
NAME_GLOBAL
} ResolvedName;
// Look up the previously consumed token, which is presumed to be a TOKEN_NAME
// in the current scope to see what name it is bound to. Returns the index of
// the name either in global or local scope. Returns -1 if not found. Sets
// [isGlobal] to non-zero if the name is in global scope, or 0 if in local.
static int resolveName(Compiler* compiler, ResolvedName* resolved)
{
Token* token = &compiler->parser->previous;
// Look it up in the local scopes. Look in reverse order so that the most
// nested variable is found first and shadows outer ones.
*resolved = NAME_LOCAL;
int local = resolveLocal(compiler);
if (local != -1) return local;
// If we got here, it's not a local, so lets see if we are closing over an
// outer local.
*resolved = NAME_UPVALUE;
int upvalue = findUpvalue(compiler);
if (upvalue != -1) return upvalue;
// If we got here, it wasn't in a local scope, so try the global scope.
*isGlobal = 1;
*resolved = NAME_GLOBAL;
return findSymbol(&compiler->parser->vm->globalSymbols,
token->start, token->length);
}
@@ -839,6 +910,43 @@ static int copyName(Compiler* compiler, char* name)
return token->length;
}
// Finishes [compiler], which is compiling a function, method, or chunk of top
// level code. If there is a parent compiler, then this emits code in the
// parent compiler to load the resulting function.
static void endCompiler(Compiler* compiler, int constant)
{
// Mark the end of the bytecode. Since it may contain multiple early returns,
// we can't rely on CODE_RETURN to tell us we're at the end.
emit(compiler, CODE_END);
// In the function that contains this one, load the resulting function object.
if (compiler->parent != NULL)
{
// If the function has no upvalues, we don't need to create a closure.
// We can just load and run the function directly.
if (compiler->fn->numUpvalues == 0)
{
emit(compiler->parent, CODE_CONSTANT);
emit(compiler->parent, constant);
}
else
{
// Capture the upvalues in the new closure object.
emit(compiler->parent, CODE_CLOSURE);
emit(compiler->parent, constant);
// Emit arguments for each upvalue to know whether to capture a local or
// an upvalue.
// TODO(bob): Do something more efficient here?
for (int i = 0; i < compiler->fn->numUpvalues; i++)
{
emit(compiler->parent, compiler->upvalues[i].isLocal);
emit(compiler->parent, compiler->upvalues[i].index);
}
}
}
}
// Grammar ---------------------------------------------------------------------
typedef enum
@@ -858,10 +966,8 @@ typedef enum
} Precedence;
// Forward declarations since the grammar is recursive.
static void expression(Compiler* compiler, int allowAssignment);
static void assignment(Compiler* compiler);
static void expression(Compiler* compiler);
static void statement(Compiler* compiler);
static void definition(Compiler* compiler);
static void parsePrecedence(Compiler* compiler, int allowAssignment,
Precedence precedence);
@@ -892,7 +998,7 @@ static void finishBlock(Compiler* compiler)
{
for (;;)
{
definition(compiler);
statement(compiler);
// If there is no newline, it must be the end of the block on the same line.
if (!match(compiler, TOKEN_LINE))
@@ -902,9 +1008,6 @@ static void finishBlock(Compiler* compiler)
}
if (match(compiler, TOKEN_RIGHT_BRACE)) break;
// Discard the result of the previous expression.
emit(compiler, CODE_POP);
}
}
@@ -942,7 +1045,7 @@ static void parameterList(Compiler* compiler, char* name, int* length)
static void grouping(Compiler* compiler, int allowAssignment)
{
assignment(compiler);
expression(compiler);
consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after expression.");
}
@@ -955,7 +1058,7 @@ static void list(Compiler* compiler, int allowAssignment)
do
{
numElements++;
assignment(compiler);
expression(compiler);
} while (match(compiler, TOKEN_COMMA));
}
@@ -1004,12 +1107,16 @@ static void function(Compiler* compiler, int allowAssignment)
{
// Block body.
finishBlock(&fnCompiler);
// Implicitly return null.
emit(&fnCompiler, CODE_NULL);
emit(&fnCompiler, CODE_RETURN);
}
else
{
// Single expression body.
// TODO(bob): Allow assignment here?
expression(&fnCompiler, 0);
expression(&fnCompiler);
emit(&fnCompiler, CODE_RETURN);
}
endCompiler(&fnCompiler, constant);
@@ -1039,7 +1146,7 @@ static void field(Compiler* compiler, int allowAssignment)
if (!allowAssignment) error(compiler, "Invalid assignment.");
// Compile the right-hand side.
statement(compiler);
expression(compiler);
emit(compiler, CODE_STORE_FIELD);
emit(compiler, field);
@@ -1053,13 +1160,9 @@ static void field(Compiler* compiler, int allowAssignment)
static void name(Compiler* compiler, int allowAssignment)
{
// Look up the name in the scope chain.
int isGlobal;
int index = resolveName(compiler, &isGlobal);
if (index == -1)
{
error(compiler, "Undefined variable.");
}
ResolvedName resolved;
int index = resolveName(compiler, &resolved);
if (index == -1) error(compiler, "Undefined variable.");
// If there's an "=" after a bare name, it's a variable assignment.
if (match(compiler, TOKEN_EQ))
@@ -1067,37 +1170,28 @@ static void name(Compiler* compiler, int allowAssignment)
if (!allowAssignment) error(compiler, "Invalid assignment.");
// Compile the right-hand side.
statement(compiler);
expression(compiler);
// TODO(bob): Handle assigning to upvalue.
if (isGlobal)
switch (resolved)
{
emit(compiler, CODE_STORE_GLOBAL);
emit(compiler, index);
}
else
{
emit(compiler, CODE_STORE_LOCAL);
emit(compiler, index);
case NAME_LOCAL: emit(compiler, CODE_STORE_LOCAL); break;
case NAME_UPVALUE: emit(compiler, CODE_STORE_UPVALUE); break;
case NAME_GLOBAL: emit(compiler, CODE_STORE_GLOBAL); break;
}
emit(compiler, index);
return;
}
// TODO(bob): Handle reading upvalue.
// Otherwise, it's just a variable access.
if (isGlobal)
switch (resolved)
{
emit(compiler, CODE_LOAD_GLOBAL);
emit(compiler, index);
}
else
{
emit(compiler, CODE_LOAD_LOCAL);
emit(compiler, index);
case NAME_LOCAL: emit(compiler, CODE_LOAD_LOCAL); break;
case NAME_UPVALUE: emit(compiler, CODE_LOAD_UPVALUE); break;
case NAME_GLOBAL: emit(compiler, CODE_LOAD_GLOBAL); break;
}
emit(compiler, index);
}
static void null(Compiler* compiler, int allowAssignment)
@@ -1190,7 +1284,7 @@ static void subscript(Compiler* compiler, int allowAssignment)
MAX_PARAMETERS);
}
statement(compiler);
expression(compiler);
// Add a space in the name for each argument. Lets us overload by
// arity.
@@ -1235,8 +1329,8 @@ void call(Compiler* compiler, int allowAssignment)
error(compiler, "Cannot pass more than %d arguments to a method.",
MAX_PARAMETERS);
}
statement(compiler);
expression(compiler);
// Add a space in the name for each argument. Lets us overload by
// arity.
@@ -1373,6 +1467,7 @@ GrammarRule rules[] =
/* TOKEN_IF */ UNUSED,
/* TOKEN_IS */ INFIX(PREC_IS, is),
/* TOKEN_NULL */ PREFIX(null),
/* TOKEN_RETURN */ UNUSED,
/* TOKEN_STATIC */ UNUSED,
/* TOKEN_THIS */ PREFIX(this_),
/* TOKEN_TRUE */ PREFIX(boolean),
@@ -1410,105 +1505,11 @@ void parsePrecedence(Compiler* compiler, int allowAssignment,
}
}
// Parses an expression (or, really, the subset of expressions that can appear
// outside of the top level of a block). Does not include "statement-like"
// things like variable declarations.
void expression(Compiler* compiler, int allowAssignment)
// Parses an expression. Unlike statements, expressions leave a resulting value
// on the stack.
void expression(Compiler* compiler)
{
parsePrecedence(compiler, allowAssignment, PREC_LOWEST);
}
// Compiles an assignment expression.
void assignment(Compiler* compiler)
{
// Assignment statement.
expression(compiler, 1);
}
// Parses a "statement": any expression including expressions like variable
// declarations which can only appear at the top level of a block.
void statement(Compiler* compiler)
{
if (match(compiler, TOKEN_IF))
{
// Compile the condition.
consume(compiler, TOKEN_LEFT_PAREN, "Expect '(' after 'if'.");
assignment(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);
// Compile the then branch.
pushScope(compiler);
definition(compiler);
popScope(compiler);
// Jump over the else branch when the if branch is taken.
emit(compiler, CODE_JUMP);
int elseJump = emit(compiler, 255);
patchJump(compiler, ifJump);
// Compile the else branch if there is one.
if (match(compiler, TOKEN_ELSE))
{
pushScope(compiler);
definition(compiler);
popScope(compiler);
}
else
{
// Just default to null.
emit(compiler, CODE_NULL);
}
// Patch the jump over the else.
patchJump(compiler, elseJump);
return;
}
if (match(compiler, TOKEN_WHILE))
{
// Remember what instruction to loop back to.
int loopStart = compiler->numCodes - 1;
// Compile the condition.
consume(compiler, TOKEN_LEFT_PAREN, "Expect '(' after 'while'.");
assignment(compiler);
consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after while condition.");
emit(compiler, CODE_JUMP_IF);
int exitJump = emit(compiler, 255);
// Compile the body.
pushScope(compiler);
definition(compiler);
popScope(compiler);
// Loop back to the top.
emit(compiler, CODE_LOOP);
int loopOffset = compiler->numCodes - loopStart;
emit(compiler, loopOffset);
patchJump(compiler, exitJump);
// A while loop always evaluates to null.
emit(compiler, CODE_NULL);
return;
}
// Curly block.
if (match(compiler, TOKEN_LEFT_BRACE))
{
pushScope(compiler);
finishBlock(compiler);
popScope(compiler);
return;
}
assignment(compiler);
parsePrecedence(compiler, 1, PREC_LOWEST);
}
// Compiles a method definition inside a class body.
@@ -1528,16 +1529,22 @@ void method(Compiler* compiler, Code instruction, SignatureFn signature)
consume(compiler, TOKEN_LEFT_BRACE, "Expect '{' to begin method body.");
finishBlock(&methodCompiler);
// TODO(bob): Single-expression methods that implicitly return the result.
// If it's a constructor, return "this", not the result of the body.
// If it's a constructor, return "this".
if (instruction == CODE_METHOD_CTOR)
{
emit(&methodCompiler, CODE_POP);
// The receiver is always stored in the first local slot.
// TODO(bob): Will need to do something different to handle functions
// enclosed in methods.
emit(&methodCompiler, CODE_LOAD_LOCAL);
emit(&methodCompiler, 0);
emit(&methodCompiler, CODE_RETURN);
}
else
{
// End the method's code.
emit(&methodCompiler, CODE_NULL);
emit(&methodCompiler, CODE_RETURN);
}
endCompiler(&methodCompiler, constant);
@@ -1547,8 +1554,34 @@ void method(Compiler* compiler, Code instruction, SignatureFn signature)
emit(compiler, symbol);
}
// Compiles a name-binding statement.
void definition(Compiler* compiler)
// Parses a curly block or an expression statement. Used in places like the
// arms of an if statement where either a single expression or a curly body is
// allowed.
void block(Compiler* compiler)
{
// Curly block.
if (match(compiler, TOKEN_LEFT_BRACE))
{
pushScope(compiler);
finishBlock(compiler);
popScope(compiler);
return;
}
// TODO(bob): Only allowing expressions here means you can't do:
//
// if (foo) return "blah"
//
// since return is a statement (or should it be an expression?).
// Expression statement.
expression(compiler);
emit(compiler, CODE_POP);
}
// 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)
{
if (match(compiler, TOKEN_CLASS))
{
@@ -1572,9 +1605,6 @@ void definition(Compiler* compiler)
// used.
int numFieldsInstruction = emit(compiler, 255);
// Store it in its name.
defineVariable(compiler, symbol);
// Compile the method definitions.
consume(compiler, TOKEN_LEFT_BRACE, "Expect '}' after class body.");
@@ -1602,8 +1632,7 @@ void definition(Compiler* compiler)
}
else if (match(compiler, TOKEN_THIS))
{
// If the method name is prefixed with "this", it's a named constructor.
// TODO(bob): Allow defining unnamed constructor.
// If the method name is prefixed with "this", it's a constructor.
instruction = CODE_METHOD_CTOR;
}
@@ -1625,6 +1654,51 @@ void definition(Compiler* compiler)
compiler->fn->bytecode[numFieldsInstruction] = fields.count;
compiler->fields = previousFields;
// Store it in its name.
defineVariable(compiler, symbol);
return;
}
if (match(compiler, TOKEN_IF))
{
// Compile the condition.
consume(compiler, TOKEN_LEFT_PAREN, "Expect '(' after 'if'.");
expression(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);
// Compile the then branch.
block(compiler);
// Jump over the else branch when the if branch is taken.
emit(compiler, CODE_JUMP);
int elseJump = emit(compiler, 255);
patchJump(compiler, ifJump);
// Compile the else branch if there is one.
if (match(compiler, TOKEN_ELSE))
{
block(compiler);
}
// Patch the jump over the else.
patchJump(compiler, elseJump);
return;
}
if (match(compiler, TOKEN_RETURN))
{
// Compile the return value.
// TODO(bob): Implicitly return null if there is a newline or } after the
// "return".
expression(compiler);
emit(compiler, CODE_RETURN);
return;
}
@@ -1637,13 +1711,38 @@ void definition(Compiler* compiler)
consume(compiler, TOKEN_EQ, "Expect '=' after variable name.");
// Compile the initializer.
statement(compiler);
expression(compiler);
defineVariable(compiler, symbol);
return;
}
statement(compiler);
if (match(compiler, TOKEN_WHILE))
{
// 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.
block(compiler);
// Loop back to the top.
emit(compiler, CODE_LOOP);
int loopOffset = compiler->numCodes - loopStart;
emit(compiler, loopOffset);
patchJump(compiler, exitJump);
return;
}
block(compiler);
}
// Parses [source] to a "function" (a chunk of top-level code) for execution by
@@ -1680,7 +1779,7 @@ ObjFn* wrenCompile(WrenVM* vm, const char* source)
for (;;)
{
definition(&compiler);
statement(&compiler);
// If there is no newline, it must be the end of the block on the same line.
if (!match(&compiler, TOKEN_LINE))
@@ -1690,11 +1789,11 @@ ObjFn* wrenCompile(WrenVM* vm, const char* source)
}
if (match(&compiler, TOKEN_EOF)) break;
// Discard the result of the previous expression.
emit(&compiler, CODE_POP);
}
emit(&compiler, CODE_NULL);
emit(&compiler, CODE_RETURN);
endCompiler(&compiler, -1);
unpinObj(vm);