// MIT License
//
// Copyright (c) 2013-2021 Robert Nystrom and Wren Contributors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// Begin file "wren.h"
#ifndef wren_h
#define wren_h
#include <stdarg.h>
#include <stdlib.h>
#include <stdbool.h>
// The Wren semantic version number components.
#define WREN_VERSION_MAJOR 0
#define WREN_VERSION_MINOR 4
#define WREN_VERSION_PATCH 0
// A human-friendly string representation of the version.
#define WREN_VERSION_STRING "0.4.0"
// A monotonically increasing numeric representation of the version number. Use
// this if you want to do range checks over versions.
#define WREN_VERSION_NUMBER (WREN_VERSION_MAJOR * 1000000 + \
WREN_VERSION_MINOR * 1000 + \
WREN_VERSION_PATCH)
#ifndef WREN_API
#if defined(_MSC_VER) && defined(WREN_API_DLLEXPORT)
#define WREN_API __declspec( dllexport )
#else
#define WREN_API
#endif
#endif //WREN_API
// A single virtual machine for executing Wren code.
//
// Wren has no global state, so all state stored by a running interpreter lives
// here.
typedef struct WrenVM WrenVM;
// A handle to a Wren object.
//
// This lets code outside of the VM hold a persistent reference to an object.
// After a handle is acquired, and until it is released, this ensures the
// garbage collector will not reclaim the object it references.
typedef struct WrenHandle WrenHandle;
// A generic allocation function that handles all explicit memory management
// used by Wren. It's used like so:
//
// - To allocate new memory, [memory] is NULL and [newSize] is the desired
// size. It should return the allocated memory or NULL on failure.
//
// - To attempt to grow an existing allocation, [memory] is the memory, and
// [newSize] is the desired size. It should return [memory] if it was able to
// grow it in place, or a new pointer if it had to move it.
//
// - To shrink memory, [memory] and [newSize] are the same as above but it will
// always return [memory].
//
// - To free memory, [memory] will be the memory to free and [newSize] will be
// zero. It should return NULL.
typedef void* (*WrenReallocateFn)(void* memory, size_t newSize, void* userData);
// A function callable from Wren code, but implemented in C.
typedef void (*WrenForeignMethodFn)(WrenVM* vm);
// A finalizer function for freeing resources owned by an instance of a foreign
// class. Unlike most foreign methods, finalizers do not have access to the VM
// and should not interact with it since it's in the middle of a garbage
// collection.
typedef void (*WrenFinalizerFn)(void* data);
// Gives the host a chance to canonicalize the imported module name,
// potentially taking into account the (previously resolved) name of the module
// that contains the import. Typically, this is used to implement relative
// imports.
typedef const char* (*WrenResolveModuleFn)(WrenVM* vm,
const char* importer, const char* name);
// Forward declare
struct WrenLoadModuleResult;
// Called after loadModuleFn is called for module [name]. The original returned result
// is handed back to you in this callback, so that you can free memory if appropriate.
typedef void (*WrenLoadModuleCompleteFn)(WrenVM* vm, const char* name, struct WrenLoadModuleResult result);
// The result of a loadModuleFn call.
// [source] is the source code for the module, or NULL if the module is not found.
// [onComplete] an optional callback that will be called once Wren is done with the result.
typedef struct WrenLoadModuleResult
{
const char* source;
WrenLoadModuleCompleteFn onComplete;
void* userData;
} WrenLoadModuleResult;
// Loads and returns the source code for the module [name].
typedef WrenLoadModuleResult (*WrenLoadModuleFn)(WrenVM* vm, const char* name);
// Returns a pointer to a foreign method on [className] in [module] with
// [signature].
typedef WrenForeignMethodFn (*WrenBindForeignMethodFn)(WrenVM* vm,
const char* module, const char* className, bool isStatic,
const char* signature);
// Displays a string of text to the user.
typedef void (*WrenWriteFn)(WrenVM* vm, const char* text);
typedef enum
{
// A syntax or resolution error detected at compile time.
WREN_ERROR_COMPILE,
// The error message for a runtime error.
WREN_ERROR_RUNTIME,
// One entry of a runtime error's stack trace.
WREN_ERROR_STACK_TRACE
} WrenErrorType;
// Reports an error to the user.
//
// An error detected during compile time is reported by calling this once with
// [type] `WREN_ERROR_COMPILE`, the resolved name of the [module] and [line]
// where the error occurs, and the compiler's error [message].
//
// A runtime error is reported by calling this once with [type]
// `WREN_ERROR_RUNTIME`, no [module] or [line], and the runtime error's
// [message]. After that, a series of [type] `WREN_ERROR_STACK_TRACE` calls are
// made for each line in the stack trace. Each of those has the resolved
// [module] and [line] where the method or function is defined and [message] is
// the name of the method or function.
typedef void (*WrenErrorFn)(
WrenVM* vm, WrenErrorType type, const char* module, int line,
const char* message);
typedef struct
{
// The callback invoked when the foreign object is created.
//
// This must be provided. Inside the body of this, it must call
// [wrenSetSlotNewForeign()] exactly once.
WrenForeignMethodFn allocate;
// The callback invoked when the garbage collector is about to collect a
// foreign object's memory.
//
// This may be `NULL` if the foreign class does not need to finalize.
WrenFinalizerFn finalize;
} WrenForeignClassMethods;
// Returns a pair of pointers to the foreign methods used to allocate and
// finalize the data for instances of [className] in resolved [module].
typedef WrenForeignClassMethods (*WrenBindForeignClassFn)(
WrenVM* vm, const char* module, const char* className);
typedef struct
{
// The callback Wren will use to allocate, reallocate, and deallocate memory.
//
// If `NULL`, defaults to a built-in function that uses `realloc` and `free`.
WrenReallocateFn reallocateFn;
// The callback Wren uses to resolve a module name.
//
// Some host applications may wish to support "relative" imports, where the
// meaning of an import string depends on the module that contains it. To
// support that without baking any policy into Wren itself, the VM gives the
// host a chance to resolve an import string.
//
// Before an import is loaded, it calls this, passing in the name of the
// module that contains the import and the import string. The host app can
// look at both of those and produce a new "canonical" string that uniquely
// identifies the module. This string is then used as the name of the module
// going forward. It is what is passed to [loadModuleFn], how duplicate
// imports of the same module are detected, and how the module is reported in
// stack traces.
//
// If you leave this function NULL, then the original import string is
// treated as the resolved string.
//
// If an import cannot be resolved by the embedder, it should return NULL and
// Wren will report that as a runtime error.
//
// Wren will take ownership of the string you return and free it for you, so
// it should be allocated using the same allocation function you provide
// above.
WrenResolveModuleFn resolveModuleFn;
// The callback Wren uses to load a module.
//
// Since Wren does not talk directly to the file system, it relies on the
// embedder to physically locate and read the source code for a module. The
// first time an import appears, Wren will call this and pass in the name of
// the module being imported. The method will return a result, which contains
// the source code for that module. Memory for the source is owned by the
// host application, and can be freed using the onComplete callback.
//
// This will only be called once for any given module name. Wren caches the
// result internally so subsequent imports of the same module will use the
// previous source and not call this.
//
// If a module with the given name could not be found by the embedder, it
// should return NULL and Wren will report that as a runtime error.
WrenLoadModuleFn loadModuleFn;
// The callback Wren uses to find a foreign method and bind it to a class.
//
// When a foreign method is declared in a class, this will be called with the
// foreign method's module, class, and signature when the class body is
// executed. It should return a pointer to the foreign function that will be
// bound to that method.
//
// If the foreign function could not be found, this should return NULL and
// Wren will report it as runtime error.
WrenBindForeignMethodFn bindForeignMethodFn;
// The callback Wren uses to find a foreign class and get its foreign methods.
//
// When a foreign class is declared, this will be called with the class's
// module and name when the class body is executed. It should return the
// foreign functions uses to allocate and (optionally) finalize the bytes
// stored in the foreign object when an instance is created.
WrenBindForeignClassFn bindForeignClassFn;
// The callback Wren uses to display text when `System.print()` or the other
// related functions are called.
//
// If this is `NULL`, Wren discards any printed text.
WrenWriteFn writeFn;
// The callback Wren uses to report errors.
//
// When an error occurs, this will be called with the module name, line
// number, and an error message. If this is `NULL`, Wren doesn't report any
// errors.
WrenErrorFn errorFn;
// The number of bytes Wren will allocate before triggering the first garbage
// collection.
//
// If zero, defaults to 10MB.
size_t initialHeapSize;
// After a collection occurs, the threshold for the next collection is
// determined based on the number of bytes remaining in use. This allows Wren
// to shrink its memory usage automatically after reclaiming a large amount
// of memory.
//
// This can be used to ensure that the heap does not get too small, which can
// in turn lead to a large number of collections afterwards as the heap grows
// back to a usable size.
//
// If zero, defaults to 1MB.
size_t minHeapSize;
// Wren will resize the heap automatically as the number of bytes
// remaining in use after a collection changes. This number determines the
// amount of additional memory Wren will use after a collection, as a
// percentage of the current heap size.
//
// For example, say that this is 50. After a garbage collection, when there
// are 400 bytes of memory still in use, the next collection will be triggered
// after a total of 600 bytes are allocated (including the 400 already in
// use.)
//
// Setting this to a smaller number wastes less memory, but triggers more
// frequent garbage collections.
//
// If zero, defaults to 50.
int heapGrowthPercent;
// User-defined data associated with the VM.
void* userData;
} WrenConfiguration;
typedef enum
{
WREN_RESULT_SUCCESS,
WREN_RESULT_COMPILE_ERROR,
WREN_RESULT_RUNTIME_ERROR
} WrenInterpretResult;
// The type of an object stored in a slot.
//
// This is not necessarily the object's *class*, but instead its low level
// representation type.
typedef enum
{
WREN_TYPE_BOOL,
WREN_TYPE_NUM,
WREN_TYPE_FOREIGN,
WREN_TYPE_LIST,
WREN_TYPE_MAP,
WREN_TYPE_NULL,
WREN_TYPE_STRING,
// The object is of a type that isn't accessible by the C API.
WREN_TYPE_UNKNOWN
} WrenType;
// Get the current wren version number.
//
// Can be used to range checks over versions.
WREN_API int wrenGetVersionNumber();
// Initializes [configuration] with all of its default values.
//
// Call this before setting the particular fields you care about.
WREN_API void wrenInitConfiguration(WrenConfiguration* configuration);
// Creates a new Wren virtual machine using the given [configuration]. Wren
// will copy the configuration data, so the argument passed to this can be
// freed after calling this. If [configuration] is `NULL`, uses a default
// configuration.
WREN_API WrenVM* wrenNewVM(WrenConfiguration* configuration);
// Disposes of all resources is use by [vm], which was previously created by a
// call to [wrenNewVM].
WREN_API void wrenFreeVM(WrenVM* vm);
// Immediately run the garbage collector to free unused memory.
WREN_API void wrenCollectGarbage(WrenVM* vm);
// Runs [source], a string of Wren source code in a new fiber in [vm] in the
// context of resolved [module].
WREN_API WrenInterpretResult wrenInterpret(WrenVM* vm, const char* module,
const char* source);
// Creates a handle that can be used to invoke a method with [signature] on
// using a receiver and arguments that are set up on the stack.
//
// This handle can be used repeatedly to directly invoke that method from C
// code using [wrenCall].
//
// When you are done with this handle, it must be released using
// [wrenReleaseHandle].
WREN_API WrenHandle* wrenMakeCallHandle(WrenVM* vm, const char* signature);
// Calls [method], using the receiver and arguments previously set up on the
// stack.
//
// [method] must have been created by a call to [wrenMakeCallHandle]. The
// arguments to the method must be already on the stack. The receiver should be
// in slot 0 with the remaining arguments following it, in order. It is an
// error if the number of arguments provided does not match the method's
// signature.
//
// After this returns, you can access the return value from slot 0 on the stack.
WREN_API WrenInterpretResult wrenCall(WrenVM* vm, WrenHandle* method);
// Releases the reference stored in [handle]. After calling this, [handle] can
// no longer be used.
WREN_API void wrenReleaseHandle(WrenVM* vm, WrenHandle* handle);
// The following functions are intended to be called from foreign methods or
// finalizers. The interface Wren provides to a foreign method is like a
// register machine: you are given a numbered array of slots that values can be
// read from and written to. Values always live in a slot (unless explicitly
// captured using wrenGetSlotHandle(), which ensures the garbage collector can
// find them.
//
// When your foreign function is called, you are given one slot for the receiver
// and each argument to the method. The receiver is in slot 0 and the arguments
// are in increasingly numbered slots after that. You are free to read and
// write to those slots as you want. If you want more slots to use as scratch
// space, you can call wrenEnsureSlots() to add more.
//
// When your function returns, every slot except slot zero is discarded and the
// value in slot zero is used as the return value of the method. If you don't
// store a return value in that slot yourself, it will retain its previous
// value, the receiver.
//
// While Wren is dynamically typed, C is not. This means the C interface has to
// support the various types of primitive values a Wren variable can hold: bool,
// double, string, etc. If we supported this for every operation in the C API,
// there would be a combinatorial explosion of functions, like "get a
// double-valued element from a list", "insert a string key and double value
// into a map", etc.
//
// To avoid that, the only way to convert to and from a raw C value is by going
// into and out of a slot. All other functions work with values already in a
// slot. So, to add an element to a list, you put the list in one slot, and the
// element in another. Then there is a single API function wrenInsertInList()
// that takes the element out of that slot and puts it into the list.
//
// The goal of this API is to be easy to use while not compromising performance.
// The latter means it does not do type or bounds checking at runtime except
// using assertions which are generally removed from release builds. C is an
// unsafe language, so it's up to you to be careful to use it correctly. In
// return, you get a very fast FFI.
// Returns the number of slots available to the current foreign method.
WREN_API int wrenGetSlotCount(WrenVM* vm);
// Ensures that the foreign method stack has at least [numSlots] available for
// use, growing the stack if needed.
//
// Does not shrink the stack if it has more than enough slots.
//
// It is an error to call this from a finalizer.
WREN_API void wrenEnsureSlots(WrenVM* vm, int numSlots);
// Gets the type of the object in [slot].
WREN_API WrenType wrenGetSlotType(WrenVM* vm, int slot);
// Reads a boolean value from [slot].
//
// It is an error to call this if the slot does not contain a boolean value.
WREN_API bool wrenGetSlotBool(WrenVM* vm, int slot);
// Reads a byte array from [slot].
//
// The memory for the returned string is owned by Wren. You can inspect it
// while in your foreign method, but cannot keep a pointer to it after the
// function returns, since the garbage collector may reclaim it.
//
// Returns a pointer to the first byte of the array and fill [length] with the
// number of bytes in the array.
//
// It is an error to call this if the slot does not contain a string.
WREN_API const char* wrenGetSlotBytes(WrenVM* vm, int slot, int* length);
// Reads a number from [slot].
//
// It is an error to call this if the slot does not contain a number.
WREN_API double wrenGetSlotDouble(WrenVM* vm, int slot);
// Reads a foreign object from [slot] and returns a pointer to the foreign data
// stored with it.
//
// It is an error to call this if the slot does not contain an instance of a
// foreign class.
WREN_API void* wrenGetSlotForeign(WrenVM* vm, int slot);
// Reads a string from [slot].
//
// The memory for the returned string is owned by Wren. You can inspect it
// while in your foreign method, but cannot keep a pointer to it after the
// function returns, since the garbage collector may reclaim it.
//
// It is an error to call this if the slot does not contain a string.
WREN_API const char* wrenGetSlotString(WrenVM* vm, int slot);
// Creates a handle for the value stored in [slot].
//
// This will prevent the object that is referred to from being garbage collected
// until the handle is released by calling [wrenReleaseHandle()].
WREN_API WrenHandle* wrenGetSlotHandle(WrenVM* vm, int slot);
// Stores the boolean [value] in [slot].
WREN_API void wrenSetSlotBool(WrenVM* vm, int slot, bool value);
// Stores the array [length] of [bytes] in [slot].
//
// The bytes are copied to a new string within Wren's heap, so you can free
// memory used by them after this is called.
WREN_API void wrenSetSlotBytes(WrenVM* vm, int slot, const char* bytes, size_t length);
// Stores the numeric [value] in [slot].
WREN_API void wrenSetSlotDouble(WrenVM* vm, int slot, double value);
// Creates a new instance of the foreign class stored in [classSlot] with [size]
// bytes of raw storage and places the resulting object in [slot].
//
// This does not invoke the foreign class's constructor on the new instance. If
// you need that to happen, call the constructor from Wren, which will then
// call the allocator foreign method. In there, call this to create the object
// and then the constructor will be invoked when the allocator returns.
//
// Returns a pointer to the foreign object's data.
WREN_API void* wrenSetSlotNewForeign(WrenVM* vm, int slot, int classSlot, size_t size);
// Stores a new empty list in [slot].
WREN_API void wrenSetSlotNewList(WrenVM* vm, int slot);
// Stores a new empty map in [slot].
WREN_API void wrenSetSlotNewMap(WrenVM* vm, int slot);
// Stores null in [slot].
WREN_API void wrenSetSlotNull(WrenVM* vm, int slot);
// Stores the string [text] in [slot].
//
// The [text] is copied to a new string within Wren's heap, so you can free
// memory used by it after this is called. The length is calculated using
// [strlen()]. If the string may contain any null bytes in the middle, then you
// should use [wrenSetSlotBytes()] instead.
WREN_API void wrenSetSlotString(WrenVM* vm, int slot, const char* text);
// Stores the value captured in [handle] in [slot].
//
// This does not release the handle for the value.
WREN_API void wrenSetSlotHandle(WrenVM* vm, int slot, WrenHandle* handle);
// Returns the number of elements in the list stored in [slot].
WREN_API int wrenGetListCount(WrenVM* vm, int slot);
// Reads element [index] from the list in [listSlot] and stores it in
// [elementSlot].
WREN_API void wrenGetListElement(WrenVM* vm, int listSlot, int index, int elementSlot);
// Sets the value stored at [index] in the list at [listSlot],
// to the value from [elementSlot].
WREN_API void wrenSetListElement(WrenVM* vm, int listSlot, int index, int elementSlot);
// Takes the value stored at [elementSlot] and inserts it into the list stored
// at [listSlot] at [index].
//
// As in Wren, negative indexes can be used to insert from the end. To append
// an element, use `-1` for the index.
WREN_API void wrenInsertInList(WrenVM* vm, int listSlot, int index, int elementSlot);
// Returns the number of entries in the map stored in [slot].
WREN_API int wrenGetMapCount(WrenVM* vm, int slot);
// Returns true if the key in [keySlot] is found in the map placed in [mapSlot].
WREN_API bool wrenGetMapContainsKey(WrenVM* vm, int mapSlot, int keySlot);
// Retrieves a value with the key in [keySlot] from the map in [mapSlot] and
// stores it in [valueSlot].
WREN_API void wrenGetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot);
// Takes the value stored at [valueSlot] and inserts it into the map stored
// at [mapSlot] with key [keySlot].
WREN_API void wrenSetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot);
// Removes a value from the map in [mapSlot], with the key from [keySlot],
// and place it in [removedValueSlot]. If not found, [removedValueSlot] is
// set to null, the same behaviour as the Wren Map API.
WREN_API void wrenRemoveMapValue(WrenVM* vm, int mapSlot, int keySlot,
int removedValueSlot);
// Looks up the top level variable with [name] in resolved [module] and stores
// it in [slot].
WREN_API void wrenGetVariable(WrenVM* vm, const char* module, const char* name,
int slot);
// Looks up the top level variable with [name] in resolved [module],
// returns false if not found. The module must be imported at the time,
// use wrenHasModule to ensure that before calling.
WREN_API bool wrenHasVariable(WrenVM* vm, const char* module, const char* name);
// Returns true if [module] has been imported/resolved before, false if not.
WREN_API bool wrenHasModule(WrenVM* vm, const char* module);
// Sets the current fiber to be aborted, and uses the value in [slot] as the
// runtime error object.
WREN_API void wrenAbortFiber(WrenVM* vm, int slot);
// Returns the user data associated with the WrenVM.
WREN_API void* wrenGetUserData(WrenVM* vm);
// Sets user data associated with the WrenVM.
WREN_API void wrenSetUserData(WrenVM* vm, void* userData);
#endif
// End file "wren.h"
// Begin file "wren_debug.h"
#ifndef wren_debug_h
#define wren_debug_h
// Begin file "wren_value.h"
#ifndef wren_value_h
#define wren_value_h
#include <stdbool.h>
#include <string.h>
// Begin file "wren_common.h"
#ifndef wren_common_h
#define wren_common_h
// This header contains macros and defines used across the entire Wren
// implementation. In particular, it contains "configuration" defines that
// control how Wren works. Some of these are only used while hacking on Wren
// itself.
//
// This header is *not* intended to be included by code outside of Wren itself.
// Wren pervasively uses the C99 integer types (uint16_t, etc.) along with some
// of the associated limit constants (UINT32_MAX, etc.). The constants are not
// part of standard C++, so aren't included by default by C++ compilers when you
// include <stdint> unless __STDC_LIMIT_MACROS is defined.
#define __STDC_LIMIT_MACROS
#include <stdint.h>
// These flags let you control some details of the interpreter's implementation.
// Usually they trade-off a bit of portability for speed. They default to the
// most efficient behavior.
// If true, then Wren uses a NaN-tagged double for its core value
// representation. Otherwise, it uses a larger more conventional struct. The
// former is significantly faster and more compact. The latter is useful for
// debugging and may be more portable.
//
// Defaults to on.
#ifndef WREN_NAN_TAGGING
#define WREN_NAN_TAGGING 1
#endif
// If true, the VM's interpreter loop uses computed gotos. See this for more:
// http://gcc.gnu.org/onlinedocs/gcc-3.1.1/gcc/Labels-as-Values.html
// Enabling this speeds up the main dispatch loop a bit, but requires compiler
// support.
// see https://bullno1.com/blog/switched-goto for alternative
// Defaults to true on supported compilers.
#ifndef WREN_COMPUTED_GOTO
#if defined(_MSC_VER) && !defined(__clang__)
// No computed gotos in Visual Studio.
#define WREN_COMPUTED_GOTO 0
#else
#define WREN_COMPUTED_GOTO 1
#endif
#endif
// The VM includes a number of optional modules. You can choose to include
// these or not. By default, they are all available. To disable one, set the
// corresponding `WREN_OPT_<name>` define to `0`.
#ifndef WREN_OPT_META
#define WREN_OPT_META 1
#endif
#ifndef WREN_OPT_RANDOM
#define WREN_OPT_RANDOM 1
#endif
// These flags are useful for debugging and hacking on Wren itself. They are not
// intended to be used for production code. They default to off.
// Set this to true to stress test the GC. It will perform a collection before
// every allocation. This is useful to ensure that memory is always correctly
// reachable.
#define WREN_DEBUG_GC_STRESS 0
// Set this to true to log memory operations as they occur.
#define WREN_DEBUG_TRACE_MEMORY 0
// Set this to true to log garbage collections as they occur.
#define WREN_DEBUG_TRACE_GC 0
// Set this to true to print out the compiled bytecode of each function.
#define WREN_DEBUG_DUMP_COMPILED_CODE 0
// Set this to trace each instruction as it's executed.
#define WREN_DEBUG_TRACE_INSTRUCTIONS 0
// The maximum number of module-level variables that may be defined at one time.
// This limitation comes from the 16 bits used for the arguments to
// `CODE_LOAD_MODULE_VAR` and `CODE_STORE_MODULE_VAR`.
#define MAX_MODULE_VARS 65536
// The maximum number of arguments that can be passed to a method. Note that
// this limitation is hardcoded in other places in the VM, in particular, the
// `CODE_CALL_XX` instructions assume a certain maximum number.
#define MAX_PARAMETERS 16
// 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. Signatures look like:
//
// foo // Getter.
// foo() // No-argument method.
// foo(_) // One-argument method.
// foo(_,_) // Two-argument method.
// init foo() // Constructor initializer.
//
// The maximum signature length takes into account the longest method name, the
// maximum number of parameters with separators between them, "init ", and "()".
#define MAX_METHOD_SIGNATURE (MAX_METHOD_NAME + (MAX_PARAMETERS * 2) + 6)
// The maximum length of an identifier. The only real reason for this limitation
// is so that error messages mentioning variables can be stack allocated.
#define MAX_VARIABLE_NAME 64
// The maximum number of fields a class can have, including inherited fields.
// This is explicit in the bytecode since `CODE_CLASS` and `CODE_SUBCLASS` take
// a single byte for the number of fields. Note that it's 255 and not 256
// because creating a class takes the *number* of fields, not the *highest
// field index*.
#define MAX_FIELDS 255
// Use the VM's allocator to allocate an object of [type].
#define ALLOCATE(vm, type) \
((type*)wrenReallocate(vm, NULL, 0, sizeof(type)))
// Use the VM's allocator to allocate an object of [mainType] containing a
// flexible array of [count] objects of [arrayType].
#define ALLOCATE_FLEX(vm, mainType, arrayType, count) \
((mainType*)wrenReallocate(vm, NULL, 0, \
sizeof(mainType) + sizeof(arrayType) * (count)))
// Use the VM's allocator to allocate an array of [count] elements of [type].
#define ALLOCATE_ARRAY(vm, type, count) \
((type*)wrenReallocate(vm, NULL, 0, sizeof(type) * (count)))
// Use the VM's allocator to free the previously allocated memory at [pointer].
#define DEALLOCATE(vm, pointer) wrenReallocate(vm, pointer, 0, 0)
// The Microsoft compiler does not support the "inline" modifier when compiling
// as plain C.
#if defined( _MSC_VER ) && !defined(__cplusplus)
#define inline _inline
#endif
// This is used to clearly mark flexible-sized arrays that appear at the end of
// some dynamically-allocated structs, known as the "struct hack".
#if __STDC_VERSION__ >= 199901L
// In C99, a flexible array member is just "[]".
#define FLEXIBLE_ARRAY
#else
// Elsewhere, use a zero-sized array. It's technically undefined behavior,
// but works reliably in most known compilers.
#define FLEXIBLE_ARRAY 0
#endif
// Assertions are used to validate program invariants. They indicate things the
// program expects to be true about its internal state during execution. If an
// assertion fails, there is a bug in Wren.
//
// Assertions add significant overhead, so are only enabled in debug builds.
#ifdef DEBUG
#include <stdio.h>
#define ASSERT(condition, message) \
do \
{ \
if (!(condition)) \
{ \
fprintf(stderr, "[%s:%d] Assert failed in %s(): %s\n", \
__FILE__, __LINE__, __func__, message); \
abort(); \
} \
} while (false)
// Indicates that we know execution should never reach this point in the
// program. In debug mode, we assert this fact because it's a bug to get here.
//
// In release mode, we use compiler-specific built in functions to tell the
// compiler the code can't be reached. This avoids "missing return" warnings
// in some cases and also lets it perform some optimizations by assuming the
// code is never reached.
#define UNREACHABLE() \
do \
{ \
fprintf(stderr, "[%s:%d] This code should not be reached in %s()\n", \
__FILE__, __LINE__, __func__); \
abort(); \
} while (false)
#else
#define ASSERT(condition, message) do { } while (false)
// Tell the compiler that this part of the code will never be reached.
#if defined( _MSC_VER )
#define UNREACHABLE() __assume(0)
#elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5))
#define UNREACHABLE() __builtin_unreachable()
#else
#define UNREACHABLE()
#endif
#endif
#endif
// End file "wren_common.h"
// Begin file "wren_math.h"
#ifndef wren_math_h
#define wren_math_h
#include <math.h>
#include <stdint.h>
// A union to let us reinterpret a double as raw bits and back.
typedef union
{
uint64_t bits64;
uint32_t bits32[2];
double num;
} WrenDoubleBits;
#define WREN_DOUBLE_QNAN_POS_MIN_BITS (UINT64_C(0x7FF8000000000000))
#define WREN_DOUBLE_QNAN_POS_MAX_BITS (UINT64_C(0x7FFFFFFFFFFFFFFF))
#define WREN_DOUBLE_NAN (wrenDoubleFromBits(WREN_DOUBLE_QNAN_POS_MIN_BITS))
static inline double wrenDoubleFromBits(uint64_t bits)
{
WrenDoubleBits data;
data.bits64 = bits;
return data.num;
}
static inline uint64_t wrenDoubleToBits(double num)
{
WrenDoubleBits data;
data.num = num;
return data.bits64;
}
#endif
// End file "wren_math.h"
// Begin file "wren_utils.h"
#ifndef wren_utils_h
#define wren_utils_h
// Reusable data structures and other utility functions.
// Forward declare this here to break a cycle between wren_utils.h and
// wren_value.h.
typedef struct sObjString ObjString;
// We need buffers of a few different types. To avoid lots of casting between
// void* and back, we'll use the preprocessor as a poor man's generics and let
// it generate a few type-specific ones.
#define DECLARE_BUFFER(name, type) \
typedef struct \
{ \
type* data; \
int count; \
int capacity; \
} name##Buffer; \
void wren##name##BufferInit(name##Buffer* buffer); \
void wren##name##BufferClear(WrenVM* vm, name##Buffer* buffer); \
void wren##name##BufferFill(WrenVM* vm, name##Buffer* buffer, type data, \
int count); \
void wren##name##BufferWrite(WrenVM* vm, name##Buffer* buffer, type data)
// This should be used once for each type instantiation, somewhere in a .c file.
#define DEFINE_BUFFER(name, type) \
void wren##name##BufferInit(name##Buffer* buffer) \
{ \
buffer->data = NULL; \
buffer->capacity = 0; \
buffer->count = 0; \
} \
\
void wren##name##BufferClear(WrenVM* vm, name##Buffer* buffer) \
{ \
wrenReallocate(vm, buffer->data, 0, 0); \
wren##name##BufferInit(buffer); \
} \
\
void wren##name##BufferFill(WrenVM* vm, name##Buffer* buffer, type data, \
int count) \
{ \
if (buffer->capacity < buffer->count + count) \
{ \
int capacity = wrenPowerOf2Ceil(buffer->count + count); \
buffer->data = (type*)wrenReallocate(vm, buffer->data, \
buffer->capacity * sizeof(type), capacity * sizeof(type)); \
buffer->capacity = capacity; \
} \
\
for (int i = 0; i < count; i++) \
{ \
buffer->data[buffer->count++] = data; \
} \
} \
\
void wren##name##BufferWrite(WrenVM* vm, name##Buffer* buffer, type data) \
{ \
wren##name##BufferFill(vm, buffer, data, 1); \
}
DECLARE_BUFFER(Byte, uint8_t);
DECLARE_BUFFER(Int, int);
DECLARE_BUFFER(String, ObjString*);
// TODO: Change this to use a map.
typedef StringBuffer SymbolTable;
// Initializes the symbol table.
void wrenSymbolTableInit(SymbolTable* symbols);
// Frees all dynamically allocated memory used by the symbol table, but not the
// SymbolTable itself.
void wrenSymbolTableClear(WrenVM* vm, SymbolTable* symbols);
// Adds name to the symbol table. Returns the index of it in the table.
int wrenSymbolTableAdd(WrenVM* vm, SymbolTable* symbols,
const char* name, size_t length);
// Adds name to the symbol table. Returns the index of it in the table. Will
// use an existing symbol if already present.
int wrenSymbolTableEnsure(WrenVM* vm, SymbolTable* symbols,
const char* name, size_t length);
// Looks up name in the symbol table. Returns its index if found or -1 if not.
int wrenSymbolTableFind(const SymbolTable* symbols,
const char* name, size_t length);
void wrenBlackenSymbolTable(WrenVM* vm, SymbolTable* symbolTable);
// Returns the number of bytes needed to encode [value] in UTF-8.
//
// Returns 0 if [value] is too large to encode.
int wrenUtf8EncodeNumBytes(int value);
// Encodes value as a series of bytes in [bytes], which is assumed to be large
// enough to hold the encoded result.
//
// Returns the number of written bytes.
int wrenUtf8Encode(int value, uint8_t* bytes);
// Decodes the UTF-8 sequence starting at [bytes] (which has max [length]),
// returning the code point.
//
// Returns -1 if the bytes are not a valid UTF-8 sequence.
int wrenUtf8Decode(const uint8_t* bytes, uint32_t length);
// Returns the number of bytes in the UTF-8 sequence starting with [byte].
//
// If the character at that index is not the beginning of a UTF-8 sequence,
// returns 0.
int wrenUtf8DecodeNumBytes(uint8_t byte);
// Returns the smallest power of two that is equal to or greater than [n].
int wrenPowerOf2Ceil(int n);
// Validates that [value] is within `[0, count)`. Also allows
// negative indices which map backwards from the end. Returns the valid positive
// index value. If invalid, returns `UINT32_MAX`.
uint32_t wrenValidateIndex(uint32_t count, int64_t value);
#endif
// End file "wren_utils.h"
// This defines the built-in types and their core representations in memory.
// Since Wren is dynamically typed, any variable can hold a value of any type,
// and the type can change at runtime. Implementing this efficiently is
// critical for performance.
//
// The main type exposed by this is [Value]. A C variable of that type is a
// storage location that can hold any Wren value. The stack, module variables,
// and instance fields are all implemented in C as variables of type Value.
//
// The built-in types for booleans, numbers, and null are unboxed: their value
// is stored directly in the Value, and copying a Value copies the value. Other
// types--classes, instances of classes, functions, lists, and strings--are all
// reference types. They are stored on the heap and the Value just stores a
// pointer to it. Copying the Value copies a reference to the same object. The
// Wren implementation calls these "Obj", or objects, though to a user, all
// values are objects.
//
// There is also a special singleton value "undefined". It is used internally
// but never appears as a real value to a user. It has two uses:
//
// - It is used to identify module variables that have been implicitly declared
// by use in a forward reference but not yet explicitly declared. These only
// exist during compilation and do not appear at runtime.
//
// - It is used to represent unused map entries in an ObjMap.
//
// There are two supported Value representations. The main one uses a technique
// called "NaN tagging" (explained in detail below) to store a number, any of
// the value types, or a pointer, all inside one double-precision floating
// point number. A larger, slower, Value type that uses a struct to store these
// is also supported, and is useful for debugging the VM.
//
// The representation is controlled by the `WREN_NAN_TAGGING` define. If that's
// defined, Nan tagging is used.
// These macros cast a Value to one of the specific object types. These do *not*
// perform any validation, so must only be used after the Value has been
// ensured to be the right type.
#define AS_CLASS(value) ((ObjClass*)AS_OBJ(value)) // ObjClass*
#define AS_CLOSURE(value) ((ObjClosure*)AS_OBJ(value)) // ObjClosure*
#define AS_FIBER(v) ((ObjFiber*)AS_OBJ(v)) // ObjFiber*
#define AS_FN(value) ((ObjFn*)AS_OBJ(value)) // ObjFn*
#define AS_FOREIGN(v) ((ObjForeign*)AS_OBJ(v)) // ObjForeign*
#define AS_INSTANCE(value) ((ObjInstance*)AS_OBJ(value)) // ObjInstance*
#define AS_LIST(value) ((ObjList*)AS_OBJ(value)) // ObjList*
#define AS_MAP(value) ((ObjMap*)AS_OBJ(value)) // ObjMap*
#define AS_MODULE(value) ((ObjModule*)AS_OBJ(value)) // ObjModule*
#define AS_NUM(value) (wrenValueToNum(value)) // double
#define AS_RANGE(v) ((ObjRange*)AS_OBJ(v)) // ObjRange*
#define AS_STRING(v) ((ObjString*)AS_OBJ(v)) // ObjString*
#define AS_CSTRING(v) (AS_STRING(v)->value) // const char*
// These macros promote a primitive C value to a full Wren Value. There are
// more defined below that are specific to the Nan tagged or other
// representation.
#define BOOL_VAL(boolean) ((boolean) ? TRUE_VAL : FALSE_VAL) // boolean
#define NUM_VAL(num) (wrenNumToValue(num)) // double
#define OBJ_VAL(obj) (wrenObjectToValue((Obj*)(obj))) // Any Obj___*
// These perform type tests on a Value, returning `true` if the Value is of the
// given type.
#define IS_BOOL(value) (wrenIsBool(value)) // Bool
#define IS_CLASS(value) (wrenIsObjType(value, OBJ_CLASS)) // ObjClass
#define IS_CLOSURE(value) (wrenIsObjType(value, OBJ_CLOSURE)) // ObjClosure
#define IS_FIBER(value) (wrenIsObjType(value, OBJ_FIBER)) // ObjFiber
#define IS_FN(value) (wrenIsObjType(value, OBJ_FN)) // ObjFn
#define IS_FOREIGN(value) (wrenIsObjType(value, OBJ_FOREIGN)) // ObjForeign
#define IS_INSTANCE(value) (wrenIsObjType(value, OBJ_INSTANCE)) // ObjInstance
#define IS_LIST(value) (wrenIsObjType(value, OBJ_LIST)) // ObjList
#define IS_MAP(value) (wrenIsObjType(value, OBJ_MAP)) // ObjMap
#define IS_RANGE(value) (wrenIsObjType(value, OBJ_RANGE)) // ObjRange
#define IS_STRING(value) (wrenIsObjType(value, OBJ_STRING)) // ObjString
// Creates a new string object from [text], which should be a bare C string
// literal. This determines the length of the string automatically at compile
// time based on the size of the character array (-1 for the terminating '\0').
#define CONST_STRING(vm, text) wrenNewStringLength((vm), (text), sizeof(text) - 1)
// Identifies which specific type a heap-allocated object is.
typedef enum {
OBJ_CLASS,
OBJ_CLOSURE,
OBJ_FIBER,
OBJ_FN,
OBJ_FOREIGN,
OBJ_INSTANCE,
OBJ_LIST,
OBJ_MAP,
OBJ_MODULE,
OBJ_RANGE,
OBJ_STRING,
OBJ_UPVALUE
} ObjType;
typedef struct sObjClass ObjClass;
// Base struct for all heap-allocated objects.
typedef struct sObj Obj;
struct sObj
{
ObjType type;
bool isDark;
// The object's class.
ObjClass* classObj;
// The next object in the linked list of all currently allocated objects.
struct sObj* next;
};
#if WREN_NAN_TAGGING
typedef uint64_t Value;
#else
typedef enum
{
VAL_FALSE,
VAL_NULL,
VAL_NUM,
VAL_TRUE,
VAL_UNDEFINED,
VAL_OBJ
} ValueType;
typedef struct
{
ValueType type;
union
{
double num;
Obj* obj;
} as;
} Value;
#endif
DECLARE_BUFFER(Value, Value);
// A heap-allocated string object.
struct sObjString
{
Obj obj;
// Number of bytes in the string, not including the null terminator.
uint32_t length;
// The hash value of the string's contents.
uint32_t hash;
// Inline array of the string's bytes followed by a null terminator.
char value[FLEXIBLE_ARRAY];
};
// The dynamically allocated data structure for a variable that has been used
// by a closure. Whenever a function accesses a variable declared in an
// enclosing function, it will get to it through this.
//
// An upvalue can be either "closed" or "open". An open upvalue points directly
// to a [Value] that is still stored on the fiber's stack because the local
// variable is still in scope in the function where it's declared.
//
// When that local variable goes out of scope, the upvalue pointing to it will
// be closed. When that happens, the value gets copied off the stack into the
// upvalue itself. That way, it can have a longer lifetime than the stack
// variable.
typedef struct sObjUpvalue
{
// The object header. Note that upvalues have this because they are garbage
// collected, but they are not first class Wren objects.
Obj obj;
// Pointer to the variable this upvalue is referencing.
Value* value;
// If the upvalue is closed (i.e. the local variable it was pointing to has
// been popped off the stack) then the closed-over value will be hoisted out
// of the stack into here. [value] will then be changed to point to this.
Value closed;
// Open upvalues are stored in a linked list by the fiber. This points to the
// next upvalue in that list.
struct sObjUpvalue* next;
} ObjUpvalue;
// The type of a primitive function.
//
// Primitives are similar to foreign functions, but have more direct access to
// VM internals. It is passed the arguments in [args]. If it returns a value,
// it places it in `args[0]` and returns `true`. If it causes a runtime error
// or modifies the running fiber, it returns `false`.
typedef bool (*Primitive)(WrenVM* vm, Value* args);
// TODO: See if it's actually a perf improvement to have this in a separate
// struct instead of in ObjFn.
// Stores debugging information for a function used for things like stack
// traces.
typedef struct
{
// The name of the function. Heap allocated and owned by the FnDebug.
char* name;
// An array of line numbers. There is one element in this array for each
// bytecode in the function's bytecode array. The value of that element is
// the line in the source code that generated that instruction.
IntBuffer sourceLines;
} FnDebug;
// A loaded module and the top-level variables it defines.
//
// While this is an Obj and is managed by the GC, it never appears as a
// first-class object in Wren.
typedef struct
{
Obj obj;
// The currently defined top-level variables.
ValueBuffer variables;
// Symbol table for the names of all module variables. Indexes here directly
// correspond to entries in [variables].
SymbolTable variableNames;
// The name of the module.
ObjString* name;
} ObjModule;
// A function object. It wraps and owns the bytecode and other debug information
// for a callable chunk of code.
//
// Function objects are not passed around and invoked directly. Instead, they
// are always referenced by an [ObjClosure] which is the real first-class
// representation of a function. This isn't strictly necessary if they function
// has no upvalues, but lets the rest of the VM assume all called objects will
// be closures.
typedef struct
{
Obj obj;
ByteBuffer code;
ValueBuffer constants;
// The module where this function was defined.
ObjModule* module;
// The maximum number of stack slots this function may use.
int maxSlots;
// The number of upvalues this function closes over.
int numUpvalues;
// The number of parameters this function expects. Used to ensure that .call
// handles a mismatch between number of parameters and arguments. This will
// only be set for fns, and not ObjFns that represent methods or scripts.
int arity;
FnDebug* debug;
} ObjFn;
// An instance of a first-class function and the environment it has closed over.
// Unlike [ObjFn], this has captured the upvalues that the function accesses.
typedef struct
{
Obj obj;
// The function that this closure is an instance of.
ObjFn* fn;
// The upvalues this function has closed over.
ObjUpvalue* upvalues[FLEXIBLE_ARRAY];
} ObjClosure;
typedef struct
{
// Pointer to the current (really next-to-be-executed) instruction in the
// function's bytecode.
uint8_t* ip;
// The closure being executed.
ObjClosure* closure;
// Pointer to the first stack slot used by this call frame. This will contain
// the receiver, followed by the function's parameters, then local variables
// and temporaries.
Value* stackStart;
} CallFrame;
// Tracks how this fiber has been invoked, aside from the ways that can be
// detected from the state of other fields in the fiber.
typedef enum
{
// The fiber is being run from another fiber using a call to `try()`.
FIBER_TRY,
// The fiber was directly invoked by `runInterpreter()`. This means it's the
// initial fiber used by a call to `wrenCall()` or `wrenInterpret()`.
FIBER_ROOT,
// The fiber is invoked some other way. If [caller] is `NULL` then the fiber
// was invoked using `call()`. If [numFrames] is zero, then the fiber has
// finished running and is done. If [numFrames] is one and that frame's `ip`
// points to the first byte of code, the fiber has not been started yet.
FIBER_OTHER,
} FiberState;
typedef struct sObjFiber
{
Obj obj;
// The stack of value slots. This is used for holding local variables and
// temporaries while the fiber is executing. It is heap-allocated and grown
// as needed.
Value* stack;
// A pointer to one past the top-most value on the stack.
Value* stackTop;
// The number of allocated slots in the stack array.
int stackCapacity;
// The stack of call frames. This is a dynamic array that grows as needed but
// never shrinks.
CallFrame* frames;
// The number of frames currently in use in [frames].
int numFrames;
// The number of [frames] allocated.
int frameCapacity;
// Pointer to the first node in the linked list of open upvalues that are
// pointing to values still on the stack. The head of the list will be the
// upvalue closest to the top of the stack, and then the list works downwards.
ObjUpvalue* openUpvalues;
// The fiber that ran this one. If this fiber is yielded, control will resume
// to this one. May be `NULL`.
struct sObjFiber* caller;
// If the fiber failed because of a runtime error, this will contain the
// error object. Otherwise, it will be null.
Value error;
FiberState state;
} ObjFiber;
typedef enum
{
// A primitive method implemented in C in the VM. Unlike foreign methods,
// this can directly manipulate the fiber's stack.
METHOD_PRIMITIVE,
// A primitive that handles .call on Fn.
METHOD_FUNCTION_CALL,
// A externally-defined C method.
METHOD_FOREIGN,
// A normal user-defined method.
METHOD_BLOCK,
// No method for the given symbol.
METHOD_NONE
} MethodType;
typedef struct
{
MethodType type;
// The method function itself. The [type] determines which field of the union
// is used.
union
{
Primitive primitive;
WrenForeignMethodFn foreign;
ObjClosure* closure;
} as;
} Method;
DECLARE_BUFFER(Method, Method);
struct sObjClass
{
Obj obj;
ObjClass* superclass;
// The number of fields needed for an instance of this class, including all
// of its superclass fields.
int numFields;
// The table of methods that are defined in or inherited by this class.
// Methods are called by symbol, and the symbol directly maps to an index in
// this table. This makes method calls fast at the expense of empty cells in
// the list for methods the class doesn't support.
//
// You can think of it as a hash table that never has collisions but has a
// really low load factor. Since methods are pretty small (just a type and a
// pointer), this should be a worthwhile trade-off.
MethodBuffer methods;
// The name of the class.
ObjString* name;
// The ClassAttribute for the class, if any
Value attributes;
};
typedef struct
{
Obj obj;
uint8_t data[FLEXIBLE_ARRAY];
} ObjForeign;
typedef struct
{
Obj obj;
Value fields[FLEXIBLE_ARRAY];
} ObjInstance;
typedef struct
{
Obj obj;
// The elements in the list.
ValueBuffer elements;
} ObjList;
typedef struct
{
// The entry's key, or UNDEFINED_VAL if the entry is not in use.
Value key;
// The value associated with the key. If the key is UNDEFINED_VAL, this will
// be false to indicate an open available entry or true to indicate a
// tombstone -- an entry that was previously in use but was then deleted.
Value value;
} MapEntry;
// A hash table mapping keys to values.
//
// We use something very simple: open addressing with linear probing. The hash
// table is an array of entries. Each entry is a key-value pair. If the key is
// the special UNDEFINED_VAL, it indicates no value is currently in that slot.
// Otherwise, it's a valid key, and the value is the value associated with it.
//
// When entries are added, the array is dynamically scaled by GROW_FACTOR to
// keep the number of filled slots under MAP_LOAD_PERCENT. Likewise, if the map
// gets empty enough, it will be resized to a smaller array. When this happens,
// all existing entries are rehashed and re-added to the new array.
//
// When an entry is removed, its slot is replaced with a "tombstone". This is an
// entry whose key is UNDEFINED_VAL and whose value is TRUE_VAL. When probing
// for a key, we will continue past tombstones, because the desired key may be
// found after them if the key that was removed was part of a prior collision.
// When the array gets resized, all tombstones are discarded.
typedef struct
{
Obj obj;
// The number of entries allocated.
uint32_t capacity;
// The number of entries in the map.
uint32_t count;
// Pointer to a contiguous array of [capacity] entries.
MapEntry* entries;
} ObjMap;
typedef struct
{
Obj obj;
// The beginning of the range.
double from;
// The end of the range. May be greater or less than [from].
double to;
// True if [to] is included in the range.
bool isInclusive;
} ObjRange;
// An IEEE 754 double-precision float is a 64-bit value with bits laid out like:
//
// 1 Sign bit
// | 11 Exponent bits
// | | 52 Mantissa (i.e. fraction) bits
// | | |
// S[Exponent-][Mantissa------------------------------------------]
//
// The details of how these are used to represent numbers aren't really
// relevant here as long we don't interfere with them. The important bit is NaN.
//
// An IEEE double can represent a few magical values like NaN ("not a number"),
// Infinity, and -Infinity. A NaN is any value where all exponent bits are set:
//
// v--NaN bits
// -11111111111----------------------------------------------------
//
// Here, "-" means "doesn't matter". Any bit sequence that matches the above is
// a NaN. With all of those "-", it obvious there are a *lot* of different
// bit patterns that all mean the same thing. NaN tagging takes advantage of
// this. We'll use those available bit patterns to represent things other than
// numbers without giving up any valid numeric values.
//
// NaN values come in two flavors: "signalling" and "quiet". The former are
// intended to halt execution, while the latter just flow through arithmetic
// operations silently. We want the latter. Quiet NaNs are indicated by setting
// the highest mantissa bit:
//
// v--Highest mantissa bit
// -[NaN ]1---------------------------------------------------
//
// If all of the NaN bits are set, it's not a number. Otherwise, it is.
// That leaves all of the remaining bits as available for us to play with. We
// stuff a few different kinds of things here: special singleton values like
// "true", "false", and "null", and pointers to objects allocated on the heap.
// We'll use the sign bit to distinguish singleton values from pointers. If
// it's set, it's a pointer.
//
// v--Pointer or singleton?
// S[NaN ]1---------------------------------------------------
//
// For singleton values, we just enumerate the different values. We'll use the
// low bits of the mantissa for that, and only need a few:
//
// 3 Type bits--v
// 0[NaN ]1------------------------------------------------[T]
//
// For pointers, we are left with 51 bits of mantissa to store an address.
// That's more than enough room for a 32-bit address. Even 64-bit machines
// only actually use 48 bits for addresses, so we've got plenty. We just stuff
// the address right into the mantissa.
//
// Ta-da, double precision numbers, pointers, and a bunch of singleton values,
// all stuffed into a single 64-bit sequence. Even better, we don't have to
// do any masking or work to extract number values: they are unmodified. This
// means math on numbers is fast.
#if WREN_NAN_TAGGING
// A mask that selects the sign bit.
#define SIGN_BIT ((uint64_t)1 << 63)
// The bits that must be set to indicate a quiet NaN.
#define QNAN ((uint64_t)0x7ffc000000000000)
// If the NaN bits are set, it's not a number.
#define IS_NUM(value) (((value) & QNAN) != QNAN)
// An object pointer is a NaN with a set sign bit.
#define IS_OBJ(value) (((value) & (QNAN | SIGN_BIT)) == (QNAN | SIGN_BIT))
#define IS_FALSE(value) ((value) == FALSE_VAL)
#define IS_NULL(value) ((value) == NULL_VAL)
#define IS_UNDEFINED(value) ((value) == UNDEFINED_VAL)
// Masks out the tag bits used to identify the singleton value.
#define MASK_TAG (7)
// Tag values for the different singleton values.
#define TAG_NAN (0)
#define TAG_NULL (1)
#define TAG_FALSE (2)
#define TAG_TRUE (3)
#define TAG_UNDEFINED (4)
#define TAG_UNUSED2 (5)
#define TAG_UNUSED3 (6)
#define TAG_UNUSED4 (7)
// Value -> 0 or 1.
#define AS_BOOL(value) ((value) == TRUE_VAL)
// Value -> Obj*.
#define AS_OBJ(value) ((Obj*)(uintptr_t)((value) & ~(SIGN_BIT | QNAN)))
// Singleton values.
#define NULL_VAL ((Value)(uint64_t)(QNAN | TAG_NULL))
#define FALSE_VAL ((Value)(uint64_t)(QNAN | TAG_FALSE))
#define TRUE_VAL ((Value)(uint64_t)(QNAN | TAG_TRUE))
#define UNDEFINED_VAL ((Value)(uint64_t)(QNAN | TAG_UNDEFINED))
// Gets the singleton type tag for a Value (which must be a singleton).
#define GET_TAG(value) ((int)((value) & MASK_TAG))
#else
// Value -> 0 or 1.
#define AS_BOOL(value) ((value).type == VAL_TRUE)
// Value -> Obj*.
#define AS_OBJ(v) ((v).as.obj)
// Determines if [value] is a garbage-collected object or not.
#define IS_OBJ(value) ((value).type == VAL_OBJ)
#define IS_FALSE(value) ((value).type == VAL_FALSE)
#define IS_NULL(value) ((value).type == VAL_NULL)
#define IS_NUM(value) ((value).type == VAL_NUM)
#define IS_UNDEFINED(value) ((value).type == VAL_UNDEFINED)
// Singleton values.
#define FALSE_VAL ((Value){ VAL_FALSE, { 0 } })
#define NULL_VAL ((Value){ VAL_NULL, { 0 } })
#define TRUE_VAL ((Value){ VAL_TRUE, { 0 } })
#define UNDEFINED_VAL ((Value){ VAL_UNDEFINED, { 0 } })
#endif
// Creates a new "raw" class. It has no metaclass or superclass whatsoever.
// This is only used for bootstrapping the initial Object and Class classes,
// which are a little special.
ObjClass* wrenNewSingleClass(WrenVM* vm, int numFields, ObjString* name);
// Makes [superclass] the superclass of [subclass], and causes subclass to
// inherit its methods. This should be called before any methods are defined
// on subclass.
void wrenBindSuperclass(WrenVM* vm, ObjClass* subclass, ObjClass* superclass);
// Creates a new class object as well as its associated metaclass.
ObjClass* wrenNewClass(WrenVM* vm, ObjClass* superclass, int numFields,
ObjString* name);
void wrenBindMethod(WrenVM* vm, ObjClass* classObj, int symbol, Method method);
// Creates a new closure object that invokes [fn]. Allocates room for its
// upvalues, but assumes outside code will populate it.
ObjClosure* wrenNewClosure(WrenVM* vm, ObjFn* fn);
// Creates a new fiber object that will invoke [closure].
ObjFiber* wrenNewFiber(WrenVM* vm, ObjClosure* closure);
// Adds a new [CallFrame] to [fiber] invoking [closure] whose stack starts at
// [stackStart].
static inline void wrenAppendCallFrame(WrenVM* vm, ObjFiber* fiber,
ObjClosure* closure, Value* stackStart)
{
// The caller should have ensured we already have enough capacity.
ASSERT(fiber->frameCapacity > fiber->numFrames, "No memory for call frame.");
CallFrame* frame = &fiber->frames[fiber->numFrames++];
frame->stackStart = stackStart;
frame->closure = closure;
frame->ip = closure->fn->code.data;
}
// Ensures [fiber]'s stack has at least [needed] slots.
void wrenEnsureStack(WrenVM* vm, ObjFiber* fiber, int needed);
static inline bool wrenHasError(const ObjFiber* fiber)
{
return !IS_NULL(fiber->error);
}
ObjForeign* wrenNewForeign(WrenVM* vm, ObjClass* classObj, size_t size);
// Creates a new empty function. Before being used, it must have code,
// constants, etc. added to it.
ObjFn* wrenNewFunction(WrenVM* vm, ObjModule* module, int maxSlots);
void wrenFunctionBindName(WrenVM* vm, ObjFn* fn, const char* name, int length);
// Creates a new instance of the given [classObj].
Value wrenNewInstance(WrenVM* vm, ObjClass* classObj);
// Creates a new list with [numElements] elements (which are left
// uninitialized.)
ObjList* wrenNewList(WrenVM* vm, uint32_t numElements);
// Inserts [value] in [list] at [index], shifting down the other elements.
void wrenListInsert(WrenVM* vm, ObjList* list, Value value, uint32_t index);
// Removes and returns the item at [index] from [list].
Value wrenListRemoveAt(WrenVM* vm, ObjList* list, uint32_t index);
// Searches for [value] in [list], returns the index or -1 if not found.
int wrenListIndexOf(WrenVM* vm, ObjList* list, Value value);
// Creates a new empty map.
ObjMap* wrenNewMap(WrenVM* vm);
// Validates that [arg] is a valid object for use as a map key. Returns true if
// it is and returns false otherwise. Use validateKey usually, for a runtime error.
// This separation exists to aid the API in surfacing errors to the developer as well.
static inline bool wrenMapIsValidKey(Value arg);
// Looks up [key] in [map]. If found, returns the value. Otherwise, returns
// `UNDEFINED_VAL`.
Value wrenMapGet(ObjMap* map, Value key);
// Associates [key] with [value] in [map].
void wrenMapSet(WrenVM* vm, ObjMap* map, Value key, Value value);
void wrenMapClear(WrenVM* vm, ObjMap* map);
// Removes [key] from [map], if present. Returns the value for the key if found
// or `NULL_VAL` otherwise.
Value wrenMapRemoveKey(WrenVM* vm, ObjMap* map, Value key);
// Creates a new module.
ObjModule* wrenNewModule(WrenVM* vm, ObjString* name);
// Creates a new range from [from] to [to].
Value wrenNewRange(WrenVM* vm, double from, double to, bool isInclusive);
// Creates a new string object and copies [text] into it.
//
// [text] must be non-NULL.
Value wrenNewString(WrenVM* vm, const char* text);
// Creates a new string object of [length] and copies [text] into it.
//
// [text] may be NULL if [length] is zero.
Value wrenNewStringLength(WrenVM* vm, const char* text, size_t length);
// Creates a new string object by taking a range of characters from [source].
// The range starts at [start], contains [count] bytes, and increments by
// [step].
Value wrenNewStringFromRange(WrenVM* vm, ObjString* source, int start,
uint32_t count, int step);
// Produces a string representation of [value].
Value wrenNumToString(WrenVM* vm, double value);
// Creates a new formatted string from [format] and any additional arguments
// used in the format string.
//
// This is a very restricted flavor of formatting, intended only for internal
// use by the VM. Two formatting characters are supported, each of which reads
// the next argument as a certain type:
//
// $ - A C string.
// @ - A Wren string object.
Value wrenStringFormat(WrenVM* vm, const char* format, ...);
// Creates a new string containing the UTF-8 encoding of [value].
Value wrenStringFromCodePoint(WrenVM* vm, int value);
// Creates a new string from the integer representation of a byte
Value wrenStringFromByte(WrenVM* vm, uint8_t value);
// Creates a new string containing the code point in [string] starting at byte
// [index]. If [index] points into the middle of a UTF-8 sequence, returns an
// empty string.
Value wrenStringCodePointAt(WrenVM* vm, ObjString* string, uint32_t index);
// Search for the first occurence of [needle] within [haystack] and returns its
// zero-based offset. Returns `UINT32_MAX` if [haystack] does not contain
// [needle].
uint32_t wrenStringFind(ObjString* haystack, ObjString* needle,
uint32_t startIndex);
// Returns true if [a] and [b] represent the same string.
static inline bool wrenStringEqualsCString(const ObjString* a,
const char* b, size_t length)
{
return a->length == length && memcmp(a->value, b, length) == 0;
}
// Creates a new open upvalue pointing to [value] on the stack.
ObjUpvalue* wrenNewUpvalue(WrenVM* vm, Value* value);
// Mark [obj] as reachable and still in use. This should only be called
// during the sweep phase of a garbage collection.
void wrenGrayObj(WrenVM* vm, Obj* obj);
// Mark [value] as reachable and still in use. This should only be called
// during the sweep phase of a garbage collection.
void wrenGrayValue(WrenVM* vm, Value value);
// Mark the values in [buffer] as reachable and still in use. This should only
// be called during the sweep phase of a garbage collection.
void wrenGrayBuffer(WrenVM* vm, ValueBuffer* buffer);
// Processes every object in the gray stack until all reachable objects have
// been marked. After that, all objects are either white (freeable) or black
// (in use and fully traversed).
void wrenBlackenObjects(WrenVM* vm);
// Releases all memory owned by [obj], including [obj] itself.
void wrenFreeObj(WrenVM* vm, Obj* obj);
// Returns the class of [value].
//
// Unlike wrenGetClassInline in wren_vm.h, this is not inlined. Inlining helps
// performance (significantly) in some cases, but degrades it in others. The
// ones used by the implementation were chosen to give the best results in the
// benchmarks.
ObjClass* wrenGetClass(WrenVM* vm, Value value);
// Returns true if [a] and [b] are strictly the same value. This is identity
// for object values, and value equality for unboxed values.
static inline bool wrenValuesSame(Value a, Value b)
{
#if WREN_NAN_TAGGING
// Value types have unique bit representations and we compare object types
// by identity (i.e. pointer), so all we need to do is compare the bits.
return a == b;
#else
if (a.type != b.type) return false;
if (a.type == VAL_NUM) return a.as.num == b.as.num;
return a.as.obj == b.as.obj;
#endif
}
// Returns true if [a] and [b] are equivalent. Immutable values (null, bools,
// numbers, ranges, and strings) are equal if they have the same data. All
// other values are equal if they are identical objects.
bool wrenValuesEqual(Value a, Value b);
// Returns true if [value] is a bool. Do not call this directly, instead use
// [IS_BOOL].
static inline bool wrenIsBool(Value value)
{
#if WREN_NAN_TAGGING
return value == TRUE_VAL || value == FALSE_VAL;
#else
return value.type == VAL_FALSE || value.type == VAL_TRUE;
#endif
}
// Returns true if [value] is an object of type [type]. Do not call this
// directly, instead use the [IS___] macro for the type in question.
static inline bool wrenIsObjType(Value value, ObjType type)
{
return IS_OBJ(value) && AS_OBJ(value)->type == type;
}
// Converts the raw object pointer [obj] to a [Value].
static inline Value wrenObjectToValue(Obj* obj)
{
#if WREN_NAN_TAGGING
// The triple casting is necessary here to satisfy some compilers:
// 1. (uintptr_t) Convert the pointer to a number of the right size.
// 2. (uint64_t) Pad it up to 64 bits in 32-bit builds.
// 3. Or in the bits to make a tagged Nan.
// 4. Cast to a typedef'd value.
return (Value)(SIGN_BIT | QNAN | (uint64_t)(uintptr_t)(obj));
#else
Value value;
value.type = VAL_OBJ;
value.as.obj = obj;
return value;
#endif
}
// Interprets [value] as a [double].
static inline double wrenValueToNum(Value value)
{
#if WREN_NAN_TAGGING
return wrenDoubleFromBits(value);
#else
return value.as.num;
#endif
}
// Converts [num] to a [Value].
static inline Value wrenNumToValue(double num)
{
#if WREN_NAN_TAGGING
return wrenDoubleToBits(num);
#else
Value value;
value.type = VAL_NUM;
value.as.num = num;
return value;
#endif
}
static inline bool wrenMapIsValidKey(Value arg)
{
return IS_BOOL(arg)
|| IS_CLASS(arg)
|| IS_NULL(arg)
|| IS_NUM(arg)
|| IS_RANGE(arg)
|| IS_STRING(arg);
}
#endif
// End file "wren_value.h"
// Begin file "wren_vm.h"
#ifndef wren_vm_h
#define wren_vm_h
// Begin file "wren_compiler.h"
#ifndef wren_compiler_h
#define wren_compiler_h
typedef struct sCompiler Compiler;
// This module defines the compiler for Wren. It takes a string of source code
// and lexes, parses, and compiles it. Wren uses a single-pass compiler. It
// does not build an actual AST during parsing and then consume that to
// generate code. Instead, the parser directly emits bytecode.
//
// This forces a few restrictions on the grammar and semantics of the language.
// Things like forward references and arbitrary lookahead are much harder. We
// get a lot in return for that, though.
//
// The implementation is much simpler since we don't need to define a bunch of
// AST data structures. More so, we don't have to deal with managing memory for
// AST objects. The compiler does almost no dynamic allocation while running.
//
// Compilation is also faster since we don't create a bunch of temporary data
// structures and destroy them after generating code.
// Compiles [source], a string of Wren source code located in [module], to an
// [ObjFn] that will execute that code when invoked. Returns `NULL` if the
// source contains any syntax errors.
//
// If [isExpression] is `true`, [source] should be a single expression, and
// this compiles it to a function that evaluates and returns that expression.
// Otherwise, [source] should be a series of top level statements.
//
// If [printErrors] is `true`, any compile errors are output to stderr.
// Otherwise, they are silently discarded.
ObjFn* wrenCompile(WrenVM* vm, ObjModule* module, const char* source,
bool isExpression, bool printErrors);
// When a class is defined, its superclass is not known until runtime since
// class definitions are just imperative statements. Most of the bytecode for a
// a method doesn't care, but there are two places where it matters:
//
// - To load or store a field, we need to know the index of the field in the
// instance's field array. We need to adjust this so that subclass fields
// are positioned after superclass fields, and we don't know this until the
// superclass is known.
//
// - Superclass calls need to know which superclass to dispatch to.
//
// We could handle this dynamically, but that adds overhead. Instead, when a
// method is bound, we walk the bytecode for the function and patch it up.
void wrenBindMethodCode(ObjClass* classObj, ObjFn* fn);
// Reaches all of the heap-allocated objects in use by [compiler] (and all of
// its parents) so that they are not collected by the GC.
void wrenMarkCompiler(WrenVM* vm, Compiler* compiler);
#endif
// End file "wren_compiler.h"
// The maximum number of temporary objects that can be made visible to the GC
// at one time.
#define WREN_MAX_TEMP_ROOTS 8
typedef enum
{
#define OPCODE(name, _) CODE_##name,
// Begin file "wren_opcodes.h"
// This defines the bytecode instructions used by the VM. It does so by invoking
// an OPCODE() macro which is expected to be defined at the point that this is
// included. (See: http://en.wikipedia.org/wiki/X_Macro for more.)
//
// The first argument is the name of the opcode. The second is its "stack
// effect" -- the amount that the op code changes the size of the stack. A
// stack effect of 1 means it pushes a value and the stack grows one larger.
// -2 means it pops two values, etc.
//
// Note that the order of instructions here affects the order of the dispatch
// table in the VM's interpreter loop. That in turn affects caching which
// affects overall performance. Take care to run benchmarks if you change the
// order here.
// Load the constant at index [arg].
OPCODE(CONSTANT, 1)
// Push null onto the stack.
OPCODE(NULL, 1)
// Push false onto the stack.
OPCODE(FALSE, 1)
// Push true onto the stack.
OPCODE(TRUE, 1)
// Pushes the value in the given local slot.
OPCODE(LOAD_LOCAL_0, 1)
OPCODE(LOAD_LOCAL_1, 1)
OPCODE(LOAD_LOCAL_2, 1)
OPCODE(LOAD_LOCAL_3, 1)
OPCODE(LOAD_LOCAL_4, 1)
OPCODE(LOAD_LOCAL_5, 1)
OPCODE(LOAD_LOCAL_6, 1)
OPCODE(LOAD_LOCAL_7, 1)
OPCODE(LOAD_LOCAL_8, 1)
// Note: The compiler assumes the following _STORE instructions always
// immediately follow their corresponding _LOAD ones.
// Pushes the value in local slot [arg].
OPCODE(LOAD_LOCAL, 1)
// Stores the top of stack in local slot [arg]. Does not pop it.
OPCODE(STORE_LOCAL, 0)
// Pushes the value in upvalue [arg].
OPCODE(LOAD_UPVALUE, 1)
// Stores the top of stack in upvalue [arg]. Does not pop it.
OPCODE(STORE_UPVALUE, 0)
// Pushes the value of the top-level variable in slot [arg].
OPCODE(LOAD_MODULE_VAR, 1)
// Stores the top of stack in top-level variable slot [arg]. Does not pop it.
OPCODE(STORE_MODULE_VAR, 0)
// Pushes the value of the field in slot [arg] of the receiver of the current
// function. This is used for regular field accesses on "this" directly in
// methods. This instruction is faster than the more general CODE_LOAD_FIELD
// instruction.
OPCODE(LOAD_FIELD_THIS, 1)
// Stores the top of the stack in field slot [arg] in the receiver of the
// current value. Does not pop the value. This instruction is faster than the
// more general CODE_LOAD_FIELD instruction.
OPCODE(STORE_FIELD_THIS, 0)
// Pops an instance and pushes the value of the field in slot [arg] of it.
OPCODE(LOAD_FIELD, 0)
// Pops an instance and stores the subsequent top of stack in field slot
// [arg] in it. Does not pop the value.
OPCODE(STORE_FIELD, -1)
// Pop and discard the top of stack.
OPCODE(POP, -1)
// Invoke the method with symbol [arg]. The number indicates the number of
// arguments (not including the receiver).
OPCODE(CALL_0, 0)
OPCODE(CALL_1, -1)
OPCODE(CALL_2, -2)
OPCODE(CALL_3, -3)
OPCODE(CALL_4, -4)
OPCODE(CALL_5, -5)
OPCODE(CALL_6, -6)
OPCODE(CALL_7, -7)
OPCODE(CALL_8, -8)
OPCODE(CALL_9, -9)
OPCODE(CALL_10, -10)
OPCODE(CALL_11, -11)
OPCODE(CALL_12, -12)
OPCODE(CALL_13, -13)
OPCODE(CALL_14, -14)
OPCODE(CALL_15, -15)
OPCODE(CALL_16, -16)
// Invoke a superclass method with symbol [arg]. The number indicates the
// number of arguments (not including the receiver).
OPCODE(SUPER_0, 0)
OPCODE(SUPER_1, -1)
OPCODE(SUPER_2, -2)
OPCODE(SUPER_3, -3)
OPCODE(SUPER_4, -4)
OPCODE(SUPER_5, -5)
OPCODE(SUPER_6, -6)
OPCODE(SUPER_7, -7)
OPCODE(SUPER_8, -8)
OPCODE(SUPER_9, -9)
OPCODE(SUPER_10, -10)
OPCODE(SUPER_11, -11)
OPCODE(SUPER_12, -12)
OPCODE(SUPER_13, -13)
OPCODE(SUPER_14, -14)
OPCODE(SUPER_15, -15)
OPCODE(SUPER_16, -16)
// Jump the instruction pointer [arg] forward.
OPCODE(JUMP, 0)
// Jump the instruction pointer [arg] backward.
OPCODE(LOOP, 0)
// Pop and if not truthy then jump the instruction pointer [arg] forward.
OPCODE(JUMP_IF, -1)
// If the top of the stack is false, jump [arg] forward. Otherwise, pop and
// continue.
OPCODE(AND, -1)
// If the top of the stack is non-false, jump [arg] forward. Otherwise, pop
// and continue.
OPCODE(OR, -1)
// Close the upvalue for the local on the top of the stack, then pop it.
OPCODE(CLOSE_UPVALUE, -1)
// Exit from the current function and return the value on the top of the
// stack.
OPCODE(RETURN, 0)
// Creates a closure for the function stored at [arg] in the constant table.
//
// Following the function argument is a number of arguments, two for each
// upvalue. The first is true if the variable being captured is a local (as
// opposed to an upvalue), and the second is the index of the local or
// upvalue being captured.
//
// Pushes the created closure.
OPCODE(CLOSURE, 1)
// Creates a new instance of a class.
//
// Assumes the class object is in slot zero, and replaces it with the new
// uninitialized instance of that class. This opcode is only emitted by the
// compiler-generated constructor metaclass methods.
OPCODE(CONSTRUCT, 0)
// Creates a new instance of a foreign class.
//
// Assumes the class object is in slot zero, and replaces it with the new
// uninitialized instance of that class. This opcode is only emitted by the
// compiler-generated constructor metaclass methods.
OPCODE(FOREIGN_CONSTRUCT, 0)
// Creates a class. Top of stack is the superclass. Below that is a string for
// the name of the class. Byte [arg] is the number of fields in the class.
OPCODE(CLASS, -1)
// Ends a class.
// Atm the stack contains the class and the ClassAttributes (or null).
OPCODE(END_CLASS, -2)
// Creates a foreign class. Top of stack is the superclass. Below that is a
// string for the name of the class.
OPCODE(FOREIGN_CLASS, -1)
// Define a method for symbol [arg]. The class receiving the method is popped
// off the stack, then the function defining the body is popped.
//
// If a foreign method is being defined, the "function" will be a string
// identifying the foreign method. Otherwise, it will be a function or
// closure.
OPCODE(METHOD_INSTANCE, -2)
// Define a method for symbol [arg]. The class whose metaclass will receive
// the method is popped off the stack, then the function defining the body is
// popped.
//
// If a foreign method is being defined, the "function" will be a string
// identifying the foreign method. Otherwise, it will be a function or
// closure.
OPCODE(METHOD_STATIC, -2)
// This is executed at the end of the module's body. Pushes NULL onto the stack
// as the "return value" of the import statement and stores the module as the
// most recently imported one.
OPCODE(END_MODULE, 1)
// Import a module whose name is the string stored at [arg] in the constant
// table.
//
// Pushes null onto the stack so that the fiber for the imported module can
// replace that with a dummy value when it returns. (Fibers always return a
// value when resuming a caller.)
OPCODE(IMPORT_MODULE, 1)
// Import a variable from the most recently imported module. The name of the
// variable to import is at [arg] in the constant table. Pushes the loaded
// variable's value.
OPCODE(IMPORT_VARIABLE, 1)
// This pseudo-instruction indicates the end of the bytecode. It should
// always be preceded by a `CODE_RETURN`, so is never actually executed.
OPCODE(END, 0)
// End file "wren_opcodes.h"
#undef OPCODE
} Code;
// A handle to a value, basically just a linked list of extra GC roots.
//
// Note that even non-heap-allocated values can be stored here.
struct WrenHandle
{
Value value;
WrenHandle* prev;
WrenHandle* next;
};
struct WrenVM
{
ObjClass* boolClass;
ObjClass* classClass;
ObjClass* fiberClass;
ObjClass* fnClass;
ObjClass* listClass;
ObjClass* mapClass;
ObjClass* nullClass;
ObjClass* numClass;
ObjClass* objectClass;
ObjClass* rangeClass;
ObjClass* stringClass;
// The fiber that is currently running.
ObjFiber* fiber;
// The loaded modules. Each key is an ObjString (except for the main module,
// whose key is null) for the module's name and the value is the ObjModule
// for the module.
ObjMap* modules;
// The most recently imported module. More specifically, the module whose
// code has most recently finished executing.
//
// Not treated like a GC root since the module is already in [modules].
ObjModule* lastModule;
// Memory management data:
// The number of bytes that are known to be currently allocated. Includes all
// memory that was proven live after the last GC, as well as any new bytes
// that were allocated since then. Does *not* include bytes for objects that
// were freed since the last GC.
size_t bytesAllocated;
// The number of total allocated bytes that will trigger the next GC.
size_t nextGC;
// The first object in the linked list of all currently allocated objects.
Obj* first;
// The "gray" set for the garbage collector. This is the stack of unprocessed
// objects while a garbage collection pass is in process.
Obj** gray;
int grayCount;
int grayCapacity;
// The list of temporary roots. This is for temporary or new objects that are
// not otherwise reachable but should not be collected.
//
// They are organized as a stack of pointers stored in this array. This
// implies that temporary roots need to have stack semantics: only the most
// recently pushed object can be released.
Obj* tempRoots[WREN_MAX_TEMP_ROOTS];
int numTempRoots;
// Pointer to the first node in the linked list of active handles or NULL if
// there are none.
WrenHandle* handles;
// Pointer to the bottom of the range of stack slots available for use from
// the C API. During a foreign method, this will be in the stack of the fiber
// that is executing a method.
//
// If not in a foreign method, this is initially NULL. If the user requests
// slots by calling wrenEnsureSlots(), a stack is created and this is
// initialized.
Value* apiStack;
WrenConfiguration config;
// Compiler and debugger data:
// The compiler that is currently compiling code. This is used so that heap
// allocated objects used by the compiler can be found if a GC is kicked off
// in the middle of a compile.
Compiler* compiler;
// There is a single global symbol table for all method names on all classes.
// Method calls are dispatched directly by index in this table.
SymbolTable methodNames;
};
// A generic allocation function that handles all explicit memory management.
// It's used like so:
//
// - To allocate new memory, [memory] is NULL and [oldSize] is zero. It should
// return the allocated memory or NULL on failure.
//
// - To attempt to grow an existing allocation, [memory] is the memory,
// [oldSize] is its previous size, and [newSize] is the desired size.
// It should return [memory] if it was able to grow it in place, or a new
// pointer if it had to move it.
//
// - To shrink memory, [memory], [oldSize], and [newSize] are the same as above
// but it will always return [memory].
//
// - To free memory, [memory] will be the memory to free and [newSize] and
// [oldSize] will be zero. It should return NULL.
void* wrenReallocate(WrenVM* vm, void* memory, size_t oldSize, size_t newSize);
// Invoke the finalizer for the foreign object referenced by [foreign].
void wrenFinalizeForeign(WrenVM* vm, ObjForeign* foreign);
// Creates a new [WrenHandle] for [value].
WrenHandle* wrenMakeHandle(WrenVM* vm, Value value);
// Compile [source] in the context of [module] and wrap in a fiber that can
// execute it.
//
// Returns NULL if a compile error occurred.
ObjClosure* wrenCompileSource(WrenVM* vm, const char* module,
const char* source, bool isExpression,
bool printErrors);
// Looks up a variable from a previously-loaded module.
//
// Aborts the current fiber if the module or variable could not be found.
Value wrenGetModuleVariable(WrenVM* vm, Value moduleName, Value variableName);
// Returns the value of the module-level variable named [name] in the main
// module.
Value wrenFindVariable(WrenVM* vm, ObjModule* module, const char* name);
// Adds a new implicitly declared top-level variable named [name] to [module]
// based on a use site occurring on [line].
//
// Does not check to see if a variable with that name is already declared or
// defined. Returns the symbol for the new variable or -2 if there are too many
// variables defined.
int wrenDeclareVariable(WrenVM* vm, ObjModule* module, const char* name,
size_t length, int line);
// Adds a new top-level variable named [name] to [module], and optionally
// populates line with the line of the implicit first use (line can be NULL).
//
// Returns the symbol for the new variable, -1 if a variable with the given name
// is already defined, or -2 if there are too many variables defined.
// Returns -3 if this is a top-level lowercase variable (localname) that was
// used before being defined.
int wrenDefineVariable(WrenVM* vm, ObjModule* module, const char* name,
size_t length, Value value, int* line);
// Pushes [closure] onto [fiber]'s callstack to invoke it. Expects [numArgs]
// arguments (including the receiver) to be on the top of the stack already.
static inline void wrenCallFunction(WrenVM* vm, ObjFiber* fiber,
ObjClosure* closure, int numArgs)
{
// Grow the call frame array if needed.
if (fiber->numFrames + 1 > fiber->frameCapacity)
{
int max = fiber->frameCapacity * 2;
fiber->frames = (CallFrame*)wrenReallocate(vm, fiber->frames,
sizeof(CallFrame) * fiber->frameCapacity, sizeof(CallFrame) * max);
fiber->frameCapacity = max;
}
// Grow the stack if needed.
int stackSize = (int)(fiber->stackTop - fiber->stack);
int needed = stackSize + closure->fn->maxSlots;
wrenEnsureStack(vm, fiber, needed);
wrenAppendCallFrame(vm, fiber, closure, fiber->stackTop - numArgs);
}
// Marks [obj] as a GC root so that it doesn't get collected.
void wrenPushRoot(WrenVM* vm, Obj* obj);
// Removes the most recently pushed temporary root.
void wrenPopRoot(WrenVM* vm);
// Returns the class of [value].
//
// Defined here instead of in wren_value.h because it's critical that this be
// inlined. That means it must be defined in the header, but the wren_value.h
// header doesn't have a full definitely of WrenVM yet.
static inline ObjClass* wrenGetClassInline(WrenVM* vm, Value value)
{
if (IS_NUM(value)) return vm->numClass;
if (IS_OBJ(value)) return AS_OBJ(value)->classObj;
#if WREN_NAN_TAGGING
switch (GET_TAG(value))
{
case TAG_FALSE: return vm->boolClass; break;
case TAG_NAN: return vm->numClass; break;
case TAG_NULL: return vm->nullClass; break;
case TAG_TRUE: return vm->boolClass; break;
case TAG_UNDEFINED: UNREACHABLE();
}
#else
switch (value.type)
{
case VAL_FALSE: return vm->boolClass;
case VAL_NULL: return vm->nullClass;
case VAL_NUM: return vm->numClass;
case VAL_TRUE: return vm->boolClass;
case VAL_OBJ: return AS_OBJ(value)->classObj;
case VAL_UNDEFINED: UNREACHABLE();
}
#endif
UNREACHABLE();
return NULL;
}
// Returns `true` if [name] is a local variable name (starts with a lowercase
// letter).
static inline bool wrenIsLocalName(const char* name)
{
return name[0] >= 'a' && name[0] <= 'z';
}
static inline bool wrenIsFalsyValue(Value value)
{
return IS_FALSE(value) || IS_NULL(value);
}
#endif
// End file "wren_vm.h"
// Prints the stack trace for the current fiber.
//
// Used when a fiber throws a runtime error which is not caught.
void wrenDebugPrintStackTrace(WrenVM* vm);
// The "dump" functions are used for debugging Wren itself. Normal code paths
// will not call them unless one of the various DEBUG_ flags is enabled.
// Prints a representation of [value] to stdout.
void wrenDumpValue(Value value);
// Prints a representation of the bytecode for [fn] at instruction [i].
int wrenDumpInstruction(WrenVM* vm, ObjFn* fn, int i);
// Prints the disassembled code for [fn] to stdout.
void wrenDumpCode(WrenVM* vm, ObjFn* fn);
// Prints the contents of the current stack for [fiber] to stdout.
void wrenDumpStack(ObjFiber* fiber);
#endif
// End file "wren_debug.h"
// Begin file "wren_debug.c"
#include <stdio.h>
void wrenDebugPrintStackTrace(WrenVM* vm)
{
// Bail if the host doesn't enable printing errors.
if (vm->config.errorFn == NULL) return;
ObjFiber* fiber = vm->fiber;
if (IS_STRING(fiber->error))
{
vm->config.errorFn(vm, WREN_ERROR_RUNTIME,
NULL, -1, AS_CSTRING(fiber->error));
}
else
{
// TODO: Print something a little useful here. Maybe the name of the error's
// class?
vm->config.errorFn(vm, WREN_ERROR_RUNTIME,
NULL, -1, "[error object]");
}
for (int i = fiber->numFrames - 1; i >= 0; i--)
{
CallFrame* frame = &fiber->frames[i];
ObjFn* fn = frame->closure->fn;
// Skip over stub functions for calling methods from the C API.
if (fn->module == NULL) continue;
// The built-in core module has no name. We explicitly omit it from stack
// traces since we don't want to highlight to a user the implementation
// detail of what part of the core module is written in C and what is Wren.
if (fn->module->name == NULL) continue;
// -1 because IP has advanced past the instruction that it just executed.
int line = fn->debug->sourceLines.data[frame->ip - fn->code.data - 1];
vm->config.errorFn(vm, WREN_ERROR_STACK_TRACE,
fn->module->name->value, line,
fn->debug->name);
}
}
static void dumpObject(Obj* obj)
{
switch (obj->type)
{
case OBJ_CLASS:
printf("[class %s %p]", ((ObjClass*)obj)->name->value, obj);
break;
case OBJ_CLOSURE: printf("[closure %p]", obj); break;
case OBJ_FIBER: printf("[fiber %p]", obj); break;
case OBJ_FN: printf("[fn %p]", obj); break;
case OBJ_FOREIGN: printf("[foreign %p]", obj); break;
case OBJ_INSTANCE: printf("[instance %p]", obj); break;
case OBJ_LIST: printf("[list %p]", obj); break;
case OBJ_MAP: printf("[map %p]", obj); break;
case OBJ_MODULE: printf("[module %p]", obj); break;
case OBJ_RANGE: printf("[range %p]", obj); break;
case OBJ_STRING: printf("%s", ((ObjString*)obj)->value); break;
case OBJ_UPVALUE: printf("[upvalue %p]", obj); break;
default: printf("[unknown object %d]", obj->type); break;
}
}
void wrenDumpValue(Value value)
{
#if WREN_NAN_TAGGING
if (IS_NUM(value))
{
printf("%.14g", AS_NUM(value));
}
else if (IS_OBJ(value))
{
dumpObject(AS_OBJ(value));
}
else
{
switch (GET_TAG(value))
{
case TAG_FALSE: printf("false"); break;
case TAG_NAN: printf("NaN"); break;
case TAG_NULL: printf("null"); break;
case TAG_TRUE: printf("true"); break;
case TAG_UNDEFINED: UNREACHABLE();
}
}
#else
switch (value.type)
{
case VAL_FALSE: printf("false"); break;
case VAL_NULL: printf("null"); break;
case VAL_NUM: printf("%.14g", AS_NUM(value)); break;
case VAL_TRUE: printf("true"); break;
case VAL_OBJ: dumpObject(AS_OBJ(value)); break;
case VAL_UNDEFINED: UNREACHABLE();
}
#endif
}
static int dumpInstruction(WrenVM* vm, ObjFn* fn, int i, int* lastLine)
{
int start = i;
uint8_t* bytecode = fn->code.data;
Code code = (Code)bytecode[i];
int line = fn->debug->sourceLines.data[i];
if (lastLine == NULL || *lastLine != line)
{
printf("%4d:", line);
if (lastLine != NULL) *lastLine = line;
}
else
{
printf(" ");
}
printf(" %04d ", i++);
#define READ_BYTE() (bytecode[i++])
#define READ_SHORT() (i += 2, (bytecode[i - 2] << 8) | bytecode[i - 1])
#define BYTE_INSTRUCTION(name) \
printf("%-16s %5d\n", name, READ_BYTE()); \
break
switch (code)
{
case CODE_CONSTANT:
{
int constant = READ_SHORT();
printf("%-16s %5d '", "CONSTANT", constant);
wrenDumpValue(fn->constants.data[constant]);
printf("'\n");
break;
}
case CODE_NULL: printf("NULL\n"); break;
case CODE_FALSE: printf("FALSE\n"); break;
case CODE_TRUE: printf("TRUE\n"); break;
case CODE_LOAD_LOCAL_0: printf("LOAD_LOCAL_0\n"); break;
case CODE_LOAD_LOCAL_1: printf("LOAD_LOCAL_1\n"); break;
case CODE_LOAD_LOCAL_2: printf("LOAD_LOCAL_2\n"); break;
case CODE_LOAD_LOCAL_3: printf("LOAD_LOCAL_3\n"); break;
case CODE_LOAD_LOCAL_4: printf("LOAD_LOCAL_4\n"); break;
case CODE_LOAD_LOCAL_5: printf("LOAD_LOCAL_5\n"); break;
case CODE_LOAD_LOCAL_6: printf("LOAD_LOCAL_6\n"); break;
case CODE_LOAD_LOCAL_7: printf("LOAD_LOCAL_7\n"); break;
case CODE_LOAD_LOCAL_8: printf("LOAD_LOCAL_8\n"); break;
case CODE_LOAD_LOCAL: BYTE_INSTRUCTION("LOAD_LOCAL");
case CODE_STORE_LOCAL: BYTE_INSTRUCTION("STORE_LOCAL");
case CODE_LOAD_UPVALUE: BYTE_INSTRUCTION("LOAD_UPVALUE");
case CODE_STORE_UPVALUE: BYTE_INSTRUCTION("STORE_UPVALUE");
case CODE_LOAD_MODULE_VAR:
{
int slot = READ_SHORT();
printf("%-16s %5d '%s'\n", "LOAD_MODULE_VAR", slot,
fn->module->variableNames.data[slot]->value);
break;
}
case CODE_STORE_MODULE_VAR:
{
int slot = READ_SHORT();
printf("%-16s %5d '%s'\n", "STORE_MODULE_VAR", slot,
fn->module->variableNames.data[slot]->value);
break;
}
case CODE_LOAD_FIELD_THIS: BYTE_INSTRUCTION("LOAD_FIELD_THIS");
case CODE_STORE_FIELD_THIS: BYTE_INSTRUCTION("STORE_FIELD_THIS");
case CODE_LOAD_FIELD: BYTE_INSTRUCTION("LOAD_FIELD");
case CODE_STORE_FIELD: BYTE_INSTRUCTION("STORE_FIELD");
case CODE_POP: printf("POP\n"); break;
case CODE_CALL_0:
case CODE_CALL_1:
case CODE_CALL_2:
case CODE_CALL_3:
case CODE_CALL_4:
case CODE_CALL_5:
case CODE_CALL_6:
case CODE_CALL_7:
case CODE_CALL_8:
case CODE_CALL_9:
case CODE_CALL_10:
case CODE_CALL_11:
case CODE_CALL_12:
case CODE_CALL_13:
case CODE_CALL_14:
case CODE_CALL_15:
case CODE_CALL_16:
{
int numArgs = bytecode[i - 1] - CODE_CALL_0;
int symbol = READ_SHORT();
printf("CALL_%-11d %5d '%s'\n", numArgs, symbol,
vm->methodNames.data[symbol]->value);
break;
}
case CODE_SUPER_0:
case CODE_SUPER_1:
case CODE_SUPER_2:
case CODE_SUPER_3:
case CODE_SUPER_4:
case CODE_SUPER_5:
case CODE_SUPER_6:
case CODE_SUPER_7:
case CODE_SUPER_8:
case CODE_SUPER_9:
case CODE_SUPER_10:
case CODE_SUPER_11:
case CODE_SUPER_12:
case CODE_SUPER_13:
case CODE_SUPER_14:
case CODE_SUPER_15:
case CODE_SUPER_16:
{
int numArgs = bytecode[i - 1] - CODE_SUPER_0;
int symbol = READ_SHORT();
int superclass = READ_SHORT();
printf("SUPER_%-10d %5d '%s' %5d\n", numArgs, symbol,
vm->methodNames.data[symbol]->value, superclass);
break;
}
case CODE_JUMP:
{
int offset = READ_SHORT();
printf("%-16s %5d to %d\n", "JUMP", offset, i + offset);
break;
}
case CODE_LOOP:
{
int offset = READ_SHORT();
printf("%-16s %5d to %d\n", "LOOP", offset, i - offset);
break;
}
case CODE_JUMP_IF:
{
int offset = READ_SHORT();
printf("%-16s %5d to %d\n", "JUMP_IF", offset, i + offset);
break;
}
case CODE_AND:
{
int offset = READ_SHORT();
printf("%-16s %5d to %d\n", "AND", offset, i + offset);
break;
}
case CODE_OR:
{
int offset = READ_SHORT();
printf("%-16s %5d to %d\n", "OR", offset, i + offset);
break;
}
case CODE_CLOSE_UPVALUE: printf("CLOSE_UPVALUE\n"); break;
case CODE_RETURN: printf("RETURN\n"); break;
case CODE_CLOSURE:
{
int constant = READ_SHORT();
printf("%-16s %5d ", "CLOSURE", constant);
wrenDumpValue(fn->constants.data[constant]);
printf(" ");
ObjFn* loadedFn = AS_FN(fn->constants.data[constant]);
for (int j = 0; j < loadedFn->numUpvalues; j++)
{
int isLocal = READ_BYTE();
int index = READ_BYTE();
if (j > 0) printf(", ");
printf("%s %d", isLocal ? "local" : "upvalue", index);
}
printf("\n");
break;
}
case CODE_CONSTRUCT: printf("CONSTRUCT\n"); break;
case CODE_FOREIGN_CONSTRUCT: printf("FOREIGN_CONSTRUCT\n"); break;
case CODE_CLASS:
{
int numFields = READ_BYTE();
printf("%-16s %5d fields\n", "CLASS", numFields);
break;
}
case CODE_FOREIGN_CLASS: printf("FOREIGN_CLASS\n"); break;
case CODE_END_CLASS: printf("END_CLASS\n"); break;
case CODE_METHOD_INSTANCE:
{
int symbol = READ_SHORT();
printf("%-16s %5d '%s'\n", "METHOD_INSTANCE", symbol,
vm->methodNames.data[symbol]->value);
break;
}
case CODE_METHOD_STATIC:
{
int symbol = READ_SHORT();
printf("%-16s %5d '%s'\n", "METHOD_STATIC", symbol,
vm->methodNames.data[symbol]->value);
break;
}
case CODE_END_MODULE:
printf("END_MODULE\n");
break;
case CODE_IMPORT_MODULE:
{
int name = READ_SHORT();
printf("%-16s %5d '", "IMPORT_MODULE", name);
wrenDumpValue(fn->constants.data[name]);
printf("'\n");
break;
}
case CODE_IMPORT_VARIABLE:
{
int variable = READ_SHORT();
printf("%-16s %5d '", "IMPORT_VARIABLE", variable);
wrenDumpValue(fn->constants.data[variable]);
printf("'\n");
break;
}
case CODE_END:
printf("END\n");
break;
default:
printf("UKNOWN! [%d]\n", bytecode[i - 1]);
break;
}
// Return how many bytes this instruction takes, or -1 if it's an END.
if (code == CODE_END) return -1;
return i - start;
#undef READ_BYTE
#undef READ_SHORT
}
int wrenDumpInstruction(WrenVM* vm, ObjFn* fn, int i)
{
return dumpInstruction(vm, fn, i, NULL);
}
void wrenDumpCode(WrenVM* vm, ObjFn* fn)
{
printf("%s: %s\n",
fn->module->name == NULL ? "<core>" : fn->module->name->value,
fn->debug->name);
int i = 0;
int lastLine = -1;
for (;;)
{
int offset = dumpInstruction(vm, fn, i, &lastLine);
if (offset == -1) break;
i += offset;
}
printf("\n");
}
void wrenDumpStack(ObjFiber* fiber)
{
printf("(fiber %p) ", fiber);
for (Value* slot = fiber->stack; slot < fiber->stackTop; slot++)
{
wrenDumpValue(*slot);
printf(" | ");
}
printf("\n");
}
// End file "wren_debug.c"
// Begin file "wren_compiler.c"
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#if WREN_DEBUG_DUMP_COMPILED_CODE
#endif
// 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.
// The maximum number of local (i.e. not module level) variables that can be
// declared in a single function, method, or chunk of top level code. This is
// the maximum number of variables in scope at one time, and spans block scopes.
//
// Note that this limitation is also explicit in the bytecode. Since
// `CODE_LOAD_LOCAL` and `CODE_STORE_LOCAL` use a single argument byte to
// identify the local, only 256 can be in scope at one time.
#define MAX_LOCALS 256
// The maximum number of upvalues (i.e. variables from enclosing functions)
// that a function can close over.
#define MAX_UPVALUES 256
// 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
// two-byte argument.
#define MAX_CONSTANTS (1 << 16)
// The maximum distance a CODE_JUMP or CODE_JUMP_IF instruction can move the
// instruction pointer.
#define MAX_JUMP (1 << 16)
// The maximum depth that interpolation can nest. For example, this string has
// three levels:
//
// "outside %(one + "%(two + "%(three)")")"
#define MAX_INTERPOLATION_NESTING 8
// The buffer size used to format a compile error message, excluding the header
// with the module name and error location. Using a hardcoded buffer for this
// is kind of hairy, but fortunately we can control what the longest possible
// message is and handle that. Ideally, we'd use `snprintf()`, but that's not
// available in standard C++98.
#define ERROR_MESSAGE_SIZE (80 + MAX_VARIABLE_NAME + 15)
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_DOTDOT,
TOKEN_DOTDOTDOT,
TOKEN_COMMA,
TOKEN_STAR,
TOKEN_SLASH,
TOKEN_PERCENT,
TOKEN_HASH,
TOKEN_PLUS,
TOKEN_MINUS,
TOKEN_LTLT,
TOKEN_GTGT,
TOKEN_PIPE,
TOKEN_PIPEPIPE,
TOKEN_CARET,
TOKEN_AMP,
TOKEN_AMPAMP,
TOKEN_BANG,
TOKEN_TILDE,
TOKEN_QUESTION,
TOKEN_EQ,
TOKEN_LT,
TOKEN_GT,
TOKEN_LTEQ,
TOKEN_GTEQ,
TOKEN_EQEQ,
TOKEN_BANGEQ,
TOKEN_BREAK,
TOKEN_CONTINUE,
TOKEN_CLASS,
TOKEN_CONSTRUCT,
TOKEN_ELSE,
TOKEN_FALSE,
TOKEN_FOR,
TOKEN_FOREIGN,
TOKEN_IF,
TOKEN_IMPORT,
TOKEN_AS,
TOKEN_IN,
TOKEN_IS,
TOKEN_NULL,
TOKEN_RETURN,
TOKEN_STATIC,
TOKEN_SUPER,
TOKEN_THIS,
TOKEN_TRUE,
TOKEN_VAR,
TOKEN_WHILE,
TOKEN_FIELD,
TOKEN_STATIC_FIELD,
TOKEN_NAME,
TOKEN_NUMBER,
// A string literal without any interpolation, or the last section of a
// string following the last interpolated expression.
TOKEN_STRING,
// A portion of a string literal preceding an interpolated expression. This
// string:
//
// "a %(b) c %(d) e"
//
// is tokenized to:
//
// TOKEN_INTERPOLATION "a "
// TOKEN_NAME b
// TOKEN_INTERPOLATION " c "
// TOKEN_NAME d
// TOKEN_STRING " e"
TOKEN_INTERPOLATION,
TOKEN_LINE,
TOKEN_ERROR,
TOKEN_EOF
} TokenType;
typedef struct
{
TokenType type;
// The beginning of the token, pointing directly into the source.
const char* start;
// The length of the token in characters.
int length;
// The 1-based line where the token appears.
int line;
// The parsed value if the token is a literal.
Value value;
} Token;
typedef struct
{
WrenVM* vm;
// The module being parsed.
ObjModule* module;
// The source code being parsed.
const char* source;
// The beginning of the currently-being-lexed token in [source].
const char* tokenStart;
// The current character being lexed in [source].
const char* currentChar;
// The 1-based line number of [currentChar].
int currentLine;
// The upcoming token.
Token next;
// The most recently lexed token.
Token current;
// The most recently consumed/advanced token.
Token previous;
// Tracks the lexing state when tokenizing interpolated strings.
//
// Interpolated strings make the lexer not strictly regular: we don't know
// whether a ")" should be treated as a RIGHT_PAREN token or as ending an
// interpolated expression unless we know whether we are inside a string
// interpolation and how many unmatched "(" there are. This is particularly
// complex because interpolation can nest:
//
// " %( " %( inner ) " ) "
//
// This tracks that state. The parser maintains a stack of ints, one for each
// level of current interpolation nesting. Each value is the number of
// unmatched "(" that are waiting to be closed.
int parens[MAX_INTERPOLATION_NESTING];
int numParens;
// Whether compile errors should be printed to stderr or discarded.
bool printErrors;
// If a syntax or compile error has occurred.
bool hasError;
} Parser;
typedef struct
{
// 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.
int length;
// 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;
// If this local variable is being used as an upvalue.
bool isUpvalue;
} Local;
typedef struct
{
// True if this upvalue is capturing a local variable from the enclosing
// function. False if it's capturing an upvalue.
bool isLocal;
// The index of the local or upvalue being captured in the enclosing function.
int index;
} CompilerUpvalue;
// Bookkeeping information for the current loop being compiled.
typedef struct sLoop
{
// Index of the instruction that the loop should jump back to.
int start;
// Index of the argument for the CODE_JUMP_IF instruction used to exit the
// loop. Stored so we can patch it once we know where the loop ends.
int exitJump;
// Index of the first instruction of the body of the loop.
int body;
// Depth of the scope(s) that need to be exited if a break is hit inside the
// loop.
int scopeDepth;
// The loop enclosing this one, or NULL if this is the outermost loop.
struct sLoop* enclosing;
} Loop;
// The different signature syntaxes for different kinds of methods.
typedef enum
{
// A name followed by a (possibly empty) parenthesized parameter list. Also
// used for binary operators.
SIG_METHOD,
// Just a name. Also used for unary operators.
SIG_GETTER,
// A name followed by "=".
SIG_SETTER,
// A square bracketed parameter list.
SIG_SUBSCRIPT,
// A square bracketed parameter list followed by "=".
SIG_SUBSCRIPT_SETTER,
// A constructor initializer function. This has a distinct signature to
// prevent it from being invoked directly outside of the constructor on the
// metaclass.
SIG_INITIALIZER
} SignatureType;
typedef struct
{
const char* name;
int length;
SignatureType type;
int arity;
} Signature;
// Bookkeeping information for compiling a class definition.
typedef struct
{
// The name of the class.
ObjString* name;
// Attributes for the class itself
ObjMap* classAttributes;
// Attributes for methods in this class
ObjMap* methodAttributes;
// Symbol table for the fields of the class.
SymbolTable fields;
// Symbols for the methods defined by the class. Used to detect duplicate
// method definitions.
IntBuffer methods;
IntBuffer staticMethods;
// True if the class being compiled is a foreign class.
bool isForeign;
// True if the current method being compiled is static.
bool inStatic;
// The signature of the method being compiled.
Signature* signature;
} ClassInfo;
struct sCompiler
{
Parser* parser;
// The compiler for the function enclosing this one, or NULL if it's the
// top level.
struct sCompiler* parent;
// The currently in scope local variables.
Local locals[MAX_LOCALS];
// The number of local variables currently in scope.
int numLocals;
// The upvalues that this function has captured from outer scopes. The count
// of them is stored in [numUpvalues].
CompilerUpvalue upvalues[MAX_UPVALUES];
// 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 module-level.
int scopeDepth;
// The current number of slots (locals and temporaries) in use.
//
// We use this and maxSlots to track the maximum number of additional slots
// a function may need while executing. When the function is called, the
// fiber will check to ensure its stack has enough room to cover that worst
// case and grow the stack if needed.
//
// This value here doesn't include parameters to the function. Since those
// are already pushed onto the stack by the caller and tracked there, we
// don't need to double count them here.
int numSlots;
// The current innermost loop being compiled, or NULL if not in a loop.
Loop* loop;
// If this is a compiler for a method, keeps track of the class enclosing it.
ClassInfo* enclosingClass;
// The function being compiled.
ObjFn* fn;
// The constants for the function being compiled.
ObjMap* constants;
// Whether or not the compiler is for a constructor initializer
bool isInitializer;
// The number of attributes seen while parsing.
// We track this separately as compile time attributes
// are not stored, so we can't rely on attributes->count
// to enforce an error message when attributes are used
// anywhere other than methods or classes.
int numAttributes;
// Attributes for the next class or method.
ObjMap* attributes;
};
// Describes where a variable is declared.
typedef enum
{
// A local variable in the current function.
SCOPE_LOCAL,
// A local variable declared in an enclosing function.
SCOPE_UPVALUE,
// A top-level module variable.
SCOPE_MODULE
} Scope;
// A reference to a variable and the scope where it is defined. This contains
// enough information to emit correct code to load or store the variable.
typedef struct
{
// The stack slot, upvalue slot, or module symbol defining the variable.
int index;
// Where the variable is declared.
Scope scope;
} Variable;
// Forward declarations
static void disallowAttributes(Compiler* compiler);
static void addToAttributeGroup(Compiler* compiler, Value group, Value key, Value value);
static void emitClassAttributes(Compiler* compiler, ClassInfo* classInfo);
static void copyAttributes(Compiler* compiler, ObjMap* into);
static void copyMethodAttributes(Compiler* compiler, bool isForeign,
bool isStatic, const char* fullSignature, int32_t length);
// The stack effect of each opcode. The index in the array is the opcode, and
// the value is the stack effect of that instruction.
static const int stackEffects[] = {
#define OPCODE(_, effect) effect,
// Begin file "wren_opcodes.h"
// This defines the bytecode instructions used by the VM. It does so by invoking
// an OPCODE() macro which is expected to be defined at the point that this is
// included. (See: http://en.wikipedia.org/wiki/X_Macro for more.)
//
// The first argument is the name of the opcode. The second is its "stack
// effect" -- the amount that the op code changes the size of the stack. A
// stack effect of 1 means it pushes a value and the stack grows one larger.
// -2 means it pops two values, etc.
//
// Note that the order of instructions here affects the order of the dispatch
// table in the VM's interpreter loop. That in turn affects caching which
// affects overall performance. Take care to run benchmarks if you change the
// order here.
// Load the constant at index [arg].
OPCODE(CONSTANT, 1)
// Push null onto the stack.
OPCODE(NULL, 1)
// Push false onto the stack.
OPCODE(FALSE, 1)
// Push true onto the stack.
OPCODE(TRUE, 1)
// Pushes the value in the given local slot.
OPCODE(LOAD_LOCAL_0, 1)
OPCODE(LOAD_LOCAL_1, 1)
OPCODE(LOAD_LOCAL_2, 1)
OPCODE(LOAD_LOCAL_3, 1)
OPCODE(LOAD_LOCAL_4, 1)
OPCODE(LOAD_LOCAL_5, 1)
OPCODE(LOAD_LOCAL_6, 1)
OPCODE(LOAD_LOCAL_7, 1)
OPCODE(LOAD_LOCAL_8, 1)
// Note: The compiler assumes the following _STORE instructions always
// immediately follow their corresponding _LOAD ones.
// Pushes the value in local slot [arg].
OPCODE(LOAD_LOCAL, 1)
// Stores the top of stack in local slot [arg]. Does not pop it.
OPCODE(STORE_LOCAL, 0)
// Pushes the value in upvalue [arg].
OPCODE(LOAD_UPVALUE, 1)
// Stores the top of stack in upvalue [arg]. Does not pop it.
OPCODE(STORE_UPVALUE, 0)
// Pushes the value of the top-level variable in slot [arg].
OPCODE(LOAD_MODULE_VAR, 1)
// Stores the top of stack in top-level variable slot [arg]. Does not pop it.
OPCODE(STORE_MODULE_VAR, 0)
// Pushes the value of the field in slot [arg] of the receiver of the current
// function. This is used for regular field accesses on "this" directly in
// methods. This instruction is faster than the more general CODE_LOAD_FIELD
// instruction.
OPCODE(LOAD_FIELD_THIS, 1)
// Stores the top of the stack in field slot [arg] in the receiver of the
// current value. Does not pop the value. This instruction is faster than the
// more general CODE_LOAD_FIELD instruction.
OPCODE(STORE_FIELD_THIS, 0)
// Pops an instance and pushes the value of the field in slot [arg] of it.
OPCODE(LOAD_FIELD, 0)
// Pops an instance and stores the subsequent top of stack in field slot
// [arg] in it. Does not pop the value.
OPCODE(STORE_FIELD, -1)
// Pop and discard the top of stack.
OPCODE(POP, -1)
// Invoke the method with symbol [arg]. The number indicates the number of
// arguments (not including the receiver).
OPCODE(CALL_0, 0)
OPCODE(CALL_1, -1)
OPCODE(CALL_2, -2)
OPCODE(CALL_3, -3)
OPCODE(CALL_4, -4)
OPCODE(CALL_5, -5)
OPCODE(CALL_6, -6)
OPCODE(CALL_7, -7)
OPCODE(CALL_8, -8)
OPCODE(CALL_9, -9)
OPCODE(CALL_10, -10)
OPCODE(CALL_11, -11)
OPCODE(CALL_12, -12)
OPCODE(CALL_13, -13)
OPCODE(CALL_14, -14)
OPCODE(CALL_15, -15)
OPCODE(CALL_16, -16)
// Invoke a superclass method with symbol [arg]. The number indicates the
// number of arguments (not including the receiver).
OPCODE(SUPER_0, 0)
OPCODE(SUPER_1, -1)
OPCODE(SUPER_2, -2)
OPCODE(SUPER_3, -3)
OPCODE(SUPER_4, -4)
OPCODE(SUPER_5, -5)
OPCODE(SUPER_6, -6)
OPCODE(SUPER_7, -7)
OPCODE(SUPER_8, -8)
OPCODE(SUPER_9, -9)
OPCODE(SUPER_10, -10)
OPCODE(SUPER_11, -11)
OPCODE(SUPER_12, -12)
OPCODE(SUPER_13, -13)
OPCODE(SUPER_14, -14)
OPCODE(SUPER_15, -15)
OPCODE(SUPER_16, -16)
// Jump the instruction pointer [arg] forward.
OPCODE(JUMP, 0)
// Jump the instruction pointer [arg] backward.
OPCODE(LOOP, 0)
// Pop and if not truthy then jump the instruction pointer [arg] forward.
OPCODE(JUMP_IF, -1)
// If the top of the stack is false, jump [arg] forward. Otherwise, pop and
// continue.
OPCODE(AND, -1)
// If the top of the stack is non-false, jump [arg] forward. Otherwise, pop
// and continue.
OPCODE(OR, -1)
// Close the upvalue for the local on the top of the stack, then pop it.
OPCODE(CLOSE_UPVALUE, -1)
// Exit from the current function and return the value on the top of the
// stack.
OPCODE(RETURN, 0)
// Creates a closure for the function stored at [arg] in the constant table.
//
// Following the function argument is a number of arguments, two for each
// upvalue. The first is true if the variable being captured is a local (as
// opposed to an upvalue), and the second is the index of the local or
// upvalue being captured.
//
// Pushes the created closure.
OPCODE(CLOSURE, 1)
// Creates a new instance of a class.
//
// Assumes the class object is in slot zero, and replaces it with the new
// uninitialized instance of that class. This opcode is only emitted by the
// compiler-generated constructor metaclass methods.
OPCODE(CONSTRUCT, 0)
// Creates a new instance of a foreign class.
//
// Assumes the class object is in slot zero, and replaces it with the new
// uninitialized instance of that class. This opcode is only emitted by the
// compiler-generated constructor metaclass methods.
OPCODE(FOREIGN_CONSTRUCT, 0)
// Creates a class. Top of stack is the superclass. Below that is a string for
// the name of the class. Byte [arg] is the number of fields in the class.
OPCODE(CLASS, -1)
// Ends a class.
// Atm the stack contains the class and the ClassAttributes (or null).
OPCODE(END_CLASS, -2)
// Creates a foreign class. Top of stack is the superclass. Below that is a
// string for the name of the class.
OPCODE(FOREIGN_CLASS, -1)
// Define a method for symbol [arg]. The class receiving the method is popped
// off the stack, then the function defining the body is popped.
//
// If a foreign method is being defined, the "function" will be a string
// identifying the foreign method. Otherwise, it will be a function or
// closure.
OPCODE(METHOD_INSTANCE, -2)
// Define a method for symbol [arg]. The class whose metaclass will receive
// the method is popped off the stack, then the function defining the body is
// popped.
//
// If a foreign method is being defined, the "function" will be a string
// identifying the foreign method. Otherwise, it will be a function or
// closure.
OPCODE(METHOD_STATIC, -2)
// This is executed at the end of the module's body. Pushes NULL onto the stack
// as the "return value" of the import statement and stores the module as the
// most recently imported one.
OPCODE(END_MODULE, 1)
// Import a module whose name is the string stored at [arg] in the constant
// table.
//
// Pushes null onto the stack so that the fiber for the imported module can
// replace that with a dummy value when it returns. (Fibers always return a
// value when resuming a caller.)
OPCODE(IMPORT_MODULE, 1)
// Import a variable from the most recently imported module. The name of the
// variable to import is at [arg] in the constant table. Pushes the loaded
// variable's value.
OPCODE(IMPORT_VARIABLE, 1)
// This pseudo-instruction indicates the end of the bytecode. It should
// always be preceded by a `CODE_RETURN`, so is never actually executed.
OPCODE(END, 0)
// End file "wren_opcodes.h"
#undef OPCODE
};
static void printError(Parser* parser, int line, const char* label,
const char* format, va_list args)
{
parser->hasError = true;
if (!parser->printErrors) return;
// Only report errors if there is a WrenErrorFn to handle them.
if (parser->vm->config.errorFn == NULL) return;
// Format the label and message.
char message[ERROR_MESSAGE_SIZE];
int length = sprintf(message, "%s: ", label);
length += vsprintf(message + length, format, args);
ASSERT(length < ERROR_MESSAGE_SIZE, "Error should not exceed buffer.");
ObjString* module = parser->module->name;
const char* module_name = module ? module->value : "<unknown>";
parser->vm->config.errorFn(parser->vm, WREN_ERROR_COMPILE,
module_name, line, message);
}
// Outputs a lexical error.
static void lexError(Parser* parser, const char* format, ...)
{
va_list args;
va_start(args, format);
printError(parser, parser->currentLine, "Error", format, args);
va_end(args);
}
// Outputs a compile or syntax error. This also marks the compilation as having
// an error, which ensures that the resulting code will be discarded and never
// run. This means that after calling error(), it's fine to generate whatever
// invalid bytecode you want since it won't be used.
//
// You'll note that most places that call error() continue to parse and compile
// after that. That's so that we can try to find as many compilation errors in
// one pass as possible instead of just bailing at the first one.
static void error(Compiler* compiler, const char* format, ...)
{
Token* token = &compiler->parser->previous;
// If the parse error was caused by an error token, the lexer has already
// reported it.
if (token->type == TOKEN_ERROR) return;
va_list args;
va_start(args, format);
if (token->type == TOKEN_LINE)
{
printError(compiler->parser, token->line, "Error at newline", format, args);
}
else if (token->type == TOKEN_EOF)
{
printError(compiler->parser, token->line,
"Error at end of file", format, args);
}
else
{
// Make sure we don't exceed the buffer with a very long token.
char label[10 + MAX_VARIABLE_NAME + 4 + 1];
if (token->length <= MAX_VARIABLE_NAME)
{
sprintf(label, "Error at '%.*s'", token->length, token->start);
}
else
{
sprintf(label, "Error at '%.*s...'", MAX_VARIABLE_NAME, token->start);
}
printError(compiler->parser, token->line, label, format, args);
}
va_end(args);
}
// Adds [constant] to the constant pool and returns its index.
static int addConstant(Compiler* compiler, Value constant)
{
if (compiler->parser->hasError) return -1;
// See if we already have a constant for the value. If so, reuse it.
if (compiler->constants != NULL)
{
Value existing = wrenMapGet(compiler->constants, constant);
if (IS_NUM(existing)) return (int)AS_NUM(existing);
}
// It's a new constant.
if (compiler->fn->constants.count < MAX_CONSTANTS)
{
if (IS_OBJ(constant)) wrenPushRoot(compiler->parser->vm, AS_OBJ(constant));
wrenValueBufferWrite(compiler->parser->vm, &compiler->fn->constants,
constant);
if (IS_OBJ(constant)) wrenPopRoot(compiler->parser->vm);
if (compiler->constants == NULL)
{
compiler->constants = wrenNewMap(compiler->parser->vm);
}
wrenMapSet(compiler->parser->vm, compiler->constants, constant,
NUM_VAL(compiler->fn->constants.count - 1));
}
else
{
error(compiler, "A function may only contain %d unique constants.",
MAX_CONSTANTS);
}
return compiler->fn->constants.count - 1;
}
// Initializes [compiler].
static void initCompiler(Compiler* compiler, Parser* parser, Compiler* parent,
bool isMethod)
{
compiler->parser = parser;
compiler->parent = parent;
compiler->loop = NULL;
compiler->enclosingClass = NULL;
compiler->isInitializer = false;
// Initialize these to NULL before allocating in case a GC gets triggered in
// the middle of initializing the compiler.
compiler->fn = NULL;
compiler->constants = NULL;
compiler->attributes = NULL;
parser->vm->compiler = compiler;
// Declare a local slot for either the closure or method receiver so that we
// don't try to reuse that slot for a user-defined local variable. For
// methods, we name it "this", so that we can resolve references to that like
// a normal variable. For functions, they have no explicit "this", so we use
// an empty name. That way references to "this" inside a function walks up
// the parent chain to find a method enclosing the function whose "this" we
// can close over.
compiler->numLocals = 1;
compiler->numSlots = compiler->numLocals;
if (isMethod)
{
compiler->locals[0].name = "this";
compiler->locals[0].length = 4;
}
else
{
compiler->locals[0].name = NULL;
compiler->locals[0].length = 0;
}
compiler->locals[0].depth = -1;
compiler->locals[0].isUpvalue = false;
if (parent == NULL)
{
// Compiling top-level code, so the initial scope is module-level.
compiler->scopeDepth = -1;
}
else
{
// The initial scope for functions and methods is local scope.
compiler->scopeDepth = 0;
}
compiler->numAttributes = 0;
compiler->attributes = wrenNewMap(parser->vm);
compiler->fn = wrenNewFunction(parser->vm, parser->module,
compiler->numLocals);
}
// Lexing ----------------------------------------------------------------------
typedef struct
{
const char* identifier;
size_t length;
TokenType tokenType;
} Keyword;
// The table of reserved words and their associated token types.
static Keyword keywords[] =
{
{"break", 5, TOKEN_BREAK},
{"continue", 8, TOKEN_CONTINUE},
{"class", 5, TOKEN_CLASS},
{"construct", 9, TOKEN_CONSTRUCT},
{"else", 4, TOKEN_ELSE},
{"false", 5, TOKEN_FALSE},
{"for", 3, TOKEN_FOR},
{"foreign", 7, TOKEN_FOREIGN},
{"if", 2, TOKEN_IF},
{"import", 6, TOKEN_IMPORT},
{"as", 2, TOKEN_AS},
{"in", 2, TOKEN_IN},
{"is", 2, TOKEN_IS},
{"null", 4, TOKEN_NULL},
{"return", 6, TOKEN_RETURN},
{"static", 6, TOKEN_STATIC},
{"super", 5, TOKEN_SUPER},
{"this", 4, TOKEN_THIS},
{"true", 4, TOKEN_TRUE},
{"var", 3, TOKEN_VAR},
{"while", 5, TOKEN_WHILE},
{NULL, 0, TOKEN_EOF} // Sentinel to mark the end of the array.
};
// Returns true if [c] is a valid (non-initial) identifier character.
static bool isName(char c)
{
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_';
}
// Returns true if [c] is a digit.
static bool isDigit(char c)
{
return c >= '0' && c <= '9';
}
// Returns the current character the parser is sitting on.
static char peekChar(Parser* parser)
{
return *parser->currentChar;
}
// 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';
return *(parser->currentChar + 1);
}
// Advances the parser forward one character.
static char nextChar(Parser* parser)
{
char c = peekChar(parser);
parser->currentChar++;
if (c == '\n') parser->currentLine++;
return c;
}
// If the current character is [c], consumes it and returns `true`.
static bool matchChar(Parser* parser, char c)
{
if (peekChar(parser) != c) return false;
nextChar(parser);
return true;
}
// Sets the parser's current token to the given [type] and current character
// range.
static void makeToken(Parser* parser, TokenType type)
{
parser->next.type = type;
parser->next.start = parser->tokenStart;
parser->next.length = (int)(parser->currentChar - parser->tokenStart);
parser->next.line = parser->currentLine;
// Make line tokens appear on the line containing the "\n".
if (type == TOKEN_LINE) parser->next.line--;
}
// 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)
{
makeToken(parser, matchChar(parser, c) ? two : one);
}
// Skips the rest of the current line.
static void skipLineComment(Parser* parser)
{
while (peekChar(parser) != '\n' && peekChar(parser) != '\0')
{
nextChar(parser);
}
}
// Skips the rest of a block comment.
static void skipBlockComment(Parser* parser)
{
int nesting = 1;
while (nesting > 0)
{
if (peekChar(parser) == '\0')
{
lexError(parser, "Unterminated block comment.");
return;
}
if (peekChar(parser) == '/' && peekNextChar(parser) == '*')
{
nextChar(parser);
nextChar(parser);
nesting++;
continue;
}
if (peekChar(parser) == '*' && peekNextChar(parser) == '/')
{
nextChar(parser);
nextChar(parser);
nesting--;
continue;
}
// Regular comment character.
nextChar(parser);
}
}
// Reads the next character, which should be a hex digit (0-9, a-f, or A-F) and
// returns its numeric value. If the character isn't a hex digit, returns -1.
static int readHexDigit(Parser* parser)
{
char c = nextChar(parser);
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
// Don't consume it if it isn't expected. Keeps us from reading past the end
// of an unterminated string.
parser->currentChar--;
return -1;
}
// Parses the numeric value of the current token.
static void makeNumber(Parser* parser, bool isHex)
{
errno = 0;
if (isHex)
{
parser->next.value = NUM_VAL((double)strtoll(parser->tokenStart, NULL, 16));
}
else
{
parser->next.value = NUM_VAL(strtod(parser->tokenStart, NULL));
}
if (errno == ERANGE)
{
lexError(parser, "Number literal was too large (%d).", sizeof(long int));
parser->next.value = NUM_VAL(0);
}
// We don't check that the entire token is consumed after calling strtoll()
// or strtod() because we've already scanned it ourselves and know it's valid.
makeToken(parser, TOKEN_NUMBER);
}
// Finishes lexing a hexadecimal number literal.
static void readHexNumber(Parser* parser)
{
// Skip past the `x` used to denote a hexadecimal literal.
nextChar(parser);
// Iterate over all the valid hexadecimal digits found.
while (readHexDigit(parser) != -1) continue;
makeNumber(parser, true);
}
// Finishes lexing a number literal.
static void readNumber(Parser* parser)
{
while (isDigit(peekChar(parser))) nextChar(parser);
// 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);
}
// See if the number is in scientific notation.
if (matchChar(parser, 'e') || matchChar(parser, 'E'))
{
// Allow a single positive/negative exponent symbol.
if(!matchChar(parser, '+'))
{
matchChar(parser, '-');
}
if (!isDigit(peekChar(parser)))
{
lexError(parser, "Unterminated scientific notation.");
}
while (isDigit(peekChar(parser))) nextChar(parser);
}
makeNumber(parser, false);
}
// Finishes lexing an identifier. Handles reserved words.
static void readName(Parser* parser, TokenType type, char firstChar)
{
ByteBuffer string;
wrenByteBufferInit(&string);
wrenByteBufferWrite(parser->vm, &string, firstChar);
while (isName(peekChar(parser)) || isDigit(peekChar(parser)))
{
char c = nextChar(parser);
wrenByteBufferWrite(parser->vm, &string, c);
}
// Update the type if it's a keyword.
size_t length = parser->currentChar - parser->tokenStart;
for (int i = 0; keywords[i].identifier != NULL; i++)
{
if (length == keywords[i].length &&
memcmp(parser->tokenStart, keywords[i].identifier, length) == 0)
{
type = keywords[i].tokenType;
break;
}
}
parser->next.value = wrenNewStringLength(parser->vm,
(char*)string.data, string.count);
wrenByteBufferClear(parser->vm, &string);
makeToken(parser, type);
}
// Reads [digits] hex digits in a string literal and returns their number value.
static int readHexEscape(Parser* parser, int digits, const char* description)
{
int value = 0;
for (int i = 0; i < digits; i++)
{
if (peekChar(parser) == '"' || peekChar(parser) == '\0')
{
lexError(parser, "Incomplete %s escape sequence.", description);
// Don't consume it if it isn't expected. Keeps us from reading past the
// end of an unterminated string.
parser->currentChar--;
break;
}
int digit = readHexDigit(parser);
if (digit == -1)
{
lexError(parser, "Invalid %s escape sequence.", description);
break;
}
value = (value * 16) | digit;
}
return value;
}
// Reads a hex digit Unicode escape sequence in a string literal.
static void readUnicodeEscape(Parser* parser, ByteBuffer* string, int length)
{
int value = readHexEscape(parser, length, "Unicode");
// Grow the buffer enough for the encoded result.
int numBytes = wrenUtf8EncodeNumBytes(value);
if (numBytes != 0)
{
wrenByteBufferFill(parser->vm, string, 0, numBytes);
wrenUtf8Encode(value, string->data + string->count - numBytes);
}
}
static void readRawString(Parser* parser)
{
ByteBuffer string;
wrenByteBufferInit(&string);
TokenType type = TOKEN_STRING;
//consume the second and third "
nextChar(parser);
nextChar(parser);
int skipStart = 0;
int firstNewline = -1;
int skipEnd = -1;
int lastNewline = -1;
for (;;)
{
char c = nextChar(parser);
char c1 = peekChar(parser);
char c2 = peekNextChar(parser);
if (c == '\r') continue;
if (c == '\n') {
lastNewline = string.count;
skipEnd = lastNewline;
firstNewline = firstNewline == -1 ? string.count : firstNewline;
}
if (c == '"' && c1 == '"' && c2 == '"') break;
bool isWhitespace = c == ' ' || c == '\t';
skipEnd = c == '\n' || isWhitespace ? skipEnd : -1;
// If we haven't seen a newline or other character yet,
// and still seeing whitespace, count the characters
// as skippable till we know otherwise
bool skippable = skipStart != -1 && isWhitespace && firstNewline == -1;
skipStart = skippable ? string.count + 1 : skipStart;
// We've counted leading whitespace till we hit something else,
// but it's not a newline, so we reset skipStart since we need these characters
if (firstNewline == -1 && !isWhitespace && c != '\n') skipStart = -1;
if (c == '\0' || c1 == '\0' || c2 == '\0')
{
lexError(parser, "Unterminated raw string.");
// Don't consume it if it isn't expected. Keeps us from reading past the
// end of an unterminated string.
parser->currentChar--;
break;
}
wrenByteBufferWrite(parser->vm, &string, c);
}
//consume the second and third "
nextChar(parser);
nextChar(parser);
int offset = 0;
int count = string.count;
if(firstNewline != -1 && skipStart == firstNewline) offset = firstNewline + 1;
if(lastNewline != -1 && skipEnd == lastNewline) count = lastNewline;
count -= (offset > count) ? count : offset;
parser->next.value = wrenNewStringLength(parser->vm,
((char*)string.data) + offset, count);
wrenByteBufferClear(parser->vm, &string);
makeToken(parser, type);
}
// Finishes lexing a string literal.
static void readString(Parser* parser)
{
ByteBuffer string;
TokenType type = TOKEN_STRING;
wrenByteBufferInit(&string);
for (;;)
{
char c = nextChar(parser);
if (c == '"') break;
if (c == '\r') continue;
if (c == '\0')
{
lexError(parser, "Unterminated string.");
// Don't consume it if it isn't expected. Keeps us from reading past the
// end of an unterminated string.
parser->currentChar--;
break;
}
if (c == '%')
{
if (parser->numParens < MAX_INTERPOLATION_NESTING)
{
// TODO: Allow format string.
if (nextChar(parser) != '(') lexError(parser, "Expect '(' after '%%'.");
parser->parens[parser->numParens++] = 1;
type = TOKEN_INTERPOLATION;
break;
}
lexError(parser, "Interpolation may only nest %d levels deep.",
MAX_INTERPOLATION_NESTING);
}
if (c == '\\')
{
switch (nextChar(parser))
{
case '"': wrenByteBufferWrite(parser->vm, &string, '"'); break;
case '\\': wrenByteBufferWrite(parser->vm, &string, '\\'); break;
case '%': wrenByteBufferWrite(parser->vm, &string, '%'); break;
case '0': wrenByteBufferWrite(parser->vm, &string, '\0'); break;
case 'a': wrenByteBufferWrite(parser->vm, &string, '\a'); break;
case 'b': wrenByteBufferWrite(parser->vm, &string, '\b'); break;
case 'e': wrenByteBufferWrite(parser->vm, &string, '\33'); break;
case 'f': wrenByteBufferWrite(parser->vm, &string, '\f'); break;
case 'n': wrenByteBufferWrite(parser->vm, &string, '\n'); break;
case 'r': wrenByteBufferWrite(parser->vm, &string, '\r'); break;
case 't': wrenByteBufferWrite(parser->vm, &string, '\t'); break;
case 'u': readUnicodeEscape(parser, &string, 4); break;
case 'U': readUnicodeEscape(parser, &string, 8); break;
case 'v': wrenByteBufferWrite(parser->vm, &string, '\v'); break;
case 'x':
wrenByteBufferWrite(parser->vm, &string,
(uint8_t)readHexEscape(parser, 2, "byte"));
break;
default:
lexError(parser, "Invalid escape character '%c'.",
*(parser->currentChar - 1));
break;
}
}
else
{
wrenByteBufferWrite(parser->vm, &string, c);
}
}
parser->next.value = wrenNewStringLength(parser->vm,
(char*)string.data, string.count);
wrenByteBufferClear(parser->vm, &string);
makeToken(parser, type);
}
// Lex the next token and store it in [parser.next].
static void nextToken(Parser* parser)
{
parser->previous = parser->current;
parser->current = parser->next;
// 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->next.type == TOKEN_EOF) return;
if (parser->current.type == TOKEN_EOF) return;
while (peekChar(parser) != '\0')
{
parser->tokenStart = parser->currentChar;
char c = nextChar(parser);
switch (c)
{
case '(':
// If we are inside an interpolated expression, count the unmatched "(".
if (parser->numParens > 0) parser->parens[parser->numParens - 1]++;
makeToken(parser, TOKEN_LEFT_PAREN);
return;
case ')':
// If we are inside an interpolated expression, count the ")".
if (parser->numParens > 0 &&
--parser->parens[parser->numParens - 1] == 0)
{
// This is the final ")", so the interpolation expression has ended.
// This ")" now begins the next section of the template string.
parser->numParens--;
readString(parser);
return;
}
makeToken(parser, TOKEN_RIGHT_PAREN);
return;
case '[': makeToken(parser, TOKEN_LEFT_BRACKET); return;
case ']': makeToken(parser, TOKEN_RIGHT_BRACKET); return;
case '{': makeToken(parser, TOKEN_LEFT_BRACE); return;
case '}': makeToken(parser, TOKEN_RIGHT_BRACE); return;
case ':': makeToken(parser, TOKEN_COLON); return;
case ',': makeToken(parser, TOKEN_COMMA); return;
case '*': makeToken(parser, TOKEN_STAR); return;
case '%': makeToken(parser, TOKEN_PERCENT); return;
case '#': {
// Ignore shebang on the first line.
if (parser->currentLine == 1 && peekChar(parser) == '!' && peekNextChar(parser) == '/')
{
skipLineComment(parser);
break;
}
// Otherwise we treat it as a token
makeToken(parser, TOKEN_HASH);
return;
}
case '^': makeToken(parser, TOKEN_CARET); return;
case '+': makeToken(parser, TOKEN_PLUS); return;
case '-': makeToken(parser, TOKEN_MINUS); return;
case '~': makeToken(parser, TOKEN_TILDE); return;
case '?': makeToken(parser, TOKEN_QUESTION); return;
case '|': twoCharToken(parser, '|', TOKEN_PIPEPIPE, TOKEN_PIPE); return;
case '&': twoCharToken(parser, '&', TOKEN_AMPAMP, TOKEN_AMP); return;
case '=': twoCharToken(parser, '=', TOKEN_EQEQ, TOKEN_EQ); return;
case '!': twoCharToken(parser, '=', TOKEN_BANGEQ, TOKEN_BANG); return;
case '.':
if (matchChar(parser, '.'))
{
twoCharToken(parser, '.', TOKEN_DOTDOTDOT, TOKEN_DOTDOT);
return;
}
makeToken(parser, TOKEN_DOT);
return;
case '/':
if (matchChar(parser, '/'))
{
skipLineComment(parser);
break;
}
if (matchChar(parser, '*'))
{
skipBlockComment(parser);
break;
}
makeToken(parser, TOKEN_SLASH);
return;
case '<':
if (matchChar(parser, '<'))
{
makeToken(parser, TOKEN_LTLT);
}
else
{
twoCharToken(parser, '=', TOKEN_LTEQ, TOKEN_LT);
}
return;
case '>':
if (matchChar(parser, '>'))
{
makeToken(parser, TOKEN_GTGT);
}
else
{
twoCharToken(parser, '=', TOKEN_GTEQ, TOKEN_GT);
}
return;
case '\n':
makeToken(parser, TOKEN_LINE);
return;
case ' ':
case '\r':
case '\t':
// Skip forward until we run out of whitespace.
while (peekChar(parser) == ' ' ||
peekChar(parser) == '\r' ||
peekChar(parser) == '\t')
{
nextChar(parser);
}
break;
case '"': {
if(peekChar(parser) == '"' && peekNextChar(parser) == '"') {
readRawString(parser);
return;
}
readString(parser); return;
}
case '_':
readName(parser,
peekChar(parser) == '_' ? TOKEN_STATIC_FIELD : TOKEN_FIELD, c);
return;
case '0':
if (peekChar(parser) == 'x')
{
readHexNumber(parser);
return;
}
readNumber(parser);
return;
default:
if (isName(c))
{
readName(parser, TOKEN_NAME, c);
}
else if (isDigit(c))
{
readNumber(parser);
}
else
{
if (c >= 32 && c <= 126)
{
lexError(parser, "Invalid character '%c'.", c);
}
else
{
// Don't show non-ASCII values since we didn't UTF-8 decode the
// bytes. Since there are no non-ASCII byte values that are
// meaningful code units in Wren, the lexer works on raw bytes,
// even though the source code and console output are UTF-8.
lexError(parser, "Invalid byte 0x%x.", (uint8_t)c);
}
parser->next.type = TOKEN_ERROR;
parser->next.length = 0;
}
return;
}
}
// If we get here, we're out of source, so just make EOF tokens.
parser->tokenStart = parser->currentChar;
makeToken(parser, TOKEN_EOF);
}
// Parsing ---------------------------------------------------------------------
// Returns the type of the current token.
static TokenType peek(Compiler* compiler)
{
return compiler->parser->current.type;
}
// Returns the type of the current token.
static TokenType peekNext(Compiler* compiler)
{
return compiler->parser->next.type;
}
// Consumes the current token if its type is [expected]. Returns true if a
// token was consumed.
static bool match(Compiler* compiler, TokenType expected)
{
if (peek(compiler) != expected) return false;
nextToken(compiler->parser);
return true;
}
// Consumes the current token. Emits an error if its type is not [expected].
static void consume(Compiler* compiler, TokenType expected,
const char* errorMessage)
{
nextToken(compiler->parser);
if (compiler->parser->previous.type != expected)
{
error(compiler, errorMessage);
// If the next token is the one we want, assume the current one is just a
// spurious error and discard it to minimize the number of cascaded errors.
if (compiler->parser->current.type == expected) nextToken(compiler->parser);
}
}
// Matches one or more newlines. Returns true if at least one was found.
static bool matchLine(Compiler* compiler)
{
if (!match(compiler, TOKEN_LINE)) return false;
while (match(compiler, TOKEN_LINE));
return true;
}
// Discards any newlines starting at the current token.
static void ignoreNewlines(Compiler* compiler)
{
matchLine(compiler);
}
// Consumes the current token. Emits an error if it is not a newline. Then
// discards any duplicate newlines following it.
static void consumeLine(Compiler* compiler, const char* errorMessage)
{
consume(compiler, TOKEN_LINE, errorMessage);
ignoreNewlines(compiler);
}
static void allowLineBeforeDot(Compiler* compiler) {
if (peek(compiler) == TOKEN_LINE && peekNext(compiler) == TOKEN_DOT) {
nextToken(compiler->parser);
}
}
// Variables and scopes --------------------------------------------------------
// Emits one single-byte argument. Returns its index.
static int emitByte(Compiler* compiler, int byte)
{
wrenByteBufferWrite(compiler->parser->vm, &compiler->fn->code, (uint8_t)byte);
// Assume the instruction is associated with the most recently consumed token.
wrenIntBufferWrite(compiler->parser->vm, &compiler->fn->debug->sourceLines,
compiler->parser->previous.line);
return compiler->fn->code.count - 1;
}
// Emits one bytecode instruction.
static void emitOp(Compiler* compiler, Code instruction)
{
emitByte(compiler, instruction);
// Keep track of the stack's high water mark.
compiler->numSlots += stackEffects[instruction];
if (compiler->numSlots > compiler->fn->maxSlots)
{
compiler->fn->maxSlots = compiler->numSlots;
}
}
// Emits one 16-bit argument, which will be written big endian.
static void emitShort(Compiler* compiler, int arg)
{
emitByte(compiler, (arg >> 8) & 0xff);
emitByte(compiler, arg & 0xff);
}
// Emits one bytecode instruction followed by a 8-bit argument. Returns the
// index of the argument in the bytecode.
static int emitByteArg(Compiler* compiler, Code instruction, int arg)
{
emitOp(compiler, instruction);
return emitByte(compiler, arg);
}
// Emits one bytecode instruction followed by a 16-bit argument, which will be
// written big endian.
static void emitShortArg(Compiler* compiler, Code instruction, int arg)
{
emitOp(compiler, instruction);
emitShort(compiler, arg);
}
// Emits [instruction] followed by a placeholder for a jump offset. The
// placeholder can be patched by calling [jumpPatch]. Returns the index of the
// placeholder.
static int emitJump(Compiler* compiler, Code instruction)
{
emitOp(compiler, instruction);
emitByte(compiler, 0xff);
return emitByte(compiler, 0xff) - 1;
}
// Creates a new constant for the current value and emits the bytecode to load
// it from the constant table.
static void emitConstant(Compiler* compiler, Value value)
{
int constant = addConstant(compiler, value);
// Compile the code to load the constant.
emitShortArg(compiler, CODE_CONSTANT, constant);
}
// Create a new local variable with [name]. Assumes the current scope is local
// and the name is unique.
static int addLocal(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++;
}
// Declares a variable in the current scope whose name is the given token.
//
// If [token] is `NULL`, uses the previously consumed token. Returns its symbol.
static int declareVariable(Compiler* compiler, Token* token)
{
if (token == NULL) token = &compiler->parser->previous;
if (token->length > MAX_VARIABLE_NAME)
{
error(compiler, "Variable name cannot be longer than %d characters.",
MAX_VARIABLE_NAME);
}
// Top-level module scope.
if (compiler->scopeDepth == -1)
{
int line = -1;
int symbol = wrenDefineVariable(compiler->parser->vm,
compiler->parser->module,
token->start, token->length,
NULL_VAL, &line);
if (symbol == -1)
{
error(compiler, "Module variable is already defined.");
}
else if (symbol == -2)
{
error(compiler, "Too many module variables defined.");
}
else if (symbol == -3)
{
error(compiler,
"Variable '%.*s' referenced before this definition (first use at line %d).",
token->length, token->start, line);
}
return symbol;
}
// 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--)
{
Local* local = &compiler->locals[i];
// Once we escape this scope and hit an outer one, we can stop.
if (local->depth < compiler->scopeDepth) break;
if (local->length == token->length &&
memcmp(local->name, token->start, token->length) == 0)
{
error(compiler, "Variable is already declared in this scope.");
return i;
}
}
if (compiler->numLocals == MAX_LOCALS)
{
error(compiler, "Cannot declare more than %d variables in one scope.",
MAX_LOCALS);
return -1;
}
return addLocal(compiler, token->start, token->length);
}
// Parses a name token and declares a variable in the current scope with that
// name. Returns its slot.
static int declareNamedVariable(Compiler* compiler)
{
consume(compiler, TOKEN_NAME, "Expect variable name.");
return declareVariable(compiler, NULL);
}
// Stores a variable with the previously defined symbol in the current scope.
static void defineVariable(Compiler* compiler, int symbol)
{
// 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 module-level variable, so store the value in the module slot and
// then discard the temporary for the initializer.
emitShortArg(compiler, CODE_STORE_MODULE_VAR, symbol);
emitOp(compiler, CODE_POP);
}
// Starts a new local block scope.
static void pushScope(Compiler* compiler)
{
compiler->scopeDepth++;
}
// Generates code to discard local variables at [depth] or greater. Does *not*
// actually undeclare variables or pop any scopes, though. This is called
// directly when compiling "break" statements to ditch the local variables
// before jumping out of the loop even though they are still in scope *past*
// the break instruction.
//
// Returns the number of local variables that were eliminated.
static int discardLocals(Compiler* compiler, int depth)
{
ASSERT(compiler->scopeDepth > -1, "Cannot exit top-level scope.");
int local = compiler->numLocals - 1;
while (local >= 0 && compiler->locals[local].depth >= depth)
{
// If the local was closed over, make sure the upvalue gets closed when it
// goes out of scope on the stack. We use emitByte() and not emitOp() here
// because we don't want to track that stack effect of these pops since the
// variables are still in scope after the break.
if (compiler->locals[local].isUpvalue)
{
emitByte(compiler, CODE_CLOSE_UPVALUE);
}
else
{
emitByte(compiler, CODE_POP);
}
local--;
}
return compiler->numLocals - local - 1;
}
// Closes the last pushed block scope and discards any local variables declared
// in that scope. This should only be called in a statement context where no
// temporaries are still on the stack.
static void popScope(Compiler* compiler)
{
int popped = discardLocals(compiler, compiler->scopeDepth);
compiler->numLocals -= popped;
compiler->numSlots -= popped;
compiler->scopeDepth--;
}
// 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)
{
// 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--)
{
if (compiler->locals[i].length == length &&
memcmp(name, compiler->locals[i].name, length) == 0)
{
return i;
}
}
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 upvalue.
static int addUpvalue(Compiler* compiler, bool isLocal, int index)
{
// Look for an existing one.
for (int i = 0; i < compiler->fn->numUpvalues; i++)
{
CompilerUpvalue* upvalue = &compiler->upvalues[i];
if (upvalue->index == index && upvalue->isLocal == isLocal) return i;
}
// If we got here, it's a new upvalue.
compiler->upvalues[compiler->fn->numUpvalues].isLocal = isLocal;
compiler->upvalues[compiler->fn->numUpvalues].index = index;
return compiler->fn->numUpvalues++;
}
// Attempts to look up [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.
//
// 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.
//
// If it reaches a method boundary, this stops and returns -1 since methods do
// not close over local variables.
static int findUpvalue(Compiler* compiler, const char* name, int length)
{
// If we are at the top level, we didn't find it.
if (compiler->parent == NULL) return -1;
// If we hit the method boundary (and the name isn't a static field), then
// stop looking for it. We'll instead treat it as a self send.
if (name[0] != '_' && compiler->parent->enclosingClass != NULL) return -1;
// See if it's a local variable in the immediately enclosing function.
int local = resolveLocal(compiler->parent, name, length);
if (local != -1)
{
// Mark the local as an upvalue so we know to close it when it goes out of
// scope.
compiler->parent->locals[local].isUpvalue = true;
return addUpvalue(compiler, true, local);
}
// See if it's an upvalue in the immediately enclosing function. In other
// words, if it's a local variable in a non-immediately enclosing function.
// This "flattens" closures automatically: it adds upvalues to all of the
// intermediate functions to get from the function where a local is declared
// all the way into the possibly deeply nested function that is closing over
// it.
int upvalue = findUpvalue(compiler->parent, name, length);
if (upvalue != -1)
{
return addUpvalue(compiler, false, upvalue);
}
// If we got here, we walked all the way up the parent chain and couldn't
// find it.
return -1;
}
// Look up [name] in the current scope to see what variable it refers to.
// Returns the variable either in local scope, or the enclosing function's
// upvalue list. Does not search the module scope. Returns a variable with
// index -1 if not found.
static Variable resolveNonmodule(Compiler* compiler,
const char* name, int length)
{
// Look it up in the local scopes.
Variable variable;
variable.scope = SCOPE_LOCAL;
variable.index = resolveLocal(compiler, name, length);
if (variable.index != -1) return variable;
// Tt's not a local, so guess that it's an upvalue.
variable.scope = SCOPE_UPVALUE;
variable.index = findUpvalue(compiler, name, length);
return variable;
}
// Look up [name] in the current scope to see what variable it refers to.
// Returns the variable either in module scope, local scope, or the enclosing
// function's upvalue list. Returns a variable with index -1 if not found.
static Variable resolveName(Compiler* compiler, const char* name, int length)
{
Variable variable = resolveNonmodule(compiler, name, length);
if (variable.index != -1) return variable;
variable.scope = SCOPE_MODULE;
variable.index = wrenSymbolTableFind(&compiler->parser->module->variableNames,
name, length);
return variable;
}
static void loadLocal(Compiler* compiler, int slot)
{
if (slot <= 8)
{
emitOp(compiler, (Code)(CODE_LOAD_LOCAL_0 + slot));
return;
}
emitByteArg(compiler, CODE_LOAD_LOCAL, slot);
}
// 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 ObjFn* endCompiler(Compiler* compiler,
const char* debugName, int debugNameLength)
{
// If we hit an error, don't finish the function since it's borked anyway.
if (compiler->parser->hasError)
{
compiler->parser->vm->compiler = compiler->parent;
return NULL;
}
// 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.
emitOp(compiler, CODE_END);
wrenFunctionBindName(compiler->parser->vm, compiler->fn,
debugName, debugNameLength);
// In the function that contains this one, load the resulting function object.
if (compiler->parent != NULL)
{
int constant = addConstant(compiler->parent, OBJ_VAL(compiler->fn));
// Wrap the function in a closure. We do this even if it has no upvalues so
// that the VM can uniformly assume all called objects are closures. This
// makes creating a function a little slower, but makes invoking them
// faster. Given that functions are invoked more often than they are
// created, this is a win.
emitShortArg(compiler->parent, CODE_CLOSURE, constant);
// Emit arguments for each upvalue to know whether to capture a local or
// an upvalue.
for (int i = 0; i < compiler->fn->numUpvalues; i++)
{
emitByte(compiler->parent, compiler->upvalues[i].isLocal ? 1 : 0);
emitByte(compiler->parent, compiler->upvalues[i].index);
}
}
// Pop this compiler off the stack.
compiler->parser->vm->compiler = compiler->parent;
#if WREN_DEBUG_DUMP_COMPILED_CODE
wrenDumpCode(compiler->parser->vm, compiler->fn);
#endif
return compiler->fn;
}
// Grammar ---------------------------------------------------------------------
typedef enum
{
PREC_NONE,
PREC_LOWEST,
PREC_ASSIGNMENT, // =
PREC_CONDITIONAL, // ?:
PREC_LOGICAL_OR, // ||
PREC_LOGICAL_AND, // &&
PREC_EQUALITY, // == !=
PREC_IS, // is
PREC_COMPARISON, // < > <= >=
PREC_BITWISE_OR, // |
PREC_BITWISE_XOR, // ^
PREC_BITWISE_AND, // &
PREC_BITWISE_SHIFT, // << >>
PREC_RANGE, // .. ...
PREC_TERM, // + -
PREC_FACTOR, // * / %
PREC_UNARY, // unary - ! ~
PREC_CALL, // . () []
PREC_PRIMARY
} Precedence;
typedef void (*GrammarFn)(Compiler*, bool canAssign);
typedef void (*SignatureFn)(Compiler* compiler, Signature* signature);
typedef struct
{
GrammarFn prefix;
GrammarFn infix;
SignatureFn method;
Precedence precedence;
const char* name;
} GrammarRule;
// Forward declarations since the grammar is recursive.
static GrammarRule* getRule(TokenType type);
static void expression(Compiler* compiler);
static void statement(Compiler* compiler);
static void definition(Compiler* compiler);
static void parsePrecedence(Compiler* compiler, Precedence precedence);
// 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)
{
// -2 to adjust for the bytecode for the jump offset itself.
int jump = compiler->fn->code.count - offset - 2;
if (jump > MAX_JUMP) error(compiler, "Too much code to jump over.");
compiler->fn->code.data[offset] = (jump >> 8) & 0xff;
compiler->fn->code.data[offset + 1] = jump & 0xff;
}
// Parses a block body, after the initial "{" has been consumed.
//
// Returns true if it was a expression body, false if it was a statement body.
// (More precisely, returns true if a value was left on the stack. An empty
// block returns false.)
static bool finishBlock(Compiler* compiler)
{
// Empty blocks do nothing.
if (match(compiler, TOKEN_RIGHT_BRACE)) return false;
// If there's no line after the "{", it's a single-expression body.
if (!matchLine(compiler))
{
expression(compiler);
consume(compiler, TOKEN_RIGHT_BRACE, "Expect '}' at end of block.");
return true;
}
// Empty blocks (with just a newline inside) do nothing.
if (match(compiler, TOKEN_RIGHT_BRACE)) return false;
// Compile the definition list.
do
{
definition(compiler);
consumeLine(compiler, "Expect newline after statement.");
}
while (peek(compiler) != TOKEN_RIGHT_BRACE && peek(compiler) != TOKEN_EOF);
consume(compiler, TOKEN_RIGHT_BRACE, "Expect '}' at end of block.");
return false;
}
// Parses a method or function body, after the initial "{" has been consumed.
//
// If [Compiler->isInitializer] is `true`, this is the body of a constructor
// initializer. In that case, this adds the code to ensure it returns `this`.
static void finishBody(Compiler* compiler)
{
bool isExpressionBody = finishBlock(compiler);
if (compiler->isInitializer)
{
// If the initializer body evaluates to a value, discard it.
if (isExpressionBody) emitOp(compiler, CODE_POP);
// The receiver is always stored in the first local slot.
emitOp(compiler, CODE_LOAD_LOCAL_0);
}
else if (!isExpressionBody)
{
// Implicitly return null in statement bodies.
emitOp(compiler, CODE_NULL);
}
emitOp(compiler, CODE_RETURN);
}
// 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);
}
}
// Parses the rest of a comma-separated parameter list after the opening
// delimeter. Updates `arity` in [signature] with the number of parameters.
static void finishParameterList(Compiler* compiler, Signature* signature)
{
do
{
ignoreNewlines(compiler);
validateNumParameters(compiler, ++signature->arity);
// Define a local variable in the method for the parameter.
declareNamedVariable(compiler);
}
while (match(compiler, TOKEN_COMMA));
}
// Gets the symbol for a method [name] with [length].
static int methodSymbol(Compiler* compiler, const char* name, int length)
{
return wrenSymbolTableEnsure(compiler->parser->vm,
&compiler->parser->vm->methodNames, name, length);
}
// Appends characters to [name] (and updates [length]) for [numParams] "_"
// surrounded by [leftBracket] and [rightBracket].
static void signatureParameterList(char name[MAX_METHOD_SIGNATURE], int* length,
int numParams, char leftBracket, char rightBracket)
{
name[(*length)++] = leftBracket;
// This function may be called with too many parameters. When that happens,
// a compile error has already been reported, but we need to make sure we
// don't overflow the string too, hence the MAX_PARAMETERS check.
for (int i = 0; i < numParams && i < MAX_PARAMETERS; i++)
{
if (i > 0) name[(*length)++] = ',';
name[(*length)++] = '_';
}
name[(*length)++] = rightBracket;
}
// Fills [name] with the stringified version of [signature] and updates
// [length] to the resulting length.
static void signatureToString(Signature* signature,
char name[MAX_METHOD_SIGNATURE], int* length)
{
*length = 0;
// Build the full name from the signature.
memcpy(name + *length, signature->name, signature->length);
*length += signature->length;
switch (signature->type)
{
case SIG_METHOD:
signatureParameterList(name, length, signature->arity, '(', ')');
break;
case SIG_GETTER:
// The signature is just the name.
break;
case SIG_SETTER:
name[(*length)++] = '=';
signatureParameterList(name, length, 1, '(', ')');
break;
case SIG_SUBSCRIPT:
signatureParameterList(name, length, signature->arity, '[', ']');
break;
case SIG_SUBSCRIPT_SETTER:
signatureParameterList(name, length, signature->arity - 1, '[', ']');
name[(*length)++] = '=';
signatureParameterList(name, length, 1, '(', ')');
break;
case SIG_INITIALIZER:
memcpy(name, "init ", 5);
memcpy(name + 5, signature->name, signature->length);
*length = 5 + signature->length;
signatureParameterList(name, length, signature->arity, '(', ')');
break;
}
name[*length] = '\0';
}
// Gets the symbol for a method with [signature].
static int signatureSymbol(Compiler* compiler, Signature* signature)
{
// Build the full name from the signature.
char name[MAX_METHOD_SIGNATURE];
int length;
signatureToString(signature, name, &length);
return methodSymbol(compiler, name, length);
}
// Returns a signature with [type] whose name is from the last consumed token.
static Signature signatureFromToken(Compiler* compiler, SignatureType type)
{
Signature signature;
// Get the token for the method name.
Token* token = &compiler->parser->previous;
signature.name = token->start;
signature.length = token->length;
signature.type = type;
signature.arity = 0;
if (signature.length > MAX_METHOD_NAME)
{
error(compiler, "Method names cannot be longer than %d characters.",
MAX_METHOD_NAME);
signature.length = MAX_METHOD_NAME;
}
return signature;
}
// Parses a comma-separated list of arguments. Modifies [signature] to include
// the arity of the argument list.
static void finishArgumentList(Compiler* compiler, Signature* signature)
{
do
{
ignoreNewlines(compiler);
validateNumParameters(compiler, ++signature->arity);
expression(compiler);
}
while (match(compiler, TOKEN_COMMA));
// Allow a newline before the closing delimiter.
ignoreNewlines(compiler);
}
// Compiles a method call with [signature] using [instruction].
static void callSignature(Compiler* compiler, Code instruction,
Signature* signature)
{
int symbol = signatureSymbol(compiler, signature);
emitShortArg(compiler, (Code)(instruction + signature->arity), symbol);
if (instruction == CODE_SUPER_0)
{
// Super calls need to be statically bound to the class's superclass. This
// ensures we call the right method even when a method containing a super
// call is inherited by another subclass.
//
// We bind it at class definition time by storing a reference to the
// superclass in a constant. So, here, we create a slot in the constant
// table and store NULL in it. When the method is bound, we'll look up the
// superclass then and store it in the constant slot.
emitShort(compiler, addConstant(compiler, NULL_VAL));
}
}
// Compiles a method call with [numArgs] for a method with [name] with [length].
static void callMethod(Compiler* compiler, int numArgs, const char* name,
int length)
{
int symbol = methodSymbol(compiler, name, length);
emitShortArg(compiler, (Code)(CODE_CALL_0 + numArgs), symbol);
}
// Compiles an (optional) argument list for a method call with [methodSignature]
// and then calls it.
static void methodCall(Compiler* compiler, Code instruction,
Signature* signature)
{
// Make a new signature that contains the updated arity and type based on
// the arguments we find.
Signature called = { signature->name, signature->length, SIG_GETTER, 0 };
// Parse the argument list, if any.
if (match(compiler, TOKEN_LEFT_PAREN))
{
called.type = SIG_METHOD;
// Allow new line before an empty argument list
ignoreNewlines(compiler);
// Allow empty an argument list.
if (peek(compiler) != TOKEN_RIGHT_PAREN)
{
finishArgumentList(compiler, &called);
}
consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after arguments.");
}
// Parse the block argument, if any.
if (match(compiler, TOKEN_LEFT_BRACE))
{
// Include the block argument in the arity.
called.type = SIG_METHOD;
called.arity++;
Compiler fnCompiler;
initCompiler(&fnCompiler, compiler->parser, compiler, false);
// Make a dummy signature to track the arity.
Signature fnSignature = { "", 0, SIG_METHOD, 0 };
// Parse the parameter list, if any.
if (match(compiler, TOKEN_PIPE))
{
finishParameterList(&fnCompiler, &fnSignature);
consume(compiler, TOKEN_PIPE, "Expect '|' after function parameters.");
}
fnCompiler.fn->arity = fnSignature.arity;
finishBody(&fnCompiler);
// Name the function based on the method its passed to.
char blockName[MAX_METHOD_SIGNATURE + 15];
int blockLength;
signatureToString(&called, blockName, &blockLength);
memmove(blockName + blockLength, " block argument", 16);
endCompiler(&fnCompiler, blockName, blockLength + 15);
}
// TODO: Allow Grace-style mixfix methods?
// If this is a super() call for an initializer, make sure we got an actual
// argument list.
if (signature->type == SIG_INITIALIZER)
{
if (called.type != SIG_METHOD)
{
error(compiler, "A superclass constructor must have an argument list.");
}
called.type = SIG_INITIALIZER;
}
callSignature(compiler, instruction, &called);
}
// Compiles a call whose name is the previously consumed token. This includes
// getters, method calls with arguments, and setter calls.
static void namedCall(Compiler* compiler, bool canAssign, Code instruction)
{
// Get the token for the method name.
Signature signature = signatureFromToken(compiler, SIG_GETTER);
if (canAssign && match(compiler, TOKEN_EQ))
{
ignoreNewlines(compiler);
// Build the setter signature.
signature.type = SIG_SETTER;
signature.arity = 1;
// Compile the assigned value.
expression(compiler);
callSignature(compiler, instruction, &signature);
}
else
{
methodCall(compiler, instruction, &signature);
allowLineBeforeDot(compiler);
}
}
// Emits the code to load [variable] onto the stack.
static void loadVariable(Compiler* compiler, Variable variable)
{
switch (variable.scope)
{
case SCOPE_LOCAL:
loadLocal(compiler, variable.index);
break;
case SCOPE_UPVALUE:
emitByteArg(compiler, CODE_LOAD_UPVALUE, variable.index);
break;
case SCOPE_MODULE:
emitShortArg(compiler, CODE_LOAD_MODULE_VAR, variable.index);
break;
default:
UNREACHABLE();
}
}
// Loads the receiver of the currently enclosing method. Correctly handles
// functions defined inside methods.
static void loadThis(Compiler* compiler)
{
loadVariable(compiler, resolveNonmodule(compiler, "this", 4));
}
// Pushes the value for a module-level variable implicitly imported from core.
static void loadCoreVariable(Compiler* compiler, const char* name)
{
int symbol = wrenSymbolTableFind(&compiler->parser->module->variableNames,
name, strlen(name));
ASSERT(symbol != -1, "Should have already defined core name.");
emitShortArg(compiler, CODE_LOAD_MODULE_VAR, symbol);
}
// A parenthesized expression.
static void grouping(Compiler* compiler, bool canAssign)
{
expression(compiler);
consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after expression.");
}
// A list literal.
static void list(Compiler* compiler, bool canAssign)
{
// Instantiate a new list.
loadCoreVariable(compiler, "List");
callMethod(compiler, 0, "new()", 5);
// Compile the list elements. Each one compiles to a ".add()" call.
do
{
ignoreNewlines(compiler);
// Stop if we hit the end of the list.
if (peek(compiler) == TOKEN_RIGHT_BRACKET) break;
// The element.
expression(compiler);
callMethod(compiler, 1, "addCore_(_)", 11);
} while (match(compiler, TOKEN_COMMA));
// Allow newlines before the closing ']'.
ignoreNewlines(compiler);
consume(compiler, TOKEN_RIGHT_BRACKET, "Expect ']' after list elements.");
}
// A map literal.
static void map(Compiler* compiler, bool canAssign)
{
// Instantiate a new map.
loadCoreVariable(compiler, "Map");
callMethod(compiler, 0, "new()", 5);
// Compile the map elements. Each one is compiled to just invoke the
// subscript setter on the map.
do
{
ignoreNewlines(compiler);
// Stop if we hit the end of the map.
if (peek(compiler) == TOKEN_RIGHT_BRACE) break;
// The key.
parsePrecedence(compiler, PREC_UNARY);
consume(compiler, TOKEN_COLON, "Expect ':' after map key.");
ignoreNewlines(compiler);
// The value.
expression(compiler);
callMethod(compiler, 2, "addCore_(_,_)", 13);
} while (match(compiler, TOKEN_COMMA));
// Allow newlines before the closing '}'.
ignoreNewlines(compiler);
consume(compiler, TOKEN_RIGHT_BRACE, "Expect '}' after map entries.");
}
// Unary operators like `-foo`.
static void unaryOp(Compiler* compiler, bool canAssign)
{
GrammarRule* rule = getRule(compiler->parser->previous.type);
ignoreNewlines(compiler);
// Compile the argument.
parsePrecedence(compiler, (Precedence)(PREC_UNARY + 1));
// Call the operator method on the left-hand side.
callMethod(compiler, 0, rule->name, 1);
}
static void boolean(Compiler* compiler, bool canAssign)
{
emitOp(compiler,
compiler->parser->previous.type == TOKEN_FALSE ? CODE_FALSE : CODE_TRUE);
}
// Walks the compiler chain to find the compiler for the nearest class
// enclosing this one. Returns NULL if not currently inside a class definition.
static Compiler* getEnclosingClassCompiler(Compiler* compiler)
{
while (compiler != NULL)
{
if (compiler->enclosingClass != NULL) return compiler;
compiler = compiler->parent;
}
return NULL;
}
// Walks the compiler chain to find the nearest class enclosing this one.
// Returns NULL if not currently inside a class definition.
static ClassInfo* getEnclosingClass(Compiler* compiler)
{
compiler = getEnclosingClassCompiler(compiler);
return compiler == NULL ? NULL : compiler->enclosingClass;
}
static void field(Compiler* compiler, bool canAssign)
{
// Initialize it with a fake value so we can keep parsing and minimize the
// number of cascaded errors.
int field = MAX_FIELDS;
ClassInfo* enclosingClass = getEnclosingClass(compiler);
if (enclosingClass == NULL)
{
error(compiler, "Cannot reference a field outside of a class definition.");
}
else if (enclosingClass->isForeign)
{
error(compiler, "Cannot define fields in a foreign class.");
}
else if (enclosingClass->inStatic)
{
error(compiler, "Cannot use an instance field in a static method.");
}
else
{
// Look up the field, or implicitly define it.
field = wrenSymbolTableEnsure(compiler->parser->vm, &enclosingClass->fields,
compiler->parser->previous.start,
compiler->parser->previous.length);
if (field >= MAX_FIELDS)
{
error(compiler, "A class can only have %d fields.", MAX_FIELDS);
}
}
// If there's an "=" after a field name, it's an assignment.
bool isLoad = true;
if (canAssign && match(compiler, TOKEN_EQ))
{
// Compile the right-hand side.
expression(compiler);
isLoad = false;
}
// If we're directly inside a method, use a more optimal instruction.
if (compiler->parent != NULL &&
compiler->parent->enclosingClass == enclosingClass)
{
emitByteArg(compiler, isLoad ? CODE_LOAD_FIELD_THIS : CODE_STORE_FIELD_THIS,
field);
}
else
{
loadThis(compiler);
emitByteArg(compiler, isLoad ? CODE_LOAD_FIELD : CODE_STORE_FIELD, field);
}
allowLineBeforeDot(compiler);
}
// Compiles a read or assignment to [variable].
static void bareName(Compiler* compiler, bool canAssign, Variable variable)
{
// If there's an "=" after a bare name, it's a variable assignment.
if (canAssign && match(compiler, TOKEN_EQ))
{
// Compile the right-hand side.
expression(compiler);
// Emit the store instruction.
switch (variable.scope)
{
case SCOPE_LOCAL:
emitByteArg(compiler, CODE_STORE_LOCAL, variable.index);
break;
case SCOPE_UPVALUE:
emitByteArg(compiler, CODE_STORE_UPVALUE, variable.index);
break;
case SCOPE_MODULE:
emitShortArg(compiler, CODE_STORE_MODULE_VAR, variable.index);
break;
default:
UNREACHABLE();
}
return;
}
// Emit the load instruction.
loadVariable(compiler, variable);
allowLineBeforeDot(compiler);
}
static void staticField(Compiler* compiler, bool canAssign)
{
Compiler* classCompiler = getEnclosingClassCompiler(compiler);
if (classCompiler == NULL)
{
error(compiler, "Cannot use a static field outside of a class definition.");
return;
}
// Look up the name in the scope chain.
Token* token = &compiler->parser->previous;
// If this is the first time we've seen this static field, implicitly
// define it as a variable in the scope surrounding the class definition.
if (resolveLocal(classCompiler, token->start, token->length) == -1)
{
int symbol = declareVariable(classCompiler, NULL);
// Implicitly initialize it to null.
emitOp(classCompiler, CODE_NULL);
defineVariable(classCompiler, symbol);
}
// It definitely exists now, so resolve it properly. This is different from
// the above resolveLocal() call because we may have already closed over it
// as an upvalue.
Variable variable = resolveName(compiler, token->start, token->length);
bareName(compiler, canAssign, variable);
}
// Compiles a variable name or method call with an implicit receiver.
static void name(Compiler* compiler, bool canAssign)
{
// Look for the name in the scope chain up to the nearest enclosing method.
Token* token = &compiler->parser->previous;
Variable variable = resolveNonmodule(compiler, token->start, token->length);
if (variable.index != -1)
{
bareName(compiler, canAssign, variable);
return;
}
// TODO: The fact that we return above here if the variable is known and parse
// an optional argument list below if not means that the grammar is not
// context-free. A line of code in a method like "someName(foo)" is a parse
// error if "someName" is a defined variable in the surrounding scope and not
// if it isn't. Fix this. One option is to have "someName(foo)" always
// resolve to a self-call if there is an argument list, but that makes
// getters a little confusing.
// If we're inside a method and the name is lowercase, treat it as a method
// on this.
if (wrenIsLocalName(token->start) && getEnclosingClass(compiler) != NULL)
{
loadThis(compiler);
namedCall(compiler, canAssign, CODE_CALL_0);
return;
}
// Otherwise, look for a module-level variable with the name.
variable.scope = SCOPE_MODULE;
variable.index = wrenSymbolTableFind(&compiler->parser->module->variableNames,
token->start, token->length);
if (variable.index == -1)
{
// Implicitly define a module-level variable in
// the hopes that we get a real definition later.
variable.index = wrenDeclareVariable(compiler->parser->vm,
compiler->parser->module,
token->start, token->length,
token->line);
if (variable.index == -2)
{
error(compiler, "Too many module variables defined.");
}
}
bareName(compiler, canAssign, variable);
}
static void null(Compiler* compiler, bool canAssign)
{
emitOp(compiler, CODE_NULL);
}
// A number or string literal.
static void literal(Compiler* compiler, bool canAssign)
{
emitConstant(compiler, compiler->parser->previous.value);
}
// A string literal that contains interpolated expressions.
//
// Interpolation is syntactic sugar for calling ".join()" on a list. So the
// string:
//
// "a %(b + c) d"
//
// is compiled roughly like:
//
// ["a ", b + c, " d"].join()
static void stringInterpolation(Compiler* compiler, bool canAssign)
{
// Instantiate a new list.
loadCoreVariable(compiler, "List");
callMethod(compiler, 0, "new()", 5);
do
{
// The opening string part.
literal(compiler, false);
callMethod(compiler, 1, "addCore_(_)", 11);
// The interpolated expression.
ignoreNewlines(compiler);
expression(compiler);
callMethod(compiler, 1, "addCore_(_)", 11);
ignoreNewlines(compiler);
} while (match(compiler, TOKEN_INTERPOLATION));
// The trailing string part.
consume(compiler, TOKEN_STRING, "Expect end of string interpolation.");
literal(compiler, false);
callMethod(compiler, 1, "addCore_(_)", 11);
// The list of interpolated parts.
callMethod(compiler, 0, "join()", 6);
}
static void super_(Compiler* compiler, bool canAssign)
{
ClassInfo* enclosingClass = getEnclosingClass(compiler);
if (enclosingClass == NULL)
{
error(compiler, "Cannot use 'super' outside of a method.");
}
loadThis(compiler);
// TODO: Super operator calls.
// TODO: There's no syntax for invoking a superclass constructor with a
// different name from the enclosing one. Figure that out.
// See if it's a named super call, or an unnamed one.
if (match(compiler, TOKEN_DOT))
{
// Compile the superclass call.
consume(compiler, TOKEN_NAME, "Expect method name after 'super.'.");
namedCall(compiler, canAssign, CODE_SUPER_0);
}
else if (enclosingClass != NULL)
{
// No explicit name, so use the name of the enclosing method. Make sure we
// check that enclosingClass isn't NULL first. We've already reported the
// error, but we don't want to crash here.
methodCall(compiler, CODE_SUPER_0, enclosingClass->signature);
}
}
static void this_(Compiler* compiler, bool canAssign)
{
if (getEnclosingClass(compiler) == NULL)
{
error(compiler, "Cannot use 'this' outside of a method.");
return;
}
loadThis(compiler);
}
// Subscript or "array indexing" operator like `foo[bar]`.
static void subscript(Compiler* compiler, bool canAssign)
{
Signature signature = { "", 0, SIG_SUBSCRIPT, 0 };
// Parse the argument list.
finishArgumentList(compiler, &signature);
consume(compiler, TOKEN_RIGHT_BRACKET, "Expect ']' after arguments.");
allowLineBeforeDot(compiler);
if (canAssign && match(compiler, TOKEN_EQ))
{
signature.type = SIG_SUBSCRIPT_SETTER;
// Compile the assigned value.
validateNumParameters(compiler, ++signature.arity);
expression(compiler);
}
callSignature(compiler, CODE_CALL_0, &signature);
}
static void call(Compiler* compiler, bool canAssign)
{
ignoreNewlines(compiler);
consume(compiler, TOKEN_NAME, "Expect method name after '.'.");
namedCall(compiler, canAssign, CODE_CALL_0);
}
static void and_(Compiler* compiler, bool canAssign)
{
ignoreNewlines(compiler);
// Skip the right argument if the left is false.
int jump = emitJump(compiler, CODE_AND);
parsePrecedence(compiler, PREC_LOGICAL_AND);
patchJump(compiler, jump);
}
static void or_(Compiler* compiler, bool canAssign)
{
ignoreNewlines(compiler);
// Skip the right argument if the left is true.
int jump = emitJump(compiler, CODE_OR);
parsePrecedence(compiler, PREC_LOGICAL_OR);
patchJump(compiler, jump);
}
static void conditional(Compiler* compiler, bool canAssign)
{
// Ignore newline after '?'.
ignoreNewlines(compiler);
// Jump to the else branch if the condition is false.
int ifJump = emitJump(compiler, CODE_JUMP_IF);
// Compile the then branch.
parsePrecedence(compiler, PREC_CONDITIONAL);
consume(compiler, TOKEN_COLON,
"Expect ':' after then branch of conditional operator.");
ignoreNewlines(compiler);
// Jump over the else branch when the if branch is taken.
int elseJump = emitJump(compiler, CODE_JUMP);
// Compile the else branch.
patchJump(compiler, ifJump);
parsePrecedence(compiler, PREC_ASSIGNMENT);
// Patch the jump over the else.
patchJump(compiler, elseJump);
}
void infixOp(Compiler* compiler, bool canAssign)
{
GrammarRule* rule = getRule(compiler->parser->previous.type);
// An infix operator cannot end an expression.
ignoreNewlines(compiler);
// Compile the right-hand side.
parsePrecedence(compiler, (Precedence)(rule->precedence + 1));
// Call the operator method on the left-hand side.
Signature signature = { rule->name, (int)strlen(rule->name), SIG_METHOD, 1 };
callSignature(compiler, CODE_CALL_0, &signature);
}
// Compiles a method signature for an infix operator.
void infixSignature(Compiler* compiler, Signature* signature)
{
// Add the RHS parameter.
signature->type = SIG_METHOD;
signature->arity = 1;
// Parse the parameter name.
consume(compiler, TOKEN_LEFT_PAREN, "Expect '(' after operator name.");
declareNamedVariable(compiler);
consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after parameter name.");
}
// Compiles a method signature for an unary operator (i.e. "!").
void unarySignature(Compiler* compiler, Signature* signature)
{
// Do nothing. The name is already complete.
signature->type = SIG_GETTER;
}
// Compiles a method signature for an operator that can either be unary or
// infix (i.e. "-").
void mixedSignature(Compiler* compiler, Signature* signature)
{
signature->type = SIG_GETTER;
// If there is a parameter, it's an infix operator, otherwise it's unary.
if (match(compiler, TOKEN_LEFT_PAREN))
{
// Add the RHS parameter.
signature->type = SIG_METHOD;
signature->arity = 1;
// Parse the parameter name.
declareNamedVariable(compiler);
consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after parameter name.");
}
}
// Compiles an optional setter parameter in a method [signature].
//
// Returns `true` if it was a setter.
static bool maybeSetter(Compiler* compiler, Signature* signature)
{
// See if it's a setter.
if (!match(compiler, TOKEN_EQ)) return false;
// It's a setter.
if (signature->type == SIG_SUBSCRIPT)
{
signature->type = SIG_SUBSCRIPT_SETTER;
}
else
{
signature->type = SIG_SETTER;
}
// Parse the value parameter.
consume(compiler, TOKEN_LEFT_PAREN, "Expect '(' after '='.");
declareNamedVariable(compiler);
consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after parameter name.");
signature->arity++;
return true;
}
// Compiles a method signature for a subscript operator.
void subscriptSignature(Compiler* compiler, Signature* signature)
{
signature->type = SIG_SUBSCRIPT;
// The signature currently has "[" as its name since that was the token that
// matched it. Clear that out.
signature->length = 0;
// Parse the parameters inside the subscript.
finishParameterList(compiler, signature);
consume(compiler, TOKEN_RIGHT_BRACKET, "Expect ']' after parameters.");
maybeSetter(compiler, signature);
}
// Parses an optional parenthesized parameter list. Updates `type` and `arity`
// in [signature] to match what was parsed.
static void parameterList(Compiler* compiler, Signature* signature)
{
// The parameter list is optional.
if (!match(compiler, TOKEN_LEFT_PAREN)) return;
signature->type = SIG_METHOD;
// Allow new line before an empty argument list
ignoreNewlines(compiler);
// Allow an empty parameter list.
if (match(compiler, TOKEN_RIGHT_PAREN)) return;
finishParameterList(compiler, signature);
consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after parameters.");
}
// Compiles a method signature for a named method or setter.
void namedSignature(Compiler* compiler, Signature* signature)
{
signature->type = SIG_GETTER;
// If it's a setter, it can't also have a parameter list.
if (maybeSetter(compiler, signature)) return;
// Regular named method with an optional parameter list.
parameterList(compiler, signature);
}
// Compiles a method signature for a constructor.
void constructorSignature(Compiler* compiler, Signature* signature)
{
consume(compiler, TOKEN_NAME, "Expect constructor name after 'construct'.");
// Capture the name.
*signature = signatureFromToken(compiler, SIG_INITIALIZER);
if (match(compiler, TOKEN_EQ))
{
error(compiler, "A constructor cannot be a setter.");
}
if (!match(compiler, TOKEN_LEFT_PAREN))
{
error(compiler, "A constructor cannot be a getter.");
return;
}
// Allow an empty parameter list.
if (match(compiler, TOKEN_RIGHT_PAREN)) return;
finishParameterList(compiler, signature);
consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after parameters.");
}
// 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/
#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 }
#define OPERATOR(name) { unaryOp, infixOp, mixedSignature, PREC_TERM, name }
GrammarRule rules[] =
{
/* TOKEN_LEFT_PAREN */ PREFIX(grouping),
/* TOKEN_RIGHT_PAREN */ UNUSED,
/* TOKEN_LEFT_BRACKET */ { list, subscript, subscriptSignature, PREC_CALL, NULL },
/* TOKEN_RIGHT_BRACKET */ UNUSED,
/* TOKEN_LEFT_BRACE */ PREFIX(map),
/* TOKEN_RIGHT_BRACE */ UNUSED,
/* TOKEN_COLON */ UNUSED,
/* TOKEN_DOT */ INFIX(PREC_CALL, call),
/* TOKEN_DOTDOT */ INFIX_OPERATOR(PREC_RANGE, ".."),
/* TOKEN_DOTDOTDOT */ INFIX_OPERATOR(PREC_RANGE, "..."),
/* TOKEN_COMMA */ UNUSED,
/* TOKEN_STAR */ INFIX_OPERATOR(PREC_FACTOR, "*"),
/* TOKEN_SLASH */ INFIX_OPERATOR(PREC_FACTOR, "/"),
/* TOKEN_PERCENT */ INFIX_OPERATOR(PREC_FACTOR, "%"),
/* TOKEN_HASH */ UNUSED,
/* TOKEN_PLUS */ INFIX_OPERATOR(PREC_TERM, "+"),
/* TOKEN_MINUS */ OPERATOR("-"),
/* TOKEN_LTLT */ INFIX_OPERATOR(PREC_BITWISE_SHIFT, "<<"),
/* TOKEN_GTGT */ INFIX_OPERATOR(PREC_BITWISE_SHIFT, ">>"),
/* TOKEN_PIPE */ INFIX_OPERATOR(PREC_BITWISE_OR, "|"),
/* TOKEN_PIPEPIPE */ INFIX(PREC_LOGICAL_OR, or_),
/* TOKEN_CARET */ INFIX_OPERATOR(PREC_BITWISE_XOR, "^"),
/* TOKEN_AMP */ INFIX_OPERATOR(PREC_BITWISE_AND, "&"),
/* TOKEN_AMPAMP */ INFIX(PREC_LOGICAL_AND, and_),
/* TOKEN_BANG */ PREFIX_OPERATOR("!"),
/* TOKEN_TILDE */ PREFIX_OPERATOR("~"),
/* TOKEN_QUESTION */ INFIX(PREC_ASSIGNMENT, conditional),
/* 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, "!="),
/* TOKEN_BREAK */ UNUSED,
/* TOKEN_CONTINUE */ UNUSED,
/* TOKEN_CLASS */ UNUSED,
/* TOKEN_CONSTRUCT */ { NULL, NULL, constructorSignature, PREC_NONE, NULL },
/* TOKEN_ELSE */ UNUSED,
/* TOKEN_FALSE */ PREFIX(boolean),
/* TOKEN_FOR */ UNUSED,
/* TOKEN_FOREIGN */ UNUSED,
/* TOKEN_IF */ UNUSED,
/* TOKEN_IMPORT */ UNUSED,
/* TOKEN_AS */ UNUSED,
/* TOKEN_IN */ UNUSED,
/* TOKEN_IS */ INFIX_OPERATOR(PREC_IS, "is"),
/* TOKEN_NULL */ PREFIX(null),
/* TOKEN_RETURN */ UNUSED,
/* TOKEN_STATIC */ UNUSED,
/* TOKEN_SUPER */ PREFIX(super_),
/* TOKEN_THIS */ PREFIX(this_),
/* TOKEN_TRUE */ PREFIX(boolean),
/* TOKEN_VAR */ UNUSED,
/* TOKEN_WHILE */ UNUSED,
/* TOKEN_FIELD */ PREFIX(field),
/* TOKEN_STATIC_FIELD */ PREFIX(staticField),
/* TOKEN_NAME */ { name, NULL, namedSignature, PREC_NONE, NULL },
/* TOKEN_NUMBER */ PREFIX(literal),
/* TOKEN_STRING */ PREFIX(literal),
/* TOKEN_INTERPOLATION */ PREFIX(stringInterpolation),
/* TOKEN_LINE */ UNUSED,
/* TOKEN_ERROR */ UNUSED,
/* TOKEN_EOF */ UNUSED
};
// Gets the [GrammarRule] associated with tokens of [type].
static GrammarRule* getRule(TokenType type)
{
return &rules[type];
}
// The main entrypoint for the top-down operator precedence parser.
void parsePrecedence(Compiler* compiler, Precedence precedence)
{
nextToken(compiler->parser);
GrammarFn prefix = rules[compiler->parser->previous.type].prefix;
if (prefix == NULL)
{
error(compiler, "Expected expression.");
return;
}
// Track if the precendence of the surrounding expression is low enough to
// allow an assignment inside this one. We can't compile an assignment like
// a normal expression because it requires us to handle the LHS specially --
// it needs to be an lvalue, not an rvalue. So, for each of the kinds of
// expressions that are valid lvalues -- names, subscripts, fields, etc. --
// we pass in whether or not it appears in a context loose enough to allow
// "=". If so, it will parse the "=" itself and handle it appropriately.
bool canAssign = precedence <= PREC_CONDITIONAL;
prefix(compiler, canAssign);
while (precedence <= rules[compiler->parser->current.type].precedence)
{
nextToken(compiler->parser);
GrammarFn infix = rules[compiler->parser->previous.type].infix;
infix(compiler, canAssign);
}
}
// Parses an expression. Unlike statements, expressions leave a resulting value
// on the stack.
void expression(Compiler* compiler)
{
parsePrecedence(compiler, PREC_LOWEST);
}
// Returns the number of bytes for the arguments to the instruction
// at [ip] in [fn]'s bytecode.
static int getByteCountForArguments(const uint8_t* bytecode,
const Value* constants, int ip)
{
Code instruction = (Code)bytecode[ip];
switch (instruction)
{
case CODE_NULL:
case CODE_FALSE:
case CODE_TRUE:
case CODE_POP:
case CODE_CLOSE_UPVALUE:
case CODE_RETURN:
case CODE_END:
case CODE_LOAD_LOCAL_0:
case CODE_LOAD_LOCAL_1:
case CODE_LOAD_LOCAL_2:
case CODE_LOAD_LOCAL_3:
case CODE_LOAD_LOCAL_4:
case CODE_LOAD_LOCAL_5:
case CODE_LOAD_LOCAL_6:
case CODE_LOAD_LOCAL_7:
case CODE_LOAD_LOCAL_8:
case CODE_CONSTRUCT:
case CODE_FOREIGN_CONSTRUCT:
case CODE_FOREIGN_CLASS:
case CODE_END_MODULE:
case CODE_END_CLASS:
return 0;
case CODE_LOAD_LOCAL:
case CODE_STORE_LOCAL:
case CODE_LOAD_UPVALUE:
case CODE_STORE_UPVALUE:
case CODE_LOAD_FIELD_THIS:
case CODE_STORE_FIELD_THIS:
case CODE_LOAD_FIELD:
case CODE_STORE_FIELD:
case CODE_CLASS:
return 1;
case CODE_CONSTANT:
case CODE_LOAD_MODULE_VAR:
case CODE_STORE_MODULE_VAR:
case CODE_CALL_0:
case CODE_CALL_1:
case CODE_CALL_2:
case CODE_CALL_3:
case CODE_CALL_4:
case CODE_CALL_5:
case CODE_CALL_6:
case CODE_CALL_7:
case CODE_CALL_8:
case CODE_CALL_9:
case CODE_CALL_10:
case CODE_CALL_11:
case CODE_CALL_12:
case CODE_CALL_13:
case CODE_CALL_14:
case CODE_CALL_15:
case CODE_CALL_16:
case CODE_JUMP:
case CODE_LOOP:
case CODE_JUMP_IF:
case CODE_AND:
case CODE_OR:
case CODE_METHOD_INSTANCE:
case CODE_METHOD_STATIC:
case CODE_IMPORT_MODULE:
case CODE_IMPORT_VARIABLE:
return 2;
case CODE_SUPER_0:
case CODE_SUPER_1:
case CODE_SUPER_2:
case CODE_SUPER_3:
case CODE_SUPER_4:
case CODE_SUPER_5:
case CODE_SUPER_6:
case CODE_SUPER_7:
case CODE_SUPER_8:
case CODE_SUPER_9:
case CODE_SUPER_10:
case CODE_SUPER_11:
case CODE_SUPER_12:
case CODE_SUPER_13:
case CODE_SUPER_14:
case CODE_SUPER_15:
case CODE_SUPER_16:
return 4;
case CODE_CLOSURE:
{
int constant = (bytecode[ip + 1] << 8) | bytecode[ip + 2];
ObjFn* loadedFn = AS_FN(constants[constant]);
// There are two bytes for the constant, then two for each upvalue.
return 2 + (loadedFn->numUpvalues * 2);
}
}
UNREACHABLE();
return 0;
}
// Marks the beginning of a loop. Keeps track of the current instruction so we
// know what to loop back to at the end of the body.
static void startLoop(Compiler* compiler, Loop* loop)
{
loop->enclosing = compiler->loop;
loop->start = compiler->fn->code.count - 1;
loop->scopeDepth = compiler->scopeDepth;
compiler->loop = loop;
}
// Emits the [CODE_JUMP_IF] instruction used to test the loop condition and
// potentially exit the loop. Keeps track of the instruction so we can patch it
// later once we know where the end of the body is.
static void testExitLoop(Compiler* compiler)
{
compiler->loop->exitJump = emitJump(compiler, CODE_JUMP_IF);
}
// Compiles the body of the loop and tracks its extent so that contained "break"
// statements can be handled correctly.
static void loopBody(Compiler* compiler)
{
compiler->loop->body = compiler->fn->code.count;
statement(compiler);
}
// Ends the current innermost loop. Patches up all jumps and breaks now that
// we know where the end of the loop is.
static void endLoop(Compiler* compiler)
{
// We don't check for overflow here since the forward jump over the loop body
// will report an error for the same problem.
int loopOffset = compiler->fn->code.count - compiler->loop->start + 2;
emitShortArg(compiler, CODE_LOOP, loopOffset);
patchJump(compiler, compiler->loop->exitJump);
// Find any break placeholder instructions (which will be CODE_END in the
// bytecode) and replace them with real jumps.
int i = compiler->loop->body;
while (i < compiler->fn->code.count)
{
if (compiler->fn->code.data[i] == CODE_END)
{
compiler->fn->code.data[i] = CODE_JUMP;
patchJump(compiler, i + 1);
i += 3;
}
else
{
// Skip this instruction and its arguments.
i += 1 + getByteCountForArguments(compiler->fn->code.data,
compiler->fn->constants.data, i);
}
}
compiler->loop = compiler->loop->enclosing;
}
static void forStatement(Compiler* compiler)
{
// A for statement like:
//
// for (i in sequence.expression) {
// System.print(i)
// }
//
// Is compiled to bytecode almost as if the source looked like this:
//
// {
// var seq_ = sequence.expression
// var iter_
// while (iter_ = seq_.iterate(iter_)) {
// var i = seq_.iteratorValue(iter_)
// System.print(i)
// }
// }
//
// It's not exactly this, because the synthetic variables `seq_` and `iter_`
// actually get names that aren't valid Wren identfiers, 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.");
ignoreNewlines(compiler);
// 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);
// Verify that there is space to hidden local variables.
// Note that we expect only two addLocal calls next to each other in the
// following code.
if (compiler->numLocals + 2 > MAX_LOCALS)
{
error(compiler, "Cannot declare more than %d variables in one scope. (Not enough space for for-loops internal variables)",
MAX_LOCALS);
return;
}
int seqSlot = addLocal(compiler, "seq ", 4);
// Create another hidden local for the iterator object.
null(compiler, false);
int iterSlot = addLocal(compiler, "iter ", 5);
consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after loop expression.");
Loop loop;
startLoop(compiler, &loop);
// Advance the iterator by calling the ".iterate" method on the sequence.
loadLocal(compiler, seqSlot);
loadLocal(compiler, iterSlot);
// Update and test the iterator.
callMethod(compiler, 1, "iterate(_)", 10);
emitByteArg(compiler, CODE_STORE_LOCAL, iterSlot);
testExitLoop(compiler);
// Get the current value in the sequence by calling ".iteratorValue".
loadLocal(compiler, seqSlot);
loadLocal(compiler, iterSlot);
callMethod(compiler, 1, "iteratorValue(_)", 16);
// Bind the loop variable in its own scope. This ensures we get a fresh
// variable each iteration so that closures for it don't all see the same one.
pushScope(compiler);
addLocal(compiler, name, length);
loopBody(compiler);
// Loop variable.
popScope(compiler);
endLoop(compiler);
// Hidden variables.
popScope(compiler);
}
static void ifStatement(Compiler* compiler)
{
// 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.
int ifJump = emitJump(compiler, CODE_JUMP_IF);
// Compile the then branch.
statement(compiler);
// Compile the else branch if there is one.
if (match(compiler, TOKEN_ELSE))
{
// Jump over the else branch when the if branch is taken.
int elseJump = emitJump(compiler, CODE_JUMP);
patchJump(compiler, ifJump);
statement(compiler);
// Patch the jump over the else.
patchJump(compiler, elseJump);
}
else
{
patchJump(compiler, ifJump);
}
}
static void whileStatement(Compiler* compiler)
{
Loop loop;
startLoop(compiler, &loop);
// Compile the condition.
consume(compiler, TOKEN_LEFT_PAREN, "Expect '(' after 'while'.");
expression(compiler);
consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after while condition.");
testExitLoop(compiler);
loopBody(compiler);
endLoop(compiler);
}
// Compiles a simple statement. These can only appear at the top-level or
// within curly blocks. Simple statements exclude variable binding statements
// like "var" and "class" which are not allowed directly in places like the
// branches of an "if" statement.
//
// Unlike expressions, statements do not leave a value on the stack.
void statement(Compiler* compiler)
{
if (match(compiler, TOKEN_BREAK))
{
if (compiler->loop == NULL)
{
error(compiler, "Cannot use 'break' outside of a loop.");
return;
}
// Since we will be jumping out of the scope, make sure any locals in it
// are discarded first.
discardLocals(compiler, compiler->loop->scopeDepth + 1);
// Emit a placeholder instruction for the jump to the end of the body. When
// 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.
emitJump(compiler, CODE_END);
}
else if (match(compiler, TOKEN_CONTINUE))
{
if (compiler->loop == NULL)
{
error(compiler, "Cannot use 'continue' outside of a loop.");
return;
}
// Since we will be jumping out of the scope, make sure any locals in it
// are discarded first.
discardLocals(compiler, compiler->loop->scopeDepth + 1);
// emit a jump back to the top of the loop
int loopOffset = compiler->fn->code.count - compiler->loop->start + 2;
emitShortArg(compiler, CODE_LOOP, loopOffset);
}
else if (match(compiler, TOKEN_FOR))
{
forStatement(compiler);
}
else if (match(compiler, TOKEN_IF))
{
ifStatement(compiler);
}
else if (match(compiler, TOKEN_RETURN))
{
// Compile the return value.
if (peek(compiler) == TOKEN_LINE)
{
// If there's no expression after return, initializers should
// return 'this' and regular methods should return null
Code result = compiler->isInitializer ? CODE_LOAD_LOCAL_0 : CODE_NULL;
emitOp(compiler, result);
}
else
{
if (compiler->isInitializer)
{
error(compiler, "A constructor cannot return a value.");
}
expression(compiler);
}
emitOp(compiler, CODE_RETURN);
}
else if (match(compiler, TOKEN_WHILE))
{
whileStatement(compiler);
}
else if (match(compiler, TOKEN_LEFT_BRACE))
{
// Block statement.
pushScope(compiler);
if (finishBlock(compiler))
{
// Block was an expression, so discard it.
emitOp(compiler, CODE_POP);
}
popScope(compiler);
}
else
{
// Expression statement.
expression(compiler);
emitOp(compiler, CODE_POP);
}
}
// Creates a matching constructor method for an initializer with [signature]
// and [initializerSymbol].
//
// Construction is a two-stage process in Wren that involves two separate
// methods. There is a static method that allocates a new instance of the class.
// It then invokes an initializer method on the new instance, forwarding all of
// the constructor arguments to it.
//
// The allocator method always has a fixed implementation:
//
// CODE_CONSTRUCT - Replace the class in slot 0 with a new instance of it.
// CODE_CALL - Invoke the initializer on the new instance.
//
// This creates that method and calls the initializer with [initializerSymbol].
static void createConstructor(Compiler* compiler, Signature* signature,
int initializerSymbol)
{
Compiler methodCompiler;
initCompiler(&methodCompiler, compiler->parser, compiler, true);
// Allocate the instance.
emitOp(&methodCompiler, compiler->enclosingClass->isForeign
? CODE_FOREIGN_CONSTRUCT : CODE_CONSTRUCT);
// Run its initializer.
emitShortArg(&methodCompiler, (Code)(CODE_CALL_0 + signature->arity),
initializerSymbol);
// Return the instance.
emitOp(&methodCompiler, CODE_RETURN);
endCompiler(&methodCompiler, "", 0);
}
// Loads the enclosing class onto the stack and then binds the function already
// on the stack as a method on that class.
static void defineMethod(Compiler* compiler, Variable classVariable,
bool isStatic, int methodSymbol)
{
// Load the class. We have to do this for each method because we can't
// keep the class on top of the stack. If there are static fields, they
// will be locals above the initial variable slot for the class on the
// stack. To skip past those, we just load the class each time right before
// defining a method.
loadVariable(compiler, classVariable);
// Define the method.
Code instruction = isStatic ? CODE_METHOD_STATIC : CODE_METHOD_INSTANCE;
emitShortArg(compiler, instruction, methodSymbol);
}
// Declares a method in the enclosing class with [signature].
//
// Reports an error if a method with that signature is already declared.
// Returns the symbol for the method.
static int declareMethod(Compiler* compiler, Signature* signature,
const char* name, int length)
{
int symbol = signatureSymbol(compiler, signature);
// See if the class has already declared method with this signature.
ClassInfo* classInfo = compiler->enclosingClass;
IntBuffer* methods = classInfo->inStatic
? &classInfo->staticMethods : &classInfo->methods;
for (int i = 0; i < methods->count; i++)
{
if (methods->data[i] == symbol)
{
const char* staticPrefix = classInfo->inStatic ? "static " : "";
error(compiler, "Class %s already defines a %smethod '%s'.",
&compiler->enclosingClass->name->value, staticPrefix, name);
break;
}
}
wrenIntBufferWrite(compiler->parser->vm, methods, symbol);
return symbol;
}
static Value consumeLiteral(Compiler* compiler, const char* message)
{
if(match(compiler, TOKEN_FALSE)) return FALSE_VAL;
if(match(compiler, TOKEN_TRUE)) return TRUE_VAL;
if(match(compiler, TOKEN_NUMBER)) return compiler->parser->previous.value;
if(match(compiler, TOKEN_STRING)) return compiler->parser->previous.value;
if(match(compiler, TOKEN_NAME)) return compiler->parser->previous.value;
error(compiler, message);
nextToken(compiler->parser);
return NULL_VAL;
}
static bool matchAttribute(Compiler* compiler) {
if(match(compiler, TOKEN_HASH))
{
compiler->numAttributes++;
bool runtimeAccess = match(compiler, TOKEN_BANG);
if(match(compiler, TOKEN_NAME))
{
Value group = compiler->parser->previous.value;
TokenType ahead = peek(compiler);
if(ahead == TOKEN_EQ || ahead == TOKEN_LINE)
{
Value key = group;
Value value = NULL_VAL;
if(match(compiler, TOKEN_EQ))
{
value = consumeLiteral(compiler, "Expect a Bool, Num, String or Identifier literal for an attribute value.");
}
if(runtimeAccess) addToAttributeGroup(compiler, NULL_VAL, key, value);
}
else if(match(compiler, TOKEN_LEFT_PAREN))
{
ignoreNewlines(compiler);
if(match(compiler, TOKEN_RIGHT_PAREN))
{
error(compiler, "Expected attributes in group, group cannot be empty.");
}
else
{
while(peek(compiler) != TOKEN_RIGHT_PAREN)
{
consume(compiler, TOKEN_NAME, "Expect name for attribute key.");
Value key = compiler->parser->previous.value;
Value value = NULL_VAL;
if(match(compiler, TOKEN_EQ))
{
value = consumeLiteral(compiler, "Expect a Bool, Num, String or Identifier literal for an attribute value.");
}
if(runtimeAccess) addToAttributeGroup(compiler, group, key, value);
ignoreNewlines(compiler);
if(!match(compiler, TOKEN_COMMA)) break;
ignoreNewlines(compiler);
}
ignoreNewlines(compiler);
consume(compiler, TOKEN_RIGHT_PAREN,
"Expected ')' after grouped attributes.");
}
}
else
{
error(compiler, "Expect an equal, newline or grouping after an attribute key.");
}
}
else
{
error(compiler, "Expect an attribute definition after #.");
}
consumeLine(compiler, "Expect newline after attribute.");
return true;
}
return false;
}
// Compiles a method definition inside a class body.
//
// Returns `true` if it compiled successfully, or `false` if the method couldn't
// be parsed.
static bool method(Compiler* compiler, Variable classVariable)
{
// Parse any attributes before the method and store them
if(matchAttribute(compiler)) {
return method(compiler, classVariable);
}
// TODO: What about foreign constructors?
bool isForeign = match(compiler, TOKEN_FOREIGN);
bool isStatic = match(compiler, TOKEN_STATIC);
compiler->enclosingClass->inStatic = isStatic;
SignatureFn signatureFn = rules[compiler->parser->current.type].method;
nextToken(compiler->parser);
if (signatureFn == NULL)
{
error(compiler, "Expect method definition.");
return false;
}
// Build the method signature.
Signature signature = signatureFromToken(compiler, SIG_GETTER);
compiler->enclosingClass->signature = &signature;
Compiler methodCompiler;
initCompiler(&methodCompiler, compiler->parser, compiler, true);
// Compile the method signature.
signatureFn(&methodCompiler, &signature);
methodCompiler.isInitializer = signature.type == SIG_INITIALIZER;
if (isStatic && signature.type == SIG_INITIALIZER)
{
error(compiler, "A constructor cannot be static.");
}
// Include the full signature in debug messages in stack traces.
char fullSignature[MAX_METHOD_SIGNATURE];
int length;
signatureToString(&signature, fullSignature, &length);
// Copy any attributes the compiler collected into the enclosing class
copyMethodAttributes(compiler, isForeign, isStatic, fullSignature, length);
// Check for duplicate methods. Doesn't matter that it's already been
// defined, error will discard bytecode anyway.
// Check if the method table already contains this symbol
int methodSymbol = declareMethod(compiler, &signature, fullSignature, length);
if (isForeign)
{
// Define a constant for the signature.
emitConstant(compiler, wrenNewStringLength(compiler->parser->vm,
fullSignature, length));
// We don't need the function we started compiling in the parameter list
// any more.
methodCompiler.parser->vm->compiler = methodCompiler.parent;
}
else
{
consume(compiler, TOKEN_LEFT_BRACE, "Expect '{' to begin method body.");
finishBody(&methodCompiler);
endCompiler(&methodCompiler, fullSignature, length);
}
// Define the method. For a constructor, this defines the instance
// initializer method.
defineMethod(compiler, classVariable, isStatic, methodSymbol);
if (signature.type == SIG_INITIALIZER)
{
// Also define a matching constructor method on the metaclass.
signature.type = SIG_METHOD;
int constructorSymbol = signatureSymbol(compiler, &signature);
createConstructor(compiler, &signature, methodSymbol);
defineMethod(compiler, classVariable, true, constructorSymbol);
}
return true;
}
// Compiles a class definition. Assumes the "class" token has already been
// consumed (along with a possibly preceding "foreign" token).
static void classDefinition(Compiler* compiler, bool isForeign)
{
// Create a variable to store the class in.
Variable classVariable;
classVariable.scope = compiler->scopeDepth == -1 ? SCOPE_MODULE : SCOPE_LOCAL;
classVariable.index = declareNamedVariable(compiler);
// Create shared class name value
Value classNameString = wrenNewStringLength(compiler->parser->vm,
compiler->parser->previous.start, compiler->parser->previous.length);
// Create class name string to track method duplicates
ObjString* className = AS_STRING(classNameString);
// Make a string constant for the name.
emitConstant(compiler, classNameString);
// Load the superclass (if there is one).
if (match(compiler, TOKEN_IS))
{
parsePrecedence(compiler, PREC_CALL);
}
else
{
// Implicitly inherit from Object.
loadCoreVariable(compiler, "Object");
}
// Store a placeholder for the number of fields argument. We don't know the
// count until we've compiled all the methods to see which fields are used.
int numFieldsInstruction = -1;
if (isForeign)
{
emitOp(compiler, CODE_FOREIGN_CLASS);
}
else
{
numFieldsInstruction = emitByteArg(compiler, CODE_CLASS, 255);
}
// Store it in its name.
defineVariable(compiler, classVariable.index);
// Push a local variable scope. Static fields in a class body are hoisted out
// into local variables declared in this scope. Methods that use them will
// have upvalues referencing them.
pushScope(compiler);
ClassInfo classInfo;
classInfo.isForeign = isForeign;
classInfo.name = className;
// Allocate attribute maps if necessary.
// A method will allocate the methods one if needed
classInfo.classAttributes = compiler->attributes->count > 0
? wrenNewMap(compiler->parser->vm)
: NULL;
classInfo.methodAttributes = NULL;
// Copy any existing attributes into the class
copyAttributes(compiler, classInfo.classAttributes);
// 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 class, the
// bytecode will be adjusted by [wrenBindMethod] to take inherited fields
// into account.
wrenSymbolTableInit(&classInfo.fields);
// Set up symbol buffers to track duplicate static and instance methods.
wrenIntBufferInit(&classInfo.methods);
wrenIntBufferInit(&classInfo.staticMethods);
compiler->enclosingClass = &classInfo;
// Compile the method definitions.
consume(compiler, TOKEN_LEFT_BRACE, "Expect '{' after class declaration.");
matchLine(compiler);
while (!match(compiler, TOKEN_RIGHT_BRACE))
{
if (!method(compiler, classVariable)) break;
// Don't require a newline after the last definition.
if (match(compiler, TOKEN_RIGHT_BRACE)) break;
consumeLine(compiler, "Expect newline after definition in class.");
}
// If any attributes are present,
// instantiate a ClassAttributes instance for the class
// and send it over to CODE_END_CLASS
bool hasAttr = classInfo.classAttributes != NULL ||
classInfo.methodAttributes != NULL;
if(hasAttr) {
emitClassAttributes(compiler, &classInfo);
loadVariable(compiler, classVariable);
// At the moment, we don't have other uses for CODE_END_CLASS,
// so we put it inside this condition. Later, we can always
// emit it and use it as needed.
emitOp(compiler, CODE_END_CLASS);
}
// Update the class with the number of fields.
if (!isForeign)
{
compiler->fn->code.data[numFieldsInstruction] =
(uint8_t)classInfo.fields.count;
}
// Clear symbol tables for tracking field and method names.
wrenSymbolTableClear(compiler->parser->vm, &classInfo.fields);
wrenIntBufferClear(compiler->parser->vm, &classInfo.methods);
wrenIntBufferClear(compiler->parser->vm, &classInfo.staticMethods);
compiler->enclosingClass = NULL;
popScope(compiler);
}
// Compiles an "import" statement.
//
// An import compiles to a series of instructions. Given:
//
// import "foo" for Bar, Baz
//
// We compile a single IMPORT_MODULE "foo" instruction to load the module
// itself. When that finishes executing the imported module, it leaves the
// ObjModule in vm->lastModule. Then, for Bar and Baz, we:
//
// * Declare a variable in the current scope with that name.
// * Emit an IMPORT_VARIABLE instruction to load the variable's value from the
// other module.
// * Compile the code to store that value in the variable in this scope.
static void import(Compiler* compiler)
{
ignoreNewlines(compiler);
consume(compiler, TOKEN_STRING, "Expect a string after 'import'.");
int moduleConstant = addConstant(compiler, compiler->parser->previous.value);
// Load the module.
emitShortArg(compiler, CODE_IMPORT_MODULE, moduleConstant);
// Discard the unused result value from calling the module body's closure.
emitOp(compiler, CODE_POP);
// The for clause is optional.
if (!match(compiler, TOKEN_FOR)) return;
// Compile the comma-separated list of variables to import.
do
{
ignoreNewlines(compiler);
consume(compiler, TOKEN_NAME, "Expect variable name.");
// We need to hold onto the source variable,
// in order to reference it in the import later
Token sourceVariableToken = compiler->parser->previous;
// Define a string constant for the original variable name.
int sourceVariableConstant = addConstant(compiler,
wrenNewStringLength(compiler->parser->vm,
sourceVariableToken.start,
sourceVariableToken.length));
// Store the symbol we care about for the variable
int slot = -1;
if(match(compiler, TOKEN_AS))
{
//import "module" for Source as Dest
//Use 'Dest' as the name by declaring a new variable for it.
//This parses a name after the 'as' and defines it.
slot = declareNamedVariable(compiler);
}
else
{
//import "module" for Source
//Uses 'Source' as the name directly
slot = declareVariable(compiler, &sourceVariableToken);
}
// Load the variable from the other module.
emitShortArg(compiler, CODE_IMPORT_VARIABLE, sourceVariableConstant);
// Store the result in the variable here.
defineVariable(compiler, slot);
} while (match(compiler, TOKEN_COMMA));
}
// Compiles a "var" variable definition statement.
static void variableDefinition(Compiler* compiler)
{
// Grab its name, but don't declare it yet. A (local) variable shouldn't be
// in scope in its own initializer.
consume(compiler, TOKEN_NAME, "Expect variable name.");
Token nameToken = compiler->parser->previous;
// Compile the initializer.
if (match(compiler, TOKEN_EQ))
{
ignoreNewlines(compiler);
expression(compiler);
}
else
{
// Default initialize it to null.
null(compiler, false);
}
// Now put it in scope.
int symbol = declareVariable(compiler, &nameToken);
defineVariable(compiler, symbol);
}
// Compiles a "definition". These are the statements that bind new variables.
// They can only appear at the top level of a block and are prohibited in places
// like the non-curly body of an if or while.
void definition(Compiler* compiler)
{
if(matchAttribute(compiler)) {
definition(compiler);
return;
}
if (match(compiler, TOKEN_CLASS))
{
classDefinition(compiler, false);
return;
}
else if (match(compiler, TOKEN_FOREIGN))
{
consume(compiler, TOKEN_CLASS, "Expect 'class' after 'foreign'.");
classDefinition(compiler, true);
return;
}
disallowAttributes(compiler);
if (match(compiler, TOKEN_IMPORT))
{
import(compiler);
}
else if (match(compiler, TOKEN_VAR))
{
variableDefinition(compiler);
}
else
{
statement(compiler);
}
}
ObjFn* wrenCompile(WrenVM* vm, ObjModule* module, const char* source,
bool isExpression, bool printErrors)
{
// Skip the UTF-8 BOM if there is one.
if (strncmp(source, "\xEF\xBB\xBF", 3) == 0) source += 3;
Parser parser;
parser.vm = vm;
parser.module = module;
parser.source = source;
parser.tokenStart = source;
parser.currentChar = source;
parser.currentLine = 1;
parser.numParens = 0;
// Zero-init the current token. This will get copied to previous when
// nextToken() is called below.
parser.next.type = TOKEN_ERROR;
parser.next.start = source;
parser.next.length = 0;
parser.next.line = 0;
parser.next.value = UNDEFINED_VAL;
parser.printErrors = printErrors;
parser.hasError = false;
// Read the first token into next
nextToken(&parser);
// Copy next -> current
nextToken(&parser);
int numExistingVariables = module->variables.count;
Compiler compiler;
initCompiler(&compiler, &parser, NULL, false);
ignoreNewlines(&compiler);
if (isExpression)
{
expression(&compiler);
consume(&compiler, TOKEN_EOF, "Expect end of expression.");
}
else
{
while (!match(&compiler, TOKEN_EOF))
{
definition(&compiler);
// If there is no newline, it must be the end of file on the same line.
if (!matchLine(&compiler))
{
consume(&compiler, TOKEN_EOF, "Expect end of file.");
break;
}
}
emitOp(&compiler, CODE_END_MODULE);
}
emitOp(&compiler, CODE_RETURN);
// See if there are any implicitly declared module-level variables that never
// got an explicit definition. They will have values that are numbers
// indicating the line where the variable was first used.
for (int i = numExistingVariables; i < parser.module->variables.count; i++)
{
if (IS_NUM(parser.module->variables.data[i]))
{
// Synthesize a token for the original use site.
parser.previous.type = TOKEN_NAME;
parser.previous.start = parser.module->variableNames.data[i]->value;
parser.previous.length = parser.module->variableNames.data[i]->length;
parser.previous.line = (int)AS_NUM(parser.module->variables.data[i]);
error(&compiler, "Variable is used but not defined.");
}
}
return endCompiler(&compiler, "(script)", 8);
}
void wrenBindMethodCode(ObjClass* classObj, ObjFn* fn)
{
int ip = 0;
for (;;)
{
Code instruction = (Code)fn->code.data[ip];
switch (instruction)
{
case CODE_LOAD_FIELD:
case CODE_STORE_FIELD:
case CODE_LOAD_FIELD_THIS:
case CODE_STORE_FIELD_THIS:
// Shift this class's fields down past the inherited ones. We don't
// check for overflow here because we'll see if the number of fields
// overflows when the subclass is created.
fn->code.data[ip + 1] += classObj->superclass->numFields;
break;
case CODE_SUPER_0:
case CODE_SUPER_1:
case CODE_SUPER_2:
case CODE_SUPER_3:
case CODE_SUPER_4:
case CODE_SUPER_5:
case CODE_SUPER_6:
case CODE_SUPER_7:
case CODE_SUPER_8:
case CODE_SUPER_9:
case CODE_SUPER_10:
case CODE_SUPER_11:
case CODE_SUPER_12:
case CODE_SUPER_13:
case CODE_SUPER_14:
case CODE_SUPER_15:
case CODE_SUPER_16:
{
// Fill in the constant slot with a reference to the superclass.
int constant = (fn->code.data[ip + 3] << 8) | fn->code.data[ip + 4];
fn->constants.data[constant] = OBJ_VAL(classObj->superclass);
break;
}
case CODE_CLOSURE:
{
// Bind the nested closure too.
int constant = (fn->code.data[ip + 1] << 8) | fn->code.data[ip + 2];
wrenBindMethodCode(classObj, AS_FN(fn->constants.data[constant]));
break;
}
case CODE_END:
return;
default:
// Other instructions are unaffected, so just skip over them.
break;
}
ip += 1 + getByteCountForArguments(fn->code.data, fn->constants.data, ip);
}
}
void wrenMarkCompiler(WrenVM* vm, Compiler* compiler)
{
wrenGrayValue(vm, compiler->parser->current.value);
wrenGrayValue(vm, compiler->parser->previous.value);
wrenGrayValue(vm, compiler->parser->next.value);
// Walk up the parent chain to mark the outer compilers too. The VM only
// tracks the innermost one.
do
{
wrenGrayObj(vm, (Obj*)compiler->fn);
wrenGrayObj(vm, (Obj*)compiler->constants);
wrenGrayObj(vm, (Obj*)compiler->attributes);
if (compiler->enclosingClass != NULL)
{
wrenBlackenSymbolTable(vm, &compiler->enclosingClass->fields);
if(compiler->enclosingClass->methodAttributes != NULL)
{
wrenGrayObj(vm, (Obj*)compiler->enclosingClass->methodAttributes);
}
if(compiler->enclosingClass->classAttributes != NULL)
{
wrenGrayObj(vm, (Obj*)compiler->enclosingClass->classAttributes);
}
}
compiler = compiler->parent;
}
while (compiler != NULL);
}
// Helpers for Attributes
// Throw an error if any attributes were found preceding,
// and clear the attributes so the error doesn't keep happening.
static void disallowAttributes(Compiler* compiler)
{
if (compiler->numAttributes > 0)
{
error(compiler, "Attributes can only specified before a class or a method");
wrenMapClear(compiler->parser->vm, compiler->attributes);
compiler->numAttributes = 0;
}
}
// Add an attribute to a given group in the compiler attribues map
static void addToAttributeGroup(Compiler* compiler,
Value group, Value key, Value value)
{
WrenVM* vm = compiler->parser->vm;
if(IS_OBJ(group)) wrenPushRoot(vm, AS_OBJ(group));
if(IS_OBJ(key)) wrenPushRoot(vm, AS_OBJ(key));
if(IS_OBJ(value)) wrenPushRoot(vm, AS_OBJ(value));
Value groupMapValue = wrenMapGet(compiler->attributes, group);
if(IS_UNDEFINED(groupMapValue))
{
groupMapValue = OBJ_VAL(wrenNewMap(vm));
wrenMapSet(vm, compiler->attributes, group, groupMapValue);
}
//we store them as a map per so we can maintain duplicate keys
//group = { key:[value, ...], }
ObjMap* groupMap = AS_MAP(groupMapValue);
//var keyItems = group[key]
//if(!keyItems) keyItems = group[key] = []
Value keyItemsValue = wrenMapGet(groupMap, key);
if(IS_UNDEFINED(keyItemsValue))
{
keyItemsValue = OBJ_VAL(wrenNewList(vm, 0));
wrenMapSet(vm, groupMap, key, keyItemsValue);
}
//keyItems.add(value)
ObjList* keyItems = AS_LIST(keyItemsValue);
wrenValueBufferWrite(vm, &keyItems->elements, value);
if(IS_OBJ(group)) wrenPopRoot(vm);
if(IS_OBJ(key)) wrenPopRoot(vm);
if(IS_OBJ(value)) wrenPopRoot(vm);
}
// Emit the attributes in the give map onto the stack
static void emitAttributes(Compiler* compiler, ObjMap* attributes)
{
// Instantiate a new map for the attributes
loadCoreVariable(compiler, "Map");
callMethod(compiler, 0, "new()", 5);
// The attributes are stored as group = { key:[value, value, ...] }
// so our first level is the group map
for(uint32_t groupIdx = 0; groupIdx < attributes->capacity; groupIdx++)
{
const MapEntry* groupEntry = &attributes->entries[groupIdx];
if(IS_UNDEFINED(groupEntry->key)) continue;
//group key
emitConstant(compiler, groupEntry->key);
//group value is gonna be a map
loadCoreVariable(compiler, "Map");
callMethod(compiler, 0, "new()", 5);
ObjMap* groupItems = AS_MAP(groupEntry->value);
for(uint32_t itemIdx = 0; itemIdx < groupItems->capacity; itemIdx++)
{
const MapEntry* itemEntry = &groupItems->entries[itemIdx];
if(IS_UNDEFINED(itemEntry->key)) continue;
emitConstant(compiler, itemEntry->key);
// Attribute key value, key = []
loadCoreVariable(compiler, "List");
callMethod(compiler, 0, "new()", 5);
// Add the items to the key list
ObjList* items = AS_LIST(itemEntry->value);
for(int itemIdx = 0; itemIdx < items->elements.count; ++itemIdx)
{
emitConstant(compiler, items->elements.data[itemIdx]);
callMethod(compiler, 1, "addCore_(_)", 11);
}
// Add the list to the map
callMethod(compiler, 2, "addCore_(_,_)", 13);
}
// Add the key/value to the map
callMethod(compiler, 2, "addCore_(_,_)", 13);
}
}
// Methods are stored as method <-> attributes, so we have to have
// an indirection to resolve for methods
static void emitAttributeMethods(Compiler* compiler, ObjMap* attributes)
{
// Instantiate a new map for the attributes
loadCoreVariable(compiler, "Map");
callMethod(compiler, 0, "new()", 5);
for(uint32_t methodIdx = 0; methodIdx < attributes->capacity; methodIdx++)
{
const MapEntry* methodEntry = &attributes->entries[methodIdx];
if(IS_UNDEFINED(methodEntry->key)) continue;
emitConstant(compiler, methodEntry->key);
ObjMap* attributeMap = AS_MAP(methodEntry->value);
emitAttributes(compiler, attributeMap);
callMethod(compiler, 2, "addCore_(_,_)", 13);
}
}
// Emit the final ClassAttributes that exists at runtime
static void emitClassAttributes(Compiler* compiler, ClassInfo* classInfo)
{
loadCoreVariable(compiler, "ClassAttributes");
classInfo->classAttributes
? emitAttributes(compiler, classInfo->classAttributes)
: null(compiler, false);
classInfo->methodAttributes
? emitAttributeMethods(compiler, classInfo->methodAttributes)
: null(compiler, false);
callMethod(compiler, 2, "new(_,_)", 8);
}
// Copy the current attributes stored in the compiler into a destination map
// This also resets the counter, since the intent is to consume the attributes
static void copyAttributes(Compiler* compiler, ObjMap* into)
{
compiler->numAttributes = 0;
if(compiler->attributes->count == 0) return;
if(into == NULL) return;
WrenVM* vm = compiler->parser->vm;
// Note we copy the actual values as is since we'll take ownership
// and clear the original map
for(uint32_t attrIdx = 0; attrIdx < compiler->attributes->capacity; attrIdx++)
{
const MapEntry* attrEntry = &compiler->attributes->entries[attrIdx];
if(IS_UNDEFINED(attrEntry->key)) continue;
wrenMapSet(vm, into, attrEntry->key, attrEntry->value);
}
wrenMapClear(vm, compiler->attributes);
}
// Copy the current attributes stored in the compiler into the method specific
// attributes for the current enclosingClass.
// This also resets the counter, since the intent is to consume the attributes
static void copyMethodAttributes(Compiler* compiler, bool isForeign,
bool isStatic, const char* fullSignature, int32_t length)
{
compiler->numAttributes = 0;
if(compiler->attributes->count == 0) return;
WrenVM* vm = compiler->parser->vm;
// Make a map for this method to copy into
ObjMap* methodAttr = wrenNewMap(vm);
wrenPushRoot(vm, (Obj*)methodAttr);
copyAttributes(compiler, methodAttr);
// Include 'foreign static ' in front as needed
int32_t fullLength = length;
if(isForeign) fullLength += 8;
if(isStatic) fullLength += 7;
char fullSignatureWithPrefix[MAX_METHOD_SIGNATURE + 8 + 7];
const char* foreignPrefix = isForeign ? "foreign " : "";
const char* staticPrefix = isStatic ? "static " : "";
sprintf(fullSignatureWithPrefix, "%s%s%.*s", foreignPrefix, staticPrefix,
length, fullSignature);
fullSignatureWithPrefix[fullLength] = '\0';
if(compiler->enclosingClass->methodAttributes == NULL) {
compiler->enclosingClass->methodAttributes = wrenNewMap(vm);
}
// Store the method attributes in the class map
Value key = wrenNewStringLength(vm, fullSignatureWithPrefix, fullLength);
wrenMapSet(vm, compiler->enclosingClass->methodAttributes, key, OBJ_VAL(methodAttr));
wrenPopRoot(vm);
}
// End file "wren_compiler.c"
// Begin file "wren_primitive.c"
// Begin file "wren_primitive.h"
#ifndef wren_primitive_h
#define wren_primitive_h
// Binds a primitive method named [name] (in Wren) implemented using C function
// [fn] to `ObjClass` [cls].
#define PRIMITIVE(cls, name, function) \
do \
{ \
int symbol = wrenSymbolTableEnsure(vm, \
&vm->methodNames, name, strlen(name)); \
Method method; \
method.type = METHOD_PRIMITIVE; \
method.as.primitive = prim_##function; \
wrenBindMethod(vm, cls, symbol, method); \
} while (false)
// Binds a primitive method named [name] (in Wren) implemented using C function
// [fn] to `ObjClass` [cls], but as a FN call.
#define FUNCTION_CALL(cls, name, function) \
do \
{ \
int symbol = wrenSymbolTableEnsure(vm, \
&vm->methodNames, name, strlen(name)); \
Method method; \
method.type = METHOD_FUNCTION_CALL; \
method.as.primitive = prim_##function; \
wrenBindMethod(vm, cls, symbol, method); \
} while (false)
// Defines a primitive method whose C function name is [name]. This abstracts
// the actual type signature of a primitive function and makes it clear which C
// functions are invoked as primitives.
#define DEF_PRIMITIVE(name) \
static bool prim_##name(WrenVM* vm, Value* args)
#define RETURN_VAL(value) \
do \
{ \
args[0] = value; \
return true; \
} while (false)
#define RETURN_OBJ(obj) RETURN_VAL(OBJ_VAL(obj))
#define RETURN_BOOL(value) RETURN_VAL(BOOL_VAL(value))
#define RETURN_FALSE RETURN_VAL(FALSE_VAL)
#define RETURN_NULL RETURN_VAL(NULL_VAL)
#define RETURN_NUM(value) RETURN_VAL(NUM_VAL(value))
#define RETURN_TRUE RETURN_VAL(TRUE_VAL)
#define RETURN_ERROR(msg) \
do \
{ \
vm->fiber->error = wrenNewStringLength(vm, msg, sizeof(msg) - 1); \
return false; \
} while (false)
#define RETURN_ERROR_FMT(...) \
do \
{ \
vm->fiber->error = wrenStringFormat(vm, __VA_ARGS__); \
return false; \
} while (false)
// Validates that the given [arg] is a function. Returns true if it is. If not,
// reports an error and returns false.
bool validateFn(WrenVM* vm, Value arg, const char* argName);
// Validates that the given [arg] is a Num. Returns true if it is. If not,
// reports an error and returns false.
bool validateNum(WrenVM* vm, Value arg, const char* argName);
// Validates that [value] is an integer. Returns true if it is. If not, reports
// an error and returns false.
bool validateIntValue(WrenVM* vm, double value, const char* argName);
// Validates that the given [arg] is an integer. Returns true if it is. If not,
// reports an error and returns false.
bool validateInt(WrenVM* vm, Value arg, const char* argName);
// Validates that [arg] is a valid object for use as a map key. Returns true if
// it is. If not, reports an error and returns false.
bool validateKey(WrenVM* vm, Value arg);
// Validates that the argument at [argIndex] is an integer within `[0, count)`.
// Also allows negative indices which map backwards from the end. Returns the
// valid positive index value. If invalid, reports an error and returns
// `UINT32_MAX`.
uint32_t validateIndex(WrenVM* vm, Value arg, uint32_t count,
const char* argName);
// Validates that the given [arg] is a String. Returns true if it is. If not,
// reports an error and returns false.
bool validateString(WrenVM* vm, Value arg, const char* argName);
// Given a [range] and the [length] of the object being operated on, determines
// the series of elements that should be chosen from the underlying object.
// Handles ranges that count backwards from the end as well as negative ranges.
//
// Returns the index from which the range should start or `UINT32_MAX` if the
// range is invalid. After calling, [length] will be updated with the number of
// elements in the resulting sequence. [step] will be direction that the range
// is going: `1` if the range is increasing from the start index or `-1` if the
// range is decreasing.
uint32_t calculateRange(WrenVM* vm, ObjRange* range, uint32_t* length,
int* step);
#endif
// End file "wren_primitive.h"
#include <math.h>
// Validates that [value] is an integer within `[0, count)`. Also allows
// negative indices which map backwards from the end. Returns the valid positive
// index value. If invalid, reports an error and returns `UINT32_MAX`.
static uint32_t validateIndexValue(WrenVM* vm, uint32_t count, double value,
const char* argName)
{
if (!validateIntValue(vm, value, argName)) return UINT32_MAX;
// Negative indices count from the end.
if (value < 0) value = count + value;
// Check bounds.
if (value >= 0 && value < count) return (uint32_t)value;
vm->fiber->error = wrenStringFormat(vm, "$ out of bounds.", argName);
return UINT32_MAX;
}
bool validateFn(WrenVM* vm, Value arg, const char* argName)
{
if (IS_CLOSURE(arg)) return true;
RETURN_ERROR_FMT("$ must be a function.", argName);
}
bool validateNum(WrenVM* vm, Value arg, const char* argName)
{
if (IS_NUM(arg)) return true;
RETURN_ERROR_FMT("$ must be a number.", argName);
}
bool validateIntValue(WrenVM* vm, double value, const char* argName)
{
if (trunc(value) == value) return true;
RETURN_ERROR_FMT("$ must be an integer.", argName);
}
bool validateInt(WrenVM* vm, Value arg, const char* argName)
{
// Make sure it's a number first.
if (!validateNum(vm, arg, argName)) return false;
return validateIntValue(vm, AS_NUM(arg), argName);
}
bool validateKey(WrenVM* vm, Value arg)
{
if (wrenMapIsValidKey(arg)) return true;
RETURN_ERROR("Key must be a value type.");
}
uint32_t validateIndex(WrenVM* vm, Value arg, uint32_t count,
const char* argName)
{
if (!validateNum(vm, arg, argName)) return UINT32_MAX;
return validateIndexValue(vm, count, AS_NUM(arg), argName);
}
bool validateString(WrenVM* vm, Value arg, const char* argName)
{
if (IS_STRING(arg)) return true;
RETURN_ERROR_FMT("$ must be a string.", argName);
}
uint32_t calculateRange(WrenVM* vm, ObjRange* range, uint32_t* length,
int* step)
{
*step = 0;
// Edge case: an empty range is allowed at the end of a sequence. This way,
// list[0..-1] and list[0...list.count] can be used to copy a list even when
// empty.
if (range->from == *length &&
range->to == (range->isInclusive ? -1.0 : (double)*length))
{
*length = 0;
return 0;
}
uint32_t from = validateIndexValue(vm, *length, range->from, "Range start");
if (from == UINT32_MAX) return UINT32_MAX;
// Bounds check the end manually to handle exclusive ranges.
double value = range->to;
if (!validateIntValue(vm, value, "Range end")) return UINT32_MAX;
// Negative indices count from the end.
if (value < 0) value = *length + value;
// Convert the exclusive range to an inclusive one.
if (!range->isInclusive)
{
// An exclusive range with the same start and end points is empty.
if (value == from)
{
*length = 0;
return from;
}
// Shift the endpoint to make it inclusive, handling both increasing and
// decreasing ranges.
value += value >= from ? -1 : 1;
}
// Check bounds.
if (value < 0 || value >= *length)
{
vm->fiber->error = CONST_STRING(vm, "Range end out of bounds.");
return UINT32_MAX;
}
uint32_t to = (uint32_t)value;
*length = abs((int)(from - to)) + 1;
*step = from < to ? 1 : -1;
return from;
}
// End file "wren_primitive.c"
// Begin file "wren_core.c"
#include <ctype.h>
#include <errno.h>
#include <float.h>
#include <math.h>
#include <string.h>
#include <time.h>
// Begin file "wren_core.h"
#ifndef wren_core_h
#define wren_core_h
// This module defines the built-in classes and their primitives methods that
// are implemented directly in C code. Some languages try to implement as much
// of the core module itself in the primary language instead of in the host
// language.
//
// With Wren, we try to do as much of it in C as possible. Primitive methods
// are always faster than code written in Wren, and it minimizes startup time
// since we don't have to parse, compile, and execute Wren code.
//
// There is one limitation, though. Methods written in C cannot call Wren ones.
// They can only be the top of the callstack, and immediately return. This
// makes it difficult to have primitive methods that rely on polymorphic
// behavior. For example, `System.print` should call `toString` on its argument,
// including user-defined `toString` methods on user-defined classes.
void wrenInitializeCore(WrenVM* vm);
#endif
// End file "wren_core.h"
// Begin file "wren_core.wren.inc"
// Generated automatically from src/vm/wren_core.wren. Do not edit.
static const char* coreModuleSource =
"class Bool {}\n"
"class Fiber {}\n"
"class Fn {}\n"
"class Null {}\n"
"class Num {}\n"
"\n"
"class Sequence {\n"
" all(f) {\n"
" var result = true\n"
" for (element in this) {\n"
" result = f.call(element)\n"
" if (!result) return result\n"
" }\n"
" return result\n"
" }\n"
"\n"
" any(f) {\n"
" var result = false\n"
" for (element in this) {\n"
" result = f.call(element)\n"
" if (result) return result\n"
" }\n"
" return result\n"
" }\n"
"\n"
" contains(element) {\n"
" for (item in this) {\n"
" if (element == item) return true\n"
" }\n"
" return false\n"
" }\n"
"\n"
" count {\n"
" var result = 0\n"
" for (element in this) {\n"
" result = result + 1\n"
" }\n"
" return result\n"
" }\n"
"\n"
" count(f) {\n"
" var result = 0\n"
" for (element in this) {\n"
" if (f.call(element)) result = result + 1\n"
" }\n"
" return result\n"
" }\n"
"\n"
" each(f) {\n"
" for (element in this) {\n"
" f.call(element)\n"
" }\n"
" }\n"
"\n"
" isEmpty { iterate(null) ? false : true }\n"
"\n"
" map(transformation) { MapSequence.new(this, transformation) }\n"
"\n"
" skip(count) {\n"
" if (!(count is Num) || !count.isInteger || count < 0) {\n"
" Fiber.abort(\"Count must be a non-negative integer.\")\n"
" }\n"
"\n"
" return SkipSequence.new(this, count)\n"
" }\n"
"\n"
" take(count) {\n"
" if (!(count is Num) || !count.isInteger || count < 0) {\n"
" Fiber.abort(\"Count must be a non-negative integer.\")\n"
" }\n"
"\n"
" return TakeSequence.new(this, count)\n"
" }\n"
"\n"
" where(predicate) { WhereSequence.new(this, predicate) }\n"
"\n"
" reduce(acc, f) {\n"
" for (element in this) {\n"
" acc = f.call(acc, element)\n"
" }\n"
" return acc\n"
" }\n"
"\n"
" reduce(f) {\n"
" var iter = iterate(null)\n"
" if (!iter) Fiber.abort(\"Can't reduce an empty sequence.\")\n"
"\n"
" // Seed with the first element.\n"
" var result = iteratorValue(iter)\n"
" while (iter = iterate(iter)) {\n"
" result = f.call(result, iteratorValue(iter))\n"
" }\n"
"\n"
" return result\n"
" }\n"
"\n"
" join() { join(\"\") }\n"
"\n"
" join(sep) {\n"
" var first = true\n"
" var result = \"\"\n"
"\n"
" for (element in this) {\n"
" if (!first) result = result + sep\n"
" first = false\n"
" result = result + element.toString\n"
" }\n"
"\n"
" return result\n"
" }\n"
"\n"
" toList {\n"
" var result = List.new()\n"
" for (element in this) {\n"
" result.add(element)\n"
" }\n"
" return result\n"
" }\n"
"}\n"
"\n"
"class MapSequence is Sequence {\n"
" construct new(sequence, fn) {\n"
" _sequence = sequence\n"
" _fn = fn\n"
" }\n"
"\n"
" iterate(iterator) { _sequence.iterate(iterator) }\n"
" iteratorValue(iterator) { _fn.call(_sequence.iteratorValue(iterator)) }\n"
"}\n"
"\n"
"class SkipSequence is Sequence {\n"
" construct new(sequence, count) {\n"
" _sequence = sequence\n"
" _count = count\n"
" }\n"
"\n"
" iterate(iterator) {\n"
" if (iterator) {\n"
" return _sequence.iterate(iterator)\n"
" } else {\n"
" iterator = _sequence.iterate(iterator)\n"
" var count = _count\n"
" while (count > 0 && iterator) {\n"
" iterator = _sequence.iterate(iterator)\n"
" count = count - 1\n"
" }\n"
" return iterator\n"
" }\n"
" }\n"
"\n"
" iteratorValue(iterator) { _sequence.iteratorValue(iterator) }\n"
"}\n"
"\n"
"class TakeSequence is Sequence {\n"
" construct new(sequence, count) {\n"
" _sequence = sequence\n"
" _count = count\n"
" }\n"
"\n"
" iterate(iterator) {\n"
" if (!iterator) _taken = 1 else _taken = _taken + 1\n"
" return _taken > _count ? null : _sequence.iterate(iterator)\n"
" }\n"
"\n"
" iteratorValue(iterator) { _sequence.iteratorValue(iterator) }\n"
"}\n"
"\n"
"class WhereSequence is Sequence {\n"
" construct new(sequence, fn) {\n"
" _sequence = sequence\n"
" _fn = fn\n"
" }\n"
"\n"
" iterate(iterator) {\n"
" while (iterator = _sequence.iterate(iterator)) {\n"
" if (_fn.call(_sequence.iteratorValue(iterator))) break\n"
" }\n"
" return iterator\n"
" }\n"
"\n"
" iteratorValue(iterator) { _sequence.iteratorValue(iterator) }\n"
"}\n"
"\n"
"class String is Sequence {\n"
" bytes { StringByteSequence.new(this) }\n"
" codePoints { StringCodePointSequence.new(this) }\n"
"\n"
" split(delimiter) {\n"
" if (!(delimiter is String) || delimiter.isEmpty) {\n"
" Fiber.abort(\"Delimiter must be a non-empty string.\")\n"
" }\n"
"\n"
" var result = []\n"
"\n"
" var last = 0\n"
" var index = 0\n"
"\n"
" var delimSize = delimiter.byteCount_\n"
" var size = byteCount_\n"
"\n"
" while (last < size && (index = indexOf(delimiter, last)) != -1) {\n"
" result.add(this[last...index])\n"
" last = index + delimSize\n"
" }\n"
"\n"
" if (last < size) {\n"
" result.add(this[last..-1])\n"
" } else {\n"
" result.add(\"\")\n"
" }\n"
" return result\n"
" }\n"
"\n"
" replace(from, to) {\n"
" if (!(from is String) || from.isEmpty) {\n"
" Fiber.abort(\"From must be a non-empty string.\")\n"
" } else if (!(to is String)) {\n"
" Fiber.abort(\"To must be a string.\")\n"
" }\n"
"\n"
" var result = \"\"\n"
"\n"
" var last = 0\n"
" var index = 0\n"
"\n"
" var fromSize = from.byteCount_\n"
" var size = byteCount_\n"
"\n"
" while (last < size && (index = indexOf(from, last)) != -1) {\n"
" result = result + this[last...index] + to\n"
" last = index + fromSize\n"
" }\n"
"\n"
" if (last < size) result = result + this[last..-1]\n"
"\n"
" return result\n"
" }\n"
"\n"
" trim() { trim_(\"\\t\\r\\n \", true, true) }\n"
" trim(chars) { trim_(chars, true, true) }\n"
" trimEnd() { trim_(\"\\t\\r\\n \", false, true) }\n"
" trimEnd(chars) { trim_(chars, false, true) }\n"
" trimStart() { trim_(\"\\t\\r\\n \", true, false) }\n"
" trimStart(chars) { trim_(chars, true, false) }\n"
"\n"
" trim_(chars, trimStart, trimEnd) {\n"
" if (!(chars is String)) {\n"
" Fiber.abort(\"Characters must be a string.\")\n"
" }\n"
"\n"
" var codePoints = chars.codePoints.toList\n"
"\n"
" var start\n"
" if (trimStart) {\n"
" while (start = iterate(start)) {\n"
" if (!codePoints.contains(codePointAt_(start))) break\n"
" }\n"
"\n"
" if (start == false) return \"\"\n"
" } else {\n"
" start = 0\n"
" }\n"
"\n"
" var end\n"
" if (trimEnd) {\n"
" end = byteCount_ - 1\n"
" while (end >= start) {\n"
" var codePoint = codePointAt_(end)\n"
" if (codePoint != -1 && !codePoints.contains(codePoint)) break\n"
" end = end - 1\n"
" }\n"
"\n"
" if (end < start) return \"\"\n"
" } else {\n"
" end = -1\n"
" }\n"
"\n"
" return this[start..end]\n"
" }\n"
"\n"
" *(count) {\n"
" if (!(count is Num) || !count.isInteger || count < 0) {\n"
" Fiber.abort(\"Count must be a non-negative integer.\")\n"
" }\n"
"\n"
" var result = \"\"\n"
" for (i in 0...count) {\n"
" result = result + this\n"
" }\n"
" return result\n"
" }\n"
"}\n"
"\n"
"class StringByteSequence is Sequence {\n"
" construct new(string) {\n"
" _string = string\n"
" }\n"
"\n"
" [index] { _string.byteAt_(index) }\n"
" iterate(iterator) { _string.iterateByte_(iterator) }\n"
" iteratorValue(iterator) { _string.byteAt_(iterator) }\n"
"\n"
" count { _string.byteCount_ }\n"
"}\n"
"\n"
"class StringCodePointSequence is Sequence {\n"
" construct new(string) {\n"
" _string = string\n"
" }\n"
"\n"
" [index] { _string.codePointAt_(index) }\n"
" iterate(iterator) { _string.iterate(iterator) }\n"
" iteratorValue(iterator) { _string.codePointAt_(iterator) }\n"
"\n"
" count { _string.count }\n"
"}\n"
"\n"
"class List is Sequence {\n"
" addAll(other) {\n"
" for (element in other) {\n"
" add(element)\n"
" }\n"
" return other\n"
" }\n"
"\n"
" sort() { sort {|low, high| low < high } }\n"
"\n"
" sort(comparer) {\n"
" if (!(comparer is Fn)) {\n"
" Fiber.abort(\"Comparer must be a function.\")\n"
" }\n"
" quicksort_(0, count - 1, comparer)\n"
" return this\n"
" }\n"
"\n"
" quicksort_(low, high, comparer) {\n"
" if (low < high) {\n"
" var p = partition_(low, high, comparer)\n"
" quicksort_(low, p - 1, comparer)\n"
" quicksort_(p + 1, high, comparer)\n"
" }\n"
" }\n"
"\n"
" partition_(low, high, comparer) {\n"
" var p = this[high]\n"
" var i = low - 1\n"
" for (j in low..(high-1)) {\n"
" if (comparer.call(this[j], p)) { \n"
" i = i + 1\n"
" var t = this[i]\n"
" this[i] = this[j]\n"
" this[j] = t\n"
" }\n"
" }\n"
" var t = this[i+1]\n"
" this[i+1] = this[high]\n"
" this[high] = t\n"
" return i+1\n"
" }\n"
"\n"
" toString { \"[%(join(\", \"))]\" }\n"
"\n"
" +(other) {\n"
" var result = this[0..-1]\n"
" for (element in other) {\n"
" result.add(element)\n"
" }\n"
" return result\n"
" }\n"
"\n"
" *(count) {\n"
" if (!(count is Num) || !count.isInteger || count < 0) {\n"
" Fiber.abort(\"Count must be a non-negative integer.\")\n"
" }\n"
"\n"
" var result = []\n"
" for (i in 0...count) {\n"
" result.addAll(this)\n"
" }\n"
" return result\n"
" }\n"
"}\n"
"\n"
"class Map is Sequence {\n"
" keys { MapKeySequence.new(this) }\n"
" values { MapValueSequence.new(this) }\n"
"\n"
" toString {\n"
" var first = true\n"
" var result = \"{\"\n"
"\n"
" for (key in keys) {\n"
" if (!first) result = result + \", \"\n"
" first = false\n"
" result = result + \"%(key): %(this[key])\"\n"
" }\n"
"\n"
" return result + \"}\"\n"
" }\n"
"\n"
" iteratorValue(iterator) {\n"
" return MapEntry.new(\n"
" keyIteratorValue_(iterator),\n"
" valueIteratorValue_(iterator))\n"
" }\n"
"}\n"
"\n"
"class MapEntry {\n"
" construct new(key, value) {\n"
" _key = key\n"
" _value = value\n"
" }\n"
"\n"
" key { _key }\n"
" value { _value }\n"
"\n"
" toString { \"%(_key):%(_value)\" }\n"
"}\n"
"\n"
"class MapKeySequence is Sequence {\n"
" construct new(map) {\n"
" _map = map\n"
" }\n"
"\n"
" iterate(n) { _map.iterate(n) }\n"
" iteratorValue(iterator) { _map.keyIteratorValue_(iterator) }\n"
"}\n"
"\n"
"class MapValueSequence is Sequence {\n"
" construct new(map) {\n"
" _map = map\n"
" }\n"
"\n"
" iterate(n) { _map.iterate(n) }\n"
" iteratorValue(iterator) { _map.valueIteratorValue_(iterator) }\n"
"}\n"
"\n"
"class Range is Sequence {}\n"
"\n"
"class System {\n"
" static print() {\n"
" writeString_(\"\\n\")\n"
" }\n"
"\n"
" static print(obj) {\n"
" writeObject_(obj)\n"
" writeString_(\"\\n\")\n"
" return obj\n"
" }\n"
"\n"
" static printAll(sequence) {\n"
" for (object in sequence) writeObject_(object)\n"
" writeString_(\"\\n\")\n"
" }\n"
"\n"
" static write(obj) {\n"
" writeObject_(obj)\n"
" return obj\n"
" }\n"
"\n"
" static writeAll(sequence) {\n"
" for (object in sequence) writeObject_(object)\n"
" }\n"
"\n"
" static writeObject_(obj) {\n"
" var string = obj.toString\n"
" if (string is String) {\n"
" writeString_(string)\n"
" } else {\n"
" writeString_(\"[invalid toString]\")\n"
" }\n"
" }\n"
"}\n"
"\n"
"class ClassAttributes {\n"
" self { _attributes }\n"
" methods { _methods }\n"
" construct new(attributes, methods) {\n"
" _attributes = attributes\n"
" _methods = methods\n"
" }\n"
" toString { \"attributes:%(_attributes) methods:%(_methods)\" }\n"
"}\n";
// End file "wren_core.wren.inc"
DEF_PRIMITIVE(bool_not)
{
RETURN_BOOL(!AS_BOOL(args[0]));
}
DEF_PRIMITIVE(bool_toString)
{
if (AS_BOOL(args[0]))
{
RETURN_VAL(CONST_STRING(vm, "true"));
}
else
{
RETURN_VAL(CONST_STRING(vm, "false"));
}
}
DEF_PRIMITIVE(class_name)
{
RETURN_OBJ(AS_CLASS(args[0])->name);
}
DEF_PRIMITIVE(class_supertype)
{
ObjClass* classObj = AS_CLASS(args[0]);
// Object has no superclass.
if (classObj->superclass == NULL) RETURN_NULL;
RETURN_OBJ(classObj->superclass);
}
DEF_PRIMITIVE(class_toString)
{
RETURN_OBJ(AS_CLASS(args[0])->name);
}
DEF_PRIMITIVE(class_attributes)
{
RETURN_VAL(AS_CLASS(args[0])->attributes);
}
DEF_PRIMITIVE(fiber_new)
{
if (!validateFn(vm, args[1], "Argument")) return false;
ObjClosure* closure = AS_CLOSURE(args[1]);
if (closure->fn->arity > 1)
{
RETURN_ERROR("Function cannot take more than one parameter.");
}
RETURN_OBJ(wrenNewFiber(vm, closure));
}
DEF_PRIMITIVE(fiber_abort)
{
vm->fiber->error = args[1];
// If the error is explicitly null, it's not really an abort.
return IS_NULL(args[1]);
}
// Transfer execution to [fiber] coming from the current fiber whose stack has
// [args].
//
// [isCall] is true if [fiber] is being called and not transferred.
//
// [hasValue] is true if a value in [args] is being passed to the new fiber.
// Otherwise, `null` is implicitly being passed.
static bool runFiber(WrenVM* vm, ObjFiber* fiber, Value* args, bool isCall,
bool hasValue, const char* verb)
{
if (wrenHasError(fiber))
{
RETURN_ERROR_FMT("Cannot $ an aborted fiber.", verb);
}
if (isCall)
{
// You can't call a called fiber, but you can transfer directly to it,
// which is why this check is gated on `isCall`. This way, after resuming a
// suspended fiber, it will run and then return to the fiber that called it
// and so on.
if (fiber->caller != NULL) RETURN_ERROR("Fiber has already been called.");
if (fiber->state == FIBER_ROOT) RETURN_ERROR("Cannot call root fiber.");
// Remember who ran it.
fiber->caller = vm->fiber;
}
if (fiber->numFrames == 0)
{
RETURN_ERROR_FMT("Cannot $ a finished fiber.", verb);
}
// When the calling fiber resumes, we'll store the result of the call in its
// stack. If the call has two arguments (the fiber and the value), we only
// need one slot for the result, so discard the other slot now.
if (hasValue) vm->fiber->stackTop--;
if (fiber->numFrames == 1 &&
fiber->frames[0].ip == fiber->frames[0].closure->fn->code.data)
{
// The fiber is being started for the first time. If its function takes a
// parameter, bind an argument to it.
if (fiber->frames[0].closure->fn->arity == 1)
{
fiber->stackTop[0] = hasValue ? args[1] : NULL_VAL;
fiber->stackTop++;
}
}
else
{
// The fiber is being resumed, make yield() or transfer() return the result.
fiber->stackTop[-1] = hasValue ? args[1] : NULL_VAL;
}
vm->fiber = fiber;
return false;
}
DEF_PRIMITIVE(fiber_call)
{
return runFiber(vm, AS_FIBER(args[0]), args, true, false, "call");
}
DEF_PRIMITIVE(fiber_call1)
{
return runFiber(vm, AS_FIBER(args[0]), args, true, true, "call");
}
DEF_PRIMITIVE(fiber_current)
{
RETURN_OBJ(vm->fiber);
}
DEF_PRIMITIVE(fiber_error)
{
RETURN_VAL(AS_FIBER(args[0])->error);
}
DEF_PRIMITIVE(fiber_isDone)
{
ObjFiber* runFiber = AS_FIBER(args[0]);
RETURN_BOOL(runFiber->numFrames == 0 || wrenHasError(runFiber));
}
DEF_PRIMITIVE(fiber_suspend)
{
// Switching to a null fiber tells the interpreter to stop and exit.
vm->fiber = NULL;
vm->apiStack = NULL;
return false;
}
DEF_PRIMITIVE(fiber_transfer)
{
return runFiber(vm, AS_FIBER(args[0]), args, false, false, "transfer to");
}
DEF_PRIMITIVE(fiber_transfer1)
{
return runFiber(vm, AS_FIBER(args[0]), args, false, true, "transfer to");
}
DEF_PRIMITIVE(fiber_transferError)
{
runFiber(vm, AS_FIBER(args[0]), args, false, true, "transfer to");
vm->fiber->error = args[1];
return false;
}
DEF_PRIMITIVE(fiber_try)
{
runFiber(vm, AS_FIBER(args[0]), args, true, false, "try");
// If we're switching to a valid fiber to try, remember that we're trying it.
if (!wrenHasError(vm->fiber)) vm->fiber->state = FIBER_TRY;
return false;
}
DEF_PRIMITIVE(fiber_try1)
{
runFiber(vm, AS_FIBER(args[0]), args, true, true, "try");
// If we're switching to a valid fiber to try, remember that we're trying it.
if (!wrenHasError(vm->fiber)) vm->fiber->state = FIBER_TRY;
return false;
}
DEF_PRIMITIVE(fiber_yield)
{
ObjFiber* current = vm->fiber;
vm->fiber = current->caller;
// Unhook this fiber from the one that called it.
current->caller = NULL;
current->state = FIBER_OTHER;
if (vm->fiber != NULL)
{
// Make the caller's run method return null.
vm->fiber->stackTop[-1] = NULL_VAL;
}
return false;
}
DEF_PRIMITIVE(fiber_yield1)
{
ObjFiber* current = vm->fiber;
vm->fiber = current->caller;
// Unhook this fiber from the one that called it.
current->caller = NULL;
current->state = FIBER_OTHER;
if (vm->fiber != NULL)
{
// Make the caller's run method return the argument passed to yield.
vm->fiber->stackTop[-1] = args[1];
// When the yielding fiber resumes, we'll store the result of the yield
// call in its stack. Since Fiber.yield(value) has two arguments (the Fiber
// class and the value) and we only need one slot for the result, discard
// the other slot now.
current->stackTop--;
}
return false;
}
DEF_PRIMITIVE(fn_new)
{
if (!validateFn(vm, args[1], "Argument")) return false;
// The block argument is already a function, so just return it.
RETURN_VAL(args[1]);
}
DEF_PRIMITIVE(fn_arity)
{
RETURN_NUM(AS_CLOSURE(args[0])->fn->arity);
}
static void call_fn(WrenVM* vm, Value* args, int numArgs)
{
// +1 to include the function itself.
wrenCallFunction(vm, vm->fiber, AS_CLOSURE(args[0]), numArgs + 1);
}
#define DEF_FN_CALL(numArgs) \
DEF_PRIMITIVE(fn_call##numArgs) \
{ \
call_fn(vm, args, numArgs); \
return false; \
}
DEF_FN_CALL(0)
DEF_FN_CALL(1)
DEF_FN_CALL(2)
DEF_FN_CALL(3)
DEF_FN_CALL(4)
DEF_FN_CALL(5)
DEF_FN_CALL(6)
DEF_FN_CALL(7)
DEF_FN_CALL(8)
DEF_FN_CALL(9)
DEF_FN_CALL(10)
DEF_FN_CALL(11)
DEF_FN_CALL(12)
DEF_FN_CALL(13)
DEF_FN_CALL(14)
DEF_FN_CALL(15)
DEF_FN_CALL(16)
DEF_PRIMITIVE(fn_toString)
{
RETURN_VAL(CONST_STRING(vm, "<fn>"));
}
// Creates a new list of size args[1], with all elements initialized to args[2].
DEF_PRIMITIVE(list_filled)
{
if (!validateInt(vm, args[1], "Size")) return false;
if (AS_NUM(args[1]) < 0) RETURN_ERROR("Size cannot be negative.");
uint32_t size = (uint32_t)AS_NUM(args[1]);
ObjList* list = wrenNewList(vm, size);
for (uint32_t i = 0; i < size; i++)
{
list->elements.data[i] = args[2];
}
RETURN_OBJ(list);
}
DEF_PRIMITIVE(list_new)
{
RETURN_OBJ(wrenNewList(vm, 0));
}
DEF_PRIMITIVE(list_add)
{
wrenValueBufferWrite(vm, &AS_LIST(args[0])->elements, args[1]);
RETURN_VAL(args[1]);
}
// Adds an element to the list and then returns the list itself. This is called
// by the compiler when compiling list literals instead of using add() to
// minimize stack churn.
DEF_PRIMITIVE(list_addCore)
{
wrenValueBufferWrite(vm, &AS_LIST(args[0])->elements, args[1]);
// Return the list.
RETURN_VAL(args[0]);
}
DEF_PRIMITIVE(list_clear)
{
wrenValueBufferClear(vm, &AS_LIST(args[0])->elements);
RETURN_NULL;
}
DEF_PRIMITIVE(list_count)
{
RETURN_NUM(AS_LIST(args[0])->elements.count);
}
DEF_PRIMITIVE(list_insert)
{
ObjList* list = AS_LIST(args[0]);
// count + 1 here so you can "insert" at the very end.
uint32_t index = validateIndex(vm, args[1], list->elements.count + 1,
"Index");
if (index == UINT32_MAX) return false;
wrenListInsert(vm, list, args[2], index);
RETURN_VAL(args[2]);
}
DEF_PRIMITIVE(list_iterate)
{
ObjList* list = AS_LIST(args[0]);
// If we're starting the iteration, return the first index.
if (IS_NULL(args[1]))
{
if (list->elements.count == 0) RETURN_FALSE;
RETURN_NUM(0);
}
if (!validateInt(vm, args[1], "Iterator")) return false;
// Stop if we're out of bounds.
double index = AS_NUM(args[1]);
if (index < 0 || index >= list->elements.count - 1) RETURN_FALSE;
// Otherwise, move to the next index.
RETURN_NUM(index + 1);
}
DEF_PRIMITIVE(list_iteratorValue)
{
ObjList* list = AS_LIST(args[0]);
uint32_t index = validateIndex(vm, args[1], list->elements.count, "Iterator");
if (index == UINT32_MAX) return false;
RETURN_VAL(list->elements.data[index]);
}
DEF_PRIMITIVE(list_removeAt)
{
ObjList* list = AS_LIST(args[0]);
uint32_t index = validateIndex(vm, args[1], list->elements.count, "Index");
if (index == UINT32_MAX) return false;
RETURN_VAL(wrenListRemoveAt(vm, list, index));
}
DEF_PRIMITIVE(list_removeValue) {
ObjList* list = AS_LIST(args[0]);
int index = wrenListIndexOf(vm, list, args[1]);
if(index == -1) RETURN_NULL;
RETURN_VAL(wrenListRemoveAt(vm, list, index));
}
DEF_PRIMITIVE(list_indexOf)
{
ObjList* list = AS_LIST(args[0]);
RETURN_NUM(wrenListIndexOf(vm, list, args[1]));
}
DEF_PRIMITIVE(list_swap)
{
ObjList* list = AS_LIST(args[0]);
uint32_t indexA = validateIndex(vm, args[1], list->elements.count, "Index 0");
if (indexA == UINT32_MAX) return false;
uint32_t indexB = validateIndex(vm, args[2], list->elements.count, "Index 1");
if (indexB == UINT32_MAX) return false;
Value a = list->elements.data[indexA];
list->elements.data[indexA] = list->elements.data[indexB];
list->elements.data[indexB] = a;
RETURN_NULL;
}
DEF_PRIMITIVE(list_subscript)
{
ObjList* list = AS_LIST(args[0]);
if (IS_NUM(args[1]))
{
uint32_t index = validateIndex(vm, args[1], list->elements.count,
"Subscript");
if (index == UINT32_MAX) return false;
RETURN_VAL(list->elements.data[index]);
}
if (!IS_RANGE(args[1]))
{
RETURN_ERROR("Subscript must be a number or a range.");
}
int step;
uint32_t count = list->elements.count;
uint32_t start = calculateRange(vm, AS_RANGE(args[1]), &count, &step);
if (start == UINT32_MAX) return false;
ObjList* result = wrenNewList(vm, count);
for (uint32_t i = 0; i < count; i++)
{
result->elements.data[i] = list->elements.data[start + i * step];
}
RETURN_OBJ(result);
}
DEF_PRIMITIVE(list_subscriptSetter)
{
ObjList* list = AS_LIST(args[0]);
uint32_t index = validateIndex(vm, args[1], list->elements.count,
"Subscript");
if (index == UINT32_MAX) return false;
list->elements.data[index] = args[2];
RETURN_VAL(args[2]);
}
DEF_PRIMITIVE(map_new)
{
RETURN_OBJ(wrenNewMap(vm));
}
DEF_PRIMITIVE(map_subscript)
{
if (!validateKey(vm, args[1])) return false;
ObjMap* map = AS_MAP(args[0]);
Value value = wrenMapGet(map, args[1]);
if (IS_UNDEFINED(value)) RETURN_NULL;
RETURN_VAL(value);
}
DEF_PRIMITIVE(map_subscriptSetter)
{
if (!validateKey(vm, args[1])) return false;
wrenMapSet(vm, AS_MAP(args[0]), args[1], args[2]);
RETURN_VAL(args[2]);
}
// Adds an entry to the map and then returns the map itself. This is called by
// the compiler when compiling map literals instead of using [_]=(_) to
// minimize stack churn.
DEF_PRIMITIVE(map_addCore)
{
if (!validateKey(vm, args[1])) return false;
wrenMapSet(vm, AS_MAP(args[0]), args[1], args[2]);
// Return the map itself.
RETURN_VAL(args[0]);
}
DEF_PRIMITIVE(map_clear)
{
wrenMapClear(vm, AS_MAP(args[0]));
RETURN_NULL;
}
DEF_PRIMITIVE(map_containsKey)
{
if (!validateKey(vm, args[1])) return false;
RETURN_BOOL(!IS_UNDEFINED(wrenMapGet(AS_MAP(args[0]), args[1])));
}
DEF_PRIMITIVE(map_count)
{
RETURN_NUM(AS_MAP(args[0])->count);
}
DEF_PRIMITIVE(map_iterate)
{
ObjMap* map = AS_MAP(args[0]);
if (map->count == 0) RETURN_FALSE;
// If we're starting the iteration, start at the first used entry.
uint32_t index = 0;
// Otherwise, start one past the last entry we stopped at.
if (!IS_NULL(args[1]))
{
if (!validateInt(vm, args[1], "Iterator")) return false;
if (AS_NUM(args[1]) < 0) RETURN_FALSE;
index = (uint32_t)AS_NUM(args[1]);
if (index >= map->capacity) RETURN_FALSE;
// Advance the iterator.
index++;
}
// Find a used entry, if any.
for (; index < map->capacity; index++)
{
if (!IS_UNDEFINED(map->entries[index].key)) RETURN_NUM(index);
}
// If we get here, walked all of the entries.
RETURN_FALSE;
}
DEF_PRIMITIVE(map_remove)
{
if (!validateKey(vm, args[1])) return false;
RETURN_VAL(wrenMapRemoveKey(vm, AS_MAP(args[0]), args[1]));
}
DEF_PRIMITIVE(map_keyIteratorValue)
{
ObjMap* map = AS_MAP(args[0]);
uint32_t index = validateIndex(vm, args[1], map->capacity, "Iterator");
if (index == UINT32_MAX) return false;
MapEntry* entry = &map->entries[index];
if (IS_UNDEFINED(entry->key))
{
RETURN_ERROR("Invalid map iterator.");
}
RETURN_VAL(entry->key);
}
DEF_PRIMITIVE(map_valueIteratorValue)
{
ObjMap* map = AS_MAP(args[0]);
uint32_t index = validateIndex(vm, args[1], map->capacity, "Iterator");
if (index == UINT32_MAX) return false;
MapEntry* entry = &map->entries[index];
if (IS_UNDEFINED(entry->key))
{
RETURN_ERROR("Invalid map iterator.");
}
RETURN_VAL(entry->value);
}
DEF_PRIMITIVE(null_not)
{
RETURN_VAL(TRUE_VAL);
}
DEF_PRIMITIVE(null_toString)
{
RETURN_VAL(CONST_STRING(vm, "null"));
}
DEF_PRIMITIVE(num_fromString)
{
if (!validateString(vm, args[1], "Argument")) return false;
ObjString* string = AS_STRING(args[1]);
// Corner case: Can't parse an empty string.
if (string->length == 0) RETURN_NULL;
errno = 0;
char* end;
double number = strtod(string->value, &end);
// Skip past any trailing whitespace.
while (*end != '\0' && isspace((unsigned char)*end)) end++;
if (errno == ERANGE) RETURN_ERROR("Number literal is too large.");
// We must have consumed the entire string. Otherwise, it contains non-number
// characters and we can't parse it.
if (end < string->value + string->length) RETURN_NULL;
RETURN_NUM(number);
}
// Defines a primitive on Num that calls infix [op] and returns [type].
#define DEF_NUM_CONSTANT(name, value) \
DEF_PRIMITIVE(num_##name) \
{ \
RETURN_NUM(value); \
}
DEF_NUM_CONSTANT(infinity, INFINITY)
DEF_NUM_CONSTANT(nan, WREN_DOUBLE_NAN)
DEF_NUM_CONSTANT(pi, 3.14159265358979323846264338327950288)
DEF_NUM_CONSTANT(tau, 6.28318530717958647692528676655900577)
DEF_NUM_CONSTANT(largest, DBL_MAX)
DEF_NUM_CONSTANT(smallest, DBL_MIN)
DEF_NUM_CONSTANT(maxSafeInteger, 9007199254740991.0)
DEF_NUM_CONSTANT(minSafeInteger, -9007199254740991.0)
// Defines a primitive on Num that calls infix [op] and returns [type].
#define DEF_NUM_INFIX(name, op, type) \
DEF_PRIMITIVE(num_##name) \
{ \
if (!validateNum(vm, args[1], "Right operand")) return false; \
RETURN_##type(AS_NUM(args[0]) op AS_NUM(args[1])); \
}
DEF_NUM_INFIX(minus, -, NUM)
DEF_NUM_INFIX(plus, +, NUM)
DEF_NUM_INFIX(multiply, *, NUM)
DEF_NUM_INFIX(divide, /, NUM)
DEF_NUM_INFIX(lt, <, BOOL)
DEF_NUM_INFIX(gt, >, BOOL)
DEF_NUM_INFIX(lte, <=, BOOL)
DEF_NUM_INFIX(gte, >=, BOOL)
// Defines a primitive on Num that call infix bitwise [op].
#define DEF_NUM_BITWISE(name, op) \
DEF_PRIMITIVE(num_bitwise##name) \
{ \
if (!validateNum(vm, args[1], "Right operand")) return false; \
uint32_t left = (uint32_t)AS_NUM(args[0]); \
uint32_t right = (uint32_t)AS_NUM(args[1]); \
RETURN_NUM(left op right); \
}
DEF_NUM_BITWISE(And, &)
DEF_NUM_BITWISE(Or, |)
DEF_NUM_BITWISE(Xor, ^)
DEF_NUM_BITWISE(LeftShift, <<)
DEF_NUM_BITWISE(RightShift, >>)
// Defines a primitive method on Num that returns the result of [fn].
#define DEF_NUM_FN(name, fn) \
DEF_PRIMITIVE(num_##name) \
{ \
RETURN_NUM(fn(AS_NUM(args[0]))); \
}
DEF_NUM_FN(abs, fabs)
DEF_NUM_FN(acos, acos)
DEF_NUM_FN(asin, asin)
DEF_NUM_FN(atan, atan)
DEF_NUM_FN(cbrt, cbrt)
DEF_NUM_FN(ceil, ceil)
DEF_NUM_FN(cos, cos)
DEF_NUM_FN(floor, floor)
DEF_NUM_FN(negate, -)
DEF_NUM_FN(round, round)
DEF_NUM_FN(sin, sin)
DEF_NUM_FN(sqrt, sqrt)
DEF_NUM_FN(tan, tan)
DEF_NUM_FN(log, log)
DEF_NUM_FN(log2, log2)
DEF_NUM_FN(exp, exp)
DEF_PRIMITIVE(num_mod)
{
if (!validateNum(vm, args[1], "Right operand")) return false;
RETURN_NUM(fmod(AS_NUM(args[0]), AS_NUM(args[1])));
}
DEF_PRIMITIVE(num_eqeq)
{
if (!IS_NUM(args[1])) RETURN_FALSE;
RETURN_BOOL(AS_NUM(args[0]) == AS_NUM(args[1]));
}
DEF_PRIMITIVE(num_bangeq)
{
if (!IS_NUM(args[1])) RETURN_TRUE;
RETURN_BOOL(AS_NUM(args[0]) != AS_NUM(args[1]));
}
DEF_PRIMITIVE(num_bitwiseNot)
{
// Bitwise operators always work on 32-bit unsigned ints.
RETURN_NUM(~(uint32_t)AS_NUM(args[0]));
}
DEF_PRIMITIVE(num_dotDot)
{
if (!validateNum(vm, args[1], "Right hand side of range")) return false;
double from = AS_NUM(args[0]);
double to = AS_NUM(args[1]);
RETURN_VAL(wrenNewRange(vm, from, to, true));
}
DEF_PRIMITIVE(num_dotDotDot)
{
if (!validateNum(vm, args[1], "Right hand side of range")) return false;
double from = AS_NUM(args[0]);
double to = AS_NUM(args[1]);
RETURN_VAL(wrenNewRange(vm, from, to, false));
}
DEF_PRIMITIVE(num_atan2)
{
if (!validateNum(vm, args[1], "x value")) return false;
RETURN_NUM(atan2(AS_NUM(args[0]), AS_NUM(args[1])));
}
DEF_PRIMITIVE(num_min)
{
if (!validateNum(vm, args[1], "Other value")) return false;
double value = AS_NUM(args[0]);
double other = AS_NUM(args[1]);
RETURN_NUM(value <= other ? value : other);
}
DEF_PRIMITIVE(num_max)
{
if (!validateNum(vm, args[1], "Other value")) return false;
double value = AS_NUM(args[0]);
double other = AS_NUM(args[1]);
RETURN_NUM(value > other ? value : other);
}
DEF_PRIMITIVE(num_clamp)
{
if (!validateNum(vm, args[1], "Min value")) return false;
if (!validateNum(vm, args[2], "Max value")) return false;
double value = AS_NUM(args[0]);
double min = AS_NUM(args[1]);
double max = AS_NUM(args[2]);
double result = (value < min) ? min : ((value > max) ? max : value);
RETURN_NUM(result);
}
DEF_PRIMITIVE(num_pow)
{
if (!validateNum(vm, args[1], "Power value")) return false;
RETURN_NUM(pow(AS_NUM(args[0]), AS_NUM(args[1])));
}
DEF_PRIMITIVE(num_fraction)
{
double unused;
RETURN_NUM(modf(AS_NUM(args[0]) , &unused));
}
DEF_PRIMITIVE(num_isInfinity)
{
RETURN_BOOL(isinf(AS_NUM(args[0])));
}
DEF_PRIMITIVE(num_isInteger)
{
double value = AS_NUM(args[0]);
if (isnan(value) || isinf(value)) RETURN_FALSE;
RETURN_BOOL(trunc(value) == value);
}
DEF_PRIMITIVE(num_isNan)
{
RETURN_BOOL(isnan(AS_NUM(args[0])));
}
DEF_PRIMITIVE(num_sign)
{
double value = AS_NUM(args[0]);
if (value > 0)
{
RETURN_NUM(1);
}
else if (value < 0)
{
RETURN_NUM(-1);
}
else
{
RETURN_NUM(0);
}
}
DEF_PRIMITIVE(num_toString)
{
RETURN_VAL(wrenNumToString(vm, AS_NUM(args[0])));
}
DEF_PRIMITIVE(num_truncate)
{
double integer;
modf(AS_NUM(args[0]) , &integer);
RETURN_NUM(integer);
}
DEF_PRIMITIVE(object_same)
{
RETURN_BOOL(wrenValuesEqual(args[1], args[2]));
}
DEF_PRIMITIVE(object_not)
{
RETURN_VAL(FALSE_VAL);
}
DEF_PRIMITIVE(object_eqeq)
{
RETURN_BOOL(wrenValuesEqual(args[0], args[1]));
}
DEF_PRIMITIVE(object_bangeq)
{
RETURN_BOOL(!wrenValuesEqual(args[0], args[1]));
}
DEF_PRIMITIVE(object_is)
{
if (!IS_CLASS(args[1]))
{
RETURN_ERROR("Right operand must be a class.");
}
ObjClass *classObj = wrenGetClass(vm, args[0]);
ObjClass *baseClassObj = AS_CLASS(args[1]);
// Walk the superclass chain looking for the class.
do
{
if (baseClassObj == classObj) RETURN_BOOL(true);
classObj = classObj->superclass;
}
while (classObj != NULL);
RETURN_BOOL(false);
}
DEF_PRIMITIVE(object_toString)
{
Obj* obj = AS_OBJ(args[0]);
Value name = OBJ_VAL(obj->classObj->name);
RETURN_VAL(wrenStringFormat(vm, "instance of @", name));
}
DEF_PRIMITIVE(object_type)
{
RETURN_OBJ(wrenGetClass(vm, args[0]));
}
DEF_PRIMITIVE(range_from)
{
RETURN_NUM(AS_RANGE(args[0])->from);
}
DEF_PRIMITIVE(range_to)
{
RETURN_NUM(AS_RANGE(args[0])->to);
}
DEF_PRIMITIVE(range_min)
{
ObjRange* range = AS_RANGE(args[0]);
RETURN_NUM(fmin(range->from, range->to));
}
DEF_PRIMITIVE(range_max)
{
ObjRange* range = AS_RANGE(args[0]);
RETURN_NUM(fmax(range->from, range->to));
}
DEF_PRIMITIVE(range_isInclusive)
{
RETURN_BOOL(AS_RANGE(args[0])->isInclusive);
}
DEF_PRIMITIVE(range_iterate)
{
ObjRange* range = AS_RANGE(args[0]);
// Special case: empty range.
if (range->from == range->to && !range->isInclusive) RETURN_FALSE;
// Start the iteration.
if (IS_NULL(args[1])) RETURN_NUM(range->from);
if (!validateNum(vm, args[1], "Iterator")) return false;
double iterator = AS_NUM(args[1]);
// Iterate towards [to] from [from].
if (range->from < range->to)
{
iterator++;
if (iterator > range->to) RETURN_FALSE;
}
else
{
iterator--;
if (iterator < range->to) RETURN_FALSE;
}
if (!range->isInclusive && iterator == range->to) RETURN_FALSE;
RETURN_NUM(iterator);
}
DEF_PRIMITIVE(range_iteratorValue)
{
// Assume the iterator is a number so that is the value of the range.
RETURN_VAL(args[1]);
}
DEF_PRIMITIVE(range_toString)
{
ObjRange* range = AS_RANGE(args[0]);
Value from = wrenNumToString(vm, range->from);
wrenPushRoot(vm, AS_OBJ(from));
Value to = wrenNumToString(vm, range->to);
wrenPushRoot(vm, AS_OBJ(to));
Value result = wrenStringFormat(vm, "@$@", from,
range->isInclusive ? ".." : "...", to);
wrenPopRoot(vm);
wrenPopRoot(vm);
RETURN_VAL(result);
}
DEF_PRIMITIVE(string_fromCodePoint)
{
if (!validateInt(vm, args[1], "Code point")) return false;
int codePoint = (int)AS_NUM(args[1]);
if (codePoint < 0)
{
RETURN_ERROR("Code point cannot be negative.");
}
else if (codePoint > 0x10ffff)
{
RETURN_ERROR("Code point cannot be greater than 0x10ffff.");
}
RETURN_VAL(wrenStringFromCodePoint(vm, codePoint));
}
DEF_PRIMITIVE(string_fromByte)
{
if (!validateInt(vm, args[1], "Byte")) return false;
int byte = (int) AS_NUM(args[1]);
if (byte < 0)
{
RETURN_ERROR("Byte cannot be negative.");
}
else if (byte > 0xff)
{
RETURN_ERROR("Byte cannot be greater than 0xff.");
}
RETURN_VAL(wrenStringFromByte(vm, (uint8_t) byte));
}
DEF_PRIMITIVE(string_byteAt)
{
ObjString* string = AS_STRING(args[0]);
uint32_t index = validateIndex(vm, args[1], string->length, "Index");
if (index == UINT32_MAX) return false;
RETURN_NUM((uint8_t)string->value[index]);
}
DEF_PRIMITIVE(string_byteCount)
{
RETURN_NUM(AS_STRING(args[0])->length);
}
DEF_PRIMITIVE(string_codePointAt)
{
ObjString* string = AS_STRING(args[0]);
uint32_t index = validateIndex(vm, args[1], string->length, "Index");
if (index == UINT32_MAX) return false;
// If we are in the middle of a UTF-8 sequence, indicate that.
const uint8_t* bytes = (uint8_t*)string->value;
if ((bytes[index] & 0xc0) == 0x80) RETURN_NUM(-1);
// Decode the UTF-8 sequence.
RETURN_NUM(wrenUtf8Decode((uint8_t*)string->value + index,
string->length - index));
}
DEF_PRIMITIVE(string_contains)
{
if (!validateString(vm, args[1], "Argument")) return false;
ObjString* string = AS_STRING(args[0]);
ObjString* search = AS_STRING(args[1]);
RETURN_BOOL(wrenStringFind(string, search, 0) != UINT32_MAX);
}
DEF_PRIMITIVE(string_endsWith)
{
if (!validateString(vm, args[1], "Argument")) return false;
ObjString* string = AS_STRING(args[0]);
ObjString* search = AS_STRING(args[1]);
// Edge case: If the search string is longer then return false right away.
if (search->length > string->length) RETURN_FALSE;
RETURN_BOOL(memcmp(string->value + string->length - search->length,
search->value, search->length) == 0);
}
DEF_PRIMITIVE(string_indexOf1)
{
if (!validateString(vm, args[1], "Argument")) return false;
ObjString* string = AS_STRING(args[0]);
ObjString* search = AS_STRING(args[1]);
uint32_t index = wrenStringFind(string, search, 0);
RETURN_NUM(index == UINT32_MAX ? -1 : (int)index);
}
DEF_PRIMITIVE(string_indexOf2)
{
if (!validateString(vm, args[1], "Argument")) return false;
ObjString* string = AS_STRING(args[0]);
ObjString* search = AS_STRING(args[1]);
uint32_t start = validateIndex(vm, args[2], string->length, "Start");
if (start == UINT32_MAX) return false;
uint32_t index = wrenStringFind(string, search, start);
RETURN_NUM(index == UINT32_MAX ? -1 : (int)index);
}
DEF_PRIMITIVE(string_iterate)
{
ObjString* string = AS_STRING(args[0]);
// If we're starting the iteration, return the first index.
if (IS_NULL(args[1]))
{
if (string->length == 0) RETURN_FALSE;
RETURN_NUM(0);
}
if (!validateInt(vm, args[1], "Iterator")) return false;
if (AS_NUM(args[1]) < 0) RETURN_FALSE;
uint32_t index = (uint32_t)AS_NUM(args[1]);
// Advance to the beginning of the next UTF-8 sequence.
do
{
index++;
if (index >= string->length) RETURN_FALSE;
} while ((string->value[index] & 0xc0) == 0x80);
RETURN_NUM(index);
}
DEF_PRIMITIVE(string_iterateByte)
{
ObjString* string = AS_STRING(args[0]);
// If we're starting the iteration, return the first index.
if (IS_NULL(args[1]))
{
if (string->length == 0) RETURN_FALSE;
RETURN_NUM(0);
}
if (!validateInt(vm, args[1], "Iterator")) return false;
if (AS_NUM(args[1]) < 0) RETURN_FALSE;
uint32_t index = (uint32_t)AS_NUM(args[1]);
// Advance to the next byte.
index++;
if (index >= string->length) RETURN_FALSE;
RETURN_NUM(index);
}
DEF_PRIMITIVE(string_iteratorValue)
{
ObjString* string = AS_STRING(args[0]);
uint32_t index = validateIndex(vm, args[1], string->length, "Iterator");
if (index == UINT32_MAX) return false;
RETURN_VAL(wrenStringCodePointAt(vm, string, index));
}
DEF_PRIMITIVE(string_startsWith)
{
if (!validateString(vm, args[1], "Argument")) return false;
ObjString* string = AS_STRING(args[0]);
ObjString* search = AS_STRING(args[1]);
// Edge case: If the search string is longer then return false right away.
if (search->length > string->length) RETURN_FALSE;
RETURN_BOOL(memcmp(string->value, search->value, search->length) == 0);
}
DEF_PRIMITIVE(string_plus)
{
if (!validateString(vm, args[1], "Right operand")) return false;
RETURN_VAL(wrenStringFormat(vm, "@@", args[0], args[1]));
}
DEF_PRIMITIVE(string_subscript)
{
ObjString* string = AS_STRING(args[0]);
if (IS_NUM(args[1]))
{
int index = validateIndex(vm, args[1], string->length, "Subscript");
if (index == -1) return false;
RETURN_VAL(wrenStringCodePointAt(vm, string, index));
}
if (!IS_RANGE(args[1]))
{
RETURN_ERROR("Subscript must be a number or a range.");
}
int step;
uint32_t count = string->length;
int start = calculateRange(vm, AS_RANGE(args[1]), &count, &step);
if (start == -1) return false;
RETURN_VAL(wrenNewStringFromRange(vm, string, start, count, step));
}
DEF_PRIMITIVE(string_toString)
{
RETURN_VAL(args[0]);
}
DEF_PRIMITIVE(system_clock)
{
RETURN_NUM((double)clock() / CLOCKS_PER_SEC);
}
DEF_PRIMITIVE(system_gc)
{
wrenCollectGarbage(vm);
RETURN_NULL;
}
DEF_PRIMITIVE(system_writeString)
{
if (vm->config.writeFn != NULL)
{
vm->config.writeFn(vm, AS_CSTRING(args[1]));
}
RETURN_VAL(args[1]);
}
// Creates either the Object or Class class in the core module with [name].
static ObjClass* defineClass(WrenVM* vm, ObjModule* module, const char* name)
{
ObjString* nameString = AS_STRING(wrenNewString(vm, name));
wrenPushRoot(vm, (Obj*)nameString);
ObjClass* classObj = wrenNewSingleClass(vm, 0, nameString);
wrenDefineVariable(vm, module, name, nameString->length, OBJ_VAL(classObj), NULL);
wrenPopRoot(vm);
return classObj;
}
void wrenInitializeCore(WrenVM* vm)
{
ObjModule* coreModule = wrenNewModule(vm, NULL);
wrenPushRoot(vm, (Obj*)coreModule);
// The core module's key is null in the module map.
wrenMapSet(vm, vm->modules, NULL_VAL, OBJ_VAL(coreModule));
wrenPopRoot(vm); // coreModule.
// Define the root Object class. This has to be done a little specially
// because it has no superclass.
vm->objectClass = defineClass(vm, coreModule, "Object");
PRIMITIVE(vm->objectClass, "!", object_not);
PRIMITIVE(vm->objectClass, "==(_)", object_eqeq);
PRIMITIVE(vm->objectClass, "!=(_)", object_bangeq);
PRIMITIVE(vm->objectClass, "is(_)", object_is);
PRIMITIVE(vm->objectClass, "toString", object_toString);
PRIMITIVE(vm->objectClass, "type", object_type);
// Now we can define Class, which is a subclass of Object.
vm->classClass = defineClass(vm, coreModule, "Class");
wrenBindSuperclass(vm, vm->classClass, vm->objectClass);
PRIMITIVE(vm->classClass, "name", class_name);
PRIMITIVE(vm->classClass, "supertype", class_supertype);
PRIMITIVE(vm->classClass, "toString", class_toString);
PRIMITIVE(vm->classClass, "attributes", class_attributes);
// Finally, we can define Object's metaclass which is a subclass of Class.
ObjClass* objectMetaclass = defineClass(vm, coreModule, "Object metaclass");
// Wire up the metaclass relationships now that all three classes are built.
vm->objectClass->obj.classObj = objectMetaclass;
objectMetaclass->obj.classObj = vm->classClass;
vm->classClass->obj.classObj = vm->classClass;
// Do this after wiring up the metaclasses so objectMetaclass doesn't get
// collected.
wrenBindSuperclass(vm, objectMetaclass, vm->classClass);
PRIMITIVE(objectMetaclass, "same(_,_)", object_same);
// The core class diagram ends up looking like this, where single lines point
// to a class's superclass, and double lines point to its metaclass:
//
// .------------------------------------. .====.
// | .---------------. | # #
// v | v | v #
// .---------. .-------------------. .-------. #
// | Object |==>| Object metaclass |==>| Class |=="
// '---------' '-------------------' '-------'
// ^ ^ ^ ^ ^
// | .--------------' # | #
// | | # | #
// .---------. .-------------------. # | # -.
// | Base |==>| Base metaclass |======" | # |
// '---------' '-------------------' | # |
// ^ | # |
// | .------------------' # | Example classes
// | | # |
// .---------. .-------------------. # |
// | Derived |==>| Derived metaclass |==========" |
// '---------' '-------------------' -'
// The rest of the classes can now be defined normally.
wrenInterpret(vm, NULL, coreModuleSource);
vm->boolClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Bool"));
PRIMITIVE(vm->boolClass, "toString", bool_toString);
PRIMITIVE(vm->boolClass, "!", bool_not);
vm->fiberClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Fiber"));
PRIMITIVE(vm->fiberClass->obj.classObj, "new(_)", fiber_new);
PRIMITIVE(vm->fiberClass->obj.classObj, "abort(_)", fiber_abort);
PRIMITIVE(vm->fiberClass->obj.classObj, "current", fiber_current);
PRIMITIVE(vm->fiberClass->obj.classObj, "suspend()", fiber_suspend);
PRIMITIVE(vm->fiberClass->obj.classObj, "yield()", fiber_yield);
PRIMITIVE(vm->fiberClass->obj.classObj, "yield(_)", fiber_yield1);
PRIMITIVE(vm->fiberClass, "call()", fiber_call);
PRIMITIVE(vm->fiberClass, "call(_)", fiber_call1);
PRIMITIVE(vm->fiberClass, "error", fiber_error);
PRIMITIVE(vm->fiberClass, "isDone", fiber_isDone);
PRIMITIVE(vm->fiberClass, "transfer()", fiber_transfer);
PRIMITIVE(vm->fiberClass, "transfer(_)", fiber_transfer1);
PRIMITIVE(vm->fiberClass, "transferError(_)", fiber_transferError);
PRIMITIVE(vm->fiberClass, "try()", fiber_try);
PRIMITIVE(vm->fiberClass, "try(_)", fiber_try1);
vm->fnClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Fn"));
PRIMITIVE(vm->fnClass->obj.classObj, "new(_)", fn_new);
PRIMITIVE(vm->fnClass, "arity", fn_arity);
FUNCTION_CALL(vm->fnClass, "call()", fn_call0);
FUNCTION_CALL(vm->fnClass, "call(_)", fn_call1);
FUNCTION_CALL(vm->fnClass, "call(_,_)", fn_call2);
FUNCTION_CALL(vm->fnClass, "call(_,_,_)", fn_call3);
FUNCTION_CALL(vm->fnClass, "call(_,_,_,_)", fn_call4);
FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_)", fn_call5);
FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_)", fn_call6);
FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_)", fn_call7);
FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_)", fn_call8);
FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_)", fn_call9);
FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_)", fn_call10);
FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_)", fn_call11);
FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_)", fn_call12);
FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_,_)", fn_call13);
FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_,_,_)", fn_call14);
FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_,_,_,_)", fn_call15);
FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_)", fn_call16);
PRIMITIVE(vm->fnClass, "toString", fn_toString);
vm->nullClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Null"));
PRIMITIVE(vm->nullClass, "!", null_not);
PRIMITIVE(vm->nullClass, "toString", null_toString);
vm->numClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Num"));
PRIMITIVE(vm->numClass->obj.classObj, "fromString(_)", num_fromString);
PRIMITIVE(vm->numClass->obj.classObj, "infinity", num_infinity);
PRIMITIVE(vm->numClass->obj.classObj, "nan", num_nan);
PRIMITIVE(vm->numClass->obj.classObj, "pi", num_pi);
PRIMITIVE(vm->numClass->obj.classObj, "tau", num_tau);
PRIMITIVE(vm->numClass->obj.classObj, "largest", num_largest);
PRIMITIVE(vm->numClass->obj.classObj, "smallest", num_smallest);
PRIMITIVE(vm->numClass->obj.classObj, "maxSafeInteger", num_maxSafeInteger);
PRIMITIVE(vm->numClass->obj.classObj, "minSafeInteger", num_minSafeInteger);
PRIMITIVE(vm->numClass, "-(_)", num_minus);
PRIMITIVE(vm->numClass, "+(_)", num_plus);
PRIMITIVE(vm->numClass, "*(_)", num_multiply);
PRIMITIVE(vm->numClass, "/(_)", num_divide);
PRIMITIVE(vm->numClass, "<(_)", num_lt);
PRIMITIVE(vm->numClass, ">(_)", num_gt);
PRIMITIVE(vm->numClass, "<=(_)", num_lte);
PRIMITIVE(vm->numClass, ">=(_)", num_gte);
PRIMITIVE(vm->numClass, "&(_)", num_bitwiseAnd);
PRIMITIVE(vm->numClass, "|(_)", num_bitwiseOr);
PRIMITIVE(vm->numClass, "^(_)", num_bitwiseXor);
PRIMITIVE(vm->numClass, "<<(_)", num_bitwiseLeftShift);
PRIMITIVE(vm->numClass, ">>(_)", num_bitwiseRightShift);
PRIMITIVE(vm->numClass, "abs", num_abs);
PRIMITIVE(vm->numClass, "acos", num_acos);
PRIMITIVE(vm->numClass, "asin", num_asin);
PRIMITIVE(vm->numClass, "atan", num_atan);
PRIMITIVE(vm->numClass, "cbrt", num_cbrt);
PRIMITIVE(vm->numClass, "ceil", num_ceil);
PRIMITIVE(vm->numClass, "cos", num_cos);
PRIMITIVE(vm->numClass, "floor", num_floor);
PRIMITIVE(vm->numClass, "-", num_negate);
PRIMITIVE(vm->numClass, "round", num_round);
PRIMITIVE(vm->numClass, "min(_)", num_min);
PRIMITIVE(vm->numClass, "max(_)", num_max);
PRIMITIVE(vm->numClass, "clamp(_,_)", num_clamp);
PRIMITIVE(vm->numClass, "sin", num_sin);
PRIMITIVE(vm->numClass, "sqrt", num_sqrt);
PRIMITIVE(vm->numClass, "tan", num_tan);
PRIMITIVE(vm->numClass, "log", num_log);
PRIMITIVE(vm->numClass, "log2", num_log2);
PRIMITIVE(vm->numClass, "exp", num_exp);
PRIMITIVE(vm->numClass, "%(_)", num_mod);
PRIMITIVE(vm->numClass, "~", num_bitwiseNot);
PRIMITIVE(vm->numClass, "..(_)", num_dotDot);
PRIMITIVE(vm->numClass, "...(_)", num_dotDotDot);
PRIMITIVE(vm->numClass, "atan(_)", num_atan2);
PRIMITIVE(vm->numClass, "pow(_)", num_pow);
PRIMITIVE(vm->numClass, "fraction", num_fraction);
PRIMITIVE(vm->numClass, "isInfinity", num_isInfinity);
PRIMITIVE(vm->numClass, "isInteger", num_isInteger);
PRIMITIVE(vm->numClass, "isNan", num_isNan);
PRIMITIVE(vm->numClass, "sign", num_sign);
PRIMITIVE(vm->numClass, "toString", num_toString);
PRIMITIVE(vm->numClass, "truncate", num_truncate);
// These are defined just so that 0 and -0 are equal, which is specified by
// IEEE 754 even though they have different bit representations.
PRIMITIVE(vm->numClass, "==(_)", num_eqeq);
PRIMITIVE(vm->numClass, "!=(_)", num_bangeq);
vm->stringClass = AS_CLASS(wrenFindVariable(vm, coreModule, "String"));
PRIMITIVE(vm->stringClass->obj.classObj, "fromCodePoint(_)", string_fromCodePoint);
PRIMITIVE(vm->stringClass->obj.classObj, "fromByte(_)", string_fromByte);
PRIMITIVE(vm->stringClass, "+(_)", string_plus);
PRIMITIVE(vm->stringClass, "[_]", string_subscript);
PRIMITIVE(vm->stringClass, "byteAt_(_)", string_byteAt);
PRIMITIVE(vm->stringClass, "byteCount_", string_byteCount);
PRIMITIVE(vm->stringClass, "codePointAt_(_)", string_codePointAt);
PRIMITIVE(vm->stringClass, "contains(_)", string_contains);
PRIMITIVE(vm->stringClass, "endsWith(_)", string_endsWith);
PRIMITIVE(vm->stringClass, "indexOf(_)", string_indexOf1);
PRIMITIVE(vm->stringClass, "indexOf(_,_)", string_indexOf2);
PRIMITIVE(vm->stringClass, "iterate(_)", string_iterate);
PRIMITIVE(vm->stringClass, "iterateByte_(_)", string_iterateByte);
PRIMITIVE(vm->stringClass, "iteratorValue(_)", string_iteratorValue);
PRIMITIVE(vm->stringClass, "startsWith(_)", string_startsWith);
PRIMITIVE(vm->stringClass, "toString", string_toString);
vm->listClass = AS_CLASS(wrenFindVariable(vm, coreModule, "List"));
PRIMITIVE(vm->listClass->obj.classObj, "filled(_,_)", list_filled);
PRIMITIVE(vm->listClass->obj.classObj, "new()", list_new);
PRIMITIVE(vm->listClass, "[_]", list_subscript);
PRIMITIVE(vm->listClass, "[_]=(_)", list_subscriptSetter);
PRIMITIVE(vm->listClass, "add(_)", list_add);
PRIMITIVE(vm->listClass, "addCore_(_)", list_addCore);
PRIMITIVE(vm->listClass, "clear()", list_clear);
PRIMITIVE(vm->listClass, "count", list_count);
PRIMITIVE(vm->listClass, "insert(_,_)", list_insert);
PRIMITIVE(vm->listClass, "iterate(_)", list_iterate);
PRIMITIVE(vm->listClass, "iteratorValue(_)", list_iteratorValue);
PRIMITIVE(vm->listClass, "removeAt(_)", list_removeAt);
PRIMITIVE(vm->listClass, "remove(_)", list_removeValue);
PRIMITIVE(vm->listClass, "indexOf(_)", list_indexOf);
PRIMITIVE(vm->listClass, "swap(_,_)", list_swap);
vm->mapClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Map"));
PRIMITIVE(vm->mapClass->obj.classObj, "new()", map_new);
PRIMITIVE(vm->mapClass, "[_]", map_subscript);
PRIMITIVE(vm->mapClass, "[_]=(_)", map_subscriptSetter);
PRIMITIVE(vm->mapClass, "addCore_(_,_)", map_addCore);
PRIMITIVE(vm->mapClass, "clear()", map_clear);
PRIMITIVE(vm->mapClass, "containsKey(_)", map_containsKey);
PRIMITIVE(vm->mapClass, "count", map_count);
PRIMITIVE(vm->mapClass, "remove(_)", map_remove);
PRIMITIVE(vm->mapClass, "iterate(_)", map_iterate);
PRIMITIVE(vm->mapClass, "keyIteratorValue_(_)", map_keyIteratorValue);
PRIMITIVE(vm->mapClass, "valueIteratorValue_(_)", map_valueIteratorValue);
vm->rangeClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Range"));
PRIMITIVE(vm->rangeClass, "from", range_from);
PRIMITIVE(vm->rangeClass, "to", range_to);
PRIMITIVE(vm->rangeClass, "min", range_min);
PRIMITIVE(vm->rangeClass, "max", range_max);
PRIMITIVE(vm->rangeClass, "isInclusive", range_isInclusive);
PRIMITIVE(vm->rangeClass, "iterate(_)", range_iterate);
PRIMITIVE(vm->rangeClass, "iteratorValue(_)", range_iteratorValue);
PRIMITIVE(vm->rangeClass, "toString", range_toString);
ObjClass* systemClass = AS_CLASS(wrenFindVariable(vm, coreModule, "System"));
PRIMITIVE(systemClass->obj.classObj, "clock", system_clock);
PRIMITIVE(systemClass->obj.classObj, "gc()", system_gc);
PRIMITIVE(systemClass->obj.classObj, "writeString_(_)", system_writeString);
// While bootstrapping the core types and running the core module, a number
// of string objects have been created, many of which were instantiated
// before stringClass was stored in the VM. Some of them *must* be created
// first -- the ObjClass for string itself has a reference to the ObjString
// for its name.
//
// These all currently have a NULL classObj pointer, so go back and assign
// them now that the string class is known.
for (Obj* obj = vm->first; obj != NULL; obj = obj->next)
{
if (obj->type == OBJ_STRING) obj->classObj = vm->stringClass;
}
}
// End file "wren_core.c"
// Begin file "wren_value.c"
#include <math.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#if WREN_DEBUG_TRACE_MEMORY
#endif
// TODO: Tune these.
// The initial (and minimum) capacity of a non-empty list or map object.
#define MIN_CAPACITY 16
// The rate at which a collection's capacity grows when the size exceeds the
// current capacity. The new capacity will be determined by *multiplying* the
// old capacity by this. Growing geometrically is necessary to ensure that
// adding to a collection has O(1) amortized complexity.
#define GROW_FACTOR 2
// The maximum percentage of map entries that can be filled before the map is
// grown. A lower load takes more memory but reduces collisions which makes
// lookup faster.
#define MAP_LOAD_PERCENT 75
// The number of call frames initially allocated when a fiber is created. Making
// this smaller makes fibers use less memory (at first) but spends more time
// reallocating when the call stack grows.
#define INITIAL_CALL_FRAMES 4
DEFINE_BUFFER(Value, Value);
DEFINE_BUFFER(Method, Method);
static void initObj(WrenVM* vm, Obj* obj, ObjType type, ObjClass* classObj)
{
obj->type = type;
obj->isDark = false;
obj->classObj = classObj;
obj->next = vm->first;
vm->first = obj;
}
ObjClass* wrenNewSingleClass(WrenVM* vm, int numFields, ObjString* name)
{
ObjClass* classObj = ALLOCATE(vm, ObjClass);
initObj(vm, &classObj->obj, OBJ_CLASS, NULL);
classObj->superclass = NULL;
classObj->numFields = numFields;
classObj->name = name;
classObj->attributes = NULL_VAL;
wrenPushRoot(vm, (Obj*)classObj);
wrenMethodBufferInit(&classObj->methods);
wrenPopRoot(vm);
return classObj;
}
void wrenBindSuperclass(WrenVM* vm, ObjClass* subclass, ObjClass* superclass)
{
ASSERT(superclass != NULL, "Must have superclass.");
subclass->superclass = superclass;
// Include the superclass in the total number of fields.
if (subclass->numFields != -1)
{
subclass->numFields += superclass->numFields;
}
else
{
ASSERT(superclass->numFields == 0,
"A foreign class cannot inherit from a class with fields.");
}
// Inherit methods from its superclass.
for (int i = 0; i < superclass->methods.count; i++)
{
wrenBindMethod(vm, subclass, i, superclass->methods.data[i]);
}
}
ObjClass* wrenNewClass(WrenVM* vm, ObjClass* superclass, int numFields,
ObjString* name)
{
// Create the metaclass.
Value metaclassName = wrenStringFormat(vm, "@ metaclass", OBJ_VAL(name));
wrenPushRoot(vm, AS_OBJ(metaclassName));
ObjClass* metaclass = wrenNewSingleClass(vm, 0, AS_STRING(metaclassName));
metaclass->obj.classObj = vm->classClass;
wrenPopRoot(vm);
// Make sure the metaclass isn't collected when we allocate the class.
wrenPushRoot(vm, (Obj*)metaclass);
// Metaclasses always inherit Class and do not parallel the non-metaclass
// hierarchy.
wrenBindSuperclass(vm, metaclass, vm->classClass);
ObjClass* classObj = wrenNewSingleClass(vm, numFields, name);
// Make sure the class isn't collected while the inherited methods are being
// bound.
wrenPushRoot(vm, (Obj*)classObj);
classObj->obj.classObj = metaclass;
wrenBindSuperclass(vm, classObj, superclass);
wrenPopRoot(vm);
wrenPopRoot(vm);
return classObj;
}
void wrenBindMethod(WrenVM* vm, ObjClass* classObj, int symbol, Method method)
{
// Make sure the buffer is big enough to contain the symbol's index.
if (symbol >= classObj->methods.count)
{
Method noMethod;
noMethod.type = METHOD_NONE;
wrenMethodBufferFill(vm, &classObj->methods, noMethod,
symbol - classObj->methods.count + 1);
}
classObj->methods.data[symbol] = method;
}
ObjClosure* wrenNewClosure(WrenVM* vm, ObjFn* fn)
{
ObjClosure* closure = ALLOCATE_FLEX(vm, ObjClosure,
ObjUpvalue*, fn->numUpvalues);
initObj(vm, &closure->obj, OBJ_CLOSURE, vm->fnClass);
closure->fn = fn;
// Clear the upvalue array. We need to do this in case a GC is triggered
// after the closure is created but before the upvalue array is populated.
for (int i = 0; i < fn->numUpvalues; i++) closure->upvalues[i] = NULL;
return closure;
}
ObjFiber* wrenNewFiber(WrenVM* vm, ObjClosure* closure)
{
// Allocate the arrays before the fiber in case it triggers a GC.
CallFrame* frames = ALLOCATE_ARRAY(vm, CallFrame, INITIAL_CALL_FRAMES);
// Add one slot for the unused implicit receiver slot that the compiler
// assumes all functions have.
int stackCapacity = closure == NULL
? 1
: wrenPowerOf2Ceil(closure->fn->maxSlots + 1);
Value* stack = ALLOCATE_ARRAY(vm, Value, stackCapacity);
ObjFiber* fiber = ALLOCATE(vm, ObjFiber);
initObj(vm, &fiber->obj, OBJ_FIBER, vm->fiberClass);
fiber->stack = stack;
fiber->stackTop = fiber->stack;
fiber->stackCapacity = stackCapacity;
fiber->frames = frames;
fiber->frameCapacity = INITIAL_CALL_FRAMES;
fiber->numFrames = 0;
fiber->openUpvalues = NULL;
fiber->caller = NULL;
fiber->error = NULL_VAL;
fiber->state = FIBER_OTHER;
if (closure != NULL)
{
// Initialize the first call frame.
wrenAppendCallFrame(vm, fiber, closure, fiber->stack);
// The first slot always holds the closure.
fiber->stackTop[0] = OBJ_VAL(closure);
fiber->stackTop++;
}
return fiber;
}
void wrenEnsureStack(WrenVM* vm, ObjFiber* fiber, int needed)
{
if (fiber->stackCapacity >= needed) return;
int capacity = wrenPowerOf2Ceil(needed);
Value* oldStack = fiber->stack;
fiber->stack = (Value*)wrenReallocate(vm, fiber->stack,
sizeof(Value) * fiber->stackCapacity,
sizeof(Value) * capacity);
fiber->stackCapacity = capacity;
// If the reallocation moves the stack, then we need to recalculate every
// pointer that points into the old stack to into the same relative distance
// in the new stack. We have to be a little careful about how these are
// calculated because pointer subtraction is only well-defined within a
// single array, hence the slightly redundant-looking arithmetic below.
if (fiber->stack != oldStack)
{
// Top of the stack.
if (vm->apiStack >= oldStack && vm->apiStack <= fiber->stackTop)
{
vm->apiStack = fiber->stack + (vm->apiStack - oldStack);
}
// Stack pointer for each call frame.
for (int i = 0; i < fiber->numFrames; i++)
{
CallFrame* frame = &fiber->frames[i];
frame->stackStart = fiber->stack + (frame->stackStart - oldStack);
}
// Open upvalues.
for (ObjUpvalue* upvalue = fiber->openUpvalues;
upvalue != NULL;
upvalue = upvalue->next)
{
upvalue->value = fiber->stack + (upvalue->value - oldStack);
}
fiber->stackTop = fiber->stack + (fiber->stackTop - oldStack);
}
}
ObjForeign* wrenNewForeign(WrenVM* vm, ObjClass* classObj, size_t size)
{
ObjForeign* object = ALLOCATE_FLEX(vm, ObjForeign, uint8_t, size);
initObj(vm, &object->obj, OBJ_FOREIGN, classObj);
// Zero out the bytes.
memset(object->data, 0, size);
return object;
}
ObjFn* wrenNewFunction(WrenVM* vm, ObjModule* module, int maxSlots)
{
FnDebug* debug = ALLOCATE(vm, FnDebug);
debug->name = NULL;
wrenIntBufferInit(&debug->sourceLines);
ObjFn* fn = ALLOCATE(vm, ObjFn);
initObj(vm, &fn->obj, OBJ_FN, vm->fnClass);
wrenValueBufferInit(&fn->constants);
wrenByteBufferInit(&fn->code);
fn->module = module;
fn->maxSlots = maxSlots;
fn->numUpvalues = 0;
fn->arity = 0;
fn->debug = debug;
return fn;
}
void wrenFunctionBindName(WrenVM* vm, ObjFn* fn, const char* name, int length)
{
fn->debug->name = ALLOCATE_ARRAY(vm, char, length + 1);
memcpy(fn->debug->name, name, length);
fn->debug->name[length] = '\0';
}
Value wrenNewInstance(WrenVM* vm, ObjClass* classObj)
{
ObjInstance* instance = ALLOCATE_FLEX(vm, ObjInstance,
Value, classObj->numFields);
initObj(vm, &instance->obj, OBJ_INSTANCE, classObj);
// Initialize fields to null.
for (int i = 0; i < classObj->numFields; i++)
{
instance->fields[i] = NULL_VAL;
}
return OBJ_VAL(instance);
}
ObjList* wrenNewList(WrenVM* vm, uint32_t numElements)
{
// Allocate this before the list object in case it triggers a GC which would
// free the list.
Value* elements = NULL;
if (numElements > 0)
{
elements = ALLOCATE_ARRAY(vm, Value, numElements);
}
ObjList* list = ALLOCATE(vm, ObjList);
initObj(vm, &list->obj, OBJ_LIST, vm->listClass);
list->elements.capacity = numElements;
list->elements.count = numElements;
list->elements.data = elements;
return list;
}
void wrenListInsert(WrenVM* vm, ObjList* list, Value value, uint32_t index)
{
if (IS_OBJ(value)) wrenPushRoot(vm, AS_OBJ(value));
// Add a slot at the end of the list.
wrenValueBufferWrite(vm, &list->elements, NULL_VAL);
if (IS_OBJ(value)) wrenPopRoot(vm);
// Shift the existing elements down.
for (uint32_t i = list->elements.count - 1; i > index; i--)
{
list->elements.data[i] = list->elements.data[i - 1];
}
// Store the new element.
list->elements.data[index] = value;
}
int wrenListIndexOf(WrenVM* vm, ObjList* list, Value value)
{
int count = list->elements.count;
for (int i = 0; i < count; i++)
{
Value item = list->elements.data[i];
if(wrenValuesEqual(item, value)) {
return i;
}
}
return -1;
}
Value wrenListRemoveAt(WrenVM* vm, ObjList* list, uint32_t index)
{
Value removed = list->elements.data[index];
if (IS_OBJ(removed)) wrenPushRoot(vm, AS_OBJ(removed));
// Shift items up.
for (int i = index; i < list->elements.count - 1; i++)
{
list->elements.data[i] = list->elements.data[i + 1];
}
// If we have too much excess capacity, shrink it.
if (list->elements.capacity / GROW_FACTOR >= list->elements.count)
{
list->elements.data = (Value*)wrenReallocate(vm, list->elements.data,
sizeof(Value) * list->elements.capacity,
sizeof(Value) * (list->elements.capacity / GROW_FACTOR));
list->elements.capacity /= GROW_FACTOR;
}
if (IS_OBJ(removed)) wrenPopRoot(vm);
list->elements.count--;
return removed;
}
ObjMap* wrenNewMap(WrenVM* vm)
{
ObjMap* map = ALLOCATE(vm, ObjMap);
initObj(vm, &map->obj, OBJ_MAP, vm->mapClass);
map->capacity = 0;
map->count = 0;
map->entries = NULL;
return map;
}
static inline uint32_t hashBits(uint64_t hash)
{
// From v8's ComputeLongHash() which in turn cites:
// Thomas Wang, Integer Hash Functions.
// http://www.concentric.net/~Ttwang/tech/inthash.htm
hash = ~hash + (hash << 18); // hash = (hash << 18) - hash - 1;
hash = hash ^ (hash >> 31);
hash = hash * 21; // hash = (hash + (hash << 2)) + (hash << 4);
hash = hash ^ (hash >> 11);
hash = hash + (hash << 6);
hash = hash ^ (hash >> 22);
return (uint32_t)(hash & 0x3fffffff);
}
// Generates a hash code for [num].
static inline uint32_t hashNumber(double num)
{
// Hash the raw bits of the value.
return hashBits(wrenDoubleToBits(num));
}
// Generates a hash code for [object].
static uint32_t hashObject(Obj* object)
{
switch (object->type)
{
case OBJ_CLASS:
// Classes just use their name.
return hashObject((Obj*)((ObjClass*)object)->name);
// Allow bare (non-closure) functions so that we can use a map to find
// existing constants in a function's constant table. This is only used
// internally. Since user code never sees a non-closure function, they
// cannot use them as map keys.
case OBJ_FN:
{
ObjFn* fn = (ObjFn*)object;
return hashNumber(fn->arity) ^ hashNumber(fn->code.count);
}
case OBJ_RANGE:
{
ObjRange* range = (ObjRange*)object;
return hashNumber(range->from) ^ hashNumber(range->to);
}
case OBJ_STRING:
return ((ObjString*)object)->hash;
default:
ASSERT(false, "Only immutable objects can be hashed.");
return 0;
}
}
// Generates a hash code for [value], which must be one of the built-in
// immutable types: null, bool, class, num, range, or string.
static uint32_t hashValue(Value value)
{
// TODO: We'll probably want to randomize this at some point.
#if WREN_NAN_TAGGING
if (IS_OBJ(value)) return hashObject(AS_OBJ(value));
// Hash the raw bits of the unboxed value.
return hashBits(value);
#else
switch (value.type)
{
case VAL_FALSE: return 0;
case VAL_NULL: return 1;
case VAL_NUM: return hashNumber(AS_NUM(value));
case VAL_TRUE: return 2;
case VAL_OBJ: return hashObject(AS_OBJ(value));
default: UNREACHABLE();
}
return 0;
#endif
}
// Looks for an entry with [key] in an array of [capacity] [entries].
//
// If found, sets [result] to point to it and returns `true`. Otherwise,
// returns `false` and points [result] to the entry where the key/value pair
// should be inserted.
static bool findEntry(MapEntry* entries, uint32_t capacity, Value key,
MapEntry** result)
{
// If there is no entry array (an empty map), we definitely won't find it.
if (capacity == 0) return false;
// Figure out where to insert it in the table. Use open addressing and
// basic linear probing.
uint32_t startIndex = hashValue(key) % capacity;
uint32_t index = startIndex;
// If we pass a tombstone and don't end up finding the key, its entry will
// be re-used for the insert.
MapEntry* tombstone = NULL;
// Walk the probe sequence until we've tried every slot.
do
{
MapEntry* entry = &entries[index];
if (IS_UNDEFINED(entry->key))
{
// If we found an empty slot, the key is not in the table. If we found a
// slot that contains a deleted key, we have to keep looking.
if (IS_FALSE(entry->value))
{
// We found an empty slot, so we've reached the end of the probe
// sequence without finding the key. If we passed a tombstone, then
// that's where we should insert the item, otherwise, put it here at
// the end of the sequence.
*result = tombstone != NULL ? tombstone : entry;
return false;
}
else
{
// We found a tombstone. We need to keep looking in case the key is
// after it, but we'll use this entry as the insertion point if the
// key ends up not being found.
if (tombstone == NULL) tombstone = entry;
}
}
else if (wrenValuesEqual(entry->key, key))
{
// We found the key.
*result = entry;
return true;
}
// Try the next slot.
index = (index + 1) % capacity;
}
while (index != startIndex);
// If we get here, the table is full of tombstones. Return the first one we
// found.
ASSERT(tombstone != NULL, "Map should have tombstones or empty entries.");
*result = tombstone;
return false;
}
// Inserts [key] and [value] in the array of [entries] with the given
// [capacity].
//
// Returns `true` if this is the first time [key] was added to the map.
static bool insertEntry(MapEntry* entries, uint32_t capacity,
Value key, Value value)
{
ASSERT(entries != NULL, "Should ensure capacity before inserting.");
MapEntry* entry;
if (findEntry(entries, capacity, key, &entry))
{
// Already present, so just replace the value.
entry->value = value;
return false;
}
else
{
entry->key = key;
entry->value = value;
return true;
}
}
// Updates [map]'s entry array to [capacity].
static void resizeMap(WrenVM* vm, ObjMap* map, uint32_t capacity)
{
// Create the new empty hash table.
MapEntry* entries = ALLOCATE_ARRAY(vm, MapEntry, capacity);
for (uint32_t i = 0; i < capacity; i++)
{
entries[i].key = UNDEFINED_VAL;
entries[i].value = FALSE_VAL;
}
// Re-add the existing entries.
if (map->capacity > 0)
{
for (uint32_t i = 0; i < map->capacity; i++)
{
MapEntry* entry = &map->entries[i];
// Don't copy empty entries or tombstones.
if (IS_UNDEFINED(entry->key)) continue;
insertEntry(entries, capacity, entry->key, entry->value);
}
}
// Replace the array.
DEALLOCATE(vm, map->entries);
map->entries = entries;
map->capacity = capacity;
}
Value wrenMapGet(ObjMap* map, Value key)
{
MapEntry* entry;
if (findEntry(map->entries, map->capacity, key, &entry)) return entry->value;
return UNDEFINED_VAL;
}
void wrenMapSet(WrenVM* vm, ObjMap* map, Value key, Value value)
{
// If the map is getting too full, make room first.
if (map->count + 1 > map->capacity * MAP_LOAD_PERCENT / 100)
{
// Figure out the new hash table size.
uint32_t capacity = map->capacity * GROW_FACTOR;
if (capacity < MIN_CAPACITY) capacity = MIN_CAPACITY;
resizeMap(vm, map, capacity);
}
if (insertEntry(map->entries, map->capacity, key, value))
{
// A new key was added.
map->count++;
}
}
void wrenMapClear(WrenVM* vm, ObjMap* map)
{
DEALLOCATE(vm, map->entries);
map->entries = NULL;
map->capacity = 0;
map->count = 0;
}
Value wrenMapRemoveKey(WrenVM* vm, ObjMap* map, Value key)
{
MapEntry* entry;
if (!findEntry(map->entries, map->capacity, key, &entry)) return NULL_VAL;
// Remove the entry from the map. Set this value to true, which marks it as a
// deleted slot. When searching for a key, we will stop on empty slots, but
// continue past deleted slots.
Value value = entry->value;
entry->key = UNDEFINED_VAL;
entry->value = TRUE_VAL;
if (IS_OBJ(value)) wrenPushRoot(vm, AS_OBJ(value));
map->count--;
if (map->count == 0)
{
// Removed the last item, so free the array.
wrenMapClear(vm, map);
}
else if (map->capacity > MIN_CAPACITY &&
map->count < map->capacity / GROW_FACTOR * MAP_LOAD_PERCENT / 100)
{
uint32_t capacity = map->capacity / GROW_FACTOR;
if (capacity < MIN_CAPACITY) capacity = MIN_CAPACITY;
// The map is getting empty, so shrink the entry array back down.
// TODO: Should we do this less aggressively than we grow?
resizeMap(vm, map, capacity);
}
if (IS_OBJ(value)) wrenPopRoot(vm);
return value;
}
ObjModule* wrenNewModule(WrenVM* vm, ObjString* name)
{
ObjModule* module = ALLOCATE(vm, ObjModule);
// Modules are never used as first-class objects, so don't need a class.
initObj(vm, (Obj*)module, OBJ_MODULE, NULL);
wrenPushRoot(vm, (Obj*)module);
wrenSymbolTableInit(&module->variableNames);
wrenValueBufferInit(&module->variables);
module->name = name;
wrenPopRoot(vm);
return module;
}
Value wrenNewRange(WrenVM* vm, double from, double to, bool isInclusive)
{
ObjRange* range = ALLOCATE(vm, ObjRange);
initObj(vm, &range->obj, OBJ_RANGE, vm->rangeClass);
range->from = from;
range->to = to;
range->isInclusive = isInclusive;
return OBJ_VAL(range);
}
// Creates a new string object with a null-terminated buffer large enough to
// hold a string of [length] but does not fill in the bytes.
//
// The caller is expected to fill in the buffer and then calculate the string's
// hash.
static ObjString* allocateString(WrenVM* vm, size_t length)
{
ObjString* string = ALLOCATE_FLEX(vm, ObjString, char, length + 1);
initObj(vm, &string->obj, OBJ_STRING, vm->stringClass);
string->length = (int)length;
string->value[length] = '\0';
return string;
}
// Calculates and stores the hash code for [string].
static void hashString(ObjString* string)
{
// FNV-1a hash. See: http://www.isthe.com/chongo/tech/comp/fnv/
uint32_t hash = 2166136261u;
// This is O(n) on the length of the string, but we only call this when a new
// string is created. Since the creation is also O(n) (to copy/initialize all
// the bytes), we allow this here.
for (uint32_t i = 0; i < string->length; i++)
{
hash ^= string->value[i];
hash *= 16777619;
}
string->hash = hash;
}
Value wrenNewString(WrenVM* vm, const char* text)
{
return wrenNewStringLength(vm, text, strlen(text));
}
Value wrenNewStringLength(WrenVM* vm, const char* text, size_t length)
{
// Allow NULL if the string is empty since byte buffers don't allocate any
// characters for a zero-length string.
ASSERT(length == 0 || text != NULL, "Unexpected NULL string.");
ObjString* string = allocateString(vm, length);
// Copy the string (if given one).
if (length > 0 && text != NULL) memcpy(string->value, text, length);
hashString(string);
return OBJ_VAL(string);
}
Value wrenNewStringFromRange(WrenVM* vm, ObjString* source, int start,
uint32_t count, int step)
{
uint8_t* from = (uint8_t*)source->value;
int length = 0;
for (uint32_t i = 0; i < count; i++)
{
length += wrenUtf8DecodeNumBytes(from[start + i * step]);
}
ObjString* result = allocateString(vm, length);
result->value[length] = '\0';
uint8_t* to = (uint8_t*)result->value;
for (uint32_t i = 0; i < count; i++)
{
int index = start + i * step;
int codePoint = wrenUtf8Decode(from + index, source->length - index);
if (codePoint != -1)
{
to += wrenUtf8Encode(codePoint, to);
}
}
hashString(result);
return OBJ_VAL(result);
}
Value wrenNumToString(WrenVM* vm, double value)
{
// Edge case: If the value is NaN or infinity, different versions of libc
// produce different outputs (some will format it signed and some won't). To
// get reliable output, handle it ourselves.
if (isnan(value)) return CONST_STRING(vm, "nan");
if (isinf(value))
{
if (value > 0.0)
{
return CONST_STRING(vm, "infinity");
}
else
{
return CONST_STRING(vm, "-infinity");
}
}
// This is large enough to hold any double converted to a string using
// "%.14g". Example:
//
// -1.12345678901234e-1022
//
// So we have:
//
// + 1 char for sign
// + 1 char for digit
// + 1 char for "."
// + 14 chars for decimal digits
// + 1 char for "e"
// + 1 char for "-" or "+"
// + 4 chars for exponent
// + 1 char for "\0"
// = 24
char buffer[24];
int length = sprintf(buffer, "%.14g", value);
return wrenNewStringLength(vm, buffer, length);
}
Value wrenStringFromCodePoint(WrenVM* vm, int value)
{
int length = wrenUtf8EncodeNumBytes(value);
ASSERT(length != 0, "Value out of range.");
ObjString* string = allocateString(vm, length);
wrenUtf8Encode(value, (uint8_t*)string->value);
hashString(string);
return OBJ_VAL(string);
}
Value wrenStringFromByte(WrenVM *vm, uint8_t value)
{
int length = 1;
ObjString* string = allocateString(vm, length);
string->value[0] = value;
hashString(string);
return OBJ_VAL(string);
}
Value wrenStringFormat(WrenVM* vm, const char* format, ...)
{
va_list argList;
// Calculate the length of the result string. Do this up front so we can
// create the final string with a single allocation.
va_start(argList, format);
size_t totalLength = 0;
for (const char* c = format; *c != '\0'; c++)
{
switch (*c)
{
case '$':
totalLength += strlen(va_arg(argList, const char*));
break;
case '@':
totalLength += AS_STRING(va_arg(argList, Value))->length;
break;
default:
// Any other character is interpreted literally.
totalLength++;
}
}
va_end(argList);
// Concatenate the string.
ObjString* result = allocateString(vm, totalLength);
va_start(argList, format);
char* start = result->value;
for (const char* c = format; *c != '\0'; c++)
{
switch (*c)
{
case '$':
{
const char* string = va_arg(argList, const char*);
size_t length = strlen(string);
memcpy(start, string, length);
start += length;
break;
}
case '@':
{
ObjString* string = AS_STRING(va_arg(argList, Value));
memcpy(start, string->value, string->length);
start += string->length;
break;
}
default:
// Any other character is interpreted literally.
*start++ = *c;
}
}
va_end(argList);
hashString(result);
return OBJ_VAL(result);
}
Value wrenStringCodePointAt(WrenVM* vm, ObjString* string, uint32_t index)
{
ASSERT(index < string->length, "Index out of bounds.");
int codePoint = wrenUtf8Decode((uint8_t*)string->value + index,
string->length - index);
if (codePoint == -1)
{
// If it isn't a valid UTF-8 sequence, treat it as a single raw byte.
char bytes[2];
bytes[0] = string->value[index];
bytes[1] = '\0';
return wrenNewStringLength(vm, bytes, 1);
}
return wrenStringFromCodePoint(vm, codePoint);
}
// Uses the Boyer-Moore-Horspool string matching algorithm.
uint32_t wrenStringFind(ObjString* haystack, ObjString* needle, uint32_t start)
{
// Edge case: An empty needle is always found.
if (needle->length == 0) return start;
// If the needle goes past the haystack it won't be found.
if (start + needle->length > haystack->length) return UINT32_MAX;
// If the startIndex is too far it also won't be found.
if (start >= haystack->length) return UINT32_MAX;
// Pre-calculate the shift table. For each character (8-bit value), we
// determine how far the search window can be advanced if that character is
// the last character in the haystack where we are searching for the needle
// and the needle doesn't match there.
uint32_t shift[UINT8_MAX];
uint32_t needleEnd = needle->length - 1;
// By default, we assume the character is not the needle at all. In that case
// case, if a match fails on that character, we can advance one whole needle
// width since.
for (uint32_t index = 0; index < UINT8_MAX; index++)
{
shift[index] = needle->length;
}
// Then, for every character in the needle, determine how far it is from the
// end. If a match fails on that character, we can advance the window such
// that it the last character in it lines up with the last place we could
// find it in the needle.
for (uint32_t index = 0; index < needleEnd; index++)
{
char c = needle->value[index];
shift[(uint8_t)c] = needleEnd - index;
}
// Slide the needle across the haystack, looking for the first match or
// stopping if the needle goes off the end.
char lastChar = needle->value[needleEnd];
uint32_t range = haystack->length - needle->length;
for (uint32_t index = start; index <= range; )
{
// Compare the last character in the haystack's window to the last character
// in the needle. If it matches, see if the whole needle matches.
char c = haystack->value[index + needleEnd];
if (lastChar == c &&
memcmp(haystack->value + index, needle->value, needleEnd) == 0)
{
// Found a match.
return index;
}
// Otherwise, slide the needle forward.
index += shift[(uint8_t)c];
}
// Not found.
return UINT32_MAX;
}
ObjUpvalue* wrenNewUpvalue(WrenVM* vm, Value* value)
{
ObjUpvalue* upvalue = ALLOCATE(vm, ObjUpvalue);
// Upvalues are never used as first-class objects, so don't need a class.
initObj(vm, &upvalue->obj, OBJ_UPVALUE, NULL);
upvalue->value = value;
upvalue->closed = NULL_VAL;
upvalue->next = NULL;
return upvalue;
}
void wrenGrayObj(WrenVM* vm, Obj* obj)
{
if (obj == NULL) return;
// Stop if the object is already darkened so we don't get stuck in a cycle.
if (obj->isDark) return;
// It's been reached.
obj->isDark = true;
// Add it to the gray list so it can be recursively explored for
// more marks later.
if (vm->grayCount >= vm->grayCapacity)
{
vm->grayCapacity = vm->grayCount * 2;
vm->gray = (Obj**)vm->config.reallocateFn(vm->gray,
vm->grayCapacity * sizeof(Obj*),
vm->config.userData);
}
vm->gray[vm->grayCount++] = obj;
}
void wrenGrayValue(WrenVM* vm, Value value)
{
if (!IS_OBJ(value)) return;
wrenGrayObj(vm, AS_OBJ(value));
}
void wrenGrayBuffer(WrenVM* vm, ValueBuffer* buffer)
{
for (int i = 0; i < buffer->count; i++)
{
wrenGrayValue(vm, buffer->data[i]);
}
}
static void blackenClass(WrenVM* vm, ObjClass* classObj)
{
// The metaclass.
wrenGrayObj(vm, (Obj*)classObj->obj.classObj);
// The superclass.
wrenGrayObj(vm, (Obj*)classObj->superclass);
// Method function objects.
for (int i = 0; i < classObj->methods.count; i++)
{
if (classObj->methods.data[i].type == METHOD_BLOCK)
{
wrenGrayObj(vm, (Obj*)classObj->methods.data[i].as.closure);
}
}
wrenGrayObj(vm, (Obj*)classObj->name);
if(!IS_NULL(classObj->attributes)) wrenGrayObj(vm, AS_OBJ(classObj->attributes));
// Keep track of how much memory is still in use.
vm->bytesAllocated += sizeof(ObjClass);
vm->bytesAllocated += classObj->methods.capacity * sizeof(Method);
}
static void blackenClosure(WrenVM* vm, ObjClosure* closure)
{
// Mark the function.
wrenGrayObj(vm, (Obj*)closure->fn);
// Mark the upvalues.
for (int i = 0; i < closure->fn->numUpvalues; i++)
{
wrenGrayObj(vm, (Obj*)closure->upvalues[i]);
}
// Keep track of how much memory is still in use.
vm->bytesAllocated += sizeof(ObjClosure);
vm->bytesAllocated += sizeof(ObjUpvalue*) * closure->fn->numUpvalues;
}
static void blackenFiber(WrenVM* vm, ObjFiber* fiber)
{
// Stack functions.
for (int i = 0; i < fiber->numFrames; i++)
{
wrenGrayObj(vm, (Obj*)fiber->frames[i].closure);
}
// Stack variables.
for (Value* slot = fiber->stack; slot < fiber->stackTop; slot++)
{
wrenGrayValue(vm, *slot);
}
// Open upvalues.
ObjUpvalue* upvalue = fiber->openUpvalues;
while (upvalue != NULL)
{
wrenGrayObj(vm, (Obj*)upvalue);
upvalue = upvalue->next;
}
// The caller.
wrenGrayObj(vm, (Obj*)fiber->caller);
wrenGrayValue(vm, fiber->error);
// Keep track of how much memory is still in use.
vm->bytesAllocated += sizeof(ObjFiber);
vm->bytesAllocated += fiber->frameCapacity * sizeof(CallFrame);
vm->bytesAllocated += fiber->stackCapacity * sizeof(Value);
}
static void blackenFn(WrenVM* vm, ObjFn* fn)
{
// Mark the constants.
wrenGrayBuffer(vm, &fn->constants);
// Mark the module it belongs to, in case it's been unloaded.
wrenGrayObj(vm, (Obj*)fn->module);
// Keep track of how much memory is still in use.
vm->bytesAllocated += sizeof(ObjFn);
vm->bytesAllocated += sizeof(uint8_t) * fn->code.capacity;
vm->bytesAllocated += sizeof(Value) * fn->constants.capacity;
// The debug line number buffer.
vm->bytesAllocated += sizeof(int) * fn->code.capacity;
// TODO: What about the function name?
}
static void blackenForeign(WrenVM* vm, ObjForeign* foreign)
{
// TODO: Keep track of how much memory the foreign object uses. We can store
// this in each foreign object, but it will balloon the size. We may not want
// that much overhead. One option would be to let the foreign class register
// a C function that returns a size for the object. That way the VM doesn't
// always have to explicitly store it.
}
static void blackenInstance(WrenVM* vm, ObjInstance* instance)
{
wrenGrayObj(vm, (Obj*)instance->obj.classObj);
// Mark the fields.
for (int i = 0; i < instance->obj.classObj->numFields; i++)
{
wrenGrayValue(vm, instance->fields[i]);
}
// Keep track of how much memory is still in use.
vm->bytesAllocated += sizeof(ObjInstance);
vm->bytesAllocated += sizeof(Value) * instance->obj.classObj->numFields;
}
static void blackenList(WrenVM* vm, ObjList* list)
{
// Mark the elements.
wrenGrayBuffer(vm, &list->elements);
// Keep track of how much memory is still in use.
vm->bytesAllocated += sizeof(ObjList);
vm->bytesAllocated += sizeof(Value) * list->elements.capacity;
}
static void blackenMap(WrenVM* vm, ObjMap* map)
{
// Mark the entries.
for (uint32_t i = 0; i < map->capacity; i++)
{
MapEntry* entry = &map->entries[i];
if (IS_UNDEFINED(entry->key)) continue;
wrenGrayValue(vm, entry->key);
wrenGrayValue(vm, entry->value);
}
// Keep track of how much memory is still in use.
vm->bytesAllocated += sizeof(ObjMap);
vm->bytesAllocated += sizeof(MapEntry) * map->capacity;
}
static void blackenModule(WrenVM* vm, ObjModule* module)
{
// Top-level variables.
for (int i = 0; i < module->variables.count; i++)
{
wrenGrayValue(vm, module->variables.data[i]);
}
wrenBlackenSymbolTable(vm, &module->variableNames);
wrenGrayObj(vm, (Obj*)module->name);
// Keep track of how much memory is still in use.
vm->bytesAllocated += sizeof(ObjModule);
}
static void blackenRange(WrenVM* vm, ObjRange* range)
{
// Keep track of how much memory is still in use.
vm->bytesAllocated += sizeof(ObjRange);
}
static void blackenString(WrenVM* vm, ObjString* string)
{
// Keep track of how much memory is still in use.
vm->bytesAllocated += sizeof(ObjString) + string->length + 1;
}
static void blackenUpvalue(WrenVM* vm, ObjUpvalue* upvalue)
{
// Mark the closed-over object (in case it is closed).
wrenGrayValue(vm, upvalue->closed);
// Keep track of how much memory is still in use.
vm->bytesAllocated += sizeof(ObjUpvalue);
}
static void blackenObject(WrenVM* vm, Obj* obj)
{
#if WREN_DEBUG_TRACE_MEMORY
printf("mark ");
wrenDumpValue(OBJ_VAL(obj));
printf(" @ %p\n", obj);
#endif
// Traverse the object's fields.
switch (obj->type)
{
case OBJ_CLASS: blackenClass( vm, (ObjClass*) obj); break;
case OBJ_CLOSURE: blackenClosure( vm, (ObjClosure*) obj); break;
case OBJ_FIBER: blackenFiber( vm, (ObjFiber*) obj); break;
case OBJ_FN: blackenFn( vm, (ObjFn*) obj); break;
case OBJ_FOREIGN: blackenForeign( vm, (ObjForeign*) obj); break;
case OBJ_INSTANCE: blackenInstance(vm, (ObjInstance*)obj); break;
case OBJ_LIST: blackenList( vm, (ObjList*) obj); break;
case OBJ_MAP: blackenMap( vm, (ObjMap*) obj); break;
case OBJ_MODULE: blackenModule( vm, (ObjModule*) obj); break;
case OBJ_RANGE: blackenRange( vm, (ObjRange*) obj); break;
case OBJ_STRING: blackenString( vm, (ObjString*) obj); break;
case OBJ_UPVALUE: blackenUpvalue( vm, (ObjUpvalue*) obj); break;
}
}
void wrenBlackenObjects(WrenVM* vm)
{
while (vm->grayCount > 0)
{
// Pop an item from the gray stack.
Obj* obj = vm->gray[--vm->grayCount];
blackenObject(vm, obj);
}
}
void wrenFreeObj(WrenVM* vm, Obj* obj)
{
#if WREN_DEBUG_TRACE_MEMORY
printf("free ");
wrenDumpValue(OBJ_VAL(obj));
printf(" @ %p\n", obj);
#endif
switch (obj->type)
{
case OBJ_CLASS:
wrenMethodBufferClear(vm, &((ObjClass*)obj)->methods);
break;
case OBJ_FIBER:
{
ObjFiber* fiber = (ObjFiber*)obj;
DEALLOCATE(vm, fiber->frames);
DEALLOCATE(vm, fiber->stack);
break;
}
case OBJ_FN:
{
ObjFn* fn = (ObjFn*)obj;
wrenValueBufferClear(vm, &fn->constants);
wrenByteBufferClear(vm, &fn->code);
wrenIntBufferClear(vm, &fn->debug->sourceLines);
DEALLOCATE(vm, fn->debug->name);
DEALLOCATE(vm, fn->debug);
break;
}
case OBJ_FOREIGN:
wrenFinalizeForeign(vm, (ObjForeign*)obj);
break;
case OBJ_LIST:
wrenValueBufferClear(vm, &((ObjList*)obj)->elements);
break;
case OBJ_MAP:
DEALLOCATE(vm, ((ObjMap*)obj)->entries);
break;
case OBJ_MODULE:
wrenSymbolTableClear(vm, &((ObjModule*)obj)->variableNames);
wrenValueBufferClear(vm, &((ObjModule*)obj)->variables);
break;
case OBJ_CLOSURE:
case OBJ_INSTANCE:
case OBJ_RANGE:
case OBJ_STRING:
case OBJ_UPVALUE:
break;
}
DEALLOCATE(vm, obj);
}
ObjClass* wrenGetClass(WrenVM* vm, Value value)
{
return wrenGetClassInline(vm, value);
}
bool wrenValuesEqual(Value a, Value b)
{
if (wrenValuesSame(a, b)) return true;
// If we get here, it's only possible for two heap-allocated immutable objects
// to be equal.
if (!IS_OBJ(a) || !IS_OBJ(b)) return false;
Obj* aObj = AS_OBJ(a);
Obj* bObj = AS_OBJ(b);
// Must be the same type.
if (aObj->type != bObj->type) return false;
switch (aObj->type)
{
case OBJ_RANGE:
{
ObjRange* aRange = (ObjRange*)aObj;
ObjRange* bRange = (ObjRange*)bObj;
return aRange->from == bRange->from &&
aRange->to == bRange->to &&
aRange->isInclusive == bRange->isInclusive;
}
case OBJ_STRING:
{
ObjString* aString = (ObjString*)aObj;
ObjString* bString = (ObjString*)bObj;
return aString->hash == bString->hash &&
wrenStringEqualsCString(aString, bString->value, bString->length);
}
default:
// All other types are only equal if they are same, which they aren't if
// we get here.
return false;
}
}
// End file "wren_value.c"
// Begin file "wren_utils.c"
#include <string.h>
DEFINE_BUFFER(Byte, uint8_t);
DEFINE_BUFFER(Int, int);
DEFINE_BUFFER(String, ObjString*);
void wrenSymbolTableInit(SymbolTable* symbols)
{
wrenStringBufferInit(symbols);
}
void wrenSymbolTableClear(WrenVM* vm, SymbolTable* symbols)
{
wrenStringBufferClear(vm, symbols);
}
int wrenSymbolTableAdd(WrenVM* vm, SymbolTable* symbols,
const char* name, size_t length)
{
ObjString* symbol = AS_STRING(wrenNewStringLength(vm, name, length));
wrenPushRoot(vm, &symbol->obj);
wrenStringBufferWrite(vm, symbols, symbol);
wrenPopRoot(vm);
return symbols->count - 1;
}
int wrenSymbolTableEnsure(WrenVM* vm, SymbolTable* symbols,
const char* name, size_t length)
{
// See if the symbol is already defined.
int existing = wrenSymbolTableFind(symbols, name, length);
if (existing != -1) return existing;
// New symbol, so add it.
return wrenSymbolTableAdd(vm, symbols, name, length);
}
int wrenSymbolTableFind(const SymbolTable* symbols,
const char* name, size_t length)
{
// See if the symbol is already defined.
// TODO: O(n). Do something better.
for (int i = 0; i < symbols->count; i++)
{
if (wrenStringEqualsCString(symbols->data[i], name, length)) return i;
}
return -1;
}
void wrenBlackenSymbolTable(WrenVM* vm, SymbolTable* symbolTable)
{
for (int i = 0; i < symbolTable->count; i++)
{
wrenGrayObj(vm, &symbolTable->data[i]->obj);
}
// Keep track of how much memory is still in use.
vm->bytesAllocated += symbolTable->capacity * sizeof(*symbolTable->data);
}
int wrenUtf8EncodeNumBytes(int value)
{
ASSERT(value >= 0, "Cannot encode a negative value.");
if (value <= 0x7f) return 1;
if (value <= 0x7ff) return 2;
if (value <= 0xffff) return 3;
if (value <= 0x10ffff) return 4;
return 0;
}
int wrenUtf8Encode(int value, uint8_t* bytes)
{
if (value <= 0x7f)
{
// Single byte (i.e. fits in ASCII).
*bytes = value & 0x7f;
return 1;
}
else if (value <= 0x7ff)
{
// Two byte sequence: 110xxxxx 10xxxxxx.
*bytes = 0xc0 | ((value & 0x7c0) >> 6);
bytes++;
*bytes = 0x80 | (value & 0x3f);
return 2;
}
else if (value <= 0xffff)
{
// Three byte sequence: 1110xxxx 10xxxxxx 10xxxxxx.
*bytes = 0xe0 | ((value & 0xf000) >> 12);
bytes++;
*bytes = 0x80 | ((value & 0xfc0) >> 6);
bytes++;
*bytes = 0x80 | (value & 0x3f);
return 3;
}
else if (value <= 0x10ffff)
{
// Four byte sequence: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx.
*bytes = 0xf0 | ((value & 0x1c0000) >> 18);
bytes++;
*bytes = 0x80 | ((value & 0x3f000) >> 12);
bytes++;
*bytes = 0x80 | ((value & 0xfc0) >> 6);
bytes++;
*bytes = 0x80 | (value & 0x3f);
return 4;
}
// Invalid Unicode value. See: http://tools.ietf.org/html/rfc3629
UNREACHABLE();
return 0;
}
int wrenUtf8Decode(const uint8_t* bytes, uint32_t length)
{
// Single byte (i.e. fits in ASCII).
if (*bytes <= 0x7f) return *bytes;
int value;
uint32_t remainingBytes;
if ((*bytes & 0xe0) == 0xc0)
{
// Two byte sequence: 110xxxxx 10xxxxxx.
value = *bytes & 0x1f;
remainingBytes = 1;
}
else if ((*bytes & 0xf0) == 0xe0)
{
// Three byte sequence: 1110xxxx 10xxxxxx 10xxxxxx.
value = *bytes & 0x0f;
remainingBytes = 2;
}
else if ((*bytes & 0xf8) == 0xf0)
{
// Four byte sequence: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx.
value = *bytes & 0x07;
remainingBytes = 3;
}
else
{
// Invalid UTF-8 sequence.
return -1;
}
// Don't read past the end of the buffer on truncated UTF-8.
if (remainingBytes > length - 1) return -1;
while (remainingBytes > 0)
{
bytes++;
remainingBytes--;
// Remaining bytes must be of form 10xxxxxx.
if ((*bytes & 0xc0) != 0x80) return -1;
value = value << 6 | (*bytes & 0x3f);
}
return value;
}
int wrenUtf8DecodeNumBytes(uint8_t byte)
{
// If the byte starts with 10xxxxx, it's the middle of a UTF-8 sequence, so
// don't count it at all.
if ((byte & 0xc0) == 0x80) return 0;
// The first byte's high bits tell us how many bytes are in the UTF-8
// sequence.
if ((byte & 0xf8) == 0xf0) return 4;
if ((byte & 0xf0) == 0xe0) return 3;
if ((byte & 0xe0) == 0xc0) return 2;
return 1;
}
// From: http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2Float
int wrenPowerOf2Ceil(int n)
{
n--;
n |= n >> 1;
n |= n >> 2;
n |= n >> 4;
n |= n >> 8;
n |= n >> 16;
n++;
return n;
}
uint32_t wrenValidateIndex(uint32_t count, int64_t value)
{
// Negative indices count from the end.
if (value < 0) value = count + value;
// Check bounds.
if (value >= 0 && value < count) return (uint32_t)value;
return UINT32_MAX;
}
// End file "wren_utils.c"
// Begin file "wren_vm.c"
#include <stdarg.h>
#include <string.h>
#if WREN_OPT_META
// Begin file "wren_opt_meta.h"
#ifndef wren_opt_meta_h
#define wren_opt_meta_h
// This module defines the Meta class and its associated methods.
#if WREN_OPT_META
const char* wrenMetaSource();
WrenForeignMethodFn wrenMetaBindForeignMethod(WrenVM* vm,
const char* className,
bool isStatic,
const char* signature);
#endif
#endif
// End file "wren_opt_meta.h"
#endif
#if WREN_OPT_RANDOM
// Begin file "wren_opt_random.h"
#ifndef wren_opt_random_h
#define wren_opt_random_h
#if WREN_OPT_RANDOM
const char* wrenRandomSource();
WrenForeignClassMethods wrenRandomBindForeignClass(WrenVM* vm,
const char* module,
const char* className);
WrenForeignMethodFn wrenRandomBindForeignMethod(WrenVM* vm,
const char* className,
bool isStatic,
const char* signature);
#endif
#endif
// End file "wren_opt_random.h"
#endif
#if WREN_DEBUG_TRACE_MEMORY || WREN_DEBUG_TRACE_GC
#include <time.h>
#include <stdio.h>
#endif
// The behavior of realloc() when the size is 0 is implementation defined. It
// may return a non-NULL pointer which must not be dereferenced but nevertheless
// should be freed. To prevent that, we avoid calling realloc() with a zero
// size.
static void* defaultReallocate(void* ptr, size_t newSize, void* _)
{
if (newSize == 0)
{
free(ptr);
return NULL;
}
return realloc(ptr, newSize);
}
int wrenGetVersionNumber()
{
return WREN_VERSION_NUMBER;
}
void wrenInitConfiguration(WrenConfiguration* config)
{
config->reallocateFn = defaultReallocate;
config->resolveModuleFn = NULL;
config->loadModuleFn = NULL;
config->bindForeignMethodFn = NULL;
config->bindForeignClassFn = NULL;
config->writeFn = NULL;
config->errorFn = NULL;
config->initialHeapSize = 1024 * 1024 * 10;
config->minHeapSize = 1024 * 1024;
config->heapGrowthPercent = 50;
config->userData = NULL;
}
WrenVM* wrenNewVM(WrenConfiguration* config)
{
WrenReallocateFn reallocate = defaultReallocate;
void* userData = NULL;
if (config != NULL) {
userData = config->userData;
reallocate = config->reallocateFn ? config->reallocateFn : defaultReallocate;
}
WrenVM* vm = (WrenVM*)reallocate(NULL, sizeof(*vm), userData);
memset(vm, 0, sizeof(WrenVM));
// Copy the configuration if given one.
if (config != NULL)
{
memcpy(&vm->config, config, sizeof(WrenConfiguration));
// We choose to set this after copying,
// rather than modifying the user config pointer
vm->config.reallocateFn = reallocate;
}
else
{
wrenInitConfiguration(&vm->config);
}
// TODO: Should we allocate and free this during a GC?
vm->grayCount = 0;
// TODO: Tune this.
vm->grayCapacity = 4;
vm->gray = (Obj**)reallocate(NULL, vm->grayCapacity * sizeof(Obj*), userData);
vm->nextGC = vm->config.initialHeapSize;
wrenSymbolTableInit(&vm->methodNames);
vm->modules = wrenNewMap(vm);
wrenInitializeCore(vm);
return vm;
}
void wrenFreeVM(WrenVM* vm)
{
ASSERT(vm->methodNames.count > 0, "VM appears to have already been freed.");
// Free all of the GC objects.
Obj* obj = vm->first;
while (obj != NULL)
{
Obj* next = obj->next;
wrenFreeObj(vm, obj);
obj = next;
}
// Free up the GC gray set.
vm->gray = (Obj**)vm->config.reallocateFn(vm->gray, 0, vm->config.userData);
// Tell the user if they didn't free any handles. We don't want to just free
// them here because the host app may still have pointers to them that they
// may try to use. Better to tell them about the bug early.
ASSERT(vm->handles == NULL, "All handles have not been released.");
wrenSymbolTableClear(vm, &vm->methodNames);
DEALLOCATE(vm, vm);
}
void wrenCollectGarbage(WrenVM* vm)
{
#if WREN_DEBUG_TRACE_MEMORY || WREN_DEBUG_TRACE_GC
printf("-- gc --\n");
size_t before = vm->bytesAllocated;
double startTime = (double)clock() / CLOCKS_PER_SEC;
#endif
// Mark all reachable objects.
// Reset this. As we mark objects, their size will be counted again so that
// we can track how much memory is in use without needing to know the size
// of each *freed* object.
//
// This is important because when freeing an unmarked object, we don't always
// know how much memory it is using. For example, when freeing an instance,
// we need to know its class to know how big it is, but its class may have
// already been freed.
vm->bytesAllocated = 0;
wrenGrayObj(vm, (Obj*)vm->modules);
// Temporary roots.
for (int i = 0; i < vm->numTempRoots; i++)
{
wrenGrayObj(vm, vm->tempRoots[i]);
}
// The current fiber.
wrenGrayObj(vm, (Obj*)vm->fiber);
// The handles.
for (WrenHandle* handle = vm->handles;
handle != NULL;
handle = handle->next)
{
wrenGrayValue(vm, handle->value);
}
// Any object the compiler is using (if there is one).
if (vm->compiler != NULL) wrenMarkCompiler(vm, vm->compiler);
// Method names.
wrenBlackenSymbolTable(vm, &vm->methodNames);
// Now that we have grayed the roots, do a depth-first search over all of the
// reachable objects.
wrenBlackenObjects(vm);
// Collect the white objects.
Obj** obj = &vm->first;
while (*obj != NULL)
{
if (!((*obj)->isDark))
{
// This object wasn't reached, so remove it from the list and free it.
Obj* unreached = *obj;
*obj = unreached->next;
wrenFreeObj(vm, unreached);
}
else
{
// This object was reached, so unmark it (for the next GC) and move on to
// the next.
(*obj)->isDark = false;
obj = &(*obj)->next;
}
}
// Calculate the next gc point, this is the current allocation plus
// a configured percentage of the current allocation.
vm->nextGC = vm->bytesAllocated + ((vm->bytesAllocated * vm->config.heapGrowthPercent) / 100);
if (vm->nextGC < vm->config.minHeapSize) vm->nextGC = vm->config.minHeapSize;
#if WREN_DEBUG_TRACE_MEMORY || WREN_DEBUG_TRACE_GC
double elapsed = ((double)clock() / CLOCKS_PER_SEC) - startTime;
// Explicit cast because size_t has different sizes on 32-bit and 64-bit and
// we need a consistent type for the format string.
printf("GC %lu before, %lu after (%lu collected), next at %lu. Took %.3fms.\n",
(unsigned long)before,
(unsigned long)vm->bytesAllocated,
(unsigned long)(before - vm->bytesAllocated),
(unsigned long)vm->nextGC,
elapsed*1000.0);
#endif
}
void* wrenReallocate(WrenVM* vm, void* memory, size_t oldSize, size_t newSize)
{
#if WREN_DEBUG_TRACE_MEMORY
// Explicit cast because size_t has different sizes on 32-bit and 64-bit and
// we need a consistent type for the format string.
printf("reallocate %p %lu -> %lu\n",
memory, (unsigned long)oldSize, (unsigned long)newSize);
#endif
// If new bytes are being allocated, add them to the total count. If objects
// are being completely deallocated, we don't track that (since we don't
// track the original size). Instead, that will be handled while marking
// during the next GC.
vm->bytesAllocated += newSize - oldSize;
#if WREN_DEBUG_GC_STRESS
// Since collecting calls this function to free things, make sure we don't
// recurse.
if (newSize > 0) wrenCollectGarbage(vm);
#else
if (newSize > 0 && vm->bytesAllocated > vm->nextGC) wrenCollectGarbage(vm);
#endif
return vm->config.reallocateFn(memory, newSize, vm->config.userData);
}
// Captures the local variable [local] into an [Upvalue]. If that local is
// already in an upvalue, the existing one will be used. (This is important to
// ensure that multiple closures closing over the same variable actually see
// the same variable.) Otherwise, it will create a new open upvalue and add it
// the fiber's list of upvalues.
static ObjUpvalue* captureUpvalue(WrenVM* vm, ObjFiber* fiber, Value* local)
{
// If there are no open upvalues at all, we must need a new one.
if (fiber->openUpvalues == NULL)
{
fiber->openUpvalues = wrenNewUpvalue(vm, local);
return fiber->openUpvalues;
}
ObjUpvalue* prevUpvalue = NULL;
ObjUpvalue* upvalue = fiber->openUpvalues;
// Walk towards the bottom of the stack until we find a previously existing
// upvalue or pass where it should be.
while (upvalue != NULL && upvalue->value > local)
{
prevUpvalue = upvalue;
upvalue = upvalue->next;
}
// Found an existing upvalue for this local.
if (upvalue != NULL && upvalue->value == local) return upvalue;
// We've walked past this local on the stack, so there must not be an
// upvalue for it already. Make a new one and link it in in the right
// place to keep the list sorted.
ObjUpvalue* createdUpvalue = wrenNewUpvalue(vm, local);
if (prevUpvalue == NULL)
{
// The new one is the first one in the list.
fiber->openUpvalues = createdUpvalue;
}
else
{
prevUpvalue->next = createdUpvalue;
}
createdUpvalue->next = upvalue;
return createdUpvalue;
}
// Closes any open upvalues that have been created for stack slots at [last]
// and above.
static void closeUpvalues(ObjFiber* fiber, Value* last)
{
while (fiber->openUpvalues != NULL &&
fiber->openUpvalues->value >= last)
{
ObjUpvalue* upvalue = fiber->openUpvalues;
// Move the value into the upvalue itself and point the upvalue to it.
upvalue->closed = *upvalue->value;
upvalue->value = &upvalue->closed;
// Remove it from the open upvalue list.
fiber->openUpvalues = upvalue->next;
}
}
// Looks up a foreign method in [moduleName] on [className] with [signature].
//
// This will try the host's foreign method binder first. If that fails, it
// falls back to handling the built-in modules.
static WrenForeignMethodFn findForeignMethod(WrenVM* vm,
const char* moduleName,
const char* className,
bool isStatic,
const char* signature)
{
WrenForeignMethodFn method = NULL;
if (vm->config.bindForeignMethodFn != NULL)
{
method = vm->config.bindForeignMethodFn(vm, moduleName, className, isStatic,
signature);
}
// If the host didn't provide it, see if it's an optional one.
if (method == NULL)
{
#if WREN_OPT_META
if (strcmp(moduleName, "meta") == 0)
{
method = wrenMetaBindForeignMethod(vm, className, isStatic, signature);
}
#endif
#if WREN_OPT_RANDOM
if (strcmp(moduleName, "random") == 0)
{
method = wrenRandomBindForeignMethod(vm, className, isStatic, signature);
}
#endif
}
return method;
}
// Defines [methodValue] as a method on [classObj].
//
// Handles both foreign methods where [methodValue] is a string containing the
// method's signature and Wren methods where [methodValue] is a function.
//
// Aborts the current fiber if the method is a foreign method that could not be
// found.
static void bindMethod(WrenVM* vm, int methodType, int symbol,
ObjModule* module, ObjClass* classObj, Value methodValue)
{
const char* className = classObj->name->value;
if (methodType == CODE_METHOD_STATIC) classObj = classObj->obj.classObj;
Method method;
if (IS_STRING(methodValue))
{
const char* name = AS_CSTRING(methodValue);
method.type = METHOD_FOREIGN;
method.as.foreign = findForeignMethod(vm, module->name->value,
className,
methodType == CODE_METHOD_STATIC,
name);
if (method.as.foreign == NULL)
{
vm->fiber->error = wrenStringFormat(vm,
"Could not find foreign method '@' for class $ in module '$'.",
methodValue, classObj->name->value, module->name->value);
return;
}
}
else
{
method.as.closure = AS_CLOSURE(methodValue);
method.type = METHOD_BLOCK;
// Patch up the bytecode now that we know the superclass.
wrenBindMethodCode(classObj, method.as.closure->fn);
}
wrenBindMethod(vm, classObj, symbol, method);
}
static void callForeign(WrenVM* vm, ObjFiber* fiber,
WrenForeignMethodFn foreign, int numArgs)
{
ASSERT(vm->apiStack == NULL, "Cannot already be in foreign call.");
vm->apiStack = fiber->stackTop - numArgs;
foreign(vm);
// Discard the stack slots for the arguments and temporaries but leave one
// for the result.
fiber->stackTop = vm->apiStack + 1;
vm->apiStack = NULL;
}
// Handles the current fiber having aborted because of an error.
//
// Walks the call chain of fibers, aborting each one until it hits a fiber that
// handles the error. If none do, tells the VM to stop.
static void runtimeError(WrenVM* vm)
{
ASSERT(wrenHasError(vm->fiber), "Should only call this after an error.");
ObjFiber* current = vm->fiber;
Value error = current->error;
while (current != NULL)
{
// Every fiber along the call chain gets aborted with the same error.
current->error = error;
// If the caller ran this fiber using "try", give it the error and stop.
if (current->state == FIBER_TRY)
{
// Make the caller's try method return the error message.
current->caller->stackTop[-1] = vm->fiber->error;
vm->fiber = current->caller;
return;
}
// Otherwise, unhook the caller since we will never resume and return to it.
ObjFiber* caller = current->caller;
current->caller = NULL;
current = caller;
}
// If we got here, nothing caught the error, so show the stack trace.
wrenDebugPrintStackTrace(vm);
vm->fiber = NULL;
vm->apiStack = NULL;
}
// Aborts the current fiber with an appropriate method not found error for a
// method with [symbol] on [classObj].
static void methodNotFound(WrenVM* vm, ObjClass* classObj, int symbol)
{
vm->fiber->error = wrenStringFormat(vm, "@ does not implement '$'.",
OBJ_VAL(classObj->name), vm->methodNames.data[symbol]->value);
}
// Looks up the previously loaded module with [name].
//
// Returns `NULL` if no module with that name has been loaded.
static ObjModule* getModule(WrenVM* vm, Value name)
{
Value moduleValue = wrenMapGet(vm->modules, name);
return !IS_UNDEFINED(moduleValue) ? AS_MODULE(moduleValue) : NULL;
}
static ObjClosure* compileInModule(WrenVM* vm, Value name, const char* source,
bool isExpression, bool printErrors)
{
// See if the module has already been loaded.
ObjModule* module = getModule(vm, name);
if (module == NULL)
{
module = wrenNewModule(vm, AS_STRING(name));
// It's possible for the wrenMapSet below to resize the modules map,
// and trigger a GC while doing so. When this happens it will collect
// the module we've just created. Once in the map it is safe.
wrenPushRoot(vm, (Obj*)module);
// Store it in the VM's module registry so we don't load the same module
// multiple times.
wrenMapSet(vm, vm->modules, name, OBJ_VAL(module));
wrenPopRoot(vm);
// Implicitly import the core module.
ObjModule* coreModule = getModule(vm, NULL_VAL);
for (int i = 0; i < coreModule->variables.count; i++)
{
wrenDefineVariable(vm, module,
coreModule->variableNames.data[i]->value,
coreModule->variableNames.data[i]->length,
coreModule->variables.data[i], NULL);
}
}
ObjFn* fn = wrenCompile(vm, module, source, isExpression, printErrors);
if (fn == NULL)
{
// TODO: Should we still store the module even if it didn't compile?
return NULL;
}
// Functions are always wrapped in closures.
wrenPushRoot(vm, (Obj*)fn);
ObjClosure* closure = wrenNewClosure(vm, fn);
wrenPopRoot(vm); // fn.
return closure;
}
// Verifies that [superclassValue] is a valid object to inherit from. That
// means it must be a class and cannot be the class of any built-in type.
//
// Also validates that it doesn't result in a class with too many fields and
// the other limitations foreign classes have.
//
// If successful, returns `null`. Otherwise, returns a string for the runtime
// error message.
static Value validateSuperclass(WrenVM* vm, Value name, Value superclassValue,
int numFields)
{
// Make sure the superclass is a class.
if (!IS_CLASS(superclassValue))
{
return wrenStringFormat(vm,
"Class '@' cannot inherit from a non-class object.",
name);
}
// Make sure it doesn't inherit from a sealed built-in type. Primitive methods
// on these classes assume the instance is one of the other Obj___ types and
// will fail horribly if it's actually an ObjInstance.
ObjClass* superclass = AS_CLASS(superclassValue);
if (superclass == vm->classClass ||
superclass == vm->fiberClass ||
superclass == vm->fnClass || // Includes OBJ_CLOSURE.
superclass == vm->listClass ||
superclass == vm->mapClass ||
superclass == vm->rangeClass ||
superclass == vm->stringClass ||
superclass == vm->boolClass ||
superclass == vm->nullClass ||
superclass == vm->numClass)
{
return wrenStringFormat(vm,
"Class '@' cannot inherit from built-in class '@'.",
name, OBJ_VAL(superclass->name));
}
if (superclass->numFields == -1)
{
return wrenStringFormat(vm,
"Class '@' cannot inherit from foreign class '@'.",
name, OBJ_VAL(superclass->name));
}
if (numFields == -1 && superclass->numFields > 0)
{
return wrenStringFormat(vm,
"Foreign class '@' may not inherit from a class with fields.",
name);
}
if (superclass->numFields + numFields > MAX_FIELDS)
{
return wrenStringFormat(vm,
"Class '@' may not have more than 255 fields, including inherited "
"ones.", name);
}
return NULL_VAL;
}
static void bindForeignClass(WrenVM* vm, ObjClass* classObj, ObjModule* module)
{
WrenForeignClassMethods methods;
methods.allocate = NULL;
methods.finalize = NULL;
// Check the optional built-in module first so the host can override it.
if (vm->config.bindForeignClassFn != NULL)
{
methods = vm->config.bindForeignClassFn(vm, module->name->value,
classObj->name->value);
}
// If the host didn't provide it, see if it's a built in optional module.
if (methods.allocate == NULL && methods.finalize == NULL)
{
#if WREN_OPT_RANDOM
if (strcmp(module->name->value, "random") == 0)
{
methods = wrenRandomBindForeignClass(vm, module->name->value,
classObj->name->value);
}
#endif
}
Method method;
method.type = METHOD_FOREIGN;
// Add the symbol even if there is no allocator so we can ensure that the
// symbol itself is always in the symbol table.
int symbol = wrenSymbolTableEnsure(vm, &vm->methodNames, "<allocate>", 10);
if (methods.allocate != NULL)
{
method.as.foreign = methods.allocate;
wrenBindMethod(vm, classObj, symbol, method);
}
// Add the symbol even if there is no finalizer so we can ensure that the
// symbol itself is always in the symbol table.
symbol = wrenSymbolTableEnsure(vm, &vm->methodNames, "<finalize>", 10);
if (methods.finalize != NULL)
{
method.as.foreign = (WrenForeignMethodFn)methods.finalize;
wrenBindMethod(vm, classObj, symbol, method);
}
}
// Completes the process for creating a new class.
//
// The class attributes instance and the class itself should be on the
// top of the fiber's stack.
//
// This process handles moving the attribute data for a class from
// compile time to runtime, since it now has all the attributes associated
// with a class, including for methods.
static void endClass(WrenVM* vm)
{
// Pull the attributes and class off the stack
Value attributes = vm->fiber->stackTop[-2];
Value classValue = vm->fiber->stackTop[-1];
// Remove the stack items
vm->fiber->stackTop -= 2;
ObjClass* classObj = AS_CLASS(classValue);
classObj->attributes = attributes;
}
// Creates a new class.
//
// If [numFields] is -1, the class is a foreign class. The name and superclass
// should be on top of the fiber's stack. After calling this, the top of the
// stack will contain the new class.
//
// Aborts the current fiber if an error occurs.
static void createClass(WrenVM* vm, int numFields, ObjModule* module)
{
// Pull the name and superclass off the stack.
Value name = vm->fiber->stackTop[-2];
Value superclass = vm->fiber->stackTop[-1];
// We have two values on the stack and we are going to leave one, so discard
// the other slot.
vm->fiber->stackTop--;
vm->fiber->error = validateSuperclass(vm, name, superclass, numFields);
if (wrenHasError(vm->fiber)) return;
ObjClass* classObj = wrenNewClass(vm, AS_CLASS(superclass), numFields,
AS_STRING(name));
vm->fiber->stackTop[-1] = OBJ_VAL(classObj);
if (numFields == -1) bindForeignClass(vm, classObj, module);
}
static void createForeign(WrenVM* vm, ObjFiber* fiber, Value* stack)
{
ObjClass* classObj = AS_CLASS(stack[0]);
ASSERT(classObj->numFields == -1, "Class must be a foreign class.");
// TODO: Don't look up every time.
int symbol = wrenSymbolTableFind(&vm->methodNames, "<allocate>", 10);
ASSERT(symbol != -1, "Should have defined <allocate> symbol.");
ASSERT(classObj->methods.count > symbol, "Class should have allocator.");
Method* method = &classObj->methods.data[symbol];
ASSERT(method->type == METHOD_FOREIGN, "Allocator should be foreign.");
// Pass the constructor arguments to the allocator as well.
ASSERT(vm->apiStack == NULL, "Cannot already be in foreign call.");
vm->apiStack = stack;
method->as.foreign(vm);
vm->apiStack = NULL;
}
void wrenFinalizeForeign(WrenVM* vm, ObjForeign* foreign)
{
// TODO: Don't look up every time.
int symbol = wrenSymbolTableFind(&vm->methodNames, "<finalize>", 10);
ASSERT(symbol != -1, "Should have defined <finalize> symbol.");
// If there are no finalizers, don't finalize it.
if (symbol == -1) return;
// If the class doesn't have a finalizer, bail out.
ObjClass* classObj = foreign->obj.classObj;
if (symbol >= classObj->methods.count) return;
Method* method = &classObj->methods.data[symbol];
if (method->type == METHOD_NONE) return;
ASSERT(method->type == METHOD_FOREIGN, "Finalizer should be foreign.");
WrenFinalizerFn finalizer = (WrenFinalizerFn)method->as.foreign;
finalizer(foreign->data);
}
// Let the host resolve an imported module name if it wants to.
static Value resolveModule(WrenVM* vm, Value name)
{
// If the host doesn't care to resolve, leave the name alone.
if (vm->config.resolveModuleFn == NULL) return name;
ObjFiber* fiber = vm->fiber;
ObjFn* fn = fiber->frames[fiber->numFrames - 1].closure->fn;
ObjString* importer = fn->module->name;
const char* resolved = vm->config.resolveModuleFn(vm, importer->value,
AS_CSTRING(name));
if (resolved == NULL)
{
vm->fiber->error = wrenStringFormat(vm,
"Could not resolve module '@' imported from '@'.",
name, OBJ_VAL(importer));
return NULL_VAL;
}
// If they resolved to the exact same string, we don't need to copy it.
if (resolved == AS_CSTRING(name)) return name;
// Copy the string into a Wren String object.
name = wrenNewString(vm, resolved);
DEALLOCATE(vm, (char*)resolved);
return name;
}
static Value importModule(WrenVM* vm, Value name)
{
name = resolveModule(vm, name);
// If the module is already loaded, we don't need to do anything.
Value existing = wrenMapGet(vm->modules, name);
if (!IS_UNDEFINED(existing)) return existing;
wrenPushRoot(vm, AS_OBJ(name));
WrenLoadModuleResult result = {0};
const char* source = NULL;
// Let the host try to provide the module.
if (vm->config.loadModuleFn != NULL)
{
result = vm->config.loadModuleFn(vm, AS_CSTRING(name));
}
// If the host didn't provide it, see if it's a built in optional module.
if (result.source == NULL)
{
result.onComplete = NULL;
ObjString* nameString = AS_STRING(name);
#if WREN_OPT_META
if (strcmp(nameString->value, "meta") == 0) result.source = wrenMetaSource();
#endif
#if WREN_OPT_RANDOM
if (strcmp(nameString->value, "random") == 0) result.source = wrenRandomSource();
#endif
}
if (result.source == NULL)
{
vm->fiber->error = wrenStringFormat(vm, "Could not load module '@'.", name);
wrenPopRoot(vm); // name.
return NULL_VAL;
}
ObjClosure* moduleClosure = compileInModule(vm, name, result.source, false, true);
// Now that we're done, give the result back in case there's cleanup to do.
if(result.onComplete) result.onComplete(vm, AS_CSTRING(name), result);
if (moduleClosure == NULL)
{
vm->fiber->error = wrenStringFormat(vm,
"Could not compile module '@'.", name);
wrenPopRoot(vm); // name.
return NULL_VAL;
}
wrenPopRoot(vm); // name.
// Return the closure that executes the module.
return OBJ_VAL(moduleClosure);
}
static Value getModuleVariable(WrenVM* vm, ObjModule* module,
Value variableName)
{
ObjString* variable = AS_STRING(variableName);
uint32_t variableEntry = wrenSymbolTableFind(&module->variableNames,
variable->value,
variable->length);
// It's a runtime error if the imported variable does not exist.
if (variableEntry != UINT32_MAX)
{
return module->variables.data[variableEntry];
}
vm->fiber->error = wrenStringFormat(vm,
"Could not find a variable named '@' in module '@'.",
variableName, OBJ_VAL(module->name));
return NULL_VAL;
}
inline static bool checkArity(WrenVM* vm, Value value, int numArgs)
{
ASSERT(IS_CLOSURE(value), "Receiver must be a closure.");
ObjFn* fn = AS_CLOSURE(value)->fn;
// We only care about missing arguments, not extras. The "- 1" is because
// numArgs includes the receiver, the function itself, which we don't want to
// count.
if (numArgs - 1 >= fn->arity) return true;
vm->fiber->error = CONST_STRING(vm, "Function expects more arguments.");
return false;
}
// The main bytecode interpreter loop. This is where the magic happens. It is
// also, as you can imagine, highly performance critical.
static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
{
// Remember the current fiber so we can find it if a GC happens.
vm->fiber = fiber;
fiber->state = FIBER_ROOT;
// Hoist these into local variables. They are accessed frequently in the loop
// but assigned less frequently. Keeping them in locals and updating them when
// a call frame has been pushed or popped gives a large speed boost.
register CallFrame* frame;
register Value* stackStart;
register uint8_t* ip;
register ObjFn* fn;
// These macros are designed to only be invoked within this function.
#define PUSH(value) (*fiber->stackTop++ = value)
#define POP() (*(--fiber->stackTop))
#define DROP() (fiber->stackTop--)
#define PEEK() (*(fiber->stackTop - 1))
#define PEEK2() (*(fiber->stackTop - 2))
#define READ_BYTE() (*ip++)
#define READ_SHORT() (ip += 2, (uint16_t)((ip[-2] << 8) | ip[-1]))
// Use this before a CallFrame is pushed to store the local variables back
// into the current one.
#define STORE_FRAME() frame->ip = ip
// Use this after a CallFrame has been pushed or popped to refresh the local
// variables.
#define LOAD_FRAME() \
do \
{ \
frame = &fiber->frames[fiber->numFrames - 1]; \
stackStart = frame->stackStart; \
ip = frame->ip; \
fn = frame->closure->fn; \
} while (false)
// Terminates the current fiber with error string [error]. If another calling
// fiber is willing to catch the error, transfers control to it, otherwise
// exits the interpreter.
#define RUNTIME_ERROR() \
do \
{ \
STORE_FRAME(); \
runtimeError(vm); \
if (vm->fiber == NULL) return WREN_RESULT_RUNTIME_ERROR; \
fiber = vm->fiber; \
LOAD_FRAME(); \
DISPATCH(); \
} while (false)
#if WREN_DEBUG_TRACE_INSTRUCTIONS
// Prints the stack and instruction before each instruction is executed.
#define DEBUG_TRACE_INSTRUCTIONS() \
do \
{ \
wrenDumpStack(fiber); \
wrenDumpInstruction(vm, fn, (int)(ip - fn->code.data)); \
} while (false)
#else
#define DEBUG_TRACE_INSTRUCTIONS() do { } while (false)
#endif
#if WREN_COMPUTED_GOTO
static void* dispatchTable[] = {
#define OPCODE(name, _) &&code_##name,
// Begin file "wren_opcodes.h"
// This defines the bytecode instructions used by the VM. It does so by invoking
// an OPCODE() macro which is expected to be defined at the point that this is
// included. (See: http://en.wikipedia.org/wiki/X_Macro for more.)
//
// The first argument is the name of the opcode. The second is its "stack
// effect" -- the amount that the op code changes the size of the stack. A
// stack effect of 1 means it pushes a value and the stack grows one larger.
// -2 means it pops two values, etc.
//
// Note that the order of instructions here affects the order of the dispatch
// table in the VM's interpreter loop. That in turn affects caching which
// affects overall performance. Take care to run benchmarks if you change the
// order here.
// Load the constant at index [arg].
OPCODE(CONSTANT, 1)
// Push null onto the stack.
OPCODE(NULL, 1)
// Push false onto the stack.
OPCODE(FALSE, 1)
// Push true onto the stack.
OPCODE(TRUE, 1)
// Pushes the value in the given local slot.
OPCODE(LOAD_LOCAL_0, 1)
OPCODE(LOAD_LOCAL_1, 1)
OPCODE(LOAD_LOCAL_2, 1)
OPCODE(LOAD_LOCAL_3, 1)
OPCODE(LOAD_LOCAL_4, 1)
OPCODE(LOAD_LOCAL_5, 1)
OPCODE(LOAD_LOCAL_6, 1)
OPCODE(LOAD_LOCAL_7, 1)
OPCODE(LOAD_LOCAL_8, 1)
// Note: The compiler assumes the following _STORE instructions always
// immediately follow their corresponding _LOAD ones.
// Pushes the value in local slot [arg].
OPCODE(LOAD_LOCAL, 1)
// Stores the top of stack in local slot [arg]. Does not pop it.
OPCODE(STORE_LOCAL, 0)
// Pushes the value in upvalue [arg].
OPCODE(LOAD_UPVALUE, 1)
// Stores the top of stack in upvalue [arg]. Does not pop it.
OPCODE(STORE_UPVALUE, 0)
// Pushes the value of the top-level variable in slot [arg].
OPCODE(LOAD_MODULE_VAR, 1)
// Stores the top of stack in top-level variable slot [arg]. Does not pop it.
OPCODE(STORE_MODULE_VAR, 0)
// Pushes the value of the field in slot [arg] of the receiver of the current
// function. This is used for regular field accesses on "this" directly in
// methods. This instruction is faster than the more general CODE_LOAD_FIELD
// instruction.
OPCODE(LOAD_FIELD_THIS, 1)
// Stores the top of the stack in field slot [arg] in the receiver of the
// current value. Does not pop the value. This instruction is faster than the
// more general CODE_LOAD_FIELD instruction.
OPCODE(STORE_FIELD_THIS, 0)
// Pops an instance and pushes the value of the field in slot [arg] of it.
OPCODE(LOAD_FIELD, 0)
// Pops an instance and stores the subsequent top of stack in field slot
// [arg] in it. Does not pop the value.
OPCODE(STORE_FIELD, -1)
// Pop and discard the top of stack.
OPCODE(POP, -1)
// Invoke the method with symbol [arg]. The number indicates the number of
// arguments (not including the receiver).
OPCODE(CALL_0, 0)
OPCODE(CALL_1, -1)
OPCODE(CALL_2, -2)
OPCODE(CALL_3, -3)
OPCODE(CALL_4, -4)
OPCODE(CALL_5, -5)
OPCODE(CALL_6, -6)
OPCODE(CALL_7, -7)
OPCODE(CALL_8, -8)
OPCODE(CALL_9, -9)
OPCODE(CALL_10, -10)
OPCODE(CALL_11, -11)
OPCODE(CALL_12, -12)
OPCODE(CALL_13, -13)
OPCODE(CALL_14, -14)
OPCODE(CALL_15, -15)
OPCODE(CALL_16, -16)
// Invoke a superclass method with symbol [arg]. The number indicates the
// number of arguments (not including the receiver).
OPCODE(SUPER_0, 0)
OPCODE(SUPER_1, -1)
OPCODE(SUPER_2, -2)
OPCODE(SUPER_3, -3)
OPCODE(SUPER_4, -4)
OPCODE(SUPER_5, -5)
OPCODE(SUPER_6, -6)
OPCODE(SUPER_7, -7)
OPCODE(SUPER_8, -8)
OPCODE(SUPER_9, -9)
OPCODE(SUPER_10, -10)
OPCODE(SUPER_11, -11)
OPCODE(SUPER_12, -12)
OPCODE(SUPER_13, -13)
OPCODE(SUPER_14, -14)
OPCODE(SUPER_15, -15)
OPCODE(SUPER_16, -16)
// Jump the instruction pointer [arg] forward.
OPCODE(JUMP, 0)
// Jump the instruction pointer [arg] backward.
OPCODE(LOOP, 0)
// Pop and if not truthy then jump the instruction pointer [arg] forward.
OPCODE(JUMP_IF, -1)
// If the top of the stack is false, jump [arg] forward. Otherwise, pop and
// continue.
OPCODE(AND, -1)
// If the top of the stack is non-false, jump [arg] forward. Otherwise, pop
// and continue.
OPCODE(OR, -1)
// Close the upvalue for the local on the top of the stack, then pop it.
OPCODE(CLOSE_UPVALUE, -1)
// Exit from the current function and return the value on the top of the
// stack.
OPCODE(RETURN, 0)
// Creates a closure for the function stored at [arg] in the constant table.
//
// Following the function argument is a number of arguments, two for each
// upvalue. The first is true if the variable being captured is a local (as
// opposed to an upvalue), and the second is the index of the local or
// upvalue being captured.
//
// Pushes the created closure.
OPCODE(CLOSURE, 1)
// Creates a new instance of a class.
//
// Assumes the class object is in slot zero, and replaces it with the new
// uninitialized instance of that class. This opcode is only emitted by the
// compiler-generated constructor metaclass methods.
OPCODE(CONSTRUCT, 0)
// Creates a new instance of a foreign class.
//
// Assumes the class object is in slot zero, and replaces it with the new
// uninitialized instance of that class. This opcode is only emitted by the
// compiler-generated constructor metaclass methods.
OPCODE(FOREIGN_CONSTRUCT, 0)
// Creates a class. Top of stack is the superclass. Below that is a string for
// the name of the class. Byte [arg] is the number of fields in the class.
OPCODE(CLASS, -1)
// Ends a class.
// Atm the stack contains the class and the ClassAttributes (or null).
OPCODE(END_CLASS, -2)
// Creates a foreign class. Top of stack is the superclass. Below that is a
// string for the name of the class.
OPCODE(FOREIGN_CLASS, -1)
// Define a method for symbol [arg]. The class receiving the method is popped
// off the stack, then the function defining the body is popped.
//
// If a foreign method is being defined, the "function" will be a string
// identifying the foreign method. Otherwise, it will be a function or
// closure.
OPCODE(METHOD_INSTANCE, -2)
// Define a method for symbol [arg]. The class whose metaclass will receive
// the method is popped off the stack, then the function defining the body is
// popped.
//
// If a foreign method is being defined, the "function" will be a string
// identifying the foreign method. Otherwise, it will be a function or
// closure.
OPCODE(METHOD_STATIC, -2)
// This is executed at the end of the module's body. Pushes NULL onto the stack
// as the "return value" of the import statement and stores the module as the
// most recently imported one.
OPCODE(END_MODULE, 1)
// Import a module whose name is the string stored at [arg] in the constant
// table.
//
// Pushes null onto the stack so that the fiber for the imported module can
// replace that with a dummy value when it returns. (Fibers always return a
// value when resuming a caller.)
OPCODE(IMPORT_MODULE, 1)
// Import a variable from the most recently imported module. The name of the
// variable to import is at [arg] in the constant table. Pushes the loaded
// variable's value.
OPCODE(IMPORT_VARIABLE, 1)
// This pseudo-instruction indicates the end of the bytecode. It should
// always be preceded by a `CODE_RETURN`, so is never actually executed.
OPCODE(END, 0)
// End file "wren_opcodes.h"
#undef OPCODE
};
#define INTERPRET_LOOP DISPATCH();
#define CASE_CODE(name) code_##name
#define DISPATCH() \
do \
{ \
DEBUG_TRACE_INSTRUCTIONS(); \
goto *dispatchTable[instruction = (Code)READ_BYTE()]; \
} while (false)
#else
#define INTERPRET_LOOP \
loop: \
DEBUG_TRACE_INSTRUCTIONS(); \
switch (instruction = (Code)READ_BYTE())
#define CASE_CODE(name) case CODE_##name
#define DISPATCH() goto loop
#endif
LOAD_FRAME();
Code instruction;
INTERPRET_LOOP
{
CASE_CODE(LOAD_LOCAL_0):
CASE_CODE(LOAD_LOCAL_1):
CASE_CODE(LOAD_LOCAL_2):
CASE_CODE(LOAD_LOCAL_3):
CASE_CODE(LOAD_LOCAL_4):
CASE_CODE(LOAD_LOCAL_5):
CASE_CODE(LOAD_LOCAL_6):
CASE_CODE(LOAD_LOCAL_7):
CASE_CODE(LOAD_LOCAL_8):
PUSH(stackStart[instruction - CODE_LOAD_LOCAL_0]);
DISPATCH();
CASE_CODE(LOAD_LOCAL):
PUSH(stackStart[READ_BYTE()]);
DISPATCH();
CASE_CODE(LOAD_FIELD_THIS):
{
uint8_t field = READ_BYTE();
Value receiver = stackStart[0];
ASSERT(IS_INSTANCE(receiver), "Receiver should be instance.");
ObjInstance* instance = AS_INSTANCE(receiver);
ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field.");
PUSH(instance->fields[field]);
DISPATCH();
}
CASE_CODE(POP): DROP(); DISPATCH();
CASE_CODE(NULL): PUSH(NULL_VAL); DISPATCH();
CASE_CODE(FALSE): PUSH(FALSE_VAL); DISPATCH();
CASE_CODE(TRUE): PUSH(TRUE_VAL); DISPATCH();
CASE_CODE(STORE_LOCAL):
stackStart[READ_BYTE()] = PEEK();
DISPATCH();
CASE_CODE(CONSTANT):
PUSH(fn->constants.data[READ_SHORT()]);
DISPATCH();
{
// The opcodes for doing method and superclass calls share a lot of code.
// However, doing an if() test in the middle of the instruction sequence
// to handle the bit that is special to super calls makes the non-super
// call path noticeably slower.
//
// Instead, we do this old school using an explicit goto to share code for
// everything at the tail end of the call-handling code that is the same
// between normal and superclass calls.
int numArgs;
int symbol;
Value* args;
ObjClass* classObj;
Method* method;
CASE_CODE(CALL_0):
CASE_CODE(CALL_1):
CASE_CODE(CALL_2):
CASE_CODE(CALL_3):
CASE_CODE(CALL_4):
CASE_CODE(CALL_5):
CASE_CODE(CALL_6):
CASE_CODE(CALL_7):
CASE_CODE(CALL_8):
CASE_CODE(CALL_9):
CASE_CODE(CALL_10):
CASE_CODE(CALL_11):
CASE_CODE(CALL_12):
CASE_CODE(CALL_13):
CASE_CODE(CALL_14):
CASE_CODE(CALL_15):
CASE_CODE(CALL_16):
// Add one for the implicit receiver argument.
numArgs = instruction - CODE_CALL_0 + 1;
symbol = READ_SHORT();
// The receiver is the first argument.
args = fiber->stackTop - numArgs;
classObj = wrenGetClassInline(vm, args[0]);
goto completeCall;
CASE_CODE(SUPER_0):
CASE_CODE(SUPER_1):
CASE_CODE(SUPER_2):
CASE_CODE(SUPER_3):
CASE_CODE(SUPER_4):
CASE_CODE(SUPER_5):
CASE_CODE(SUPER_6):
CASE_CODE(SUPER_7):
CASE_CODE(SUPER_8):
CASE_CODE(SUPER_9):
CASE_CODE(SUPER_10):
CASE_CODE(SUPER_11):
CASE_CODE(SUPER_12):
CASE_CODE(SUPER_13):
CASE_CODE(SUPER_14):
CASE_CODE(SUPER_15):
CASE_CODE(SUPER_16):
// Add one for the implicit receiver argument.
numArgs = instruction - CODE_SUPER_0 + 1;
symbol = READ_SHORT();
// The receiver is the first argument.
args = fiber->stackTop - numArgs;
// The superclass is stored in a constant.
classObj = AS_CLASS(fn->constants.data[READ_SHORT()]);
goto completeCall;
completeCall:
// If the class's method table doesn't include the symbol, bail.
if (symbol >= classObj->methods.count ||
(method = &classObj->methods.data[symbol])->type == METHOD_NONE)
{
methodNotFound(vm, classObj, symbol);
RUNTIME_ERROR();
}
switch (method->type)
{
case METHOD_PRIMITIVE:
if (method->as.primitive(vm, args))
{
// The result is now in the first arg slot. Discard the other
// stack slots.
fiber->stackTop -= numArgs - 1;
} else {
// An error, fiber switch, or call frame change occurred.
STORE_FRAME();
// If we don't have a fiber to switch to, stop interpreting.
fiber = vm->fiber;
if (fiber == NULL) return WREN_RESULT_SUCCESS;
if (wrenHasError(fiber)) RUNTIME_ERROR();
LOAD_FRAME();
}
break;
case METHOD_FUNCTION_CALL:
if (!checkArity(vm, args[0], numArgs)) {
RUNTIME_ERROR();
break;
}
STORE_FRAME();
method->as.primitive(vm, args);
LOAD_FRAME();
break;
case METHOD_FOREIGN:
callForeign(vm, fiber, method->as.foreign, numArgs);
if (wrenHasError(fiber)) RUNTIME_ERROR();
break;
case METHOD_BLOCK:
STORE_FRAME();
wrenCallFunction(vm, fiber, (ObjClosure*)method->as.closure, numArgs);
LOAD_FRAME();
break;
case METHOD_NONE:
UNREACHABLE();
break;
}
DISPATCH();
}
CASE_CODE(LOAD_UPVALUE):
{
ObjUpvalue** upvalues = frame->closure->upvalues;
PUSH(*upvalues[READ_BYTE()]->value);
DISPATCH();
}
CASE_CODE(STORE_UPVALUE):
{
ObjUpvalue** upvalues = frame->closure->upvalues;
*upvalues[READ_BYTE()]->value = PEEK();
DISPATCH();
}
CASE_CODE(LOAD_MODULE_VAR):
PUSH(fn->module->variables.data[READ_SHORT()]);
DISPATCH();
CASE_CODE(STORE_MODULE_VAR):
fn->module->variables.data[READ_SHORT()] = PEEK();
DISPATCH();
CASE_CODE(STORE_FIELD_THIS):
{
uint8_t field = READ_BYTE();
Value receiver = stackStart[0];
ASSERT(IS_INSTANCE(receiver), "Receiver should be instance.");
ObjInstance* instance = AS_INSTANCE(receiver);
ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field.");
instance->fields[field] = PEEK();
DISPATCH();
}
CASE_CODE(LOAD_FIELD):
{
uint8_t field = READ_BYTE();
Value receiver = POP();
ASSERT(IS_INSTANCE(receiver), "Receiver should be instance.");
ObjInstance* instance = AS_INSTANCE(receiver);
ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field.");
PUSH(instance->fields[field]);
DISPATCH();
}
CASE_CODE(STORE_FIELD):
{
uint8_t field = READ_BYTE();
Value receiver = POP();
ASSERT(IS_INSTANCE(receiver), "Receiver should be instance.");
ObjInstance* instance = AS_INSTANCE(receiver);
ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field.");
instance->fields[field] = PEEK();
DISPATCH();
}
CASE_CODE(JUMP):
{
uint16_t offset = READ_SHORT();
ip += offset;
DISPATCH();
}
CASE_CODE(LOOP):
{
// Jump back to the top of the loop.
uint16_t offset = READ_SHORT();
ip -= offset;
DISPATCH();
}
CASE_CODE(JUMP_IF):
{
uint16_t offset = READ_SHORT();
Value condition = POP();
if (wrenIsFalsyValue(condition)) ip += offset;
DISPATCH();
}
CASE_CODE(AND):
{
uint16_t offset = READ_SHORT();
Value condition = PEEK();
if (wrenIsFalsyValue(condition))
{
// Short-circuit the right hand side.
ip += offset;
}
else
{
// Discard the condition and evaluate the right hand side.
DROP();
}
DISPATCH();
}
CASE_CODE(OR):
{
uint16_t offset = READ_SHORT();
Value condition = PEEK();
if (wrenIsFalsyValue(condition))
{
// Discard the condition and evaluate the right hand side.
DROP();
}
else
{
// Short-circuit the right hand side.
ip += offset;
}
DISPATCH();
}
CASE_CODE(CLOSE_UPVALUE):
// Close the upvalue for the local if we have one.
closeUpvalues(fiber, fiber->stackTop - 1);
DROP();
DISPATCH();
CASE_CODE(RETURN):
{
Value result = POP();
fiber->numFrames--;
// Close any upvalues still in scope.
closeUpvalues(fiber, stackStart);
// If the fiber is complete, end it.
if (fiber->numFrames == 0)
{
// See if there's another fiber to return to. If not, we're done.
if (fiber->caller == NULL)
{
// Store the final result value at the beginning of the stack so the
// C API can get it.
fiber->stack[0] = result;
fiber->stackTop = fiber->stack + 1;
return WREN_RESULT_SUCCESS;
}
ObjFiber* resumingFiber = fiber->caller;
fiber->caller = NULL;
fiber = resumingFiber;
vm->fiber = resumingFiber;
// Store the result in the resuming fiber.
fiber->stackTop[-1] = result;
}
else
{
// Store the result of the block in the first slot, which is where the
// caller expects it.
stackStart[0] = result;
// Discard the stack slots for the call frame (leaving one slot for the
// result).
fiber->stackTop = frame->stackStart + 1;
}
LOAD_FRAME();
DISPATCH();
}
CASE_CODE(CONSTRUCT):
ASSERT(IS_CLASS(stackStart[0]), "'this' should be a class.");
stackStart[0] = wrenNewInstance(vm, AS_CLASS(stackStart[0]));
DISPATCH();
CASE_CODE(FOREIGN_CONSTRUCT):
ASSERT(IS_CLASS(stackStart[0]), "'this' should be a class.");
createForeign(vm, fiber, stackStart);
if (wrenHasError(fiber)) RUNTIME_ERROR();
DISPATCH();
CASE_CODE(CLOSURE):
{
// Create the closure and push it on the stack before creating upvalues
// so that it doesn't get collected.
ObjFn* function = AS_FN(fn->constants.data[READ_SHORT()]);
ObjClosure* closure = wrenNewClosure(vm, function);
PUSH(OBJ_VAL(closure));
// Capture upvalues, if any.
for (int i = 0; i < function->numUpvalues; i++)
{
uint8_t isLocal = READ_BYTE();
uint8_t index = READ_BYTE();
if (isLocal)
{
// Make an new upvalue to close over the parent's local variable.
closure->upvalues[i] = captureUpvalue(vm, fiber,
frame->stackStart + index);
}
else
{
// Use the same upvalue as the current call frame.
closure->upvalues[i] = frame->closure->upvalues[index];
}
}
DISPATCH();
}
CASE_CODE(END_CLASS):
{
endClass(vm);
if (wrenHasError(fiber)) RUNTIME_ERROR();
DISPATCH();
}
CASE_CODE(CLASS):
{
createClass(vm, READ_BYTE(), NULL);
if (wrenHasError(fiber)) RUNTIME_ERROR();
DISPATCH();
}
CASE_CODE(FOREIGN_CLASS):
{
createClass(vm, -1, fn->module);
if (wrenHasError(fiber)) RUNTIME_ERROR();
DISPATCH();
}
CASE_CODE(METHOD_INSTANCE):
CASE_CODE(METHOD_STATIC):
{
uint16_t symbol = READ_SHORT();
ObjClass* classObj = AS_CLASS(PEEK());
Value method = PEEK2();
bindMethod(vm, instruction, symbol, fn->module, classObj, method);
if (wrenHasError(fiber)) RUNTIME_ERROR();
DROP();
DROP();
DISPATCH();
}
CASE_CODE(END_MODULE):
{
vm->lastModule = fn->module;
PUSH(NULL_VAL);
DISPATCH();
}
CASE_CODE(IMPORT_MODULE):
{
// Make a slot on the stack for the module's fiber to place the return
// value. It will be popped after this fiber is resumed. Store the
// imported module's closure in the slot in case a GC happens when
// invoking the closure.
PUSH(importModule(vm, fn->constants.data[READ_SHORT()]));
if (wrenHasError(fiber)) RUNTIME_ERROR();
// If we get a closure, call it to execute the module body.
if (IS_CLOSURE(PEEK()))
{
STORE_FRAME();
ObjClosure* closure = AS_CLOSURE(PEEK());
wrenCallFunction(vm, fiber, closure, 1);
LOAD_FRAME();
}
else
{
// The module has already been loaded. Remember it so we can import
// variables from it if needed.
vm->lastModule = AS_MODULE(PEEK());
}
DISPATCH();
}
CASE_CODE(IMPORT_VARIABLE):
{
Value variable = fn->constants.data[READ_SHORT()];
ASSERT(vm->lastModule != NULL, "Should have already imported module.");
Value result = getModuleVariable(vm, vm->lastModule, variable);
if (wrenHasError(fiber)) RUNTIME_ERROR();
PUSH(result);
DISPATCH();
}
CASE_CODE(END):
// A CODE_END should always be preceded by a CODE_RETURN. If we get here,
// the compiler generated wrong code.
UNREACHABLE();
}
// We should only exit this function from an explicit return from CODE_RETURN
// or a runtime error.
UNREACHABLE();
return WREN_RESULT_RUNTIME_ERROR;
#undef READ_BYTE
#undef READ_SHORT
}
WrenHandle* wrenMakeCallHandle(WrenVM* vm, const char* signature)
{
ASSERT(signature != NULL, "Signature cannot be NULL.");
int signatureLength = (int)strlen(signature);
ASSERT(signatureLength > 0, "Signature cannot be empty.");
// Count the number parameters the method expects.
int numParams = 0;
if (signature[signatureLength - 1] == ')')
{
for (int i = signatureLength - 1; i > 0 && signature[i] != '('; i--)
{
if (signature[i] == '_') numParams++;
}
}
// Count subscript arguments.
if (signature[0] == '[')
{
for (int i = 0; i < signatureLength && signature[i] != ']'; i++)
{
if (signature[i] == '_') numParams++;
}
}
// Add the signatue to the method table.
int method = wrenSymbolTableEnsure(vm, &vm->methodNames,
signature, signatureLength);
// Create a little stub function that assumes the arguments are on the stack
// and calls the method.
ObjFn* fn = wrenNewFunction(vm, NULL, numParams + 1);
// Wrap the function in a closure and then in a handle. Do this here so it
// doesn't get collected as we fill it in.
WrenHandle* value = wrenMakeHandle(vm, OBJ_VAL(fn));
value->value = OBJ_VAL(wrenNewClosure(vm, fn));
wrenByteBufferWrite(vm, &fn->code, (uint8_t)(CODE_CALL_0 + numParams));
wrenByteBufferWrite(vm, &fn->code, (method >> 8) & 0xff);
wrenByteBufferWrite(vm, &fn->code, method & 0xff);
wrenByteBufferWrite(vm, &fn->code, CODE_RETURN);
wrenByteBufferWrite(vm, &fn->code, CODE_END);
wrenIntBufferFill(vm, &fn->debug->sourceLines, 0, 5);
wrenFunctionBindName(vm, fn, signature, signatureLength);
return value;
}
WrenInterpretResult wrenCall(WrenVM* vm, WrenHandle* method)
{
ASSERT(method != NULL, "Method cannot be NULL.");
ASSERT(IS_CLOSURE(method->value), "Method must be a method handle.");
ASSERT(vm->fiber != NULL, "Must set up arguments for call first.");
ASSERT(vm->apiStack != NULL, "Must set up arguments for call first.");
ASSERT(vm->fiber->numFrames == 0, "Can not call from a foreign method.");
ObjClosure* closure = AS_CLOSURE(method->value);
ASSERT(vm->fiber->stackTop - vm->fiber->stack >= closure->fn->arity,
"Stack must have enough arguments for method.");
// Clear the API stack. Now that wrenCall() has control, we no longer need
// it. We use this being non-null to tell if re-entrant calls to foreign
// methods are happening, so it's important to clear it out now so that you
// can call foreign methods from within calls to wrenCall().
vm->apiStack = NULL;
// Discard any extra temporary slots. We take for granted that the stub
// function has exactly one slot for each argument.
vm->fiber->stackTop = &vm->fiber->stack[closure->fn->maxSlots];
wrenCallFunction(vm, vm->fiber, closure, 0);
WrenInterpretResult result = runInterpreter(vm, vm->fiber);
// If the call didn't abort, then set up the API stack to point to the
// beginning of the stack so the host can access the call's return value.
if (vm->fiber != NULL) vm->apiStack = vm->fiber->stack;
return result;
}
WrenHandle* wrenMakeHandle(WrenVM* vm, Value value)
{
if (IS_OBJ(value)) wrenPushRoot(vm, AS_OBJ(value));
// Make a handle for it.
WrenHandle* handle = ALLOCATE(vm, WrenHandle);
handle->value = value;
if (IS_OBJ(value)) wrenPopRoot(vm);
// Add it to the front of the linked list of handles.
if (vm->handles != NULL) vm->handles->prev = handle;
handle->prev = NULL;
handle->next = vm->handles;
vm->handles = handle;
return handle;
}
void wrenReleaseHandle(WrenVM* vm, WrenHandle* handle)
{
ASSERT(handle != NULL, "Handle cannot be NULL.");
// Update the VM's head pointer if we're releasing the first handle.
if (vm->handles == handle) vm->handles = handle->next;
// Unlink it from the list.
if (handle->prev != NULL) handle->prev->next = handle->next;
if (handle->next != NULL) handle->next->prev = handle->prev;
// Clear it out. This isn't strictly necessary since we're going to free it,
// but it makes for easier debugging.
handle->prev = NULL;
handle->next = NULL;
handle->value = NULL_VAL;
DEALLOCATE(vm, handle);
}
WrenInterpretResult wrenInterpret(WrenVM* vm, const char* module,
const char* source)
{
ObjClosure* closure = wrenCompileSource(vm, module, source, false, true);
if (closure == NULL) return WREN_RESULT_COMPILE_ERROR;
wrenPushRoot(vm, (Obj*)closure);
ObjFiber* fiber = wrenNewFiber(vm, closure);
wrenPopRoot(vm); // closure.
vm->apiStack = NULL;
return runInterpreter(vm, fiber);
}
ObjClosure* wrenCompileSource(WrenVM* vm, const char* module, const char* source,
bool isExpression, bool printErrors)
{
Value nameValue = NULL_VAL;
if (module != NULL)
{
nameValue = wrenNewString(vm, module);
wrenPushRoot(vm, AS_OBJ(nameValue));
}
ObjClosure* closure = compileInModule(vm, nameValue, source,
isExpression, printErrors);
if (module != NULL) wrenPopRoot(vm); // nameValue.
return closure;
}
Value wrenGetModuleVariable(WrenVM* vm, Value moduleName, Value variableName)
{
ObjModule* module = getModule(vm, moduleName);
if (module == NULL)
{
vm->fiber->error = wrenStringFormat(vm, "Module '@' is not loaded.",
moduleName);
return NULL_VAL;
}
return getModuleVariable(vm, module, variableName);
}
Value wrenFindVariable(WrenVM* vm, ObjModule* module, const char* name)
{
int symbol = wrenSymbolTableFind(&module->variableNames, name, strlen(name));
return module->variables.data[symbol];
}
int wrenDeclareVariable(WrenVM* vm, ObjModule* module, const char* name,
size_t length, int line)
{
if (module->variables.count == MAX_MODULE_VARS) return -2;
// Implicitly defined variables get a "value" that is the line where the
// variable is first used. We'll use that later to report an error on the
// right line.
wrenValueBufferWrite(vm, &module->variables, NUM_VAL(line));
return wrenSymbolTableAdd(vm, &module->variableNames, name, length);
}
int wrenDefineVariable(WrenVM* vm, ObjModule* module, const char* name,
size_t length, Value value, int* line)
{
if (module->variables.count == MAX_MODULE_VARS) return -2;
if (IS_OBJ(value)) wrenPushRoot(vm, AS_OBJ(value));
// See if the variable is already explicitly or implicitly declared.
int symbol = wrenSymbolTableFind(&module->variableNames, name, length);
if (symbol == -1)
{
// Brand new variable.
symbol = wrenSymbolTableAdd(vm, &module->variableNames, name, length);
wrenValueBufferWrite(vm, &module->variables, value);
}
else if (IS_NUM(module->variables.data[symbol]))
{
// An implicitly declared variable's value will always be a number.
// Now we have a real definition.
if(line) *line = (int)AS_NUM(module->variables.data[symbol]);
module->variables.data[symbol] = value;
// If this was a localname we want to error if it was
// referenced before this definition.
if (wrenIsLocalName(name)) symbol = -3;
}
else
{
// Already explicitly declared.
symbol = -1;
}
if (IS_OBJ(value)) wrenPopRoot(vm);
return symbol;
}
// TODO: Inline?
void wrenPushRoot(WrenVM* vm, Obj* obj)
{
ASSERT(obj != NULL, "Can't root NULL.");
ASSERT(vm->numTempRoots < WREN_MAX_TEMP_ROOTS, "Too many temporary roots.");
vm->tempRoots[vm->numTempRoots++] = obj;
}
void wrenPopRoot(WrenVM* vm)
{
ASSERT(vm->numTempRoots > 0, "No temporary roots to release.");
vm->numTempRoots--;
}
int wrenGetSlotCount(WrenVM* vm)
{
if (vm->apiStack == NULL) return 0;
return (int)(vm->fiber->stackTop - vm->apiStack);
}
void wrenEnsureSlots(WrenVM* vm, int numSlots)
{
// If we don't have a fiber accessible, create one for the API to use.
if (vm->apiStack == NULL)
{
vm->fiber = wrenNewFiber(vm, NULL);
vm->apiStack = vm->fiber->stack;
}
int currentSize = (int)(vm->fiber->stackTop - vm->apiStack);
if (currentSize >= numSlots) return;
// Grow the stack if needed.
int needed = (int)(vm->apiStack - vm->fiber->stack) + numSlots;
wrenEnsureStack(vm, vm->fiber, needed);
vm->fiber->stackTop = vm->apiStack + numSlots;
}
// Ensures that [slot] is a valid index into the API's stack of slots.
static void validateApiSlot(WrenVM* vm, int slot)
{
ASSERT(slot >= 0, "Slot cannot be negative.");
ASSERT(slot < wrenGetSlotCount(vm), "Not that many slots.");
}
// Gets the type of the object in [slot].
WrenType wrenGetSlotType(WrenVM* vm, int slot)
{
validateApiSlot(vm, slot);
if (IS_BOOL(vm->apiStack[slot])) return WREN_TYPE_BOOL;
if (IS_NUM(vm->apiStack[slot])) return WREN_TYPE_NUM;
if (IS_FOREIGN(vm->apiStack[slot])) return WREN_TYPE_FOREIGN;
if (IS_LIST(vm->apiStack[slot])) return WREN_TYPE_LIST;
if (IS_MAP(vm->apiStack[slot])) return WREN_TYPE_MAP;
if (IS_NULL(vm->apiStack[slot])) return WREN_TYPE_NULL;
if (IS_STRING(vm->apiStack[slot])) return WREN_TYPE_STRING;
return WREN_TYPE_UNKNOWN;
}
bool wrenGetSlotBool(WrenVM* vm, int slot)
{
validateApiSlot(vm, slot);
ASSERT(IS_BOOL(vm->apiStack[slot]), "Slot must hold a bool.");
return AS_BOOL(vm->apiStack[slot]);
}
const char* wrenGetSlotBytes(WrenVM* vm, int slot, int* length)
{
validateApiSlot(vm, slot);
ASSERT(IS_STRING(vm->apiStack[slot]), "Slot must hold a string.");
ObjString* string = AS_STRING(vm->apiStack[slot]);
*length = string->length;
return string->value;
}
double wrenGetSlotDouble(WrenVM* vm, int slot)
{
validateApiSlot(vm, slot);
ASSERT(IS_NUM(vm->apiStack[slot]), "Slot must hold a number.");
return AS_NUM(vm->apiStack[slot]);
}
void* wrenGetSlotForeign(WrenVM* vm, int slot)
{
validateApiSlot(vm, slot);
ASSERT(IS_FOREIGN(vm->apiStack[slot]),
"Slot must hold a foreign instance.");
return AS_FOREIGN(vm->apiStack[slot])->data;
}
const char* wrenGetSlotString(WrenVM* vm, int slot)
{
validateApiSlot(vm, slot);
ASSERT(IS_STRING(vm->apiStack[slot]), "Slot must hold a string.");
return AS_CSTRING(vm->apiStack[slot]);
}
WrenHandle* wrenGetSlotHandle(WrenVM* vm, int slot)
{
validateApiSlot(vm, slot);
return wrenMakeHandle(vm, vm->apiStack[slot]);
}
// Stores [value] in [slot] in the foreign call stack.
static void setSlot(WrenVM* vm, int slot, Value value)
{
validateApiSlot(vm, slot);
vm->apiStack[slot] = value;
}
void wrenSetSlotBool(WrenVM* vm, int slot, bool value)
{
setSlot(vm, slot, BOOL_VAL(value));
}
void wrenSetSlotBytes(WrenVM* vm, int slot, const char* bytes, size_t length)
{
ASSERT(bytes != NULL, "Byte array cannot be NULL.");
setSlot(vm, slot, wrenNewStringLength(vm, bytes, length));
}
void wrenSetSlotDouble(WrenVM* vm, int slot, double value)
{
setSlot(vm, slot, NUM_VAL(value));
}
void* wrenSetSlotNewForeign(WrenVM* vm, int slot, int classSlot, size_t size)
{
validateApiSlot(vm, slot);
validateApiSlot(vm, classSlot);
ASSERT(IS_CLASS(vm->apiStack[classSlot]), "Slot must hold a class.");
ObjClass* classObj = AS_CLASS(vm->apiStack[classSlot]);
ASSERT(classObj->numFields == -1, "Class must be a foreign class.");
ObjForeign* foreign = wrenNewForeign(vm, classObj, size);
vm->apiStack[slot] = OBJ_VAL(foreign);
return (void*)foreign->data;
}
void wrenSetSlotNewList(WrenVM* vm, int slot)
{
setSlot(vm, slot, OBJ_VAL(wrenNewList(vm, 0)));
}
void wrenSetSlotNewMap(WrenVM* vm, int slot)
{
setSlot(vm, slot, OBJ_VAL(wrenNewMap(vm)));
}
void wrenSetSlotNull(WrenVM* vm, int slot)
{
setSlot(vm, slot, NULL_VAL);
}
void wrenSetSlotString(WrenVM* vm, int slot, const char* text)
{
ASSERT(text != NULL, "String cannot be NULL.");
setSlot(vm, slot, wrenNewString(vm, text));
}
void wrenSetSlotHandle(WrenVM* vm, int slot, WrenHandle* handle)
{
ASSERT(handle != NULL, "Handle cannot be NULL.");
setSlot(vm, slot, handle->value);
}
int wrenGetListCount(WrenVM* vm, int slot)
{
validateApiSlot(vm, slot);
ASSERT(IS_LIST(vm->apiStack[slot]), "Slot must hold a list.");
ValueBuffer elements = AS_LIST(vm->apiStack[slot])->elements;
return elements.count;
}
void wrenGetListElement(WrenVM* vm, int listSlot, int index, int elementSlot)
{
validateApiSlot(vm, listSlot);
validateApiSlot(vm, elementSlot);
ASSERT(IS_LIST(vm->apiStack[listSlot]), "Slot must hold a list.");
ValueBuffer elements = AS_LIST(vm->apiStack[listSlot])->elements;
uint32_t usedIndex = wrenValidateIndex(elements.count, index);
ASSERT(usedIndex != UINT32_MAX, "Index out of bounds.");
vm->apiStack[elementSlot] = elements.data[usedIndex];
}
void wrenSetListElement(WrenVM* vm, int listSlot, int index, int elementSlot)
{
validateApiSlot(vm, listSlot);
validateApiSlot(vm, elementSlot);
ASSERT(IS_LIST(vm->apiStack[listSlot]), "Slot must hold a list.");
ObjList* list = AS_LIST(vm->apiStack[listSlot]);
uint32_t usedIndex = wrenValidateIndex(list->elements.count, index);
ASSERT(usedIndex != UINT32_MAX, "Index out of bounds.");
list->elements.data[usedIndex] = vm->apiStack[elementSlot];
}
void wrenInsertInList(WrenVM* vm, int listSlot, int index, int elementSlot)
{
validateApiSlot(vm, listSlot);
validateApiSlot(vm, elementSlot);
ASSERT(IS_LIST(vm->apiStack[listSlot]), "Must insert into a list.");
ObjList* list = AS_LIST(vm->apiStack[listSlot]);
// Negative indices count from the end.
// We don't use wrenValidateIndex here because insert allows 1 past the end.
if (index < 0) index = list->elements.count + 1 + index;
ASSERT(index <= list->elements.count, "Index out of bounds.");
wrenListInsert(vm, list, vm->apiStack[elementSlot], index);
}
int wrenGetMapCount(WrenVM* vm, int slot)
{
validateApiSlot(vm, slot);
ASSERT(IS_MAP(vm->apiStack[slot]), "Slot must hold a map.");
ObjMap* map = AS_MAP(vm->apiStack[slot]);
return map->count;
}
bool wrenGetMapContainsKey(WrenVM* vm, int mapSlot, int keySlot)
{
validateApiSlot(vm, mapSlot);
validateApiSlot(vm, keySlot);
ASSERT(IS_MAP(vm->apiStack[mapSlot]), "Slot must hold a map.");
Value key = vm->apiStack[keySlot];
ASSERT(wrenMapIsValidKey(key), "Key must be a value type");
if (!validateKey(vm, key)) return false;
ObjMap* map = AS_MAP(vm->apiStack[mapSlot]);
Value value = wrenMapGet(map, key);
return !IS_UNDEFINED(value);
}
void wrenGetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot)
{
validateApiSlot(vm, mapSlot);
validateApiSlot(vm, keySlot);
validateApiSlot(vm, valueSlot);
ASSERT(IS_MAP(vm->apiStack[mapSlot]), "Slot must hold a map.");
ObjMap* map = AS_MAP(vm->apiStack[mapSlot]);
Value value = wrenMapGet(map, vm->apiStack[keySlot]);
if (IS_UNDEFINED(value)) {
value = NULL_VAL;
}
vm->apiStack[valueSlot] = value;
}
void wrenSetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot)
{
validateApiSlot(vm, mapSlot);
validateApiSlot(vm, keySlot);
validateApiSlot(vm, valueSlot);
ASSERT(IS_MAP(vm->apiStack[mapSlot]), "Must insert into a map.");
Value key = vm->apiStack[keySlot];
ASSERT(wrenMapIsValidKey(key), "Key must be a value type");
if (!validateKey(vm, key)) {
return;
}
Value value = vm->apiStack[valueSlot];
ObjMap* map = AS_MAP(vm->apiStack[mapSlot]);
wrenMapSet(vm, map, key, value);
}
void wrenRemoveMapValue(WrenVM* vm, int mapSlot, int keySlot,
int removedValueSlot)
{
validateApiSlot(vm, mapSlot);
validateApiSlot(vm, keySlot);
ASSERT(IS_MAP(vm->apiStack[mapSlot]), "Slot must hold a map.");
Value key = vm->apiStack[keySlot];
if (!validateKey(vm, key)) {
return;
}
ObjMap* map = AS_MAP(vm->apiStack[mapSlot]);
Value removed = wrenMapRemoveKey(vm, map, key);
setSlot(vm, removedValueSlot, removed);
}
void wrenGetVariable(WrenVM* vm, const char* module, const char* name,
int slot)
{
ASSERT(module != NULL, "Module cannot be NULL.");
ASSERT(name != NULL, "Variable name cannot be NULL.");
Value moduleName = wrenStringFormat(vm, "$", module);
wrenPushRoot(vm, AS_OBJ(moduleName));
ObjModule* moduleObj = getModule(vm, moduleName);
ASSERT(moduleObj != NULL, "Could not find module.");
wrenPopRoot(vm); // moduleName.
int variableSlot = wrenSymbolTableFind(&moduleObj->variableNames,
name, strlen(name));
ASSERT(variableSlot != -1, "Could not find variable.");
setSlot(vm, slot, moduleObj->variables.data[variableSlot]);
}
bool wrenHasVariable(WrenVM* vm, const char* module, const char* name)
{
ASSERT(module != NULL, "Module cannot be NULL.");
ASSERT(name != NULL, "Variable name cannot be NULL.");
Value moduleName = wrenStringFormat(vm, "$", module);
wrenPushRoot(vm, AS_OBJ(moduleName));
//We don't use wrenHasModule since we want to use the module object.
ObjModule* moduleObj = getModule(vm, moduleName);
ASSERT(moduleObj != NULL, "Could not find module.");
wrenPopRoot(vm); // moduleName.
int variableSlot = wrenSymbolTableFind(&moduleObj->variableNames,
name, strlen(name));
return variableSlot != -1;
}
bool wrenHasModule(WrenVM* vm, const char* module)
{
ASSERT(module != NULL, "Module cannot be NULL.");
Value moduleName = wrenStringFormat(vm, "$", module);
wrenPushRoot(vm, AS_OBJ(moduleName));
ObjModule* moduleObj = getModule(vm, moduleName);
wrenPopRoot(vm); // moduleName.
return moduleObj != NULL;
}
void wrenAbortFiber(WrenVM* vm, int slot)
{
validateApiSlot(vm, slot);
vm->fiber->error = vm->apiStack[slot];
}
void* wrenGetUserData(WrenVM* vm)
{
return vm->config.userData;
}
void wrenSetUserData(WrenVM* vm, void* userData)
{
vm->config.userData = userData;
}
// End file "wren_vm.c"
// Begin file "wren_opt_random.c"
#if WREN_OPT_RANDOM
#include <string.h>
#include <time.h>
// Begin file "wren_opt_random.wren.inc"
// Generated automatically from src/optional/wren_opt_random.wren. Do not edit.
static const char* randomModuleSource =
"foreign class Random {\n"
" construct new() {\n"
" seed_()\n"
" }\n"
"\n"
" construct new(seed) {\n"
" if (seed is Num) {\n"
" seed_(seed)\n"
" } else if (seed is Sequence) {\n"
" if (seed.isEmpty) Fiber.abort(\"Sequence cannot be empty.\")\n"
"\n"
" // TODO: Empty sequence.\n"
" var seeds = []\n"
" for (element in seed) {\n"
" if (!(element is Num)) Fiber.abort(\"Sequence elements must all be numbers.\")\n"
"\n"
" seeds.add(element)\n"
" if (seeds.count == 16) break\n"
" }\n"
"\n"
" // Cycle the values to fill in any missing slots.\n"
" var i = 0\n"
" while (seeds.count < 16) {\n"
" seeds.add(seeds[i])\n"
" i = i + 1\n"
" }\n"
"\n"
" seed_(\n"
" seeds[0], seeds[1], seeds[2], seeds[3],\n"
" seeds[4], seeds[5], seeds[6], seeds[7],\n"
" seeds[8], seeds[9], seeds[10], seeds[11],\n"
" seeds[12], seeds[13], seeds[14], seeds[15])\n"
" } else {\n"
" Fiber.abort(\"Seed must be a number or a sequence of numbers.\")\n"
" }\n"
" }\n"
"\n"
" foreign seed_()\n"
" foreign seed_(seed)\n"
" foreign seed_(n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12, n13, n14, n15, n16)\n"
"\n"
" foreign float()\n"
" float(end) { float() * end }\n"
" float(start, end) { float() * (end - start) + start }\n"
"\n"
" foreign int()\n"
" int(end) { (float() * end).floor }\n"
" int(start, end) { (float() * (end - start)).floor + start }\n"
"\n"
" sample(list) {\n"
" if (list.count == 0) Fiber.abort(\"Not enough elements to sample.\")\n"
" return list[int(list.count)]\n"
" }\n"
" sample(list, count) {\n"
" if (count > list.count) Fiber.abort(\"Not enough elements to sample.\")\n"
"\n"
" var result = []\n"
"\n"
" // The algorithm described in \"Programming pearls: a sample of brilliance\".\n"
" // Use a hash map for sample sizes less than 1/4 of the population size and\n"
" // an array of booleans for larger samples. This simple heuristic improves\n"
" // performance for large sample sizes as well as reduces memory usage.\n"
" if (count * 4 < list.count) {\n"
" var picked = {}\n"
" for (i in list.count - count...list.count) {\n"
" var index = int(i + 1)\n"
" if (picked.containsKey(index)) index = i\n"
" picked[index] = true\n"
" result.add(list[index])\n"
" }\n"
" } else {\n"
" var picked = List.filled(list.count, false)\n"
" for (i in list.count - count...list.count) {\n"
" var index = int(i + 1)\n"
" if (picked[index]) index = i\n"
" picked[index] = true\n"
" result.add(list[index])\n"
" }\n"
" }\n"
"\n"
" return result\n"
" }\n"
"\n"
" shuffle(list) {\n"
" if (list.isEmpty) return\n"
"\n"
" // Fisher-Yates shuffle.\n"
" for (i in 0...list.count - 1) {\n"
" var from = int(i, list.count)\n"
" var temp = list[from]\n"
" list[from] = list[i]\n"
" list[i] = temp\n"
" }\n"
" }\n"
"}\n";
// End file "wren_opt_random.wren.inc"
// Implements the well equidistributed long-period linear PRNG (WELL512a).
//
// https://en.wikipedia.org/wiki/Well_equidistributed_long-period_linear
typedef struct
{
uint32_t state[16];
uint32_t index;
} Well512;
// Code from: http://www.lomont.org/Math/Papers/2008/Lomont_PRNG_2008.pdf
static uint32_t advanceState(Well512* well)
{
uint32_t a, b, c, d;
a = well->state[well->index];
c = well->state[(well->index + 13) & 15];
b = a ^ c ^ (a << 16) ^ (c << 15);
c = well->state[(well->index + 9) & 15];
c ^= (c >> 11);
a = well->state[well->index] = b ^ c;
d = a ^ ((a << 5) & 0xda442d24U);
well->index = (well->index + 15) & 15;
a = well->state[well->index];
well->state[well->index] = a ^ b ^ d ^ (a << 2) ^ (b << 18) ^ (c << 28);
return well->state[well->index];
}
static void randomAllocate(WrenVM* vm)
{
Well512* well = (Well512*)wrenSetSlotNewForeign(vm, 0, 0, sizeof(Well512));
well->index = 0;
}
static void randomSeed0(WrenVM* vm)
{
Well512* well = (Well512*)wrenGetSlotForeign(vm, 0);
srand((uint32_t)time(NULL));
for (int i = 0; i < 16; i++)
{
well->state[i] = rand();
}
}
static void randomSeed1(WrenVM* vm)
{
Well512* well = (Well512*)wrenGetSlotForeign(vm, 0);
srand((uint32_t)wrenGetSlotDouble(vm, 1));
for (int i = 0; i < 16; i++)
{
well->state[i] = rand();
}
}
static void randomSeed16(WrenVM* vm)
{
Well512* well = (Well512*)wrenGetSlotForeign(vm, 0);
for (int i = 0; i < 16; i++)
{
well->state[i] = (uint32_t)wrenGetSlotDouble(vm, i + 1);
}
}
static void randomFloat(WrenVM* vm)
{
Well512* well = (Well512*)wrenGetSlotForeign(vm, 0);
// A double has 53 bits of precision in its mantissa, and we'd like to take
// full advantage of that, so we need 53 bits of random source data.
// First, start with 32 random bits, shifted to the left 21 bits.
double result = (double)advanceState(well) * (1 << 21);
// Then add another 21 random bits.
result += (double)(advanceState(well) & ((1 << 21) - 1));
// Now we have a number from 0 - (2^53). Divide be the range to get a double
// from 0 to 1.0 (half-inclusive).
result /= 9007199254740992.0;
wrenSetSlotDouble(vm, 0, result);
}
static void randomInt0(WrenVM* vm)
{
Well512* well = (Well512*)wrenGetSlotForeign(vm, 0);
wrenSetSlotDouble(vm, 0, (double)advanceState(well));
}
const char* wrenRandomSource()
{
return randomModuleSource;
}
WrenForeignClassMethods wrenRandomBindForeignClass(WrenVM* vm,
const char* module,
const char* className)
{
ASSERT(strcmp(className, "Random") == 0, "Should be in Random class.");
WrenForeignClassMethods methods;
methods.allocate = randomAllocate;
methods.finalize = NULL;
return methods;
}
WrenForeignMethodFn wrenRandomBindForeignMethod(WrenVM* vm,
const char* className,
bool isStatic,
const char* signature)
{
ASSERT(strcmp(className, "Random") == 0, "Should be in Random class.");
if (strcmp(signature, "<allocate>") == 0) return randomAllocate;
if (strcmp(signature, "seed_()") == 0) return randomSeed0;
if (strcmp(signature, "seed_(_)") == 0) return randomSeed1;
if (strcmp(signature, "seed_(_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_)") == 0)
{
return randomSeed16;
}
if (strcmp(signature, "float()") == 0) return randomFloat;
if (strcmp(signature, "int()") == 0) return randomInt0;
ASSERT(false, "Unknown method.");
return NULL;
}
#endif
// End file "wren_opt_random.c"
// Begin file "wren_opt_meta.c"
#if WREN_OPT_META
#include <string.h>
// Begin file "wren_opt_meta.wren.inc"
// Generated automatically from src/optional/wren_opt_meta.wren. Do not edit.
static const char* metaModuleSource =
"class Meta {\n"
" static getModuleVariables(module) {\n"
" if (!(module is String)) Fiber.abort(\"Module name must be a string.\")\n"
" var result = getModuleVariables_(module)\n"
" if (result != null) return result\n"
"\n"
" Fiber.abort(\"Could not find a module named '%(module)'.\")\n"
" }\n"
"\n"
" static eval(source) {\n"
" if (!(source is String)) Fiber.abort(\"Source code must be a string.\")\n"
"\n"
" var closure = compile_(source, false, false)\n"
" // TODO: Include compile errors.\n"
" if (closure == null) Fiber.abort(\"Could not compile source code.\")\n"
"\n"
" closure.call()\n"
" }\n"
"\n"
" static compileExpression(source) {\n"
" if (!(source is String)) Fiber.abort(\"Source code must be a string.\")\n"
" return compile_(source, true, true)\n"
" }\n"
"\n"
" static compile(source) {\n"
" if (!(source is String)) Fiber.abort(\"Source code must be a string.\")\n"
" return compile_(source, false, true)\n"
" }\n"
"\n"
" foreign static compile_(source, isExpression, printErrors)\n"
" foreign static getModuleVariables_(module)\n"
"}\n";
// End file "wren_opt_meta.wren.inc"
void metaCompile(WrenVM* vm)
{
const char* source = wrenGetSlotString(vm, 1);
bool isExpression = wrenGetSlotBool(vm, 2);
bool printErrors = wrenGetSlotBool(vm, 3);
// TODO: Allow passing in module?
// Look up the module surrounding the callsite. This is brittle. The -2 walks
// up the callstack assuming that the meta module has one level of
// indirection before hitting the user's code. Any change to meta may require
// this constant to be tweaked.
ObjFiber* currentFiber = vm->fiber;
ObjFn* fn = currentFiber->frames[currentFiber->numFrames - 2].closure->fn;
ObjString* module = fn->module->name;
ObjClosure* closure = wrenCompileSource(vm, module->value, source,
isExpression, printErrors);
// Return the result. We can't use the public API for this since we have a
// bare ObjClosure*.
if (closure == NULL)
{
vm->apiStack[0] = NULL_VAL;
}
else
{
vm->apiStack[0] = OBJ_VAL(closure);
}
}
void metaGetModuleVariables(WrenVM* vm)
{
wrenEnsureSlots(vm, 3);
Value moduleValue = wrenMapGet(vm->modules, vm->apiStack[1]);
if (IS_UNDEFINED(moduleValue))
{
vm->apiStack[0] = NULL_VAL;
return;
}
ObjModule* module = AS_MODULE(moduleValue);
ObjList* names = wrenNewList(vm, module->variableNames.count);
vm->apiStack[0] = OBJ_VAL(names);
// Initialize the elements to null in case a collection happens when we
// allocate the strings below.
for (int i = 0; i < names->elements.count; i++)
{
names->elements.data[i] = NULL_VAL;
}
for (int i = 0; i < names->elements.count; i++)
{
names->elements.data[i] = OBJ_VAL(module->variableNames.data[i]);
}
}
const char* wrenMetaSource()
{
return metaModuleSource;
}
WrenForeignMethodFn wrenMetaBindForeignMethod(WrenVM* vm,
const char* className,
bool isStatic,
const char* signature)
{
// There is only one foreign method in the meta module.
ASSERT(strcmp(className, "Meta") == 0, "Should be in Meta class.");
ASSERT(isStatic, "Should be static.");
if (strcmp(signature, "compile_(_,_,_)") == 0)
{
return metaCompile;
}
if (strcmp(signature, "getModuleVariables_(_)") == 0)
{
return metaGetModuleVariables;
}
ASSERT(false, "Unknown method.");
return NULL;
}
#endif
// End file "wren_opt_meta.c"