2015-03-03 09:06:34 +01:00
|
|
|
#include <errno.h>
|
2013-12-15 08:41:23 +01:00
|
|
|
#include <stdbool.h>
|
2013-10-22 22:25:39 +02:00
|
|
|
#include <string.h>
|
|
|
|
|
|
2013-11-28 17:11:50 +01:00
|
|
|
#include "wren_common.h"
|
|
|
|
|
#include "wren_compiler.h"
|
2013-12-27 01:58:03 +01:00
|
|
|
#include "wren_vm.h"
|
2013-10-22 21:16:39 +02:00
|
|
|
|
2014-04-07 16:45:09 +02:00
|
|
|
#if WREN_DEBUG_DUMP_COMPILED_CODE
|
2014-01-06 17:01:04 +01:00
|
|
|
#include "wren_debug.h"
|
|
|
|
|
#endif
|
|
|
|
|
|
2013-11-28 17:11:50 +01:00
|
|
|
// This is written in bottom-up order, so the tokenization comes first, then
|
|
|
|
|
// parsing/code generation. This minimizes the number of explicit forward
|
|
|
|
|
// declarations needed.
|
|
|
|
|
|
2015-01-26 16:45:21 +01:00
|
|
|
// The maximum number of local (i.e. not module level) variables that can be
|
|
|
|
|
// declared in a single function, method, or chunk of top level code. This is
|
|
|
|
|
// the maximum number of variables in scope at one time, and spans block scopes.
|
2013-12-01 03:51:27 +01:00
|
|
|
//
|
|
|
|
|
// Note that this limitation is also explicit in the bytecode. Since
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
// `CODE_LOAD_LOCAL` and `CODE_STORE_LOCAL` use a single argument byte to
|
2013-12-01 03:51:27 +01:00
|
|
|
// identify the local, only 256 can be in scope at one time.
|
2015-11-11 16:55:48 +01:00
|
|
|
#define MAX_LOCALS 256
|
2013-12-01 03:51:27 +01:00
|
|
|
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
// The maximum number of upvalues (i.e. variables from enclosing functions)
|
|
|
|
|
// that a function can close over.
|
2015-11-11 16:55:48 +01:00
|
|
|
#define MAX_UPVALUES 256
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
|
2013-12-24 00:08:47 +01:00
|
|
|
// The maximum number of distinct constants that a function can contain. This
|
|
|
|
|
// value is explicit in the bytecode since `CODE_CONSTANT` only takes a single
|
2014-01-04 20:08:31 +01:00
|
|
|
// two-byte argument.
|
|
|
|
|
#define MAX_CONSTANTS (1 << 16)
|
2013-12-24 00:08:47 +01:00
|
|
|
|
2016-02-19 15:57:38 +01:00
|
|
|
// The maximum distance a CODE_JUMP or CODE_JUMP_IF instruction can move the
|
|
|
|
|
// instruction pointer.
|
|
|
|
|
#define MAX_JUMP (1 << 16)
|
|
|
|
|
|
2015-11-11 16:55:48 +01:00
|
|
|
// The maximum depth that interpolation can nest. For example, this string has
|
|
|
|
|
// three levels:
|
|
|
|
|
//
|
|
|
|
|
// "outside %(one + "%(two + "%(three)")")"
|
|
|
|
|
#define MAX_INTERPOLATION_NESTING 8
|
|
|
|
|
|
2016-03-12 06:30:54 +01:00
|
|
|
// The buffer size used to format a compile error message, excluding the header
|
|
|
|
|
// with the module name and error location. Using a hardcoded buffer for this
|
|
|
|
|
// is kind of hairy, but fortunately we can control what the longest possible
|
|
|
|
|
// message is and handle that. Ideally, we'd use `snprintf()`, but that's not
|
|
|
|
|
// available in standard C++98.
|
|
|
|
|
#define ERROR_MESSAGE_SIZE (60 + MAX_VARIABLE_NAME + 15)
|
|
|
|
|
|
2013-10-22 22:25:39 +02:00
|
|
|
typedef enum
|
|
|
|
|
{
|
|
|
|
|
TOKEN_LEFT_PAREN,
|
|
|
|
|
TOKEN_RIGHT_PAREN,
|
|
|
|
|
TOKEN_LEFT_BRACKET,
|
|
|
|
|
TOKEN_RIGHT_BRACKET,
|
|
|
|
|
TOKEN_LEFT_BRACE,
|
|
|
|
|
TOKEN_RIGHT_BRACE,
|
|
|
|
|
TOKEN_COLON,
|
|
|
|
|
TOKEN_DOT,
|
2013-12-26 00:01:45 +01:00
|
|
|
TOKEN_DOTDOT,
|
|
|
|
|
TOKEN_DOTDOTDOT,
|
2013-10-22 22:25:39 +02:00
|
|
|
TOKEN_COMMA,
|
|
|
|
|
TOKEN_STAR,
|
|
|
|
|
TOKEN_SLASH,
|
|
|
|
|
TOKEN_PERCENT,
|
|
|
|
|
TOKEN_PLUS,
|
|
|
|
|
TOKEN_MINUS,
|
2015-02-16 22:26:54 +01:00
|
|
|
TOKEN_LTLT,
|
|
|
|
|
TOKEN_GTGT,
|
2013-10-22 22:25:39 +02:00
|
|
|
TOKEN_PIPE,
|
2013-11-20 03:24:58 +01:00
|
|
|
TOKEN_PIPEPIPE,
|
2015-02-16 11:28:34 +01:00
|
|
|
TOKEN_CARET,
|
2013-10-22 22:25:39 +02:00
|
|
|
TOKEN_AMP,
|
2013-11-19 16:35:25 +01:00
|
|
|
TOKEN_AMPAMP,
|
2013-10-22 22:25:39 +02:00
|
|
|
TOKEN_BANG,
|
2013-12-05 07:09:31 +01:00
|
|
|
TOKEN_TILDE,
|
2014-02-15 20:36:01 +01:00
|
|
|
TOKEN_QUESTION,
|
2013-10-22 22:25:39 +02:00
|
|
|
TOKEN_EQ,
|
|
|
|
|
TOKEN_LT,
|
|
|
|
|
TOKEN_GT,
|
|
|
|
|
TOKEN_LTEQ,
|
|
|
|
|
TOKEN_GTEQ,
|
|
|
|
|
TOKEN_EQEQ,
|
|
|
|
|
TOKEN_BANGEQ,
|
|
|
|
|
|
2013-12-24 19:15:50 +01:00
|
|
|
TOKEN_BREAK,
|
feat: add class parsing and object type system with ObjBlock, ObjClass, and ObjNum structs
Introduce token types TOKEN_CLASS and TOKEN_META for class declaration parsing in the compiler. Refactor the internal object model from a single Obj union to distinct ObjNum, ObjBlock, and ObjClass structs, replacing the old Block type with ObjBlock throughout the compiler, VM, and main entry point. Implement makeClass() and makeBlock() factory functions, update makeNum() to return ObjNum*, and add CODE_CLASS and CODE_DUP bytecodes to support class definition at compile time. Modify the symbol table and method dispatch to use a Method union (primitive or block) stored in ObjClass, and update the VM's callBlock signature and newVM initialization accordingly.
2013-10-24 07:50:04 +02:00
|
|
|
TOKEN_CLASS,
|
2015-07-21 16:24:53 +02:00
|
|
|
TOKEN_CONSTRUCT,
|
2013-11-06 00:40:21 +01:00
|
|
|
TOKEN_ELSE,
|
2013-11-04 06:38:58 +01:00
|
|
|
TOKEN_FALSE,
|
2013-12-24 19:15:50 +01:00
|
|
|
TOKEN_FOR,
|
2015-03-24 15:58:15 +01:00
|
|
|
TOKEN_FOREIGN,
|
2013-11-06 00:40:21 +01:00
|
|
|
TOKEN_IF,
|
2015-02-15 01:46:00 +01:00
|
|
|
TOKEN_IMPORT,
|
2013-12-25 06:04:11 +01:00
|
|
|
TOKEN_IN,
|
feat: add `is` operator for type checking and refactor primitives registration
Add TOKEN_IS token and CODE_IS bytecode to support runtime type checking via the `is` keyword. Refactor primitive registration by replacing `registerPrimitives` with `loadCore`, which compiles a core library string to define built-in classes (Bool, Class, Function, Num, Null, String, IO) and then attaches primitive methods to them. Update Primitive typedef to remove unused numArgs parameter, and add findGlobal helper to retrieve globals by name. Include test files for class syntax, class instantiation, and the new `is` operator.
2013-11-08 02:07:32 +01:00
|
|
|
TOKEN_IS,
|
2013-11-06 03:22:22 +01:00
|
|
|
TOKEN_NULL,
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
TOKEN_RETURN,
|
2013-11-10 20:46:13 +01:00
|
|
|
TOKEN_STATIC,
|
2013-12-11 17:02:17 +01:00
|
|
|
TOKEN_SUPER,
|
2013-11-10 00:42:23 +01:00
|
|
|
TOKEN_THIS,
|
2013-11-04 06:38:58 +01:00
|
|
|
TOKEN_TRUE,
|
2013-10-22 22:25:39 +02:00
|
|
|
TOKEN_VAR,
|
2013-11-18 07:38:59 +01:00
|
|
|
TOKEN_WHILE,
|
2013-10-22 22:25:39 +02:00
|
|
|
|
2013-11-23 23:55:05 +01:00
|
|
|
TOKEN_FIELD,
|
2014-01-18 18:33:18 +01:00
|
|
|
TOKEN_STATIC_FIELD,
|
2013-10-22 22:25:39 +02:00
|
|
|
TOKEN_NAME,
|
|
|
|
|
TOKEN_NUMBER,
|
2015-11-11 16:55:48 +01:00
|
|
|
|
|
|
|
|
// A string literal without any interpolation, or the last section of a
|
|
|
|
|
// string following the last interpolated expression.
|
2013-10-22 22:25:39 +02:00
|
|
|
TOKEN_STRING,
|
2015-11-11 16:55:48 +01:00
|
|
|
|
|
|
|
|
// A portion of a string literal preceding an interpolated expression. This
|
|
|
|
|
// string:
|
|
|
|
|
//
|
|
|
|
|
// "a %(b) c %(d) e"
|
|
|
|
|
//
|
|
|
|
|
// is tokenized to:
|
|
|
|
|
//
|
|
|
|
|
// TOKEN_INTERPOLATION "a "
|
|
|
|
|
// TOKEN_NAME b
|
|
|
|
|
// TOKEN_INTERPOLATION " c "
|
|
|
|
|
// TOKEN_NAME d
|
|
|
|
|
// TOKEN_STRING " e"
|
|
|
|
|
TOKEN_INTERPOLATION,
|
2013-10-22 22:25:39 +02:00
|
|
|
|
|
|
|
|
TOKEN_LINE,
|
|
|
|
|
|
|
|
|
|
TOKEN_ERROR,
|
2013-10-24 21:39:01 +02:00
|
|
|
TOKEN_EOF
|
2013-10-22 22:25:39 +02:00
|
|
|
} TokenType;
|
|
|
|
|
|
2014-02-15 07:15:51 +01:00
|
|
|
typedef struct
|
2013-10-22 22:25:39 +02:00
|
|
|
{
|
|
|
|
|
TokenType type;
|
2013-10-28 15:12:39 +01:00
|
|
|
|
2013-12-02 00:22:24 +01:00
|
|
|
// The beginning of the token, pointing directly into the source.
|
|
|
|
|
const char* start;
|
2013-10-28 15:12:39 +01:00
|
|
|
|
2013-12-02 00:22:24 +01:00
|
|
|
// The length of the token in characters.
|
|
|
|
|
int length;
|
2013-10-28 15:12:39 +01:00
|
|
|
|
|
|
|
|
// The 1-based line where the token appears.
|
|
|
|
|
int line;
|
2015-11-11 16:55:48 +01:00
|
|
|
|
|
|
|
|
// The parsed value if the token is a literal.
|
|
|
|
|
Value value;
|
2013-10-22 22:25:39 +02:00
|
|
|
} Token;
|
|
|
|
|
|
2013-10-22 21:16:39 +02:00
|
|
|
typedef struct
|
|
|
|
|
{
|
2013-11-25 16:47:02 +01:00
|
|
|
WrenVM* vm;
|
2013-10-23 22:51:41 +02:00
|
|
|
|
2015-01-27 16:11:45 +01:00
|
|
|
// The module being parsed.
|
|
|
|
|
ObjModule* module;
|
2015-01-26 16:45:21 +01:00
|
|
|
|
2014-01-02 16:31:31 +01:00
|
|
|
// The source code being parsed.
|
2013-10-22 22:25:39 +02:00
|
|
|
const char* source;
|
|
|
|
|
|
2013-12-02 00:22:24 +01:00
|
|
|
// The beginning of the currently-being-lexed token in [source].
|
|
|
|
|
const char* tokenStart;
|
2013-10-22 22:25:39 +02:00
|
|
|
|
2013-12-02 00:22:24 +01:00
|
|
|
// The current character being lexed in [source].
|
|
|
|
|
const char* currentChar;
|
2013-10-22 22:25:39 +02:00
|
|
|
|
2013-10-28 15:12:39 +01:00
|
|
|
// The 1-based line number of [currentChar].
|
|
|
|
|
int currentLine;
|
|
|
|
|
|
2013-10-22 22:25:39 +02:00
|
|
|
// The most recently lexed token.
|
|
|
|
|
Token current;
|
|
|
|
|
|
|
|
|
|
// The most recently consumed/advanced token.
|
|
|
|
|
Token previous;
|
2015-11-11 16:55:48 +01:00
|
|
|
|
|
|
|
|
// Tracks the lexing state when tokenizing interpolated strings.
|
|
|
|
|
//
|
|
|
|
|
// Interpolated strings make the lexer not strictly regular: we don't know
|
|
|
|
|
// whether a ")" should be treated as a RIGHT_PAREN token or as ending an
|
|
|
|
|
// interpolated expression unless we know whether we are inside a string
|
|
|
|
|
// interpolation and how many unmatched "(" there are. This is particularly
|
|
|
|
|
// complex because interpolation can nest:
|
|
|
|
|
//
|
|
|
|
|
// " %( " %( inner ) " ) "
|
|
|
|
|
//
|
|
|
|
|
// This tracks that state. The parser maintains a stack of ints, one for each
|
|
|
|
|
// level of current interpolation nesting. Each value is the number of
|
|
|
|
|
// unmatched "(" that are waiting to be closed.
|
|
|
|
|
int parens[MAX_INTERPOLATION_NESTING];
|
|
|
|
|
int numParens;
|
|
|
|
|
|
2013-12-15 08:41:23 +01:00
|
|
|
// If subsequent newline tokens should be discarded.
|
|
|
|
|
bool skipNewlines;
|
2013-10-24 21:39:01 +02:00
|
|
|
|
2015-04-04 06:22:58 +02:00
|
|
|
// Whether compile errors should be printed to stderr or discarded.
|
|
|
|
|
bool printErrors;
|
|
|
|
|
|
2013-12-15 08:41:23 +01:00
|
|
|
// If a syntax or compile error has occurred.
|
|
|
|
|
bool hasError;
|
2013-10-24 21:39:01 +02:00
|
|
|
} Parser;
|
|
|
|
|
|
2013-12-01 03:28:01 +01:00
|
|
|
typedef struct
|
2013-11-18 18:19:03 +01:00
|
|
|
{
|
2013-12-01 03:28:01 +01:00
|
|
|
// The name of the local variable. This points directly into the original
|
|
|
|
|
// source code string.
|
|
|
|
|
const char* name;
|
|
|
|
|
|
|
|
|
|
// The length of the local variable's name.
|
2013-12-02 00:22:24 +01:00
|
|
|
int length;
|
2013-11-18 18:19:03 +01:00
|
|
|
|
2013-12-01 03:28:01 +01:00
|
|
|
// The depth in the scope chain that this variable was declared at. Zero is
|
|
|
|
|
// 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;
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
|
2013-12-15 08:41:23 +01:00
|
|
|
// If this local variable is being used as an upvalue.
|
|
|
|
|
bool isUpvalue;
|
2013-12-01 03:28:01 +01:00
|
|
|
} Local;
|
2013-11-18 18:19:03 +01:00
|
|
|
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
typedef struct
|
|
|
|
|
{
|
2013-12-15 08:41:23 +01:00
|
|
|
// True if this upvalue is capturing a local variable from the enclosing
|
|
|
|
|
// function. False if it's capturing an upvalue.
|
|
|
|
|
bool isLocal;
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
|
|
|
|
|
// The index of the local or upvalue being captured in the enclosing function.
|
|
|
|
|
int index;
|
|
|
|
|
} CompilerUpvalue;
|
|
|
|
|
|
2015-01-15 08:08:25 +01:00
|
|
|
// Bookkeeping information for the current loop being compiled.
|
2014-01-06 17:01:04 +01:00
|
|
|
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;
|
|
|
|
|
|
2015-07-10 18:18:22 +02:00
|
|
|
// The different signature syntaxes for different kinds of methods.
|
|
|
|
|
typedef enum
|
|
|
|
|
{
|
|
|
|
|
// A name followed by a (possibly empty) parenthesized parameter list. Also
|
|
|
|
|
// used for binary operators.
|
|
|
|
|
SIG_METHOD,
|
|
|
|
|
|
|
|
|
|
// Just a name. Also used for unary operators.
|
|
|
|
|
SIG_GETTER,
|
|
|
|
|
|
|
|
|
|
// A name followed by "=".
|
|
|
|
|
SIG_SETTER,
|
|
|
|
|
|
|
|
|
|
// A square bracketed parameter list.
|
|
|
|
|
SIG_SUBSCRIPT,
|
|
|
|
|
|
|
|
|
|
// A square bracketed parameter list followed by "=".
|
|
|
|
|
SIG_SUBSCRIPT_SETTER,
|
|
|
|
|
|
|
|
|
|
// A constructor initializer function. This has a distinct signature to
|
|
|
|
|
// prevent it from being invoked directly outside of the constructor on the
|
|
|
|
|
// metaclass.
|
|
|
|
|
SIG_INITIALIZER
|
|
|
|
|
} SignatureType;
|
|
|
|
|
|
|
|
|
|
typedef struct
|
|
|
|
|
{
|
|
|
|
|
const char* name;
|
|
|
|
|
int length;
|
|
|
|
|
SignatureType type;
|
|
|
|
|
int arity;
|
|
|
|
|
} Signature;
|
|
|
|
|
|
2014-01-18 23:03:52 +01:00
|
|
|
// Bookkeeping information for compiling a class definition.
|
|
|
|
|
typedef struct
|
|
|
|
|
{
|
2016-03-15 15:15:55 +01:00
|
|
|
// The name of the class.
|
2016-02-24 07:07:08 +01:00
|
|
|
ObjString* name;
|
|
|
|
|
|
2014-01-18 23:03:52 +01:00
|
|
|
// Symbol table for the fields of the class.
|
2015-12-15 02:10:10 +01:00
|
|
|
SymbolTable fields;
|
2016-03-15 15:15:55 +01:00
|
|
|
|
|
|
|
|
// Symbols for the methods defined by the class. Used to detect duplicate
|
|
|
|
|
// method definitions.
|
|
|
|
|
IntBuffer methods;
|
2016-02-28 21:53:34 +01:00
|
|
|
IntBuffer staticMethods;
|
2014-01-18 23:03:52 +01:00
|
|
|
|
2015-08-15 21:07:53 +02:00
|
|
|
// True if the class being compiled is a foreign class.
|
|
|
|
|
bool isForeign;
|
|
|
|
|
|
2014-01-18 23:03:52 +01:00
|
|
|
// True if the current method being compiled is static.
|
2015-07-10 18:18:22 +02:00
|
|
|
bool inStatic;
|
2014-01-18 23:03:52 +01:00
|
|
|
|
2015-07-10 18:18:22 +02:00
|
|
|
// The signature of the method being compiled.
|
|
|
|
|
Signature* signature;
|
2014-01-18 23:03:52 +01:00
|
|
|
} ClassCompiler;
|
|
|
|
|
|
2013-12-27 01:58:03 +01:00
|
|
|
struct sCompiler
|
2013-10-24 21:39:01 +02:00
|
|
|
{
|
|
|
|
|
Parser* parser;
|
|
|
|
|
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
// The compiler for the function enclosing this one, or NULL if it's the
|
2013-10-24 21:39:01 +02:00
|
|
|
// top level.
|
|
|
|
|
struct sCompiler* parent;
|
|
|
|
|
|
2014-01-02 16:31:31 +01:00
|
|
|
// The currently in scope local variables.
|
|
|
|
|
Local locals[MAX_LOCALS];
|
2013-11-23 23:55:05 +01:00
|
|
|
|
2013-12-01 03:28:01 +01:00
|
|
|
// The number of local variables currently in scope.
|
|
|
|
|
int numLocals;
|
|
|
|
|
|
2014-01-02 16:31:31 +01:00
|
|
|
// The upvalues that this function has captured from outer scopes. The count
|
|
|
|
|
// of them is stored in [numUpvalues].
|
|
|
|
|
CompilerUpvalue upvalues[MAX_UPVALUES];
|
|
|
|
|
|
2013-12-01 03:28:01 +01:00
|
|
|
// The current level of block scope nesting, where zero is no nesting. A -1
|
|
|
|
|
// here means top-level code is being compiled and there is no block scope
|
2015-01-26 16:45:21 +01:00
|
|
|
// in effect at all. Any variables declared will be module-level.
|
2013-12-01 03:28:01 +01:00
|
|
|
int scopeDepth;
|
2015-12-14 07:57:40 +01:00
|
|
|
|
|
|
|
|
// The current number of slots (locals and temporaries) in use.
|
2015-12-14 17:00:22 +01:00
|
|
|
//
|
|
|
|
|
// We use this and maxSlots to track the maximum number of additional slots
|
|
|
|
|
// a function may need while executing. When the function is called, the
|
|
|
|
|
// fiber will check to ensure its stack has enough room to cover that worst
|
|
|
|
|
// case and grow the stack if needed.
|
|
|
|
|
//
|
|
|
|
|
// This value here doesn't include parameters to the function. Since those
|
|
|
|
|
// are already pushed onto the stack by the caller and tracked there, we
|
|
|
|
|
// don't need to double count them here.
|
2015-12-14 07:57:40 +01:00
|
|
|
int numSlots;
|
2013-12-01 03:28:01 +01:00
|
|
|
|
2014-01-06 17:01:04 +01:00
|
|
|
// The current innermost loop being compiled, or NULL if not in a loop.
|
|
|
|
|
Loop* loop;
|
2013-12-24 19:15:50 +01:00
|
|
|
|
2014-01-18 23:03:52 +01:00
|
|
|
// If this is a compiler for a method, keeps track of the class enclosing it.
|
|
|
|
|
ClassCompiler* enclosingClass;
|
2013-12-19 16:02:27 +01:00
|
|
|
|
2016-03-04 16:49:34 +01:00
|
|
|
// The function being compiled.
|
|
|
|
|
ObjFn* fn;
|
2013-12-27 01:58:03 +01:00
|
|
|
};
|
2013-10-22 21:16:39 +02:00
|
|
|
|
2016-02-12 16:16:41 +01:00
|
|
|
// Describes where a variable is declared.
|
|
|
|
|
typedef enum
|
|
|
|
|
{
|
|
|
|
|
// A local variable in the current function.
|
|
|
|
|
SCOPE_LOCAL,
|
|
|
|
|
|
|
|
|
|
// A local variable declared in an enclosing function.
|
|
|
|
|
SCOPE_UPVALUE,
|
|
|
|
|
|
|
|
|
|
// A top-level module variable.
|
|
|
|
|
SCOPE_MODULE
|
|
|
|
|
} Scope;
|
|
|
|
|
|
|
|
|
|
// A reference to a variable and the scope where it is defined. This contains
|
|
|
|
|
// enough information to emit correct code to load or store the variable.
|
|
|
|
|
typedef struct
|
|
|
|
|
{
|
|
|
|
|
// The stack slot, upvalue slot, or module symbol defining the variable.
|
|
|
|
|
int index;
|
|
|
|
|
|
|
|
|
|
// Where the variable is declared.
|
|
|
|
|
Scope scope;
|
|
|
|
|
} Variable;
|
|
|
|
|
|
2015-12-14 07:57:40 +01:00
|
|
|
// The stack effect of each opcode. The index in the array is the opcode, and
|
|
|
|
|
// the value is the stack effect of that instruction.
|
|
|
|
|
static const int stackEffects[] = {
|
|
|
|
|
#define OPCODE(_, effect) effect,
|
|
|
|
|
#include "wren_opcodes.h"
|
|
|
|
|
#undef OPCODE
|
|
|
|
|
};
|
|
|
|
|
|
2016-03-12 06:30:54 +01:00
|
|
|
static void printError(Parser* parser, int line, const char* label,
|
|
|
|
|
const char* format, va_list args)
|
|
|
|
|
{
|
|
|
|
|
parser->hasError = true;
|
|
|
|
|
if (!parser->printErrors) return;
|
|
|
|
|
|
2016-03-16 16:04:03 +01:00
|
|
|
// Only report errors if there is a WrenErrorFn to handle them.
|
|
|
|
|
if (parser->vm->config.errorFn == NULL) return;
|
|
|
|
|
|
2016-03-12 06:30:54 +01:00
|
|
|
// Format the label and message.
|
|
|
|
|
char message[ERROR_MESSAGE_SIZE];
|
|
|
|
|
int length = sprintf(message, "%s: ", label);
|
|
|
|
|
length += vsprintf(message + length, format, args);
|
|
|
|
|
ASSERT(length < ERROR_MESSAGE_SIZE, "Error should not exceed buffer.");
|
|
|
|
|
|
2016-03-16 16:04:03 +01:00
|
|
|
parser->vm->config.errorFn(parser->module->name->value, line, message);
|
2016-03-12 06:30:54 +01:00
|
|
|
}
|
|
|
|
|
|
2013-12-24 00:08:47 +01:00
|
|
|
// Outputs a compile or syntax error. This also marks the compilation as having
|
|
|
|
|
// an error, which ensures that the resulting code will be discarded and never
|
|
|
|
|
// run. This means that after calling lexError(), it's fine to generate whatever
|
|
|
|
|
// invalid bytecode you want since it won't be used.
|
|
|
|
|
static void lexError(Parser* parser, const char* format, ...)
|
|
|
|
|
{
|
|
|
|
|
va_list args;
|
|
|
|
|
va_start(args, format);
|
2016-03-12 06:30:54 +01:00
|
|
|
printError(parser, parser->currentLine, "Error", format, args);
|
2013-12-24 00:08:47 +01:00
|
|
|
va_end(args);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Outputs a compile or syntax error. This also marks the compilation as having
|
|
|
|
|
// an error, which ensures that the resulting code will be discarded and never
|
|
|
|
|
// run. This means that after calling error(), it's fine to generate whatever
|
|
|
|
|
// invalid bytecode you want since it won't be used.
|
|
|
|
|
//
|
|
|
|
|
// You'll note that most places that call error() continue to parse and compile
|
|
|
|
|
// after that. That's so that we can try to find as many compilation errors in
|
|
|
|
|
// one pass as possible instead of just bailing at the first one.
|
|
|
|
|
static void error(Compiler* compiler, const char* format, ...)
|
|
|
|
|
{
|
|
|
|
|
Token* token = &compiler->parser->previous;
|
2015-01-04 22:10:46 +01:00
|
|
|
|
|
|
|
|
// If the parse error was caused by an error token, the lexer has already
|
|
|
|
|
// reported it.
|
|
|
|
|
if (token->type == TOKEN_ERROR) return;
|
2016-03-12 06:30:54 +01:00
|
|
|
|
|
|
|
|
va_list args;
|
|
|
|
|
va_start(args, format);
|
2013-12-24 00:08:47 +01:00
|
|
|
if (token->type == TOKEN_LINE)
|
|
|
|
|
{
|
2016-03-12 06:30:54 +01:00
|
|
|
printError(compiler->parser, token->line, "Error at newline", format, args);
|
2013-12-24 00:08:47 +01:00
|
|
|
}
|
2015-01-15 08:08:25 +01:00
|
|
|
else if (token->type == TOKEN_EOF)
|
|
|
|
|
{
|
2016-03-12 06:30:54 +01:00
|
|
|
printError(compiler->parser, token->line, "Error at end of file", format, args);
|
2015-01-15 08:08:25 +01:00
|
|
|
}
|
2013-12-24 00:08:47 +01:00
|
|
|
else
|
|
|
|
|
{
|
2016-03-12 06:30:54 +01:00
|
|
|
// Make sure we don't exceed the buffer with a very long token.
|
|
|
|
|
char label[ERROR_MESSAGE_SIZE];
|
|
|
|
|
if (token->length <= MAX_VARIABLE_NAME)
|
|
|
|
|
{
|
|
|
|
|
sprintf(label, "Error at '%.*s'", token->length, token->start);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
sprintf(label, "Error at '%.*s...'", MAX_VARIABLE_NAME, token->start);
|
|
|
|
|
}
|
|
|
|
|
printError(compiler->parser, token->line, label, format, args);
|
2013-12-24 00:08:47 +01:00
|
|
|
}
|
|
|
|
|
va_end(args);
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-20 05:54:47 +01:00
|
|
|
// Adds [constant] to the constant pool and returns its index.
|
|
|
|
|
static int addConstant(Compiler* compiler, Value constant)
|
|
|
|
|
{
|
2015-08-22 07:54:30 +02:00
|
|
|
if (compiler->parser->hasError) return -1;
|
|
|
|
|
|
2016-03-04 16:49:34 +01:00
|
|
|
if (compiler->fn->constants.count < MAX_CONSTANTS)
|
2013-12-24 00:08:47 +01:00
|
|
|
{
|
2015-03-22 20:22:47 +01:00
|
|
|
if (IS_OBJ(constant)) wrenPushRoot(compiler->parser->vm, AS_OBJ(constant));
|
2016-03-04 16:49:34 +01:00
|
|
|
wrenValueBufferWrite(compiler->parser->vm, &compiler->fn->constants,
|
|
|
|
|
constant);
|
2015-03-22 20:22:47 +01:00
|
|
|
if (IS_OBJ(constant)) wrenPopRoot(compiler->parser->vm);
|
2013-12-24 00:08:47 +01:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
error(compiler, "A function may only contain %d unique constants.",
|
|
|
|
|
MAX_CONSTANTS);
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-04 16:49:34 +01:00
|
|
|
return compiler->fn->constants.count - 1;
|
2013-11-20 05:54:47 +01:00
|
|
|
}
|
|
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
// Initializes [compiler].
|
2013-12-27 01:58:03 +01:00
|
|
|
static void initCompiler(Compiler* compiler, Parser* parser, Compiler* parent,
|
2014-01-18 23:03:52 +01:00
|
|
|
bool isFunction)
|
2013-10-28 05:59:14 +01:00
|
|
|
{
|
|
|
|
|
compiler->parser = parser;
|
|
|
|
|
compiler->parent = parent;
|
2014-01-06 17:01:04 +01:00
|
|
|
compiler->loop = NULL;
|
2014-01-18 23:03:52 +01:00
|
|
|
compiler->enclosingClass = NULL;
|
2016-03-04 16:49:34 +01:00
|
|
|
|
|
|
|
|
// Initialize this to NULL before allocating in case a GC gets triggered in
|
|
|
|
|
// the middle of initializing the compiler.
|
|
|
|
|
compiler->fn = NULL;
|
2013-11-10 00:42:23 +01:00
|
|
|
|
2016-03-04 02:48:47 +01:00
|
|
|
parser->vm->compiler = compiler;
|
2013-12-27 01:58:03 +01:00
|
|
|
|
2013-12-01 03:28:01 +01:00
|
|
|
if (parent == NULL)
|
|
|
|
|
{
|
2014-01-02 16:31:31 +01:00
|
|
|
compiler->numLocals = 0;
|
|
|
|
|
|
2015-01-26 16:45:21 +01:00
|
|
|
// Compiling top-level code, so the initial scope is module-level.
|
2013-12-01 03:28:01 +01:00
|
|
|
compiler->scopeDepth = -1;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2013-12-22 00:55:08 +01:00
|
|
|
// Declare a fake local variable for the receiver so that it's slot in the
|
|
|
|
|
// stack is taken. For methods, we call this "this", so that we can resolve
|
|
|
|
|
// references to that like a normal variable. For functions, they have no
|
|
|
|
|
// explicit "this". So we pick a bogus name. That way references to "this"
|
|
|
|
|
// inside a function will try to walk up the parent chain to find a method
|
|
|
|
|
// enclosing the function whose "this" we can close over.
|
2013-12-01 03:28:01 +01:00
|
|
|
compiler->numLocals = 1;
|
2014-01-18 23:03:52 +01:00
|
|
|
if (isFunction)
|
2013-12-22 00:55:08 +01:00
|
|
|
{
|
2014-01-18 23:03:52 +01:00
|
|
|
compiler->locals[0].name = NULL;
|
|
|
|
|
compiler->locals[0].length = 0;
|
2013-12-22 00:55:08 +01:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2014-01-18 23:03:52 +01:00
|
|
|
compiler->locals[0].name = "this";
|
|
|
|
|
compiler->locals[0].length = 4;
|
2013-12-22 00:55:08 +01:00
|
|
|
}
|
2013-12-01 03:28:01 +01:00
|
|
|
compiler->locals[0].depth = -1;
|
2013-12-15 08:41:23 +01:00
|
|
|
compiler->locals[0].isUpvalue = false;
|
2013-12-01 03:28:01 +01:00
|
|
|
|
|
|
|
|
// The initial scope for function or method is a local scope.
|
|
|
|
|
compiler->scopeDepth = 0;
|
2014-01-16 16:09:43 +01:00
|
|
|
}
|
2015-12-14 07:57:40 +01:00
|
|
|
|
|
|
|
|
compiler->numSlots = compiler->numLocals;
|
2014-01-02 16:31:31 +01:00
|
|
|
|
2016-03-04 16:49:34 +01:00
|
|
|
compiler->fn = wrenNewFunction(parser->vm, parser->module,
|
|
|
|
|
compiler->numLocals);
|
2013-10-28 05:59:14 +01:00
|
|
|
}
|
|
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
// Lexing ----------------------------------------------------------------------
|
2013-10-22 21:16:39 +02:00
|
|
|
|
2015-09-18 05:51:24 +02:00
|
|
|
typedef struct
|
|
|
|
|
{
|
|
|
|
|
const char* identifier;
|
|
|
|
|
size_t length;
|
|
|
|
|
TokenType tokenType;
|
|
|
|
|
} Keyword;
|
|
|
|
|
|
|
|
|
|
// The table of reserved words and their associated token types.
|
|
|
|
|
static Keyword keywords[] =
|
|
|
|
|
{
|
|
|
|
|
{"break", 5, TOKEN_BREAK},
|
|
|
|
|
{"class", 5, TOKEN_CLASS},
|
|
|
|
|
{"construct", 9, TOKEN_CONSTRUCT},
|
|
|
|
|
{"else", 4, TOKEN_ELSE},
|
|
|
|
|
{"false", 5, TOKEN_FALSE},
|
|
|
|
|
{"for", 3, TOKEN_FOR},
|
|
|
|
|
{"foreign", 7, TOKEN_FOREIGN},
|
|
|
|
|
{"if", 2, TOKEN_IF},
|
|
|
|
|
{"import", 6, TOKEN_IMPORT},
|
|
|
|
|
{"in", 2, TOKEN_IN},
|
|
|
|
|
{"is", 2, TOKEN_IS},
|
|
|
|
|
{"null", 4, TOKEN_NULL},
|
|
|
|
|
{"return", 6, TOKEN_RETURN},
|
|
|
|
|
{"static", 6, TOKEN_STATIC},
|
|
|
|
|
{"super", 5, TOKEN_SUPER},
|
|
|
|
|
{"this", 4, TOKEN_THIS},
|
|
|
|
|
{"true", 4, TOKEN_TRUE},
|
|
|
|
|
{"var", 3, TOKEN_VAR},
|
|
|
|
|
{"while", 5, TOKEN_WHILE},
|
|
|
|
|
{NULL, 0, TOKEN_EOF} // Sentinel to mark the end of the array.
|
|
|
|
|
};
|
|
|
|
|
|
2013-12-15 08:41:23 +01:00
|
|
|
// Returns true if [c] is a valid (non-initial) identifier character.
|
|
|
|
|
static bool isName(char c)
|
2013-11-07 16:04:25 +01:00
|
|
|
{
|
|
|
|
|
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_';
|
2013-10-22 21:16:39 +02:00
|
|
|
}
|
|
|
|
|
|
2013-12-15 08:41:23 +01:00
|
|
|
// Returns true if [c] is a digit.
|
|
|
|
|
static bool isDigit(char c)
|
2013-10-25 06:32:17 +02:00
|
|
|
{
|
2013-11-07 16:04:25 +01:00
|
|
|
return c >= '0' && c <= '9';
|
2013-10-25 06:32:17 +02:00
|
|
|
}
|
|
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
// Returns the current character the parser is sitting on.
|
|
|
|
|
static char peekChar(Parser* parser)
|
2013-10-28 05:59:14 +01:00
|
|
|
{
|
2013-12-02 00:22:24 +01:00
|
|
|
return *parser->currentChar;
|
2013-11-07 16:04:25 +01:00
|
|
|
}
|
2013-10-28 05:59:14 +01:00
|
|
|
|
2013-11-10 06:32:57 +01:00
|
|
|
// Returns the character after the current character.
|
|
|
|
|
static char peekNextChar(Parser* parser)
|
|
|
|
|
{
|
|
|
|
|
// If we're at the end of the source, don't read past it.
|
|
|
|
|
if (peekChar(parser) == '\0') return '\0';
|
2013-12-02 00:22:24 +01:00
|
|
|
return *(parser->currentChar + 1);
|
2013-11-10 06:32:57 +01:00
|
|
|
}
|
|
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
// Advances the parser forward one character.
|
|
|
|
|
static char nextChar(Parser* parser)
|
|
|
|
|
{
|
|
|
|
|
char c = peekChar(parser);
|
|
|
|
|
parser->currentChar++;
|
2013-12-23 23:51:06 +01:00
|
|
|
if (c == '\n') parser->currentLine++;
|
2013-11-07 16:04:25 +01:00
|
|
|
return c;
|
|
|
|
|
}
|
2013-10-28 05:59:14 +01:00
|
|
|
|
2015-08-22 08:08:11 +02:00
|
|
|
// If the current character is [c], consumes it and returns `true`.
|
|
|
|
|
static bool matchChar(Parser* parser, char c)
|
|
|
|
|
{
|
|
|
|
|
if (peekChar(parser) != c) return false;
|
|
|
|
|
nextChar(parser);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
// Sets the parser's current token to the given [type] and current character
|
|
|
|
|
// range.
|
|
|
|
|
static void makeToken(Parser* parser, TokenType type)
|
|
|
|
|
{
|
|
|
|
|
parser->current.type = type;
|
|
|
|
|
parser->current.start = parser->tokenStart;
|
2013-12-02 00:22:24 +01:00
|
|
|
parser->current.length = (int)(parser->currentChar - parser->tokenStart);
|
2013-11-07 16:04:25 +01:00
|
|
|
parser->current.line = parser->currentLine;
|
2015-11-11 16:55:48 +01:00
|
|
|
|
2014-02-15 20:36:01 +01:00
|
|
|
// Make line tokens appear on the line containing the "\n".
|
|
|
|
|
if (type == TOKEN_LINE) parser->current.line--;
|
2013-11-07 16:04:25 +01:00
|
|
|
}
|
2013-10-28 05:59:14 +01:00
|
|
|
|
2013-11-20 05:19:02 +01:00
|
|
|
// If the current character is [c], then consumes it and makes a token of type
|
|
|
|
|
// [two]. Otherwise makes a token of type [one].
|
|
|
|
|
static void twoCharToken(Parser* parser, char c, TokenType two, TokenType one)
|
|
|
|
|
{
|
2015-08-22 08:08:11 +02:00
|
|
|
makeToken(parser, matchChar(parser, c) ? two : one);
|
2013-11-20 05:19:02 +01:00
|
|
|
}
|
|
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
// Skips the rest of the current line.
|
|
|
|
|
static void skipLineComment(Parser* parser)
|
|
|
|
|
{
|
|
|
|
|
while (peekChar(parser) != '\n' && peekChar(parser) != '\0')
|
2013-10-28 05:59:14 +01:00
|
|
|
{
|
2013-11-07 16:04:25 +01:00
|
|
|
nextChar(parser);
|
2013-10-28 05:59:14 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-14 02:09:47 +01:00
|
|
|
// Skips the rest of a block comment.
|
|
|
|
|
static void skipBlockComment(Parser* parser)
|
|
|
|
|
{
|
|
|
|
|
int nesting = 1;
|
|
|
|
|
while (nesting > 0)
|
|
|
|
|
{
|
2013-12-23 23:51:06 +01:00
|
|
|
if (peekChar(parser) == '\0')
|
|
|
|
|
{
|
|
|
|
|
lexError(parser, "Unterminated block comment.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
2013-11-14 02:09:47 +01:00
|
|
|
|
2013-11-14 08:06:53 +01:00
|
|
|
if (peekChar(parser) == '/' && peekNextChar(parser) == '*')
|
2013-11-14 02:09:47 +01:00
|
|
|
{
|
|
|
|
|
nextChar(parser);
|
2013-11-14 08:06:53 +01:00
|
|
|
nextChar(parser);
|
|
|
|
|
nesting++;
|
2013-11-14 02:09:47 +01:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-14 08:06:53 +01:00
|
|
|
if (peekChar(parser) == '*' && peekNextChar(parser) == '/')
|
2013-11-14 02:09:47 +01:00
|
|
|
{
|
|
|
|
|
nextChar(parser);
|
2013-11-14 08:06:53 +01:00
|
|
|
nextChar(parser);
|
|
|
|
|
nesting--;
|
2013-11-14 02:09:47 +01:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Regular comment character.
|
|
|
|
|
nextChar(parser);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-25 22:02:44 +01:00
|
|
|
// Reads the next character, which should be a hex digit (0-9, a-f, or A-F) and
|
|
|
|
|
// returns its numeric value. If the character isn't a hex digit, returns -1.
|
|
|
|
|
static int readHexDigit(Parser* parser)
|
|
|
|
|
{
|
|
|
|
|
char c = nextChar(parser);
|
|
|
|
|
if (c >= '0' && c <= '9') return c - '0';
|
|
|
|
|
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
|
|
|
|
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
|
|
|
|
|
|
|
|
|
// Don't consume it if it isn't expected. Keeps us from reading past the end
|
|
|
|
|
// of an unterminated string.
|
|
|
|
|
parser->currentChar--;
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-03 16:17:56 +01:00
|
|
|
// Parses the numeric value of the current token.
|
|
|
|
|
static void makeNumber(Parser* parser, bool isHex)
|
2015-01-25 22:02:44 +01:00
|
|
|
{
|
2015-03-03 16:17:56 +01:00
|
|
|
errno = 0;
|
2015-01-25 22:02:44 +01:00
|
|
|
|
2015-03-03 16:17:56 +01:00
|
|
|
// We don't check that the entire token is consumed because we've already
|
|
|
|
|
// scanned it ourselves and know it's valid.
|
2015-11-11 16:55:48 +01:00
|
|
|
parser->current.value = NUM_VAL(isHex ? strtol(parser->tokenStart, NULL, 16)
|
|
|
|
|
: strtod(parser->tokenStart, NULL));
|
|
|
|
|
|
2015-03-03 09:06:34 +01:00
|
|
|
if (errno == ERANGE)
|
|
|
|
|
{
|
|
|
|
|
lexError(parser, "Number literal was too large.");
|
2015-11-11 16:55:48 +01:00
|
|
|
parser->current.value = NUM_VAL(0);
|
2015-03-03 09:06:34 +01:00
|
|
|
}
|
2015-01-25 22:02:44 +01:00
|
|
|
|
|
|
|
|
makeToken(parser, TOKEN_NUMBER);
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-03 16:17:56 +01:00
|
|
|
// Finishes lexing a hexadecimal number literal.
|
|
|
|
|
static void readHexNumber(Parser* parser)
|
|
|
|
|
{
|
|
|
|
|
// Skip past the `x` used to denote a hexadecimal literal.
|
|
|
|
|
nextChar(parser);
|
|
|
|
|
|
|
|
|
|
// Iterate over all the valid hexadecimal digits found.
|
|
|
|
|
while (readHexDigit(parser) != -1) continue;
|
|
|
|
|
|
|
|
|
|
makeNumber(parser, true);
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
// Finishes lexing a number literal.
|
|
|
|
|
static void readNumber(Parser* parser)
|
2013-10-28 05:59:14 +01:00
|
|
|
{
|
2013-11-07 16:04:25 +01:00
|
|
|
while (isDigit(peekChar(parser))) nextChar(parser);
|
|
|
|
|
|
2013-11-10 06:32:57 +01:00
|
|
|
// See if it has a floating point. Make sure there is a digit after the "."
|
|
|
|
|
// so we don't get confused by method calls on number literals.
|
|
|
|
|
if (peekChar(parser) == '.' && isDigit(peekNextChar(parser)))
|
|
|
|
|
{
|
|
|
|
|
nextChar(parser);
|
|
|
|
|
while (isDigit(peekChar(parser))) nextChar(parser);
|
|
|
|
|
}
|
2015-07-10 18:07:48 +02:00
|
|
|
|
2015-08-22 07:54:30 +02:00
|
|
|
// See if the number is in scientific notation.
|
2015-08-22 08:08:11 +02:00
|
|
|
if (matchChar(parser, 'e') || matchChar(parser, 'E'))
|
2015-07-10 18:07:48 +02:00
|
|
|
{
|
2015-08-22 08:08:11 +02:00
|
|
|
// Allow a negative exponent.
|
|
|
|
|
matchChar(parser, '-');
|
2015-07-10 18:07:48 +02:00
|
|
|
|
2015-08-22 07:54:30 +02:00
|
|
|
if (!isDigit(peekChar(parser)))
|
2015-07-10 18:07:48 +02:00
|
|
|
{
|
|
|
|
|
lexError(parser, "Unterminated scientific notation.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while (isDigit(peekChar(parser))) nextChar(parser);
|
|
|
|
|
}
|
2013-11-10 06:32:57 +01:00
|
|
|
|
2015-03-03 16:17:56 +01:00
|
|
|
makeNumber(parser, false);
|
2013-10-28 05:59:14 +01:00
|
|
|
}
|
|
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
// Finishes lexing an identifier. Handles reserved words.
|
2013-11-23 23:55:05 +01:00
|
|
|
static void readName(Parser* parser, TokenType type)
|
2013-10-28 05:59:14 +01:00
|
|
|
{
|
2013-11-07 16:04:25 +01:00
|
|
|
while (isName(peekChar(parser)) || isDigit(peekChar(parser)))
|
2013-10-28 05:59:14 +01:00
|
|
|
{
|
2013-11-07 16:04:25 +01:00
|
|
|
nextChar(parser);
|
2013-10-28 05:59:14 +01:00
|
|
|
}
|
|
|
|
|
|
2015-09-18 05:51:24 +02:00
|
|
|
// Update the type if it's a keyword.
|
|
|
|
|
size_t length = parser->currentChar - parser->tokenStart;
|
|
|
|
|
for (int i = 0; keywords[i].identifier != NULL; i++)
|
|
|
|
|
{
|
|
|
|
|
if (length == keywords[i].length &&
|
|
|
|
|
memcmp(parser->tokenStart, keywords[i].identifier, length) == 0)
|
|
|
|
|
{
|
|
|
|
|
type = keywords[i].tokenType;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
makeToken(parser, type);
|
2013-10-28 05:59:14 +01:00
|
|
|
}
|
|
|
|
|
|
2015-03-28 04:44:07 +01:00
|
|
|
// Reads [digits] hex digits in a string literal and returns their number value.
|
|
|
|
|
static int readHexEscape(Parser* parser, int digits, const char* description)
|
2014-01-05 21:27:12 +01:00
|
|
|
{
|
|
|
|
|
int value = 0;
|
2015-03-28 04:44:07 +01:00
|
|
|
for (int i = 0; i < digits; i++)
|
2014-01-05 21:27:12 +01:00
|
|
|
{
|
2014-02-02 19:31:46 +01:00
|
|
|
if (peekChar(parser) == '"' || peekChar(parser) == '\0')
|
|
|
|
|
{
|
2015-03-28 04:44:07 +01:00
|
|
|
lexError(parser, "Incomplete %s escape sequence.", description);
|
2014-02-02 19:31:46 +01:00
|
|
|
|
|
|
|
|
// Don't consume it if it isn't expected. Keeps us from reading past the
|
|
|
|
|
// end of an unterminated string.
|
|
|
|
|
parser->currentChar--;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-21 10:22:56 +01:00
|
|
|
int digit = readHexDigit(parser);
|
2014-02-02 19:31:46 +01:00
|
|
|
if (digit == -1)
|
|
|
|
|
{
|
2015-03-28 04:44:07 +01:00
|
|
|
lexError(parser, "Invalid %s escape sequence.", description);
|
2014-02-02 19:31:46 +01:00
|
|
|
break;
|
|
|
|
|
}
|
2014-01-05 21:27:12 +01:00
|
|
|
|
|
|
|
|
value = (value * 16) | digit;
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-28 04:44:07 +01:00
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-03 18:28:25 +02:00
|
|
|
// Reads a hex digit Unicode escape sequence in a string literal.
|
|
|
|
|
static void readUnicodeEscape(Parser* parser, ByteBuffer* string, int length)
|
2015-03-28 04:44:07 +01:00
|
|
|
{
|
2015-10-03 18:28:25 +02:00
|
|
|
int value = readHexEscape(parser, length, "Unicode");
|
2015-03-28 04:44:07 +01:00
|
|
|
|
2015-03-27 15:43:36 +01:00
|
|
|
// Grow the buffer enough for the encoded result.
|
2015-09-02 07:14:55 +02:00
|
|
|
int numBytes = wrenUtf8EncodeNumBytes(value);
|
2015-03-27 15:43:36 +01:00
|
|
|
if (numBytes != 0)
|
2015-01-23 00:18:30 +01:00
|
|
|
{
|
2015-08-22 07:54:30 +02:00
|
|
|
wrenByteBufferFill(parser->vm, string, 0, numBytes);
|
|
|
|
|
wrenUtf8Encode(value, string->data + string->count - numBytes);
|
2015-01-23 00:18:30 +01:00
|
|
|
}
|
2013-11-24 06:00:47 +01:00
|
|
|
}
|
|
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
// Finishes lexing a string literal.
|
|
|
|
|
static void readString(Parser* parser)
|
2013-10-22 21:16:39 +02:00
|
|
|
{
|
2015-08-22 07:54:30 +02:00
|
|
|
ByteBuffer string;
|
2015-11-11 16:55:48 +01:00
|
|
|
TokenType type = TOKEN_STRING;
|
2015-08-22 07:54:30 +02:00
|
|
|
wrenByteBufferInit(&string);
|
2015-11-11 16:55:48 +01:00
|
|
|
|
2013-11-24 06:00:47 +01:00
|
|
|
for (;;)
|
|
|
|
|
{
|
|
|
|
|
char c = nextChar(parser);
|
|
|
|
|
if (c == '"') break;
|
|
|
|
|
|
2014-02-02 19:31:46 +01:00
|
|
|
if (c == '\0')
|
|
|
|
|
{
|
|
|
|
|
lexError(parser, "Unterminated string.");
|
|
|
|
|
|
|
|
|
|
// Don't consume it if it isn't expected. Keeps us from reading past the
|
|
|
|
|
// end of an unterminated string.
|
|
|
|
|
parser->currentChar--;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2015-11-11 16:55:48 +01:00
|
|
|
if (c == '%')
|
|
|
|
|
{
|
|
|
|
|
if (parser->numParens < MAX_INTERPOLATION_NESTING)
|
|
|
|
|
{
|
|
|
|
|
// TODO: Allow format string.
|
2015-12-05 19:15:18 +01:00
|
|
|
if (nextChar(parser) != '(') lexError(parser, "Expect '(' after '%%'.");
|
2015-11-11 16:55:48 +01:00
|
|
|
|
2016-02-24 15:52:12 +01:00
|
|
|
parser->parens[parser->numParens++] = 1;
|
2015-11-11 16:55:48 +01:00
|
|
|
type = TOKEN_INTERPOLATION;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lexError(parser, "Interpolation may only nest %d levels deep.",
|
|
|
|
|
MAX_INTERPOLATION_NESTING);
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-24 06:00:47 +01:00
|
|
|
if (c == '\\')
|
|
|
|
|
{
|
|
|
|
|
switch (nextChar(parser))
|
|
|
|
|
{
|
2015-08-22 07:54:30 +02:00
|
|
|
case '"': wrenByteBufferWrite(parser->vm, &string, '"'); break;
|
|
|
|
|
case '\\': wrenByteBufferWrite(parser->vm, &string, '\\'); break;
|
2015-11-11 16:55:48 +01:00
|
|
|
case '%': wrenByteBufferWrite(parser->vm, &string, '%'); break;
|
2015-08-22 07:54:30 +02:00
|
|
|
case '0': wrenByteBufferWrite(parser->vm, &string, '\0'); break;
|
|
|
|
|
case 'a': wrenByteBufferWrite(parser->vm, &string, '\a'); break;
|
|
|
|
|
case 'b': wrenByteBufferWrite(parser->vm, &string, '\b'); break;
|
|
|
|
|
case 'f': wrenByteBufferWrite(parser->vm, &string, '\f'); break;
|
|
|
|
|
case 'n': wrenByteBufferWrite(parser->vm, &string, '\n'); break;
|
|
|
|
|
case 'r': wrenByteBufferWrite(parser->vm, &string, '\r'); break;
|
|
|
|
|
case 't': wrenByteBufferWrite(parser->vm, &string, '\t'); break;
|
2015-10-03 18:28:25 +02:00
|
|
|
case 'u': readUnicodeEscape(parser, &string, 4); break;
|
|
|
|
|
case 'U': readUnicodeEscape(parser, &string, 8); break;
|
2015-08-22 07:54:30 +02:00
|
|
|
case 'v': wrenByteBufferWrite(parser->vm, &string, '\v'); break;
|
2015-03-28 04:44:07 +01:00
|
|
|
case 'x':
|
2015-08-22 07:54:30 +02:00
|
|
|
wrenByteBufferWrite(parser->vm, &string,
|
|
|
|
|
(uint8_t)readHexEscape(parser, 2, "byte"));
|
2015-03-28 04:44:07 +01:00
|
|
|
break;
|
|
|
|
|
|
2013-11-24 06:00:47 +01:00
|
|
|
default:
|
2014-02-02 19:31:46 +01:00
|
|
|
lexError(parser, "Invalid escape character '%c'.",
|
|
|
|
|
*(parser->currentChar - 1));
|
2013-11-24 06:00:47 +01:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2015-08-22 07:54:30 +02:00
|
|
|
wrenByteBufferWrite(parser->vm, &string, c);
|
2013-11-24 06:00:47 +01:00
|
|
|
}
|
|
|
|
|
}
|
feat: add class parsing and object type system with ObjBlock, ObjClass, and ObjNum structs
Introduce token types TOKEN_CLASS and TOKEN_META for class declaration parsing in the compiler. Refactor the internal object model from a single Obj union to distinct ObjNum, ObjBlock, and ObjClass structs, replacing the old Block type with ObjBlock throughout the compiler, VM, and main entry point. Implement makeClass() and makeBlock() factory functions, update makeNum() to return ObjNum*, and add CODE_CLASS and CODE_DUP bytecodes to support class definition at compile time. Modify the symbol table and method dispatch to use a Method union (primitive or block) stored in ObjClass, and update the VM's callBlock signature and newVM initialization accordingly.
2013-10-24 07:50:04 +02:00
|
|
|
|
2015-11-11 16:55:48 +01:00
|
|
|
parser->current.value = wrenNewString(parser->vm,
|
|
|
|
|
(char*)string.data, string.count);
|
|
|
|
|
|
2015-08-22 07:54:30 +02:00
|
|
|
wrenByteBufferClear(parser->vm, &string);
|
2015-11-11 16:55:48 +01:00
|
|
|
makeToken(parser, type);
|
2013-11-07 16:04:25 +01:00
|
|
|
}
|
feat: add class parsing and object type system with ObjBlock, ObjClass, and ObjNum structs
Introduce token types TOKEN_CLASS and TOKEN_META for class declaration parsing in the compiler. Refactor the internal object model from a single Obj union to distinct ObjNum, ObjBlock, and ObjClass structs, replacing the old Block type with ObjBlock throughout the compiler, VM, and main entry point. Implement makeClass() and makeBlock() factory functions, update makeNum() to return ObjNum*, and add CODE_CLASS and CODE_DUP bytecodes to support class definition at compile time. Modify the symbol table and method dispatch to use a Method union (primitive or block) stored in ObjClass, and update the VM's callBlock signature and newVM initialization accordingly.
2013-10-24 07:50:04 +02:00
|
|
|
|
refactor: move newline handling from lexer into parser grammar rules
This change shifts newline tokenization from the lexer's readRawToken into the parser's nextToken, allowing the grammar to selectively ignore newlines in specific contexts (e.g., after operators like "|", "is", "for", "if", "while") while treating them as errors in others (e.g., after "var", "new", "class", "static", before commas in lists). The lexer now emits TOKEN_LINE for semicolons and newlines uniformly, and the parser decides which to elide. Test files are updated to reflect the new error expectations and to remove tests for newlines that are now grammatically allowed.
2014-04-04 16:51:18 +02:00
|
|
|
// Lex the next token and store it in [parser.current].
|
|
|
|
|
static void nextToken(Parser* parser)
|
2013-11-07 16:04:25 +01:00
|
|
|
{
|
refactor: move newline handling from lexer into parser grammar rules
This change shifts newline tokenization from the lexer's readRawToken into the parser's nextToken, allowing the grammar to selectively ignore newlines in specific contexts (e.g., after operators like "|", "is", "for", "if", "while") while treating them as errors in others (e.g., after "var", "new", "class", "static", before commas in lists). The lexer now emits TOKEN_LINE for semicolons and newlines uniformly, and the parser decides which to elide. Test files are updated to reflect the new error expectations and to remove tests for newlines that are now grammatically allowed.
2014-04-04 16:51:18 +02:00
|
|
|
parser->previous = parser->current;
|
|
|
|
|
|
|
|
|
|
// If we are out of tokens, don't try to tokenize any more. We *do* still
|
|
|
|
|
// copy the TOKEN_EOF to previous so that code that expects it to be consumed
|
|
|
|
|
// will still work.
|
|
|
|
|
if (parser->current.type == TOKEN_EOF) return;
|
2015-11-11 16:55:48 +01:00
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
while (peekChar(parser) != '\0')
|
|
|
|
|
{
|
|
|
|
|
parser->tokenStart = parser->currentChar;
|
feat: add class parsing and object type system with ObjBlock, ObjClass, and ObjNum structs
Introduce token types TOKEN_CLASS and TOKEN_META for class declaration parsing in the compiler. Refactor the internal object model from a single Obj union to distinct ObjNum, ObjBlock, and ObjClass structs, replacing the old Block type with ObjBlock throughout the compiler, VM, and main entry point. Implement makeClass() and makeBlock() factory functions, update makeNum() to return ObjNum*, and add CODE_CLASS and CODE_DUP bytecodes to support class definition at compile time. Modify the symbol table and method dispatch to use a Method union (primitive or block) stored in ObjClass, and update the VM's callBlock signature and newVM initialization accordingly.
2013-10-24 07:50:04 +02:00
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
char c = nextChar(parser);
|
|
|
|
|
switch (c)
|
2013-10-24 21:39:01 +02:00
|
|
|
{
|
2015-11-11 16:55:48 +01:00
|
|
|
case '(':
|
|
|
|
|
// If we are inside an interpolated expression, count the unmatched "(".
|
|
|
|
|
if (parser->numParens > 0) parser->parens[parser->numParens - 1]++;
|
|
|
|
|
makeToken(parser, TOKEN_LEFT_PAREN);
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
case ')':
|
|
|
|
|
// If we are inside an interpolated expression, count the ")".
|
|
|
|
|
if (parser->numParens > 0 &&
|
|
|
|
|
--parser->parens[parser->numParens - 1] == 0)
|
|
|
|
|
{
|
|
|
|
|
// This is the final ")", so the interpolation expression has ended.
|
|
|
|
|
// This ")" now begins the next section of the template string.
|
|
|
|
|
parser->numParens--;
|
|
|
|
|
readString(parser);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
makeToken(parser, TOKEN_RIGHT_PAREN);
|
|
|
|
|
return;
|
|
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
case '[': makeToken(parser, TOKEN_LEFT_BRACKET); return;
|
|
|
|
|
case ']': makeToken(parser, TOKEN_RIGHT_BRACKET); return;
|
|
|
|
|
case '{': makeToken(parser, TOKEN_LEFT_BRACE); return;
|
|
|
|
|
case '}': makeToken(parser, TOKEN_RIGHT_BRACE); return;
|
|
|
|
|
case ':': makeToken(parser, TOKEN_COLON); return;
|
2015-08-22 16:56:38 +02:00
|
|
|
case ',': makeToken(parser, TOKEN_COMMA); return;
|
|
|
|
|
case '*': makeToken(parser, TOKEN_STAR); return;
|
|
|
|
|
case '%': makeToken(parser, TOKEN_PERCENT); return;
|
|
|
|
|
case '^': makeToken(parser, TOKEN_CARET); return;
|
|
|
|
|
case '+': makeToken(parser, TOKEN_PLUS); return;
|
|
|
|
|
case '-': makeToken(parser, TOKEN_MINUS); return;
|
|
|
|
|
case '~': makeToken(parser, TOKEN_TILDE); return;
|
|
|
|
|
case '?': makeToken(parser, TOKEN_QUESTION); return;
|
|
|
|
|
|
|
|
|
|
case '|': twoCharToken(parser, '|', TOKEN_PIPEPIPE, TOKEN_PIPE); return;
|
|
|
|
|
case '&': twoCharToken(parser, '&', TOKEN_AMPAMP, TOKEN_AMP); return;
|
|
|
|
|
case '=': twoCharToken(parser, '=', TOKEN_EQEQ, TOKEN_EQ); return;
|
|
|
|
|
case '!': twoCharToken(parser, '=', TOKEN_BANGEQ, TOKEN_BANG); return;
|
|
|
|
|
|
2013-12-26 00:01:45 +01:00
|
|
|
case '.':
|
2015-08-22 08:08:11 +02:00
|
|
|
if (matchChar(parser, '.'))
|
2013-12-26 00:01:45 +01:00
|
|
|
{
|
2015-08-22 08:08:11 +02:00
|
|
|
twoCharToken(parser, '.', TOKEN_DOTDOTDOT, TOKEN_DOTDOT);
|
2013-12-26 00:01:45 +01:00
|
|
|
return;
|
|
|
|
|
}
|
2015-08-22 16:56:38 +02:00
|
|
|
|
2013-12-26 00:01:45 +01:00
|
|
|
makeToken(parser, TOKEN_DOT);
|
|
|
|
|
return;
|
2015-08-22 16:56:38 +02:00
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
case '/':
|
2015-08-22 08:08:11 +02:00
|
|
|
if (matchChar(parser, '/'))
|
2013-11-07 16:04:25 +01:00
|
|
|
{
|
|
|
|
|
skipLineComment(parser);
|
|
|
|
|
break;
|
|
|
|
|
}
|
2013-11-20 05:19:02 +01:00
|
|
|
|
2015-08-22 08:08:11 +02:00
|
|
|
if (matchChar(parser, '*'))
|
2013-11-14 02:09:47 +01:00
|
|
|
{
|
|
|
|
|
skipBlockComment(parser);
|
|
|
|
|
break;
|
|
|
|
|
}
|
2013-11-20 05:19:02 +01:00
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
makeToken(parser, TOKEN_SLASH);
|
|
|
|
|
return;
|
2013-10-24 21:39:01 +02:00
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
case '<':
|
2015-08-22 08:08:11 +02:00
|
|
|
if (matchChar(parser, '<'))
|
2015-02-16 11:28:34 +01:00
|
|
|
{
|
2015-02-16 22:26:54 +01:00
|
|
|
makeToken(parser, TOKEN_LTLT);
|
2015-02-16 11:28:34 +01:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
twoCharToken(parser, '=', TOKEN_LTEQ, TOKEN_LT);
|
|
|
|
|
}
|
2013-11-07 16:04:25 +01:00
|
|
|
return;
|
feat: add class parsing and object type system with ObjBlock, ObjClass, and ObjNum structs
Introduce token types TOKEN_CLASS and TOKEN_META for class declaration parsing in the compiler. Refactor the internal object model from a single Obj union to distinct ObjNum, ObjBlock, and ObjClass structs, replacing the old Block type with ObjBlock throughout the compiler, VM, and main entry point. Implement makeClass() and makeBlock() factory functions, update makeNum() to return ObjNum*, and add CODE_CLASS and CODE_DUP bytecodes to support class definition at compile time. Modify the symbol table and method dispatch to use a Method union (primitive or block) stored in ObjClass, and update the VM's callBlock signature and newVM initialization accordingly.
2013-10-24 07:50:04 +02:00
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
case '>':
|
2015-08-22 08:08:11 +02:00
|
|
|
if (matchChar(parser, '>'))
|
2015-02-16 11:28:34 +01:00
|
|
|
{
|
2015-02-16 22:26:54 +01:00
|
|
|
makeToken(parser, TOKEN_GTGT);
|
2015-02-16 11:28:34 +01:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
twoCharToken(parser, '=', TOKEN_GTEQ, TOKEN_GT);
|
|
|
|
|
}
|
2013-11-07 16:04:25 +01:00
|
|
|
return;
|
feat: add class parsing and object type system with ObjBlock, ObjClass, and ObjNum structs
Introduce token types TOKEN_CLASS and TOKEN_META for class declaration parsing in the compiler. Refactor the internal object model from a single Obj union to distinct ObjNum, ObjBlock, and ObjClass structs, replacing the old Block type with ObjBlock throughout the compiler, VM, and main entry point. Implement makeClass() and makeBlock() factory functions, update makeNum() to return ObjNum*, and add CODE_CLASS and CODE_DUP bytecodes to support class definition at compile time. Modify the symbol table and method dispatch to use a Method union (primitive or block) stored in ObjClass, and update the VM's callBlock signature and newVM initialization accordingly.
2013-10-24 07:50:04 +02:00
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
case '\n':
|
|
|
|
|
makeToken(parser, TOKEN_LINE);
|
|
|
|
|
return;
|
2013-10-24 00:32:59 +02:00
|
|
|
|
2013-11-20 05:19:02 +01:00
|
|
|
case ' ':
|
2015-01-06 03:33:49 +01:00
|
|
|
case '\r':
|
2015-01-12 05:01:30 +01:00
|
|
|
case '\t':
|
2013-11-20 05:19:02 +01:00
|
|
|
// Skip forward until we run out of whitespace.
|
2015-01-12 05:01:30 +01:00
|
|
|
while (peekChar(parser) == ' ' ||
|
|
|
|
|
peekChar(parser) == '\r' ||
|
|
|
|
|
peekChar(parser) == '\t')
|
2015-01-12 04:52:59 +01:00
|
|
|
{
|
|
|
|
|
nextChar(parser);
|
|
|
|
|
}
|
2013-11-20 05:19:02 +01:00
|
|
|
break;
|
|
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
case '"': readString(parser); return;
|
2014-01-18 18:33:18 +01:00
|
|
|
case '_':
|
|
|
|
|
readName(parser,
|
|
|
|
|
peekChar(parser) == '_' ? TOKEN_STATIC_FIELD : TOKEN_FIELD);
|
|
|
|
|
return;
|
2013-10-24 00:32:59 +02:00
|
|
|
|
2015-01-25 22:02:44 +01:00
|
|
|
case '0':
|
|
|
|
|
if (peekChar(parser) == 'x')
|
|
|
|
|
{
|
|
|
|
|
readHexNumber(parser);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-26 02:51:50 +01:00
|
|
|
readNumber(parser);
|
|
|
|
|
return;
|
|
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
default:
|
2016-03-12 02:23:31 +01:00
|
|
|
if (parser->currentLine == 1 && c == '#' && peekChar(parser) == '!')
|
|
|
|
|
{
|
|
|
|
|
// Ignore shebang on the first line.
|
|
|
|
|
skipLineComment(parser);
|
|
|
|
|
break;
|
|
|
|
|
}
|
2013-11-07 16:04:25 +01:00
|
|
|
if (isName(c))
|
|
|
|
|
{
|
2013-11-23 23:55:05 +01:00
|
|
|
readName(parser, TOKEN_NAME);
|
2013-11-07 16:04:25 +01:00
|
|
|
}
|
|
|
|
|
else if (isDigit(c))
|
|
|
|
|
{
|
|
|
|
|
readNumber(parser);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2013-12-23 23:51:06 +01:00
|
|
|
lexError(parser, "Invalid character '%c'.", c);
|
2013-11-07 16:04:25 +01:00
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
2013-10-22 21:16:39 +02:00
|
|
|
}
|
|
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
// If we get here, we're out of source, so just make EOF tokens.
|
|
|
|
|
parser->tokenStart = parser->currentChar;
|
|
|
|
|
makeToken(parser, TOKEN_EOF);
|
2013-10-22 21:16:39 +02:00
|
|
|
}
|
|
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
// Parsing ---------------------------------------------------------------------
|
2013-11-06 00:40:21 +01:00
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
// Returns the type of the current token.
|
|
|
|
|
static TokenType peek(Compiler* compiler)
|
|
|
|
|
{
|
|
|
|
|
return compiler->parser->current.type;
|
|
|
|
|
}
|
2013-11-06 00:40:21 +01:00
|
|
|
|
2013-12-15 08:41:23 +01:00
|
|
|
// Consumes the current token if its type is [expected]. Returns true if a
|
2013-11-07 16:04:25 +01:00
|
|
|
// token was consumed.
|
2013-12-15 08:41:23 +01:00
|
|
|
static bool match(Compiler* compiler, TokenType expected)
|
2013-11-07 16:04:25 +01:00
|
|
|
{
|
2013-12-15 08:41:23 +01:00
|
|
|
if (peek(compiler) != expected) return false;
|
2013-11-07 16:04:25 +01:00
|
|
|
|
|
|
|
|
nextToken(compiler->parser);
|
2013-12-15 08:41:23 +01:00
|
|
|
return true;
|
2013-10-31 15:04:44 +01:00
|
|
|
}
|
|
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
// Consumes the current token. Emits an error if its type is not [expected].
|
2015-01-20 23:40:34 +01:00
|
|
|
static void consume(Compiler* compiler, TokenType expected,
|
2013-11-14 08:06:53 +01:00
|
|
|
const char* errorMessage)
|
2013-10-31 15:04:44 +01:00
|
|
|
{
|
2013-11-03 19:16:14 +01:00
|
|
|
nextToken(compiler->parser);
|
2013-11-07 16:04:25 +01:00
|
|
|
if (compiler->parser->previous.type != expected)
|
|
|
|
|
{
|
2013-11-14 08:06:53 +01:00
|
|
|
error(compiler, errorMessage);
|
refactor: move newline handling from lexer into parser grammar rules
This change shifts newline tokenization from the lexer's readRawToken into the parser's nextToken, allowing the grammar to selectively ignore newlines in specific contexts (e.g., after operators like "|", "is", "for", "if", "while") while treating them as errors in others (e.g., after "var", "new", "class", "static", before commas in lists). The lexer now emits TOKEN_LINE for semicolons and newlines uniformly, and the parser decides which to elide. Test files are updated to reflect the new error expectations and to remove tests for newlines that are now grammatically allowed.
2014-04-04 16:51:18 +02:00
|
|
|
|
|
|
|
|
// If the next token is the one we want, assume the current one is just a
|
|
|
|
|
// spurious error and discard it to minimize the number of cascaded errors.
|
|
|
|
|
if (compiler->parser->current.type == expected) nextToken(compiler->parser);
|
2013-11-07 16:04:25 +01:00
|
|
|
}
|
|
|
|
|
}
|
2013-10-31 15:04:44 +01:00
|
|
|
|
refactor: move newline handling from lexer into parser grammar rules
This change shifts newline tokenization from the lexer's readRawToken into the parser's nextToken, allowing the grammar to selectively ignore newlines in specific contexts (e.g., after operators like "|", "is", "for", "if", "while") while treating them as errors in others (e.g., after "var", "new", "class", "static", before commas in lists). The lexer now emits TOKEN_LINE for semicolons and newlines uniformly, and the parser decides which to elide. Test files are updated to reflect the new error expectations and to remove tests for newlines that are now grammatically allowed.
2014-04-04 16:51:18 +02:00
|
|
|
// Matches one or more newlines. Returns true if at least one was found.
|
|
|
|
|
static bool matchLine(Compiler* compiler)
|
|
|
|
|
{
|
|
|
|
|
if (!match(compiler, TOKEN_LINE)) return false;
|
|
|
|
|
|
|
|
|
|
while (match(compiler, TOKEN_LINE));
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-22 16:56:38 +02:00
|
|
|
// Discards any newlines starting at the current token.
|
refactor: move newline handling from lexer into parser grammar rules
This change shifts newline tokenization from the lexer's readRawToken into the parser's nextToken, allowing the grammar to selectively ignore newlines in specific contexts (e.g., after operators like "|", "is", "for", "if", "while") while treating them as errors in others (e.g., after "var", "new", "class", "static", before commas in lists). The lexer now emits TOKEN_LINE for semicolons and newlines uniformly, and the parser decides which to elide. Test files are updated to reflect the new error expectations and to remove tests for newlines that are now grammatically allowed.
2014-04-04 16:51:18 +02:00
|
|
|
static void ignoreNewlines(Compiler* compiler)
|
|
|
|
|
{
|
|
|
|
|
matchLine(compiler);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Consumes the current token. Emits an error if it is not a newline. Then
|
|
|
|
|
// discards any duplicate newlines following it.
|
2015-01-20 23:40:34 +01:00
|
|
|
static void consumeLine(Compiler* compiler, const char* errorMessage)
|
refactor: move newline handling from lexer into parser grammar rules
This change shifts newline tokenization from the lexer's readRawToken into the parser's nextToken, allowing the grammar to selectively ignore newlines in specific contexts (e.g., after operators like "|", "is", "for", "if", "while") while treating them as errors in others (e.g., after "var", "new", "class", "static", before commas in lists). The lexer now emits TOKEN_LINE for semicolons and newlines uniformly, and the parser decides which to elide. Test files are updated to reflect the new error expectations and to remove tests for newlines that are now grammatically allowed.
2014-04-04 16:51:18 +02:00
|
|
|
{
|
2015-01-20 23:40:34 +01:00
|
|
|
consume(compiler, TOKEN_LINE, errorMessage);
|
refactor: move newline handling from lexer into parser grammar rules
This change shifts newline tokenization from the lexer's readRawToken into the parser's nextToken, allowing the grammar to selectively ignore newlines in specific contexts (e.g., after operators like "|", "is", "for", "if", "while") while treating them as errors in others (e.g., after "var", "new", "class", "static", before commas in lists). The lexer now emits TOKEN_LINE for semicolons and newlines uniformly, and the parser decides which to elide. Test files are updated to reflect the new error expectations and to remove tests for newlines that are now grammatically allowed.
2014-04-04 16:51:18 +02:00
|
|
|
ignoreNewlines(compiler);
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-01 23:59:56 +01:00
|
|
|
// Variables and scopes --------------------------------------------------------
|
2013-11-07 16:04:25 +01:00
|
|
|
|
2015-12-14 07:57:40 +01:00
|
|
|
// Emits one single-byte argument. Returns its index.
|
|
|
|
|
static int emitByte(Compiler* compiler, int byte)
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
{
|
2016-03-04 16:49:34 +01:00
|
|
|
wrenByteBufferWrite(compiler->parser->vm, &compiler->fn->code, (uint8_t)byte);
|
2015-12-14 07:57:40 +01:00
|
|
|
|
2014-01-01 22:25:24 +01:00
|
|
|
// Assume the instruction is associated with the most recently consumed token.
|
2016-03-04 16:49:34 +01:00
|
|
|
wrenIntBufferWrite(compiler->parser->vm, &compiler->fn->debug->sourceLines,
|
2015-12-14 07:57:40 +01:00
|
|
|
compiler->parser->previous.line);
|
|
|
|
|
|
2016-03-04 16:49:34 +01:00
|
|
|
return compiler->fn->code.count - 1;
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
}
|
|
|
|
|
|
2015-12-14 07:57:40 +01:00
|
|
|
// Emits one bytecode instruction.
|
|
|
|
|
static void emitOp(Compiler* compiler, Code instruction)
|
|
|
|
|
{
|
|
|
|
|
emitByte(compiler, instruction);
|
|
|
|
|
|
|
|
|
|
// Keep track of the stack's high water mark.
|
|
|
|
|
compiler->numSlots += stackEffects[instruction];
|
2016-03-04 16:49:34 +01:00
|
|
|
if (compiler->numSlots > compiler->fn->maxSlots)
|
2015-12-14 07:57:40 +01:00
|
|
|
{
|
2016-03-04 16:49:34 +01:00
|
|
|
compiler->fn->maxSlots = compiler->numSlots;
|
2015-12-14 07:57:40 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-16 00:59:14 +01:00
|
|
|
// Emits one 16-bit argument, which will be written big endian.
|
2015-03-22 18:36:08 +01:00
|
|
|
static void emitShort(Compiler* compiler, int arg)
|
2015-01-16 00:59:14 +01:00
|
|
|
{
|
2015-12-14 07:57:40 +01:00
|
|
|
emitByte(compiler, (arg >> 8) & 0xff);
|
|
|
|
|
emitByte(compiler, arg & 0xff);
|
2015-01-16 00:59:14 +01:00
|
|
|
}
|
|
|
|
|
|
2014-01-04 20:08:31 +01:00
|
|
|
// Emits one bytecode instruction followed by a 8-bit argument. Returns the
|
|
|
|
|
// index of the argument in the bytecode.
|
2015-03-22 18:36:08 +01:00
|
|
|
static int emitByteArg(Compiler* compiler, Code instruction, int arg)
|
2013-12-25 06:04:11 +01:00
|
|
|
{
|
2015-12-14 07:57:40 +01:00
|
|
|
emitOp(compiler, instruction);
|
|
|
|
|
return emitByte(compiler, arg);
|
2013-12-25 06:04:11 +01:00
|
|
|
}
|
|
|
|
|
|
2014-01-04 20:08:31 +01:00
|
|
|
// Emits one bytecode instruction followed by a 16-bit argument, which will be
|
|
|
|
|
// written big endian.
|
2015-03-22 18:36:08 +01:00
|
|
|
static void emitShortArg(Compiler* compiler, Code instruction, int arg)
|
2014-01-04 20:08:31 +01:00
|
|
|
{
|
2015-12-14 07:57:40 +01:00
|
|
|
emitOp(compiler, instruction);
|
2015-01-16 00:59:14 +01:00
|
|
|
emitShort(compiler, arg);
|
2014-01-04 20:08:31 +01:00
|
|
|
}
|
|
|
|
|
|
2014-01-08 08:03:38 +01:00
|
|
|
// Emits [instruction] followed by a placeholder for a jump offset. The
|
|
|
|
|
// placeholder can be patched by calling [jumpPatch]. Returns the index of the
|
|
|
|
|
// placeholder.
|
|
|
|
|
static int emitJump(Compiler* compiler, Code instruction)
|
|
|
|
|
{
|
2015-12-14 07:57:40 +01:00
|
|
|
emitOp(compiler, instruction);
|
|
|
|
|
emitByte(compiler, 0xff);
|
|
|
|
|
return emitByte(compiler, 0xff) - 1;
|
2014-01-08 08:03:38 +01:00
|
|
|
}
|
|
|
|
|
|
2015-08-22 07:54:30 +02:00
|
|
|
// Creates a new constant for the current value and emits the bytecode to load
|
|
|
|
|
// it from the constant table.
|
2015-09-12 19:14:04 +02:00
|
|
|
static void emitConstant(Compiler* compiler, Value value)
|
2015-08-22 07:54:30 +02:00
|
|
|
{
|
2015-09-12 19:14:04 +02:00
|
|
|
int constant = addConstant(compiler, value);
|
2015-08-22 07:54:30 +02:00
|
|
|
|
|
|
|
|
// Compile the code to load the constant.
|
|
|
|
|
emitShortArg(compiler, CODE_CONSTANT, constant);
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-25 06:04:11 +01:00
|
|
|
// Create a new local variable with [name]. Assumes the current scope is local
|
|
|
|
|
// and the name is unique.
|
2015-08-22 16:56:38 +02:00
|
|
|
static int addLocal(Compiler* compiler, const char* name, int length)
|
2013-12-25 06:04:11 +01:00
|
|
|
{
|
|
|
|
|
Local* local = &compiler->locals[compiler->numLocals];
|
|
|
|
|
local->name = name;
|
|
|
|
|
local->length = length;
|
|
|
|
|
local->depth = compiler->scopeDepth;
|
|
|
|
|
local->isUpvalue = false;
|
|
|
|
|
return compiler->numLocals++;
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-01 16:05:40 +02:00
|
|
|
// Declares a variable in the current scope whose name is the given token.
|
|
|
|
|
//
|
|
|
|
|
// If [token] is `NULL`, uses the previously consumed token. Returns its symbol.
|
|
|
|
|
static int declareVariable(Compiler* compiler, Token* token)
|
2013-11-07 16:04:25 +01:00
|
|
|
{
|
2015-04-01 16:05:40 +02:00
|
|
|
if (token == NULL) token = &compiler->parser->previous;
|
2013-12-01 03:28:01 +01:00
|
|
|
|
2014-04-22 16:06:26 +02:00
|
|
|
if (token->length > MAX_VARIABLE_NAME)
|
|
|
|
|
{
|
|
|
|
|
error(compiler, "Variable name cannot be longer than %d characters.",
|
|
|
|
|
MAX_VARIABLE_NAME);
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-26 16:45:21 +01:00
|
|
|
// Top-level module scope.
|
2013-12-01 03:28:01 +01:00
|
|
|
if (compiler->scopeDepth == -1)
|
2013-10-31 15:04:44 +01:00
|
|
|
{
|
2015-01-26 16:45:21 +01:00
|
|
|
int symbol = wrenDefineVariable(compiler->parser->vm,
|
|
|
|
|
compiler->parser->module,
|
|
|
|
|
token->start, token->length, NULL_VAL);
|
2013-12-01 03:28:01 +01:00
|
|
|
|
|
|
|
|
if (symbol == -1)
|
|
|
|
|
{
|
2015-01-26 16:45:21 +01:00
|
|
|
error(compiler, "Module variable is already defined.");
|
2013-12-01 03:28:01 +01:00
|
|
|
}
|
2014-04-07 01:16:15 +02:00
|
|
|
else if (symbol == -2)
|
|
|
|
|
{
|
2015-01-26 16:45:21 +01:00
|
|
|
error(compiler, "Too many module variables defined.");
|
2014-04-07 01:16:15 +02:00
|
|
|
}
|
2013-12-01 03:28:01 +01:00
|
|
|
|
|
|
|
|
return symbol;
|
2013-11-07 16:04:25 +01:00
|
|
|
}
|
2013-12-01 03:28:01 +01:00
|
|
|
|
|
|
|
|
// See if there is already a variable with this name declared in the current
|
|
|
|
|
// scope. (Outer scopes are OK: those get shadowed.)
|
|
|
|
|
for (int i = compiler->numLocals - 1; i >= 0; i--)
|
2013-11-07 16:04:25 +01:00
|
|
|
{
|
2013-12-01 03:28:01 +01:00
|
|
|
Local* local = &compiler->locals[i];
|
2013-10-31 15:04:44 +01:00
|
|
|
|
2013-12-01 03:28:01 +01:00
|
|
|
// Once we escape this scope and hit an outer one, we can stop.
|
|
|
|
|
if (local->depth < compiler->scopeDepth) break;
|
2013-10-31 15:04:44 +01:00
|
|
|
|
2013-12-02 00:22:24 +01:00
|
|
|
if (local->length == token->length &&
|
2016-02-12 16:27:42 +01:00
|
|
|
memcmp(local->name, token->start, token->length) == 0)
|
2013-12-01 03:28:01 +01:00
|
|
|
{
|
|
|
|
|
error(compiler, "Variable is already declared in this scope.");
|
|
|
|
|
return i;
|
|
|
|
|
}
|
2013-10-31 15:04:44 +01:00
|
|
|
}
|
2013-11-07 16:04:25 +01:00
|
|
|
|
2013-12-01 03:51:27 +01:00
|
|
|
if (compiler->numLocals == MAX_LOCALS)
|
|
|
|
|
{
|
|
|
|
|
error(compiler, "Cannot declare more than %d variables in one scope.",
|
|
|
|
|
MAX_LOCALS);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-22 16:56:38 +02:00
|
|
|
return addLocal(compiler, token->start, token->length);
|
2013-10-22 21:16:39 +02:00
|
|
|
}
|
|
|
|
|
|
2014-01-19 22:01:51 +01:00
|
|
|
// Parses a name token and declares a variable in the current scope with that
|
2015-02-15 01:46:00 +01:00
|
|
|
// name. Returns its slot.
|
2014-01-19 22:01:51 +01:00
|
|
|
static int declareNamedVariable(Compiler* compiler)
|
|
|
|
|
{
|
2015-02-15 01:46:00 +01:00
|
|
|
consume(compiler, TOKEN_NAME, "Expect variable name.");
|
2015-04-01 16:05:40 +02:00
|
|
|
return declareVariable(compiler, NULL);
|
2014-01-19 22:01:51 +01:00
|
|
|
}
|
|
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
// Stores a variable with the previously defined symbol in the current scope.
|
2013-11-09 20:38:01 +01:00
|
|
|
static void defineVariable(Compiler* compiler, int symbol)
|
2013-11-07 16:04:25 +01:00
|
|
|
{
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
// 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;
|
|
|
|
|
|
2015-01-26 16:45:21 +01:00
|
|
|
// It's a module-level variable, so store the value in the module slot and
|
|
|
|
|
// then discard the temporary for the initializer.
|
|
|
|
|
emitShortArg(compiler, CODE_STORE_MODULE_VAR, symbol);
|
2015-12-14 07:57:40 +01:00
|
|
|
emitOp(compiler, CODE_POP);
|
2013-11-07 16:04:25 +01:00
|
|
|
}
|
|
|
|
|
|
2013-12-01 03:28:01 +01:00
|
|
|
// Starts a new local block scope.
|
|
|
|
|
static void pushScope(Compiler* compiler)
|
|
|
|
|
{
|
|
|
|
|
compiler->scopeDepth++;
|
|
|
|
|
}
|
|
|
|
|
|
2014-01-06 17:01:04 +01:00
|
|
|
// Generates code to discard local variables at [depth] or greater. Does *not*
|
|
|
|
|
// actually undeclare variables or pop any scopes, though. This is called
|
|
|
|
|
// 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)
|
2013-12-01 03:28:01 +01:00
|
|
|
{
|
2014-01-06 17:01:04 +01:00
|
|
|
ASSERT(compiler->scopeDepth > -1, "Cannot exit top-level scope.");
|
2013-12-01 03:28:01 +01:00
|
|
|
|
2014-01-06 17:01:04 +01:00
|
|
|
int local = compiler->numLocals - 1;
|
|
|
|
|
while (local >= 0 && compiler->locals[local].depth >= depth)
|
2013-12-01 03:28:01 +01:00
|
|
|
{
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
// If the local was closed over, make sure the upvalue gets closed when it
|
2015-12-14 07:57:40 +01:00
|
|
|
// goes out of scope on the stack. We use emitByte() and not emitOp() here
|
|
|
|
|
// because we don't want to track that stack effect of these pops since the
|
|
|
|
|
// variables are still in scope after the break.
|
2014-01-06 17:01:04 +01:00
|
|
|
if (compiler->locals[local].isUpvalue)
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
{
|
2015-12-14 07:57:40 +01:00
|
|
|
emitByte(compiler, CODE_CLOSE_UPVALUE);
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2015-12-14 07:57:40 +01:00
|
|
|
emitByte(compiler, CODE_POP);
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
}
|
2015-12-14 07:57:40 +01:00
|
|
|
|
2014-01-06 17:01:04 +01:00
|
|
|
|
|
|
|
|
local--;
|
2013-12-01 03:28:01 +01:00
|
|
|
}
|
|
|
|
|
|
2014-01-06 17:01:04 +01:00
|
|
|
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)
|
|
|
|
|
{
|
2015-12-14 07:57:40 +01:00
|
|
|
int popped = discardLocals(compiler, compiler->scopeDepth);
|
|
|
|
|
compiler->numLocals -= popped;
|
|
|
|
|
compiler->numSlots -= popped;
|
2013-12-01 03:28:01 +01:00
|
|
|
compiler->scopeDepth--;
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-23 05:46:12 +01:00
|
|
|
// Attempts to look up the name in the local variables of [compiler]. If found,
|
|
|
|
|
// returns its index, otherwise returns -1.
|
|
|
|
|
static int resolveLocal(Compiler* compiler, const char* name, int length)
|
2013-12-01 03:28:01 +01:00
|
|
|
{
|
|
|
|
|
// Look it up in the local scopes. Look in reverse order so that the most
|
|
|
|
|
// nested variable is found first and shadows outer ones.
|
|
|
|
|
for (int i = compiler->numLocals - 1; i >= 0; i--)
|
|
|
|
|
{
|
2013-12-23 05:46:12 +01:00
|
|
|
if (compiler->locals[i].length == length &&
|
2016-02-12 16:27:42 +01:00
|
|
|
memcmp(name, compiler->locals[i].name, length) == 0)
|
2013-12-01 03:28:01 +01:00
|
|
|
{
|
|
|
|
|
return i;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
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
|
2015-12-27 05:12:35 +01:00
|
|
|
// index of the upvalue.
|
2013-12-15 08:41:23 +01:00
|
|
|
static int addUpvalue(Compiler* compiler, bool isLocal, int index)
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
{
|
|
|
|
|
// Look for an existing one.
|
2016-03-04 16:49:34 +01:00
|
|
|
for (int i = 0; i < compiler->fn->numUpvalues; i++)
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
{
|
|
|
|
|
CompilerUpvalue* upvalue = &compiler->upvalues[i];
|
|
|
|
|
if (upvalue->index == index && upvalue->isLocal == isLocal) return i;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If we got here, it's a new upvalue.
|
2016-03-04 16:49:34 +01:00
|
|
|
compiler->upvalues[compiler->fn->numUpvalues].isLocal = isLocal;
|
|
|
|
|
compiler->upvalues[compiler->fn->numUpvalues].index = index;
|
|
|
|
|
return compiler->fn->numUpvalues++;
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
}
|
|
|
|
|
|
2013-12-23 05:46:12 +01:00
|
|
|
// Attempts to look up [name] 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.
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
//
|
|
|
|
|
// 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.
|
2015-01-14 06:36:42 +01:00
|
|
|
//
|
|
|
|
|
// If it reaches a method boundary, this stops and returns -1 since methods do
|
|
|
|
|
// not close over local variables.
|
2013-12-23 05:46:12 +01:00
|
|
|
static int findUpvalue(Compiler* compiler, const char* name, int length)
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
{
|
2015-10-05 16:44:51 +02:00
|
|
|
// If we are at the top level, we didn't find it.
|
|
|
|
|
if (compiler->parent == NULL) return -1;
|
|
|
|
|
|
|
|
|
|
// If we hit the method boundary (and the name isn't a static field), then
|
|
|
|
|
// stop looking for it. We'll instead treat it as a self send.
|
|
|
|
|
if (name[0] != '_' && compiler->parent->enclosingClass != NULL) return -1;
|
|
|
|
|
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
// See if it's a local variable in the immediately enclosing function.
|
2013-12-23 05:46:12 +01:00
|
|
|
int local = resolveLocal(compiler->parent, name, length);
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
if (local != -1)
|
|
|
|
|
{
|
|
|
|
|
// Mark the local as an upvalue so we know to close it when it goes out of
|
|
|
|
|
// scope.
|
2013-12-15 08:41:23 +01:00
|
|
|
compiler->parent->locals[local].isUpvalue = true;
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
|
2013-12-20 16:05:31 +01:00
|
|
|
return addUpvalue(compiler, true, local);
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// See if it's an upvalue in the immediately enclosing function. In other
|
2015-01-14 06:36:42 +01:00
|
|
|
// words, if it's a local variable in a non-immediately enclosing function.
|
|
|
|
|
// This "flattens" closures automatically: it adds 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.
|
2013-12-23 05:46:12 +01:00
|
|
|
int upvalue = findUpvalue(compiler->parent, name, length);
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
if (upvalue != -1)
|
|
|
|
|
{
|
2013-12-20 16:05:31 +01:00
|
|
|
return addUpvalue(compiler, false, upvalue);
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If we got here, we walked all the way up the parent chain and couldn't
|
|
|
|
|
// find it.
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-12 16:16:41 +01:00
|
|
|
// Look up [name] in the current scope to see what variable it refers to.
|
|
|
|
|
// Returns the variable either in local scope, or the enclosing function's
|
|
|
|
|
// upvalue list. Does not search the module scope. Returns a variable with
|
|
|
|
|
// index -1 if not found.
|
|
|
|
|
static Variable resolveNonmodule(Compiler* compiler,
|
|
|
|
|
const char* name, int length)
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
{
|
2016-02-12 16:16:41 +01:00
|
|
|
// Look it up in the local scopes.
|
|
|
|
|
Variable variable;
|
|
|
|
|
variable.scope = SCOPE_LOCAL;
|
|
|
|
|
variable.index = resolveLocal(compiler, name, length);
|
|
|
|
|
if (variable.index != -1) return variable;
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
|
2016-02-12 16:16:41 +01:00
|
|
|
// Tt's not a local, so guess that it's an upvalue.
|
|
|
|
|
variable.scope = SCOPE_UPVALUE;
|
|
|
|
|
variable.index = findUpvalue(compiler, name, length);
|
|
|
|
|
return variable;
|
2015-01-14 06:36:42 +01:00
|
|
|
}
|
|
|
|
|
|
2016-02-12 16:16:41 +01:00
|
|
|
// Look up [name] in the current scope to see what variable it refers to.
|
|
|
|
|
// Returns the variable either in module scope, local scope, or the enclosing
|
|
|
|
|
// function's upvalue list. Returns a variable with index -1 if not found.
|
|
|
|
|
static Variable resolveName(Compiler* compiler, const char* name, int length)
|
2015-01-14 06:36:42 +01:00
|
|
|
{
|
2016-02-12 16:16:41 +01:00
|
|
|
Variable variable = resolveNonmodule(compiler, name, length);
|
|
|
|
|
if (variable.index != -1) return variable;
|
2013-12-01 23:59:56 +01:00
|
|
|
|
2016-02-12 16:16:41 +01:00
|
|
|
variable.scope = SCOPE_MODULE;
|
|
|
|
|
variable.index = wrenSymbolTableFind(&compiler->parser->module->variableNames,
|
|
|
|
|
name, length);
|
|
|
|
|
return variable;
|
2013-12-01 03:28:01 +01:00
|
|
|
}
|
|
|
|
|
|
2014-12-07 04:59:11 +01:00
|
|
|
static void loadLocal(Compiler* compiler, int slot)
|
|
|
|
|
{
|
|
|
|
|
if (slot <= 8)
|
|
|
|
|
{
|
2015-12-14 07:57:40 +01:00
|
|
|
emitOp(compiler, (Code)(CODE_LOAD_LOCAL_0 + slot));
|
2014-12-07 04:59:11 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-16 00:59:14 +01:00
|
|
|
emitByteArg(compiler, CODE_LOAD_LOCAL, slot);
|
2014-12-07 04:59:11 +01:00
|
|
|
}
|
|
|
|
|
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
// 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.
|
2014-01-01 22:25:24 +01:00
|
|
|
static ObjFn* endCompiler(Compiler* compiler,
|
|
|
|
|
const char* debugName, int debugNameLength)
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
{
|
2016-03-04 16:49:34 +01:00
|
|
|
// If we hit an error, don't finish the function since it's borked anyway.
|
2013-12-27 02:28:33 +01:00
|
|
|
if (compiler->parser->hasError)
|
|
|
|
|
{
|
2016-03-04 16:49:34 +01:00
|
|
|
compiler->parser->vm->compiler = compiler->parent;
|
2013-12-27 02:28:33 +01:00
|
|
|
return NULL;
|
|
|
|
|
}
|
2013-12-27 01:58:03 +01:00
|
|
|
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
// 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.
|
2015-12-14 07:57:40 +01:00
|
|
|
emitOp(compiler, CODE_END);
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
|
2016-03-04 16:49:34 +01:00
|
|
|
wrenFunctionBindName(compiler->parser->vm, compiler->fn,
|
|
|
|
|
debugName, debugNameLength);
|
|
|
|
|
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
// In the function that contains this one, load the resulting function object.
|
|
|
|
|
if (compiler->parent != NULL)
|
|
|
|
|
{
|
2016-03-04 16:49:34 +01:00
|
|
|
int constant = addConstant(compiler->parent, OBJ_VAL(compiler->fn));
|
2013-12-27 01:58:03 +01:00
|
|
|
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
// If the function has no upvalues, we don't need to create a closure.
|
|
|
|
|
// We can just load and run the function directly.
|
2016-03-04 16:49:34 +01:00
|
|
|
if (compiler->fn->numUpvalues == 0)
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
{
|
2015-01-16 00:59:14 +01:00
|
|
|
emitShortArg(compiler->parent, CODE_CONSTANT, constant);
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Capture the upvalues in the new closure object.
|
2015-01-16 00:59:14 +01:00
|
|
|
emitShortArg(compiler->parent, CODE_CLOSURE, constant);
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
|
|
|
|
|
// Emit arguments for each upvalue to know whether to capture a local or
|
|
|
|
|
// an upvalue.
|
2016-03-04 16:49:34 +01:00
|
|
|
for (int i = 0; i < compiler->fn->numUpvalues; i++)
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
{
|
2015-12-14 07:57:40 +01:00
|
|
|
emitByte(compiler->parent, compiler->upvalues[i].isLocal ? 1 : 0);
|
|
|
|
|
emitByte(compiler->parent, compiler->upvalues[i].index);
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2013-12-27 01:58:03 +01:00
|
|
|
|
|
|
|
|
// Pop this compiler off the stack.
|
2016-03-04 02:48:47 +01:00
|
|
|
compiler->parser->vm->compiler = compiler->parent;
|
2016-03-04 16:49:34 +01:00
|
|
|
|
2014-04-07 16:45:09 +02:00
|
|
|
#if WREN_DEBUG_DUMP_COMPILED_CODE
|
2016-03-04 16:49:34 +01:00
|
|
|
wrenDumpCode(compiler->parser->vm, compiler->fn);
|
2014-01-06 17:01:04 +01:00
|
|
|
#endif
|
|
|
|
|
|
2016-03-04 16:49:34 +01:00
|
|
|
return compiler->fn;
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
}
|
|
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
// Grammar ---------------------------------------------------------------------
|
|
|
|
|
|
feat: add `is` operator for type checking and refactor primitives registration
Add TOKEN_IS token and CODE_IS bytecode to support runtime type checking via the `is` keyword. Refactor primitive registration by replacing `registerPrimitives` with `loadCore`, which compiles a core library string to define built-in classes (Bool, Class, Function, Num, Null, String, IO) and then attaches primitive methods to them. Update Primitive typedef to remove unused numArgs parameter, and add findGlobal helper to retrieve globals by name. Include test files for class syntax, class instantiation, and the new `is` operator.
2013-11-08 02:07:32 +01:00
|
|
|
typedef enum
|
2013-11-07 16:04:25 +01:00
|
|
|
{
|
|
|
|
|
PREC_NONE,
|
|
|
|
|
PREC_LOWEST,
|
2015-02-23 17:28:43 +01:00
|
|
|
PREC_ASSIGNMENT, // =
|
2016-03-04 01:30:48 +01:00
|
|
|
PREC_CONDITIONAL, // ?:
|
2015-02-25 16:04:02 +01:00
|
|
|
PREC_LOGICAL_OR, // ||
|
|
|
|
|
PREC_LOGICAL_AND, // &&
|
2015-02-23 17:28:43 +01:00
|
|
|
PREC_EQUALITY, // == !=
|
|
|
|
|
PREC_IS, // is
|
|
|
|
|
PREC_COMPARISON, // < > <= >=
|
2015-02-25 16:02:29 +01:00
|
|
|
PREC_BITWISE_OR, // |
|
|
|
|
|
PREC_BITWISE_XOR, // ^
|
|
|
|
|
PREC_BITWISE_AND, // &
|
2015-02-23 17:28:43 +01:00
|
|
|
PREC_BITWISE_SHIFT, // << >>
|
2015-02-25 16:02:29 +01:00
|
|
|
PREC_RANGE, // .. ...
|
2015-02-23 17:28:43 +01:00
|
|
|
PREC_TERM, // + -
|
|
|
|
|
PREC_FACTOR, // * / %
|
|
|
|
|
PREC_UNARY, // unary - ! ~
|
|
|
|
|
PREC_CALL, // . () []
|
2015-01-25 08:21:50 +01:00
|
|
|
PREC_PRIMARY
|
feat: add `is` operator for type checking and refactor primitives registration
Add TOKEN_IS token and CODE_IS bytecode to support runtime type checking via the `is` keyword. Refactor primitive registration by replacing `registerPrimitives` with `loadCore`, which compiles a core library string to define built-in classes (Bool, Class, Function, Num, Null, String, IO) and then attaches primitive methods to them. Update Primitive typedef to remove unused numArgs parameter, and add findGlobal helper to retrieve globals by name. Include test files for class syntax, class instantiation, and the new `is` operator.
2013-11-08 02:07:32 +01:00
|
|
|
} Precedence;
|
|
|
|
|
|
2016-03-04 01:30:48 +01:00
|
|
|
typedef void (*GrammarFn)(Compiler*, bool canAssign);
|
2013-11-07 16:04:25 +01:00
|
|
|
|
2015-02-27 06:56:15 +01:00
|
|
|
typedef void (*SignatureFn)(Compiler* compiler, Signature* signature);
|
2013-11-14 19:27:33 +01:00
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
typedef struct
|
|
|
|
|
{
|
2013-11-13 16:10:52 +01:00
|
|
|
GrammarFn prefix;
|
|
|
|
|
GrammarFn infix;
|
2013-11-14 19:27:33 +01:00
|
|
|
SignatureFn method;
|
feat: add `is` operator for type checking and refactor primitives registration
Add TOKEN_IS token and CODE_IS bytecode to support runtime type checking via the `is` keyword. Refactor primitive registration by replacing `registerPrimitives` with `loadCore`, which compiles a core library string to define built-in classes (Bool, Class, Function, Num, Null, String, IO) and then attaches primitive methods to them. Update Primitive typedef to remove unused numArgs parameter, and add findGlobal helper to retrieve globals by name. Include test files for class syntax, class instantiation, and the new `is` operator.
2013-11-08 02:07:32 +01:00
|
|
|
Precedence precedence;
|
2013-11-07 16:04:25 +01:00
|
|
|
const char* name;
|
2013-11-13 16:10:52 +01:00
|
|
|
} GrammarRule;
|
2013-11-07 16:04:25 +01:00
|
|
|
|
2015-01-16 00:59:14 +01:00
|
|
|
// Forward declarations since the grammar is recursive.
|
|
|
|
|
static GrammarRule* getRule(TokenType type);
|
|
|
|
|
static void expression(Compiler* compiler);
|
|
|
|
|
static void statement(Compiler* compiler);
|
|
|
|
|
static void definition(Compiler* compiler);
|
2016-03-04 01:30:48 +01:00
|
|
|
static void parsePrecedence(Compiler* compiler, Precedence precedence);
|
2013-11-07 16:04:25 +01:00
|
|
|
|
2013-11-19 16:35:25 +01:00
|
|
|
// Replaces the placeholder argument for a previous CODE_JUMP or CODE_JUMP_IF
|
|
|
|
|
// instruction with an offset that jumps to the current end of bytecode.
|
|
|
|
|
static void patchJump(Compiler* compiler, int offset)
|
|
|
|
|
{
|
2014-04-07 03:59:53 +02:00
|
|
|
// -2 to adjust for the bytecode for the jump offset itself.
|
2016-03-04 16:49:34 +01:00
|
|
|
int jump = compiler->fn->code.count - offset - 2;
|
2016-02-19 15:57:38 +01:00
|
|
|
if (jump > MAX_JUMP) error(compiler, "Too much code to jump over.");
|
|
|
|
|
|
2016-03-04 16:49:34 +01:00
|
|
|
compiler->fn->code.data[offset] = (jump >> 8) & 0xff;
|
|
|
|
|
compiler->fn->code.data[offset + 1] = jump & 0xff;
|
2013-11-19 16:35:25 +01:00
|
|
|
}
|
|
|
|
|
|
2013-11-20 05:54:47 +01:00
|
|
|
// Parses a block body, after the initial "{" has been consumed.
|
2014-04-03 16:48:19 +02:00
|
|
|
//
|
2015-04-01 16:05:40 +02:00
|
|
|
// Returns true if it was a expression body, false if it was a statement body.
|
|
|
|
|
// (More precisely, returns true if a value was left on the stack. An empty
|
|
|
|
|
// block returns false.)
|
2014-04-03 16:48:19 +02:00
|
|
|
static bool finishBlock(Compiler* compiler)
|
2013-11-20 05:54:47 +01:00
|
|
|
{
|
2013-12-29 19:46:21 +01:00
|
|
|
// Empty blocks do nothing.
|
2014-04-03 16:48:19 +02:00
|
|
|
if (match(compiler, TOKEN_RIGHT_BRACE)) {
|
2015-04-01 16:05:40 +02:00
|
|
|
return false;
|
2014-04-03 16:48:19 +02:00
|
|
|
}
|
2013-12-29 19:46:21 +01:00
|
|
|
|
2014-04-03 16:48:19 +02:00
|
|
|
// If there's no line after the "{", it's a single-expression body.
|
refactor: move newline handling from lexer into parser grammar rules
This change shifts newline tokenization from the lexer's readRawToken into the parser's nextToken, allowing the grammar to selectively ignore newlines in specific contexts (e.g., after operators like "|", "is", "for", "if", "while") while treating them as errors in others (e.g., after "var", "new", "class", "static", before commas in lists). The lexer now emits TOKEN_LINE for semicolons and newlines uniformly, and the parser decides which to elide. Test files are updated to reflect the new error expectations and to remove tests for newlines that are now grammatically allowed.
2014-04-04 16:51:18 +02:00
|
|
|
if (!matchLine(compiler))
|
2014-04-03 16:48:19 +02:00
|
|
|
{
|
|
|
|
|
expression(compiler);
|
|
|
|
|
consume(compiler, TOKEN_RIGHT_BRACE, "Expect '}' at end of block.");
|
2015-04-01 16:05:40 +02:00
|
|
|
return true;
|
2014-04-03 16:48:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Empty blocks (with just a newline inside) do nothing.
|
|
|
|
|
if (match(compiler, TOKEN_RIGHT_BRACE)) {
|
2015-04-01 16:05:40 +02:00
|
|
|
return false;
|
2014-04-03 16:48:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Compile the definition list.
|
|
|
|
|
do
|
2013-11-20 05:54:47 +01:00
|
|
|
{
|
2013-12-24 19:27:48 +01:00
|
|
|
definition(compiler);
|
2013-11-20 05:54:47 +01:00
|
|
|
|
2014-04-03 16:48:19 +02:00
|
|
|
// If we got into a weird error state, don't get stuck in a loop.
|
|
|
|
|
if (peek(compiler) == TOKEN_EOF) return true;
|
|
|
|
|
|
refactor: move newline handling from lexer into parser grammar rules
This change shifts newline tokenization from the lexer's readRawToken into the parser's nextToken, allowing the grammar to selectively ignore newlines in specific contexts (e.g., after operators like "|", "is", "for", "if", "while") while treating them as errors in others (e.g., after "var", "new", "class", "static", before commas in lists). The lexer now emits TOKEN_LINE for semicolons and newlines uniformly, and the parser decides which to elide. Test files are updated to reflect the new error expectations and to remove tests for newlines that are now grammatically allowed.
2014-04-04 16:51:18 +02:00
|
|
|
consumeLine(compiler, "Expect newline after statement.");
|
2014-04-03 16:48:19 +02:00
|
|
|
}
|
|
|
|
|
while (!match(compiler, TOKEN_RIGHT_BRACE));
|
2015-04-01 16:05:40 +02:00
|
|
|
return false;
|
2014-04-03 16:48:19 +02:00
|
|
|
}
|
2013-11-20 05:54:47 +01:00
|
|
|
|
2014-04-03 16:48:19 +02:00
|
|
|
// Parses a method or function body, after the initial "{" has been consumed.
|
2015-07-10 18:18:22 +02:00
|
|
|
//
|
|
|
|
|
// It [isInitializer] is `true`, this is the body of a constructor initializer.
|
|
|
|
|
// In that case, this adds the code to ensure it returns `this`.
|
|
|
|
|
static void finishBody(Compiler* compiler, bool isInitializer)
|
2014-04-03 16:48:19 +02:00
|
|
|
{
|
2015-04-01 16:05:40 +02:00
|
|
|
bool isExpressionBody = finishBlock(compiler);
|
2014-04-03 16:48:19 +02:00
|
|
|
|
2015-07-10 18:18:22 +02:00
|
|
|
if (isInitializer)
|
2014-04-03 16:48:19 +02:00
|
|
|
{
|
2015-07-10 18:18:22 +02:00
|
|
|
// If the initializer body evaluates to a value, discard it.
|
2015-12-14 07:57:40 +01:00
|
|
|
if (isExpressionBody) emitOp(compiler, CODE_POP);
|
2014-04-03 16:48:19 +02:00
|
|
|
|
|
|
|
|
// The receiver is always stored in the first local slot.
|
2015-12-14 07:57:40 +01:00
|
|
|
emitOp(compiler, CODE_LOAD_LOCAL_0);
|
2013-11-20 05:54:47 +01:00
|
|
|
}
|
2015-04-01 16:05:40 +02:00
|
|
|
else if (!isExpressionBody)
|
2014-04-03 16:48:19 +02:00
|
|
|
{
|
|
|
|
|
// Implicitly return null in statement bodies.
|
2015-12-14 07:57:40 +01:00
|
|
|
emitOp(compiler, CODE_NULL);
|
2014-04-03 16:48:19 +02:00
|
|
|
}
|
|
|
|
|
|
2015-12-14 07:57:40 +01:00
|
|
|
emitOp(compiler, CODE_RETURN);
|
2013-11-20 05:54:47 +01:00
|
|
|
}
|
|
|
|
|
|
2013-12-21 18:37:59 +01:00
|
|
|
// The VM can only handle a certain number of parameters, so check that we
|
|
|
|
|
// haven't exceeded that and give a usable error.
|
|
|
|
|
static void validateNumParameters(Compiler* compiler, int numArgs)
|
|
|
|
|
{
|
|
|
|
|
if (numArgs == MAX_PARAMETERS + 1)
|
|
|
|
|
{
|
|
|
|
|
// Only show an error at exactly max + 1 so that we can keep parsing the
|
|
|
|
|
// parameters and minimize cascaded errors.
|
|
|
|
|
error(compiler, "Methods cannot have more than %d parameters.",
|
|
|
|
|
MAX_PARAMETERS);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-27 15:51:37 +01:00
|
|
|
// Parses the rest of a comma-separated parameter list after the opening
|
|
|
|
|
// delimeter. Updates `arity` in [signature] with the number of parameters.
|
2015-04-06 15:53:53 +02:00
|
|
|
static void finishParameterList(Compiler* compiler, Signature* signature)
|
2013-11-15 03:25:28 +01:00
|
|
|
{
|
2014-02-04 18:34:05 +01:00
|
|
|
do
|
2013-11-15 03:25:28 +01:00
|
|
|
{
|
refactor: move newline handling from lexer into parser grammar rules
This change shifts newline tokenization from the lexer's readRawToken into the parser's nextToken, allowing the grammar to selectively ignore newlines in specific contexts (e.g., after operators like "|", "is", "for", "if", "while") while treating them as errors in others (e.g., after "var", "new", "class", "static", before commas in lists). The lexer now emits TOKEN_LINE for semicolons and newlines uniformly, and the parser decides which to elide. Test files are updated to reflect the new error expectations and to remove tests for newlines that are now grammatically allowed.
2014-04-04 16:51:18 +02:00
|
|
|
ignoreNewlines(compiler);
|
2015-02-27 06:56:15 +01:00
|
|
|
validateNumParameters(compiler, ++signature->arity);
|
2013-11-30 00:08:27 +01:00
|
|
|
|
2014-02-04 18:34:05 +01:00
|
|
|
// Define a local variable in the method for the parameter.
|
|
|
|
|
declareNamedVariable(compiler);
|
2013-11-15 03:25:28 +01:00
|
|
|
}
|
2014-02-04 18:34:05 +01:00
|
|
|
while (match(compiler, TOKEN_COMMA));
|
2015-01-21 03:25:54 +01:00
|
|
|
}
|
|
|
|
|
|
2015-02-28 22:31:15 +01:00
|
|
|
// Gets the symbol for a method [name] with [length].
|
refactor: extract SymbolTable into dedicated wren_utils module with prefixed API
Move SymbolTable struct and its associated functions (initSymbolTable, clearSymbolTable, addSymbol, findSymbol, ensureSymbol, getSymbolName) from wren_vm.c and wren_value.h into new wren_utils.c and wren_utils.h files. Rename all functions with wrenSymbolTable prefix for consistent naming convention, update all call sites across compiler, core, debug, and VM files, and relocate MAX_SYMBOLS constant to wren_common.h.
2014-01-02 05:57:58 +01:00
|
|
|
static int methodSymbol(Compiler* compiler, const char* name, int length)
|
|
|
|
|
{
|
|
|
|
|
return wrenSymbolTableEnsure(compiler->parser->vm,
|
2014-04-07 03:59:53 +02:00
|
|
|
&compiler->parser->vm->methodNames, name, length);
|
refactor: extract SymbolTable into dedicated wren_utils module with prefixed API
Move SymbolTable struct and its associated functions (initSymbolTable, clearSymbolTable, addSymbol, findSymbol, ensureSymbol, getSymbolName) from wren_vm.c and wren_value.h into new wren_utils.c and wren_utils.h files. Rename all functions with wrenSymbolTable prefix for consistent naming convention, update all call sites across compiler, core, debug, and VM files, and relocate MAX_SYMBOLS constant to wren_common.h.
2014-01-02 05:57:58 +01:00
|
|
|
}
|
|
|
|
|
|
2015-02-27 06:56:15 +01:00
|
|
|
// Appends characters to [name] (and updates [length]) for [numParams] "_"
|
|
|
|
|
// surrounded by [leftBracket] and [rightBracket].
|
|
|
|
|
static void signatureParameterList(char name[MAX_METHOD_SIGNATURE], int* length,
|
|
|
|
|
int numParams, char leftBracket, char rightBracket)
|
|
|
|
|
{
|
|
|
|
|
name[(*length)++] = leftBracket;
|
|
|
|
|
for (int i = 0; i < numParams; i++)
|
|
|
|
|
{
|
|
|
|
|
if (i > 0) name[(*length)++] = ',';
|
|
|
|
|
name[(*length)++] = '_';
|
|
|
|
|
}
|
|
|
|
|
name[(*length)++] = rightBracket;
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-27 16:10:44 +01:00
|
|
|
// Fills [name] with the stringified version of [signature] and updates
|
|
|
|
|
// [length] to the resulting length.
|
|
|
|
|
static void signatureToString(Signature* signature,
|
|
|
|
|
char name[MAX_METHOD_SIGNATURE], int* length)
|
2015-02-27 06:56:15 +01:00
|
|
|
{
|
2015-07-10 18:18:22 +02:00
|
|
|
*length = 0;
|
|
|
|
|
|
2015-02-27 06:56:15 +01:00
|
|
|
// Build the full name from the signature.
|
2015-07-10 18:18:22 +02:00
|
|
|
memcpy(name + *length, signature->name, signature->length);
|
|
|
|
|
*length += signature->length;
|
2015-02-27 06:56:15 +01:00
|
|
|
|
|
|
|
|
switch (signature->type)
|
|
|
|
|
{
|
|
|
|
|
case SIG_METHOD:
|
2015-02-27 16:10:44 +01:00
|
|
|
signatureParameterList(name, length, signature->arity, '(', ')');
|
2015-02-27 06:56:15 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case SIG_GETTER:
|
|
|
|
|
// The signature is just the name.
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case SIG_SETTER:
|
2015-02-27 16:10:44 +01:00
|
|
|
name[(*length)++] = '=';
|
|
|
|
|
signatureParameterList(name, length, 1, '(', ')');
|
2015-02-27 06:56:15 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case SIG_SUBSCRIPT:
|
2015-02-27 16:10:44 +01:00
|
|
|
signatureParameterList(name, length, signature->arity, '[', ']');
|
2015-02-27 06:56:15 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case SIG_SUBSCRIPT_SETTER:
|
2015-02-27 16:10:44 +01:00
|
|
|
signatureParameterList(name, length, signature->arity - 1, '[', ']');
|
|
|
|
|
name[(*length)++] = '=';
|
|
|
|
|
signatureParameterList(name, length, 1, '(', ')');
|
2015-02-27 06:56:15 +01:00
|
|
|
break;
|
2015-07-10 18:18:22 +02:00
|
|
|
|
|
|
|
|
case SIG_INITIALIZER:
|
2015-08-21 07:30:44 +02:00
|
|
|
memcpy(name, "init ", 5);
|
2015-07-10 18:18:22 +02:00
|
|
|
memcpy(name + 5, signature->name, signature->length);
|
|
|
|
|
*length = 5 + signature->length;
|
|
|
|
|
signatureParameterList(name, length, signature->arity, '(', ')');
|
|
|
|
|
break;
|
2015-02-27 06:56:15 +01:00
|
|
|
}
|
|
|
|
|
|
2015-02-27 16:10:44 +01:00
|
|
|
name[*length] = '\0';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Gets the symbol for a method with [signature].
|
|
|
|
|
static int signatureSymbol(Compiler* compiler, Signature* signature)
|
|
|
|
|
{
|
|
|
|
|
// Build the full name from the signature.
|
|
|
|
|
char name[MAX_METHOD_SIGNATURE];
|
|
|
|
|
int length;
|
|
|
|
|
signatureToString(signature, name, &length);
|
2015-02-27 06:56:15 +01:00
|
|
|
|
|
|
|
|
return methodSymbol(compiler, name, length);
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-06 16:34:19 +02:00
|
|
|
// Returns a signature with [type] whose name is from the last consumed token.
|
|
|
|
|
static Signature signatureFromToken(Compiler* compiler, SignatureType type)
|
2015-02-27 06:56:15 +01:00
|
|
|
{
|
2015-08-06 16:34:19 +02:00
|
|
|
Signature signature;
|
|
|
|
|
|
2015-02-27 06:56:15 +01:00
|
|
|
// Get the token for the method name.
|
|
|
|
|
Token* token = &compiler->parser->previous;
|
2015-08-06 16:34:19 +02:00
|
|
|
signature.name = token->start;
|
|
|
|
|
signature.length = token->length;
|
|
|
|
|
signature.type = type;
|
|
|
|
|
signature.arity = 0;
|
2015-02-27 06:56:15 +01:00
|
|
|
|
2015-08-06 16:34:19 +02:00
|
|
|
if (signature.length > MAX_METHOD_NAME)
|
2015-02-27 06:56:15 +01:00
|
|
|
{
|
|
|
|
|
error(compiler, "Method names cannot be longer than %d characters.",
|
|
|
|
|
MAX_METHOD_NAME);
|
2015-08-06 16:34:19 +02:00
|
|
|
signature.length = MAX_METHOD_NAME;
|
2015-02-27 06:56:15 +01:00
|
|
|
}
|
2015-08-06 16:34:19 +02:00
|
|
|
|
|
|
|
|
return signature;
|
2015-02-27 06:56:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Parses a comma-separated list of arguments. Modifies [signature] to include
|
|
|
|
|
// the arity of the argument list.
|
|
|
|
|
static void finishArgumentList(Compiler* compiler, Signature* signature)
|
2015-02-22 19:42:49 +01:00
|
|
|
{
|
|
|
|
|
do
|
|
|
|
|
{
|
|
|
|
|
ignoreNewlines(compiler);
|
2015-02-27 06:56:15 +01:00
|
|
|
validateNumParameters(compiler, ++signature->arity);
|
2015-02-22 19:42:49 +01:00
|
|
|
expression(compiler);
|
|
|
|
|
}
|
|
|
|
|
while (match(compiler, TOKEN_COMMA));
|
|
|
|
|
|
|
|
|
|
// Allow a newline before the closing delimiter.
|
|
|
|
|
ignoreNewlines(compiler);
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-27 06:56:15 +01:00
|
|
|
// Compiles a method call with [signature] using [instruction].
|
|
|
|
|
static void callSignature(Compiler* compiler, Code instruction,
|
|
|
|
|
Signature* signature)
|
2015-02-22 20:04:43 +01:00
|
|
|
{
|
2015-02-27 06:56:15 +01:00
|
|
|
int symbol = signatureSymbol(compiler, signature);
|
|
|
|
|
emitShortArg(compiler, (Code)(instruction + signature->arity), symbol);
|
2015-06-02 16:33:39 +02:00
|
|
|
|
|
|
|
|
if (instruction == CODE_SUPER_0)
|
|
|
|
|
{
|
|
|
|
|
// Super calls need to be statically bound to the class's superclass. This
|
|
|
|
|
// ensures we call the right method even when a method containing a super
|
|
|
|
|
// call is inherited by another subclass.
|
|
|
|
|
//
|
|
|
|
|
// We bind it at class definition time by storing a reference to the
|
|
|
|
|
// superclass in a constant. So, here, we create a slot in the constant
|
|
|
|
|
// table and store NULL in it. When the method is bound, we'll look up the
|
|
|
|
|
// superclass then and store it in the constant slot.
|
2015-08-22 07:54:30 +02:00
|
|
|
emitShort(compiler, addConstant(compiler, NULL_VAL));
|
2015-06-02 16:33:39 +02:00
|
|
|
}
|
2015-02-22 20:04:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Compiles a method call with [numArgs] for a method with [name] with [length].
|
|
|
|
|
static void callMethod(Compiler* compiler, int numArgs, const char* name,
|
|
|
|
|
int length)
|
|
|
|
|
{
|
2015-02-27 06:56:15 +01:00
|
|
|
int symbol = methodSymbol(compiler, name, length);
|
|
|
|
|
emitShortArg(compiler, (Code)(CODE_CALL_0 + numArgs), symbol);
|
2015-02-22 20:04:43 +01:00
|
|
|
}
|
|
|
|
|
|
2015-07-10 18:18:22 +02:00
|
|
|
// Compiles an (optional) argument list for a method call with [methodSignature]
|
|
|
|
|
// and then calls it.
|
2013-12-19 16:02:27 +01:00
|
|
|
static void methodCall(Compiler* compiler, Code instruction,
|
2015-08-06 16:34:19 +02:00
|
|
|
Signature* signature)
|
2013-12-13 17:37:49 +01:00
|
|
|
{
|
2015-07-10 18:18:22 +02:00
|
|
|
// Make a new signature that contains the updated arity and type based on
|
|
|
|
|
// the arguments we find.
|
2015-08-06 16:34:19 +02:00
|
|
|
Signature called = { signature->name, signature->length, SIG_GETTER, 0 };
|
2015-02-27 06:56:15 +01:00
|
|
|
|
2013-12-13 17:37:49 +01:00
|
|
|
// Parse the argument list, if any.
|
|
|
|
|
if (match(compiler, TOKEN_LEFT_PAREN))
|
|
|
|
|
{
|
2015-08-06 16:34:19 +02:00
|
|
|
called.type = SIG_METHOD;
|
2015-02-27 08:08:36 +01:00
|
|
|
|
|
|
|
|
// Allow empty an argument list.
|
|
|
|
|
if (peek(compiler) != TOKEN_RIGHT_PAREN)
|
|
|
|
|
{
|
2015-08-06 16:34:19 +02:00
|
|
|
finishArgumentList(compiler, &called);
|
2015-02-27 08:08:36 +01:00
|
|
|
}
|
2013-12-13 17:37:49 +01:00
|
|
|
consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after arguments.");
|
|
|
|
|
}
|
|
|
|
|
|
2014-04-03 02:26:39 +02:00
|
|
|
// Parse the block argument, if any.
|
|
|
|
|
if (match(compiler, TOKEN_LEFT_BRACE))
|
|
|
|
|
{
|
2015-02-22 20:04:43 +01:00
|
|
|
// Include the block argument in the arity.
|
2015-08-06 16:34:19 +02:00
|
|
|
called.type = SIG_METHOD;
|
|
|
|
|
called.arity++;
|
2014-04-03 02:26:39 +02:00
|
|
|
|
|
|
|
|
Compiler fnCompiler;
|
|
|
|
|
initCompiler(&fnCompiler, compiler->parser, compiler, true);
|
2015-02-27 06:56:15 +01:00
|
|
|
|
|
|
|
|
// Make a dummy signature to track the arity.
|
2015-08-06 16:34:19 +02:00
|
|
|
Signature fnSignature = { "", 0, SIG_METHOD, 0 };
|
2015-02-27 15:51:37 +01:00
|
|
|
|
|
|
|
|
// Parse the parameter list, if any.
|
|
|
|
|
if (match(compiler, TOKEN_PIPE))
|
|
|
|
|
{
|
|
|
|
|
finishParameterList(&fnCompiler, &fnSignature);
|
|
|
|
|
consume(compiler, TOKEN_PIPE, "Expect '|' after function parameters.");
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-04 16:49:34 +01:00
|
|
|
fnCompiler.fn->arity = fnSignature.arity;
|
2014-04-03 02:26:39 +02:00
|
|
|
|
2014-04-03 16:48:19 +02:00
|
|
|
finishBody(&fnCompiler, false);
|
2014-04-03 02:26:39 +02:00
|
|
|
|
2015-06-27 17:36:20 +02:00
|
|
|
// Name the function based on the method its passed to.
|
|
|
|
|
char blockName[MAX_METHOD_SIGNATURE + 15];
|
|
|
|
|
int blockLength;
|
2015-08-06 16:34:19 +02:00
|
|
|
signatureToString(&called, blockName, &blockLength);
|
2015-06-27 17:36:20 +02:00
|
|
|
memmove(blockName + blockLength, " block argument", 16);
|
|
|
|
|
|
|
|
|
|
endCompiler(&fnCompiler, blockName, blockLength + 15);
|
2014-04-03 02:26:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: Allow Grace-style mixfix methods?
|
|
|
|
|
|
2015-07-10 18:18:22 +02:00
|
|
|
// If this is a super() call for an initializer, make sure we got an actual
|
|
|
|
|
// argument list.
|
2015-08-06 16:34:19 +02:00
|
|
|
if (signature->type == SIG_INITIALIZER)
|
2015-07-10 18:18:22 +02:00
|
|
|
{
|
2015-08-06 16:34:19 +02:00
|
|
|
if (called.type != SIG_METHOD)
|
2015-07-10 18:18:22 +02:00
|
|
|
{
|
|
|
|
|
error(compiler, "A superclass constructor must have an argument list.");
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-06 16:34:19 +02:00
|
|
|
called.type = SIG_INITIALIZER;
|
2015-07-10 18:18:22 +02:00
|
|
|
}
|
|
|
|
|
|
2015-08-06 16:34:19 +02:00
|
|
|
callSignature(compiler, instruction, &called);
|
2013-12-13 17:37:49 +01:00
|
|
|
}
|
|
|
|
|
|
feat: make `this` the implicit receiver for method calls inside class bodies
In the compiler, when a bare name is encountered inside a class definition and it does not resolve to a local variable or global, emit an implicit `this.` load before the call, enabling getter, setter, and method calls without explicit receiver. Update all benchmark, builtin, and test files to remove redundant `this.` qualifiers, and add comprehensive test suites for implicit receiver behavior across instance, inherited, static, nested, and shadowing scenarios.
2014-02-13 02:33:35 +01:00
|
|
|
// Compiles a call whose name is the previously consumed token. This includes
|
|
|
|
|
// getters, method calls with arguments, and setter calls.
|
2016-03-04 01:30:48 +01:00
|
|
|
static void namedCall(Compiler* compiler, bool canAssign, Code instruction)
|
2013-12-19 16:02:27 +01:00
|
|
|
{
|
2015-02-27 06:56:15 +01:00
|
|
|
// Get the token for the method name.
|
2015-08-06 16:34:19 +02:00
|
|
|
Signature signature = signatureFromToken(compiler, SIG_GETTER);
|
2013-12-19 16:02:27 +01:00
|
|
|
|
2016-03-04 01:30:48 +01:00
|
|
|
if (canAssign && match(compiler, TOKEN_EQ))
|
2013-12-20 16:05:31 +01:00
|
|
|
{
|
refactor: move newline handling from lexer into parser grammar rules
This change shifts newline tokenization from the lexer's readRawToken into the parser's nextToken, allowing the grammar to selectively ignore newlines in specific contexts (e.g., after operators like "|", "is", "for", "if", "while") while treating them as errors in others (e.g., after "var", "new", "class", "static", before commas in lists). The lexer now emits TOKEN_LINE for semicolons and newlines uniformly, and the parser decides which to elide. Test files are updated to reflect the new error expectations and to remove tests for newlines that are now grammatically allowed.
2014-04-04 16:51:18 +02:00
|
|
|
ignoreNewlines(compiler);
|
|
|
|
|
|
2015-02-27 06:56:15 +01:00
|
|
|
// Build the setter signature.
|
|
|
|
|
signature.type = SIG_SETTER;
|
|
|
|
|
signature.arity = 1;
|
2013-12-20 16:05:31 +01:00
|
|
|
|
|
|
|
|
// Compile the assigned value.
|
|
|
|
|
expression(compiler);
|
2015-02-27 06:56:15 +01:00
|
|
|
callSignature(compiler, instruction, &signature);
|
2013-12-20 16:05:31 +01:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2015-07-10 18:18:22 +02:00
|
|
|
methodCall(compiler, instruction, &signature);
|
2013-12-20 16:05:31 +01:00
|
|
|
}
|
2013-12-19 16:02:27 +01:00
|
|
|
}
|
|
|
|
|
|
2016-02-12 16:16:41 +01:00
|
|
|
// Emits the code to load [variable] onto the stack.
|
|
|
|
|
static void loadVariable(Compiler* compiler, Variable variable)
|
|
|
|
|
{
|
|
|
|
|
switch (variable.scope)
|
|
|
|
|
{
|
|
|
|
|
case SCOPE_LOCAL:
|
|
|
|
|
loadLocal(compiler, variable.index);
|
|
|
|
|
break;
|
|
|
|
|
case SCOPE_UPVALUE:
|
|
|
|
|
emitByteArg(compiler, CODE_LOAD_UPVALUE, variable.index);
|
|
|
|
|
break;
|
|
|
|
|
case SCOPE_MODULE:
|
|
|
|
|
emitShortArg(compiler, CODE_LOAD_MODULE_VAR, variable.index);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
UNREACHABLE();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-23 20:17:40 +01:00
|
|
|
// Loads the receiver of the currently enclosing method. Correctly handles
|
|
|
|
|
// functions defined inside methods.
|
|
|
|
|
static void loadThis(Compiler* compiler)
|
|
|
|
|
{
|
2016-02-12 16:16:41 +01:00
|
|
|
loadVariable(compiler, resolveNonmodule(compiler, "this", 4));
|
2013-12-23 20:17:40 +01:00
|
|
|
}
|
|
|
|
|
|
2015-09-12 19:14:04 +02:00
|
|
|
// Pushes the value for a module-level variable implicitly imported from core.
|
|
|
|
|
static void loadCoreVariable(Compiler* compiler, const char* name)
|
|
|
|
|
{
|
|
|
|
|
int symbol = wrenSymbolTableFind(&compiler->parser->module->variableNames,
|
|
|
|
|
name, strlen(name));
|
|
|
|
|
ASSERT(symbol != -1, "Should have already defined core name.");
|
|
|
|
|
emitShortArg(compiler, CODE_LOAD_MODULE_VAR, symbol);
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-25 08:21:50 +01:00
|
|
|
// A parenthesized expression.
|
2016-03-04 01:30:48 +01:00
|
|
|
static void grouping(Compiler* compiler, bool canAssign)
|
2013-10-22 21:16:39 +02:00
|
|
|
{
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
expression(compiler);
|
2013-11-14 08:06:53 +01:00
|
|
|
consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after expression.");
|
2013-11-03 19:16:14 +01:00
|
|
|
}
|
2013-10-27 19:20:19 +01:00
|
|
|
|
2015-01-25 08:21:50 +01:00
|
|
|
// A list literal.
|
2016-03-04 01:30:48 +01:00
|
|
|
static void list(Compiler* compiler, bool canAssign)
|
2013-11-24 22:38:31 +01:00
|
|
|
{
|
2015-01-26 07:49:30 +01:00
|
|
|
// Instantiate a new list.
|
2015-09-12 19:14:04 +02:00
|
|
|
loadCoreVariable(compiler, "List");
|
2015-07-10 18:18:22 +02:00
|
|
|
callMethod(compiler, 0, "new()", 5);
|
2015-08-31 16:23:36 +02:00
|
|
|
|
2015-01-26 07:49:30 +01:00
|
|
|
// Compile the list elements. Each one compiles to a ".add()" call.
|
2015-08-31 16:23:36 +02:00
|
|
|
do
|
2013-11-24 22:38:31 +01:00
|
|
|
{
|
2015-08-31 16:23:36 +02:00
|
|
|
ignoreNewlines(compiler);
|
2015-01-26 07:49:30 +01:00
|
|
|
|
2015-08-31 16:23:36 +02:00
|
|
|
// Stop if we hit the end of the list.
|
|
|
|
|
if (peek(compiler) == TOKEN_RIGHT_BRACKET) break;
|
2015-05-30 22:18:37 +02:00
|
|
|
|
2015-08-31 16:23:36 +02:00
|
|
|
// The element.
|
|
|
|
|
expression(compiler);
|
2015-11-11 16:55:48 +01:00
|
|
|
callMethod(compiler, 1, "addCore_(_)", 11);
|
2015-08-31 16:23:36 +02:00
|
|
|
} while (match(compiler, TOKEN_COMMA));
|
2013-11-24 22:38:31 +01:00
|
|
|
|
refactor: move newline handling from lexer into parser grammar rules
This change shifts newline tokenization from the lexer's readRawToken into the parser's nextToken, allowing the grammar to selectively ignore newlines in specific contexts (e.g., after operators like "|", "is", "for", "if", "while") while treating them as errors in others (e.g., after "var", "new", "class", "static", before commas in lists). The lexer now emits TOKEN_LINE for semicolons and newlines uniformly, and the parser decides which to elide. Test files are updated to reflect the new error expectations and to remove tests for newlines that are now grammatically allowed.
2014-04-04 16:51:18 +02:00
|
|
|
// Allow newlines before the closing ']'.
|
|
|
|
|
ignoreNewlines(compiler);
|
2013-11-24 22:38:31 +01:00
|
|
|
consume(compiler, TOKEN_RIGHT_BRACKET, "Expect ']' after list elements.");
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-25 08:21:50 +01:00
|
|
|
// A map literal.
|
2016-03-04 01:30:48 +01:00
|
|
|
static void map(Compiler* compiler, bool canAssign)
|
2015-01-25 08:21:50 +01:00
|
|
|
{
|
|
|
|
|
// Instantiate a new map.
|
2015-09-12 19:14:04 +02:00
|
|
|
loadCoreVariable(compiler, "Map");
|
2015-07-10 18:18:22 +02:00
|
|
|
callMethod(compiler, 0, "new()", 5);
|
2015-01-25 08:21:50 +01:00
|
|
|
|
|
|
|
|
// Compile the map elements. Each one is compiled to just invoke the
|
|
|
|
|
// subscript setter on the map.
|
2015-08-31 16:23:36 +02:00
|
|
|
do
|
2015-01-25 08:21:50 +01:00
|
|
|
{
|
2015-08-31 16:23:36 +02:00
|
|
|
ignoreNewlines(compiler);
|
2015-05-30 22:17:58 +02:00
|
|
|
|
2015-08-31 16:23:36 +02:00
|
|
|
// Stop if we hit the end of the map.
|
|
|
|
|
if (peek(compiler) == TOKEN_RIGHT_BRACE) break;
|
2015-01-25 08:21:50 +01:00
|
|
|
|
2015-08-31 16:23:36 +02:00
|
|
|
// The key.
|
2016-03-04 01:30:48 +01:00
|
|
|
parsePrecedence(compiler, PREC_UNARY);
|
2015-08-31 16:23:36 +02:00
|
|
|
consume(compiler, TOKEN_COLON, "Expect ':' after map key.");
|
|
|
|
|
ignoreNewlines(compiler);
|
2015-01-25 08:21:50 +01:00
|
|
|
|
2015-08-31 16:23:36 +02:00
|
|
|
// The value.
|
|
|
|
|
expression(compiler);
|
2015-11-11 16:55:48 +01:00
|
|
|
callMethod(compiler, 2, "addCore_(_,_)", 13);
|
2015-08-31 16:23:36 +02:00
|
|
|
} while (match(compiler, TOKEN_COMMA));
|
2015-01-25 08:21:50 +01:00
|
|
|
|
|
|
|
|
// Allow newlines before the closing '}'.
|
|
|
|
|
ignoreNewlines(compiler);
|
|
|
|
|
consume(compiler, TOKEN_RIGHT_BRACE, "Expect '}' after map entries.");
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-13 16:10:52 +01:00
|
|
|
// Unary operators like `-foo`.
|
2016-03-04 01:30:48 +01:00
|
|
|
static void unaryOp(Compiler* compiler, bool canAssign)
|
2013-11-13 16:10:52 +01:00
|
|
|
{
|
2015-01-16 00:59:14 +01:00
|
|
|
GrammarRule* rule = getRule(compiler->parser->previous.type);
|
2013-11-13 16:10:52 +01:00
|
|
|
|
refactor: move newline handling from lexer into parser grammar rules
This change shifts newline tokenization from the lexer's readRawToken into the parser's nextToken, allowing the grammar to selectively ignore newlines in specific contexts (e.g., after operators like "|", "is", "for", "if", "while") while treating them as errors in others (e.g., after "var", "new", "class", "static", before commas in lists). The lexer now emits TOKEN_LINE for semicolons and newlines uniformly, and the parser decides which to elide. Test files are updated to reflect the new error expectations and to remove tests for newlines that are now grammatically allowed.
2014-04-04 16:51:18 +02:00
|
|
|
ignoreNewlines(compiler);
|
|
|
|
|
|
2013-11-13 16:10:52 +01:00
|
|
|
// Compile the argument.
|
2016-03-04 01:30:48 +01:00
|
|
|
parsePrecedence(compiler, (Precedence)(PREC_UNARY + 1));
|
2013-11-13 16:10:52 +01:00
|
|
|
|
|
|
|
|
// Call the operator method on the left-hand side.
|
2015-02-22 20:04:43 +01:00
|
|
|
callMethod(compiler, 0, rule->name, 1);
|
2013-11-13 16:10:52 +01:00
|
|
|
}
|
|
|
|
|
|
2016-03-04 01:30:48 +01:00
|
|
|
static void boolean(Compiler* compiler, bool canAssign)
|
2013-11-04 06:38:58 +01:00
|
|
|
{
|
2015-12-14 07:57:40 +01:00
|
|
|
emitOp(compiler,
|
|
|
|
|
compiler->parser->previous.type == TOKEN_FALSE ? CODE_FALSE : CODE_TRUE);
|
2013-11-04 06:38:58 +01:00
|
|
|
}
|
|
|
|
|
|
2014-01-19 22:01:51 +01:00
|
|
|
// Walks the compiler chain to find the compiler for the nearest class
|
|
|
|
|
// enclosing this one. Returns NULL if not currently inside a class definition.
|
|
|
|
|
static Compiler* getEnclosingClassCompiler(Compiler* compiler)
|
2014-01-18 23:03:52 +01:00
|
|
|
{
|
|
|
|
|
while (compiler != NULL)
|
|
|
|
|
{
|
2014-01-19 22:01:51 +01:00
|
|
|
if (compiler->enclosingClass != NULL) return compiler;
|
2014-01-18 23:03:52 +01:00
|
|
|
compiler = compiler->parent;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2014-01-19 22:01:51 +01:00
|
|
|
// Walks the compiler chain to find the nearest class enclosing this one.
|
|
|
|
|
// Returns NULL if not currently inside a class definition.
|
|
|
|
|
static ClassCompiler* getEnclosingClass(Compiler* compiler)
|
|
|
|
|
{
|
|
|
|
|
compiler = getEnclosingClassCompiler(compiler);
|
|
|
|
|
return compiler == NULL ? NULL : compiler->enclosingClass;
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-04 01:30:48 +01:00
|
|
|
static void field(Compiler* compiler, bool canAssign)
|
2013-11-23 23:55:05 +01:00
|
|
|
{
|
2014-01-16 16:09:43 +01:00
|
|
|
// Initialize it with a fake value so we can keep parsing and minimize the
|
|
|
|
|
// number of cascaded errors.
|
|
|
|
|
int field = 255;
|
|
|
|
|
|
2014-01-18 23:03:52 +01:00
|
|
|
ClassCompiler* enclosingClass = getEnclosingClass(compiler);
|
|
|
|
|
|
|
|
|
|
if (enclosingClass == NULL)
|
|
|
|
|
{
|
|
|
|
|
error(compiler, "Cannot reference a field outside of a class definition.");
|
|
|
|
|
}
|
2015-08-15 21:07:53 +02:00
|
|
|
else if (enclosingClass->isForeign)
|
|
|
|
|
{
|
|
|
|
|
error(compiler, "Cannot define fields in a foreign class.");
|
|
|
|
|
}
|
2015-07-10 18:18:22 +02:00
|
|
|
else if (enclosingClass->inStatic)
|
2014-01-16 16:09:43 +01:00
|
|
|
{
|
|
|
|
|
error(compiler, "Cannot use an instance field in a static method.");
|
|
|
|
|
}
|
2014-01-18 23:03:52 +01:00
|
|
|
else
|
2013-11-29 19:53:56 +01:00
|
|
|
{
|
|
|
|
|
// Look up the field, or implicitly define it.
|
2015-12-15 02:10:10 +01:00
|
|
|
field = wrenSymbolTableEnsure(compiler->parser->vm, &enclosingClass->fields,
|
2013-12-02 00:22:24 +01:00
|
|
|
compiler->parser->previous.start,
|
|
|
|
|
compiler->parser->previous.length);
|
2014-01-15 16:59:10 +01:00
|
|
|
|
|
|
|
|
if (field >= MAX_FIELDS)
|
|
|
|
|
{
|
|
|
|
|
error(compiler, "A class can only have %d fields.", MAX_FIELDS);
|
|
|
|
|
}
|
2013-11-29 19:53:56 +01:00
|
|
|
}
|
2013-11-23 23:55:05 +01:00
|
|
|
|
|
|
|
|
// If there's an "=" after a field name, it's an assignment.
|
2014-01-18 23:03:52 +01:00
|
|
|
bool isLoad = true;
|
2016-03-04 01:30:48 +01:00
|
|
|
if (canAssign && match(compiler, TOKEN_EQ))
|
2013-11-23 23:55:05 +01:00
|
|
|
{
|
|
|
|
|
// Compile the right-hand side.
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
expression(compiler);
|
2014-01-18 23:03:52 +01:00
|
|
|
isLoad = false;
|
|
|
|
|
}
|
2013-11-23 23:55:05 +01:00
|
|
|
|
2014-01-18 23:03:52 +01:00
|
|
|
// If we're directly inside a method, use a more optimal instruction.
|
2014-01-19 22:01:51 +01:00
|
|
|
if (compiler->parent != NULL &&
|
|
|
|
|
compiler->parent->enclosingClass == enclosingClass)
|
2014-01-18 23:03:52 +01:00
|
|
|
{
|
2015-01-16 00:59:14 +01:00
|
|
|
emitByteArg(compiler, isLoad ? CODE_LOAD_FIELD_THIS : CODE_STORE_FIELD_THIS,
|
|
|
|
|
field);
|
2013-12-23 20:17:40 +01:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2014-01-18 23:03:52 +01:00
|
|
|
loadThis(compiler);
|
2015-01-16 00:59:14 +01:00
|
|
|
emitByteArg(compiler, isLoad ? CODE_LOAD_FIELD : CODE_STORE_FIELD, field);
|
2013-11-23 23:55:05 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-12 16:16:41 +01:00
|
|
|
// Compiles a read or assignment to [variable].
|
2016-03-04 01:30:48 +01:00
|
|
|
static void bareName(Compiler* compiler, bool canAssign, Variable variable)
|
2013-10-23 22:51:41 +02:00
|
|
|
{
|
2013-11-13 16:10:52 +01:00
|
|
|
// If there's an "=" after a bare name, it's a variable assignment.
|
2016-03-04 01:30:48 +01:00
|
|
|
if (canAssign && match(compiler, TOKEN_EQ))
|
2013-10-26 05:40:24 +02:00
|
|
|
{
|
2013-11-13 16:10:52 +01:00
|
|
|
// Compile the right-hand side.
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
expression(compiler);
|
2013-12-01 23:59:56 +01:00
|
|
|
|
2013-12-23 05:53:35 +01:00
|
|
|
// Emit the store instruction.
|
2016-02-12 16:16:41 +01:00
|
|
|
switch (variable.scope)
|
2013-11-13 16:10:52 +01:00
|
|
|
{
|
2016-02-12 16:16:41 +01:00
|
|
|
case SCOPE_LOCAL:
|
|
|
|
|
emitByteArg(compiler, CODE_STORE_LOCAL, variable.index);
|
2014-01-04 20:08:31 +01:00
|
|
|
break;
|
2016-02-12 16:16:41 +01:00
|
|
|
case SCOPE_UPVALUE:
|
|
|
|
|
emitByteArg(compiler, CODE_STORE_UPVALUE, variable.index);
|
2014-01-04 20:08:31 +01:00
|
|
|
break;
|
2016-02-12 16:16:41 +01:00
|
|
|
case SCOPE_MODULE:
|
|
|
|
|
emitShortArg(compiler, CODE_STORE_MODULE_VAR, variable.index);
|
2014-01-04 20:08:31 +01:00
|
|
|
break;
|
2013-12-23 05:53:35 +01:00
|
|
|
default:
|
|
|
|
|
UNREACHABLE();
|
2013-11-13 16:10:52 +01:00
|
|
|
}
|
2016-02-12 16:16:41 +01:00
|
|
|
return;
|
2013-12-23 05:53:35 +01:00
|
|
|
}
|
2016-02-12 16:16:41 +01:00
|
|
|
|
|
|
|
|
// Emit the load instruction.
|
|
|
|
|
loadVariable(compiler, variable);
|
2013-10-22 21:16:39 +02:00
|
|
|
}
|
|
|
|
|
|
2016-03-04 01:30:48 +01:00
|
|
|
static void staticField(Compiler* compiler, bool canAssign)
|
2014-01-19 22:01:51 +01:00
|
|
|
{
|
|
|
|
|
Compiler* classCompiler = getEnclosingClassCompiler(compiler);
|
|
|
|
|
if (classCompiler == NULL)
|
|
|
|
|
{
|
|
|
|
|
error(compiler, "Cannot use a static field outside of a class definition.");
|
2016-02-12 16:16:41 +01:00
|
|
|
return;
|
2014-01-19 22:01:51 +01:00
|
|
|
}
|
|
|
|
|
|
2016-02-12 16:16:41 +01:00
|
|
|
// Look up the name in the scope chain.
|
|
|
|
|
Token* token = &compiler->parser->previous;
|
2014-01-19 22:01:51 +01:00
|
|
|
|
2016-02-12 16:16:41 +01:00
|
|
|
// If this is the first time we've seen this static field, implicitly
|
|
|
|
|
// define it as a variable in the scope surrounding the class definition.
|
|
|
|
|
if (resolveLocal(classCompiler, token->start, token->length) == -1)
|
|
|
|
|
{
|
|
|
|
|
int symbol = declareVariable(classCompiler, NULL);
|
2015-01-14 06:36:42 +01:00
|
|
|
|
2016-02-12 16:16:41 +01:00
|
|
|
// Implicitly initialize it to null.
|
|
|
|
|
emitOp(classCompiler, CODE_NULL);
|
|
|
|
|
defineVariable(classCompiler, symbol);
|
2014-01-19 22:01:51 +01:00
|
|
|
}
|
|
|
|
|
|
2016-02-12 16:16:41 +01:00
|
|
|
// It definitely exists now, so resolve it properly. This is different from
|
|
|
|
|
// the above resolveLocal() call because we may have already closed over it
|
|
|
|
|
// as an upvalue.
|
|
|
|
|
Variable variable = resolveName(compiler, token->start, token->length);
|
2016-03-04 01:30:48 +01:00
|
|
|
bareName(compiler, canAssign, variable);
|
2014-01-19 22:01:51 +01:00
|
|
|
}
|
|
|
|
|
|
2015-01-14 06:36:42 +01:00
|
|
|
// Returns `true` if [name] is a local variable name (starts with a lowercase
|
|
|
|
|
// letter).
|
|
|
|
|
static bool isLocalName(const char* name)
|
|
|
|
|
{
|
|
|
|
|
return name[0] >= 'a' && name[0] <= 'z';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Compiles a variable name or method call with an implicit receiver.
|
2016-03-04 01:30:48 +01:00
|
|
|
static void name(Compiler* compiler, bool canAssign)
|
2014-01-19 22:01:51 +01:00
|
|
|
{
|
2015-01-14 06:36:42 +01:00
|
|
|
// Look for the name in the scope chain up to the nearest enclosing method.
|
2014-01-19 22:01:51 +01:00
|
|
|
Token* token = &compiler->parser->previous;
|
|
|
|
|
|
2016-02-12 16:16:41 +01:00
|
|
|
Variable variable = resolveNonmodule(compiler, token->start, token->length);
|
|
|
|
|
if (variable.index != -1)
|
feat: make `this` the implicit receiver for method calls inside class bodies
In the compiler, when a bare name is encountered inside a class definition and it does not resolve to a local variable or global, emit an implicit `this.` load before the call, enabling getter, setter, and method calls without explicit receiver. Update all benchmark, builtin, and test files to remove redundant `this.` qualifiers, and add comprehensive test suites for implicit receiver behavior across instance, inherited, static, nested, and shadowing scenarios.
2014-02-13 02:33:35 +01:00
|
|
|
{
|
2016-03-04 01:30:48 +01:00
|
|
|
bareName(compiler, canAssign, variable);
|
feat: make `this` the implicit receiver for method calls inside class bodies
In the compiler, when a bare name is encountered inside a class definition and it does not resolve to a local variable or global, emit an implicit `this.` load before the call, enabling getter, setter, and method calls without explicit receiver. Update all benchmark, builtin, and test files to remove redundant `this.` qualifiers, and add comprehensive test suites for implicit receiver behavior across instance, inherited, static, nested, and shadowing scenarios.
2014-02-13 02:33:35 +01:00
|
|
|
return;
|
|
|
|
|
}
|
2015-01-14 06:36:42 +01:00
|
|
|
|
|
|
|
|
// TODO: The fact that we return above here if the variable is known and parse
|
|
|
|
|
// an optional argument list below if not means that the grammar is not
|
2014-11-27 03:33:54 +01:00
|
|
|
// context-free. A line of code in a method like "someName(foo)" is a parse
|
|
|
|
|
// error if "someName" is a defined variable in the surrounding scope and not
|
|
|
|
|
// if it isn't. Fix this. One option is to have "someName(foo)" always
|
|
|
|
|
// resolve to a self-call if there is an argument list, but that makes
|
|
|
|
|
// getters a little confusing.
|
2014-01-19 22:01:51 +01:00
|
|
|
|
2015-01-14 06:36:42 +01:00
|
|
|
// If we're inside a method and the name is lowercase, treat it as a method
|
|
|
|
|
// on this.
|
|
|
|
|
if (isLocalName(token->start) && getEnclosingClass(compiler) != NULL)
|
feat: make `this` the implicit receiver for method calls inside class bodies
In the compiler, when a bare name is encountered inside a class definition and it does not resolve to a local variable or global, emit an implicit `this.` load before the call, enabling getter, setter, and method calls without explicit receiver. Update all benchmark, builtin, and test files to remove redundant `this.` qualifiers, and add comprehensive test suites for implicit receiver behavior across instance, inherited, static, nested, and shadowing scenarios.
2014-02-13 02:33:35 +01:00
|
|
|
{
|
2015-01-14 06:36:42 +01:00
|
|
|
loadThis(compiler);
|
2016-03-04 01:30:48 +01:00
|
|
|
namedCall(compiler, canAssign, CODE_CALL_0);
|
feat: make `this` the implicit receiver for method calls inside class bodies
In the compiler, when a bare name is encountered inside a class definition and it does not resolve to a local variable or global, emit an implicit `this.` load before the call, enabling getter, setter, and method calls without explicit receiver. Update all benchmark, builtin, and test files to remove redundant `this.` qualifiers, and add comprehensive test suites for implicit receiver behavior across instance, inherited, static, nested, and shadowing scenarios.
2014-02-13 02:33:35 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-26 16:45:21 +01:00
|
|
|
// Otherwise, look for a module-level variable with the name.
|
2016-02-12 16:16:41 +01:00
|
|
|
variable.scope = SCOPE_MODULE;
|
|
|
|
|
variable.index = wrenSymbolTableFind(&compiler->parser->module->variableNames,
|
|
|
|
|
token->start, token->length);
|
|
|
|
|
if (variable.index == -1)
|
feat: make `this` the implicit receiver for method calls inside class bodies
In the compiler, when a bare name is encountered inside a class definition and it does not resolve to a local variable or global, emit an implicit `this.` load before the call, enabling getter, setter, and method calls without explicit receiver. Update all benchmark, builtin, and test files to remove redundant `this.` qualifiers, and add comprehensive test suites for implicit receiver behavior across instance, inherited, static, nested, and shadowing scenarios.
2014-02-13 02:33:35 +01:00
|
|
|
{
|
2015-01-15 08:08:25 +01:00
|
|
|
if (isLocalName(token->start))
|
|
|
|
|
{
|
|
|
|
|
error(compiler, "Undefined variable.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-26 16:45:21 +01:00
|
|
|
// If it's a nonlocal name, implicitly define a module-level variable in
|
|
|
|
|
// the hopes that we get a real definition later.
|
2016-02-12 16:16:41 +01:00
|
|
|
variable.index = wrenDeclareVariable(compiler->parser->vm,
|
|
|
|
|
compiler->parser->module,
|
2016-03-16 16:00:28 +01:00
|
|
|
token->start, token->length,
|
|
|
|
|
token->line);
|
2015-01-15 08:08:25 +01:00
|
|
|
|
2016-02-12 16:16:41 +01:00
|
|
|
if (variable.index == -2)
|
2015-01-15 08:08:25 +01:00
|
|
|
{
|
2015-01-26 16:45:21 +01:00
|
|
|
error(compiler, "Too many module variables defined.");
|
2015-01-15 08:08:25 +01:00
|
|
|
}
|
feat: make `this` the implicit receiver for method calls inside class bodies
In the compiler, when a bare name is encountered inside a class definition and it does not resolve to a local variable or global, emit an implicit `this.` load before the call, enabling getter, setter, and method calls without explicit receiver. Update all benchmark, builtin, and test files to remove redundant `this.` qualifiers, and add comprehensive test suites for implicit receiver behavior across instance, inherited, static, nested, and shadowing scenarios.
2014-02-13 02:33:35 +01:00
|
|
|
}
|
2016-02-12 16:16:41 +01:00
|
|
|
|
2016-03-04 01:30:48 +01:00
|
|
|
bareName(compiler, canAssign, variable);
|
2014-01-19 22:01:51 +01:00
|
|
|
}
|
|
|
|
|
|
2016-03-04 01:30:48 +01:00
|
|
|
static void null(Compiler* compiler, bool canAssign)
|
2013-11-06 03:22:22 +01:00
|
|
|
{
|
2015-12-14 07:57:40 +01:00
|
|
|
emitOp(compiler, CODE_NULL);
|
2013-11-06 03:22:22 +01:00
|
|
|
}
|
|
|
|
|
|
2015-08-22 07:54:30 +02:00
|
|
|
// A number or string literal.
|
2016-03-04 01:30:48 +01:00
|
|
|
static void literal(Compiler* compiler, bool canAssign)
|
2015-02-15 01:46:00 +01:00
|
|
|
{
|
2015-11-11 16:55:48 +01:00
|
|
|
emitConstant(compiler, compiler->parser->previous.value);
|
|
|
|
|
}
|
|
|
|
|
|
2015-11-23 16:08:14 +01:00
|
|
|
// A string literal that contains interpolated expressions.
|
|
|
|
|
//
|
|
|
|
|
// Interpolation is syntactic sugar for calling ".join()" on a list. So the
|
|
|
|
|
// string:
|
|
|
|
|
//
|
|
|
|
|
// "a %(b + c) d"
|
|
|
|
|
//
|
|
|
|
|
// is compiled roughly like:
|
|
|
|
|
//
|
|
|
|
|
// ["a ", b + c, " d"].join()
|
2016-03-04 01:30:48 +01:00
|
|
|
static void stringInterpolation(Compiler* compiler, bool canAssign)
|
2015-11-11 16:55:48 +01:00
|
|
|
{
|
|
|
|
|
// Instantiate a new list.
|
|
|
|
|
loadCoreVariable(compiler, "List");
|
|
|
|
|
callMethod(compiler, 0, "new()", 5);
|
|
|
|
|
|
|
|
|
|
do
|
|
|
|
|
{
|
|
|
|
|
// The opening string part.
|
|
|
|
|
literal(compiler, false);
|
|
|
|
|
callMethod(compiler, 1, "addCore_(_)", 11);
|
|
|
|
|
|
2015-11-23 16:08:14 +01:00
|
|
|
// The interpolated expression.
|
2015-11-11 16:55:48 +01:00
|
|
|
ignoreNewlines(compiler);
|
2015-11-23 16:08:14 +01:00
|
|
|
expression(compiler);
|
2015-11-11 16:55:48 +01:00
|
|
|
callMethod(compiler, 1, "addCore_(_)", 11);
|
|
|
|
|
|
|
|
|
|
ignoreNewlines(compiler);
|
|
|
|
|
} while (match(compiler, TOKEN_INTERPOLATION));
|
|
|
|
|
|
|
|
|
|
// The trailing string part.
|
|
|
|
|
consume(compiler, TOKEN_STRING, "Expect end of string interpolation.");
|
|
|
|
|
literal(compiler, false);
|
|
|
|
|
callMethod(compiler, 1, "addCore_(_)", 11);
|
|
|
|
|
|
2015-11-23 16:08:14 +01:00
|
|
|
// The list of interpolated parts.
|
|
|
|
|
callMethod(compiler, 0, "join()", 6);
|
2013-10-26 05:40:24 +02:00
|
|
|
}
|
|
|
|
|
|
2016-03-04 01:30:48 +01:00
|
|
|
static void super_(Compiler* compiler, bool canAssign)
|
2013-12-13 17:37:49 +01:00
|
|
|
{
|
2014-01-18 23:03:52 +01:00
|
|
|
ClassCompiler* enclosingClass = getEnclosingClass(compiler);
|
|
|
|
|
|
|
|
|
|
if (enclosingClass == NULL)
|
2013-12-16 07:08:40 +01:00
|
|
|
{
|
|
|
|
|
error(compiler, "Cannot use 'super' outside of a method.");
|
|
|
|
|
}
|
2013-12-23 05:53:35 +01:00
|
|
|
|
2015-01-12 00:43:51 +01:00
|
|
|
loadThis(compiler);
|
2013-12-13 17:37:49 +01:00
|
|
|
|
2015-01-12 00:43:51 +01:00
|
|
|
// TODO: Super operator calls.
|
2015-07-10 18:18:22 +02:00
|
|
|
// TODO: There's no syntax for invoking a superclass constructor with a
|
|
|
|
|
// different name from the enclosing one. Figure that out.
|
2013-12-19 16:02:27 +01:00
|
|
|
|
2015-01-12 00:43:51 +01:00
|
|
|
// See if it's a named super call, or an unnamed one.
|
|
|
|
|
if (match(compiler, TOKEN_DOT))
|
|
|
|
|
{
|
|
|
|
|
// Compile the superclass call.
|
|
|
|
|
consume(compiler, TOKEN_NAME, "Expect method name after 'super.'.");
|
2016-03-04 01:30:48 +01:00
|
|
|
namedCall(compiler, canAssign, CODE_SUPER_0);
|
2015-01-12 00:43:51 +01:00
|
|
|
}
|
2015-02-27 06:56:15 +01:00
|
|
|
else if (enclosingClass != NULL)
|
2015-01-12 00:43:51 +01:00
|
|
|
{
|
2015-02-27 06:56:15 +01:00
|
|
|
// No explicit name, so use the name of the enclosing method. Make sure we
|
|
|
|
|
// check that enclosingClass isn't NULL first. We've already reported the
|
|
|
|
|
// error, but we don't want to crash here.
|
2015-07-10 18:18:22 +02:00
|
|
|
methodCall(compiler, CODE_SUPER_0, enclosingClass->signature);
|
2013-12-19 16:02:27 +01:00
|
|
|
}
|
2013-12-13 17:37:49 +01:00
|
|
|
}
|
|
|
|
|
|
2016-03-04 01:30:48 +01:00
|
|
|
static void this_(Compiler* compiler, bool canAssign)
|
2013-11-10 00:42:23 +01:00
|
|
|
{
|
2014-01-18 23:03:52 +01:00
|
|
|
if (getEnclosingClass(compiler) == NULL)
|
2013-11-10 00:42:23 +01:00
|
|
|
{
|
|
|
|
|
error(compiler, "Cannot use 'this' outside of a method.");
|
2013-12-22 00:55:08 +01:00
|
|
|
return;
|
2013-11-10 00:42:23 +01:00
|
|
|
}
|
|
|
|
|
|
2013-12-23 20:17:40 +01:00
|
|
|
loadThis(compiler);
|
2013-11-10 00:42:23 +01:00
|
|
|
}
|
|
|
|
|
|
2013-11-24 19:45:07 +01:00
|
|
|
// Subscript or "array indexing" operator like `foo[bar]`.
|
2016-03-04 01:30:48 +01:00
|
|
|
static void subscript(Compiler* compiler, bool canAssign)
|
2013-11-24 19:45:07 +01:00
|
|
|
{
|
2015-08-06 16:34:19 +02:00
|
|
|
Signature signature = { "", 0, SIG_SUBSCRIPT, 0 };
|
2013-11-24 19:45:07 +01:00
|
|
|
|
|
|
|
|
// Parse the argument list.
|
2015-02-27 06:56:15 +01:00
|
|
|
finishArgumentList(compiler, &signature);
|
2013-11-24 19:45:07 +01:00
|
|
|
consume(compiler, TOKEN_RIGHT_BRACKET, "Expect ']' after arguments.");
|
|
|
|
|
|
2016-03-04 01:30:48 +01:00
|
|
|
if (canAssign && match(compiler, TOKEN_EQ))
|
2013-12-21 18:37:59 +01:00
|
|
|
{
|
2015-02-27 06:56:15 +01:00
|
|
|
signature.type = SIG_SUBSCRIPT_SETTER;
|
2013-12-21 18:37:59 +01:00
|
|
|
|
|
|
|
|
// Compile the assigned value.
|
2015-02-27 06:56:15 +01:00
|
|
|
validateNumParameters(compiler, ++signature.arity);
|
2013-12-21 18:37:59 +01:00
|
|
|
expression(compiler);
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-27 06:56:15 +01:00
|
|
|
callSignature(compiler, CODE_CALL_0, &signature);
|
2013-11-24 19:45:07 +01:00
|
|
|
}
|
|
|
|
|
|
2016-03-04 01:30:48 +01:00
|
|
|
static void call(Compiler* compiler, bool canAssign)
|
2013-11-03 19:16:14 +01:00
|
|
|
{
|
refactor: move newline handling from lexer into parser grammar rules
This change shifts newline tokenization from the lexer's readRawToken into the parser's nextToken, allowing the grammar to selectively ignore newlines in specific contexts (e.g., after operators like "|", "is", "for", "if", "while") while treating them as errors in others (e.g., after "var", "new", "class", "static", before commas in lists). The lexer now emits TOKEN_LINE for semicolons and newlines uniformly, and the parser decides which to elide. Test files are updated to reflect the new error expectations and to remove tests for newlines that are now grammatically allowed.
2014-04-04 16:51:18 +02:00
|
|
|
ignoreNewlines(compiler);
|
feat: make `this` the implicit receiver for method calls inside class bodies
In the compiler, when a bare name is encountered inside a class definition and it does not resolve to a local variable or global, emit an implicit `this.` load before the call, enabling getter, setter, and method calls without explicit receiver. Update all benchmark, builtin, and test files to remove redundant `this.` qualifiers, and add comprehensive test suites for implicit receiver behavior across instance, inherited, static, nested, and shadowing scenarios.
2014-02-13 02:33:35 +01:00
|
|
|
consume(compiler, TOKEN_NAME, "Expect method name after '.'.");
|
2016-03-04 01:30:48 +01:00
|
|
|
namedCall(compiler, canAssign, CODE_CALL_0);
|
2013-11-03 19:16:14 +01:00
|
|
|
}
|
|
|
|
|
|
2016-03-04 01:30:48 +01:00
|
|
|
static void and_(Compiler* compiler, bool canAssign)
|
2013-11-19 16:35:25 +01:00
|
|
|
{
|
refactor: move newline handling from lexer into parser grammar rules
This change shifts newline tokenization from the lexer's readRawToken into the parser's nextToken, allowing the grammar to selectively ignore newlines in specific contexts (e.g., after operators like "|", "is", "for", "if", "while") while treating them as errors in others (e.g., after "var", "new", "class", "static", before commas in lists). The lexer now emits TOKEN_LINE for semicolons and newlines uniformly, and the parser decides which to elide. Test files are updated to reflect the new error expectations and to remove tests for newlines that are now grammatically allowed.
2014-04-04 16:51:18 +02:00
|
|
|
ignoreNewlines(compiler);
|
|
|
|
|
|
2013-11-19 16:35:25 +01:00
|
|
|
// Skip the right argument if the left is false.
|
2014-01-08 08:03:38 +01:00
|
|
|
int jump = emitJump(compiler, CODE_AND);
|
2016-03-04 01:30:48 +01:00
|
|
|
parsePrecedence(compiler, PREC_LOGICAL_AND);
|
2013-11-19 16:35:25 +01:00
|
|
|
patchJump(compiler, jump);
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-04 01:30:48 +01:00
|
|
|
static void or_(Compiler* compiler, bool canAssign)
|
2013-11-20 03:24:58 +01:00
|
|
|
{
|
refactor: move newline handling from lexer into parser grammar rules
This change shifts newline tokenization from the lexer's readRawToken into the parser's nextToken, allowing the grammar to selectively ignore newlines in specific contexts (e.g., after operators like "|", "is", "for", "if", "while") while treating them as errors in others (e.g., after "var", "new", "class", "static", before commas in lists). The lexer now emits TOKEN_LINE for semicolons and newlines uniformly, and the parser decides which to elide. Test files are updated to reflect the new error expectations and to remove tests for newlines that are now grammatically allowed.
2014-04-04 16:51:18 +02:00
|
|
|
ignoreNewlines(compiler);
|
|
|
|
|
|
2013-11-20 03:24:58 +01:00
|
|
|
// Skip the right argument if the left is true.
|
2014-01-08 08:03:38 +01:00
|
|
|
int jump = emitJump(compiler, CODE_OR);
|
2016-03-04 01:30:48 +01:00
|
|
|
parsePrecedence(compiler, PREC_LOGICAL_OR);
|
2013-11-20 03:24:58 +01:00
|
|
|
patchJump(compiler, jump);
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-04 01:30:48 +01:00
|
|
|
static void conditional(Compiler* compiler, bool canAssign)
|
2014-02-15 20:36:01 +01:00
|
|
|
{
|
refactor: move newline handling from lexer into parser grammar rules
This change shifts newline tokenization from the lexer's readRawToken into the parser's nextToken, allowing the grammar to selectively ignore newlines in specific contexts (e.g., after operators like "|", "is", "for", "if", "while") while treating them as errors in others (e.g., after "var", "new", "class", "static", before commas in lists). The lexer now emits TOKEN_LINE for semicolons and newlines uniformly, and the parser decides which to elide. Test files are updated to reflect the new error expectations and to remove tests for newlines that are now grammatically allowed.
2014-04-04 16:51:18 +02:00
|
|
|
// Ignore newline after '?'.
|
|
|
|
|
ignoreNewlines(compiler);
|
|
|
|
|
|
2014-02-15 20:36:01 +01:00
|
|
|
// Jump to the else branch if the condition is false.
|
|
|
|
|
int ifJump = emitJump(compiler, CODE_JUMP_IF);
|
|
|
|
|
|
|
|
|
|
// Compile the then branch.
|
2016-03-04 01:30:48 +01:00
|
|
|
parsePrecedence(compiler, PREC_CONDITIONAL);
|
2014-02-15 20:36:01 +01:00
|
|
|
|
|
|
|
|
consume(compiler, TOKEN_COLON,
|
|
|
|
|
"Expect ':' after then branch of conditional operator.");
|
refactor: move newline handling from lexer into parser grammar rules
This change shifts newline tokenization from the lexer's readRawToken into the parser's nextToken, allowing the grammar to selectively ignore newlines in specific contexts (e.g., after operators like "|", "is", "for", "if", "while") while treating them as errors in others (e.g., after "var", "new", "class", "static", before commas in lists). The lexer now emits TOKEN_LINE for semicolons and newlines uniformly, and the parser decides which to elide. Test files are updated to reflect the new error expectations and to remove tests for newlines that are now grammatically allowed.
2014-04-04 16:51:18 +02:00
|
|
|
ignoreNewlines(compiler);
|
2014-02-15 20:36:01 +01:00
|
|
|
|
|
|
|
|
// Jump over the else branch when the if branch is taken.
|
|
|
|
|
int elseJump = emitJump(compiler, CODE_JUMP);
|
|
|
|
|
|
|
|
|
|
// Compile the else branch.
|
|
|
|
|
patchJump(compiler, ifJump);
|
|
|
|
|
|
2016-03-04 01:30:48 +01:00
|
|
|
parsePrecedence(compiler, PREC_ASSIGNMENT);
|
2014-02-15 20:36:01 +01:00
|
|
|
|
|
|
|
|
// Patch the jump over the else.
|
|
|
|
|
patchJump(compiler, elseJump);
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-04 01:30:48 +01:00
|
|
|
void infixOp(Compiler* compiler, bool canAssign)
|
2013-11-03 19:16:14 +01:00
|
|
|
{
|
2015-01-16 00:59:14 +01:00
|
|
|
GrammarRule* rule = getRule(compiler->parser->previous.type);
|
2013-11-03 19:16:14 +01:00
|
|
|
|
refactor: move newline handling from lexer into parser grammar rules
This change shifts newline tokenization from the lexer's readRawToken into the parser's nextToken, allowing the grammar to selectively ignore newlines in specific contexts (e.g., after operators like "|", "is", "for", "if", "while") while treating them as errors in others (e.g., after "var", "new", "class", "static", before commas in lists). The lexer now emits TOKEN_LINE for semicolons and newlines uniformly, and the parser decides which to elide. Test files are updated to reflect the new error expectations and to remove tests for newlines that are now grammatically allowed.
2014-04-04 16:51:18 +02:00
|
|
|
// An infix operator cannot end an expression.
|
|
|
|
|
ignoreNewlines(compiler);
|
2014-04-03 04:41:53 +02:00
|
|
|
|
2013-11-03 19:16:14 +01:00
|
|
|
// Compile the right-hand side.
|
2016-03-04 01:30:48 +01:00
|
|
|
parsePrecedence(compiler, (Precedence)(rule->precedence + 1));
|
2013-11-03 19:16:14 +01:00
|
|
|
|
|
|
|
|
// Call the operator method on the left-hand side.
|
2015-08-06 16:34:19 +02:00
|
|
|
Signature signature = { rule->name, (int)strlen(rule->name), SIG_METHOD, 1 };
|
2015-02-27 06:56:15 +01:00
|
|
|
callSignature(compiler, CODE_CALL_0, &signature);
|
2013-11-03 19:16:14 +01:00
|
|
|
}
|
|
|
|
|
|
2013-11-14 19:27:33 +01:00
|
|
|
// Compiles a method signature for an infix operator.
|
2015-02-27 06:56:15 +01:00
|
|
|
void infixSignature(Compiler* compiler, Signature* signature)
|
2013-11-14 19:27:33 +01:00
|
|
|
{
|
2015-02-27 06:56:15 +01:00
|
|
|
// Add the RHS parameter.
|
|
|
|
|
signature->type = SIG_METHOD;
|
|
|
|
|
signature->arity = 1;
|
2013-11-14 19:27:33 +01:00
|
|
|
|
|
|
|
|
// Parse the parameter name.
|
2014-04-09 02:54:37 +02:00
|
|
|
consume(compiler, TOKEN_LEFT_PAREN, "Expect '(' after operator name.");
|
2014-01-19 22:01:51 +01:00
|
|
|
declareNamedVariable(compiler);
|
2014-04-09 02:54:37 +02:00
|
|
|
consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after parameter name.");
|
2013-11-14 19:27:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Compiles a method signature for an unary operator (i.e. "!").
|
2015-02-27 06:56:15 +01:00
|
|
|
void unarySignature(Compiler* compiler, Signature* signature)
|
2013-11-14 19:27:33 +01:00
|
|
|
{
|
|
|
|
|
// Do nothing. The name is already complete.
|
2015-07-10 19:27:32 +02:00
|
|
|
signature->type = SIG_GETTER;
|
2013-11-14 19:27:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Compiles a method signature for an operator that can either be unary or
|
|
|
|
|
// infix (i.e. "-").
|
2015-02-27 06:56:15 +01:00
|
|
|
void mixedSignature(Compiler* compiler, Signature* signature)
|
2013-11-14 19:27:33 +01:00
|
|
|
{
|
2015-02-27 06:56:15 +01:00
|
|
|
signature->type = SIG_GETTER;
|
|
|
|
|
|
2014-04-09 02:54:37 +02:00
|
|
|
// If there is a parameter, it's an infix operator, otherwise it's unary.
|
|
|
|
|
if (match(compiler, TOKEN_LEFT_PAREN))
|
2013-11-14 19:27:33 +01:00
|
|
|
{
|
2015-02-27 06:56:15 +01:00
|
|
|
// Add the RHS parameter.
|
|
|
|
|
signature->type = SIG_METHOD;
|
|
|
|
|
signature->arity = 1;
|
2013-11-14 19:27:33 +01:00
|
|
|
|
|
|
|
|
// Parse the parameter name.
|
2014-01-19 22:01:51 +01:00
|
|
|
declareNamedVariable(compiler);
|
2014-04-09 02:54:37 +02:00
|
|
|
consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after parameter name.");
|
2013-11-14 19:27:33 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-27 06:56:15 +01:00
|
|
|
// Compiles an optional setter parameter in a method [signature].
|
2015-01-21 03:25:54 +01:00
|
|
|
//
|
|
|
|
|
// Returns `true` if it was a setter.
|
2015-02-27 06:56:15 +01:00
|
|
|
static bool maybeSetter(Compiler* compiler, Signature* signature)
|
2015-01-21 03:25:54 +01:00
|
|
|
{
|
|
|
|
|
// See if it's a setter.
|
|
|
|
|
if (!match(compiler, TOKEN_EQ)) return false;
|
|
|
|
|
|
|
|
|
|
// It's a setter.
|
2015-02-27 06:56:15 +01:00
|
|
|
if (signature->type == SIG_SUBSCRIPT)
|
|
|
|
|
{
|
|
|
|
|
signature->type = SIG_SUBSCRIPT_SETTER;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
signature->type = SIG_SETTER;
|
|
|
|
|
}
|
2015-01-21 03:25:54 +01:00
|
|
|
|
|
|
|
|
// Parse the value parameter.
|
|
|
|
|
consume(compiler, TOKEN_LEFT_PAREN, "Expect '(' after '='.");
|
|
|
|
|
declareNamedVariable(compiler);
|
|
|
|
|
consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after parameter name.");
|
2015-02-27 06:56:15 +01:00
|
|
|
|
|
|
|
|
signature->arity++;
|
2015-03-03 09:06:34 +01:00
|
|
|
|
2015-01-21 03:25:54 +01:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Compiles a method signature for a subscript operator.
|
2015-02-27 06:56:15 +01:00
|
|
|
void subscriptSignature(Compiler* compiler, Signature* signature)
|
2015-01-21 03:25:54 +01:00
|
|
|
{
|
2015-02-27 06:56:15 +01:00
|
|
|
signature->type = SIG_SUBSCRIPT;
|
|
|
|
|
|
|
|
|
|
// The signature currently has "[" as its name since that was the token that
|
|
|
|
|
// matched it. Clear that out.
|
|
|
|
|
signature->length = 0;
|
|
|
|
|
|
2015-01-21 03:25:54 +01:00
|
|
|
// Parse the parameters inside the subscript.
|
2015-02-27 15:51:37 +01:00
|
|
|
finishParameterList(compiler, signature);
|
|
|
|
|
consume(compiler, TOKEN_RIGHT_BRACKET, "Expect ']' after parameters.");
|
2015-01-21 03:25:54 +01:00
|
|
|
|
2015-02-27 06:56:15 +01:00
|
|
|
maybeSetter(compiler, signature);
|
2015-01-21 03:25:54 +01:00
|
|
|
}
|
|
|
|
|
|
2015-02-27 15:51:37 +01:00
|
|
|
// Parses an optional parenthesized parameter list. Updates `type` and `arity`
|
|
|
|
|
// in [signature] to match what was parsed.
|
|
|
|
|
static void parameterList(Compiler* compiler, Signature* signature)
|
|
|
|
|
{
|
|
|
|
|
// The parameter list is optional.
|
|
|
|
|
if (!match(compiler, TOKEN_LEFT_PAREN)) return;
|
2015-07-10 18:18:22 +02:00
|
|
|
|
2015-02-27 15:51:37 +01:00
|
|
|
signature->type = SIG_METHOD;
|
2015-07-10 18:18:22 +02:00
|
|
|
|
2015-02-27 15:51:37 +01:00
|
|
|
// Allow an empty parameter list.
|
|
|
|
|
if (match(compiler, TOKEN_RIGHT_PAREN)) return;
|
|
|
|
|
|
|
|
|
|
finishParameterList(compiler, signature);
|
|
|
|
|
consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after parameters.");
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-20 16:05:31 +01:00
|
|
|
// Compiles a method signature for a named method or setter.
|
2015-02-27 06:56:15 +01:00
|
|
|
void namedSignature(Compiler* compiler, Signature* signature)
|
2013-12-20 16:05:31 +01:00
|
|
|
{
|
2015-02-27 06:56:15 +01:00
|
|
|
signature->type = SIG_GETTER;
|
2015-07-10 18:18:22 +02:00
|
|
|
|
2015-01-21 03:25:54 +01:00
|
|
|
// If it's a setter, it can't also have a parameter list.
|
2015-02-27 06:56:15 +01:00
|
|
|
if (maybeSetter(compiler, signature)) return;
|
2013-12-20 16:05:31 +01:00
|
|
|
|
2015-01-21 03:25:54 +01:00
|
|
|
// Regular named method with an optional parameter list.
|
2015-02-27 15:51:37 +01:00
|
|
|
parameterList(compiler, signature);
|
2013-12-20 16:05:31 +01:00
|
|
|
}
|
|
|
|
|
|
2013-12-19 16:02:27 +01:00
|
|
|
// Compiles a method signature for a constructor.
|
2015-02-27 06:56:15 +01:00
|
|
|
void constructorSignature(Compiler* compiler, Signature* signature)
|
2013-12-19 16:02:27 +01:00
|
|
|
{
|
2015-08-19 19:33:54 +02:00
|
|
|
consume(compiler, TOKEN_NAME, "Expect constructor name after 'construct'.");
|
2015-07-10 18:18:22 +02:00
|
|
|
|
|
|
|
|
// Capture the name.
|
2015-08-06 16:34:19 +02:00
|
|
|
*signature = signatureFromToken(compiler, SIG_INITIALIZER);
|
2015-07-10 18:18:22 +02:00
|
|
|
|
|
|
|
|
if (match(compiler, TOKEN_EQ))
|
|
|
|
|
{
|
|
|
|
|
error(compiler, "A constructor cannot be a setter.");
|
|
|
|
|
}
|
2015-02-27 06:56:15 +01:00
|
|
|
|
2015-07-10 18:18:22 +02:00
|
|
|
if (!match(compiler, TOKEN_LEFT_PAREN))
|
|
|
|
|
{
|
|
|
|
|
error(compiler, "A constructor cannot be a getter.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Allow an empty parameter list.
|
|
|
|
|
if (match(compiler, TOKEN_RIGHT_PAREN)) return;
|
|
|
|
|
|
|
|
|
|
finishParameterList(compiler, signature);
|
|
|
|
|
consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after parameters.");
|
2013-12-19 16:02:27 +01:00
|
|
|
}
|
|
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
// This table defines all of the parsing rules for the prefix and infix
|
|
|
|
|
// expressions in the grammar. Expressions are parsed using a Pratt parser.
|
|
|
|
|
//
|
|
|
|
|
// See: http://journal.stuffwithstuff.com/2011/03/19/pratt-parsers-expression-parsing-made-easy/
|
2013-11-14 19:27:33 +01:00
|
|
|
#define UNUSED { NULL, NULL, NULL, PREC_NONE, NULL }
|
|
|
|
|
#define PREFIX(fn) { fn, NULL, NULL, PREC_NONE, NULL }
|
|
|
|
|
#define INFIX(prec, fn) { NULL, fn, NULL, prec, NULL }
|
|
|
|
|
#define INFIX_OPERATOR(prec, name) { NULL, infixOp, infixSignature, prec, name }
|
|
|
|
|
#define PREFIX_OPERATOR(name) { unaryOp, NULL, unarySignature, PREC_NONE, name }
|
2013-12-05 07:09:31 +01:00
|
|
|
#define OPERATOR(name) { unaryOp, infixOp, mixedSignature, PREC_TERM, name }
|
2013-11-13 16:10:52 +01:00
|
|
|
|
|
|
|
|
GrammarRule rules[] =
|
2013-10-22 21:16:39 +02:00
|
|
|
{
|
2013-11-07 16:04:25 +01:00
|
|
|
/* TOKEN_LEFT_PAREN */ PREFIX(grouping),
|
|
|
|
|
/* TOKEN_RIGHT_PAREN */ UNUSED,
|
2015-01-21 03:25:54 +01:00
|
|
|
/* TOKEN_LEFT_BRACKET */ { list, subscript, subscriptSignature, PREC_CALL, NULL },
|
2013-11-07 16:04:25 +01:00
|
|
|
/* TOKEN_RIGHT_BRACKET */ UNUSED,
|
2015-01-25 08:21:50 +01:00
|
|
|
/* TOKEN_LEFT_BRACE */ PREFIX(map),
|
2013-11-07 16:04:25 +01:00
|
|
|
/* TOKEN_RIGHT_BRACE */ UNUSED,
|
|
|
|
|
/* TOKEN_COLON */ UNUSED,
|
|
|
|
|
/* TOKEN_DOT */ INFIX(PREC_CALL, call),
|
2015-02-27 06:56:15 +01:00
|
|
|
/* TOKEN_DOTDOT */ INFIX_OPERATOR(PREC_RANGE, ".."),
|
|
|
|
|
/* TOKEN_DOTDOTDOT */ INFIX_OPERATOR(PREC_RANGE, "..."),
|
2013-11-07 16:04:25 +01:00
|
|
|
/* TOKEN_COMMA */ UNUSED,
|
2015-02-27 06:56:15 +01:00
|
|
|
/* TOKEN_STAR */ INFIX_OPERATOR(PREC_FACTOR, "*"),
|
|
|
|
|
/* TOKEN_SLASH */ INFIX_OPERATOR(PREC_FACTOR, "/"),
|
|
|
|
|
/* TOKEN_PERCENT */ INFIX_OPERATOR(PREC_FACTOR, "%"),
|
|
|
|
|
/* TOKEN_PLUS */ INFIX_OPERATOR(PREC_TERM, "+"),
|
|
|
|
|
/* TOKEN_MINUS */ OPERATOR("-"),
|
|
|
|
|
/* TOKEN_LTLT */ INFIX_OPERATOR(PREC_BITWISE_SHIFT, "<<"),
|
|
|
|
|
/* TOKEN_GTGT */ INFIX_OPERATOR(PREC_BITWISE_SHIFT, ">>"),
|
|
|
|
|
/* TOKEN_PIPE */ INFIX_OPERATOR(PREC_BITWISE_OR, "|"),
|
2015-02-25 16:04:02 +01:00
|
|
|
/* TOKEN_PIPEPIPE */ INFIX(PREC_LOGICAL_OR, or_),
|
2015-02-27 06:56:15 +01:00
|
|
|
/* TOKEN_CARET */ INFIX_OPERATOR(PREC_BITWISE_XOR, "^"),
|
|
|
|
|
/* TOKEN_AMP */ INFIX_OPERATOR(PREC_BITWISE_AND, "&"),
|
2015-02-25 16:04:02 +01:00
|
|
|
/* TOKEN_AMPAMP */ INFIX(PREC_LOGICAL_AND, and_),
|
2013-11-13 16:10:52 +01:00
|
|
|
/* TOKEN_BANG */ PREFIX_OPERATOR("!"),
|
2013-12-05 07:09:31 +01:00
|
|
|
/* TOKEN_TILDE */ PREFIX_OPERATOR("~"),
|
2014-02-15 20:36:01 +01:00
|
|
|
/* TOKEN_QUESTION */ INFIX(PREC_ASSIGNMENT, conditional),
|
2013-11-07 16:04:25 +01:00
|
|
|
/* TOKEN_EQ */ UNUSED,
|
2015-02-27 06:56:15 +01:00
|
|
|
/* TOKEN_LT */ INFIX_OPERATOR(PREC_COMPARISON, "<"),
|
|
|
|
|
/* TOKEN_GT */ INFIX_OPERATOR(PREC_COMPARISON, ">"),
|
|
|
|
|
/* TOKEN_LTEQ */ INFIX_OPERATOR(PREC_COMPARISON, "<="),
|
|
|
|
|
/* TOKEN_GTEQ */ INFIX_OPERATOR(PREC_COMPARISON, ">="),
|
|
|
|
|
/* TOKEN_EQEQ */ INFIX_OPERATOR(PREC_EQUALITY, "=="),
|
|
|
|
|
/* TOKEN_BANGEQ */ INFIX_OPERATOR(PREC_EQUALITY, "!="),
|
2013-12-24 19:15:50 +01:00
|
|
|
/* TOKEN_BREAK */ UNUSED,
|
2013-11-07 16:04:25 +01:00
|
|
|
/* TOKEN_CLASS */ UNUSED,
|
2015-07-21 16:24:53 +02:00
|
|
|
/* TOKEN_CONSTRUCT */ { NULL, NULL, constructorSignature, PREC_NONE, NULL },
|
2013-11-07 16:04:25 +01:00
|
|
|
/* TOKEN_ELSE */ UNUSED,
|
|
|
|
|
/* TOKEN_FALSE */ PREFIX(boolean),
|
2013-12-24 19:15:50 +01:00
|
|
|
/* TOKEN_FOR */ UNUSED,
|
2015-03-24 15:58:15 +01:00
|
|
|
/* TOKEN_FOREIGN */ UNUSED,
|
2013-11-07 16:04:25 +01:00
|
|
|
/* TOKEN_IF */ UNUSED,
|
2015-02-15 01:46:00 +01:00
|
|
|
/* TOKEN_IMPORT */ UNUSED,
|
2013-12-25 06:04:11 +01:00
|
|
|
/* TOKEN_IN */ UNUSED,
|
2015-06-19 16:58:07 +02:00
|
|
|
/* TOKEN_IS */ INFIX_OPERATOR(PREC_IS, "is"),
|
2013-11-07 16:04:25 +01:00
|
|
|
/* TOKEN_NULL */ PREFIX(null),
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
/* TOKEN_RETURN */ UNUSED,
|
2013-11-10 20:46:13 +01:00
|
|
|
/* TOKEN_STATIC */ UNUSED,
|
2013-12-13 17:37:49 +01:00
|
|
|
/* TOKEN_SUPER */ PREFIX(super_),
|
2015-07-21 16:24:53 +02:00
|
|
|
/* TOKEN_THIS */ PREFIX(this_),
|
2013-11-07 16:04:25 +01:00
|
|
|
/* TOKEN_TRUE */ PREFIX(boolean),
|
|
|
|
|
/* TOKEN_VAR */ UNUSED,
|
2013-11-18 07:38:59 +01:00
|
|
|
/* TOKEN_WHILE */ UNUSED,
|
2013-11-23 23:55:05 +01:00
|
|
|
/* TOKEN_FIELD */ PREFIX(field),
|
2014-01-19 22:01:51 +01:00
|
|
|
/* TOKEN_STATIC_FIELD */ PREFIX(staticField),
|
2013-12-20 16:05:31 +01:00
|
|
|
/* TOKEN_NAME */ { name, NULL, namedSignature, PREC_NONE, NULL },
|
2015-08-22 07:54:30 +02:00
|
|
|
/* TOKEN_NUMBER */ PREFIX(literal),
|
|
|
|
|
/* TOKEN_STRING */ PREFIX(literal),
|
2015-11-11 16:55:48 +01:00
|
|
|
/* TOKEN_INTERPOLATION */ PREFIX(stringInterpolation),
|
2013-11-07 16:04:25 +01:00
|
|
|
/* TOKEN_LINE */ UNUSED,
|
|
|
|
|
/* TOKEN_ERROR */ UNUSED,
|
|
|
|
|
/* TOKEN_EOF */ UNUSED
|
|
|
|
|
};
|
2013-10-22 21:16:39 +02:00
|
|
|
|
2015-01-16 00:59:14 +01:00
|
|
|
// Gets the [GrammarRule] associated with tokens of [type].
|
|
|
|
|
static GrammarRule* getRule(TokenType type)
|
|
|
|
|
{
|
|
|
|
|
return &rules[type];
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
// The main entrypoint for the top-down operator precedence parser.
|
2016-03-04 01:30:48 +01:00
|
|
|
void parsePrecedence(Compiler* compiler, Precedence precedence)
|
2013-10-22 21:16:39 +02:00
|
|
|
{
|
2013-10-28 05:59:14 +01:00
|
|
|
nextToken(compiler->parser);
|
2013-11-13 16:10:52 +01:00
|
|
|
GrammarFn prefix = rules[compiler->parser->previous.type].prefix;
|
2013-11-07 16:04:25 +01:00
|
|
|
|
|
|
|
|
if (prefix == NULL)
|
2013-10-22 21:16:39 +02:00
|
|
|
{
|
2015-02-27 08:08:36 +01:00
|
|
|
error(compiler, "Expected expression.");
|
2013-11-07 16:04:25 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-04 01:30:48 +01:00
|
|
|
// Track if the precendence of the surrounding expression is low enough to
|
|
|
|
|
// allow an assignment inside this one. We can't compile an assignment like
|
|
|
|
|
// a normal expression because it requires us to handle the LHS specially --
|
|
|
|
|
// it needs to be an lvalue, not an rvalue. So, for each of the kinds of
|
|
|
|
|
// expressions that are valid lvalues -- names, subscripts, fields, etc. --
|
|
|
|
|
// we pass in whether or not it appears in a context loose enough to allow
|
|
|
|
|
// "=". If so, it will parse the "=" itself and handle it appropriately.
|
|
|
|
|
bool canAssign = precedence <= PREC_CONDITIONAL;
|
|
|
|
|
prefix(compiler, canAssign);
|
2013-11-07 16:04:25 +01:00
|
|
|
|
|
|
|
|
while (precedence <= rules[compiler->parser->current.type].precedence)
|
|
|
|
|
{
|
|
|
|
|
nextToken(compiler->parser);
|
2013-11-13 16:10:52 +01:00
|
|
|
GrammarFn infix = rules[compiler->parser->previous.type].infix;
|
2016-03-04 01:30:48 +01:00
|
|
|
infix(compiler, canAssign);
|
2013-10-22 21:16:39 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
// Parses an expression. Unlike statements, expressions leave a resulting value
|
|
|
|
|
// on the stack.
|
|
|
|
|
void expression(Compiler* compiler)
|
2013-11-13 16:10:52 +01:00
|
|
|
{
|
2016-03-04 01:30:48 +01:00
|
|
|
parsePrecedence(compiler, PREC_LOWEST);
|
2013-11-13 16:10:52 +01:00
|
|
|
}
|
|
|
|
|
|
2013-12-24 19:15:50 +01:00
|
|
|
// Returns the number of arguments to the instruction at [ip] in [fn]'s
|
|
|
|
|
// bytecode.
|
2013-12-27 01:58:03 +01:00
|
|
|
static int getNumArguments(const uint8_t* bytecode, const Value* constants,
|
|
|
|
|
int ip)
|
2013-12-24 19:15:50 +01:00
|
|
|
{
|
2015-01-16 00:59:14 +01:00
|
|
|
Code instruction = (Code)bytecode[ip];
|
2013-12-24 19:15:50 +01:00
|
|
|
switch (instruction)
|
|
|
|
|
{
|
|
|
|
|
case CODE_NULL:
|
|
|
|
|
case CODE_FALSE:
|
|
|
|
|
case CODE_TRUE:
|
|
|
|
|
case CODE_POP:
|
|
|
|
|
case CODE_CLOSE_UPVALUE:
|
|
|
|
|
case CODE_RETURN:
|
2014-01-08 08:03:38 +01:00
|
|
|
case CODE_END:
|
2014-12-07 04:59:11 +01:00
|
|
|
case CODE_LOAD_LOCAL_0:
|
|
|
|
|
case CODE_LOAD_LOCAL_1:
|
|
|
|
|
case CODE_LOAD_LOCAL_2:
|
|
|
|
|
case CODE_LOAD_LOCAL_3:
|
|
|
|
|
case CODE_LOAD_LOCAL_4:
|
|
|
|
|
case CODE_LOAD_LOCAL_5:
|
|
|
|
|
case CODE_LOAD_LOCAL_6:
|
|
|
|
|
case CODE_LOAD_LOCAL_7:
|
|
|
|
|
case CODE_LOAD_LOCAL_8:
|
2015-07-10 18:18:22 +02:00
|
|
|
case CODE_CONSTRUCT:
|
2015-08-15 21:07:53 +02:00
|
|
|
case CODE_FOREIGN_CONSTRUCT:
|
|
|
|
|
case CODE_FOREIGN_CLASS:
|
2013-12-24 19:15:50 +01:00
|
|
|
return 0;
|
|
|
|
|
|
2014-01-08 08:03:38 +01:00
|
|
|
case CODE_LOAD_LOCAL:
|
|
|
|
|
case CODE_STORE_LOCAL:
|
|
|
|
|
case CODE_LOAD_UPVALUE:
|
|
|
|
|
case CODE_STORE_UPVALUE:
|
|
|
|
|
case CODE_LOAD_FIELD_THIS:
|
|
|
|
|
case CODE_STORE_FIELD_THIS:
|
|
|
|
|
case CODE_LOAD_FIELD:
|
|
|
|
|
case CODE_STORE_FIELD:
|
2014-01-27 02:25:38 +01:00
|
|
|
case CODE_CLASS:
|
2014-01-08 08:03:38 +01:00
|
|
|
return 1;
|
|
|
|
|
|
2014-01-04 20:08:31 +01:00
|
|
|
case CODE_CONSTANT:
|
2015-01-26 16:45:21 +01:00
|
|
|
case CODE_LOAD_MODULE_VAR:
|
|
|
|
|
case CODE_STORE_MODULE_VAR:
|
2014-01-04 20:08:31 +01:00
|
|
|
case CODE_CALL_0:
|
|
|
|
|
case CODE_CALL_1:
|
|
|
|
|
case CODE_CALL_2:
|
|
|
|
|
case CODE_CALL_3:
|
|
|
|
|
case CODE_CALL_4:
|
|
|
|
|
case CODE_CALL_5:
|
|
|
|
|
case CODE_CALL_6:
|
|
|
|
|
case CODE_CALL_7:
|
|
|
|
|
case CODE_CALL_8:
|
|
|
|
|
case CODE_CALL_9:
|
|
|
|
|
case CODE_CALL_10:
|
|
|
|
|
case CODE_CALL_11:
|
|
|
|
|
case CODE_CALL_12:
|
|
|
|
|
case CODE_CALL_13:
|
|
|
|
|
case CODE_CALL_14:
|
|
|
|
|
case CODE_CALL_15:
|
|
|
|
|
case CODE_CALL_16:
|
2015-06-02 16:33:39 +02:00
|
|
|
case CODE_JUMP:
|
|
|
|
|
case CODE_LOOP:
|
|
|
|
|
case CODE_JUMP_IF:
|
|
|
|
|
case CODE_AND:
|
|
|
|
|
case CODE_OR:
|
|
|
|
|
case CODE_METHOD_INSTANCE:
|
|
|
|
|
case CODE_METHOD_STATIC:
|
|
|
|
|
return 2;
|
|
|
|
|
|
2014-01-04 20:08:31 +01:00
|
|
|
case CODE_SUPER_0:
|
|
|
|
|
case CODE_SUPER_1:
|
|
|
|
|
case CODE_SUPER_2:
|
|
|
|
|
case CODE_SUPER_3:
|
|
|
|
|
case CODE_SUPER_4:
|
|
|
|
|
case CODE_SUPER_5:
|
|
|
|
|
case CODE_SUPER_6:
|
|
|
|
|
case CODE_SUPER_7:
|
|
|
|
|
case CODE_SUPER_8:
|
|
|
|
|
case CODE_SUPER_9:
|
|
|
|
|
case CODE_SUPER_10:
|
|
|
|
|
case CODE_SUPER_11:
|
|
|
|
|
case CODE_SUPER_12:
|
|
|
|
|
case CODE_SUPER_13:
|
|
|
|
|
case CODE_SUPER_14:
|
|
|
|
|
case CODE_SUPER_15:
|
|
|
|
|
case CODE_SUPER_16:
|
2015-02-17 16:21:51 +01:00
|
|
|
return 4;
|
|
|
|
|
|
2013-12-24 19:15:50 +01:00
|
|
|
case CODE_CLOSURE:
|
|
|
|
|
{
|
2014-01-04 20:08:31 +01:00
|
|
|
int constant = (bytecode[ip + 1] << 8) | bytecode[ip + 2];
|
2013-12-27 01:58:03 +01:00
|
|
|
ObjFn* loadedFn = AS_FN(constants[constant]);
|
2013-12-24 19:15:50 +01:00
|
|
|
|
2014-01-27 02:24:59 +01:00
|
|
|
// There are two bytes for the constant, then two for each upvalue.
|
|
|
|
|
return 2 + (loadedFn->numUpvalues * 2);
|
2013-12-24 19:15:50 +01:00
|
|
|
}
|
2014-11-26 23:03:05 +01:00
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
UNREACHABLE();
|
2015-10-17 06:51:30 +02:00
|
|
|
return 0;
|
2013-12-24 19:15:50 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-01-06 17:01:04 +01:00
|
|
|
// 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)
|
|
|
|
|
{
|
|
|
|
|
loop->enclosing = compiler->loop;
|
2016-03-04 16:49:34 +01:00
|
|
|
loop->start = compiler->fn->code.count - 1;
|
2014-01-06 17:01:04 +01:00
|
|
|
loop->scopeDepth = compiler->scopeDepth;
|
|
|
|
|
compiler->loop = loop;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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)
|
2013-12-24 19:15:50 +01:00
|
|
|
{
|
2014-01-08 08:03:38 +01:00
|
|
|
compiler->loop->exitJump = emitJump(compiler, CODE_JUMP_IF);
|
2013-12-25 06:04:11 +01:00
|
|
|
}
|
2013-12-24 19:15:50 +01:00
|
|
|
|
2014-01-06 17:01:04 +01:00
|
|
|
// Compiles the body of the loop and tracks its extent so that contained "break"
|
|
|
|
|
// statements can be handled correctly.
|
|
|
|
|
static void loopBody(Compiler* compiler)
|
2013-12-25 06:04:11 +01:00
|
|
|
{
|
2016-03-04 16:49:34 +01:00
|
|
|
compiler->loop->body = compiler->fn->code.count;
|
2016-02-25 16:04:48 +01:00
|
|
|
statement(compiler);
|
2014-01-06 17:01:04 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
{
|
2016-02-19 15:57:38 +01:00
|
|
|
// We don't check for overflow here since the forward jump over the loop body
|
|
|
|
|
// will report an error for the same problem.
|
2016-03-04 16:49:34 +01:00
|
|
|
int loopOffset = compiler->fn->code.count - compiler->loop->start + 2;
|
2015-01-16 00:59:14 +01:00
|
|
|
emitShortArg(compiler, CODE_LOOP, loopOffset);
|
2014-01-06 17:01:04 +01:00
|
|
|
|
|
|
|
|
patchJump(compiler, compiler->loop->exitJump);
|
|
|
|
|
|
2013-12-24 19:15:50 +01:00
|
|
|
// Find any break placeholder instructions (which will be CODE_END in the
|
|
|
|
|
// bytecode) and replace them with real jumps.
|
2014-01-06 17:01:04 +01:00
|
|
|
int i = compiler->loop->body;
|
2016-03-04 16:49:34 +01:00
|
|
|
while (i < compiler->fn->code.count)
|
2013-12-24 19:15:50 +01:00
|
|
|
{
|
2016-03-04 16:49:34 +01:00
|
|
|
if (compiler->fn->code.data[i] == CODE_END)
|
2013-12-24 19:15:50 +01:00
|
|
|
{
|
2016-03-04 16:49:34 +01:00
|
|
|
compiler->fn->code.data[i] = CODE_JUMP;
|
2013-12-24 19:15:50 +01:00
|
|
|
patchJump(compiler, i + 1);
|
2014-11-27 03:33:54 +01:00
|
|
|
i += 3;
|
2013-12-24 19:15:50 +01:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Skip this instruction and its arguments.
|
2016-03-04 16:49:34 +01:00
|
|
|
i += 1 + getNumArguments(compiler->fn->code.data,
|
|
|
|
|
compiler->fn->constants.data, i);
|
2013-12-24 19:15:50 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-01-06 17:01:04 +01:00
|
|
|
compiler->loop = compiler->loop->enclosing;
|
2013-12-24 19:15:50 +01:00
|
|
|
}
|
|
|
|
|
|
2013-12-25 06:04:11 +01:00
|
|
|
static void forStatement(Compiler* compiler)
|
|
|
|
|
{
|
|
|
|
|
// A for statement like:
|
|
|
|
|
//
|
|
|
|
|
// for (i in sequence.expression) {
|
2016-02-28 01:05:50 +01:00
|
|
|
// System.print(i)
|
2013-12-25 06:04:11 +01:00
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// Is compiled to bytecode almost as if the source looked like this:
|
|
|
|
|
//
|
|
|
|
|
// {
|
|
|
|
|
// var seq_ = sequence.expression
|
|
|
|
|
// var iter_
|
2014-01-16 16:09:43 +01:00
|
|
|
// while (iter_ = seq_.iterate(iter_)) {
|
refactor: move newline handling from lexer into parser grammar rules
This change shifts newline tokenization from the lexer's readRawToken into the parser's nextToken, allowing the grammar to selectively ignore newlines in specific contexts (e.g., after operators like "|", "is", "for", "if", "while") while treating them as errors in others (e.g., after "var", "new", "class", "static", before commas in lists). The lexer now emits TOKEN_LINE for semicolons and newlines uniformly, and the parser decides which to elide. Test files are updated to reflect the new error expectations and to remove tests for newlines that are now grammatically allowed.
2014-04-04 16:51:18 +02:00
|
|
|
// var i = seq_.iteratorValue(iter_)
|
2016-02-28 01:05:50 +01:00
|
|
|
// System.print(i)
|
2013-12-25 06:04:11 +01:00
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// It's not exactly this, because the synthetic variables `seq_` and `iter_`
|
2014-01-16 16:09:43 +01:00
|
|
|
// actually get names that aren't valid Wren identfiers, but that's the basic
|
|
|
|
|
// idea.
|
2013-12-25 06:04:11 +01:00
|
|
|
//
|
|
|
|
|
// 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.");
|
refactor: move newline handling from lexer into parser grammar rules
This change shifts newline tokenization from the lexer's readRawToken into the parser's nextToken, allowing the grammar to selectively ignore newlines in specific contexts (e.g., after operators like "|", "is", "for", "if", "while") while treating them as errors in others (e.g., after "var", "new", "class", "static", before commas in lists). The lexer now emits TOKEN_LINE for semicolons and newlines uniformly, and the parser decides which to elide. Test files are updated to reflect the new error expectations and to remove tests for newlines that are now grammatically allowed.
2014-04-04 16:51:18 +02:00
|
|
|
ignoreNewlines(compiler);
|
2013-12-25 06:04:11 +01:00
|
|
|
|
|
|
|
|
// 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);
|
2015-08-22 16:56:38 +02:00
|
|
|
int seqSlot = addLocal(compiler, "seq ", 4);
|
2013-12-25 06:04:11 +01:00
|
|
|
|
|
|
|
|
// Create another hidden local for the iterator object.
|
|
|
|
|
null(compiler, false);
|
2015-08-22 16:56:38 +02:00
|
|
|
int iterSlot = addLocal(compiler, "iter ", 5);
|
2013-12-25 06:04:11 +01:00
|
|
|
|
|
|
|
|
consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after loop expression.");
|
|
|
|
|
|
2014-01-06 17:01:04 +01:00
|
|
|
Loop loop;
|
|
|
|
|
startLoop(compiler, &loop);
|
2013-12-25 06:04:11 +01:00
|
|
|
|
|
|
|
|
// Advance the iterator by calling the ".iterate" method on the sequence.
|
2014-12-07 04:59:11 +01:00
|
|
|
loadLocal(compiler, seqSlot);
|
|
|
|
|
loadLocal(compiler, iterSlot);
|
2013-12-25 06:04:11 +01:00
|
|
|
|
2016-02-28 01:05:50 +01:00
|
|
|
// Update and test the iterator.
|
2015-02-27 06:56:15 +01:00
|
|
|
callMethod(compiler, 1, "iterate(_)", 10);
|
2015-01-16 00:59:14 +01:00
|
|
|
emitByteArg(compiler, CODE_STORE_LOCAL, iterSlot);
|
2014-01-06 17:01:04 +01:00
|
|
|
testExitLoop(compiler);
|
2013-12-25 06:04:11 +01:00
|
|
|
|
|
|
|
|
// Get the current value in the sequence by calling ".iteratorValue".
|
2014-12-07 04:59:11 +01:00
|
|
|
loadLocal(compiler, seqSlot);
|
|
|
|
|
loadLocal(compiler, iterSlot);
|
2015-02-27 06:56:15 +01:00
|
|
|
callMethod(compiler, 1, "iteratorValue(_)", 16);
|
2013-12-25 06:04:11 +01:00
|
|
|
|
2014-01-06 17:01:04 +01:00
|
|
|
// 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);
|
2015-08-22 16:56:38 +02:00
|
|
|
addLocal(compiler, name, length);
|
2013-12-25 06:04:11 +01:00
|
|
|
|
2014-01-06 17:01:04 +01:00
|
|
|
loopBody(compiler);
|
2013-12-25 06:04:11 +01:00
|
|
|
|
2014-01-06 17:01:04 +01:00
|
|
|
// Loop variable.
|
2013-12-25 06:04:11 +01:00
|
|
|
popScope(compiler);
|
|
|
|
|
|
2014-01-06 17:01:04 +01:00
|
|
|
endLoop(compiler);
|
2013-12-25 06:04:11 +01:00
|
|
|
|
2014-01-06 17:01:04 +01:00
|
|
|
// Hidden variables.
|
2013-12-25 06:04:11 +01:00
|
|
|
popScope(compiler);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void whileStatement(Compiler* compiler)
|
|
|
|
|
{
|
2014-01-06 17:01:04 +01:00
|
|
|
Loop loop;
|
|
|
|
|
startLoop(compiler, &loop);
|
2013-12-25 06:04:11 +01:00
|
|
|
|
|
|
|
|
// Compile the condition.
|
|
|
|
|
consume(compiler, TOKEN_LEFT_PAREN, "Expect '(' after 'while'.");
|
|
|
|
|
expression(compiler);
|
|
|
|
|
consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after while condition.");
|
|
|
|
|
|
2014-01-06 17:01:04 +01:00
|
|
|
testExitLoop(compiler);
|
|
|
|
|
loopBody(compiler);
|
|
|
|
|
endLoop(compiler);
|
2013-12-25 06:04:11 +01:00
|
|
|
}
|
|
|
|
|
|
2016-02-25 16:04:48 +01:00
|
|
|
// Compiles a simple statement. These can only appear at the top-level or
|
|
|
|
|
// within curly blocks. Simple statements exclude variable binding statements
|
|
|
|
|
// like "var" and "class" which are not allowed directly in places like the
|
|
|
|
|
// branches of an "if" statement.
|
|
|
|
|
//
|
|
|
|
|
// Unlike expressions, statements do not leave a value on the stack.
|
2013-12-20 16:05:31 +01:00
|
|
|
void statement(Compiler* compiler)
|
|
|
|
|
{
|
2013-12-24 19:15:50 +01:00
|
|
|
if (match(compiler, TOKEN_BREAK))
|
|
|
|
|
{
|
2014-01-06 17:01:04 +01:00
|
|
|
if (compiler->loop == NULL)
|
2013-12-24 19:15:50 +01:00
|
|
|
{
|
|
|
|
|
error(compiler, "Cannot use 'break' outside of a loop.");
|
2014-01-06 17:01:04 +01:00
|
|
|
return;
|
2013-12-24 19:15:50 +01:00
|
|
|
}
|
|
|
|
|
|
2014-01-06 17:01:04 +01:00
|
|
|
// Since we will be jumping out of the scope, make sure any locals in it
|
|
|
|
|
// are discarded first.
|
|
|
|
|
discardLocals(compiler, compiler->loop->scopeDepth + 1);
|
|
|
|
|
|
2013-12-24 19:15:50 +01:00
|
|
|
// 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
|
|
|
|
|
// replace these with `CODE_JUMP` instructions with appropriate offsets.
|
|
|
|
|
// We use `CODE_END` here because that can't occur in the middle of
|
|
|
|
|
// bytecode.
|
2014-01-08 08:03:38 +01:00
|
|
|
emitJump(compiler, CODE_END);
|
2013-12-24 19:15:50 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-21 10:23:46 +01:00
|
|
|
if (match(compiler, TOKEN_FOR)) {
|
|
|
|
|
forStatement(compiler);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2013-12-25 06:04:11 +01:00
|
|
|
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
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.
|
2014-01-08 08:03:38 +01:00
|
|
|
int ifJump = emitJump(compiler, CODE_JUMP_IF);
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
|
|
|
|
|
// Compile the then branch.
|
2016-02-25 16:04:48 +01:00
|
|
|
statement(compiler);
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
|
|
|
|
|
// Compile the else branch if there is one.
|
|
|
|
|
if (match(compiler, TOKEN_ELSE))
|
|
|
|
|
{
|
2013-12-18 16:37:41 +01:00
|
|
|
// Jump over the else branch when the if branch is taken.
|
2014-01-08 08:03:38 +01:00
|
|
|
int elseJump = emitJump(compiler, CODE_JUMP);
|
2013-12-18 16:37:41 +01:00
|
|
|
patchJump(compiler, ifJump);
|
2016-02-28 01:05:50 +01:00
|
|
|
|
2016-02-25 16:04:48 +01:00
|
|
|
statement(compiler);
|
2013-12-18 16:37:41 +01:00
|
|
|
|
|
|
|
|
// Patch the jump over the else.
|
|
|
|
|
patchJump(compiler, elseJump);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
patchJump(compiler, ifJump);
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (match(compiler, TOKEN_RETURN))
|
|
|
|
|
{
|
|
|
|
|
// Compile the return value.
|
2014-04-03 16:48:19 +02:00
|
|
|
if (peek(compiler) == TOKEN_LINE)
|
2014-01-13 04:37:11 +01:00
|
|
|
{
|
|
|
|
|
// Implicitly return null if there is no value.
|
2015-12-14 07:57:40 +01:00
|
|
|
emitOp(compiler, CODE_NULL);
|
2014-01-13 04:37:11 +01:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
expression(compiler);
|
|
|
|
|
}
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
|
2015-12-14 07:57:40 +01:00
|
|
|
emitOp(compiler, CODE_RETURN);
|
2013-11-07 16:04:25 +01:00
|
|
|
return;
|
|
|
|
|
}
|
2013-10-22 22:25:39 +02:00
|
|
|
|
2015-01-21 10:23:46 +01:00
|
|
|
if (match(compiler, TOKEN_WHILE)) {
|
|
|
|
|
whileStatement(compiler);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2013-12-24 19:27:48 +01:00
|
|
|
|
2016-02-25 16:04:48 +01:00
|
|
|
// Block statement.
|
|
|
|
|
if (match(compiler, TOKEN_LEFT_BRACE))
|
|
|
|
|
{
|
|
|
|
|
pushScope(compiler);
|
|
|
|
|
if (finishBlock(compiler))
|
|
|
|
|
{
|
|
|
|
|
// Block was an expression, so discard it.
|
|
|
|
|
emitOp(compiler, CODE_POP);
|
|
|
|
|
}
|
|
|
|
|
popScope(compiler);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-24 19:27:48 +01:00
|
|
|
// Expression statement.
|
|
|
|
|
expression(compiler);
|
2015-12-14 07:57:40 +01:00
|
|
|
emitOp(compiler, CODE_POP);
|
2013-12-24 19:27:48 +01:00
|
|
|
}
|
|
|
|
|
|
2015-07-10 18:18:22 +02:00
|
|
|
// Creates a matching constructor method for an initializer with [signature]
|
|
|
|
|
// and [initializerSymbol].
|
|
|
|
|
//
|
|
|
|
|
// Construction is a two-stage process in Wren that involves two separate
|
|
|
|
|
// methods. There is a static method that allocates a new instance of the class.
|
|
|
|
|
// It then invokes an initializer method on the new instance, forwarding all of
|
|
|
|
|
// the constructor arguments to it.
|
|
|
|
|
//
|
|
|
|
|
// The allocator method always has a fixed implementation:
|
|
|
|
|
//
|
|
|
|
|
// CODE_CONSTRUCT - Replace the class in slot 0 with a new instance of it.
|
|
|
|
|
// CODE_CALL - Invoke the initializer on the new instance.
|
|
|
|
|
//
|
|
|
|
|
// This creates that method and calls the initializer with [initializerSymbol].
|
|
|
|
|
static void createConstructor(Compiler* compiler, Signature* signature,
|
|
|
|
|
int initializerSymbol)
|
|
|
|
|
{
|
|
|
|
|
Compiler methodCompiler;
|
|
|
|
|
initCompiler(&methodCompiler, compiler->parser, compiler, false);
|
|
|
|
|
|
|
|
|
|
// Allocate the instance.
|
2015-12-14 07:57:40 +01:00
|
|
|
emitOp(&methodCompiler, compiler->enclosingClass->isForeign
|
2015-08-15 21:07:53 +02:00
|
|
|
? CODE_FOREIGN_CONSTRUCT : CODE_CONSTRUCT);
|
2015-07-10 18:18:22 +02:00
|
|
|
|
|
|
|
|
// Run its initializer.
|
2015-07-10 18:22:04 +02:00
|
|
|
emitShortArg(&methodCompiler, (Code)(CODE_CALL_0 + signature->arity),
|
2015-07-10 18:18:22 +02:00
|
|
|
initializerSymbol);
|
|
|
|
|
|
|
|
|
|
// Return the instance.
|
2015-12-14 07:57:40 +01:00
|
|
|
emitOp(&methodCompiler, CODE_RETURN);
|
2015-07-10 18:18:22 +02:00
|
|
|
|
|
|
|
|
endCompiler(&methodCompiler, "", 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Loads the enclosing class onto the stack and then binds the function already
|
|
|
|
|
// on the stack as a method on that class.
|
2016-02-12 16:43:44 +01:00
|
|
|
static void defineMethod(Compiler* compiler, Variable classVariable,
|
|
|
|
|
bool isStatic, int methodSymbol)
|
2015-07-10 18:18:22 +02:00
|
|
|
{
|
|
|
|
|
// Load the class. We have to do this for each method because we can't
|
|
|
|
|
// keep the class on top of the stack. If there are static fields, they
|
|
|
|
|
// will be locals above the initial variable slot for the class on the
|
|
|
|
|
// stack. To skip past those, we just load the class each time right before
|
|
|
|
|
// defining a method.
|
2016-02-12 16:43:44 +01:00
|
|
|
loadVariable(compiler, classVariable);
|
|
|
|
|
|
2015-07-10 18:18:22 +02:00
|
|
|
// Define the method.
|
|
|
|
|
Code instruction = isStatic ? CODE_METHOD_STATIC : CODE_METHOD_INSTANCE;
|
|
|
|
|
emitShortArg(compiler, instruction, methodSymbol);
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-15 15:15:55 +01:00
|
|
|
// Declares a method in the enclosing class with [signature].
|
|
|
|
|
//
|
|
|
|
|
// Reports an error if a method with that signature is already declared.
|
|
|
|
|
// Returns the symbol for the method.
|
|
|
|
|
static int declareMethod(Compiler* compiler, Signature* signature,
|
|
|
|
|
const char* name, int length)
|
2016-03-13 04:14:19 +01:00
|
|
|
{
|
2016-03-15 15:15:55 +01:00
|
|
|
int symbol = signatureSymbol(compiler, signature);
|
|
|
|
|
|
|
|
|
|
// See if the class has already declared method with this signature.
|
|
|
|
|
ClassCompiler* clas = compiler->enclosingClass;
|
|
|
|
|
IntBuffer* methods = clas->inStatic ? &clas->staticMethods : &clas->methods;
|
|
|
|
|
for (int i = 0; i < methods->count; i++)
|
2016-03-13 04:14:19 +01:00
|
|
|
{
|
2016-03-15 15:15:55 +01:00
|
|
|
if (methods->data[i] == symbol)
|
|
|
|
|
{
|
|
|
|
|
const char* staticPrefix = clas->inStatic ? "static " : "";
|
|
|
|
|
error(compiler, "Class %s already defines a %smethod '%s'.",
|
|
|
|
|
&compiler->enclosingClass->name->value, staticPrefix, name);
|
|
|
|
|
break;
|
|
|
|
|
}
|
2016-03-13 04:14:19 +01:00
|
|
|
}
|
|
|
|
|
|
2016-03-15 15:15:55 +01:00
|
|
|
wrenIntBufferWrite(compiler->parser->vm, methods, symbol);
|
|
|
|
|
return symbol;
|
2016-03-13 04:14:19 +01:00
|
|
|
}
|
|
|
|
|
|
2015-07-10 18:18:22 +02:00
|
|
|
// Compiles a method definition inside a class body.
|
|
|
|
|
//
|
|
|
|
|
// Returns `true` if it compiled successfully, or `false` if the method couldn't
|
|
|
|
|
// be parsed.
|
2016-02-12 16:43:44 +01:00
|
|
|
static bool method(Compiler* compiler, Variable classVariable)
|
2014-01-19 22:01:51 +01:00
|
|
|
{
|
2015-07-10 18:18:22 +02:00
|
|
|
// TODO: What about foreign constructors?
|
|
|
|
|
bool isForeign = match(compiler, TOKEN_FOREIGN);
|
2016-02-28 21:53:34 +01:00
|
|
|
bool isStatic = match(compiler, TOKEN_STATIC);
|
|
|
|
|
compiler->enclosingClass->inStatic = isStatic;
|
2015-07-10 18:18:22 +02:00
|
|
|
|
|
|
|
|
SignatureFn signatureFn = rules[compiler->parser->current.type].method;
|
|
|
|
|
nextToken(compiler->parser);
|
|
|
|
|
|
|
|
|
|
if (signatureFn == NULL)
|
|
|
|
|
{
|
|
|
|
|
error(compiler, "Expect method definition.");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Build the method signature.
|
2015-08-06 16:34:19 +02:00
|
|
|
Signature signature = signatureFromToken(compiler, SIG_GETTER);
|
2015-12-15 02:10:10 +01:00
|
|
|
compiler->enclosingClass->signature = &signature;
|
2014-01-19 22:01:51 +01:00
|
|
|
|
|
|
|
|
Compiler methodCompiler;
|
|
|
|
|
initCompiler(&methodCompiler, compiler->parser, compiler, false);
|
|
|
|
|
|
|
|
|
|
// Compile the method signature.
|
2015-02-27 06:56:15 +01:00
|
|
|
signatureFn(&methodCompiler, &signature);
|
2015-07-10 18:18:22 +02:00
|
|
|
|
2016-02-28 21:53:34 +01:00
|
|
|
if (isStatic && signature.type == SIG_INITIALIZER)
|
2015-07-10 18:18:22 +02:00
|
|
|
{
|
|
|
|
|
error(compiler, "A constructor cannot be static.");
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-27 16:10:44 +01:00
|
|
|
// Include the full signature in debug messages in stack traces.
|
2015-03-24 15:58:15 +01:00
|
|
|
char fullSignature[MAX_METHOD_SIGNATURE];
|
2015-02-27 16:10:44 +01:00
|
|
|
int length;
|
2015-03-24 15:58:15 +01:00
|
|
|
signatureToString(&signature, fullSignature, &length);
|
2016-03-15 15:15:55 +01:00
|
|
|
|
|
|
|
|
// Check for duplicate methods. Doesn't matter that it's already been
|
|
|
|
|
// defined, error will discard bytecode anyway.
|
|
|
|
|
// Check if the method table already contains this symbol
|
|
|
|
|
int methodSymbol = declareMethod(compiler, &signature, fullSignature, length);
|
2016-02-24 07:07:08 +01:00
|
|
|
|
2015-03-24 15:58:15 +01:00
|
|
|
if (isForeign)
|
|
|
|
|
{
|
|
|
|
|
// Define a constant for the signature.
|
2015-09-12 19:14:04 +02:00
|
|
|
emitConstant(compiler, wrenNewString(compiler->parser->vm,
|
|
|
|
|
fullSignature, length));
|
2015-03-24 15:58:15 +01:00
|
|
|
|
|
|
|
|
// We don't need the function we started compiling in the parameter list
|
|
|
|
|
// any more.
|
2016-03-04 16:49:34 +01:00
|
|
|
methodCompiler.parser->vm->compiler = methodCompiler.parent;
|
2015-03-24 15:58:15 +01:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
consume(compiler, TOKEN_LEFT_BRACE, "Expect '{' to begin method body.");
|
2015-07-10 18:18:22 +02:00
|
|
|
finishBody(&methodCompiler, signature.type == SIG_INITIALIZER);
|
2015-03-24 15:58:15 +01:00
|
|
|
endCompiler(&methodCompiler, fullSignature, length);
|
|
|
|
|
}
|
2015-07-10 18:18:22 +02:00
|
|
|
|
|
|
|
|
// Define the method. For a constructor, this defines the instance
|
|
|
|
|
// initializer method.
|
2016-02-28 21:53:34 +01:00
|
|
|
defineMethod(compiler, classVariable, isStatic, methodSymbol);
|
2015-02-27 16:10:44 +01:00
|
|
|
|
2015-07-10 18:18:22 +02:00
|
|
|
if (signature.type == SIG_INITIALIZER)
|
|
|
|
|
{
|
|
|
|
|
// Also define a matching constructor method on the metaclass.
|
|
|
|
|
signature.type = SIG_METHOD;
|
|
|
|
|
int constructorSymbol = signatureSymbol(compiler, &signature);
|
|
|
|
|
|
|
|
|
|
createConstructor(compiler, &signature, methodSymbol);
|
2016-02-12 16:43:44 +01:00
|
|
|
defineMethod(compiler, classVariable, true, constructorSymbol);
|
2015-07-10 18:18:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-24 19:27:48 +01:00
|
|
|
// Compiles a class definition. Assumes the "class" token has already been
|
2015-08-15 21:07:53 +02:00
|
|
|
// consumed (along with a possibly preceding "foreign" token).
|
|
|
|
|
static void classDefinition(Compiler* compiler, bool isForeign)
|
2013-12-24 19:27:48 +01:00
|
|
|
{
|
|
|
|
|
// Create a variable to store the class in.
|
2016-02-12 16:43:44 +01:00
|
|
|
Variable classVariable;
|
|
|
|
|
classVariable.scope = compiler->scopeDepth == -1 ? SCOPE_MODULE : SCOPE_LOCAL;
|
|
|
|
|
classVariable.index = declareNamedVariable(compiler);
|
|
|
|
|
|
2016-03-13 04:14:19 +01:00
|
|
|
// Create shared class name value
|
|
|
|
|
Value classNameString = wrenNewString(compiler->parser->vm,
|
|
|
|
|
compiler->parser->previous.start, compiler->parser->previous.length);
|
|
|
|
|
|
|
|
|
|
// Create class name string to track method duplicates
|
|
|
|
|
ObjString* className = AS_STRING(classNameString);
|
2016-02-24 07:07:08 +01:00
|
|
|
|
2014-01-29 00:31:11 +01:00
|
|
|
// Make a string constant for the name.
|
2016-03-13 04:14:19 +01:00
|
|
|
emitConstant(compiler, classNameString);
|
2014-01-29 00:31:11 +01:00
|
|
|
|
2013-12-24 19:27:48 +01:00
|
|
|
// Load the superclass (if there is one).
|
|
|
|
|
if (match(compiler, TOKEN_IS))
|
|
|
|
|
{
|
2016-03-04 01:30:48 +01:00
|
|
|
parsePrecedence(compiler, PREC_CALL);
|
2013-12-24 19:27:48 +01:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2015-09-12 19:14:04 +02:00
|
|
|
// Implicitly inherit from Object.
|
|
|
|
|
loadCoreVariable(compiler, "Object");
|
2013-12-24 19:27:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Store a placeholder for the number of fields argument. We don't know
|
|
|
|
|
// the value until we've compiled all the methods to see which fields are
|
|
|
|
|
// used.
|
2015-08-15 21:07:53 +02:00
|
|
|
int numFieldsInstruction = -1;
|
|
|
|
|
if (isForeign)
|
|
|
|
|
{
|
2015-12-14 07:57:40 +01:00
|
|
|
emitOp(compiler, CODE_FOREIGN_CLASS);
|
2015-08-15 21:07:53 +02:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
numFieldsInstruction = emitByteArg(compiler, CODE_CLASS, 255);
|
|
|
|
|
}
|
2013-12-24 19:27:48 +01:00
|
|
|
|
2014-01-19 22:01:51 +01:00
|
|
|
// Store it in its name.
|
2016-02-12 16:43:44 +01:00
|
|
|
defineVariable(compiler, classVariable.index);
|
2014-01-19 22:01:51 +01:00
|
|
|
|
|
|
|
|
// Push a local variable scope. Static fields in a class body are hoisted out
|
|
|
|
|
// into local variables declared in this scope. Methods that use them will
|
|
|
|
|
// have upvalues referencing them.
|
|
|
|
|
pushScope(compiler);
|
|
|
|
|
|
2014-01-18 23:03:52 +01:00
|
|
|
ClassCompiler classCompiler;
|
2015-08-15 21:07:53 +02:00
|
|
|
classCompiler.isForeign = isForeign;
|
2016-02-24 07:07:08 +01:00
|
|
|
classCompiler.name = className;
|
2014-01-18 23:03:52 +01:00
|
|
|
|
2013-12-24 19:27:48 +01:00
|
|
|
// Set up a symbol table for the class's fields. We'll initially compile
|
2015-02-21 09:09:55 +01:00
|
|
|
// them to slots starting at zero. When the method is bound to the class, the
|
|
|
|
|
// bytecode will be adjusted by [wrenBindMethod] to take inherited fields
|
|
|
|
|
// into account.
|
2015-12-15 02:10:10 +01:00
|
|
|
wrenSymbolTableInit(&classCompiler.fields);
|
2016-03-13 04:14:19 +01:00
|
|
|
|
2016-03-15 15:15:55 +01:00
|
|
|
// Set up symbol buffers to track duplicate static and instance methods.
|
|
|
|
|
wrenIntBufferInit(&classCompiler.methods);
|
2016-02-28 21:53:34 +01:00
|
|
|
wrenIntBufferInit(&classCompiler.staticMethods);
|
2014-01-19 22:01:51 +01:00
|
|
|
compiler->enclosingClass = &classCompiler;
|
|
|
|
|
|
2013-12-24 19:27:48 +01:00
|
|
|
// Compile the method definitions.
|
2014-04-03 02:31:58 +02:00
|
|
|
consume(compiler, TOKEN_LEFT_BRACE, "Expect '{' after class declaration.");
|
refactor: move newline handling from lexer into parser grammar rules
This change shifts newline tokenization from the lexer's readRawToken into the parser's nextToken, allowing the grammar to selectively ignore newlines in specific contexts (e.g., after operators like "|", "is", "for", "if", "while") while treating them as errors in others (e.g., after "var", "new", "class", "static", before commas in lists). The lexer now emits TOKEN_LINE for semicolons and newlines uniformly, and the parser decides which to elide. Test files are updated to reflect the new error expectations and to remove tests for newlines that are now grammatically allowed.
2014-04-04 16:51:18 +02:00
|
|
|
matchLine(compiler);
|
2014-04-03 02:31:58 +02:00
|
|
|
|
2013-12-24 19:27:48 +01:00
|
|
|
while (!match(compiler, TOKEN_RIGHT_BRACE))
|
|
|
|
|
{
|
2016-02-12 16:43:44 +01:00
|
|
|
if (!method(compiler, classVariable)) break;
|
2015-07-10 18:18:22 +02:00
|
|
|
|
2014-02-15 05:15:49 +01:00
|
|
|
// Don't require a newline after the last definition.
|
|
|
|
|
if (match(compiler, TOKEN_RIGHT_BRACE)) break;
|
|
|
|
|
|
refactor: move newline handling from lexer into parser grammar rules
This change shifts newline tokenization from the lexer's readRawToken into the parser's nextToken, allowing the grammar to selectively ignore newlines in specific contexts (e.g., after operators like "|", "is", "for", "if", "while") while treating them as errors in others (e.g., after "var", "new", "class", "static", before commas in lists). The lexer now emits TOKEN_LINE for semicolons and newlines uniformly, and the parser decides which to elide. Test files are updated to reflect the new error expectations and to remove tests for newlines that are now grammatically allowed.
2014-04-04 16:51:18 +02:00
|
|
|
consumeLine(compiler, "Expect newline after definition in class.");
|
2013-12-24 19:27:48 +01:00
|
|
|
}
|
2015-07-10 18:18:22 +02:00
|
|
|
|
2013-12-24 19:27:48 +01:00
|
|
|
// Update the class with the number of fields.
|
2015-08-15 21:07:53 +02:00
|
|
|
if (!isForeign)
|
|
|
|
|
{
|
2016-03-04 16:49:34 +01:00
|
|
|
compiler->fn->code.data[numFieldsInstruction] =
|
2015-12-15 02:10:10 +01:00
|
|
|
(uint8_t)classCompiler.fields.count;
|
2015-08-15 21:07:53 +02:00
|
|
|
}
|
|
|
|
|
|
2016-03-15 15:15:55 +01:00
|
|
|
// Clear symbol tables for tracking field and method names.
|
2015-12-15 02:10:10 +01:00
|
|
|
wrenSymbolTableClear(compiler->parser->vm, &classCompiler.fields);
|
2016-03-15 15:15:55 +01:00
|
|
|
wrenIntBufferClear(compiler->parser->vm, &classCompiler.methods);
|
2016-02-28 21:53:34 +01:00
|
|
|
wrenIntBufferClear(compiler->parser->vm, &classCompiler.staticMethods);
|
2014-01-19 22:01:51 +01:00
|
|
|
compiler->enclosingClass = NULL;
|
|
|
|
|
popScope(compiler);
|
2013-12-24 19:27:48 +01:00
|
|
|
}
|
|
|
|
|
|
2015-08-22 16:56:38 +02:00
|
|
|
// Compiles an "import" statement.
|
2016-02-27 07:43:54 +01:00
|
|
|
//
|
|
|
|
|
// An import just desugars to calling a few special core methods. Given:
|
|
|
|
|
//
|
|
|
|
|
// import "foo" for Bar, Baz
|
|
|
|
|
//
|
|
|
|
|
// We compile it to:
|
|
|
|
|
//
|
|
|
|
|
// System.importModule("foo")
|
|
|
|
|
// var Bar = System.getModuleVariable("foo", "Bar")
|
|
|
|
|
// var Baz = System.getModuleVariable("foo", "Baz")
|
2015-02-15 01:46:00 +01:00
|
|
|
static void import(Compiler* compiler)
|
|
|
|
|
{
|
2015-12-08 04:41:10 +01:00
|
|
|
ignoreNewlines(compiler);
|
2015-02-15 01:46:00 +01:00
|
|
|
consume(compiler, TOKEN_STRING, "Expect a string after 'import'.");
|
2015-11-11 16:55:48 +01:00
|
|
|
int moduleConstant = addConstant(compiler, compiler->parser->previous.value);
|
2015-02-15 01:46:00 +01:00
|
|
|
|
2015-02-17 07:45:58 +01:00
|
|
|
// Load the module.
|
2016-02-27 07:43:54 +01:00
|
|
|
loadCoreVariable(compiler, "System");
|
|
|
|
|
emitShortArg(compiler, CODE_CONSTANT, moduleConstant);
|
|
|
|
|
callMethod(compiler, 1, "importModule(_)", 15);
|
2015-02-17 07:45:58 +01:00
|
|
|
|
|
|
|
|
// Discard the unused result value from calling the module's fiber.
|
2015-12-14 07:57:40 +01:00
|
|
|
emitOp(compiler, CODE_POP);
|
2015-02-15 01:46:00 +01:00
|
|
|
|
2015-02-17 16:32:33 +01:00
|
|
|
// The for clause is optional.
|
|
|
|
|
if (!match(compiler, TOKEN_FOR)) return;
|
2015-02-15 01:46:00 +01:00
|
|
|
|
2015-02-17 16:32:33 +01:00
|
|
|
// Compile the comma-separated list of variables to import.
|
|
|
|
|
do
|
|
|
|
|
{
|
2015-12-08 04:41:10 +01:00
|
|
|
ignoreNewlines(compiler);
|
2015-04-01 16:05:40 +02:00
|
|
|
int slot = declareNamedVariable(compiler);
|
2015-02-17 16:32:33 +01:00
|
|
|
|
|
|
|
|
// Define a string constant for the variable name.
|
|
|
|
|
int variableConstant = addConstant(compiler,
|
|
|
|
|
wrenNewString(compiler->parser->vm,
|
|
|
|
|
compiler->parser->previous.start,
|
|
|
|
|
compiler->parser->previous.length));
|
|
|
|
|
|
|
|
|
|
// Load the variable from the other module.
|
2016-02-27 07:43:54 +01:00
|
|
|
loadCoreVariable(compiler, "System");
|
|
|
|
|
emitShortArg(compiler, CODE_CONSTANT, moduleConstant);
|
|
|
|
|
emitShortArg(compiler, CODE_CONSTANT, variableConstant);
|
|
|
|
|
callMethod(compiler, 2, "getModuleVariable(_,_)", 22);
|
|
|
|
|
|
2015-02-17 16:32:33 +01:00
|
|
|
// Store the result in the variable here.
|
|
|
|
|
defineVariable(compiler, slot);
|
|
|
|
|
} while (match(compiler, TOKEN_COMMA));
|
2015-02-15 01:46:00 +01:00
|
|
|
}
|
|
|
|
|
|
2015-08-22 16:56:38 +02:00
|
|
|
// Compiles a "var" variable definition statement.
|
2013-12-25 06:04:11 +01:00
|
|
|
static void variableDefinition(Compiler* compiler)
|
2013-12-24 19:27:48 +01:00
|
|
|
{
|
2015-04-01 16:05:40 +02:00
|
|
|
// Grab its name, but don't declare it yet. A (local) variable shouldn't be
|
|
|
|
|
// in scope in its own initializer.
|
|
|
|
|
consume(compiler, TOKEN_NAME, "Expect variable name.");
|
|
|
|
|
Token nameToken = compiler->parser->previous;
|
2013-12-25 06:04:11 +01:00
|
|
|
|
|
|
|
|
// Compile the initializer.
|
|
|
|
|
if (match(compiler, TOKEN_EQ))
|
2013-12-24 19:27:48 +01:00
|
|
|
{
|
2013-12-25 06:04:11 +01:00
|
|
|
expression(compiler);
|
2013-12-24 19:27:48 +01:00
|
|
|
}
|
2013-12-25 06:04:11 +01:00
|
|
|
else
|
2013-10-24 21:39:01 +02:00
|
|
|
{
|
2013-12-25 06:04:11 +01:00
|
|
|
// Default initialize it to null.
|
|
|
|
|
null(compiler, false);
|
|
|
|
|
}
|
2013-10-22 22:25:39 +02:00
|
|
|
|
2015-04-01 16:05:40 +02:00
|
|
|
// Now put it in scope.
|
|
|
|
|
int symbol = declareVariable(compiler, &nameToken);
|
2013-12-25 06:04:11 +01:00
|
|
|
defineVariable(compiler, symbol);
|
|
|
|
|
}
|
2013-10-22 22:25:39 +02:00
|
|
|
|
2013-12-25 06:04:11 +01:00
|
|
|
// 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)
|
|
|
|
|
{
|
2015-07-21 16:24:53 +02:00
|
|
|
if (match(compiler, TOKEN_CLASS))
|
|
|
|
|
{
|
2015-08-15 21:07:53 +02:00
|
|
|
classDefinition(compiler, false);
|
|
|
|
|
}
|
2015-08-22 16:56:38 +02:00
|
|
|
else if (match(compiler, TOKEN_FOREIGN))
|
2015-08-15 21:07:53 +02:00
|
|
|
{
|
|
|
|
|
consume(compiler, TOKEN_CLASS, "Expect 'class' after 'foreign'.");
|
|
|
|
|
classDefinition(compiler, true);
|
2015-01-21 10:23:46 +01:00
|
|
|
}
|
2015-08-22 16:56:38 +02:00
|
|
|
else if (match(compiler, TOKEN_IMPORT))
|
2015-07-21 16:24:53 +02:00
|
|
|
{
|
2015-02-15 01:46:00 +01:00
|
|
|
import(compiler);
|
|
|
|
|
}
|
2015-08-22 16:56:38 +02:00
|
|
|
else if (match(compiler, TOKEN_VAR))
|
2015-07-21 16:24:53 +02:00
|
|
|
{
|
2015-01-21 10:23:46 +01:00
|
|
|
variableDefinition(compiler);
|
|
|
|
|
}
|
2015-08-22 16:56:38 +02:00
|
|
|
else
|
|
|
|
|
{
|
2016-02-25 16:04:48 +01:00
|
|
|
statement(compiler);
|
2015-08-22 16:56:38 +02:00
|
|
|
}
|
2013-10-22 21:16:39 +02:00
|
|
|
}
|
|
|
|
|
|
2015-10-14 16:37:13 +02:00
|
|
|
ObjFn* wrenCompile(WrenVM* vm, ObjModule* module, const char* source,
|
|
|
|
|
bool printErrors)
|
2013-10-22 22:25:39 +02:00
|
|
|
{
|
2013-11-07 16:04:25 +01:00
|
|
|
Parser parser;
|
|
|
|
|
parser.vm = vm;
|
2015-02-05 21:03:19 +01:00
|
|
|
parser.module = module;
|
2013-11-07 16:04:25 +01:00
|
|
|
parser.source = source;
|
2013-10-22 22:25:39 +02:00
|
|
|
|
2013-12-02 00:22:24 +01:00
|
|
|
parser.tokenStart = source;
|
|
|
|
|
parser.currentChar = source;
|
2013-11-07 16:04:25 +01:00
|
|
|
parser.currentLine = 1;
|
2015-11-11 16:55:48 +01:00
|
|
|
parser.numParens = 0;
|
2013-11-07 16:04:25 +01:00
|
|
|
|
|
|
|
|
// Zero-init the current token. This will get copied to previous when
|
|
|
|
|
// advance() is called below.
|
2013-11-29 19:53:56 +01:00
|
|
|
parser.current.type = TOKEN_ERROR;
|
2013-12-02 00:22:24 +01:00
|
|
|
parser.current.start = source;
|
|
|
|
|
parser.current.length = 0;
|
2013-11-07 16:04:25 +01:00
|
|
|
parser.current.line = 0;
|
2015-11-11 16:55:48 +01:00
|
|
|
parser.current.value = UNDEFINED_VAL;
|
2013-11-07 16:04:25 +01:00
|
|
|
|
2014-01-02 16:31:31 +01:00
|
|
|
// Ignore leading newlines.
|
|
|
|
|
parser.skipNewlines = true;
|
2015-04-04 06:22:58 +02:00
|
|
|
parser.printErrors = printErrors;
|
2014-01-02 16:31:31 +01:00
|
|
|
parser.hasError = false;
|
|
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
// Read the first token.
|
|
|
|
|
nextToken(&parser);
|
2013-10-24 21:39:01 +02:00
|
|
|
|
2013-11-10 00:42:23 +01:00
|
|
|
Compiler compiler;
|
2014-01-18 23:03:52 +01:00
|
|
|
initCompiler(&compiler, &parser, NULL, true);
|
refactor: move newline handling from lexer into parser grammar rules
This change shifts newline tokenization from the lexer's readRawToken into the parser's nextToken, allowing the grammar to selectively ignore newlines in specific contexts (e.g., after operators like "|", "is", "for", "if", "while") while treating them as errors in others (e.g., after "var", "new", "class", "static", before commas in lists). The lexer now emits TOKEN_LINE for semicolons and newlines uniformly, and the parser decides which to elide. Test files are updated to reflect the new error expectations and to remove tests for newlines that are now grammatically allowed.
2014-04-04 16:51:18 +02:00
|
|
|
ignoreNewlines(&compiler);
|
2013-11-10 00:42:23 +01:00
|
|
|
|
2015-01-04 22:00:53 +01:00
|
|
|
while (!match(&compiler, TOKEN_EOF))
|
2013-11-10 00:42:23 +01:00
|
|
|
{
|
2013-12-24 19:27:48 +01:00
|
|
|
definition(&compiler);
|
2013-11-10 00:42:23 +01:00
|
|
|
|
|
|
|
|
// If there is no newline, it must be the end of the block on the same line.
|
refactor: move newline handling from lexer into parser grammar rules
This change shifts newline tokenization from the lexer's readRawToken into the parser's nextToken, allowing the grammar to selectively ignore newlines in specific contexts (e.g., after operators like "|", "is", "for", "if", "while") while treating them as errors in others (e.g., after "var", "new", "class", "static", before commas in lists). The lexer now emits TOKEN_LINE for semicolons and newlines uniformly, and the parser decides which to elide. Test files are updated to reflect the new error expectations and to remove tests for newlines that are now grammatically allowed.
2014-04-04 16:51:18 +02:00
|
|
|
if (!matchLine(&compiler))
|
2013-11-10 00:42:23 +01:00
|
|
|
{
|
2013-11-14 08:06:53 +01:00
|
|
|
consume(&compiler, TOKEN_EOF, "Expect end of file.");
|
2013-11-10 00:42:23 +01:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-14 07:57:40 +01:00
|
|
|
emitOp(&compiler, CODE_NULL);
|
|
|
|
|
emitOp(&compiler, CODE_RETURN);
|
feat: implement closures with upvalue support and statement/expression separation
Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
2013-12-04 16:43:50 +01:00
|
|
|
|
2015-01-26 16:45:21 +01:00
|
|
|
// See if there are any implicitly declared module-level variables that never
|
2016-03-16 16:00:28 +01:00
|
|
|
// got an explicit definition. They will have values that are numbers
|
|
|
|
|
// indicating the line where the variable was first used.
|
2015-01-26 16:45:21 +01:00
|
|
|
for (int i = 0; i < parser.module->variables.count; i++)
|
2015-01-15 08:08:25 +01:00
|
|
|
{
|
2016-03-16 16:00:28 +01:00
|
|
|
if (IS_NUM(parser.module->variables.data[i]))
|
2015-01-15 08:08:25 +01:00
|
|
|
{
|
2016-03-16 16:00:28 +01:00
|
|
|
// Synthesize a token for the original use site.
|
|
|
|
|
parser.previous.type = TOKEN_NAME;
|
|
|
|
|
parser.previous.start = parser.module->variableNames.data[i].buffer;
|
|
|
|
|
parser.previous.length = parser.module->variableNames.data[i].length;
|
|
|
|
|
parser.previous.line = (int)AS_NUM(parser.module->variables.data[i]);
|
|
|
|
|
error(&compiler, "Variable is used but not defined.");
|
2015-01-15 08:08:25 +01:00
|
|
|
}
|
|
|
|
|
}
|
2015-11-11 16:55:48 +01:00
|
|
|
|
2014-01-01 22:25:24 +01:00
|
|
|
return endCompiler(&compiler, "(script)", 8);
|
2013-11-07 16:04:25 +01:00
|
|
|
}
|
2013-12-11 01:13:25 +01:00
|
|
|
|
2014-01-04 20:08:31 +01:00
|
|
|
void wrenBindMethodCode(ObjClass* classObj, ObjFn* fn)
|
2013-12-11 01:13:25 +01:00
|
|
|
{
|
|
|
|
|
int ip = 0;
|
|
|
|
|
for (;;)
|
|
|
|
|
{
|
2016-03-04 16:49:34 +01:00
|
|
|
Code instruction = (Code)fn->code.data[ip++];
|
2013-12-11 01:13:25 +01:00
|
|
|
switch (instruction)
|
|
|
|
|
{
|
|
|
|
|
case CODE_LOAD_FIELD:
|
|
|
|
|
case CODE_STORE_FIELD:
|
2013-12-23 20:17:40 +01:00
|
|
|
case CODE_LOAD_FIELD_THIS:
|
|
|
|
|
case CODE_STORE_FIELD_THIS:
|
2014-01-15 16:59:10 +01:00
|
|
|
// Shift this class's fields down past the inherited ones. We don't
|
|
|
|
|
// check for overflow here because we'll see if the number of fields
|
|
|
|
|
// overflows when the subclass is created.
|
2016-03-04 16:49:34 +01:00
|
|
|
fn->code.data[ip++] += classObj->superclass->numFields;
|
2013-12-11 01:13:25 +01:00
|
|
|
break;
|
|
|
|
|
|
2015-06-02 16:33:39 +02:00
|
|
|
case CODE_SUPER_0:
|
|
|
|
|
case CODE_SUPER_1:
|
|
|
|
|
case CODE_SUPER_2:
|
|
|
|
|
case CODE_SUPER_3:
|
|
|
|
|
case CODE_SUPER_4:
|
|
|
|
|
case CODE_SUPER_5:
|
|
|
|
|
case CODE_SUPER_6:
|
|
|
|
|
case CODE_SUPER_7:
|
|
|
|
|
case CODE_SUPER_8:
|
|
|
|
|
case CODE_SUPER_9:
|
|
|
|
|
case CODE_SUPER_10:
|
|
|
|
|
case CODE_SUPER_11:
|
|
|
|
|
case CODE_SUPER_12:
|
|
|
|
|
case CODE_SUPER_13:
|
|
|
|
|
case CODE_SUPER_14:
|
|
|
|
|
case CODE_SUPER_15:
|
|
|
|
|
case CODE_SUPER_16:
|
|
|
|
|
{
|
|
|
|
|
// Skip over the symbol.
|
|
|
|
|
ip += 2;
|
|
|
|
|
|
|
|
|
|
// Fill in the constant slot with a reference to the superclass.
|
2016-03-04 16:49:34 +01:00
|
|
|
int constant = (fn->code.data[ip] << 8) | fn->code.data[ip + 1];
|
|
|
|
|
fn->constants.data[constant] = OBJ_VAL(classObj->superclass);
|
2015-06-02 16:33:39 +02:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2014-01-15 16:03:24 +01:00
|
|
|
case CODE_CLOSURE:
|
|
|
|
|
{
|
|
|
|
|
// Bind the nested closure too.
|
2016-03-04 16:49:34 +01:00
|
|
|
int constant = (fn->code.data[ip] << 8) | fn->code.data[ip + 1];
|
|
|
|
|
wrenBindMethodCode(classObj, AS_FN(fn->constants.data[constant]));
|
2014-01-15 16:03:24 +01:00
|
|
|
|
2016-03-04 16:49:34 +01:00
|
|
|
ip += getNumArguments(fn->code.data, fn->constants.data, ip - 1);
|
2014-01-15 16:03:24 +01:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-11 01:13:25 +01:00
|
|
|
case CODE_END:
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
default:
|
2013-12-25 07:10:30 +01:00
|
|
|
// Other instructions are unaffected, so just skip over them.
|
2016-03-04 16:49:34 +01:00
|
|
|
ip += getNumArguments(fn->code.data, fn->constants.data, ip - 1);
|
2013-12-11 01:13:25 +01:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2013-12-27 01:58:03 +01:00
|
|
|
|
|
|
|
|
void wrenMarkCompiler(WrenVM* vm, Compiler* compiler)
|
|
|
|
|
{
|
2015-11-11 16:55:48 +01:00
|
|
|
wrenGrayValue(vm, compiler->parser->current.value);
|
|
|
|
|
wrenGrayValue(vm, compiler->parser->previous.value);
|
2014-01-01 22:25:24 +01:00
|
|
|
|
2013-12-27 01:58:03 +01:00
|
|
|
// Walk up the parent chain to mark the outer compilers too. The VM only
|
|
|
|
|
// tracks the innermost one.
|
2015-03-22 20:22:47 +01:00
|
|
|
do
|
2013-12-27 01:58:03 +01:00
|
|
|
{
|
2016-03-04 16:49:34 +01:00
|
|
|
wrenGrayObj(vm, (Obj*)compiler->fn);
|
2013-12-27 01:58:03 +01:00
|
|
|
compiler = compiler->parent;
|
|
|
|
|
}
|
2015-03-22 20:22:47 +01:00
|
|
|
while (compiler != NULL);
|
2013-12-27 01:58:03 +01:00
|
|
|
}
|