diff --git a/deps/wren/include/wren.h b/deps/wren/include/wren.h index 45d5d3b9..7845911c 100644 --- a/deps/wren/include/wren.h +++ b/deps/wren/include/wren.h @@ -7,18 +7,26 @@ // The Wren semantic version number components. #define WREN_VERSION_MAJOR 0 -#define WREN_VERSION_MINOR 3 +#define WREN_VERSION_MINOR 4 #define WREN_VERSION_PATCH 0 // A human-friendly string representation of the version. -#define WREN_VERSION_STRING "0.3.0" +#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 + \ +#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 @@ -47,7 +55,7 @@ typedef struct WrenHandle WrenHandle; // // - 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); +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); @@ -65,8 +73,25 @@ typedef void (*WrenFinalizerFn)(void* data); 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 char* (*WrenLoadModuleFn)(WrenVM* vm, const char* name); +typedef WrenLoadModuleResult (*WrenLoadModuleFn)(WrenVM* vm, const char* name); // Returns a pointer to a foreign method on [className] in [module] with // [signature]. @@ -163,9 +188,9 @@ typedef struct // 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 VM should return the soure code for that - // module. Memory for the source should be allocated using [reallocateFn] and - // Wren will take ownership over it. + // 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 @@ -263,6 +288,7 @@ typedef enum WREN_TYPE_NUM, WREN_TYPE_FOREIGN, WREN_TYPE_LIST, + WREN_TYPE_MAP, WREN_TYPE_NULL, WREN_TYPE_STRING, @@ -270,27 +296,32 @@ typedef enum 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. -void wrenInitConfiguration(WrenConfiguration* configuration); +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. -WrenVM* wrenNewVM(WrenConfiguration* configuration); +WREN_API WrenVM* wrenNewVM(WrenConfiguration* configuration); // Disposes of all resources is use by [vm], which was previously created by a // call to [wrenNewVM]. -void wrenFreeVM(WrenVM* vm); +WREN_API void wrenFreeVM(WrenVM* vm); // Immediately run the garbage collector to free unused memory. -void wrenCollectGarbage(WrenVM* vm); +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]. -WrenInterpretResult wrenInterpret(WrenVM* vm, const char* 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 @@ -301,7 +332,7 @@ WrenInterpretResult wrenInterpret(WrenVM* vm, const char* module, // // When you are done with this handle, it must be released using // [wrenReleaseHandle]. -WrenHandle* wrenMakeCallHandle(WrenVM* vm, const char* signature); +WREN_API WrenHandle* wrenMakeCallHandle(WrenVM* vm, const char* signature); // Calls [method], using the receiver and arguments previously set up on the // stack. @@ -313,11 +344,11 @@ WrenHandle* wrenMakeCallHandle(WrenVM* vm, const char* signature); // signature. // // After this returns, you can access the return value from slot 0 on the stack. -WrenInterpretResult wrenCall(WrenVM* vm, WrenHandle* method); +WREN_API WrenInterpretResult wrenCall(WrenVM* vm, WrenHandle* method); // Releases the reference stored in [handle]. After calling this, [handle] can // no longer be used. -void wrenReleaseHandle(WrenVM* vm, WrenHandle* handle); +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 @@ -357,7 +388,7 @@ void wrenReleaseHandle(WrenVM* vm, WrenHandle* handle); // return, you get a very fast FFI. // Returns the number of slots available to the current foreign method. -int wrenGetSlotCount(WrenVM* vm); +WREN_API int wrenGetSlotCount(WrenVM* vm); // Ensures that the foreign method stack has at least [numSlots] available for // use, growing the stack if needed. @@ -365,15 +396,15 @@ int wrenGetSlotCount(WrenVM* vm); // Does not shrink the stack if it has more than enough slots. // // It is an error to call this from a finalizer. -void wrenEnsureSlots(WrenVM* vm, int numSlots); +WREN_API void wrenEnsureSlots(WrenVM* vm, int numSlots); // Gets the type of the object in [slot]. -WrenType wrenGetSlotType(WrenVM* vm, int 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. -bool wrenGetSlotBool(WrenVM* vm, int slot); +WREN_API bool wrenGetSlotBool(WrenVM* vm, int slot); // Reads a byte array from [slot]. // @@ -385,19 +416,19 @@ bool wrenGetSlotBool(WrenVM* vm, int slot); // number of bytes in the array. // // It is an error to call this if the slot does not contain a string. -const char* wrenGetSlotBytes(WrenVM* vm, int slot, int* length); +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. -double wrenGetSlotDouble(WrenVM* vm, int slot); +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. -void* wrenGetSlotForeign(WrenVM* vm, int slot); +WREN_API void* wrenGetSlotForeign(WrenVM* vm, int slot); // Reads a string from [slot]. // @@ -406,25 +437,25 @@ void* wrenGetSlotForeign(WrenVM* vm, int slot); // function returns, since the garbage collector may reclaim it. // // It is an error to call this if the slot does not contain a string. -const char* wrenGetSlotString(WrenVM* vm, int slot); +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()]. -WrenHandle* wrenGetSlotHandle(WrenVM* vm, int slot); +WREN_API WrenHandle* wrenGetSlotHandle(WrenVM* vm, int slot); // Stores the boolean [value] in [slot]. -void wrenSetSlotBool(WrenVM* vm, int slot, bool value); +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. -void wrenSetSlotBytes(WrenVM* vm, int slot, const char* bytes, size_t length); +WREN_API void wrenSetSlotBytes(WrenVM* vm, int slot, const char* bytes, size_t length); // Stores the numeric [value] in [slot]. -void wrenSetSlotDouble(WrenVM* vm, int slot, double value); +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]. @@ -435,13 +466,16 @@ void wrenSetSlotDouble(WrenVM* vm, int slot, double value); // and then the constructor will be invoked when the allocator returns. // // Returns a pointer to the foreign object's data. -void* wrenSetSlotNewForeign(WrenVM* vm, int slot, int classSlot, size_t size); +WREN_API void* wrenSetSlotNewForeign(WrenVM* vm, int slot, int classSlot, size_t size); // Stores a new empty list in [slot]. -void wrenSetSlotNewList(WrenVM* vm, int 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]. -void wrenSetSlotNull(WrenVM* vm, int slot); +WREN_API void wrenSetSlotNull(WrenVM* vm, int slot); // Stores the string [text] in [slot]. // @@ -449,40 +483,72 @@ void wrenSetSlotNull(WrenVM* vm, int slot); // 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. -void wrenSetSlotString(WrenVM* vm, int slot, const char* text); +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. -void wrenSetSlotHandle(WrenVM* vm, int slot, WrenHandle* handle); +WREN_API void wrenSetSlotHandle(WrenVM* vm, int slot, WrenHandle* handle); // Returns the number of elements in the list stored in [slot]. -int wrenGetListCount(WrenVM* vm, int slot); +WREN_API int wrenGetListCount(WrenVM* vm, int slot); // Reads element [index] from the list in [listSlot] and stores it in // [elementSlot]. -void wrenGetListElement(WrenVM* vm, int listSlot, int index, int 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. -void wrenInsertInList(WrenVM* vm, int listSlot, int index, int elementSlot); +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]. -void wrenGetVariable(WrenVM* vm, const char* module, const char* name, +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. -void wrenAbortFiber(WrenVM* vm, int slot); +WREN_API void wrenAbortFiber(WrenVM* vm, int slot); // Returns the user data associated with the WrenVM. -void* wrenGetUserData(WrenVM* vm); +WREN_API void* wrenGetUserData(WrenVM* vm); // Sets user data associated with the WrenVM. -void wrenSetUserData(WrenVM* vm, void* userData); +WREN_API void wrenSetUserData(WrenVM* vm, void* userData); #endif diff --git a/deps/wren/include/wren.hpp b/deps/wren/include/wren.hpp index 6f870282..65b94daa 100644 --- a/deps/wren/include/wren.hpp +++ b/deps/wren/include/wren.hpp @@ -1,11 +1,11 @@ -#ifndef wren_hpp -#define wren_hpp - -// This is a convenience header for users that want to compile Wren as C and -// link to it from a C++ application. - -extern "C" { - #include "wren.h" -} - -#endif +#ifndef wren_hpp +#define wren_hpp + +// This is a convenience header for users that want to compile Wren as C and +// link to it from a C++ application. + +extern "C" { + #include "wren.h" +} + +#endif diff --git a/deps/wren/src/optional/wren_opt_meta.c b/deps/wren/src/optional/wren_opt_meta.c index 879a1691..d161cf28 100644 --- a/deps/wren/src/optional/wren_opt_meta.c +++ b/deps/wren/src/optional/wren_opt_meta.c @@ -1,96 +1,96 @@ -#include "wren_opt_meta.h" - -#if WREN_OPT_META - -#include - -#include "wren_vm.h" -#include "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 +#include "wren_opt_meta.h" + +#if WREN_OPT_META + +#include + +#include "wren_vm.h" +#include "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 diff --git a/deps/wren/src/optional/wren_opt_meta.h b/deps/wren/src/optional/wren_opt_meta.h index c697376a..afe14afb 100644 --- a/deps/wren/src/optional/wren_opt_meta.h +++ b/deps/wren/src/optional/wren_opt_meta.h @@ -1,18 +1,18 @@ -#ifndef wren_opt_meta_h -#define wren_opt_meta_h - -#include "wren_common.h" -#include "wren.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 +#ifndef wren_opt_meta_h +#define wren_opt_meta_h + +#include "wren_common.h" +#include "wren.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 diff --git a/deps/wren/src/optional/wren_opt_meta.wren b/deps/wren/src/optional/wren_opt_meta.wren index 09aa2899..d6fdecf4 100644 --- a/deps/wren/src/optional/wren_opt_meta.wren +++ b/deps/wren/src/optional/wren_opt_meta.wren @@ -1,32 +1,32 @@ -class Meta { - static getModuleVariables(module) { - if (!(module is String)) Fiber.abort("Module name must be a string.") - var result = getModuleVariables_(module) - if (result != null) return result - - Fiber.abort("Could not find a module named '%(module)'.") - } - - static eval(source) { - if (!(source is String)) Fiber.abort("Source code must be a string.") - - var closure = compile_(source, false, false) - // TODO: Include compile errors. - if (closure == null) Fiber.abort("Could not compile source code.") - - closure.call() - } - - static compileExpression(source) { - if (!(source is String)) Fiber.abort("Source code must be a string.") - return compile_(source, true, true) - } - - static compile(source) { - if (!(source is String)) Fiber.abort("Source code must be a string.") - return compile_(source, false, true) - } - - foreign static compile_(source, isExpression, printErrors) - foreign static getModuleVariables_(module) -} +class Meta { + static getModuleVariables(module) { + if (!(module is String)) Fiber.abort("Module name must be a string.") + var result = getModuleVariables_(module) + if (result != null) return result + + Fiber.abort("Could not find a module named '%(module)'.") + } + + static eval(source) { + if (!(source is String)) Fiber.abort("Source code must be a string.") + + var closure = compile_(source, false, false) + // TODO: Include compile errors. + if (closure == null) Fiber.abort("Could not compile source code.") + + closure.call() + } + + static compileExpression(source) { + if (!(source is String)) Fiber.abort("Source code must be a string.") + return compile_(source, true, true) + } + + static compile(source) { + if (!(source is String)) Fiber.abort("Source code must be a string.") + return compile_(source, false, true) + } + + foreign static compile_(source, isExpression, printErrors) + foreign static getModuleVariables_(module) +} diff --git a/deps/wren/src/optional/wren_opt_meta.wren.inc b/deps/wren/src/optional/wren_opt_meta.wren.inc index 0bb60bd8..a372e10b 100644 --- a/deps/wren/src/optional/wren_opt_meta.wren.inc +++ b/deps/wren/src/optional/wren_opt_meta.wren.inc @@ -1,34 +1,34 @@ -// 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"; +// 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"; diff --git a/deps/wren/src/optional/wren_opt_random.c b/deps/wren/src/optional/wren_opt_random.c index 0e72870d..de634458 100644 --- a/deps/wren/src/optional/wren_opt_random.c +++ b/deps/wren/src/optional/wren_opt_random.c @@ -1,144 +1,144 @@ -#include "wren_opt_random.h" - -#if WREN_OPT_RANDOM - -#include -#include - -#include "wren.h" -#include "wren_vm.h" - -#include "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, "") == 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 +#include "wren_opt_random.h" + +#if WREN_OPT_RANDOM + +#include +#include + +#include "wren.h" +#include "wren_vm.h" + +#include "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, "") == 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 diff --git a/deps/wren/src/optional/wren_opt_random.h b/deps/wren/src/optional/wren_opt_random.h index e9433507..a128e864 100644 --- a/deps/wren/src/optional/wren_opt_random.h +++ b/deps/wren/src/optional/wren_opt_random.h @@ -1,20 +1,20 @@ -#ifndef wren_opt_random_h -#define wren_opt_random_h - -#include "wren_common.h" -#include "wren.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 +#ifndef wren_opt_random_h +#define wren_opt_random_h + +#include "wren_common.h" +#include "wren.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 diff --git a/deps/wren/src/optional/wren_opt_random.wren b/deps/wren/src/optional/wren_opt_random.wren index b94f53b3..a7813b9f 100644 --- a/deps/wren/src/optional/wren_opt_random.wren +++ b/deps/wren/src/optional/wren_opt_random.wren @@ -47,65 +47,38 @@ foreign class Random { int(end) { (float() * end).floor } int(start, end) { (float() * (end - start)).floor + start } - sample(list) { sample(list, 1)[0] } + sample(list) { + if (list.count == 0) Fiber.abort("Not enough elements to sample.") + return list[int(list.count)] + } sample(list, count) { if (count > list.count) Fiber.abort("Not enough elements to sample.") - // There are (at least) two simple algorithms for choosing a number of - // samples from a list without replacement -- where we don't pick the same - // element more than once. - // - // The first is faster when the number of samples is small relative to the - // size of the collection. In many cases, it avoids scanning the entire - // list. In the common case of just wanting one sample, it's a single - // random index lookup. - // - // However, its performance degrades badly as the sample size increases. - // Vitter's algorithm always scans the entire list, but it's also always - // O(n). - // - // The cutoff point between the two follows a quadratic curve on the same - // size. Based on some empirical testing, scaling that by 5 seems to fit - // pretty closely and chooses the fastest one for the given sample and - // collection size. - if (count * count * 5 < list.count) { - // Pick random elements and retry if you hit a previously chosen one. - var picked = {} - var result = [] - for (i in 0...count) { - // Find an index that we haven't already selected. - var index - while (true) { - index = int(list.count) - if (!picked.containsKey(index)) break - } + var result = [] + // The algorithm described in "Programming pearls: a sample of brilliance". + // Use a hash map for sample sizes less than 1/4 of the population size and + // an array of booleans for larger samples. This simple heuristic improves + // performance for large sample sizes as well as reduces memory usage. + if (count * 4 < list.count) { + var picked = {} + for (i in list.count - count...list.count) { + var index = int(i + 1) + if (picked.containsKey(index)) index = i picked[index] = true result.add(list[index]) } - - return result } else { - // Jeffrey Vitter's Algorithm R. - - // Fill the reservoir with the first elements in the list. - var result = list[0...count] - - // We want to ensure the results are always in random order, so shuffle - // them. In cases where the sample size is the entire collection, this - // devolves to running Fisher-Yates on a copy of the list. - shuffle(result) - - // Now walk the rest of the list. For each element, randomly consider - // replacing one of the reservoir elements with it. The probability here - // works out such that it does this uniformly. - for (i in count...list.count) { - var slot = int(0, i + 1) - if (slot < count) result[slot] = list[i] + var picked = List.filled(list.count, false) + for (i in list.count - count...list.count) { + var index = int(i + 1) + if (picked[index]) index = i + picked[index] = true + result.add(list[index]) } - - return result } + + return result } shuffle(list) { diff --git a/deps/wren/src/optional/wren_opt_random.wren.inc b/deps/wren/src/optional/wren_opt_random.wren.inc index 4ccbc5e9..164f9d58 100644 --- a/deps/wren/src/optional/wren_opt_random.wren.inc +++ b/deps/wren/src/optional/wren_opt_random.wren.inc @@ -49,65 +49,38 @@ static const char* randomModuleSource = " int(end) { (float() * end).floor }\n" " int(start, end) { (float() * (end - start)).floor + start }\n" "\n" -" sample(list) { sample(list, 1)[0] }\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" -" // There are (at least) two simple algorithms for choosing a number of\n" -" // samples from a list without replacement -- where we don't pick the same\n" -" // element more than once.\n" -" //\n" -" // The first is faster when the number of samples is small relative to the\n" -" // size of the collection. In many cases, it avoids scanning the entire\n" -" // list. In the common case of just wanting one sample, it's a single\n" -" // random index lookup.\n" -" //\n" -" // However, its performance degrades badly as the sample size increases.\n" -" // Vitter's algorithm always scans the entire list, but it's also always\n" -" // O(n).\n" -" //\n" -" // The cutoff point between the two follows a quadratic curve on the same\n" -" // size. Based on some empirical testing, scaling that by 5 seems to fit\n" -" // pretty closely and chooses the fastest one for the given sample and\n" -" // collection size.\n" -" if (count * count * 5 < list.count) {\n" -" // Pick random elements and retry if you hit a previously chosen one.\n" -" var picked = {}\n" -" var result = []\n" -" for (i in 0...count) {\n" -" // Find an index that we haven't already selected.\n" -" var index\n" -" while (true) {\n" -" index = int(list.count)\n" -" if (!picked.containsKey(index)) break\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" -"\n" -" return result\n" " } else {\n" -" // Jeffrey Vitter's Algorithm R.\n" -"\n" -" // Fill the reservoir with the first elements in the list.\n" -" var result = list[0...count]\n" -"\n" -" // We want to ensure the results are always in random order, so shuffle\n" -" // them. In cases where the sample size is the entire collection, this\n" -" // devolves to running Fisher-Yates on a copy of the list.\n" -" shuffle(result)\n" -"\n" -" // Now walk the rest of the list. For each element, randomly consider\n" -" // replacing one of the reservoir elements with it. The probability here\n" -" // works out such that it does this uniformly.\n" -" for (i in count...list.count) {\n" -" var slot = int(0, i + 1)\n" -" if (slot < count) result[slot] = list[i]\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" -" return result\n" " }\n" +"\n" +" return result\n" " }\n" "\n" " shuffle(list) {\n" diff --git a/deps/wren/src/vm/wren_common.h b/deps/wren/src/vm/wren_common.h index c3b5682c..4ee9e7e2 100644 --- a/deps/wren/src/vm/wren_common.h +++ b/deps/wren/src/vm/wren_common.h @@ -33,10 +33,10 @@ // 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 - #ifdef _MSC_VER + #if defined(_MSC_VER) && !defined(__clang__) // No computed gotos in Visual Studio. #define WREN_COMPUTED_GOTO 0 #else @@ -114,17 +114,17 @@ #define MAX_FIELDS 255 // Use the VM's allocator to allocate an object of [type]. -#define ALLOCATE(vm, 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))) +#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) \ +#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]. @@ -156,17 +156,16 @@ #include - #define ASSERT(condition, message) \ - do \ - { \ - if (!(condition)) \ - { \ - fprintf(stderr, "[%s:%d] Assert failed in %s(): %s\n", \ - __FILE__, __LINE__, __func__, message); \ - abort(); \ - } \ - } \ - while(0) + #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. @@ -175,18 +174,17 @@ // 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 (0) + #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 (0) + #define ASSERT(condition, message) do { } while (false) // Tell the compiler that this part of the code will never be reached. #if defined( _MSC_VER ) diff --git a/deps/wren/src/vm/wren_compiler.c b/deps/wren/src/vm/wren_compiler.c index f9cdb52d..4ab695f8 100644 --- a/deps/wren/src/vm/wren_compiler.c +++ b/deps/wren/src/vm/wren_compiler.c @@ -1,3590 +1,4130 @@ -#include -#include -#include -#include - -#include "wren_common.h" -#include "wren_compiler.h" -#include "wren_vm.h" - -#if WREN_DEBUG_DUMP_COMPILED_CODE - #include "wren_debug.h" -#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_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_CLASS, - TOKEN_CONSTRUCT, - TOKEN_ELSE, - TOKEN_FALSE, - TOKEN_FOR, - TOKEN_FOREIGN, - TOKEN_IF, - TOKEN_IMPORT, - 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 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; - - // If subsequent newline tokens should be discarded. - bool skipNewlines; - - // 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; - - // 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; - - ObjMap* constants; -}; - -// 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; - -// The stack effect of each opcode. The index in the array is the opcode, and -// the value is the stack effect of that instruction. -static const int stackEffects[] = { - #define OPCODE(_, effect) effect, - #include "wren_opcodes.h" - #undef OPCODE -}; - -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 : ""; - - 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; - - // 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; - - 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->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}, - {"class", 5, TOKEN_CLASS}, - {"construct", 9, TOKEN_CONSTRUCT}, - {"else", 4, TOKEN_ELSE}, - {"false", 5, TOKEN_FALSE}, - {"for", 3, TOKEN_FOR}, - {"foreign", 7, TOKEN_FOREIGN}, - {"if", 2, TOKEN_IF}, - {"import", 6, TOKEN_IMPORT}, - {"in", 2, TOKEN_IN}, - {"is", 2, TOKEN_IS}, - {"null", 4, TOKEN_NULL}, - {"return", 6, TOKEN_RETURN}, - {"static", 6, TOKEN_STATIC}, - {"super", 5, TOKEN_SUPER}, - {"this", 4, TOKEN_THIS}, - {"true", 4, TOKEN_TRUE}, - {"var", 3, TOKEN_VAR}, - {"while", 5, TOKEN_WHILE}, - {NULL, 0, TOKEN_EOF} // Sentinel to mark the end of the array. -}; - -// 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->current.type = type; - parser->current.start = parser->tokenStart; - parser->current.length = (int)(parser->currentChar - parser->tokenStart); - parser->current.line = parser->currentLine; - - // Make line tokens appear on the line containing the "\n". - if (type == TOKEN_LINE) parser->current.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->current.value = NUM_VAL((double)strtoll(parser->tokenStart, NULL, 16)); - } - else - { - parser->current.value = NUM_VAL(strtod(parser->tokenStart, NULL)); - } - - if (errno == ERANGE) - { - lexError(parser, "Number literal was too large (%d).", sizeof(long int)); - parser->current.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 negative exponent. - 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) -{ - while (isName(peekChar(parser)) || isDigit(peekChar(parser))) - { - nextChar(parser); - } - - // 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; - } - } - - 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); - } -} - -// 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 == '\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 '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->current.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.current]. -static void nextToken(Parser* parser) -{ - parser->previous = parser->current; - - // If we are out of tokens, don't try to tokenize any more. We *do* still - // copy the TOKEN_EOF to previous so that code that expects it to be consumed - // will still work. - if (parser->current.type == TOKEN_EOF) return; - - 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 '^': 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 '"': readString(parser); return; - case '_': - readName(parser, - peekChar(parser) == '_' ? TOKEN_STATIC_FIELD : TOKEN_FIELD); - return; - - case '0': - if (peekChar(parser) == 'x') - { - readHexNumber(parser); - return; - } - - readNumber(parser); - return; - - default: - if (parser->currentLine == 1 && c == '#' && peekChar(parser) == '!') - { - // Ignore shebang on the first line. - skipLineComment(parser); - break; - } - if (isName(c)) - { - readName(parser, TOKEN_NAME); - } - 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->current.type = TOKEN_ERROR; - parser->current.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; -} - -// 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); -} - -// 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. -// -// It [isInitializer] is `true`, this is the body of a constructor initializer. -// In that case, this adds the code to ensure it returns `this`. -static void finishBody(Compiler* compiler, bool isInitializer) -{ - bool isExpressionBody = finishBlock(compiler); - - if (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 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, false); - - // 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); - } -} - -// 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 = 255; - - 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); - } -} - -// 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); -} - -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."); - - 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 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_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_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_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 arguments to the instruction at [ip] in [fn]'s -// bytecode. -static int getNumArguments(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: - 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: - 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: - case CODE_IMPORT_VARIABLE: - 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 + getNumArguments(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_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) - { - // Implicitly return null if there is no value. - emitOp(compiler, CODE_NULL); - } - else - { - 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; -} - -// 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) -{ - // 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); - - 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); - - // 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, signature.type == SIG_INITIALIZER); - 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; - - // 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."); - } - - // 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); - int slot = declareNamedVariable(compiler); - - // Define a string constant for the variable name. - int variableConstant = addConstant(compiler, - wrenNewStringLength(compiler->parser->vm, - compiler->parser->previous.start, - compiler->parser->previous.length)); - - // Load the variable from the other module. - emitShortArg(compiler, CODE_IMPORT_VARIABLE, variableConstant); - - // 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 (match(compiler, TOKEN_CLASS)) - { - classDefinition(compiler, false); - } - else if (match(compiler, TOKEN_FOREIGN)) - { - consume(compiler, TOKEN_CLASS, "Expect 'class' after 'foreign'."); - classDefinition(compiler, true); - } - else 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 - // advance() is called below. - parser.current.type = TOKEN_ERROR; - parser.current.start = source; - parser.current.length = 0; - parser.current.line = 0; - parser.current.value = UNDEFINED_VAL; - - // Ignore leading newlines. - parser.skipNewlines = true; - parser.printErrors = printErrors; - parser.hasError = false; - - // Read the first token. - 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 + getNumArguments(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); - - // 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); - - if (compiler->enclosingClass != NULL) - { - wrenBlackenSymbolTable(vm, &compiler->enclosingClass->fields); - } - - compiler = compiler->parent; - } - while (compiler != NULL); -} +#include +#include +#include +#include + +#include "wren_common.h" +#include "wren_compiler.h" +#include "wren_vm.h" + +#if WREN_DEBUG_DUMP_COMPILED_CODE + #include "wren_debug.h" +#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, + #include "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 : ""; + + 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; + + 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 == '\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 == '\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); +} diff --git a/deps/wren/src/vm/wren_compiler.h b/deps/wren/src/vm/wren_compiler.h index c9a9667b..1a7eb46e 100644 --- a/deps/wren/src/vm/wren_compiler.h +++ b/deps/wren/src/vm/wren_compiler.h @@ -1,57 +1,57 @@ -#ifndef wren_compiler_h -#define wren_compiler_h - -#include "wren.h" -#include "wren_value.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 +#ifndef wren_compiler_h +#define wren_compiler_h + +#include "wren.h" +#include "wren_value.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 diff --git a/deps/wren/src/vm/wren_core.c b/deps/wren/src/vm/wren_core.c index c1d5fd20..d0a121f8 100644 --- a/deps/wren/src/vm/wren_core.c +++ b/deps/wren/src/vm/wren_core.c @@ -1,1393 +1,1487 @@ -#include -#include -#include -#include -#include -#include - -#include "wren_common.h" -#include "wren_core.h" -#include "wren_primitive.h" -#include "wren_value.h" - -#include "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(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_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) -{ - // We only care about missing arguments, not extras. - if (AS_CLOSURE(args[0])->fn->arity > numArgs) - { - vm->fiber->error = CONST_STRING(vm, "Function expects more arguments."); - return; - } - - // +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, "")); -} - -// 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_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); -} - -DEF_PRIMITIVE(num_pi) -{ - RETURN_NUM(3.14159265358979323846); -} - -// 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(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_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) -{ - RETURN_NUM(atan2(AS_NUM(args[0]), AS_NUM(args[1]))); -} - -DEF_PRIMITIVE(num_pow) -{ - RETURN_NUM(pow(AS_NUM(args[0]), AS_NUM(args[1]))); -} - -DEF_PRIMITIVE(num_fraction) -{ - double dummy; - RETURN_NUM(modf(AS_NUM(args[0]) , &dummy)); -} - -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_largest) -{ - RETURN_NUM(DBL_MAX); -} - -DEF_PRIMITIVE(num_smallest) -{ - RETURN_NUM(DBL_MIN); -} - -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); - - // 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); - - vm->fnClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Fn")); - PRIMITIVE(vm->fnClass->obj.classObj, "new(_)", fn_new); - - PRIMITIVE(vm->fnClass, "arity", fn_arity); - PRIMITIVE(vm->fnClass, "call()", fn_call0); - PRIMITIVE(vm->fnClass, "call(_)", fn_call1); - PRIMITIVE(vm->fnClass, "call(_,_)", fn_call2); - PRIMITIVE(vm->fnClass, "call(_,_,_)", fn_call3); - PRIMITIVE(vm->fnClass, "call(_,_,_,_)", fn_call4); - PRIMITIVE(vm->fnClass, "call(_,_,_,_,_)", fn_call5); - PRIMITIVE(vm->fnClass, "call(_,_,_,_,_,_)", fn_call6); - PRIMITIVE(vm->fnClass, "call(_,_,_,_,_,_,_)", fn_call7); - PRIMITIVE(vm->fnClass, "call(_,_,_,_,_,_,_,_)", fn_call8); - PRIMITIVE(vm->fnClass, "call(_,_,_,_,_,_,_,_,_)", fn_call9); - PRIMITIVE(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_)", fn_call10); - PRIMITIVE(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_)", fn_call11); - PRIMITIVE(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_)", fn_call12); - PRIMITIVE(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_,_)", fn_call13); - PRIMITIVE(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_,_,_)", fn_call14); - PRIMITIVE(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_,_,_,_)", fn_call15); - PRIMITIVE(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, "pi", num_pi); - PRIMITIVE(vm->numClass->obj.classObj, "largest", num_largest); - PRIMITIVE(vm->numClass->obj.classObj, "smallest", num_smallest); - 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, "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, "sin", num_sin); - PRIMITIVE(vm->numClass, "sqrt", num_sqrt); - PRIMITIVE(vm->numClass, "tan", num_tan); - PRIMITIVE(vm->numClass, "log", num_log); - 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); - - 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; - } -} +#include +#include +#include +#include +#include +#include + +#include "wren_common.h" +#include "wren_core.h" +#include "wren_math.h" +#include "wren_primitive.h" +#include "wren_value.h" + +#include "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, "")); +} + +// 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; + } +} diff --git a/deps/wren/src/vm/wren_core.h b/deps/wren/src/vm/wren_core.h index 0c462b95..bbba86cf 100644 --- a/deps/wren/src/vm/wren_core.h +++ b/deps/wren/src/vm/wren_core.h @@ -1,23 +1,23 @@ -#ifndef wren_core_h -#define wren_core_h - -#include "wren_vm.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, `IO.write` should call `toString` on its argument, -// including user-defined `toString` methods on user-defined classes. - -void wrenInitializeCore(WrenVM* vm); - -#endif +#ifndef wren_core_h +#define wren_core_h + +#include "wren_vm.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, `IO.write` should call `toString` on its argument, +// including user-defined `toString` methods on user-defined classes. + +void wrenInitializeCore(WrenVM* vm); + +#endif diff --git a/deps/wren/src/vm/wren_core.wren b/deps/wren/src/vm/wren_core.wren index 8c1f44e9..e1192b8d 100644 --- a/deps/wren/src/vm/wren_core.wren +++ b/deps/wren/src/vm/wren_core.wren @@ -1,438 +1,483 @@ -class Bool {} -class Fiber {} -class Fn {} -class Null {} -class Num {} - -class Sequence { - all(f) { - var result = true - for (element in this) { - result = f.call(element) - if (!result) return result - } - return result - } - - any(f) { - var result = false - for (element in this) { - result = f.call(element) - if (result) return result - } - return result - } - - contains(element) { - for (item in this) { - if (element == item) return true - } - return false - } - - count { - var result = 0 - for (element in this) { - result = result + 1 - } - return result - } - - count(f) { - var result = 0 - for (element in this) { - if (f.call(element)) result = result + 1 - } - return result - } - - each(f) { - for (element in this) { - f.call(element) - } - } - - isEmpty { iterate(null) ? false : true } - - map(transformation) { MapSequence.new(this, transformation) } - - skip(count) { - if (!(count is Num) || !count.isInteger || count < 0) { - Fiber.abort("Count must be a non-negative integer.") - } - - return SkipSequence.new(this, count) - } - - take(count) { - if (!(count is Num) || !count.isInteger || count < 0) { - Fiber.abort("Count must be a non-negative integer.") - } - - return TakeSequence.new(this, count) - } - - where(predicate) { WhereSequence.new(this, predicate) } - - reduce(acc, f) { - for (element in this) { - acc = f.call(acc, element) - } - return acc - } - - reduce(f) { - var iter = iterate(null) - if (!iter) Fiber.abort("Can't reduce an empty sequence.") - - // Seed with the first element. - var result = iteratorValue(iter) - while (iter = iterate(iter)) { - result = f.call(result, iteratorValue(iter)) - } - - return result - } - - join() { join("") } - - join(sep) { - var first = true - var result = "" - - for (element in this) { - if (!first) result = result + sep - first = false - result = result + element.toString - } - - return result - } - - toList { - var result = List.new() - for (element in this) { - result.add(element) - } - return result - } -} - -class MapSequence is Sequence { - construct new(sequence, fn) { - _sequence = sequence - _fn = fn - } - - iterate(iterator) { _sequence.iterate(iterator) } - iteratorValue(iterator) { _fn.call(_sequence.iteratorValue(iterator)) } -} - -class SkipSequence is Sequence { - construct new(sequence, count) { - _sequence = sequence - _count = count - } - - iterate(iterator) { - if (iterator) { - return _sequence.iterate(iterator) - } else { - iterator = _sequence.iterate(iterator) - var count = _count - while (count > 0 && iterator) { - iterator = _sequence.iterate(iterator) - count = count - 1 - } - return iterator - } - } - - iteratorValue(iterator) { _sequence.iteratorValue(iterator) } -} - -class TakeSequence is Sequence { - construct new(sequence, count) { - _sequence = sequence - _count = count - } - - iterate(iterator) { - if (!iterator) _taken = 1 else _taken = _taken + 1 - return _taken > _count ? null : _sequence.iterate(iterator) - } - - iteratorValue(iterator) { _sequence.iteratorValue(iterator) } -} - -class WhereSequence is Sequence { - construct new(sequence, fn) { - _sequence = sequence - _fn = fn - } - - iterate(iterator) { - while (iterator = _sequence.iterate(iterator)) { - if (_fn.call(_sequence.iteratorValue(iterator))) break - } - return iterator - } - - iteratorValue(iterator) { _sequence.iteratorValue(iterator) } -} - -class String is Sequence { - bytes { StringByteSequence.new(this) } - codePoints { StringCodePointSequence.new(this) } - - split(delimiter) { - if (!(delimiter is String) || delimiter.isEmpty) { - Fiber.abort("Delimiter must be a non-empty string.") - } - - var result = [] - - var last = 0 - var index = 0 - - var delimSize = delimiter.byteCount_ - var size = byteCount_ - - while (last < size && (index = indexOf(delimiter, last)) != -1) { - result.add(this[last...index]) - last = index + delimSize - } - - if (last < size) { - result.add(this[last..-1]) - } else { - result.add("") - } - return result - } - - replace(from, to) { - if (!(from is String) || from.isEmpty) { - Fiber.abort("From must be a non-empty string.") - } else if (!(to is String)) { - Fiber.abort("To must be a string.") - } - - var result = "" - - var last = 0 - var index = 0 - - var fromSize = from.byteCount_ - var size = byteCount_ - - while (last < size && (index = indexOf(from, last)) != -1) { - result = result + this[last...index] + to - last = index + fromSize - } - - if (last < size) result = result + this[last..-1] - - return result - } - - trim() { trim_("\t\r\n ", true, true) } - trim(chars) { trim_(chars, true, true) } - trimEnd() { trim_("\t\r\n ", false, true) } - trimEnd(chars) { trim_(chars, false, true) } - trimStart() { trim_("\t\r\n ", true, false) } - trimStart(chars) { trim_(chars, true, false) } - - trim_(chars, trimStart, trimEnd) { - if (!(chars is String)) { - Fiber.abort("Characters must be a string.") - } - - var codePoints = chars.codePoints.toList - - var start - if (trimStart) { - while (start = iterate(start)) { - if (!codePoints.contains(codePointAt_(start))) break - } - - if (start == false) return "" - } else { - start = 0 - } - - var end - if (trimEnd) { - end = byteCount_ - 1 - while (end >= start) { - var codePoint = codePointAt_(end) - if (codePoint != -1 && !codePoints.contains(codePoint)) break - end = end - 1 - } - - if (end < start) return "" - } else { - end = -1 - } - - return this[start..end] - } - - *(count) { - if (!(count is Num) || !count.isInteger || count < 0) { - Fiber.abort("Count must be a non-negative integer.") - } - - var result = "" - for (i in 0...count) { - result = result + this - } - return result - } -} - -class StringByteSequence is Sequence { - construct new(string) { - _string = string - } - - [index] { _string.byteAt_(index) } - iterate(iterator) { _string.iterateByte_(iterator) } - iteratorValue(iterator) { _string.byteAt_(iterator) } - - count { _string.byteCount_ } -} - -class StringCodePointSequence is Sequence { - construct new(string) { - _string = string - } - - [index] { _string.codePointAt_(index) } - iterate(iterator) { _string.iterate(iterator) } - iteratorValue(iterator) { _string.codePointAt_(iterator) } - - count { _string.count } -} - -class List is Sequence { - addAll(other) { - for (element in other) { - add(element) - } - return other - } - - toString { "[%(join(", "))]" } - - +(other) { - var result = this[0..-1] - for (element in other) { - result.add(element) - } - return result - } - - *(count) { - if (!(count is Num) || !count.isInteger || count < 0) { - Fiber.abort("Count must be a non-negative integer.") - } - - var result = [] - for (i in 0...count) { - result.addAll(this) - } - return result - } -} - -class Map is Sequence { - keys { MapKeySequence.new(this) } - values { MapValueSequence.new(this) } - - toString { - var first = true - var result = "{" - - for (key in keys) { - if (!first) result = result + ", " - first = false - result = result + "%(key): %(this[key])" - } - - return result + "}" - } - - iteratorValue(iterator) { - return MapEntry.new( - keyIteratorValue_(iterator), - valueIteratorValue_(iterator)) - } -} - -class MapEntry { - construct new(key, value) { - _key = key - _value = value - } - - key { _key } - value { _value } - - toString { "%(_key):%(_value)" } -} - -class MapKeySequence is Sequence { - construct new(map) { - _map = map - } - - iterate(n) { _map.iterate(n) } - iteratorValue(iterator) { _map.keyIteratorValue_(iterator) } -} - -class MapValueSequence is Sequence { - construct new(map) { - _map = map - } - - iterate(n) { _map.iterate(n) } - iteratorValue(iterator) { _map.valueIteratorValue_(iterator) } -} - -class Range is Sequence {} - -class System { - static print() { - writeString_("\n") - } - - static print(obj) { - writeObject_(obj) - writeString_("\n") - return obj - } - - static printAll(sequence) { - for (object in sequence) writeObject_(object) - writeString_("\n") - } - - static write(obj) { - writeObject_(obj) - return obj - } - - static writeAll(sequence) { - for (object in sequence) writeObject_(object) - } - - static writeObject_(obj) { - var string = obj.toString - if (string is String) { - writeString_(string) - } else { - writeString_("[invalid toString]") - } - } -} +class Bool {} +class Fiber {} +class Fn {} +class Null {} +class Num {} + +class Sequence { + all(f) { + var result = true + for (element in this) { + result = f.call(element) + if (!result) return result + } + return result + } + + any(f) { + var result = false + for (element in this) { + result = f.call(element) + if (result) return result + } + return result + } + + contains(element) { + for (item in this) { + if (element == item) return true + } + return false + } + + count { + var result = 0 + for (element in this) { + result = result + 1 + } + return result + } + + count(f) { + var result = 0 + for (element in this) { + if (f.call(element)) result = result + 1 + } + return result + } + + each(f) { + for (element in this) { + f.call(element) + } + } + + isEmpty { iterate(null) ? false : true } + + map(transformation) { MapSequence.new(this, transformation) } + + skip(count) { + if (!(count is Num) || !count.isInteger || count < 0) { + Fiber.abort("Count must be a non-negative integer.") + } + + return SkipSequence.new(this, count) + } + + take(count) { + if (!(count is Num) || !count.isInteger || count < 0) { + Fiber.abort("Count must be a non-negative integer.") + } + + return TakeSequence.new(this, count) + } + + where(predicate) { WhereSequence.new(this, predicate) } + + reduce(acc, f) { + for (element in this) { + acc = f.call(acc, element) + } + return acc + } + + reduce(f) { + var iter = iterate(null) + if (!iter) Fiber.abort("Can't reduce an empty sequence.") + + // Seed with the first element. + var result = iteratorValue(iter) + while (iter = iterate(iter)) { + result = f.call(result, iteratorValue(iter)) + } + + return result + } + + join() { join("") } + + join(sep) { + var first = true + var result = "" + + for (element in this) { + if (!first) result = result + sep + first = false + result = result + element.toString + } + + return result + } + + toList { + var result = List.new() + for (element in this) { + result.add(element) + } + return result + } +} + +class MapSequence is Sequence { + construct new(sequence, fn) { + _sequence = sequence + _fn = fn + } + + iterate(iterator) { _sequence.iterate(iterator) } + iteratorValue(iterator) { _fn.call(_sequence.iteratorValue(iterator)) } +} + +class SkipSequence is Sequence { + construct new(sequence, count) { + _sequence = sequence + _count = count + } + + iterate(iterator) { + if (iterator) { + return _sequence.iterate(iterator) + } else { + iterator = _sequence.iterate(iterator) + var count = _count + while (count > 0 && iterator) { + iterator = _sequence.iterate(iterator) + count = count - 1 + } + return iterator + } + } + + iteratorValue(iterator) { _sequence.iteratorValue(iterator) } +} + +class TakeSequence is Sequence { + construct new(sequence, count) { + _sequence = sequence + _count = count + } + + iterate(iterator) { + if (!iterator) _taken = 1 else _taken = _taken + 1 + return _taken > _count ? null : _sequence.iterate(iterator) + } + + iteratorValue(iterator) { _sequence.iteratorValue(iterator) } +} + +class WhereSequence is Sequence { + construct new(sequence, fn) { + _sequence = sequence + _fn = fn + } + + iterate(iterator) { + while (iterator = _sequence.iterate(iterator)) { + if (_fn.call(_sequence.iteratorValue(iterator))) break + } + return iterator + } + + iteratorValue(iterator) { _sequence.iteratorValue(iterator) } +} + +class String is Sequence { + bytes { StringByteSequence.new(this) } + codePoints { StringCodePointSequence.new(this) } + + split(delimiter) { + if (!(delimiter is String) || delimiter.isEmpty) { + Fiber.abort("Delimiter must be a non-empty string.") + } + + var result = [] + + var last = 0 + var index = 0 + + var delimSize = delimiter.byteCount_ + var size = byteCount_ + + while (last < size && (index = indexOf(delimiter, last)) != -1) { + result.add(this[last...index]) + last = index + delimSize + } + + if (last < size) { + result.add(this[last..-1]) + } else { + result.add("") + } + return result + } + + replace(from, to) { + if (!(from is String) || from.isEmpty) { + Fiber.abort("From must be a non-empty string.") + } else if (!(to is String)) { + Fiber.abort("To must be a string.") + } + + var result = "" + + var last = 0 + var index = 0 + + var fromSize = from.byteCount_ + var size = byteCount_ + + while (last < size && (index = indexOf(from, last)) != -1) { + result = result + this[last...index] + to + last = index + fromSize + } + + if (last < size) result = result + this[last..-1] + + return result + } + + trim() { trim_("\t\r\n ", true, true) } + trim(chars) { trim_(chars, true, true) } + trimEnd() { trim_("\t\r\n ", false, true) } + trimEnd(chars) { trim_(chars, false, true) } + trimStart() { trim_("\t\r\n ", true, false) } + trimStart(chars) { trim_(chars, true, false) } + + trim_(chars, trimStart, trimEnd) { + if (!(chars is String)) { + Fiber.abort("Characters must be a string.") + } + + var codePoints = chars.codePoints.toList + + var start + if (trimStart) { + while (start = iterate(start)) { + if (!codePoints.contains(codePointAt_(start))) break + } + + if (start == false) return "" + } else { + start = 0 + } + + var end + if (trimEnd) { + end = byteCount_ - 1 + while (end >= start) { + var codePoint = codePointAt_(end) + if (codePoint != -1 && !codePoints.contains(codePoint)) break + end = end - 1 + } + + if (end < start) return "" + } else { + end = -1 + } + + return this[start..end] + } + + *(count) { + if (!(count is Num) || !count.isInteger || count < 0) { + Fiber.abort("Count must be a non-negative integer.") + } + + var result = "" + for (i in 0...count) { + result = result + this + } + return result + } +} + +class StringByteSequence is Sequence { + construct new(string) { + _string = string + } + + [index] { _string.byteAt_(index) } + iterate(iterator) { _string.iterateByte_(iterator) } + iteratorValue(iterator) { _string.byteAt_(iterator) } + + count { _string.byteCount_ } +} + +class StringCodePointSequence is Sequence { + construct new(string) { + _string = string + } + + [index] { _string.codePointAt_(index) } + iterate(iterator) { _string.iterate(iterator) } + iteratorValue(iterator) { _string.codePointAt_(iterator) } + + count { _string.count } +} + +class List is Sequence { + addAll(other) { + for (element in other) { + add(element) + } + return other + } + + sort() { sort {|low, high| low < high } } + + sort(comparer) { + if (!(comparer is Fn)) { + Fiber.abort("Comparer must be a function.") + } + quicksort_(0, count - 1, comparer) + return this + } + + quicksort_(low, high, comparer) { + if (low < high) { + var p = partition_(low, high, comparer) + quicksort_(low, p - 1, comparer) + quicksort_(p + 1, high, comparer) + } + } + + partition_(low, high, comparer) { + var p = this[high] + var i = low - 1 + for (j in low..(high-1)) { + if (comparer.call(this[j], p)) { + i = i + 1 + var t = this[i] + this[i] = this[j] + this[j] = t + } + } + var t = this[i+1] + this[i+1] = this[high] + this[high] = t + return i+1 + } + + toString { "[%(join(", "))]" } + + +(other) { + var result = this[0..-1] + for (element in other) { + result.add(element) + } + return result + } + + *(count) { + if (!(count is Num) || !count.isInteger || count < 0) { + Fiber.abort("Count must be a non-negative integer.") + } + + var result = [] + for (i in 0...count) { + result.addAll(this) + } + return result + } +} + +class Map is Sequence { + keys { MapKeySequence.new(this) } + values { MapValueSequence.new(this) } + + toString { + var first = true + var result = "{" + + for (key in keys) { + if (!first) result = result + ", " + first = false + result = result + "%(key): %(this[key])" + } + + return result + "}" + } + + iteratorValue(iterator) { + return MapEntry.new( + keyIteratorValue_(iterator), + valueIteratorValue_(iterator)) + } +} + +class MapEntry { + construct new(key, value) { + _key = key + _value = value + } + + key { _key } + value { _value } + + toString { "%(_key):%(_value)" } +} + +class MapKeySequence is Sequence { + construct new(map) { + _map = map + } + + iterate(n) { _map.iterate(n) } + iteratorValue(iterator) { _map.keyIteratorValue_(iterator) } +} + +class MapValueSequence is Sequence { + construct new(map) { + _map = map + } + + iterate(n) { _map.iterate(n) } + iteratorValue(iterator) { _map.valueIteratorValue_(iterator) } +} + +class Range is Sequence {} + +class System { + static print() { + writeString_("\n") + } + + static print(obj) { + writeObject_(obj) + writeString_("\n") + return obj + } + + static printAll(sequence) { + for (object in sequence) writeObject_(object) + writeString_("\n") + } + + static write(obj) { + writeObject_(obj) + return obj + } + + static writeAll(sequence) { + for (object in sequence) writeObject_(object) + } + + static writeObject_(obj) { + var string = obj.toString + if (string is String) { + writeString_(string) + } else { + writeString_("[invalid toString]") + } + } +} + +class ClassAttributes { + self { _attributes } + methods { _methods } + construct new(attributes, methods) { + _attributes = attributes + _methods = methods + } + toString { "attributes:%(_attributes) methods:%(_methods)" } +} \ No newline at end of file diff --git a/deps/wren/src/vm/wren_core.wren.inc b/deps/wren/src/vm/wren_core.wren.inc index a4adda34..b4f7e1a8 100644 --- a/deps/wren/src/vm/wren_core.wren.inc +++ b/deps/wren/src/vm/wren_core.wren.inc @@ -1,440 +1,486 @@ -// 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" -" 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"; +// 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"; + diff --git a/deps/wren/src/vm/wren_debug.c b/deps/wren/src/vm/wren_debug.c index 0de86c31..2c466486 100644 --- a/deps/wren/src/vm/wren_debug.c +++ b/deps/wren/src/vm/wren_debug.c @@ -1,387 +1,388 @@ -#include - -#include "wren_debug.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_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 ? "" : 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"); -} +#include + +#include "wren_debug.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 ? "" : 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"); +} diff --git a/deps/wren/src/vm/wren_debug.h b/deps/wren/src/vm/wren_debug.h index 0840beb2..eea71065 100644 --- a/deps/wren/src/vm/wren_debug.h +++ b/deps/wren/src/vm/wren_debug.h @@ -1,27 +1,27 @@ -#ifndef wren_debug_h -#define wren_debug_h - -#include "wren_value.h" -#include "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 +#ifndef wren_debug_h +#define wren_debug_h + +#include "wren_value.h" +#include "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 diff --git a/deps/wren/src/vm/wren_math.h b/deps/wren/src/vm/wren_math.h new file mode 100644 index 00000000..bb5a2b39 --- /dev/null +++ b/deps/wren/src/vm/wren_math.h @@ -0,0 +1,34 @@ +#ifndef wren_math_h +#define wren_math_h + +#include +#include + +// 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 diff --git a/deps/wren/src/vm/wren_opcodes.h b/deps/wren/src/vm/wren_opcodes.h index 61cc5c58..46ba8b47 100644 --- a/deps/wren/src/vm/wren_opcodes.h +++ b/deps/wren/src/vm/wren_opcodes.h @@ -1,213 +1,217 @@ -// 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) - -// 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) +// 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) diff --git a/deps/wren/src/vm/wren_primitive.c b/deps/wren/src/vm/wren_primitive.c index 59c3feee..ed1882ea 100644 --- a/deps/wren/src/vm/wren_primitive.c +++ b/deps/wren/src/vm/wren_primitive.c @@ -1,125 +1,119 @@ -#include "wren_primitive.h" - -#include - -// 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; - - vm->fiber->error = wrenStringFormat(vm, "$ must be a function.", argName); - return false; -} - -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 (IS_BOOL(arg) || IS_CLASS(arg) || IS_NULL(arg) || - IS_NUM(arg) || IS_RANGE(arg) || IS_STRING(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; -} +#include "wren_primitive.h" + +#include + +// 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; +} diff --git a/deps/wren/src/vm/wren_primitive.h b/deps/wren/src/vm/wren_primitive.h index 7f6e0ddf..a6421c1d 100644 --- a/deps/wren/src/vm/wren_primitive.h +++ b/deps/wren/src/vm/wren_primitive.h @@ -1,88 +1,109 @@ -#ifndef wren_primitive_h -#define wren_primitive_h - -#include "wren_vm.h" - -// Binds a primitive method named [name] (in Wren) implemented using C function -// [fn] to `ObjClass` [cls]. -#define PRIMITIVE(cls, name, function) \ - { \ - 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); \ - } - -// 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 (0) - -#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 (0); - -#define RETURN_ERROR_FMT(msg, arg) \ - do { \ - vm->fiber->error = wrenStringFormat(vm, msg, arg); \ - return false; \ - } while (0); - -// 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 +#ifndef wren_primitive_h +#define wren_primitive_h + +#include "wren_vm.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 diff --git a/deps/wren/src/vm/wren_utils.c b/deps/wren/src/vm/wren_utils.c index 1e86e3e5..e8464486 100644 --- a/deps/wren/src/vm/wren_utils.c +++ b/deps/wren/src/vm/wren_utils.c @@ -1,196 +1,207 @@ -#include - -#include "wren_utils.h" -#include "wren_vm.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; -} +#include + +#include "wren_utils.h" +#include "wren_vm.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; +} diff --git a/deps/wren/src/vm/wren_utils.h b/deps/wren/src/vm/wren_utils.h index ce7e28ff..c2a6e4a3 100644 --- a/deps/wren/src/vm/wren_utils.h +++ b/deps/wren/src/vm/wren_utils.h @@ -1,121 +1,126 @@ -#ifndef wren_utils_h -#define wren_utils_h - -#include "wren.h" -#include "wren_common.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); - -#endif +#ifndef wren_utils_h +#define wren_utils_h + +#include "wren.h" +#include "wren_common.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 diff --git a/deps/wren/src/vm/wren_value.c b/deps/wren/src/vm/wren_value.c index a03130b5..c49a3b6b 100644 --- a/deps/wren/src/vm/wren_value.c +++ b/deps/wren/src/vm/wren_value.c @@ -1,1315 +1,1330 @@ -#include -#include -#include -#include - -#include "wren.h" -#include "wren_value.h" -#include "wren_vm.h" - -#if WREN_DEBUG_TRACE_MEMORY - #include "wren_debug.h" -#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; - - 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; -} - -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. - DoubleBits bits; - bits.num = num; - return hashBits(bits.bits64); -} - -// 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->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); - - // 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); - - // 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; - } -} +#include +#include +#include +#include + +#include "wren.h" +#include "wren_value.h" +#include "wren_vm.h" + +#if WREN_DEBUG_TRACE_MEMORY + #include "wren_debug.h" +#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); + + // 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; + } +} diff --git a/deps/wren/src/vm/wren_value.h b/deps/wren/src/vm/wren_value.h index 14b9fdde..2ca0cfdc 100644 --- a/deps/wren/src/vm/wren_value.h +++ b/deps/wren/src/vm/wren_value.h @@ -1,876 +1,890 @@ -#ifndef wren_value_h -#define wren_value_h - -#include -#include - -#include "wren_common.h" -#include "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_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 too 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 similiar 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 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 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; -}; - -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 - -// A union to let us reinterpret a double as raw bits and back. -typedef union -{ - uint64_t bits64; - uint32_t bits32[2]; - double num; -} DoubleBits; - -// 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); - -// Creates a new empty map. -ObjMap* wrenNewMap(WrenVM* vm); - -// 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 - DoubleBits data; - data.bits64 = value; - return data.num; -#else - return value.as.num; -#endif -} - -// Converts [num] to a [Value]. -static inline Value wrenNumToValue(double num) -{ -#if WREN_NAN_TAGGING - DoubleBits data; - data.num = num; - return data.bits64; -#else - Value value; - value.type = VAL_NUM; - value.as.num = num; - return value; -#endif -} - -#endif +#ifndef wren_value_h +#define wren_value_h + +#include +#include + +#include "wren_common.h" +#include "wren_math.h" +#include "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 diff --git a/deps/wren/src/vm/wren_vm.c b/deps/wren/src/vm/wren_vm.c index 22af1618..254d0b03 100644 --- a/deps/wren/src/vm/wren_vm.c +++ b/deps/wren/src/vm/wren_vm.c @@ -1,1783 +1,1993 @@ -#include -#include - -#include "wren.h" -#include "wren_common.h" -#include "wren_compiler.h" -#include "wren_core.h" -#include "wren_debug.h" -#include "wren_vm.h" - -#if WREN_OPT_META - #include "wren_opt_meta.h" -#endif -#if WREN_OPT_RANDOM - #include "wren_opt_random.h" -#endif - -#if WREN_DEBUG_TRACE_MEMORY || WREN_DEBUG_TRACE_GC - #include - #include -#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) -{ - if (newSize == 0) - { - free(ptr); - return NULL; - } - - return realloc(ptr, newSize); -} - -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; - if (config != NULL) reallocate = config->reallocateFn; - - WrenVM* vm = (WrenVM*)reallocate(NULL, sizeof(*vm)); - memset(vm, 0, sizeof(WrenVM)); - - // Copy the configuration if given one. - if (config != NULL) - { - memcpy(&vm->config, config, sizeof(WrenConfiguration)); - } - 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*)); - 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); - - // 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 %.3fs.\n", - (unsigned long)before, - (unsigned long)vm->bytesAllocated, - (unsigned long)(before - vm->bytesAllocated), - (unsigned long)vm->nextGC, - elapsed); -#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); -} - -// 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)); - - // 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)); - - // 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) - { - 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, "", 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, "", 10); - if (methods.finalize != NULL) - { - method.as.foreign = (WrenForeignMethodFn)methods.finalize; - wrenBindMethod(vm, classObj, symbol, method); - } -} - -// 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, "", 10); - ASSERT(symbol != -1, "Should have defined 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, "", 10); - ASSERT(symbol != -1, "Should have defined 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)); - - const char* source = NULL; - bool allocatedSource = true; - - // Let the host try to provide the module. - if (vm->config.loadModuleFn != NULL) - { - source = vm->config.loadModuleFn(vm, AS_CSTRING(name)); - } - - // If the host didn't provide it, see if it's a built in optional module. - if (source == NULL) - { - ObjString* nameString = AS_STRING(name); -#if WREN_OPT_META - if (strcmp(nameString->value, "meta") == 0) source = wrenMetaSource(); -#endif -#if WREN_OPT_RANDOM - if (strcmp(nameString->value, "random") == 0) source = wrenRandomSource(); -#endif - - // TODO: Should we give the host the ability to provide strings that don't - // need to be freed? - allocatedSource = false; - } - - if (source == NULL) - { - vm->fiber->error = wrenStringFormat(vm, "Could not load module '@'.", name); - wrenPopRoot(vm); // name. - return NULL_VAL; - } - - ObjClosure* moduleClosure = compileInModule(vm, name, source, false, true); - - // Modules loaded by the host are expected to be dynamically allocated with - // ownership given to the VM, which will free it. The built in optional - // modules are constant strings which don't need to be freed. - if (allocatedSource) DEALLOCATE(vm, (char*)source); - - 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; -} - -// 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() \ - frame = &fiber->frames[fiber->numFrames - 1]; \ - stackStart = frame->stackStart; \ - ip = frame->ip; \ - fn = frame->closure->fn; - - // 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, - #include "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_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 (IS_FALSE(condition) || IS_NULL(condition)) ip += offset; - DISPATCH(); - } - - CASE_CODE(AND): - { - uint16_t offset = READ_SHORT(); - Value condition = PEEK(); - - if (IS_FALSE(condition) || IS_NULL(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 (IS_FALSE(condition) || IS_NULL(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(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. - - 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_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 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; - vm->apiStack[elementSlot] = elements.data[index]; -} - -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. - 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); -} - -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."); - validateApiSlot(vm, slot); - - 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]); -} - -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; -} +#include +#include + +#include "wren.h" +#include "wren_common.h" +#include "wren_compiler.h" +#include "wren_core.h" +#include "wren_debug.h" +#include "wren_primitive.h" +#include "wren_vm.h" + +#if WREN_OPT_META + #include "wren_opt_meta.h" +#endif +#if WREN_OPT_RANDOM + #include "wren_opt_random.h" +#endif + +#if WREN_DEBUG_TRACE_MEMORY || WREN_DEBUG_TRACE_GC + #include + #include +#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, "", 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, "", 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, "", 10); + ASSERT(symbol != -1, "Should have defined 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, "", 10); + ASSERT(symbol != -1, "Should have defined 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, + #include "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; +} diff --git a/deps/wren/src/vm/wren_vm.h b/deps/wren/src/vm/wren_vm.h index dd04f405..7ab74c9e 100644 --- a/deps/wren/src/vm/wren_vm.h +++ b/deps/wren/src/vm/wren_vm.h @@ -1,246 +1,251 @@ -#ifndef wren_vm_h -#define wren_vm_h - -#include "wren_common.h" -#include "wren_compiler.h" -#include "wren_value.h" -#include "wren_utils.h" - -// The maximum number of temporary objects that can be made visible to the GC -// at one time. -#define WREN_MAX_TEMP_ROOTS 5 - -typedef enum -{ - #define OPCODE(name, _) CODE_##name, - #include "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'; -} - -#endif +#ifndef wren_vm_h +#define wren_vm_h + +#include "wren_common.h" +#include "wren_compiler.h" +#include "wren_value.h" +#include "wren_utils.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, + #include "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