2013-12-15 08:41:23 +01:00
|
|
|
#include <stdarg.h>
|
|
|
|
|
#include <stdbool.h>
|
2013-10-22 21:16:39 +02:00
|
|
|
#include <stdio.h>
|
2013-10-22 22:25:39 +02:00
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
|
2013-11-28 17:11:50 +01:00
|
|
|
#include "wren_common.h"
|
|
|
|
|
#include "wren_compiler.h"
|
2013-10-22 21:16:39 +02:00
|
|
|
|
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.
|
|
|
|
|
|
2013-11-30 00:08:27 +01:00
|
|
|
// The maximum number of arguments that can be passed to a method. Note that
|
|
|
|
|
// this limtation is hardcoded in other places in the VM, in particular, the
|
|
|
|
|
// `CODE_CALL_XX` instructions assume a certain maximum number.
|
|
|
|
|
#define MAX_PARAMETERS (16)
|
|
|
|
|
|
2013-12-01 03:51:27 +01:00
|
|
|
// The maximum number of local (i.e. non-global) 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.
|
|
|
|
|
//
|
|
|
|
|
// 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.
|
|
|
|
|
#define MAX_LOCALS (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
|
|
|
// The maximum number of upvalues (i.e. variables from enclosing functions)
|
|
|
|
|
// that a function can close over.
|
|
|
|
|
#define MAX_UPVALUES (256)
|
|
|
|
|
|
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
|
|
|
|
|
// argument.
|
|
|
|
|
#define MAX_CONSTANTS (256)
|
|
|
|
|
|
2013-12-07 05:09:43 +01:00
|
|
|
// The maximum name of a method, not including the signature. This is an
|
|
|
|
|
// arbitrary but enforced maximum just so we know how long the method name
|
|
|
|
|
// strings need to be in the parser.
|
|
|
|
|
#define MAX_METHOD_NAME (64)
|
|
|
|
|
|
|
|
|
|
// The maximum length of a method signature. This includes the name, and the
|
|
|
|
|
// extra spaces added to handle arity, and another byte to terminate the string.
|
|
|
|
|
#define MAX_METHOD_SIGNATURE (MAX_METHOD_NAME + MAX_PARAMETERS + 1)
|
|
|
|
|
|
2013-12-15 00:28:18 +01:00
|
|
|
// TODO: Get rid of this and use a growable buffer.
|
2013-12-07 05:09:43 +01:00
|
|
|
#define MAX_STRING (1024)
|
|
|
|
|
|
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,
|
|
|
|
|
TOKEN_COMMA,
|
|
|
|
|
TOKEN_STAR,
|
|
|
|
|
TOKEN_SLASH,
|
|
|
|
|
TOKEN_PERCENT,
|
|
|
|
|
TOKEN_PLUS,
|
|
|
|
|
TOKEN_MINUS,
|
|
|
|
|
TOKEN_PIPE,
|
2013-11-20 03:24:58 +01:00
|
|
|
TOKEN_PIPEPIPE,
|
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,
|
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,
|
2013-11-06 00:40:21 +01:00
|
|
|
TOKEN_ELSE,
|
2013-11-04 06:38:58 +01:00
|
|
|
TOKEN_FALSE,
|
2013-11-06 16:47:47 +01:00
|
|
|
TOKEN_FN,
|
2013-12-24 19:15:50 +01:00
|
|
|
TOKEN_FOR,
|
2013-11-06 00:40:21 +01:00
|
|
|
TOKEN_IF,
|
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-12-19 16:02:27 +01:00
|
|
|
TOKEN_NEW,
|
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,
|
2013-10-22 22:25:39 +02:00
|
|
|
TOKEN_NAME,
|
|
|
|
|
TOKEN_NUMBER,
|
|
|
|
|
TOKEN_STRING,
|
|
|
|
|
|
|
|
|
|
TOKEN_LINE,
|
|
|
|
|
|
|
|
|
|
TOKEN_ERROR,
|
2013-10-24 21:39:01 +02:00
|
|
|
TOKEN_EOF
|
2013-10-22 22:25:39 +02:00
|
|
|
} TokenType;
|
|
|
|
|
|
|
|
|
|
typedef struct Token_s
|
|
|
|
|
{
|
|
|
|
|
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;
|
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
|
|
|
|
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;
|
2013-10-22 21:16:39 +02:00
|
|
|
|
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
|
|
|
|
2013-12-15 08:41:23 +01:00
|
|
|
// If a syntax or compile error has occurred.
|
|
|
|
|
bool hasError;
|
2013-11-24 06:00:47 +01:00
|
|
|
|
2013-12-15 00:28:18 +01:00
|
|
|
// TODO: Dynamically allocate this.
|
2013-11-24 06:00:47 +01:00
|
|
|
// A buffer for the unescaped text of the current token if it's a string
|
|
|
|
|
// literal. Unlike the raw token, this will have escape sequences translated
|
|
|
|
|
// to their literal equivalent.
|
|
|
|
|
char currentString[MAX_STRING];
|
|
|
|
|
int currentStringLength;
|
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;
|
|
|
|
|
|
2013-10-24 21:39:01 +02:00
|
|
|
typedef struct sCompiler
|
|
|
|
|
{
|
|
|
|
|
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;
|
|
|
|
|
|
2013-11-06 15:52:28 +01:00
|
|
|
// The function being compiled.
|
|
|
|
|
ObjFn* fn;
|
2013-10-22 21:16:39 +02:00
|
|
|
int numCodes;
|
|
|
|
|
|
2013-11-23 23:55:05 +01:00
|
|
|
// Symbol table for the fields of the nearest enclosing class, or NULL if not
|
|
|
|
|
// currently inside a class.
|
|
|
|
|
SymbolTable* fields;
|
|
|
|
|
|
2013-12-01 03:28:01 +01:00
|
|
|
// The number of local variables currently in scope.
|
|
|
|
|
int numLocals;
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
// in effect at all. Any variables declared will be global.
|
|
|
|
|
int scopeDepth;
|
|
|
|
|
|
2013-12-24 19:15:50 +01:00
|
|
|
// Index of the first instruction of the body of the innermost loop currently
|
|
|
|
|
// being compiled. Will be -1 if not currently inside a loop.
|
|
|
|
|
int loopBody;
|
|
|
|
|
|
2013-12-19 16:02:27 +01:00
|
|
|
// The name of the method this compiler is compiling, or NULL if this
|
|
|
|
|
// compiler is not for a method. Note that this is just the bare method name,
|
|
|
|
|
// and not its full signature.
|
|
|
|
|
const char* methodName;
|
|
|
|
|
|
|
|
|
|
// The length of the method name being compiled.
|
|
|
|
|
int methodLength;
|
|
|
|
|
|
2013-12-01 03:28:01 +01:00
|
|
|
// The currently in scope local variables.
|
|
|
|
|
Local locals[MAX_LOCALS];
|
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 upvalues that this function has captured from outer scopes. The count
|
|
|
|
|
// of them is stored in `fn->numUpvalues`.
|
|
|
|
|
CompilerUpvalue upvalues[MAX_UPVALUES];
|
2013-10-22 21:16:39 +02:00
|
|
|
} Compiler;
|
|
|
|
|
|
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, ...)
|
|
|
|
|
{
|
|
|
|
|
parser->hasError = true;
|
|
|
|
|
|
|
|
|
|
fprintf(stderr, "[Line %d] Error: ", parser->currentLine);
|
|
|
|
|
|
|
|
|
|
va_list args;
|
|
|
|
|
va_start(args, format);
|
|
|
|
|
vfprintf(stderr, format, args);
|
|
|
|
|
va_end(args);
|
|
|
|
|
|
|
|
|
|
fprintf(stderr, "\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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, ...)
|
|
|
|
|
{
|
|
|
|
|
compiler->parser->hasError = true;
|
|
|
|
|
|
|
|
|
|
Token* token = &compiler->parser->previous;
|
|
|
|
|
fprintf(stderr, "[Line %d] Error on ", token->line);
|
|
|
|
|
if (token->type == TOKEN_LINE)
|
|
|
|
|
{
|
|
|
|
|
// Don't print the newline itself since that looks wonky.
|
|
|
|
|
fprintf(stderr, "newline: ");
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
fprintf(stderr, "'%.*s': ", token->length, token->start);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
va_list args;
|
|
|
|
|
va_start(args, format);
|
|
|
|
|
vfprintf(stderr, format, args);
|
|
|
|
|
va_end(args);
|
|
|
|
|
|
|
|
|
|
fprintf(stderr, "\n");
|
|
|
|
|
}
|
|
|
|
|
|
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)
|
|
|
|
|
{
|
2013-12-24 00:08:47 +01:00
|
|
|
// See if an equivalent constant has already been added.
|
|
|
|
|
for (int i = 0; i < compiler->fn->numConstants; i++)
|
|
|
|
|
{
|
|
|
|
|
if (wrenValuesEqual(compiler->fn->constants[i], constant)) return i;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (compiler->fn->numConstants < MAX_CONSTANTS)
|
|
|
|
|
{
|
|
|
|
|
compiler->fn->constants[compiler->fn->numConstants++] = constant;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
error(compiler, "A function may only contain %d unique constants.",
|
|
|
|
|
MAX_CONSTANTS);
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-20 05:54:47 +01:00
|
|
|
return compiler->fn->numConstants - 1;
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
// Initializes [compiler].
|
2013-12-19 16:02:27 +01:00
|
|
|
static int initCompiler(Compiler* compiler, Parser* parser, Compiler* parent,
|
|
|
|
|
const char* methodName, int methodLength)
|
2013-10-28 05:59:14 +01:00
|
|
|
{
|
|
|
|
|
compiler->parser = parser;
|
|
|
|
|
compiler->parent = parent;
|
|
|
|
|
compiler->numCodes = 0;
|
2013-12-24 19:15:50 +01:00
|
|
|
compiler->loopBody = -1;
|
2013-12-19 16:02:27 +01:00
|
|
|
compiler->methodName = methodName;
|
|
|
|
|
compiler->methodLength = methodLength;
|
2013-11-10 00:42:23 +01:00
|
|
|
|
2013-12-01 03:28:01 +01:00
|
|
|
if (parent == NULL)
|
|
|
|
|
{
|
|
|
|
|
// Compiling top-level code, so the initial scope is global.
|
|
|
|
|
compiler->scopeDepth = -1;
|
|
|
|
|
compiler->numLocals = 0;
|
|
|
|
|
}
|
|
|
|
|
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;
|
2013-12-22 00:55:08 +01:00
|
|
|
if (methodName != NULL)
|
|
|
|
|
{
|
|
|
|
|
compiler->locals[0].name = "this";
|
|
|
|
|
compiler->locals[0].length = 4;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
compiler->locals[0].name = NULL;
|
|
|
|
|
compiler->locals[0].length = 0;
|
|
|
|
|
}
|
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;
|
|
|
|
|
}
|
2013-10-28 05:59:14 +01:00
|
|
|
|
2013-11-23 23:55:05 +01:00
|
|
|
// Propagate the enclosing class downwards.
|
|
|
|
|
compiler->fields = parent != NULL ? parent->fields : NULL;
|
|
|
|
|
|
refactor: rename internal value creation functions to wren-prefixed public API names
Move `allocate`, `initObj`, `newClass`, and related static helpers from `vm.c` into `value.c` with consistent `wren` prefix, and update all call sites in `compiler.c`, `primitives.c`, and `vm.c` to use the new names (`wrenNewFunction`, `wrenNewString`, `wrenNewClass`, `wrenNewInstance`, `wrenNewList`). Remove the old declarations from `vm.h` and add comprehensive documentation comments to `value.h` explaining the NaN-tagging representation and type system.
2013-11-28 05:00:03 +01:00
|
|
|
compiler->fn = wrenNewFunction(parser->vm);
|
2013-11-18 18:19:03 +01:00
|
|
|
|
2013-11-20 05:54:47 +01:00
|
|
|
if (parent == NULL) return -1;
|
|
|
|
|
|
|
|
|
|
// Add the block to the constant table. Do this eagerly so it's reachable by
|
|
|
|
|
// the GC.
|
|
|
|
|
return addConstant(parent, OBJ_VAL(compiler->fn));
|
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
|
|
|
|
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
|
|
|
|
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;
|
|
|
|
|
}
|
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)
|
|
|
|
|
{
|
|
|
|
|
if (peekChar(parser) == c)
|
|
|
|
|
{
|
|
|
|
|
nextChar(parser);
|
|
|
|
|
makeToken(parser, two);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
makeToken(parser, one);
|
|
|
|
|
}
|
|
|
|
|
|
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)
|
|
|
|
|
{
|
|
|
|
|
nextChar(parser); // The opening "*".
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-15 08:41:23 +01:00
|
|
|
// Returns true if the current token's text matches [keyword].
|
|
|
|
|
static bool isKeyword(Parser* parser, const char* keyword)
|
2013-10-28 05:59:14 +01:00
|
|
|
{
|
2013-11-07 16:04:25 +01:00
|
|
|
size_t length = parser->currentChar - parser->tokenStart;
|
|
|
|
|
size_t keywordLength = strlen(keyword);
|
|
|
|
|
return length == keywordLength &&
|
2013-12-02 00:22:24 +01:00
|
|
|
strncmp(parser->tokenStart, keyword, length) == 0;
|
2013-10-28 05:59:14 +01:00
|
|
|
}
|
|
|
|
|
|
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-12-15 00:28:18 +01:00
|
|
|
// TODO: Hex, scientific, etc.
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
makeToken(parser, TOKEN_NUMBER);
|
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
|
|
|
}
|
|
|
|
|
|
2013-12-24 19:15:50 +01:00
|
|
|
if (isKeyword(parser, "break")) type = TOKEN_BREAK;
|
2013-11-07 16:04:25 +01:00
|
|
|
if (isKeyword(parser, "class")) type = TOKEN_CLASS;
|
|
|
|
|
if (isKeyword(parser, "else")) type = TOKEN_ELSE;
|
|
|
|
|
if (isKeyword(parser, "false")) type = TOKEN_FALSE;
|
|
|
|
|
if (isKeyword(parser, "fn")) type = TOKEN_FN;
|
2013-12-24 19:15:50 +01:00
|
|
|
if (isKeyword(parser, "for")) type = TOKEN_FOR;
|
2013-11-07 16:04:25 +01:00
|
|
|
if (isKeyword(parser, "if")) type = TOKEN_IF;
|
2013-12-25 06:04:11 +01:00
|
|
|
if (isKeyword(parser, "in")) type = 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
|
|
|
if (isKeyword(parser, "is")) type = TOKEN_IS;
|
2013-12-19 16:02:27 +01:00
|
|
|
if (isKeyword(parser, "new")) type = TOKEN_NEW;
|
2013-11-07 16:04:25 +01:00
|
|
|
if (isKeyword(parser, "null")) type = 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
|
|
|
if (isKeyword(parser, "return")) type = TOKEN_RETURN;
|
2013-11-10 20:46:13 +01:00
|
|
|
if (isKeyword(parser, "static")) type = TOKEN_STATIC;
|
2013-12-11 17:02:17 +01:00
|
|
|
if (isKeyword(parser, "super")) type = TOKEN_SUPER;
|
2013-11-10 00:42:23 +01:00
|
|
|
if (isKeyword(parser, "this")) type = TOKEN_THIS;
|
2013-11-07 16:04:25 +01:00
|
|
|
if (isKeyword(parser, "true")) type = TOKEN_TRUE;
|
|
|
|
|
if (isKeyword(parser, "var")) type = TOKEN_VAR;
|
2013-11-18 07:38:59 +01:00
|
|
|
if (isKeyword(parser, "while")) type = TOKEN_WHILE;
|
2013-11-03 19:16:14 +01:00
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
makeToken(parser, type);
|
2013-10-28 05:59:14 +01:00
|
|
|
}
|
|
|
|
|
|
2013-11-24 06:00:47 +01:00
|
|
|
// Adds [c] to the current string literal being tokenized.
|
|
|
|
|
static void addStringChar(Parser* parser, char c)
|
|
|
|
|
{
|
|
|
|
|
parser->currentString[parser->currentStringLength++] = c;
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
{
|
2013-11-24 06:00:47 +01:00
|
|
|
parser->currentStringLength = 0;
|
|
|
|
|
for (;;)
|
|
|
|
|
{
|
|
|
|
|
char c = nextChar(parser);
|
|
|
|
|
if (c == '"') break;
|
|
|
|
|
|
|
|
|
|
if (c == '\\')
|
|
|
|
|
{
|
|
|
|
|
switch (nextChar(parser))
|
|
|
|
|
{
|
|
|
|
|
case '"': addStringChar(parser, '"'); break;
|
|
|
|
|
case '\\': addStringChar(parser, '\\'); break;
|
|
|
|
|
case 'n': addStringChar(parser, '\n'); break;
|
2013-11-30 01:19:13 +01:00
|
|
|
case 't': addStringChar(parser, '\t'); break;
|
2013-11-24 06:00:47 +01:00
|
|
|
default:
|
2013-12-15 00:28:18 +01:00
|
|
|
// TODO: Emit error token.
|
2013-11-24 06:00:47 +01:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-15 00:28:18 +01:00
|
|
|
// TODO: Other escapes (\r, etc.), Unicode escape sequences.
|
2013-11-24 06:00:47 +01:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
addStringChar(parser, c);
|
|
|
|
|
}
|
|
|
|
|
}
|
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
|
|
|
makeToken(parser, TOKEN_STRING);
|
|
|
|
|
}
|
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-12-02 00:22:24 +01:00
|
|
|
// Lex the next token and store it in [parser.current]. Does not do any newline
|
2013-11-07 16:04:25 +01:00
|
|
|
// filtering.
|
|
|
|
|
static void readRawToken(Parser* parser)
|
|
|
|
|
{
|
|
|
|
|
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
|
|
|
{
|
2013-11-07 16:04:25 +01:00
|
|
|
case '(': makeToken(parser, TOKEN_LEFT_PAREN); return;
|
|
|
|
|
case ')': makeToken(parser, TOKEN_RIGHT_PAREN); return;
|
|
|
|
|
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;
|
2013-12-05 04:12:21 +01:00
|
|
|
case ';': makeToken(parser, TOKEN_LINE); return;
|
2013-11-07 16:04:25 +01:00
|
|
|
case ':': makeToken(parser, TOKEN_COLON); return;
|
|
|
|
|
case '.': makeToken(parser, TOKEN_DOT); return;
|
|
|
|
|
case ',': makeToken(parser, TOKEN_COMMA); return;
|
|
|
|
|
case '*': makeToken(parser, TOKEN_STAR); return;
|
2013-12-05 07:09:31 +01:00
|
|
|
case '%': makeToken(parser, TOKEN_PERCENT); return;
|
|
|
|
|
case '+': makeToken(parser, TOKEN_PLUS); return;
|
|
|
|
|
case '~': makeToken(parser, TOKEN_TILDE); return;
|
2013-11-07 16:04:25 +01:00
|
|
|
case '/':
|
|
|
|
|
if (peekChar(parser) == '/')
|
|
|
|
|
{
|
|
|
|
|
skipLineComment(parser);
|
|
|
|
|
break;
|
|
|
|
|
}
|
2013-11-20 05:19:02 +01:00
|
|
|
|
|
|
|
|
if (peekChar(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 '-':
|
|
|
|
|
if (isDigit(peekChar(parser)))
|
|
|
|
|
{
|
|
|
|
|
readNumber(parser);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
makeToken(parser, TOKEN_MINUS);
|
|
|
|
|
}
|
|
|
|
|
return;
|
2013-10-25 06:32:17 +02:00
|
|
|
|
2013-11-20 03:24:58 +01:00
|
|
|
case '|':
|
2013-11-20 05:19:02 +01:00
|
|
|
twoCharToken(parser, '|', TOKEN_PIPEPIPE, TOKEN_PIPE);
|
2013-11-20 03:24:58 +01:00
|
|
|
return;
|
|
|
|
|
|
2013-11-19 16:35:25 +01:00
|
|
|
case '&':
|
2013-11-20 05:19:02 +01:00
|
|
|
twoCharToken(parser, '&', TOKEN_AMPAMP, TOKEN_AMP);
|
2013-11-19 16:35:25 +01:00
|
|
|
return;
|
|
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
case '=':
|
2013-11-20 05:19:02 +01:00
|
|
|
twoCharToken(parser, '=', TOKEN_EQEQ, TOKEN_EQ);
|
2013-11-07 16:04:25 +01:00
|
|
|
return;
|
2013-10-25 06:32:17 +02:00
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
case '<':
|
2013-11-20 05:19:02 +01:00
|
|
|
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 '>':
|
2013-11-20 05:19:02 +01:00
|
|
|
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 '!':
|
2013-11-20 05:19:02 +01:00
|
|
|
twoCharToken(parser, '=', TOKEN_BANGEQ, TOKEN_BANG);
|
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
|
|
|
case '\n':
|
|
|
|
|
makeToken(parser, TOKEN_LINE);
|
|
|
|
|
return;
|
2013-10-24 00:32:59 +02:00
|
|
|
|
2013-11-20 05:19:02 +01:00
|
|
|
case ' ':
|
|
|
|
|
// Skip forward until we run out of whitespace.
|
|
|
|
|
while (peekChar(parser) == ' ') nextChar(parser);
|
|
|
|
|
break;
|
|
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
case '"': readString(parser); return;
|
2013-11-23 23:55:05 +01:00
|
|
|
case '_': readName(parser, TOKEN_FIELD); return;
|
2013-10-24 00:32:59 +02:00
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
default:
|
|
|
|
|
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
|
|
|
// Lex the next token in the source file and store it in parser.current. Omits
|
|
|
|
|
// newlines that aren't meaningful.
|
|
|
|
|
static void nextToken(Parser* parser)
|
2013-10-31 15:04:44 +01:00
|
|
|
{
|
2013-11-07 16:04:25 +01:00
|
|
|
parser->previous = parser->current;
|
|
|
|
|
|
2013-11-29 19:53:56 +01:00
|
|
|
// 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;
|
|
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
for (;;)
|
2013-11-06 00:40:21 +01:00
|
|
|
{
|
2013-11-07 16:04:25 +01:00
|
|
|
readRawToken(parser);
|
2013-11-06 00:40:21 +01:00
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
switch (parser->current.type)
|
|
|
|
|
{
|
|
|
|
|
case TOKEN_LINE:
|
|
|
|
|
if (!parser->skipNewlines)
|
|
|
|
|
{
|
|
|
|
|
// Collapse multiple newlines into one.
|
2013-12-15 08:41:23 +01:00
|
|
|
parser->skipNewlines = true;
|
2013-11-06 00:40:21 +01:00
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
// Emit this newline.
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
break;
|
2013-11-06 00:40:21 +01:00
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
// Discard newlines after tokens that cannot end an expression.
|
|
|
|
|
case TOKEN_LEFT_PAREN:
|
|
|
|
|
case TOKEN_LEFT_BRACKET:
|
|
|
|
|
case TOKEN_LEFT_BRACE:
|
|
|
|
|
case TOKEN_DOT:
|
|
|
|
|
case TOKEN_COMMA:
|
|
|
|
|
case TOKEN_STAR:
|
|
|
|
|
case TOKEN_SLASH:
|
|
|
|
|
case TOKEN_PERCENT:
|
|
|
|
|
case TOKEN_PLUS:
|
|
|
|
|
case TOKEN_MINUS:
|
|
|
|
|
case TOKEN_PIPE:
|
2013-11-20 03:24:58 +01:00
|
|
|
case TOKEN_PIPEPIPE:
|
2013-11-07 16:04:25 +01:00
|
|
|
case TOKEN_AMP:
|
2013-11-19 16:35:25 +01:00
|
|
|
case TOKEN_AMPAMP:
|
2013-11-07 16:04:25 +01:00
|
|
|
case TOKEN_BANG:
|
2013-12-05 07:09:31 +01:00
|
|
|
case TOKEN_TILDE:
|
2013-11-07 16:04:25 +01:00
|
|
|
case TOKEN_EQ:
|
|
|
|
|
case TOKEN_LT:
|
|
|
|
|
case TOKEN_GT:
|
|
|
|
|
case TOKEN_LTEQ:
|
|
|
|
|
case TOKEN_GTEQ:
|
|
|
|
|
case TOKEN_EQEQ:
|
|
|
|
|
case TOKEN_BANGEQ:
|
|
|
|
|
case TOKEN_CLASS:
|
|
|
|
|
case TOKEN_ELSE:
|
2013-12-24 19:15:50 +01:00
|
|
|
case TOKEN_FOR:
|
2013-11-07 16:04:25 +01:00
|
|
|
case TOKEN_IF:
|
2013-12-25 06:04:11 +01:00
|
|
|
case 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
|
|
|
case TOKEN_IS:
|
2013-12-19 16:02:27 +01:00
|
|
|
case TOKEN_NEW:
|
2013-11-10 20:46:13 +01:00
|
|
|
case TOKEN_STATIC:
|
2013-12-11 17:02:17 +01:00
|
|
|
case TOKEN_SUPER:
|
2013-11-07 16:04:25 +01:00
|
|
|
case TOKEN_VAR:
|
2013-11-18 07:38:59 +01:00
|
|
|
case TOKEN_WHILE:
|
2013-12-15 08:41:23 +01:00
|
|
|
parser->skipNewlines = true;
|
2013-11-06 00:40:21 +01:00
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
// Emit this token.
|
|
|
|
|
return;
|
2013-11-06 00:40:21 +01:00
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
// Newlines are meaningful after other tokens.
|
|
|
|
|
default:
|
2013-12-15 08:41:23 +01:00
|
|
|
parser->skipNewlines = false;
|
2013-11-07 16:04:25 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2013-11-06 00:40:21 +01: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].
|
2013-12-02 00:22:24 +01:00
|
|
|
// Returns the consumed token.
|
|
|
|
|
static Token* 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);
|
2013-11-07 16:04:25 +01:00
|
|
|
}
|
2013-12-02 00:22:24 +01:00
|
|
|
|
|
|
|
|
return &compiler->parser->previous;
|
2013-11-07 16:04:25 +01:00
|
|
|
}
|
2013-10-31 15:04:44 +01:00
|
|
|
|
2013-12-01 23:59:56 +01:00
|
|
|
// Variables and scopes --------------------------------------------------------
|
2013-11-07 16:04:25 +01:00
|
|
|
|
2013-12-25 06:04:11 +01:00
|
|
|
// Emits one bytecode instruction or argument. Returns its 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
|
|
|
static int emit(Compiler* compiler, Code code)
|
|
|
|
|
{
|
|
|
|
|
compiler->fn->bytecode[compiler->numCodes++] = code;
|
|
|
|
|
return compiler->numCodes - 1;
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-25 06:04:11 +01:00
|
|
|
// Emits one bytecode instruction followed by an argument. Returns the index of
|
|
|
|
|
// the argument in the bytecode.
|
|
|
|
|
static int emit1(Compiler* compiler, Code instruction, uint8_t arg)
|
|
|
|
|
{
|
|
|
|
|
emit(compiler, instruction);
|
|
|
|
|
return emit(compiler, arg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create a new local variable with [name]. Assumes the current scope is local
|
|
|
|
|
// and the name is unique.
|
|
|
|
|
static int defineLocal(Compiler* compiler, const char* name, int length)
|
|
|
|
|
{
|
|
|
|
|
Local* local = &compiler->locals[compiler->numLocals];
|
|
|
|
|
local->name = name;
|
|
|
|
|
local->length = length;
|
|
|
|
|
local->depth = compiler->scopeDepth;
|
|
|
|
|
local->isUpvalue = false;
|
|
|
|
|
return compiler->numLocals++;
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-09 20:18:27 +01:00
|
|
|
// Parses a name token and declares a variable in the current scope with that
|
2013-11-07 16:04:25 +01:00
|
|
|
// name. Returns its symbol.
|
2013-11-09 20:18:27 +01:00
|
|
|
static int declareVariable(Compiler* compiler)
|
2013-11-07 16:04:25 +01:00
|
|
|
{
|
2013-12-02 00:22:24 +01:00
|
|
|
Token* token = consume(compiler, TOKEN_NAME, "Expected variable name.");
|
2013-12-01 03:28:01 +01:00
|
|
|
|
|
|
|
|
// Top-level global scope.
|
|
|
|
|
if (compiler->scopeDepth == -1)
|
2013-10-31 15:04:44 +01:00
|
|
|
{
|
2013-12-01 03:28:01 +01:00
|
|
|
SymbolTable* symbols = &compiler->parser->vm->globalSymbols;
|
|
|
|
|
|
2013-12-02 00:22:24 +01:00
|
|
|
int symbol = addSymbol(symbols, token->start, token->length);
|
2013-12-01 03:28:01 +01:00
|
|
|
if (symbol == -1)
|
|
|
|
|
{
|
|
|
|
|
error(compiler, "Global variable is already defined.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 &&
|
|
|
|
|
strncmp(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;
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-25 06:04:11 +01:00
|
|
|
return defineLocal(compiler, token->start, token->length);
|
2013-10-22 21:16:39 +02: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;
|
|
|
|
|
|
|
|
|
|
// It's a global variable, so store the value in the global slot and then
|
|
|
|
|
// discard the temporary for the initializer.
|
2013-12-25 06:04:11 +01:00
|
|
|
emit1(compiler, CODE_STORE_GLOBAL, symbol);
|
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(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++;
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
// Closes the last pushed block scope. This should only be called in a statement
|
|
|
|
|
// context where no temporaries are still on the stack.
|
2013-12-01 03:28:01 +01:00
|
|
|
static void popScope(Compiler* compiler)
|
|
|
|
|
{
|
|
|
|
|
ASSERT(compiler->scopeDepth > -1, "Cannot pop top-level scope.");
|
|
|
|
|
|
|
|
|
|
// Pop locals off the stack.
|
|
|
|
|
while (compiler->numLocals > 0 &&
|
|
|
|
|
compiler->locals[compiler->numLocals - 1].depth ==
|
|
|
|
|
compiler->scopeDepth)
|
|
|
|
|
{
|
|
|
|
|
compiler->numLocals--;
|
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
|
|
|
|
|
// goes out of scope on the stack.
|
|
|
|
|
if (compiler->locals[compiler->numLocals].isUpvalue)
|
|
|
|
|
{
|
|
|
|
|
emit(compiler, CODE_CLOSE_UPVALUE);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
emit(compiler, CODE_POP);
|
|
|
|
|
}
|
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 &&
|
|
|
|
|
strncmp(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
|
|
|
|
|
// index of the uvpalue.
|
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.
|
|
|
|
|
for (int i = 0; i < compiler->fn->numUpvalues; i++)
|
|
|
|
|
{
|
|
|
|
|
CompilerUpvalue* upvalue = &compiler->upvalues[i];
|
|
|
|
|
if (upvalue->index == index && upvalue->isLocal == isLocal) return i;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If we got here, it's a new upvalue.
|
|
|
|
|
compiler->upvalues[compiler->fn->numUpvalues].isLocal = isLocal;
|
|
|
|
|
compiler->upvalues[compiler->fn->numUpvalues].index = index;
|
|
|
|
|
return compiler->fn->numUpvalues++;
|
|
|
|
|
}
|
|
|
|
|
|
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.
|
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
|
|
|
{
|
|
|
|
|
// If we are out of enclosing functions, it can't be an upvalue.
|
|
|
|
|
if (compiler->parent == NULL)
|
|
|
|
|
{
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// See if it's a local variable in the immediately enclosing function.
|
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
|
|
|
|
|
// words, if its a local variable in a non-immediately enclosing function.
|
|
|
|
|
// This will "flatten" closures automatically: it will add upvalues to all
|
|
|
|
|
// of the intermediate functions to get from the function where a local is
|
|
|
|
|
// declared all the way into the possibly deeply nested function that is
|
|
|
|
|
// closing over it.
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-23 05:46:12 +01:00
|
|
|
// Look up [name] in the current scope to see what name it is bound to. Returns
|
|
|
|
|
// the index of the name either in global scope, local scope, or the enclosing
|
2013-12-23 05:53:35 +01:00
|
|
|
// function's upvalue list. Returns -1 if not found.
|
|
|
|
|
//
|
|
|
|
|
// Sets [loadInstruction] to the instruction needed to load the variable. Will
|
|
|
|
|
// be one of [CODE_LOAD_LOCAL], [CODE_LOAD_UPVALUE], or [CODE_LOAD_GLOBAL].
|
2013-12-23 05:46:12 +01:00
|
|
|
static int resolveName(Compiler* compiler, const char* name, int length,
|
2013-12-23 05:53:35 +01:00
|
|
|
Code* loadInstruction)
|
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 it up in the local scopes. Look in reverse order so that the most
|
|
|
|
|
// nested variable is found first and shadows outer ones.
|
2013-12-23 05:53:35 +01:00
|
|
|
*loadInstruction = CODE_LOAD_LOCAL;
|
2013-12-23 05:46:12 +01:00
|
|
|
int local = resolveLocal(compiler, 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) return local;
|
|
|
|
|
|
|
|
|
|
// If we got here, it's not a local, so lets see if we are closing over an
|
|
|
|
|
// outer local.
|
2013-12-23 05:53:35 +01:00
|
|
|
*loadInstruction = CODE_LOAD_UPVALUE;
|
2013-12-23 05:46:12 +01:00
|
|
|
int upvalue = findUpvalue(compiler, 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) return upvalue;
|
2013-12-01 23:59:56 +01:00
|
|
|
|
2013-12-01 03:28:01 +01:00
|
|
|
// If we got here, it wasn't in a local scope, so try the global scope.
|
2013-12-23 05:53:35 +01:00
|
|
|
*loadInstruction = CODE_LOAD_GLOBAL;
|
2013-12-23 05:46:12 +01:00
|
|
|
return findSymbol(&compiler->parser->vm->globalSymbols, name, length);
|
2013-12-01 03:28:01 +01:00
|
|
|
}
|
|
|
|
|
|
2013-12-01 09:04:46 +01:00
|
|
|
// Copies the identifier from the previously consumed `TOKEN_NAME` into [name],
|
|
|
|
|
// which should point to a buffer large enough to contain it. Returns the
|
|
|
|
|
// length of the name.
|
|
|
|
|
static int copyName(Compiler* compiler, char* name)
|
|
|
|
|
{
|
2013-12-02 00:22:24 +01:00
|
|
|
Token* token = &compiler->parser->previous;
|
2013-12-07 05:09:43 +01:00
|
|
|
int length = token->length;
|
|
|
|
|
|
|
|
|
|
if (length > MAX_METHOD_NAME)
|
|
|
|
|
{
|
|
|
|
|
error(compiler, "Method names cannot be longer than %d characters.",
|
|
|
|
|
MAX_METHOD_NAME);
|
|
|
|
|
length = MAX_METHOD_NAME;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
strncpy(name, token->start, length);
|
|
|
|
|
return length;
|
2013-12-01 09:04:46 +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.
|
|
|
|
|
static void endCompiler(Compiler* compiler, int constant)
|
|
|
|
|
{
|
|
|
|
|
// Mark the end of the bytecode. Since it may contain multiple early returns,
|
|
|
|
|
// we can't rely on CODE_RETURN to tell us we're at the end.
|
|
|
|
|
emit(compiler, CODE_END);
|
|
|
|
|
|
|
|
|
|
// In the function that contains this one, load the resulting function object.
|
|
|
|
|
if (compiler->parent != NULL)
|
|
|
|
|
{
|
|
|
|
|
// If the function has no upvalues, we don't need to create a closure.
|
|
|
|
|
// We can just load and run the function directly.
|
|
|
|
|
if (compiler->fn->numUpvalues == 0)
|
|
|
|
|
{
|
2013-12-25 06:04:11 +01:00
|
|
|
emit1(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.
|
2013-12-25 06:04:11 +01:00
|
|
|
emit1(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.
|
2013-12-15 00:28:18 +01:00
|
|
|
// TODO: Do something more efficient here?
|
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
|
|
|
for (int i = 0; i < compiler->fn->numUpvalues; i++)
|
|
|
|
|
{
|
2013-12-25 06:04:11 +01:00
|
|
|
emit1(compiler->parent, compiler->upvalues[i].isLocal ? 1 : 0,
|
|
|
|
|
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-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,
|
2013-11-13 16:10:52 +01:00
|
|
|
PREC_ASSIGNMENT, // =
|
2013-11-19 16:35:25 +01:00
|
|
|
PREC_LOGIC, // && ||
|
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
|
|
|
PREC_IS, // is
|
2013-11-07 16:04:25 +01:00
|
|
|
PREC_EQUALITY, // == !=
|
|
|
|
|
PREC_COMPARISON, // < > <= >=
|
|
|
|
|
PREC_BITWISE, // | &
|
|
|
|
|
PREC_TERM, // + -
|
|
|
|
|
PREC_FACTOR, // * / %
|
2013-11-13 16:10:52 +01:00
|
|
|
PREC_UNARY, // unary - ! ~
|
2013-11-24 19:45:07 +01:00
|
|
|
PREC_CALL // . () []
|
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;
|
|
|
|
|
|
|
|
|
|
// Forward declarations since the grammar is recursive.
|
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
|
|
|
static void expression(Compiler* compiler);
|
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
|
|
|
static void statement(Compiler* compiler);
|
2013-12-24 19:27:48 +01:00
|
|
|
static void definition(Compiler* compiler);
|
2013-12-15 08:41:23 +01:00
|
|
|
static void parsePrecedence(Compiler* compiler, bool allowAssignment,
|
2013-11-13 16:10:52 +01:00
|
|
|
Precedence precedence);
|
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
|
|
|
|
2013-12-15 08:41:23 +01:00
|
|
|
typedef void (*GrammarFn)(Compiler*, bool allowAssignment);
|
2013-11-07 16:04:25 +01:00
|
|
|
|
2013-11-14 19:27:33 +01:00
|
|
|
typedef void (*SignatureFn)(Compiler* compiler, char* name, int* length);
|
|
|
|
|
|
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
|
|
|
|
2013-11-13 16:10:52 +01:00
|
|
|
GrammarRule rules[];
|
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)
|
|
|
|
|
{
|
|
|
|
|
compiler->fn->bytecode[offset] = compiler->numCodes - offset - 1;
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-20 05:54:47 +01:00
|
|
|
// Parses a block body, after the initial "{" has been consumed.
|
|
|
|
|
static void finishBlock(Compiler* compiler)
|
|
|
|
|
{
|
|
|
|
|
for (;;)
|
|
|
|
|
{
|
2013-12-24 19:27:48 +01:00
|
|
|
definition(compiler);
|
2013-11-20 05:54:47 +01:00
|
|
|
|
|
|
|
|
// If there is no newline, it must be the end of the block on the same line.
|
|
|
|
|
if (!match(compiler, TOKEN_LINE))
|
|
|
|
|
{
|
|
|
|
|
consume(compiler, TOKEN_RIGHT_BRACE, "Expect '}' after block body.");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (match(compiler, TOKEN_RIGHT_BRACE)) break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-07 05:09:43 +01:00
|
|
|
// Parses an optional parenthesis-delimited parameter list. If the parameter
|
|
|
|
|
// list is for a method, [name] will be the name of the method and this will
|
|
|
|
|
// modify it to handle arity in the signature. For functions, [name] will be
|
|
|
|
|
// `null`.
|
2013-11-15 03:25:28 +01:00
|
|
|
static void parameterList(Compiler* compiler, char* name, int* length)
|
|
|
|
|
{
|
|
|
|
|
// Parse the parameter list, if any.
|
|
|
|
|
if (match(compiler, TOKEN_LEFT_PAREN))
|
|
|
|
|
{
|
2013-11-30 00:08:27 +01:00
|
|
|
int numParams = 0;
|
2013-11-15 03:25:28 +01:00
|
|
|
do
|
|
|
|
|
{
|
2013-12-21 18:37:59 +01:00
|
|
|
validateNumParameters(compiler, ++numParams);
|
2013-11-30 00:08:27 +01:00
|
|
|
|
2013-11-15 03:25:28 +01:00
|
|
|
// Define a local variable in the method for the parameter.
|
|
|
|
|
declareVariable(compiler);
|
|
|
|
|
|
|
|
|
|
// Add a space in the name for the parameter.
|
|
|
|
|
if (name != NULL) name[(*length)++] = ' ';
|
|
|
|
|
}
|
|
|
|
|
while (match(compiler, TOKEN_COMMA));
|
|
|
|
|
consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after parameters.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-19 16:02:27 +01:00
|
|
|
// Compiles an (optional) argument list and then calls it.
|
|
|
|
|
static void methodCall(Compiler* compiler, Code instruction,
|
|
|
|
|
char name[MAX_METHOD_SIGNATURE], int length)
|
2013-12-13 17:37:49 +01:00
|
|
|
{
|
|
|
|
|
// Parse the argument list, if any.
|
|
|
|
|
int numArgs = 0;
|
|
|
|
|
if (match(compiler, TOKEN_LEFT_PAREN))
|
|
|
|
|
{
|
|
|
|
|
do
|
|
|
|
|
{
|
2013-12-21 18:37:59 +01:00
|
|
|
validateNumParameters(compiler, ++numArgs);
|
2013-12-13 17:37:49 +01:00
|
|
|
expression(compiler);
|
|
|
|
|
|
|
|
|
|
// Add a space in the name for each argument. Lets us overload by
|
|
|
|
|
// arity.
|
|
|
|
|
name[length++] = ' ';
|
|
|
|
|
}
|
|
|
|
|
while (match(compiler, TOKEN_COMMA));
|
|
|
|
|
consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after arguments.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int symbol = ensureSymbol(&compiler->parser->vm->methods, name, length);
|
|
|
|
|
|
2013-12-25 06:04:11 +01:00
|
|
|
emit1(compiler, instruction + numArgs, symbol);
|
2013-12-13 17:37:49 +01:00
|
|
|
}
|
|
|
|
|
|
2013-12-20 16:05:31 +01:00
|
|
|
// Compiles an expression that starts with ".name". That includes getters,
|
|
|
|
|
// method calls with arguments, and setter calls.
|
2013-12-19 16:02:27 +01:00
|
|
|
static void namedCall(Compiler* compiler, bool allowAssignment,
|
|
|
|
|
Code instruction)
|
|
|
|
|
{
|
|
|
|
|
// Build the method name.
|
|
|
|
|
consume(compiler, TOKEN_NAME, "Expect method name after '.'.");
|
|
|
|
|
char name[MAX_METHOD_SIGNATURE];
|
|
|
|
|
int length = copyName(compiler, name);
|
|
|
|
|
|
2013-12-20 16:05:31 +01:00
|
|
|
if (match(compiler, TOKEN_EQ))
|
|
|
|
|
{
|
|
|
|
|
if (!allowAssignment) error(compiler, "Invalid assignment.");
|
|
|
|
|
|
|
|
|
|
name[length++] = '=';
|
|
|
|
|
name[length++] = ' ';
|
|
|
|
|
|
|
|
|
|
// Compile the assigned value.
|
|
|
|
|
expression(compiler);
|
2013-12-19 16:02:27 +01:00
|
|
|
|
2013-12-20 16:05:31 +01:00
|
|
|
int symbol = ensureSymbol(&compiler->parser->vm->methods, name, length);
|
2013-12-25 06:04:11 +01:00
|
|
|
emit1(compiler, instruction + 1, symbol);
|
2013-12-20 16:05:31 +01:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
methodCall(compiler, instruction, name, length);
|
|
|
|
|
}
|
2013-12-19 16:02:27 +01:00
|
|
|
}
|
|
|
|
|
|
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)
|
|
|
|
|
{
|
|
|
|
|
Code loadInstruction;
|
|
|
|
|
int index = resolveName(compiler, "this", 4, &loadInstruction);
|
2013-12-25 06:04:11 +01:00
|
|
|
emit1(compiler, loadInstruction, index);
|
2013-12-23 20:17:40 +01:00
|
|
|
}
|
|
|
|
|
|
2013-12-15 08:41:23 +01:00
|
|
|
static void grouping(Compiler* compiler, bool allowAssignment)
|
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
|
|
|
|
2013-12-15 08:41:23 +01:00
|
|
|
static void list(Compiler* compiler, bool allowAssignment)
|
2013-11-24 22:38:31 +01:00
|
|
|
{
|
|
|
|
|
// Compile the list elements.
|
|
|
|
|
int numElements = 0;
|
|
|
|
|
if (peek(compiler) != TOKEN_RIGHT_BRACKET)
|
|
|
|
|
{
|
|
|
|
|
do
|
|
|
|
|
{
|
|
|
|
|
numElements++;
|
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-20 21:42:11 +01:00
|
|
|
|
|
|
|
|
// Ignore a newline after the element but before the ',' or ']'.
|
|
|
|
|
match(compiler, TOKEN_LINE);
|
2013-11-24 22:38:31 +01:00
|
|
|
} while (match(compiler, TOKEN_COMMA));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
consume(compiler, TOKEN_RIGHT_BRACKET, "Expect ']' after list elements.");
|
|
|
|
|
|
|
|
|
|
// Create the list.
|
2013-12-15 00:28:18 +01:00
|
|
|
// TODO: Handle lists >255 elements.
|
2013-12-25 06:04:11 +01:00
|
|
|
emit1(compiler, CODE_LIST, numElements);
|
2013-11-24 22:38:31 +01:00
|
|
|
}
|
|
|
|
|
|
2013-11-13 16:10:52 +01:00
|
|
|
// Unary operators like `-foo`.
|
2013-12-15 08:41:23 +01:00
|
|
|
static void unaryOp(Compiler* compiler, bool allowAssignment)
|
2013-11-13 16:10:52 +01:00
|
|
|
{
|
|
|
|
|
GrammarRule* rule = &rules[compiler->parser->previous.type];
|
|
|
|
|
|
|
|
|
|
// Compile the argument.
|
2013-12-20 16:05:31 +01:00
|
|
|
parsePrecedence(compiler, false, PREC_UNARY + 1);
|
2013-11-13 16:10:52 +01:00
|
|
|
|
|
|
|
|
// Call the operator method on the left-hand side.
|
|
|
|
|
int symbol = ensureSymbol(&compiler->parser->vm->methods, rule->name, 1);
|
2013-12-25 06:04:11 +01:00
|
|
|
emit1(compiler, CODE_CALL_0, symbol);
|
2013-11-13 16:10:52 +01:00
|
|
|
}
|
|
|
|
|
|
2013-12-15 08:41:23 +01:00
|
|
|
static void boolean(Compiler* compiler, bool allowAssignment)
|
2013-11-04 06:38:58 +01:00
|
|
|
{
|
2013-12-25 06:04:11 +01:00
|
|
|
emit(compiler,
|
|
|
|
|
compiler->parser->previous.type == TOKEN_FALSE ? CODE_FALSE : CODE_TRUE);
|
2013-11-04 06:38:58 +01:00
|
|
|
}
|
|
|
|
|
|
2013-12-15 08:41:23 +01:00
|
|
|
static void function(Compiler* compiler, bool allowAssignment)
|
2013-11-06 16:47:47 +01:00
|
|
|
{
|
|
|
|
|
Compiler fnCompiler;
|
2013-12-19 16:02:27 +01:00
|
|
|
int constant = initCompiler(&fnCompiler, compiler->parser, compiler,
|
|
|
|
|
NULL, 0);
|
2013-11-13 16:10:52 +01:00
|
|
|
|
2013-11-15 03:25:28 +01:00
|
|
|
parameterList(&fnCompiler, NULL, NULL);
|
|
|
|
|
|
2013-11-06 16:47:47 +01:00
|
|
|
if (match(&fnCompiler, TOKEN_LEFT_BRACE))
|
|
|
|
|
{
|
|
|
|
|
// Block body.
|
2013-11-20 05:54:47 +01:00
|
|
|
finishBlock(&fnCompiler);
|
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
|
|
|
|
|
|
|
|
// Implicitly return null.
|
|
|
|
|
emit(&fnCompiler, CODE_NULL);
|
|
|
|
|
emit(&fnCompiler, CODE_RETURN);
|
2013-11-06 16:47:47 +01:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Single expression body.
|
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(&fnCompiler);
|
|
|
|
|
emit(&fnCompiler, CODE_RETURN);
|
2013-11-06 16:47:47 +01:00
|
|
|
}
|
|
|
|
|
|
2013-12-01 23:59:56 +01:00
|
|
|
endCompiler(&fnCompiler, constant);
|
2013-11-06 16:47:47 +01:00
|
|
|
}
|
|
|
|
|
|
2013-12-15 08:41:23 +01:00
|
|
|
static void field(Compiler* compiler, bool allowAssignment)
|
2013-11-23 23:55:05 +01:00
|
|
|
{
|
2013-11-29 19:53:56 +01:00
|
|
|
int field;
|
|
|
|
|
if (compiler->fields != NULL)
|
|
|
|
|
{
|
|
|
|
|
// Look up the field, or implicitly define it.
|
|
|
|
|
field = ensureSymbol(compiler->fields,
|
2013-12-02 00:22:24 +01:00
|
|
|
compiler->parser->previous.start,
|
|
|
|
|
compiler->parser->previous.length);
|
2013-11-29 19:53:56 +01:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
error(compiler, "Cannot reference a field outside of a class definition.");
|
|
|
|
|
// Initialize it with a fake value so we can keep parsing and minimize the
|
|
|
|
|
// number of cascaded errors.
|
|
|
|
|
field = 255;
|
|
|
|
|
}
|
2013-11-23 23:55:05 +01:00
|
|
|
|
|
|
|
|
// If there's an "=" after a field name, it's an assignment.
|
|
|
|
|
if (match(compiler, TOKEN_EQ))
|
|
|
|
|
{
|
|
|
|
|
if (!allowAssignment) error(compiler, "Invalid assignment.");
|
|
|
|
|
|
|
|
|
|
// 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-11-23 23:55:05 +01:00
|
|
|
|
2013-12-23 20:17:40 +01:00
|
|
|
// If we're directly inside a method, use a more optimal instruction.
|
|
|
|
|
if (compiler->methodName != NULL)
|
|
|
|
|
{
|
2013-12-25 06:04:11 +01:00
|
|
|
emit1(compiler, CODE_STORE_FIELD_THIS, field);
|
2013-12-23 20:17:40 +01:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
loadThis(compiler);
|
2013-12-25 06:04:11 +01:00
|
|
|
emit1(compiler, CODE_STORE_FIELD, field);
|
2013-12-23 20:17:40 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// If we're directly inside a method, use a more optimal instruction.
|
|
|
|
|
if (compiler->methodName != NULL)
|
|
|
|
|
{
|
2013-12-25 06:04:11 +01:00
|
|
|
emit1(compiler, CODE_LOAD_FIELD_THIS, field);
|
2013-12-23 20:17:40 +01:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
loadThis(compiler);
|
2013-12-25 06:04:11 +01:00
|
|
|
emit1(compiler, CODE_LOAD_FIELD, field);
|
2013-12-23 20:17:40 +01:00
|
|
|
}
|
2013-11-23 23:55:05 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-15 08:41:23 +01:00
|
|
|
static void name(Compiler* compiler, bool allowAssignment)
|
2013-10-23 22:51:41 +02:00
|
|
|
{
|
2013-12-01 03:28:01 +01:00
|
|
|
// Look up the name in the scope chain.
|
2013-12-23 05:46:12 +01:00
|
|
|
Token* token = &compiler->parser->previous;
|
|
|
|
|
|
2013-12-23 05:53:35 +01:00
|
|
|
Code loadInstruction;
|
|
|
|
|
int index = resolveName(compiler, token->start, token->length,
|
|
|
|
|
&loadInstruction);
|
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 (index == -1) error(compiler, "Undefined variable.");
|
2013-11-13 16:10:52 +01:00
|
|
|
|
|
|
|
|
// If there's an "=" after a bare name, it's a variable assignment.
|
|
|
|
|
if (match(compiler, TOKEN_EQ))
|
2013-10-26 05:40:24 +02:00
|
|
|
{
|
2013-11-13 16:10:52 +01:00
|
|
|
if (!allowAssignment) error(compiler, "Invalid assignment.");
|
|
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
|
switch (loadInstruction)
|
2013-11-13 16:10:52 +01:00
|
|
|
{
|
2013-12-25 06:04:11 +01:00
|
|
|
case CODE_LOAD_LOCAL: emit1(compiler, CODE_STORE_LOCAL, index); break;
|
|
|
|
|
case CODE_LOAD_UPVALUE: emit1(compiler, CODE_STORE_UPVALUE, index); break;
|
|
|
|
|
case CODE_LOAD_GLOBAL: emit1(compiler, CODE_STORE_GLOBAL, index); break;
|
2013-12-23 05:53:35 +01:00
|
|
|
default:
|
|
|
|
|
UNREACHABLE();
|
2013-11-13 16:10:52 +01:00
|
|
|
}
|
2013-12-23 05:53:35 +01:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2013-12-25 06:04:11 +01:00
|
|
|
emit1(compiler, loadInstruction, index);
|
2013-10-26 05:40:24 +02:00
|
|
|
}
|
2013-10-22 21:16:39 +02:00
|
|
|
}
|
|
|
|
|
|
2013-12-15 08:41:23 +01:00
|
|
|
static void null(Compiler* compiler, bool allowAssignment)
|
2013-11-06 03:22:22 +01:00
|
|
|
{
|
|
|
|
|
emit(compiler, CODE_NULL);
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-15 08:41:23 +01:00
|
|
|
static void number(Compiler* compiler, bool allowAssignment)
|
2013-10-22 21:16:39 +02:00
|
|
|
{
|
2013-10-24 21:39:01 +02:00
|
|
|
Token* token = &compiler->parser->previous;
|
2013-10-22 22:37:53 +02:00
|
|
|
char* end;
|
2013-11-10 06:32:57 +01:00
|
|
|
|
2013-12-02 00:22:24 +01:00
|
|
|
double value = strtod(token->start, &end);
|
2013-12-15 00:28:18 +01:00
|
|
|
// TODO: Check errno == ERANGE here.
|
2013-12-02 00:22:24 +01:00
|
|
|
if (end == token->start)
|
2013-10-22 22:37:53 +02:00
|
|
|
{
|
|
|
|
|
error(compiler, "Invalid number literal.");
|
|
|
|
|
value = 0;
|
|
|
|
|
}
|
|
|
|
|
|
2013-10-22 21:16:39 +02:00
|
|
|
// Define a constant for the literal.
|
2013-11-16 20:41:29 +01:00
|
|
|
int constant = addConstant(compiler, NUM_VAL(value));
|
2013-10-22 21:16:39 +02:00
|
|
|
|
|
|
|
|
// Compile the code to load the constant.
|
2013-12-25 06:04:11 +01:00
|
|
|
emit1(compiler, CODE_CONSTANT, constant);
|
2013-10-22 21:16:39 +02:00
|
|
|
}
|
|
|
|
|
|
2013-12-15 08:41:23 +01:00
|
|
|
static void string(Compiler* compiler, bool allowAssignment)
|
2013-10-26 05:40:24 +02:00
|
|
|
{
|
|
|
|
|
// Define a constant for the literal.
|
refactor: rename internal value creation functions to wren-prefixed public API names
Move `allocate`, `initObj`, `newClass`, and related static helpers from `vm.c` into `value.c` with consistent `wren` prefix, and update all call sites in `compiler.c`, `primitives.c`, and `vm.c` to use the new names (`wrenNewFunction`, `wrenNewString`, `wrenNewClass`, `wrenNewInstance`, `wrenNewList`). Remove the old declarations from `vm.h` and add comprehensive documentation comments to `value.h` explaining the NaN-tagging representation and type system.
2013-11-28 05:00:03 +01:00
|
|
|
int constant = addConstant(compiler, wrenNewString(compiler->parser->vm,
|
2013-11-24 06:00:47 +01:00
|
|
|
compiler->parser->currentString, compiler->parser->currentStringLength));
|
2013-11-12 17:32:35 +01:00
|
|
|
|
2013-10-26 05:40:24 +02:00
|
|
|
// Compile the code to load the constant.
|
2013-12-25 06:04:11 +01:00
|
|
|
emit1(compiler, CODE_CONSTANT, constant);
|
2013-10-26 05:40:24 +02:00
|
|
|
}
|
|
|
|
|
|
2013-12-16 07:08:40 +01:00
|
|
|
// Returns true if [compiler] is compiling a chunk of code that is either
|
|
|
|
|
// directly or indirectly contained in a method for a class.
|
|
|
|
|
static bool isInsideMethod(Compiler* compiler)
|
|
|
|
|
{
|
|
|
|
|
// Walk up the parent chain to see if there is an enclosing method.
|
|
|
|
|
while (compiler != NULL)
|
|
|
|
|
{
|
2013-12-19 16:02:27 +01:00
|
|
|
if (compiler->methodName != NULL) return true;
|
2013-12-16 07:08:40 +01:00
|
|
|
compiler = compiler->parent;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-19 16:02:27 +01:00
|
|
|
static void new_(Compiler* compiler, bool allowAssignment)
|
|
|
|
|
{
|
|
|
|
|
// TODO: Instead of an expression, explicitly only allow a dotted name.
|
|
|
|
|
// Compile the class.
|
|
|
|
|
parsePrecedence(compiler, false, PREC_CALL);
|
|
|
|
|
|
|
|
|
|
// Create the instance of the class.
|
|
|
|
|
emit(compiler, CODE_NEW);
|
|
|
|
|
|
|
|
|
|
// Invoke the constructor on the new instance.
|
|
|
|
|
char name[MAX_METHOD_SIGNATURE];
|
|
|
|
|
strcpy(name, "new");
|
|
|
|
|
methodCall(compiler, CODE_CALL_0, name, 3);
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-15 08:41:23 +01:00
|
|
|
static void super_(Compiler* compiler, bool allowAssignment)
|
2013-12-13 17:37:49 +01:00
|
|
|
{
|
2013-12-16 07:08:40 +01:00
|
|
|
if (!isInsideMethod(compiler))
|
|
|
|
|
{
|
|
|
|
|
error(compiler, "Cannot use 'super' outside of a method.");
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-23 20:17:40 +01:00
|
|
|
loadThis(compiler);
|
2013-12-23 05:53:35 +01:00
|
|
|
|
2013-12-17 18:39:05 +01:00
|
|
|
// TODO: Super operator calls.
|
2013-12-13 17:37:49 +01:00
|
|
|
|
2013-12-19 16:02:27 +01:00
|
|
|
// See if it's a named super call, or an unnamed one.
|
|
|
|
|
if (match(compiler, TOKEN_DOT))
|
|
|
|
|
{
|
|
|
|
|
// Compile the superclass call.
|
|
|
|
|
namedCall(compiler, allowAssignment, CODE_SUPER_0);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// No explicit name, so use the name of the enclosing method.
|
|
|
|
|
char name[MAX_METHOD_SIGNATURE];
|
|
|
|
|
int length = 0;
|
|
|
|
|
|
|
|
|
|
Compiler* thisCompiler = compiler;
|
|
|
|
|
while (thisCompiler != NULL)
|
|
|
|
|
{
|
|
|
|
|
if (thisCompiler->methodName != NULL)
|
|
|
|
|
{
|
|
|
|
|
length = thisCompiler->methodLength;
|
|
|
|
|
strncpy(name, thisCompiler->methodName, length);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
thisCompiler = thisCompiler->parent;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Call the superclass method with the same name.
|
|
|
|
|
methodCall(compiler, CODE_SUPER_0, name, length);
|
|
|
|
|
}
|
2013-12-13 17:37:49 +01:00
|
|
|
}
|
|
|
|
|
|
2013-12-15 08:41:23 +01:00
|
|
|
static void this_(Compiler* compiler, bool allowAssignment)
|
2013-11-10 00:42:23 +01:00
|
|
|
{
|
2013-12-16 07:08:40 +01:00
|
|
|
if (!isInsideMethod(compiler))
|
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]`.
|
2013-12-15 08:41:23 +01:00
|
|
|
static void subscript(Compiler* compiler, bool allowAssignment)
|
2013-11-24 19:45:07 +01:00
|
|
|
{
|
2013-12-01 09:04:46 +01:00
|
|
|
char name[MAX_METHOD_SIGNATURE];
|
2013-11-24 19:45:07 +01:00
|
|
|
int length = 1;
|
|
|
|
|
int numArgs = 0;
|
|
|
|
|
|
|
|
|
|
// Build the method name. To allow overloading by arity, we add a space to
|
|
|
|
|
// the name for each argument.
|
|
|
|
|
name[0] = '[';
|
|
|
|
|
|
|
|
|
|
// Parse the argument list.
|
|
|
|
|
do
|
|
|
|
|
{
|
2013-12-21 18:37:59 +01:00
|
|
|
validateNumParameters(compiler, ++numArgs);
|
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-24 19:45:07 +01:00
|
|
|
|
|
|
|
|
// Add a space in the name for each argument. Lets us overload by
|
|
|
|
|
// arity.
|
|
|
|
|
name[length++] = ' ';
|
|
|
|
|
}
|
|
|
|
|
while (match(compiler, TOKEN_COMMA));
|
2013-11-30 00:08:27 +01:00
|
|
|
|
2013-11-24 19:45:07 +01:00
|
|
|
consume(compiler, TOKEN_RIGHT_BRACKET, "Expect ']' after arguments.");
|
|
|
|
|
|
|
|
|
|
name[length++] = ']';
|
|
|
|
|
|
2013-12-21 18:37:59 +01:00
|
|
|
if (match(compiler, TOKEN_EQ))
|
|
|
|
|
{
|
|
|
|
|
if (!allowAssignment) error(compiler, "Invalid assignment.");
|
|
|
|
|
|
|
|
|
|
name[length++] = '=';
|
|
|
|
|
|
|
|
|
|
// Compile the assigned value.
|
|
|
|
|
validateNumParameters(compiler, ++numArgs);
|
|
|
|
|
expression(compiler);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int symbol = ensureSymbol(&compiler->parser->vm->methods, name, length);
|
2013-11-24 19:45:07 +01:00
|
|
|
|
|
|
|
|
// Compile the method call.
|
2013-12-25 06:04:11 +01:00
|
|
|
emit1(compiler, CODE_CALL_0 + numArgs, symbol);
|
2013-11-24 19:45:07 +01:00
|
|
|
}
|
|
|
|
|
|
2013-12-15 08:41:23 +01:00
|
|
|
void call(Compiler* compiler, bool allowAssignment)
|
2013-11-03 19:16:14 +01:00
|
|
|
{
|
2013-12-13 17:37:49 +01:00
|
|
|
namedCall(compiler, allowAssignment, CODE_CALL_0);
|
2013-11-03 19:16:14 +01:00
|
|
|
}
|
|
|
|
|
|
2013-12-15 08:41:23 +01:00
|
|
|
void is(Compiler* compiler, bool allowAssignment)
|
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
|
|
|
{
|
|
|
|
|
// Compile the right-hand side.
|
2013-12-20 16:05:31 +01:00
|
|
|
parsePrecedence(compiler, false, PREC_CALL);
|
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
|
|
|
|
|
|
|
|
emit(compiler, CODE_IS);
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-15 08:41:23 +01:00
|
|
|
void and(Compiler* compiler, bool allowAssignment)
|
2013-11-19 16:35:25 +01:00
|
|
|
{
|
|
|
|
|
// Skip the right argument if the left is false.
|
2013-12-25 06:04:11 +01:00
|
|
|
int jump = emit1(compiler, CODE_AND, 255);
|
2013-12-20 16:05:31 +01:00
|
|
|
parsePrecedence(compiler, false, PREC_LOGIC);
|
2013-11-19 16:35:25 +01:00
|
|
|
patchJump(compiler, jump);
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-15 08:41:23 +01:00
|
|
|
void or(Compiler* compiler, bool allowAssignment)
|
2013-11-20 03:24:58 +01:00
|
|
|
{
|
|
|
|
|
// Skip the right argument if the left is true.
|
2013-12-25 06:04:11 +01:00
|
|
|
int jump = emit1(compiler, CODE_OR, 255);
|
2013-12-20 16:05:31 +01:00
|
|
|
parsePrecedence(compiler, false, PREC_LOGIC);
|
2013-11-20 03:24:58 +01:00
|
|
|
patchJump(compiler, jump);
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-15 08:41:23 +01:00
|
|
|
void infixOp(Compiler* compiler, bool allowAssignment)
|
2013-11-03 19:16:14 +01:00
|
|
|
{
|
2013-11-13 16:10:52 +01:00
|
|
|
GrammarRule* rule = &rules[compiler->parser->previous.type];
|
2013-11-03 19:16:14 +01:00
|
|
|
|
|
|
|
|
// Compile the right-hand side.
|
2013-12-20 16:05:31 +01:00
|
|
|
parsePrecedence(compiler, false, rule->precedence + 1);
|
2013-11-03 19:16:14 +01:00
|
|
|
|
|
|
|
|
// Call the operator method on the left-hand side.
|
2013-11-09 20:18:27 +01:00
|
|
|
int symbol = ensureSymbol(&compiler->parser->vm->methods,
|
2013-11-04 06:38:58 +01:00
|
|
|
rule->name, strlen(rule->name));
|
2013-12-25 06:04:11 +01:00
|
|
|
emit1(compiler, CODE_CALL_1, symbol);
|
2013-11-03 19:16:14 +01:00
|
|
|
}
|
|
|
|
|
|
2013-11-14 19:27:33 +01:00
|
|
|
// Compiles a method signature for an infix operator.
|
|
|
|
|
void infixSignature(Compiler* compiler, char* name, int* length)
|
|
|
|
|
{
|
|
|
|
|
// Add a space for the RHS parameter.
|
|
|
|
|
name[(*length)++] = ' ';
|
|
|
|
|
|
|
|
|
|
// Parse the parameter name.
|
|
|
|
|
declareVariable(compiler);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Compiles a method signature for an unary operator (i.e. "!").
|
|
|
|
|
void unarySignature(Compiler* compiler, char* name, int* length)
|
|
|
|
|
{
|
|
|
|
|
// Do nothing. The name is already complete.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Compiles a method signature for an operator that can either be unary or
|
|
|
|
|
// infix (i.e. "-").
|
|
|
|
|
void mixedSignature(Compiler* compiler, char* name, int* length)
|
|
|
|
|
{
|
|
|
|
|
// If there is a parameter name, it's an infix operator, otherwise it's unary.
|
|
|
|
|
if (compiler->parser->current.type == TOKEN_NAME)
|
|
|
|
|
{
|
|
|
|
|
// Add a space for the RHS parameter.
|
|
|
|
|
name[(*length)++] = ' ';
|
|
|
|
|
|
|
|
|
|
// Parse the parameter name.
|
|
|
|
|
declareVariable(compiler);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-20 16:05:31 +01:00
|
|
|
// Compiles a method signature for a named method or setter.
|
|
|
|
|
void namedSignature(Compiler* compiler, char* name, int* length)
|
|
|
|
|
{
|
|
|
|
|
if (match(compiler, TOKEN_EQ))
|
|
|
|
|
{
|
|
|
|
|
// It's a setter.
|
|
|
|
|
// TODO: Allow setters with parameters? Like: foo.bar(1, 2) = "blah"
|
|
|
|
|
name[(*length)++] = '=';
|
|
|
|
|
name[(*length)++] = ' ';
|
|
|
|
|
|
|
|
|
|
// Parse the value parameter.
|
|
|
|
|
declareVariable(compiler);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Regular named method with an optional parameter list.
|
|
|
|
|
parameterList(compiler, name, length);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-19 16:02:27 +01:00
|
|
|
// Compiles a method signature for a constructor.
|
|
|
|
|
void constructorSignature(Compiler* compiler, char* name, int* length)
|
|
|
|
|
{
|
|
|
|
|
// Add the parameters, if there are any.
|
|
|
|
|
parameterList(compiler, name, length);
|
|
|
|
|
}
|
|
|
|
|
|
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,
|
2013-11-24 22:38:31 +01:00
|
|
|
/* TOKEN_LEFT_BRACKET */ { list, subscript, NULL, PREC_CALL, NULL },
|
2013-11-07 16:04:25 +01:00
|
|
|
/* TOKEN_RIGHT_BRACKET */ UNUSED,
|
|
|
|
|
/* TOKEN_LEFT_BRACE */ UNUSED,
|
|
|
|
|
/* TOKEN_RIGHT_BRACE */ UNUSED,
|
|
|
|
|
/* TOKEN_COLON */ UNUSED,
|
|
|
|
|
/* TOKEN_DOT */ INFIX(PREC_CALL, call),
|
|
|
|
|
/* TOKEN_COMMA */ UNUSED,
|
|
|
|
|
/* TOKEN_STAR */ INFIX_OPERATOR(PREC_FACTOR, "* "),
|
|
|
|
|
/* TOKEN_SLASH */ INFIX_OPERATOR(PREC_FACTOR, "/ "),
|
|
|
|
|
/* TOKEN_PERCENT */ INFIX_OPERATOR(PREC_TERM, "% "),
|
|
|
|
|
/* TOKEN_PLUS */ INFIX_OPERATOR(PREC_TERM, "+ "),
|
2013-12-05 07:09:31 +01:00
|
|
|
/* TOKEN_MINUS */ OPERATOR("- "),
|
2013-11-07 16:04:25 +01:00
|
|
|
/* TOKEN_PIPE */ UNUSED,
|
2013-11-20 03:24:58 +01:00
|
|
|
/* TOKEN_PIPEPIPE */ INFIX(PREC_LOGIC, or),
|
2013-11-07 16:04:25 +01:00
|
|
|
/* TOKEN_AMP */ UNUSED,
|
2013-11-19 16:35:25 +01:00
|
|
|
/* TOKEN_AMPAMP */ INFIX(PREC_LOGIC, 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("~"),
|
2013-11-07 16:04:25 +01:00
|
|
|
/* TOKEN_EQ */ UNUSED,
|
|
|
|
|
/* 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,
|
|
|
|
|
/* TOKEN_ELSE */ UNUSED,
|
|
|
|
|
/* TOKEN_FALSE */ PREFIX(boolean),
|
|
|
|
|
/* TOKEN_FN */ PREFIX(function),
|
2013-12-24 19:15:50 +01:00
|
|
|
/* TOKEN_FOR */ UNUSED,
|
2013-11-07 16:04:25 +01:00
|
|
|
/* TOKEN_IF */ UNUSED,
|
2013-12-25 06:04:11 +01:00
|
|
|
/* TOKEN_IN */ UNUSED,
|
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 */ INFIX(PREC_IS, is),
|
2013-12-19 16:02:27 +01:00
|
|
|
/* TOKEN_NEW */ { new_, NULL, constructorSignature, PREC_NONE, NULL },
|
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_),
|
2013-11-10 00:42:23 +01: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),
|
2013-12-20 16:05:31 +01:00
|
|
|
/* TOKEN_NAME */ { name, NULL, namedSignature, PREC_NONE, NULL },
|
2013-11-07 16:04:25 +01:00
|
|
|
/* TOKEN_NUMBER */ PREFIX(number),
|
|
|
|
|
/* TOKEN_STRING */ PREFIX(string),
|
|
|
|
|
/* TOKEN_LINE */ UNUSED,
|
|
|
|
|
/* TOKEN_ERROR */ UNUSED,
|
|
|
|
|
/* TOKEN_EOF */ UNUSED
|
|
|
|
|
};
|
2013-10-22 21:16:39 +02:00
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
// The main entrypoint for the top-down operator precedence parser.
|
2013-12-15 08:41:23 +01:00
|
|
|
void parsePrecedence(Compiler* compiler, bool allowAssignment,
|
2013-11-13 16:10:52 +01:00
|
|
|
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
|
|
|
{
|
2013-11-29 19:53:56 +01:00
|
|
|
error(compiler, "Unexpected token for expression.");
|
2013-11-07 16:04:25 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-13 16:10:52 +01:00
|
|
|
prefix(compiler, allowAssignment);
|
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;
|
|
|
|
|
infix(compiler, allowAssignment);
|
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
|
|
|
{
|
2013-12-20 16:05:31 +01:00
|
|
|
parsePrecedence(compiler, true, PREC_LOWEST);
|
2013-11-13 16:10:52 +01:00
|
|
|
}
|
|
|
|
|
|
2013-11-14 19:27:33 +01:00
|
|
|
// Compiles a method definition inside a class body.
|
2013-12-15 08:41:23 +01:00
|
|
|
void method(Compiler* compiler, Code instruction, bool isConstructor,
|
2013-12-13 20:32:47 +01:00
|
|
|
SignatureFn signature)
|
2013-11-14 19:27:33 +01:00
|
|
|
{
|
|
|
|
|
// Build the method name.
|
2013-12-01 09:04:46 +01:00
|
|
|
char name[MAX_METHOD_SIGNATURE];
|
|
|
|
|
int length = copyName(compiler, name);
|
2013-11-14 19:27:33 +01:00
|
|
|
|
2013-12-19 16:02:27 +01:00
|
|
|
Compiler methodCompiler;
|
|
|
|
|
int constant = initCompiler(&methodCompiler, compiler->parser, compiler,
|
|
|
|
|
name, length);
|
|
|
|
|
|
2013-11-14 19:27:33 +01:00
|
|
|
// Compile the method signature.
|
|
|
|
|
signature(&methodCompiler, name, &length);
|
|
|
|
|
|
|
|
|
|
int symbol = ensureSymbol(&compiler->parser->vm->methods, name, length);
|
|
|
|
|
|
2013-12-17 18:39:05 +01:00
|
|
|
consume(compiler, TOKEN_LEFT_BRACE, "Expect '{' to begin method body.");
|
2013-12-13 20:32:47 +01:00
|
|
|
|
2013-11-20 05:54:47 +01:00
|
|
|
finishBlock(&methodCompiler);
|
2013-12-15 00:28:18 +01:00
|
|
|
// TODO: Single-expression methods that implicitly return the result.
|
2013-11-14 19:27:33 +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 it's a constructor, return "this".
|
2013-12-13 20:32:47 +01:00
|
|
|
if (isConstructor)
|
2013-11-20 16:20:16 +01:00
|
|
|
{
|
|
|
|
|
// The receiver is always stored in the first local slot.
|
2013-12-25 06:04:11 +01:00
|
|
|
emit1(&methodCompiler, CODE_LOAD_LOCAL, 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
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2013-12-19 16:02:27 +01:00
|
|
|
// Implicitly return null in case there is no explicit 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
|
|
|
emit(&methodCompiler, CODE_NULL);
|
2013-11-20 16:20:16 +01:00
|
|
|
}
|
|
|
|
|
|
2013-12-19 16:02:27 +01:00
|
|
|
emit(&methodCompiler, CODE_RETURN);
|
|
|
|
|
|
2013-12-01 23:59:56 +01:00
|
|
|
endCompiler(&methodCompiler, constant);
|
2013-11-14 19:27:33 +01:00
|
|
|
|
2013-12-01 23:59:56 +01:00
|
|
|
// Compile the code to define the method.
|
2013-12-25 06:04:11 +01:00
|
|
|
emit1(compiler, instruction, symbol);
|
2013-11-14 19:27:33 +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
|
|
|
// Parses a curly block or an expression statement. Used in places like the
|
|
|
|
|
// arms of an if statement where either a single expression or a curly body is
|
|
|
|
|
// allowed.
|
|
|
|
|
void block(Compiler* compiler)
|
|
|
|
|
{
|
|
|
|
|
// Curly block.
|
|
|
|
|
if (match(compiler, TOKEN_LEFT_BRACE))
|
|
|
|
|
{
|
|
|
|
|
pushScope(compiler);
|
|
|
|
|
finishBlock(compiler);
|
|
|
|
|
popScope(compiler);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-24 19:27:48 +01:00
|
|
|
// Single statement body.
|
|
|
|
|
statement(compiler);
|
2013-12-20 16:05:31 +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
|
|
|
|
2013-12-24 19:15:50 +01:00
|
|
|
// Returns the number of arguments to the instruction at [ip] in [fn]'s
|
|
|
|
|
// bytecode.
|
|
|
|
|
static int getNumArguments(ObjFn* fn, int ip)
|
|
|
|
|
{
|
|
|
|
|
Code instruction = fn->bytecode[ip];
|
|
|
|
|
switch (instruction)
|
|
|
|
|
{
|
|
|
|
|
case CODE_NULL:
|
|
|
|
|
case CODE_FALSE:
|
|
|
|
|
case CODE_TRUE:
|
|
|
|
|
case CODE_POP:
|
|
|
|
|
case CODE_IS:
|
|
|
|
|
case CODE_CLOSE_UPVALUE:
|
|
|
|
|
case CODE_RETURN:
|
|
|
|
|
case CODE_NEW:
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
// Instructions with two arguments:
|
|
|
|
|
case CODE_METHOD_INSTANCE:
|
|
|
|
|
case CODE_METHOD_STATIC:
|
|
|
|
|
return 2;
|
|
|
|
|
|
|
|
|
|
case CODE_CLOSURE:
|
|
|
|
|
{
|
|
|
|
|
int constant = fn->bytecode[ip + 1];
|
|
|
|
|
ObjFn* loadedFn = AS_FN(fn->constants[constant]);
|
|
|
|
|
|
|
|
|
|
// There is an argument for the constant, then one for each upvalue.
|
|
|
|
|
return 1 + loadedFn->numUpvalues;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
// Most instructions have one argument.
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-25 06:04:11 +01:00
|
|
|
static int startLoopBody(Compiler* compiler)
|
2013-12-24 19:15:50 +01:00
|
|
|
{
|
|
|
|
|
int outerLoopBody = compiler->loopBody;
|
|
|
|
|
compiler->loopBody = compiler->numCodes;
|
2013-12-25 06:04:11 +01:00
|
|
|
return outerLoopBody;
|
|
|
|
|
}
|
2013-12-24 19:15:50 +01:00
|
|
|
|
2013-12-25 06:04:11 +01:00
|
|
|
static void endLoopBody(Compiler* compiler, int outerLoopBody)
|
|
|
|
|
{
|
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.
|
|
|
|
|
int i = compiler->loopBody;
|
|
|
|
|
while (i < compiler->numCodes)
|
|
|
|
|
{
|
|
|
|
|
if (compiler->fn->bytecode[i] == CODE_END)
|
|
|
|
|
{
|
|
|
|
|
compiler->fn->bytecode[i] = CODE_JUMP;
|
|
|
|
|
patchJump(compiler, i + 1);
|
|
|
|
|
i += 2;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Skip this instruction and its arguments.
|
|
|
|
|
i += 1 + getNumArguments(compiler->fn, i);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
compiler->loopBody = outerLoopBody;
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-25 06:04:11 +01:00
|
|
|
static void forStatement(Compiler* compiler)
|
|
|
|
|
{
|
|
|
|
|
// A for statement like:
|
|
|
|
|
//
|
|
|
|
|
// for (i in sequence.expression) {
|
|
|
|
|
// IO.write(i)
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// Is compiled to bytecode almost as if the source looked like this:
|
|
|
|
|
//
|
|
|
|
|
// {
|
|
|
|
|
// var seq_ = sequence.expression
|
|
|
|
|
// var iter_
|
|
|
|
|
// while (true) {
|
|
|
|
|
// iter_ = seq_.iterate(iter_)
|
|
|
|
|
// if (!iter_) break
|
|
|
|
|
// var i = set_.iteratorValue(iter_)
|
|
|
|
|
// IO.write(i)
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// It's not exactly this, because the synthetic variables `seq_` and `iter_`
|
|
|
|
|
// actually get names that aren't valid Wren identfiers. Also, the `while`
|
|
|
|
|
// and `break` are just the bytecode for explicit loops and jumps. But that's
|
|
|
|
|
// the basic idea.
|
|
|
|
|
//
|
|
|
|
|
// The important parts are:
|
|
|
|
|
// - The sequence expression is only evaluated once.
|
|
|
|
|
// - The .iterate() method is used to advance the iterator and determine if
|
|
|
|
|
// it should exit the loop.
|
|
|
|
|
// - The .iteratorValue() method is used to get the value at the current
|
|
|
|
|
// iterator position.
|
|
|
|
|
|
|
|
|
|
// Create a scope for the hidden local variables used for the iterator.
|
|
|
|
|
pushScope(compiler);
|
|
|
|
|
|
|
|
|
|
consume(compiler, TOKEN_LEFT_PAREN, "Expect '(' after 'for'.");
|
|
|
|
|
consume(compiler, TOKEN_NAME, "Expect for loop variable name.");
|
|
|
|
|
|
|
|
|
|
// Remember the name of the loop variable.
|
|
|
|
|
const char* name = compiler->parser->previous.start;
|
|
|
|
|
int length = compiler->parser->previous.length;
|
|
|
|
|
|
|
|
|
|
consume(compiler, TOKEN_IN, "Expect 'in' after loop variable.");
|
|
|
|
|
|
|
|
|
|
// Evaluate the sequence expression and store it in a hidden local variable.
|
|
|
|
|
// The space in the variable name ensures it won't collide with a user-defined
|
|
|
|
|
// variable.
|
|
|
|
|
expression(compiler);
|
|
|
|
|
int seqSlot = defineLocal(compiler, "seq ", 4);
|
|
|
|
|
|
|
|
|
|
// Create another hidden local for the iterator object.
|
|
|
|
|
null(compiler, false);
|
|
|
|
|
int iterSlot = defineLocal(compiler, "iter ", 5);
|
|
|
|
|
|
|
|
|
|
consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after loop expression.");
|
|
|
|
|
|
|
|
|
|
// Remember what instruction to loop back to.
|
|
|
|
|
int loopStart = compiler->numCodes - 1;
|
|
|
|
|
|
|
|
|
|
// Advance the iterator by calling the ".iterate" method on the sequence.
|
|
|
|
|
emit1(compiler, CODE_LOAD_LOCAL, seqSlot);
|
|
|
|
|
emit1(compiler, CODE_LOAD_LOCAL, iterSlot);
|
|
|
|
|
|
|
|
|
|
int iterateSymbol = ensureSymbol(&compiler->parser->vm->methods,
|
|
|
|
|
"iterate ", 8);
|
|
|
|
|
emit1(compiler, CODE_CALL_1, iterateSymbol);
|
|
|
|
|
|
|
|
|
|
// Store the iterator back in its local for the next iteration.
|
|
|
|
|
emit1(compiler, CODE_STORE_LOCAL, iterSlot);
|
|
|
|
|
// TODO: We can probably get this working with a bit less stack juggling.
|
|
|
|
|
|
|
|
|
|
// If it returned something falsy, jump out of the loop.
|
|
|
|
|
int exitJump = emit1(compiler, CODE_JUMP_IF, 255);
|
|
|
|
|
|
|
|
|
|
// Create a scope for the loop body.
|
|
|
|
|
pushScope(compiler);
|
|
|
|
|
|
|
|
|
|
// Get the current value in the sequence by calling ".iteratorValue".
|
|
|
|
|
emit1(compiler, CODE_LOAD_LOCAL, seqSlot);
|
|
|
|
|
emit1(compiler, CODE_LOAD_LOCAL, iterSlot);
|
|
|
|
|
|
|
|
|
|
int iteratorValueSymbol = ensureSymbol(&compiler->parser->vm->methods,
|
|
|
|
|
"iteratorValue ", 14);
|
|
|
|
|
emit1(compiler, CODE_CALL_1, iteratorValueSymbol);
|
|
|
|
|
|
|
|
|
|
// Bind it to the loop variable.
|
|
|
|
|
defineLocal(compiler, name, length);
|
|
|
|
|
|
|
|
|
|
// Compile the body.
|
|
|
|
|
int outerLoopBody = startLoopBody(compiler);
|
|
|
|
|
block(compiler);
|
|
|
|
|
|
|
|
|
|
popScope(compiler);
|
|
|
|
|
|
|
|
|
|
// Loop back to the top.
|
|
|
|
|
emit(compiler, CODE_LOOP);
|
|
|
|
|
int loopOffset = compiler->numCodes - loopStart;
|
|
|
|
|
emit(compiler, loopOffset);
|
|
|
|
|
|
|
|
|
|
patchJump(compiler, exitJump);
|
|
|
|
|
endLoopBody(compiler, outerLoopBody);
|
|
|
|
|
|
|
|
|
|
popScope(compiler);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void whileStatement(Compiler* compiler)
|
|
|
|
|
{
|
|
|
|
|
// Remember what instruction to loop back to.
|
|
|
|
|
int loopStart = compiler->numCodes - 1;
|
|
|
|
|
|
|
|
|
|
// Compile the condition.
|
|
|
|
|
consume(compiler, TOKEN_LEFT_PAREN, "Expect '(' after 'while'.");
|
|
|
|
|
expression(compiler);
|
|
|
|
|
consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after while condition.");
|
|
|
|
|
|
|
|
|
|
int exitJump = emit1(compiler, CODE_JUMP_IF, 255);
|
|
|
|
|
|
|
|
|
|
// Compile the body.
|
|
|
|
|
int outerLoopBody = startLoopBody(compiler);
|
|
|
|
|
block(compiler);
|
|
|
|
|
|
|
|
|
|
// Loop back to the top.
|
|
|
|
|
emit(compiler, CODE_LOOP);
|
|
|
|
|
int loopOffset = compiler->numCodes - loopStart;
|
|
|
|
|
emit(compiler, loopOffset);
|
|
|
|
|
|
|
|
|
|
patchJump(compiler, exitJump);
|
|
|
|
|
endLoopBody(compiler, outerLoopBody);
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-20 16:05:31 +01:00
|
|
|
// Compiles a statement. These can only appear at the top-level or within
|
|
|
|
|
// curly blocks. Unlike expressions, these do not leave a value on the stack.
|
|
|
|
|
void statement(Compiler* compiler)
|
|
|
|
|
{
|
2013-12-24 19:15:50 +01:00
|
|
|
if (match(compiler, TOKEN_BREAK))
|
|
|
|
|
{
|
|
|
|
|
if (compiler->loopBody == -1)
|
|
|
|
|
{
|
|
|
|
|
error(compiler, "Cannot use 'break' outside of a loop.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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.
|
2013-12-25 06:04:11 +01:00
|
|
|
emit1(compiler, CODE_END, 0);
|
2013-12-24 19:15:50 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-25 06:04:11 +01:00
|
|
|
if (match(compiler, TOKEN_FOR)) return forStatement(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
|
|
|
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.
|
2013-12-25 06:04:11 +01:00
|
|
|
int ifJump = emit1(compiler, CODE_JUMP_IF, 255);
|
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.
|
|
|
|
|
block(compiler);
|
|
|
|
|
|
|
|
|
|
// 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.
|
2013-12-25 06:04:11 +01:00
|
|
|
int elseJump = emit1(compiler, CODE_JUMP, 255);
|
2013-12-18 16:37:41 +01:00
|
|
|
|
|
|
|
|
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
|
|
|
block(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.
|
2013-12-15 00:28:18 +01:00
|
|
|
// TODO: Implicitly return null if there is a newline or } after the
|
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".
|
|
|
|
|
expression(compiler);
|
|
|
|
|
|
|
|
|
|
emit(compiler, CODE_RETURN);
|
2013-11-07 16:04:25 +01:00
|
|
|
return;
|
|
|
|
|
}
|
2013-10-22 22:25:39 +02:00
|
|
|
|
2013-12-25 06:04:11 +01:00
|
|
|
if (match(compiler, TOKEN_WHILE)) return whileStatement(compiler);
|
2013-12-24 19:27:48 +01:00
|
|
|
|
|
|
|
|
// Expression statement.
|
|
|
|
|
expression(compiler);
|
|
|
|
|
emit(compiler, CODE_POP);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Compiles a class definition. Assumes the "class" token has already been
|
|
|
|
|
// consumed.
|
|
|
|
|
static void classDefinition(Compiler* compiler)
|
|
|
|
|
{
|
|
|
|
|
// Create a variable to store the class in.
|
|
|
|
|
// TODO: Allow anonymous classes?
|
|
|
|
|
int symbol = declareVariable(compiler);
|
|
|
|
|
|
|
|
|
|
// Load the superclass (if there is one).
|
|
|
|
|
if (match(compiler, TOKEN_IS))
|
|
|
|
|
{
|
|
|
|
|
parsePrecedence(compiler, false, PREC_CALL);
|
|
|
|
|
emit(compiler, CODE_SUBCLASS);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Create the empty class.
|
|
|
|
|
emit(compiler, CODE_CLASS);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
|
int numFieldsInstruction = emit(compiler, 255);
|
|
|
|
|
|
|
|
|
|
// Set up a symbol table for the class's fields. We'll initially compile
|
|
|
|
|
// them to slots starting at zero. When the method is bound to the close
|
|
|
|
|
// the bytecode will be adjusted by [wrenBindMethod] to take inherited
|
|
|
|
|
// fields into account.
|
|
|
|
|
SymbolTable* previousFields = compiler->fields;
|
|
|
|
|
SymbolTable fields;
|
|
|
|
|
initSymbolTable(&fields);
|
|
|
|
|
compiler->fields = &fields;
|
|
|
|
|
|
|
|
|
|
// Compile the method definitions.
|
|
|
|
|
consume(compiler, TOKEN_LEFT_BRACE, "Expect '}' after class body.");
|
|
|
|
|
while (!match(compiler, TOKEN_RIGHT_BRACE))
|
|
|
|
|
{
|
|
|
|
|
Code instruction = CODE_METHOD_INSTANCE;
|
|
|
|
|
bool isConstructor = false;
|
|
|
|
|
|
|
|
|
|
if (match(compiler, TOKEN_STATIC))
|
|
|
|
|
{
|
|
|
|
|
instruction = CODE_METHOD_STATIC;
|
|
|
|
|
// TODO: Need to handle fields inside static methods correctly.
|
|
|
|
|
// Currently, they're compiled as instance fields, which will be wrong
|
|
|
|
|
// wrong wrong given that the receiver is actually the class obj.
|
|
|
|
|
}
|
|
|
|
|
else if (peek(compiler) == TOKEN_NEW)
|
|
|
|
|
{
|
|
|
|
|
// If the method name is "new", it's a constructor.
|
|
|
|
|
isConstructor = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SignatureFn signature = rules[compiler->parser->current.type].method;
|
|
|
|
|
nextToken(compiler->parser);
|
|
|
|
|
|
|
|
|
|
if (signature == NULL)
|
|
|
|
|
{
|
|
|
|
|
error(compiler, "Expect method definition.");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
method(compiler, instruction, isConstructor, signature);
|
|
|
|
|
consume(compiler, TOKEN_LINE,
|
|
|
|
|
"Expect newline after definition in class.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update the class with the number of fields.
|
|
|
|
|
compiler->fn->bytecode[numFieldsInstruction] = fields.count;
|
|
|
|
|
|
|
|
|
|
compiler->fields = previousFields;
|
|
|
|
|
|
|
|
|
|
// Store it in its name.
|
|
|
|
|
defineVariable(compiler, symbol);
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-25 06:04:11 +01:00
|
|
|
static void variableDefinition(Compiler* compiler)
|
2013-12-24 19:27:48 +01:00
|
|
|
{
|
2013-12-25 06:04:11 +01:00
|
|
|
// TODO: Variable should not be in scope until after initializer.
|
|
|
|
|
int symbol = declareVariable(compiler);
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
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)
|
|
|
|
|
{
|
|
|
|
|
if (match(compiler, TOKEN_CLASS)) return classDefinition(compiler);
|
|
|
|
|
if (match(compiler, TOKEN_VAR)) return variableDefinition(compiler);
|
2013-10-22 22:25: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
|
|
|
block(compiler);
|
2013-10-22 21:16:39 +02:00
|
|
|
}
|
|
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
// Parses [source] to a "function" (a chunk of top-level code) for execution by
|
|
|
|
|
// [vm].
|
2013-11-25 16:47:02 +01:00
|
|
|
ObjFn* wrenCompile(WrenVM* vm, const char* source)
|
2013-10-22 22:25:39 +02:00
|
|
|
{
|
2013-11-07 16:04:25 +01:00
|
|
|
Parser parser;
|
|
|
|
|
parser.vm = vm;
|
|
|
|
|
parser.source = source;
|
2013-12-15 08:41:23 +01:00
|
|
|
parser.hasError = false;
|
2013-10-22 22:25:39 +02:00
|
|
|
|
2013-11-07 16:04:25 +01:00
|
|
|
// Ignore leading newlines.
|
2013-12-15 08:41:23 +01:00
|
|
|
parser.skipNewlines = true;
|
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;
|
|
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
|
|
|
|
|
// Read the first token.
|
|
|
|
|
nextToken(&parser);
|
2013-10-24 21:39:01 +02:00
|
|
|
|
2013-11-10 00:42:23 +01:00
|
|
|
Compiler compiler;
|
2013-12-19 16:02:27 +01:00
|
|
|
initCompiler(&compiler, &parser, NULL, NULL, 0);
|
2013-11-10 00:42:23 +01:00
|
|
|
|
2013-11-29 18:14:19 +01:00
|
|
|
PinnedObj pinned;
|
|
|
|
|
pinObj(vm, (Obj*)compiler.fn, &pinned);
|
2013-11-12 17:32:35 +01:00
|
|
|
|
2013-11-10 00:42:23 +01:00
|
|
|
for (;;)
|
|
|
|
|
{
|
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.
|
|
|
|
|
if (!match(&compiler, TOKEN_LINE))
|
|
|
|
|
{
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (match(&compiler, TOKEN_EOF)) break;
|
|
|
|
|
}
|
|
|
|
|
|
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(&compiler, CODE_NULL);
|
|
|
|
|
emit(&compiler, CODE_RETURN);
|
|
|
|
|
|
2013-12-01 23:59:56 +01:00
|
|
|
endCompiler(&compiler, -1);
|
2013-11-10 00:42:23 +01:00
|
|
|
|
2013-11-29 18:14:19 +01:00
|
|
|
unpinObj(vm);
|
2013-11-12 17:32:35 +01:00
|
|
|
|
2013-11-10 00:42:23 +01:00
|
|
|
return parser.hasError ? NULL : compiler.fn;
|
2013-11-07 16:04:25 +01:00
|
|
|
}
|
2013-12-11 01:13:25 +01:00
|
|
|
|
|
|
|
|
void wrenBindMethod(ObjClass* classObj, ObjFn* fn)
|
|
|
|
|
{
|
2013-12-15 00:28:18 +01:00
|
|
|
// TODO: What about functions nested inside [fn]?
|
2013-12-11 01:13:25 +01:00
|
|
|
int ip = 0;
|
|
|
|
|
for (;;)
|
|
|
|
|
{
|
|
|
|
|
Code instruction = fn->bytecode[ip++];
|
|
|
|
|
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:
|
2013-12-11 01:13:25 +01:00
|
|
|
// Shift this class's fields down past the inherited ones.
|
|
|
|
|
fn->bytecode[ip++] += classObj->superclass->numFields;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case CODE_END:
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
default:
|
2013-12-25 07:10:30 +01:00
|
|
|
// Other instructions are unaffected, so just skip over them.
|
|
|
|
|
ip += getNumArguments(fn, ip - 1);
|
2013-12-11 01:13:25 +01:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|