diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..dcb25402 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,29 @@ +FROM alpine:latest +LABEL authors="tristan" + +# Install necessary packages +RUN apk update && \ + apk add --no-cache g++ boost-dev curl-dev cmake make + +# Verify the installations +RUN g++ --version && \ + ls /usr/include/boost + +# Set the working directory +WORKDIR /app + +# Add your application source code to the container +COPY . /app + +# Set the build type (default to Release, can be overridden at build time) +ARG BUILD_TYPE=Release + +# Create a build directory and run cmake and make +RUN mkdir -p build && cd build && \ + cmake -DCMAKE_BUILD_TYPE=${BUILD_TYPE} .. && \ + cmake --build . && \ + cd .. + +# Define the command to run your application +ENV FAN_LIB "./lang" +CMD ["./build/fan"] \ No newline at end of file diff --git a/README.md b/README.md index d5a4add9..74cd6641 100644 --- a/README.md +++ b/README.md @@ -9,12 +9,21 @@ Fan is a dynamically-typed programming language. It is based on [Wren](https://wren.io), but features significant customizations to its runtime. Fan's aim is to be _statically compilable_, portable, and come with batteries-included. +## Docker +Right now, you still have to clone the repository, but then just run the following Docker command to build a release version +of Fan. Once I figure out the registry, this will be simpler still. + +```shell +docker build -t fan . +``` + + ```sh git clone --recurse-submodules https://github.com/tristanisham/fan.git ``` Fan currently supports, GCC, Clang, and targets *nix systems. Windows support -is a work in progross. MSVC is God's punishment for man's arrogance. +is a work in progress. MSVC is God's punishment for man's arrogance. ## Setup & Dependencies diff --git a/include/vm.hpp b/include/vm.hpp index 38eef5b0..59b48b5f 100644 --- a/include/vm.hpp +++ b/include/vm.hpp @@ -6,13 +6,12 @@ #include #ifndef NDEBUG - #include - #define DEBUG_PRINT(...) std::cout << rang::fg::blue << "DEBUG " << rang::fg::reset << __VA_ARGS__ << std::endl; +#include +#define DEBUG_PRINT(...) std::cout << rang::fg::blue << "DEBUG " << rang::fg::reset << __VA_ARGS__ << std::endl; #else - #define DEBUG_PRINT(...) +#define DEBUG_PRINT(...) #endif - namespace vm { size_t createVmMap(WrenVM* vm, const int& slot, const std::unordered_map& entries); diff --git a/include/wren.h b/include/wren.h index 881fe808..3e97c4d7 100644 --- a/include/wren.h +++ b/include/wren.h @@ -15,9 +15,7 @@ // A monotonically increasing numeric representation of the version number. Use // this if you want to do range checks over versions. -#define WREN_VERSION_NUMBER \ - (WREN_VERSION_MAJOR * 1000000 + WREN_VERSION_MINOR * 1000 + \ - WREN_VERSION_PATCH) +#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) @@ -25,7 +23,7 @@ #else #define WREN_API #endif -#endif // WREN_API +#endif // WREN_API // A single virtual machine for executing Wren code. // @@ -55,23 +53,22 @@ 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, void *userData); +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); +typedef void (*WrenForeignMethodFn)(WrenVM* vm); // A finalizer function for freeing resources owned by an instance of a foreign // class. Unlike most foreign methods, finalizers do not have access to the VM // and should not interact with it since it's in the middle of a garbage // collection. -typedef void (*WrenFinalizerFn)(void *data); +typedef void (*WrenFinalizerFn)(void* data); // Gives the host a chance to canonicalize the imported module name, // potentially taking into account the (previously resolved) name of the module // that contains the import. Typically, this is used to implement relative // imports. -typedef const char *(*WrenResolveModuleFn)(WrenVM *vm, const char *importer, - const char *name); +typedef const char* (*WrenResolveModuleFn)(WrenVM* vm, const char* importer, const char* name); // Forward declare struct WrenLoadModuleResult; @@ -79,42 +76,37 @@ 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); +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; + const char* source; + WrenLoadModuleCompleteFn onComplete; + void* userData; } WrenLoadModuleResult; // Loads and returns the source code for the module [name]. -typedef WrenLoadModuleResult (*WrenLoadModuleFn)(WrenVM *vm, const char *name); +typedef WrenLoadModuleResult (*WrenLoadModuleFn)(WrenVM* vm, const char* name); // Returns a pointer to a foreign method on [className] in [module] with // [signature]. -typedef WrenForeignMethodFn (*WrenBindForeignMethodFn)(WrenVM *vm, - const char *module, - const char *className, - bool isStatic, - const char *signature); +typedef WrenForeignMethodFn (*WrenBindForeignMethodFn)(WrenVM* vm, const char* module, const char* className, bool isStatic, const char* signature); // Displays a string of text to the user. -typedef void (*WrenWriteFn)(WrenVM *vm, const char *text); +typedef void (*WrenWriteFn)(WrenVM* vm, const char* text); typedef enum { - // A syntax or resolution error detected at compile time. - WREN_ERROR_COMPILE, + // A syntax or resolution error detected at compile time. + WREN_ERROR_COMPILE, - // The error message for a runtime error. - WREN_ERROR_RUNTIME, + // The error message for a runtime error. + WREN_ERROR_RUNTIME, - // One entry of a runtime error's stack trace. - WREN_ERROR_STACK_TRACE + // One entry of a runtime error's stack trace. + WREN_ERROR_STACK_TRACE } WrenErrorType; // Reports an error to the user. @@ -129,169 +121,163 @@ typedef enum { // made for each line in the stack trace. Each of those has the resolved // [module] and [line] where the method or function is defined and [message] is // the name of the method or function. -typedef void (*WrenErrorFn)(WrenVM *vm, WrenErrorType type, const char *module, - int line, const char *message); +typedef void (*WrenErrorFn)(WrenVM* vm, WrenErrorType type, const char* module, int line, const char* message); typedef struct { - // The callback invoked when the foreign object is created. - // - // This must be provided. Inside the body of this, it must call - // [wrenSetSlotNewForeign()] exactly once. - WrenForeignMethodFn allocate; - - // The callback invoked when the garbage collector is about to collect a - // foreign object's memory. - // - // This may be `NULL` if the foreign class does not need to finalize. - WrenFinalizerFn finalize; + // The callback invoked when the foreign object is created. + // + // This must be provided. Inside the body of this, it must call + // [wrenSetSlotNewForeign()] exactly once. + WrenForeignMethodFn allocate; + + // The callback invoked when the garbage collector is about to collect a + // foreign object's memory. + // + // This may be `NULL` if the foreign class does not need to finalize. + WrenFinalizerFn finalize; } WrenForeignClassMethods; // Returns a pair of pointers to the foreign methods used to allocate and // finalize the data for instances of [className] in resolved [module]. -typedef WrenForeignClassMethods (*WrenBindForeignClassFn)( - WrenVM *vm, const char *module, const char *className); +typedef WrenForeignClassMethods (*WrenBindForeignClassFn)(WrenVM* vm, const char* module, const char* className); typedef struct { - // The callback Wren will use to allocate, reallocate, and deallocate memory. - // - // If `NULL`, defaults to a built-in function that uses `realloc` and `free`. - WrenReallocateFn reallocateFn; - - // The callback Wren uses to resolve a module name. - // - // Some host applications may wish to support "relative" imports, where the - // meaning of an import string depends on the module that contains it. To - // support that without baking any policy into Wren itself, the VM gives the - // host a chance to resolve an import string. - // - // Before an import is loaded, it calls this, passing in the name of the - // module that contains the import and the import string. The host app can - // look at both of those and produce a new "canonical" string that uniquely - // identifies the module. This string is then used as the name of the module - // going forward. It is what is passed to [loadModuleFn], how duplicate - // imports of the same module are detected, and how the module is reported in - // stack traces. - // - // If you leave this function NULL, then the original import string is - // treated as the resolved string. - // - // If an import cannot be resolved by the embedder, it should return NULL and - // Wren will report that as a runtime error. - // - // Wren will take ownership of the string you return and free it for you, so - // it should be allocated using the same allocation function you provide - // above. - WrenResolveModuleFn resolveModuleFn; - - // The callback Wren uses to load a module. - // - // Since Wren does not talk directly to the file system, it relies on the - // embedder to physically locate and read the source code for a module. The - // first time an import appears, Wren will call this and pass in the name of - // the module being imported. The method will return a result, which contains - // the source code for that module. Memory for the source is owned by the - // host application, and can be freed using the onComplete callback. - // - // This will only be called once for any given module name. Wren caches the - // result internally so subsequent imports of the same module will use the - // previous source and not call this. - // - // If a module with the given name could not be found by the embedder, it - // should return NULL and Wren will report that as a runtime error. - WrenLoadModuleFn loadModuleFn; - - // The callback Wren uses to find a foreign method and bind it to a class. - // - // When a foreign method is declared in a class, this will be called with the - // foreign method's module, class, and signature when the class body is - // executed. It should return a pointer to the foreign function that will be - // bound to that method. - // - // If the foreign function could not be found, this should return NULL and - // Wren will report it as runtime error. - WrenBindForeignMethodFn bindForeignMethodFn; - - // The callback Wren uses to find a foreign class and get its foreign methods. - // - // When a foreign class is declared, this will be called with the class's - // module and name when the class body is executed. It should return the - // foreign functions uses to allocate and (optionally) finalize the bytes - // stored in the foreign object when an instance is created. - WrenBindForeignClassFn bindForeignClassFn; - - // The callback Wren uses to display text when `System.print()` or the other - // related functions are called. - // - // If this is `NULL`, Wren discards any printed text. - WrenWriteFn writeFn; - - // The callback Wren uses to report errors. - // - // When an error occurs, this will be called with the module name, line - // number, and an error message. If this is `NULL`, Wren doesn't report any - // errors. - WrenErrorFn errorFn; - - // The number of bytes Wren will allocate before triggering the first garbage - // collection. - // - // If zero, defaults to 10MB. - size_t initialHeapSize; - - // After a collection occurs, the threshold for the next collection is - // determined based on the number of bytes remaining in use. This allows Wren - // to shrink its memory usage automatically after reclaiming a large amount - // of memory. - // - // This can be used to ensure that the heap does not get too small, which can - // in turn lead to a large number of collections afterwards as the heap grows - // back to a usable size. - // - // If zero, defaults to 1MB. - size_t minHeapSize; - - // Wren will resize the heap automatically as the number of bytes - // remaining in use after a collection changes. This number determines the - // amount of additional memory Wren will use after a collection, as a - // percentage of the current heap size. - // - // For example, say that this is 50. After a garbage collection, when there - // are 400 bytes of memory still in use, the next collection will be triggered - // after a total of 600 bytes are allocated (including the 400 already in - // use.) - // - // Setting this to a smaller number wastes less memory, but triggers more - // frequent garbage collections. - // - // If zero, defaults to 50. - int heapGrowthPercent; - - // User-defined data associated with the VM. - void *userData; + // The callback Wren will use to allocate, reallocate, and deallocate memory. + // + // If `NULL`, defaults to a built-in function that uses `realloc` and `free`. + WrenReallocateFn reallocateFn; + + // The callback Wren uses to resolve a module name. + // + // Some host applications may wish to support "relative" imports, where the + // meaning of an import string depends on the module that contains it. To + // support that without baking any policy into Wren itself, the VM gives the + // host a chance to resolve an import string. + // + // Before an import is loaded, it calls this, passing in the name of the + // module that contains the import and the import string. The host app can + // look at both of those and produce a new "canonical" string that uniquely + // identifies the module. This string is then used as the name of the module + // going forward. It is what is passed to [loadModuleFn], how duplicate + // imports of the same module are detected, and how the module is reported in + // stack traces. + // + // If you leave this function NULL, then the original import string is + // treated as the resolved string. + // + // If an import cannot be resolved by the embedder, it should return NULL and + // Wren will report that as a runtime error. + // + // Wren will take ownership of the string you return and free it for you, so + // it should be allocated using the same allocation function you provide + // above. + WrenResolveModuleFn resolveModuleFn; + + // The callback Wren uses to load a module. + // + // Since Wren does not talk directly to the file system, it relies on the + // embedder to physically locate and read the source code for a module. The + // first time an import appears, Wren will call this and pass in the name of + // the module being imported. The method will return a result, which contains + // the source code for that module. Memory for the source is owned by the + // host application, and can be freed using the onComplete callback. + // + // This will only be called once for any given module name. Wren caches the + // result internally so subsequent imports of the same module will use the + // previous source and not call this. + // + // If a module with the given name could not be found by the embedder, it + // should return NULL and Wren will report that as a runtime error. + WrenLoadModuleFn loadModuleFn; + + // The callback Wren uses to find a foreign method and bind it to a class. + // + // When a foreign method is declared in a class, this will be called with the + // foreign method's module, class, and signature when the class body is + // executed. It should return a pointer to the foreign function that will be + // bound to that method. + // + // If the foreign function could not be found, this should return NULL and + // Wren will report it as runtime error. + WrenBindForeignMethodFn bindForeignMethodFn; + + // The callback Wren uses to find a foreign class and get its foreign methods. + // + // When a foreign class is declared, this will be called with the class's + // module and name when the class body is executed. It should return the + // foreign functions uses to allocate and (optionally) finalize the bytes + // stored in the foreign object when an instance is created. + WrenBindForeignClassFn bindForeignClassFn; + + // The callback Wren uses to display text when `System.print()` or the other + // related functions are called. + // + // If this is `NULL`, Wren discards any printed text. + WrenWriteFn writeFn; + + // The callback Wren uses to report errors. + // + // When an error occurs, this will be called with the module name, line + // number, and an error message. If this is `NULL`, Wren doesn't report any + // errors. + WrenErrorFn errorFn; + + // The number of bytes Wren will allocate before triggering the first garbage + // collection. + // + // If zero, defaults to 10MB. + size_t initialHeapSize; + + // After a collection occurs, the threshold for the next collection is + // determined based on the number of bytes remaining in use. This allows Wren + // to shrink its memory usage automatically after reclaiming a large amount + // of memory. + // + // This can be used to ensure that the heap does not get too small, which can + // in turn lead to a large number of collections afterwards as the heap grows + // back to a usable size. + // + // If zero, defaults to 1MB. + size_t minHeapSize; + + // Wren will resize the heap automatically as the number of bytes + // remaining in use after a collection changes. This number determines the + // amount of additional memory Wren will use after a collection, as a + // percentage of the current heap size. + // + // For example, say that this is 50. After a garbage collection, when there + // are 400 bytes of memory still in use, the next collection will be triggered + // after a total of 600 bytes are allocated (including the 400 already in + // use.) + // + // Setting this to a smaller number wastes less memory, but triggers more + // frequent garbage collections. + // + // If zero, defaults to 50. + int heapGrowthPercent; + + // User-defined data associated with the VM. + void* userData; } WrenConfiguration; -typedef enum { - WREN_RESULT_SUCCESS, - WREN_RESULT_COMPILE_ERROR, - WREN_RESULT_RUNTIME_ERROR -} WrenInterpretResult; +typedef enum { WREN_RESULT_SUCCESS, WREN_RESULT_COMPILE_ERROR, WREN_RESULT_RUNTIME_ERROR } WrenInterpretResult; // The type of an object stored in a slot. // // This is not necessarily the object's *class*, but instead its low level // representation type. typedef enum { - WREN_TYPE_BOOL, - WREN_TYPE_NUM, - WREN_TYPE_FOREIGN, - WREN_TYPE_LIST, - WREN_TYPE_MAP, - WREN_TYPE_NULL, - WREN_TYPE_STRING, - - // The object is of a type that isn't accessible by the C API. - WREN_TYPE_UNKNOWN + WREN_TYPE_BOOL, + WREN_TYPE_NUM, + WREN_TYPE_FOREIGN, + WREN_TYPE_LIST, + WREN_TYPE_MAP, + WREN_TYPE_NULL, + WREN_TYPE_STRING, + + // The object is of a type that isn't accessible by the C API. + WREN_TYPE_UNKNOWN } WrenType; // Get the current wren version number. @@ -302,25 +288,24 @@ WREN_API int wrenGetVersionNumber(); // Initializes [configuration] with all of its default values. // // Call this before setting the particular fields you care about. -WREN_API void wrenInitConfiguration(WrenConfiguration *configuration); +WREN_API void wrenInitConfiguration(WrenConfiguration* configuration); // Creates a new Wren virtual machine using the given [configuration]. Wren // will copy the configuration data, so the argument passed to this can be // freed after calling this. If [configuration] is `NULL`, uses a default // configuration. -WREN_API WrenVM *wrenNewVM(WrenConfiguration *configuration); +WREN_API WrenVM* wrenNewVM(WrenConfiguration* configuration); // Disposes of all resources is use by [vm], which was previously created by a // call to [wrenNewVM]. -WREN_API void wrenFreeVM(WrenVM *vm); +WREN_API void wrenFreeVM(WrenVM* vm); // Immediately run the garbage collector to free unused memory. -WREN_API 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]. -WREN_API WrenInterpretResult wrenInterpret(WrenVM *vm, const char *module, - const char *source); +WREN_API WrenInterpretResult wrenInterpret(WrenVM* vm, const char* module, const char* source); // Creates a handle that can be used to invoke a method with [signature] on // using a receiver and arguments that are set up on the stack. @@ -330,7 +315,7 @@ WREN_API WrenInterpretResult wrenInterpret(WrenVM *vm, const char *module, // // When you are done with this handle, it must be released using // [wrenReleaseHandle]. -WREN_API 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. @@ -342,11 +327,11 @@ WREN_API WrenHandle *wrenMakeCallHandle(WrenVM *vm, const char *signature); // signature. // // After this returns, you can access the return value from slot 0 on the stack. -WREN_API 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. -WREN_API 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 @@ -386,7 +371,7 @@ WREN_API void wrenReleaseHandle(WrenVM *vm, WrenHandle *handle); // return, you get a very fast FFI. // Returns the number of slots available to the current foreign method. -WREN_API 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. @@ -394,15 +379,15 @@ WREN_API 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. -WREN_API void wrenEnsureSlots(WrenVM *vm, int numSlots); +WREN_API void wrenEnsureSlots(WrenVM* vm, int numSlots); // Gets the type of the object in [slot]. -WREN_API 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. -WREN_API bool wrenGetSlotBool(WrenVM *vm, int slot); +WREN_API bool wrenGetSlotBool(WrenVM* vm, int slot); // Reads a byte array from [slot]. // @@ -414,19 +399,19 @@ WREN_API 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. -WREN_API 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. -WREN_API 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. -WREN_API void *wrenGetSlotForeign(WrenVM *vm, int slot); +WREN_API void* wrenGetSlotForeign(WrenVM* vm, int slot); // Reads a string from [slot]. // @@ -435,26 +420,25 @@ WREN_API 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. -WREN_API 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()]. -WREN_API WrenHandle *wrenGetSlotHandle(WrenVM *vm, int slot); +WREN_API WrenHandle* wrenGetSlotHandle(WrenVM* vm, int slot); // Stores the boolean [value] in [slot]. -WREN_API 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. -WREN_API 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]. -WREN_API 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]. @@ -465,17 +449,16 @@ WREN_API 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. -WREN_API 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]. -WREN_API 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); +WREN_API void wrenSetSlotNewMap(WrenVM* vm, int slot); // Stores null in [slot]. -WREN_API void wrenSetSlotNull(WrenVM *vm, int slot); +WREN_API void wrenSetSlotNull(WrenVM* vm, int slot); // Stores the string [text] in [slot]. // @@ -483,77 +466,70 @@ WREN_API 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. -WREN_API 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. -WREN_API 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]. -WREN_API 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]. -WREN_API 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); +WREN_API void wrenSetListElement(WrenVM* vm, int listSlot, int index, int elementSlot); // Takes the value stored at [elementSlot] and inserts it into the list stored // at [listSlot] at [index]. // // As in Wren, negative indexes can be used to insert from the end. To append // an element, use `-1` for the index. -WREN_API void wrenInsertInList(WrenVM *vm, int listSlot, int index, - int elementSlot); +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); +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); +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); +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); +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); +WREN_API void wrenRemoveMapValue(WrenVM* vm, int mapSlot, int keySlot, int removedValueSlot); // Looks up the top level variable with [name] in resolved [module] and stores // it in [slot]. -WREN_API void wrenGetVariable(WrenVM *vm, const char *module, const char *name, - int slot); +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); +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); +WREN_API bool wrenHasModule(WrenVM* vm, const char* module); // Sets the current fiber to be aborted, and uses the value in [slot] as the // runtime error object. -WREN_API void wrenAbortFiber(WrenVM *vm, int slot); +WREN_API void wrenAbortFiber(WrenVM* vm, int slot); // Returns the user data associated with the WrenVM. -WREN_API void *wrenGetUserData(WrenVM *vm); +WREN_API void* wrenGetUserData(WrenVM* vm); // Sets user data associated with the WrenVM. -WREN_API void wrenSetUserData(WrenVM *vm, void *userData); +WREN_API void wrenSetUserData(WrenVM* vm, void* userData); #endif diff --git a/src/wren/wren.c b/src/wren/wren.c index acadbf8a..309b293a 100644 --- a/src/wren/wren.c +++ b/src/wren/wren.c @@ -1,17 +1,17 @@ // MIT License -// +// // Copyright (c) 2013-2021 Robert Nystrom and Wren Contributors -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -25,8 +25,8 @@ #define wren_h #include -#include #include +#include // The Wren semantic version number components. #define WREN_VERSION_MAJOR 0 @@ -38,17 +38,15 @@ // A monotonically increasing numeric representation of the version number. Use // this if you want to do range checks over versions. -#define WREN_VERSION_NUMBER (WREN_VERSION_MAJOR * 1000000 + \ - WREN_VERSION_MINOR * 1000 + \ - WREN_VERSION_PATCH) +#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 +#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. // @@ -93,8 +91,7 @@ typedef void (*WrenFinalizerFn)(void* data); // potentially taking into account the (previously resolved) name of the module // that contains the import. Typically, this is used to implement relative // imports. -typedef const char* (*WrenResolveModuleFn)(WrenVM* vm, - const char* importer, const char* name); +typedef const char* (*WrenResolveModuleFn)(WrenVM* vm, const char* importer, const char* name); // Forward declare struct WrenLoadModuleResult; @@ -103,14 +100,13 @@ struct WrenLoadModuleResult; // 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. +// 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; +typedef struct WrenLoadModuleResult { + const char* source; + WrenLoadModuleCompleteFn onComplete; + void* userData; } WrenLoadModuleResult; // Loads and returns the source code for the module [name]. @@ -118,23 +114,20 @@ typedef WrenLoadModuleResult (*WrenLoadModuleFn)(WrenVM* vm, const char* name); // Returns a pointer to a foreign method on [className] in [module] with // [signature]. -typedef WrenForeignMethodFn (*WrenBindForeignMethodFn)(WrenVM* vm, - const char* module, const char* className, bool isStatic, - const char* signature); +typedef WrenForeignMethodFn (*WrenBindForeignMethodFn)(WrenVM* vm, const char* module, const char* className, bool isStatic, const char* signature); // Displays a string of text to the user. typedef void (*WrenWriteFn)(WrenVM* vm, const char* text); -typedef enum -{ - // A syntax or resolution error detected at compile time. - WREN_ERROR_COMPILE, +typedef enum { + // A syntax or resolution error detected at compile time. + WREN_ERROR_COMPILE, - // The error message for a runtime error. - WREN_ERROR_RUNTIME, + // The error message for a runtime error. + WREN_ERROR_RUNTIME, - // One entry of a runtime error's stack trace. - WREN_ERROR_STACK_TRACE + // One entry of a runtime error's stack trace. + WREN_ERROR_STACK_TRACE } WrenErrorType; // Reports an error to the user. @@ -149,174 +142,163 @@ typedef enum // made for each line in the stack trace. Each of those has the resolved // [module] and [line] where the method or function is defined and [message] is // the name of the method or function. -typedef void (*WrenErrorFn)( - WrenVM* vm, WrenErrorType type, const char* module, int line, - const char* message); - -typedef struct -{ - // The callback invoked when the foreign object is created. - // - // This must be provided. Inside the body of this, it must call - // [wrenSetSlotNewForeign()] exactly once. - WrenForeignMethodFn allocate; - - // The callback invoked when the garbage collector is about to collect a - // foreign object's memory. - // - // This may be `NULL` if the foreign class does not need to finalize. - WrenFinalizerFn finalize; +typedef void (*WrenErrorFn)(WrenVM* vm, WrenErrorType type, const char* module, int line, const char* message); + +typedef struct { + // The callback invoked when the foreign object is created. + // + // This must be provided. Inside the body of this, it must call + // [wrenSetSlotNewForeign()] exactly once. + WrenForeignMethodFn allocate; + + // The callback invoked when the garbage collector is about to collect a + // foreign object's memory. + // + // This may be `NULL` if the foreign class does not need to finalize. + WrenFinalizerFn finalize; } WrenForeignClassMethods; // Returns a pair of pointers to the foreign methods used to allocate and // finalize the data for instances of [className] in resolved [module]. -typedef WrenForeignClassMethods (*WrenBindForeignClassFn)( - WrenVM* vm, const char* module, const char* className); - -typedef struct -{ - // The callback Wren will use to allocate, reallocate, and deallocate memory. - // - // If `NULL`, defaults to a built-in function that uses `realloc` and `free`. - WrenReallocateFn reallocateFn; - - // The callback Wren uses to resolve a module name. - // - // Some host applications may wish to support "relative" imports, where the - // meaning of an import string depends on the module that contains it. To - // support that without baking any policy into Wren itself, the VM gives the - // host a chance to resolve an import string. - // - // Before an import is loaded, it calls this, passing in the name of the - // module that contains the import and the import string. The host app can - // look at both of those and produce a new "canonical" string that uniquely - // identifies the module. This string is then used as the name of the module - // going forward. It is what is passed to [loadModuleFn], how duplicate - // imports of the same module are detected, and how the module is reported in - // stack traces. - // - // If you leave this function NULL, then the original import string is - // treated as the resolved string. - // - // If an import cannot be resolved by the embedder, it should return NULL and - // Wren will report that as a runtime error. - // - // Wren will take ownership of the string you return and free it for you, so - // it should be allocated using the same allocation function you provide - // above. - WrenResolveModuleFn resolveModuleFn; - - // The callback Wren uses to load a module. - // - // Since Wren does not talk directly to the file system, it relies on the - // embedder to physically locate and read the source code for a module. The - // first time an import appears, Wren will call this and pass in the name of - // the module being imported. The method will return a result, which contains - // the source code for that module. Memory for the source is owned by the - // host application, and can be freed using the onComplete callback. - // - // This will only be called once for any given module name. Wren caches the - // result internally so subsequent imports of the same module will use the - // previous source and not call this. - // - // If a module with the given name could not be found by the embedder, it - // should return NULL and Wren will report that as a runtime error. - WrenLoadModuleFn loadModuleFn; - - // The callback Wren uses to find a foreign method and bind it to a class. - // - // When a foreign method is declared in a class, this will be called with the - // foreign method's module, class, and signature when the class body is - // executed. It should return a pointer to the foreign function that will be - // bound to that method. - // - // If the foreign function could not be found, this should return NULL and - // Wren will report it as runtime error. - WrenBindForeignMethodFn bindForeignMethodFn; - - // The callback Wren uses to find a foreign class and get its foreign methods. - // - // When a foreign class is declared, this will be called with the class's - // module and name when the class body is executed. It should return the - // foreign functions uses to allocate and (optionally) finalize the bytes - // stored in the foreign object when an instance is created. - WrenBindForeignClassFn bindForeignClassFn; - - // The callback Wren uses to display text when `System.print()` or the other - // related functions are called. - // - // If this is `NULL`, Wren discards any printed text. - WrenWriteFn writeFn; - - // The callback Wren uses to report errors. - // - // When an error occurs, this will be called with the module name, line - // number, and an error message. If this is `NULL`, Wren doesn't report any - // errors. - WrenErrorFn errorFn; - - // The number of bytes Wren will allocate before triggering the first garbage - // collection. - // - // If zero, defaults to 10MB. - size_t initialHeapSize; - - // After a collection occurs, the threshold for the next collection is - // determined based on the number of bytes remaining in use. This allows Wren - // to shrink its memory usage automatically after reclaiming a large amount - // of memory. - // - // This can be used to ensure that the heap does not get too small, which can - // in turn lead to a large number of collections afterwards as the heap grows - // back to a usable size. - // - // If zero, defaults to 1MB. - size_t minHeapSize; - - // Wren will resize the heap automatically as the number of bytes - // remaining in use after a collection changes. This number determines the - // amount of additional memory Wren will use after a collection, as a - // percentage of the current heap size. - // - // For example, say that this is 50. After a garbage collection, when there - // are 400 bytes of memory still in use, the next collection will be triggered - // after a total of 600 bytes are allocated (including the 400 already in - // use.) - // - // Setting this to a smaller number wastes less memory, but triggers more - // frequent garbage collections. - // - // If zero, defaults to 50. - int heapGrowthPercent; - - // User-defined data associated with the VM. - void* userData; +typedef WrenForeignClassMethods (*WrenBindForeignClassFn)(WrenVM* vm, const char* module, const char* className); + +typedef struct { + // The callback Wren will use to allocate, reallocate, and deallocate memory. + // + // If `NULL`, defaults to a built-in function that uses `realloc` and `free`. + WrenReallocateFn reallocateFn; + + // The callback Wren uses to resolve a module name. + // + // Some host applications may wish to support "relative" imports, where the + // meaning of an import string depends on the module that contains it. To + // support that without baking any policy into Wren itself, the VM gives the + // host a chance to resolve an import string. + // + // Before an import is loaded, it calls this, passing in the name of the + // module that contains the import and the import string. The host app can + // look at both of those and produce a new "canonical" string that uniquely + // identifies the module. This string is then used as the name of the module + // going forward. It is what is passed to [loadModuleFn], how duplicate + // imports of the same module are detected, and how the module is reported in + // stack traces. + // + // If you leave this function NULL, then the original import string is + // treated as the resolved string. + // + // If an import cannot be resolved by the embedder, it should return NULL and + // Wren will report that as a runtime error. + // + // Wren will take ownership of the string you return and free it for you, so + // it should be allocated using the same allocation function you provide + // above. + WrenResolveModuleFn resolveModuleFn; + + // The callback Wren uses to load a module. + // + // Since Wren does not talk directly to the file system, it relies on the + // embedder to physically locate and read the source code for a module. The + // first time an import appears, Wren will call this and pass in the name of + // the module being imported. The method will return a result, which contains + // the source code for that module. Memory for the source is owned by the + // host application, and can be freed using the onComplete callback. + // + // This will only be called once for any given module name. Wren caches the + // result internally so subsequent imports of the same module will use the + // previous source and not call this. + // + // If a module with the given name could not be found by the embedder, it + // should return NULL and Wren will report that as a runtime error. + WrenLoadModuleFn loadModuleFn; + + // The callback Wren uses to find a foreign method and bind it to a class. + // + // When a foreign method is declared in a class, this will be called with the + // foreign method's module, class, and signature when the class body is + // executed. It should return a pointer to the foreign function that will be + // bound to that method. + // + // If the foreign function could not be found, this should return NULL and + // Wren will report it as runtime error. + WrenBindForeignMethodFn bindForeignMethodFn; + + // The callback Wren uses to find a foreign class and get its foreign methods. + // + // When a foreign class is declared, this will be called with the class's + // module and name when the class body is executed. It should return the + // foreign functions uses to allocate and (optionally) finalize the bytes + // stored in the foreign object when an instance is created. + WrenBindForeignClassFn bindForeignClassFn; + + // The callback Wren uses to display text when `System.print()` or the other + // related functions are called. + // + // If this is `NULL`, Wren discards any printed text. + WrenWriteFn writeFn; + + // The callback Wren uses to report errors. + // + // When an error occurs, this will be called with the module name, line + // number, and an error message. If this is `NULL`, Wren doesn't report any + // errors. + WrenErrorFn errorFn; + + // The number of bytes Wren will allocate before triggering the first garbage + // collection. + // + // If zero, defaults to 10MB. + size_t initialHeapSize; + + // After a collection occurs, the threshold for the next collection is + // determined based on the number of bytes remaining in use. This allows Wren + // to shrink its memory usage automatically after reclaiming a large amount + // of memory. + // + // This can be used to ensure that the heap does not get too small, which can + // in turn lead to a large number of collections afterwards as the heap grows + // back to a usable size. + // + // If zero, defaults to 1MB. + size_t minHeapSize; + + // Wren will resize the heap automatically as the number of bytes + // remaining in use after a collection changes. This number determines the + // amount of additional memory Wren will use after a collection, as a + // percentage of the current heap size. + // + // For example, say that this is 50. After a garbage collection, when there + // are 400 bytes of memory still in use, the next collection will be triggered + // after a total of 600 bytes are allocated (including the 400 already in + // use.) + // + // Setting this to a smaller number wastes less memory, but triggers more + // frequent garbage collections. + // + // If zero, defaults to 50. + int heapGrowthPercent; + + // User-defined data associated with the VM. + void* userData; } WrenConfiguration; -typedef enum -{ - WREN_RESULT_SUCCESS, - WREN_RESULT_COMPILE_ERROR, - WREN_RESULT_RUNTIME_ERROR -} WrenInterpretResult; +typedef enum { WREN_RESULT_SUCCESS, WREN_RESULT_COMPILE_ERROR, WREN_RESULT_RUNTIME_ERROR } WrenInterpretResult; // The type of an object stored in a slot. // // This is not necessarily the object's *class*, but instead its low level // representation type. -typedef enum -{ - WREN_TYPE_BOOL, - WREN_TYPE_NUM, - WREN_TYPE_FOREIGN, - WREN_TYPE_LIST, - WREN_TYPE_MAP, - WREN_TYPE_NULL, - WREN_TYPE_STRING, - - // The object is of a type that isn't accessible by the C API. - WREN_TYPE_UNKNOWN +typedef enum { + WREN_TYPE_BOOL, + WREN_TYPE_NUM, + WREN_TYPE_FOREIGN, + WREN_TYPE_LIST, + WREN_TYPE_MAP, + WREN_TYPE_NULL, + WREN_TYPE_STRING, + + // The object is of a type that isn't accessible by the C API. + WREN_TYPE_UNKNOWN } WrenType; // Get the current wren version number. @@ -344,8 +326,7 @@ WREN_API void wrenCollectGarbage(WrenVM* vm); // Runs [source], a string of Wren source code in a new fiber in [vm] in the // context of resolved [module]. -WREN_API WrenInterpretResult wrenInterpret(WrenVM* vm, const char* module, - const char* source); +WREN_API WrenInterpretResult wrenInterpret(WrenVM* vm, const char* module, const char* source); // Creates a handle that can be used to invoke a method with [signature] on // using a receiver and arguments that are set up on the stack. @@ -520,8 +501,8 @@ WREN_API int wrenGetListCount(WrenVM* vm, int slot); // [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]. +// 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 @@ -548,16 +529,14 @@ WREN_API void wrenSetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlo // 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); +WREN_API void wrenRemoveMapValue(WrenVM* vm, int mapSlot, int keySlot, int removedValueSlot); // Looks up the top level variable with [name] in resolved [module] and stores // it in [slot]. -WREN_API void wrenGetVariable(WrenVM* vm, const char* module, const char* name, - int slot); +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, +// 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); @@ -616,7 +595,7 @@ WREN_API void wrenSetUserData(WrenVM* vm, void* userData); // // Defaults to on. #ifndef WREN_NAN_TAGGING - #define WREN_NAN_TAGGING 1 +#define WREN_NAN_TAGGING 1 #endif // If true, the VM's interpreter loop uses computed gotos. See this for more: @@ -626,23 +605,23 @@ WREN_API void wrenSetUserData(WrenVM* vm, void* userData); // see https://bullno1.com/blog/switched-goto for alternative // Defaults to true on supported compilers. #ifndef WREN_COMPUTED_GOTO - #if defined(_MSC_VER) && !defined(__clang__) - // No computed gotos in Visual Studio. - #define WREN_COMPUTED_GOTO 0 - #else - #define WREN_COMPUTED_GOTO 1 - #endif +#if defined(_MSC_VER) && !defined(__clang__) +// No computed gotos in Visual Studio. +#define WREN_COMPUTED_GOTO 0 +#else +#define WREN_COMPUTED_GOTO 1 +#endif #endif // The VM includes a number of optional modules. You can choose to include // these or not. By default, they are all available. To disable one, set the // corresponding `WREN_OPT_` define to `0`. #ifndef WREN_OPT_META - #define WREN_OPT_META 1 +#define WREN_OPT_META 1 #endif #ifndef WREN_OPT_RANDOM - #define WREN_OPT_RANDOM 1 +#define WREN_OPT_RANDOM 1 #endif // These flags are useful for debugging and hacking on Wren itself. They are not @@ -704,37 +683,33 @@ WREN_API void wrenSetUserData(WrenVM* vm, void* userData); #define MAX_FIELDS 255 // Use the VM's allocator to allocate an object of [type]. -#define ALLOCATE(vm, type) \ - ((type*)wrenReallocate(vm, NULL, 0, sizeof(type))) +#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) \ - ((type*)wrenReallocate(vm, NULL, 0, sizeof(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]. #define DEALLOCATE(vm, pointer) wrenReallocate(vm, pointer, 0, 0) // The Microsoft compiler does not support the "inline" modifier when compiling // as plain C. -#if defined( _MSC_VER ) && !defined(__cplusplus) - #define inline _inline +#if defined(_MSC_VER) && !defined(__cplusplus) +#define inline _inline #endif // This is used to clearly mark flexible-sized arrays that appear at the end of // some dynamically-allocated structs, known as the "struct hack". #if __STDC_VERSION__ >= 199901L - // In C99, a flexible array member is just "[]". - #define FLEXIBLE_ARRAY +// In C99, a flexible array member is just "[]". +#define FLEXIBLE_ARRAY #else - // Elsewhere, use a zero-sized array. It's technically undefined behavior, - // but works reliably in most known compilers. - #define FLEXIBLE_ARRAY 0 +// Elsewhere, use a zero-sized array. It's technically undefined behavior, +// but works reliably in most known compilers. +#define FLEXIBLE_ARRAY 0 #endif // Assertions are used to validate program invariants. They indicate things the @@ -744,46 +719,43 @@ WREN_API void wrenSetUserData(WrenVM* vm, void* userData); // Assertions add significant overhead, so are only enabled in debug builds. #ifdef DEBUG - #include - - #define ASSERT(condition, message) \ - do \ - { \ - if (!(condition)) \ - { \ - fprintf(stderr, "[%s:%d] Assert failed in %s(): %s\n", \ - __FILE__, __LINE__, __func__, message); \ - abort(); \ - } \ - } while (false) - - // Indicates that we know execution should never reach this point in the - // program. In debug mode, we assert this fact because it's a bug to get here. - // - // In release mode, we use compiler-specific built in functions to tell the - // compiler the code can't be reached. This avoids "missing return" warnings - // in some cases and also lets it perform some optimizations by assuming the - // code is never reached. - #define UNREACHABLE() \ - do \ - { \ - fprintf(stderr, "[%s:%d] This code should not be reached in %s()\n", \ - __FILE__, __LINE__, __func__); \ - abort(); \ - } while (false) +#include + +#define ASSERT(condition, message) \ + do { \ + if (!(condition)) { \ + fprintf(stderr, "[%s:%d] Assert failed in %s(): %s\n", __FILE__, __LINE__, __func__, message); \ + abort(); \ + } \ + } while (false) + +// Indicates that we know execution should never reach this point in the +// program. In debug mode, we assert this fact because it's a bug to get here. +// +// In release mode, we use compiler-specific built in functions to tell the +// compiler the code can't be reached. This avoids "missing return" warnings +// in some cases and also lets it perform some optimizations by assuming the +// code is never reached. +#define UNREACHABLE() \ + do { \ + fprintf(stderr, "[%s:%d] This code should not be reached in %s()\n", __FILE__, __LINE__, __func__); \ + abort(); \ + } while (false) #else - #define ASSERT(condition, message) do { } while (false) +#define ASSERT(condition, message) \ + do { \ + } while (false) - // Tell the compiler that this part of the code will never be reached. - #if defined( _MSC_VER ) - #define UNREACHABLE() __assume(0) - #elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)) - #define UNREACHABLE() __builtin_unreachable() - #else - #define UNREACHABLE() - #endif +// Tell the compiler that this part of the code will never be reached. +#if defined(_MSC_VER) +#define UNREACHABLE() __assume(0) +#elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)) +#define UNREACHABLE() __builtin_unreachable() +#else +#define UNREACHABLE() +#endif #endif @@ -797,11 +769,10 @@ WREN_API void wrenSetUserData(WrenVM* vm, void* userData); #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; +typedef union { + uint64_t bits64; + uint32_t bits32[2]; + double num; } WrenDoubleBits; #define WREN_DOUBLE_QNAN_POS_MIN_BITS (UINT64_C(0x7FF8000000000000)) @@ -809,18 +780,16 @@ typedef union #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 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; +static inline uint64_t wrenDoubleToBits(double num) { + WrenDoubleBits data; + data.num = num; + return data.bits64; } #endif @@ -829,7 +798,6 @@ static inline uint64_t wrenDoubleToBits(double num) #ifndef wren_utils_h #define wren_utils_h - // Reusable data structures and other utility functions. // Forward declare this here to break a cycle between wren_utils.h and @@ -839,55 +807,45 @@ 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) +#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); \ - } +#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); @@ -904,17 +862,14 @@ void wrenSymbolTableInit(SymbolTable* symbols); 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); +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); +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); +int wrenSymbolTableFind(const SymbolTable* symbols, const char* name, size_t length); void wrenBlackenSymbolTable(WrenVM* vm, SymbolTable* symbolTable); @@ -990,40 +945,40 @@ uint32_t wrenValidateIndex(uint32_t count, int64_t value); // 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* +#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___* +#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 +#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 @@ -1031,35 +986,21 @@ uint32_t wrenValidateIndex(uint32_t count, int64_t value); #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 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; +struct sObj { + ObjType type; + bool isDark; - // The object's class. - ObjClass* classObj; + // The object's class. + ObjClass* classObj; - // The next object in the linked list of all currently allocated objects. - struct sObj* next; + // The next object in the linked list of all currently allocated objects. + struct sObj* next; }; #if WREN_NAN_TAGGING @@ -1068,24 +1009,14 @@ 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; +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 @@ -1093,18 +1024,17 @@ typedef struct DECLARE_BUFFER(Value, Value); // A heap-allocated string object. -struct sObjString -{ - Obj obj; +struct sObjString { + Obj obj; - // Number of bytes in the string, not including the null terminator. - uint32_t length; + // 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; + // 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]; + // 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 @@ -1119,23 +1049,22 @@ struct sObjString // 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; +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. @@ -1150,34 +1079,32 @@ typedef bool (*Primitive)(WrenVM* vm, Value* args); // 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; +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; +typedef struct { + Obj obj; - // The currently defined top-level variables. - ValueBuffer variables; + // 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; + // 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; + // The name of the module. + ObjString* name; } ObjModule; // A function object. It wraps and owns the bytecode and other debug information @@ -1188,206 +1115,193 @@ typedef struct // 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; +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; +typedef struct { + Obj obj; - // The function that this closure is an instance of. - ObjFn* fn; + // The function that this closure is an instance of. + ObjFn* fn; - // The upvalues this function has closed over. - ObjUpvalue* upvalues[FLEXIBLE_ARRAY]; + // 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; +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, +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; +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, +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 primitive that handles .call on Fn. - METHOD_FUNCTION_CALL, + // A externally-defined C method. + METHOD_FOREIGN, - // A externally-defined C method. - METHOD_FOREIGN, + // A normal user-defined method. + METHOD_BLOCK, - // A normal user-defined method. - METHOD_BLOCK, - - // No method for the given symbol. - METHOD_NONE + // 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; +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; +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]; +typedef struct { + Obj obj; + uint8_t data[FLEXIBLE_ARRAY]; } ObjForeign; -typedef struct -{ - Obj obj; - Value fields[FLEXIBLE_ARRAY]; +typedef struct { + Obj obj; + Value fields[FLEXIBLE_ARRAY]; } ObjInstance; -typedef struct -{ - Obj obj; +typedef struct { + Obj obj; - // The elements in the list. - ValueBuffer elements; + // 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; +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; + // 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. @@ -1407,32 +1321,30 @@ typedef struct // 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; +typedef struct { + Obj obj; - // The number of entries allocated. - uint32_t capacity; + // The number of entries allocated. + uint32_t capacity; - // The number of entries in the map. - uint32_t count; + // The number of entries in the map. + uint32_t count; - // Pointer to a contiguous array of [capacity] entries. - MapEntry* entries; + // Pointer to a contiguous array of [capacity] entries. + MapEntry* entries; } ObjMap; -typedef struct -{ - Obj obj; +typedef struct { + Obj obj; - // The beginning of the range. - double from; + // The beginning of the range. + double from; - // The end of the range. May be greater or less than [from]. - double to; + // The end of the range. May be greater or less than [from]. + double to; - // True if [to] is included in the range. - bool isInclusive; + // 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: @@ -1505,22 +1417,22 @@ typedef struct // 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_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_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) +#define TAG_UNUSED2 (5) +#define TAG_UNUSED3 (6) +#define TAG_UNUSED4 (7) // Value -> 0 or 1. #define AS_BOOL(value) ((value) == TRUE_VAL) @@ -1529,9 +1441,9 @@ typedef struct #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 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). @@ -1548,16 +1460,16 @@ typedef struct // 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_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 } }) +#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 @@ -1572,8 +1484,7 @@ ObjClass* wrenNewSingleClass(WrenVM* vm, int numFields, ObjString* name); 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); +ObjClass* wrenNewClass(WrenVM* vm, ObjClass* superclass, int numFields, ObjString* name); void wrenBindMethod(WrenVM* vm, ObjClass* classObj, int symbol, Method method); @@ -1586,24 +1497,21 @@ 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; +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); +static inline bool wrenHasError(const ObjFiber* fiber) { + return !IS_NULL(fiber->error); } ObjForeign* wrenNewForeign(WrenVM* vm, ObjClass* classObj, size_t size); @@ -1670,8 +1578,7 @@ 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); +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); @@ -1701,14 +1608,11 @@ 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); +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; +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. @@ -1744,16 +1648,17 @@ 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) -{ +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; + // 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; + 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 } @@ -1764,71 +1669,60 @@ 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) -{ +static inline bool wrenIsBool(Value value) { #if WREN_NAN_TAGGING - return value == TRUE_VAL || value == FALSE_VAL; + return value == TRUE_VAL || value == FALSE_VAL; #else - return value.type == VAL_FALSE || value.type == VAL_TRUE; + 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; +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) -{ +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)); + // 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; + Value value; + value.type = VAL_OBJ; + value.as.obj = obj; + return value; #endif } // Interprets [value] as a [double]. -static inline double wrenValueToNum(Value value) -{ +static inline double wrenValueToNum(Value value) { #if WREN_NAN_TAGGING - return wrenDoubleFromBits(value); + return wrenDoubleFromBits(value); #else - return value.as.num; + return value.as.num; #endif } // Converts [num] to a [Value]. -static inline Value wrenNumToValue(double num) -{ +static inline Value wrenNumToValue(double num) { #if WREN_NAN_TAGGING - return wrenDoubleToBits(num); + return wrenDoubleToBits(num); #else - Value value; - value.type = VAL_NUM; - value.as.num = num; - return value; + 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); +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 @@ -1841,7 +1735,6 @@ static inline bool wrenMapIsValidKey(Value arg) #ifndef wren_compiler_h #define wren_compiler_h - typedef struct sCompiler Compiler; // This module defines the compiler for Wren. It takes a string of source code @@ -1870,8 +1763,7 @@ typedef struct sCompiler Compiler; // // 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); +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 @@ -1899,325 +1791,285 @@ void wrenMarkCompiler(WrenVM* vm, Compiler* compiler); // at one time. #define WREN_MAX_TEMP_ROOTS 8 -typedef enum -{ - #define OPCODE(name, _) CODE_##name, -// Begin file "wren_opcodes.h" -// This defines the bytecode instructions used by the VM. It does so by invoking -// an OPCODE() macro which is expected to be defined at the point that this is -// included. (See: http://en.wikipedia.org/wiki/X_Macro for more.) -// -// The first argument is the name of the opcode. The second is its "stack -// effect" -- the amount that the op code changes the size of the stack. A -// stack effect of 1 means it pushes a value and the stack grows one larger. -// -2 means it pops two values, etc. -// -// Note that the order of instructions here affects the order of the dispatch -// table in the VM's interpreter loop. That in turn affects caching which -// affects overall performance. Take care to run benchmarks if you change the -// order here. - -// Load the constant at index [arg]. -OPCODE(CONSTANT, 1) - -// Push null onto the stack. -OPCODE(NULL, 1) - -// Push false onto the stack. -OPCODE(FALSE, 1) - -// Push true onto the stack. -OPCODE(TRUE, 1) - -// Pushes the value in the given local slot. -OPCODE(LOAD_LOCAL_0, 1) -OPCODE(LOAD_LOCAL_1, 1) -OPCODE(LOAD_LOCAL_2, 1) -OPCODE(LOAD_LOCAL_3, 1) -OPCODE(LOAD_LOCAL_4, 1) -OPCODE(LOAD_LOCAL_5, 1) -OPCODE(LOAD_LOCAL_6, 1) -OPCODE(LOAD_LOCAL_7, 1) -OPCODE(LOAD_LOCAL_8, 1) - -// Note: The compiler assumes the following _STORE instructions always -// immediately follow their corresponding _LOAD ones. - -// Pushes the value in local slot [arg]. -OPCODE(LOAD_LOCAL, 1) - -// Stores the top of stack in local slot [arg]. Does not pop it. -OPCODE(STORE_LOCAL, 0) - -// Pushes the value in upvalue [arg]. -OPCODE(LOAD_UPVALUE, 1) - -// Stores the top of stack in upvalue [arg]. Does not pop it. -OPCODE(STORE_UPVALUE, 0) - -// Pushes the value of the top-level variable in slot [arg]. -OPCODE(LOAD_MODULE_VAR, 1) - -// Stores the top of stack in top-level variable slot [arg]. Does not pop it. -OPCODE(STORE_MODULE_VAR, 0) - -// Pushes the value of the field in slot [arg] of the receiver of the current -// function. This is used for regular field accesses on "this" directly in -// methods. This instruction is faster than the more general CODE_LOAD_FIELD -// instruction. -OPCODE(LOAD_FIELD_THIS, 1) - -// Stores the top of the stack in field slot [arg] in the receiver of the -// current value. Does not pop the value. This instruction is faster than the -// more general CODE_LOAD_FIELD instruction. -OPCODE(STORE_FIELD_THIS, 0) - -// Pops an instance and pushes the value of the field in slot [arg] of it. -OPCODE(LOAD_FIELD, 0) - -// Pops an instance and stores the subsequent top of stack in field slot -// [arg] in it. Does not pop the value. -OPCODE(STORE_FIELD, -1) - -// Pop and discard the top of stack. -OPCODE(POP, -1) - -// Invoke the method with symbol [arg]. The number indicates the number of -// arguments (not including the receiver). -OPCODE(CALL_0, 0) -OPCODE(CALL_1, -1) -OPCODE(CALL_2, -2) -OPCODE(CALL_3, -3) -OPCODE(CALL_4, -4) -OPCODE(CALL_5, -5) -OPCODE(CALL_6, -6) -OPCODE(CALL_7, -7) -OPCODE(CALL_8, -8) -OPCODE(CALL_9, -9) -OPCODE(CALL_10, -10) -OPCODE(CALL_11, -11) -OPCODE(CALL_12, -12) -OPCODE(CALL_13, -13) -OPCODE(CALL_14, -14) -OPCODE(CALL_15, -15) -OPCODE(CALL_16, -16) - -// Invoke a superclass method with symbol [arg]. The number indicates the -// number of arguments (not including the receiver). -OPCODE(SUPER_0, 0) -OPCODE(SUPER_1, -1) -OPCODE(SUPER_2, -2) -OPCODE(SUPER_3, -3) -OPCODE(SUPER_4, -4) -OPCODE(SUPER_5, -5) -OPCODE(SUPER_6, -6) -OPCODE(SUPER_7, -7) -OPCODE(SUPER_8, -8) -OPCODE(SUPER_9, -9) -OPCODE(SUPER_10, -10) -OPCODE(SUPER_11, -11) -OPCODE(SUPER_12, -12) -OPCODE(SUPER_13, -13) -OPCODE(SUPER_14, -14) -OPCODE(SUPER_15, -15) -OPCODE(SUPER_16, -16) - -// Jump the instruction pointer [arg] forward. -OPCODE(JUMP, 0) - -// Jump the instruction pointer [arg] backward. -OPCODE(LOOP, 0) - -// Pop and if not truthy then jump the instruction pointer [arg] forward. -OPCODE(JUMP_IF, -1) - -// If the top of the stack is false, jump [arg] forward. Otherwise, pop and -// continue. -OPCODE(AND, -1) - -// If the top of the stack is non-false, jump [arg] forward. Otherwise, pop -// and continue. -OPCODE(OR, -1) - -// Close the upvalue for the local on the top of the stack, then pop it. -OPCODE(CLOSE_UPVALUE, -1) - -// Exit from the current function and return the value on the top of the -// stack. -OPCODE(RETURN, 0) - -// Creates a closure for the function stored at [arg] in the constant table. -// -// Following the function argument is a number of arguments, two for each -// upvalue. The first is true if the variable being captured is a local (as -// opposed to an upvalue), and the second is the index of the local or -// upvalue being captured. -// -// Pushes the created closure. -OPCODE(CLOSURE, 1) - -// Creates a new instance of a class. -// -// Assumes the class object is in slot zero, and replaces it with the new -// uninitialized instance of that class. This opcode is only emitted by the -// compiler-generated constructor metaclass methods. -OPCODE(CONSTRUCT, 0) - -// Creates a new instance of a foreign class. -// -// Assumes the class object is in slot zero, and replaces it with the new -// uninitialized instance of that class. This opcode is only emitted by the -// compiler-generated constructor metaclass methods. -OPCODE(FOREIGN_CONSTRUCT, 0) - -// Creates a class. Top of stack is the superclass. Below that is a string for -// the name of the class. Byte [arg] is the number of fields in the class. -OPCODE(CLASS, -1) - -// Ends a class. -// Atm the stack contains the class and the ClassAttributes (or null). -OPCODE(END_CLASS, -2) - -// Creates a foreign class. Top of stack is the superclass. Below that is a -// string for the name of the class. -OPCODE(FOREIGN_CLASS, -1) - -// Define a method for symbol [arg]. The class receiving the method is popped -// off the stack, then the function defining the body is popped. -// -// If a foreign method is being defined, the "function" will be a string -// identifying the foreign method. Otherwise, it will be a function or -// closure. -OPCODE(METHOD_INSTANCE, -2) - -// Define a method for symbol [arg]. The class whose metaclass will receive -// the method is popped off the stack, then the function defining the body is -// popped. -// -// If a foreign method is being defined, the "function" will be a string -// identifying the foreign method. Otherwise, it will be a function or -// closure. -OPCODE(METHOD_STATIC, -2) - -// This is executed at the end of the module's body. Pushes NULL onto the stack -// as the "return value" of the import statement and stores the module as the -// most recently imported one. -OPCODE(END_MODULE, 1) - -// Import a module whose name is the string stored at [arg] in the constant -// table. -// -// Pushes null onto the stack so that the fiber for the imported module can -// replace that with a dummy value when it returns. (Fibers always return a -// value when resuming a caller.) -OPCODE(IMPORT_MODULE, 1) - -// Import a variable from the most recently imported module. The name of the -// variable to import is at [arg] in the constant table. Pushes the loaded -// variable's value. -OPCODE(IMPORT_VARIABLE, 1) - -// This pseudo-instruction indicates the end of the bytecode. It should -// always be preceded by a `CODE_RETURN`, so is never actually executed. -OPCODE(END, 0) +typedef enum { +#define OPCODE(name, _) CODE_##name, + // Begin file "wren_opcodes.h" + // This defines the bytecode instructions used by the VM. It does so by invoking + // an OPCODE() macro which is expected to be defined at the point that this is + // included. (See: http://en.wikipedia.org/wiki/X_Macro for more.) + // + // The first argument is the name of the opcode. The second is its "stack + // effect" -- the amount that the op code changes the size of the stack. A + // stack effect of 1 means it pushes a value and the stack grows one larger. + // -2 means it pops two values, etc. + // + // Note that the order of instructions here affects the order of the dispatch + // table in the VM's interpreter loop. That in turn affects caching which + // affects overall performance. Take care to run benchmarks if you change the + // order here. + + // Load the constant at index [arg]. + OPCODE(CONSTANT, 1) + + // Push null onto the stack. + OPCODE(NULL, 1) + + // Push false onto the stack. + OPCODE(FALSE, 1) + + // Push true onto the stack. + OPCODE(TRUE, 1) + + // Pushes the value in the given local slot. + OPCODE(LOAD_LOCAL_0, 1) OPCODE(LOAD_LOCAL_1, 1) OPCODE(LOAD_LOCAL_2, 1) OPCODE(LOAD_LOCAL_3, 1) OPCODE(LOAD_LOCAL_4, 1) OPCODE(LOAD_LOCAL_5, 1) OPCODE(LOAD_LOCAL_6, 1) OPCODE(LOAD_LOCAL_7, 1) + OPCODE(LOAD_LOCAL_8, 1) + + // Note: The compiler assumes the following _STORE instructions always + // immediately follow their corresponding _LOAD ones. + + // Pushes the value in local slot [arg]. + OPCODE(LOAD_LOCAL, 1) + + // Stores the top of stack in local slot [arg]. Does not pop it. + OPCODE(STORE_LOCAL, 0) + + // Pushes the value in upvalue [arg]. + OPCODE(LOAD_UPVALUE, 1) + + // Stores the top of stack in upvalue [arg]. Does not pop it. + OPCODE(STORE_UPVALUE, 0) + + // Pushes the value of the top-level variable in slot [arg]. + OPCODE(LOAD_MODULE_VAR, 1) + + // Stores the top of stack in top-level variable slot [arg]. Does not pop it. + OPCODE(STORE_MODULE_VAR, 0) + + // Pushes the value of the field in slot [arg] of the receiver of the current + // function. This is used for regular field accesses on "this" directly in + // methods. This instruction is faster than the more general CODE_LOAD_FIELD + // instruction. + OPCODE(LOAD_FIELD_THIS, 1) + + // Stores the top of the stack in field slot [arg] in the receiver of the + // current value. Does not pop the value. This instruction is faster than the + // more general CODE_LOAD_FIELD instruction. + OPCODE(STORE_FIELD_THIS, 0) + + // Pops an instance and pushes the value of the field in slot [arg] of it. + OPCODE(LOAD_FIELD, 0) + + // Pops an instance and stores the subsequent top of stack in field slot + // [arg] in it. Does not pop the value. + OPCODE(STORE_FIELD, -1) + + // Pop and discard the top of stack. + OPCODE(POP, -1) + + // Invoke the method with symbol [arg]. The number indicates the number of + // arguments (not including the receiver). + OPCODE(CALL_0, 0) OPCODE(CALL_1, -1) OPCODE(CALL_2, -2) OPCODE(CALL_3, -3) OPCODE(CALL_4, -4) OPCODE(CALL_5, -5) OPCODE(CALL_6, -6) OPCODE(CALL_7, -7) OPCODE(CALL_8, -8) OPCODE(CALL_9, -9) + OPCODE(CALL_10, -10) OPCODE(CALL_11, -11) OPCODE(CALL_12, -12) OPCODE(CALL_13, -13) OPCODE(CALL_14, -14) OPCODE(CALL_15, -15) OPCODE(CALL_16, -16) + + // Invoke a superclass method with symbol [arg]. The number indicates the + // number of arguments (not including the receiver). + OPCODE(SUPER_0, 0) OPCODE(SUPER_1, -1) OPCODE(SUPER_2, -2) OPCODE(SUPER_3, -3) OPCODE(SUPER_4, -4) OPCODE(SUPER_5, -5) OPCODE(SUPER_6, -6) OPCODE(SUPER_7, -7) OPCODE(SUPER_8, -8) + OPCODE(SUPER_9, -9) OPCODE(SUPER_10, -10) OPCODE(SUPER_11, -11) OPCODE(SUPER_12, -12) OPCODE(SUPER_13, -13) OPCODE(SUPER_14, -14) OPCODE(SUPER_15, -15) OPCODE(SUPER_16, -16) + + // Jump the instruction pointer [arg] forward. + OPCODE(JUMP, 0) + + // Jump the instruction pointer [arg] backward. + OPCODE(LOOP, 0) + + // Pop and if not truthy then jump the instruction pointer [arg] forward. + OPCODE(JUMP_IF, -1) + + // If the top of the stack is false, jump [arg] forward. Otherwise, pop and + // continue. + OPCODE(AND, -1) + + // If the top of the stack is non-false, jump [arg] forward. Otherwise, pop + // and continue. + OPCODE(OR, -1) + + // Close the upvalue for the local on the top of the stack, then pop it. + OPCODE(CLOSE_UPVALUE, -1) + + // Exit from the current function and return the value on the top of the + // stack. + OPCODE(RETURN, 0) + + // Creates a closure for the function stored at [arg] in the constant table. + // + // Following the function argument is a number of arguments, two for each + // upvalue. The first is true if the variable being captured is a local (as + // opposed to an upvalue), and the second is the index of the local or + // upvalue being captured. + // + // Pushes the created closure. + OPCODE(CLOSURE, 1) + + // Creates a new instance of a class. + // + // Assumes the class object is in slot zero, and replaces it with the new + // uninitialized instance of that class. This opcode is only emitted by the + // compiler-generated constructor metaclass methods. + OPCODE(CONSTRUCT, 0) + + // Creates a new instance of a foreign class. + // + // Assumes the class object is in slot zero, and replaces it with the new + // uninitialized instance of that class. This opcode is only emitted by the + // compiler-generated constructor metaclass methods. + OPCODE(FOREIGN_CONSTRUCT, 0) + + // Creates a class. Top of stack is the superclass. Below that is a string for + // the name of the class. Byte [arg] is the number of fields in the class. + OPCODE(CLASS, -1) + + // Ends a class. + // Atm the stack contains the class and the ClassAttributes (or null). + OPCODE(END_CLASS, -2) + + // Creates a foreign class. Top of stack is the superclass. Below that is a + // string for the name of the class. + OPCODE(FOREIGN_CLASS, -1) + + // Define a method for symbol [arg]. The class receiving the method is popped + // off the stack, then the function defining the body is popped. + // + // If a foreign method is being defined, the "function" will be a string + // identifying the foreign method. Otherwise, it will be a function or + // closure. + OPCODE(METHOD_INSTANCE, -2) + + // Define a method for symbol [arg]. The class whose metaclass will receive + // the method is popped off the stack, then the function defining the body is + // popped. + // + // If a foreign method is being defined, the "function" will be a string + // identifying the foreign method. Otherwise, it will be a function or + // closure. + OPCODE(METHOD_STATIC, -2) + + // This is executed at the end of the module's body. Pushes NULL onto the stack + // as the "return value" of the import statement and stores the module as the + // most recently imported one. + OPCODE(END_MODULE, 1) + + // Import a module whose name is the string stored at [arg] in the constant + // table. + // + // Pushes null onto the stack so that the fiber for the imported module can + // replace that with a dummy value when it returns. (Fibers always return a + // value when resuming a caller.) + OPCODE(IMPORT_MODULE, 1) + + // Import a variable from the most recently imported module. The name of the + // variable to import is at [arg] in the constant table. Pushes the loaded + // variable's value. + OPCODE(IMPORT_VARIABLE, 1) + + // This pseudo-instruction indicates the end of the bytecode. It should + // always be preceded by a `CODE_RETURN`, so is never actually executed. + OPCODE(END, 0) // End file "wren_opcodes.h" - #undef OPCODE +#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; +struct WrenHandle { + Value value; - WrenHandle* prev; - WrenHandle* next; + 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; +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. @@ -2248,9 +2100,7 @@ WrenHandle* wrenMakeHandle(WrenVM* vm, Value value); // execute it. // // Returns NULL if a compile error occurred. -ObjClosure* wrenCompileSource(WrenVM* vm, const char* module, - const char* source, bool isExpression, - bool printErrors); +ObjClosure* wrenCompileSource(WrenVM* vm, const char* module, const char* source, bool isExpression, bool printErrors); // Looks up a variable from a previously-loaded module. // @@ -2267,8 +2117,7 @@ Value wrenFindVariable(WrenVM* vm, ObjModule* module, const char* name); // 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); +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). @@ -2277,29 +2126,24 @@ int wrenDeclareVariable(WrenVM* vm, ObjModule* module, const char* 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); +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); +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. @@ -2313,46 +2157,58 @@ void wrenPopRoot(WrenVM* vm); // 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; +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(); - } + 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(); - } + 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; + 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 wrenIsLocalName(const char* name) { + return name[0] >= 'a' && name[0] <= 'z'; } -static inline bool wrenIsFalsyValue(Value value) -{ - return IS_FALSE(value) || IS_NULL(value); +static inline bool wrenIsFalsyValue(Value value) { + return IS_FALSE(value) || IS_NULL(value); } #endif @@ -2383,597 +2239,620 @@ void wrenDumpStack(ObjFiber* fiber); // Begin file "wren_debug.c" #include - -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) -{ +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(); - } - } + 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(); - } + 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"); +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"); } // End file "wren_debug.c" // Begin file "wren_utils.c" #include - 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; +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; +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; +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; + // Check bounds. + if (value >= 0 && value < count) + return (uint32_t)value; - return UINT32_MAX; + return UINT32_MAX; } // End file "wren_utils.c" // Begin file "wren_primitive.c" @@ -2981,66 +2860,57 @@ uint32_t wrenValidateIndex(uint32_t count, int64_t value) #ifndef wren_primitive_h #define wren_primitive_h - // Binds a primitive method named [name] (in Wren) implemented using C function // [fn] to `ObjClass` [cls]. -#define PRIMITIVE(cls, name, function) \ - do \ - { \ - int symbol = wrenSymbolTableEnsure(vm, \ - &vm->methodNames, name, strlen(name)); \ - Method method; \ - method.type = METHOD_PRIMITIVE; \ - method.as.primitive = prim_##function; \ - wrenBindMethod(vm, cls, symbol, method); \ - } while (false) +#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) +#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) +#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. @@ -3066,8 +2936,7 @@ bool validateKey(WrenVM* vm, Value arg); // 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); +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. @@ -3082,8 +2951,7 @@ bool validateString(WrenVM* vm, Value arg, const char* argName); // 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); +uint32_t calculateRange(WrenVM* vm, ObjRange* range, uint32_t* length, int* step); #endif // End file "wren_primitive.h" @@ -3093,117 +2961,113 @@ uint32_t calculateRange(WrenVM* vm, ObjRange* range, uint32_t* length, // 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; - } +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; + uint32_t to = (uint32_t)value; + *length = abs((int)(from - to)) + 1; + *step = from < to ? 1 : -1; + return from; } // End file "wren_primitive.c" // Begin file "wren_core.c" @@ -3218,7 +3082,6 @@ uint32_t calculateRange(WrenVM* vm, ObjRange* range, uint32_t* length, #ifndef wren_core_h #define wren_core_h - // This module defines the built-in classes and their primitives methods that // are implemented directly in C code. Some languages try to implement as much // of the core module itself in the primary language instead of in the host @@ -3241,553 +3104,542 @@ void wrenInitializeCore(WrenVM* vm); // Begin file "wren_core.wren.inc" // Generated automatically from src/vm/wren_core.wren. Do not edit. -static const char* coreModuleSource = -"class Bool {}\n" -"class Fiber {}\n" -"class Fn {}\n" -"class Null {}\n" -"class Num {}\n" -"\n" -"class Sequence {\n" -" all(f) {\n" -" var result = true\n" -" for (element in this) {\n" -" result = f.call(element)\n" -" if (!result) return result\n" -" }\n" -" return result\n" -" }\n" -"\n" -" any(f) {\n" -" var result = false\n" -" for (element in this) {\n" -" result = f.call(element)\n" -" if (result) return result\n" -" }\n" -" return result\n" -" }\n" -"\n" -" contains(element) {\n" -" for (item in this) {\n" -" if (element == item) return true\n" -" }\n" -" return false\n" -" }\n" -"\n" -" count {\n" -" var result = 0\n" -" for (element in this) {\n" -" result = result + 1\n" -" }\n" -" return result\n" -" }\n" -"\n" -" count(f) {\n" -" var result = 0\n" -" for (element in this) {\n" -" if (f.call(element)) result = result + 1\n" -" }\n" -" return result\n" -" }\n" -"\n" -" each(f) {\n" -" for (element in this) {\n" -" f.call(element)\n" -" }\n" -" }\n" -"\n" -" isEmpty { iterate(null) ? false : true }\n" -"\n" -" map(transformation) { MapSequence.new(this, transformation) }\n" -"\n" -" skip(count) {\n" -" if (!(count is Num) || !count.isInteger || count < 0) {\n" -" Fiber.abort(\"Count must be a non-negative integer.\")\n" -" }\n" -"\n" -" return SkipSequence.new(this, count)\n" -" }\n" -"\n" -" take(count) {\n" -" if (!(count is Num) || !count.isInteger || count < 0) {\n" -" Fiber.abort(\"Count must be a non-negative integer.\")\n" -" }\n" -"\n" -" return TakeSequence.new(this, count)\n" -" }\n" -"\n" -" where(predicate) { WhereSequence.new(this, predicate) }\n" -"\n" -" reduce(acc, f) {\n" -" for (element in this) {\n" -" acc = f.call(acc, element)\n" -" }\n" -" return acc\n" -" }\n" -"\n" -" reduce(f) {\n" -" var iter = iterate(null)\n" -" if (!iter) Fiber.abort(\"Can't reduce an empty sequence.\")\n" -"\n" -" // Seed with the first element.\n" -" var result = iteratorValue(iter)\n" -" while (iter = iterate(iter)) {\n" -" result = f.call(result, iteratorValue(iter))\n" -" }\n" -"\n" -" return result\n" -" }\n" -"\n" -" join() { join(\"\") }\n" -"\n" -" join(sep) {\n" -" var first = true\n" -" var result = \"\"\n" -"\n" -" for (element in this) {\n" -" if (!first) result = result + sep\n" -" first = false\n" -" result = result + element.toString\n" -" }\n" -"\n" -" return result\n" -" }\n" -"\n" -" toList {\n" -" var result = List.new()\n" -" for (element in this) {\n" -" result.add(element)\n" -" }\n" -" return result\n" -" }\n" -"}\n" -"\n" -"class MapSequence is Sequence {\n" -" construct new(sequence, fn) {\n" -" _sequence = sequence\n" -" _fn = fn\n" -" }\n" -"\n" -" iterate(iterator) { _sequence.iterate(iterator) }\n" -" iteratorValue(iterator) { _fn.call(_sequence.iteratorValue(iterator)) }\n" -"}\n" -"\n" -"class SkipSequence is Sequence {\n" -" construct new(sequence, count) {\n" -" _sequence = sequence\n" -" _count = count\n" -" }\n" -"\n" -" iterate(iterator) {\n" -" if (iterator) {\n" -" return _sequence.iterate(iterator)\n" -" } else {\n" -" iterator = _sequence.iterate(iterator)\n" -" var count = _count\n" -" while (count > 0 && iterator) {\n" -" iterator = _sequence.iterate(iterator)\n" -" count = count - 1\n" -" }\n" -" return iterator\n" -" }\n" -" }\n" -"\n" -" iteratorValue(iterator) { _sequence.iteratorValue(iterator) }\n" -"}\n" -"\n" -"class TakeSequence is Sequence {\n" -" construct new(sequence, count) {\n" -" _sequence = sequence\n" -" _count = count\n" -" }\n" -"\n" -" iterate(iterator) {\n" -" if (!iterator) _taken = 1 else _taken = _taken + 1\n" -" return _taken > _count ? null : _sequence.iterate(iterator)\n" -" }\n" -"\n" -" iteratorValue(iterator) { _sequence.iteratorValue(iterator) }\n" -"}\n" -"\n" -"class WhereSequence is Sequence {\n" -" construct new(sequence, fn) {\n" -" _sequence = sequence\n" -" _fn = fn\n" -" }\n" -"\n" -" iterate(iterator) {\n" -" while (iterator = _sequence.iterate(iterator)) {\n" -" if (_fn.call(_sequence.iteratorValue(iterator))) break\n" -" }\n" -" return iterator\n" -" }\n" -"\n" -" iteratorValue(iterator) { _sequence.iteratorValue(iterator) }\n" -"}\n" -"\n" -"class String is Sequence {\n" -" bytes { StringByteSequence.new(this) }\n" -" codePoints { StringCodePointSequence.new(this) }\n" -"\n" -" split(delimiter) {\n" -" if (!(delimiter is String) || delimiter.isEmpty) {\n" -" Fiber.abort(\"Delimiter must be a non-empty string.\")\n" -" }\n" -"\n" -" var result = []\n" -"\n" -" var last = 0\n" -" var index = 0\n" -"\n" -" var delimSize = delimiter.byteCount_\n" -" var size = byteCount_\n" -"\n" -" while (last < size && (index = indexOf(delimiter, last)) != -1) {\n" -" result.add(this[last...index])\n" -" last = index + delimSize\n" -" }\n" -"\n" -" if (last < size) {\n" -" result.add(this[last..-1])\n" -" } else {\n" -" result.add(\"\")\n" -" }\n" -" return result\n" -" }\n" -"\n" -" replace(from, to) {\n" -" if (!(from is String) || from.isEmpty) {\n" -" Fiber.abort(\"From must be a non-empty string.\")\n" -" } else if (!(to is String)) {\n" -" Fiber.abort(\"To must be a string.\")\n" -" }\n" -"\n" -" var result = \"\"\n" -"\n" -" var last = 0\n" -" var index = 0\n" -"\n" -" var fromSize = from.byteCount_\n" -" var size = byteCount_\n" -"\n" -" while (last < size && (index = indexOf(from, last)) != -1) {\n" -" result = result + this[last...index] + to\n" -" last = index + fromSize\n" -" }\n" -"\n" -" if (last < size) result = result + this[last..-1]\n" -"\n" -" return result\n" -" }\n" -"\n" -" trim() { trim_(\"\\t\\r\\n \", true, true) }\n" -" trim(chars) { trim_(chars, true, true) }\n" -" trimEnd() { trim_(\"\\t\\r\\n \", false, true) }\n" -" trimEnd(chars) { trim_(chars, false, true) }\n" -" trimStart() { trim_(\"\\t\\r\\n \", true, false) }\n" -" trimStart(chars) { trim_(chars, true, false) }\n" -"\n" -" trim_(chars, trimStart, trimEnd) {\n" -" if (!(chars is String)) {\n" -" Fiber.abort(\"Characters must be a string.\")\n" -" }\n" -"\n" -" var codePoints = chars.codePoints.toList\n" -"\n" -" var start\n" -" if (trimStart) {\n" -" while (start = iterate(start)) {\n" -" if (!codePoints.contains(codePointAt_(start))) break\n" -" }\n" -"\n" -" if (start == false) return \"\"\n" -" } else {\n" -" start = 0\n" -" }\n" -"\n" -" var end\n" -" if (trimEnd) {\n" -" end = byteCount_ - 1\n" -" while (end >= start) {\n" -" var codePoint = codePointAt_(end)\n" -" if (codePoint != -1 && !codePoints.contains(codePoint)) break\n" -" end = end - 1\n" -" }\n" -"\n" -" if (end < start) return \"\"\n" -" } else {\n" -" end = -1\n" -" }\n" -"\n" -" return this[start..end]\n" -" }\n" -"\n" -" *(count) {\n" -" if (!(count is Num) || !count.isInteger || count < 0) {\n" -" Fiber.abort(\"Count must be a non-negative integer.\")\n" -" }\n" -"\n" -" var result = \"\"\n" -" for (i in 0...count) {\n" -" result = result + this\n" -" }\n" -" return result\n" -" }\n" -"}\n" -"\n" -"class StringByteSequence is Sequence {\n" -" construct new(string) {\n" -" _string = string\n" -" }\n" -"\n" -" [index] { _string.byteAt_(index) }\n" -" iterate(iterator) { _string.iterateByte_(iterator) }\n" -" iteratorValue(iterator) { _string.byteAt_(iterator) }\n" -"\n" -" count { _string.byteCount_ }\n" -"}\n" -"\n" -"class StringCodePointSequence is Sequence {\n" -" construct new(string) {\n" -" _string = string\n" -" }\n" -"\n" -" [index] { _string.codePointAt_(index) }\n" -" iterate(iterator) { _string.iterate(iterator) }\n" -" iteratorValue(iterator) { _string.codePointAt_(iterator) }\n" -"\n" -" count { _string.count }\n" -"}\n" -"\n" -"class List is Sequence {\n" -" addAll(other) {\n" -" for (element in other) {\n" -" add(element)\n" -" }\n" -" return other\n" -" }\n" -"\n" -" sort() { sort {|low, high| low < high } }\n" -"\n" -" sort(comparer) {\n" -" if (!(comparer is Fn)) {\n" -" Fiber.abort(\"Comparer must be a function.\")\n" -" }\n" -" quicksort_(0, count - 1, comparer)\n" -" return this\n" -" }\n" -"\n" -" quicksort_(low, high, comparer) {\n" -" if (low < high) {\n" -" var p = partition_(low, high, comparer)\n" -" quicksort_(low, p - 1, comparer)\n" -" quicksort_(p + 1, high, comparer)\n" -" }\n" -" }\n" -"\n" -" partition_(low, high, comparer) {\n" -" var p = this[high]\n" -" var i = low - 1\n" -" for (j in low..(high-1)) {\n" -" if (comparer.call(this[j], p)) { \n" -" i = i + 1\n" -" var t = this[i]\n" -" this[i] = this[j]\n" -" this[j] = t\n" -" }\n" -" }\n" -" var t = this[i+1]\n" -" this[i+1] = this[high]\n" -" this[high] = t\n" -" return i+1\n" -" }\n" -"\n" -" toString { \"[%(join(\", \"))]\" }\n" -"\n" -" +(other) {\n" -" var result = this[0..-1]\n" -" for (element in other) {\n" -" result.add(element)\n" -" }\n" -" return result\n" -" }\n" -"\n" -" *(count) {\n" -" if (!(count is Num) || !count.isInteger || count < 0) {\n" -" Fiber.abort(\"Count must be a non-negative integer.\")\n" -" }\n" -"\n" -" var result = []\n" -" for (i in 0...count) {\n" -" result.addAll(this)\n" -" }\n" -" return result\n" -" }\n" -"}\n" -"\n" -"class Map is Sequence {\n" -" keys { MapKeySequence.new(this) }\n" -" values { MapValueSequence.new(this) }\n" -"\n" -" toString {\n" -" var first = true\n" -" var result = \"{\"\n" -"\n" -" for (key in keys) {\n" -" if (!first) result = result + \", \"\n" -" first = false\n" -" result = result + \"%(key): %(this[key])\"\n" -" }\n" -"\n" -" return result + \"}\"\n" -" }\n" -"\n" -" iteratorValue(iterator) {\n" -" return MapEntry.new(\n" -" keyIteratorValue_(iterator),\n" -" valueIteratorValue_(iterator))\n" -" }\n" -"}\n" -"\n" -"class MapEntry {\n" -" construct new(key, value) {\n" -" _key = key\n" -" _value = value\n" -" }\n" -"\n" -" key { _key }\n" -" value { _value }\n" -"\n" -" toString { \"%(_key):%(_value)\" }\n" -"}\n" -"\n" -"class MapKeySequence is Sequence {\n" -" construct new(map) {\n" -" _map = map\n" -" }\n" -"\n" -" iterate(n) { _map.iterate(n) }\n" -" iteratorValue(iterator) { _map.keyIteratorValue_(iterator) }\n" -"}\n" -"\n" -"class MapValueSequence is Sequence {\n" -" construct new(map) {\n" -" _map = map\n" -" }\n" -"\n" -" iterate(n) { _map.iterate(n) }\n" -" iteratorValue(iterator) { _map.valueIteratorValue_(iterator) }\n" -"}\n" -"\n" -"class Range is Sequence {}\n" -"\n" -"class System {\n" -" static print() {\n" -" writeString_(\"\\n\")\n" -" }\n" -"\n" -" static print(obj) {\n" -" writeObject_(obj)\n" -" writeString_(\"\\n\")\n" -" return obj\n" -" }\n" -"\n" -" static printAll(sequence) {\n" -" for (object in sequence) writeObject_(object)\n" -" writeString_(\"\\n\")\n" -" }\n" -"\n" -" static write(obj) {\n" -" writeObject_(obj)\n" -" return obj\n" -" }\n" -"\n" -" static writeAll(sequence) {\n" -" for (object in sequence) writeObject_(object)\n" -" }\n" -"\n" -" static writeObject_(obj) {\n" -" var string = obj.toString\n" -" if (string is String) {\n" -" writeString_(string)\n" -" } else {\n" -" writeString_(\"[invalid toString]\")\n" -" }\n" -" }\n" -"}\n" -"\n" -"class ClassAttributes {\n" -" self { _attributes }\n" -" methods { _methods }\n" -" construct new(attributes, methods) {\n" -" _attributes = attributes\n" -" _methods = methods\n" -" }\n" -" toString { \"attributes:%(_attributes) methods:%(_methods)\" }\n" -"}\n"; +static const char* coreModuleSource = "class Bool {}\n" + "class Fiber {}\n" + "class Fn {}\n" + "class Null {}\n" + "class Num {}\n" + "\n" + "class Sequence {\n" + " all(f) {\n" + " var result = true\n" + " for (element in this) {\n" + " result = f.call(element)\n" + " if (!result) return result\n" + " }\n" + " return result\n" + " }\n" + "\n" + " any(f) {\n" + " var result = false\n" + " for (element in this) {\n" + " result = f.call(element)\n" + " if (result) return result\n" + " }\n" + " return result\n" + " }\n" + "\n" + " contains(element) {\n" + " for (item in this) {\n" + " if (element == item) return true\n" + " }\n" + " return false\n" + " }\n" + "\n" + " count {\n" + " var result = 0\n" + " for (element in this) {\n" + " result = result + 1\n" + " }\n" + " return result\n" + " }\n" + "\n" + " count(f) {\n" + " var result = 0\n" + " for (element in this) {\n" + " if (f.call(element)) result = result + 1\n" + " }\n" + " return result\n" + " }\n" + "\n" + " each(f) {\n" + " for (element in this) {\n" + " f.call(element)\n" + " }\n" + " }\n" + "\n" + " isEmpty { iterate(null) ? false : true }\n" + "\n" + " map(transformation) { MapSequence.new(this, transformation) }\n" + "\n" + " skip(count) {\n" + " if (!(count is Num) || !count.isInteger || count < 0) {\n" + " Fiber.abort(\"Count must be a non-negative integer.\")\n" + " }\n" + "\n" + " return SkipSequence.new(this, count)\n" + " }\n" + "\n" + " take(count) {\n" + " if (!(count is Num) || !count.isInteger || count < 0) {\n" + " Fiber.abort(\"Count must be a non-negative integer.\")\n" + " }\n" + "\n" + " return TakeSequence.new(this, count)\n" + " }\n" + "\n" + " where(predicate) { WhereSequence.new(this, predicate) }\n" + "\n" + " reduce(acc, f) {\n" + " for (element in this) {\n" + " acc = f.call(acc, element)\n" + " }\n" + " return acc\n" + " }\n" + "\n" + " reduce(f) {\n" + " var iter = iterate(null)\n" + " if (!iter) Fiber.abort(\"Can't reduce an empty sequence.\")\n" + "\n" + " // Seed with the first element.\n" + " var result = iteratorValue(iter)\n" + " while (iter = iterate(iter)) {\n" + " result = f.call(result, iteratorValue(iter))\n" + " }\n" + "\n" + " return result\n" + " }\n" + "\n" + " join() { join(\"\") }\n" + "\n" + " join(sep) {\n" + " var first = true\n" + " var result = \"\"\n" + "\n" + " for (element in this) {\n" + " if (!first) result = result + sep\n" + " first = false\n" + " result = result + element.toString\n" + " }\n" + "\n" + " return result\n" + " }\n" + "\n" + " toList {\n" + " var result = List.new()\n" + " for (element in this) {\n" + " result.add(element)\n" + " }\n" + " return result\n" + " }\n" + "}\n" + "\n" + "class MapSequence is Sequence {\n" + " construct new(sequence, fn) {\n" + " _sequence = sequence\n" + " _fn = fn\n" + " }\n" + "\n" + " iterate(iterator) { _sequence.iterate(iterator) }\n" + " iteratorValue(iterator) { _fn.call(_sequence.iteratorValue(iterator)) }\n" + "}\n" + "\n" + "class SkipSequence is Sequence {\n" + " construct new(sequence, count) {\n" + " _sequence = sequence\n" + " _count = count\n" + " }\n" + "\n" + " iterate(iterator) {\n" + " if (iterator) {\n" + " return _sequence.iterate(iterator)\n" + " } else {\n" + " iterator = _sequence.iterate(iterator)\n" + " var count = _count\n" + " while (count > 0 && iterator) {\n" + " iterator = _sequence.iterate(iterator)\n" + " count = count - 1\n" + " }\n" + " return iterator\n" + " }\n" + " }\n" + "\n" + " iteratorValue(iterator) { _sequence.iteratorValue(iterator) }\n" + "}\n" + "\n" + "class TakeSequence is Sequence {\n" + " construct new(sequence, count) {\n" + " _sequence = sequence\n" + " _count = count\n" + " }\n" + "\n" + " iterate(iterator) {\n" + " if (!iterator) _taken = 1 else _taken = _taken + 1\n" + " return _taken > _count ? null : _sequence.iterate(iterator)\n" + " }\n" + "\n" + " iteratorValue(iterator) { _sequence.iteratorValue(iterator) }\n" + "}\n" + "\n" + "class WhereSequence is Sequence {\n" + " construct new(sequence, fn) {\n" + " _sequence = sequence\n" + " _fn = fn\n" + " }\n" + "\n" + " iterate(iterator) {\n" + " while (iterator = _sequence.iterate(iterator)) {\n" + " if (_fn.call(_sequence.iteratorValue(iterator))) break\n" + " }\n" + " return iterator\n" + " }\n" + "\n" + " iteratorValue(iterator) { _sequence.iteratorValue(iterator) }\n" + "}\n" + "\n" + "class String is Sequence {\n" + " bytes { StringByteSequence.new(this) }\n" + " codePoints { StringCodePointSequence.new(this) }\n" + "\n" + " split(delimiter) {\n" + " if (!(delimiter is String) || delimiter.isEmpty) {\n" + " Fiber.abort(\"Delimiter must be a non-empty string.\")\n" + " }\n" + "\n" + " var result = []\n" + "\n" + " var last = 0\n" + " var index = 0\n" + "\n" + " var delimSize = delimiter.byteCount_\n" + " var size = byteCount_\n" + "\n" + " while (last < size && (index = indexOf(delimiter, last)) != -1) {\n" + " result.add(this[last...index])\n" + " last = index + delimSize\n" + " }\n" + "\n" + " if (last < size) {\n" + " result.add(this[last..-1])\n" + " } else {\n" + " result.add(\"\")\n" + " }\n" + " return result\n" + " }\n" + "\n" + " replace(from, to) {\n" + " if (!(from is String) || from.isEmpty) {\n" + " Fiber.abort(\"From must be a non-empty string.\")\n" + " } else if (!(to is String)) {\n" + " Fiber.abort(\"To must be a string.\")\n" + " }\n" + "\n" + " var result = \"\"\n" + "\n" + " var last = 0\n" + " var index = 0\n" + "\n" + " var fromSize = from.byteCount_\n" + " var size = byteCount_\n" + "\n" + " while (last < size && (index = indexOf(from, last)) != -1) {\n" + " result = result + this[last...index] + to\n" + " last = index + fromSize\n" + " }\n" + "\n" + " if (last < size) result = result + this[last..-1]\n" + "\n" + " return result\n" + " }\n" + "\n" + " trim() { trim_(\"\\t\\r\\n \", true, true) }\n" + " trim(chars) { trim_(chars, true, true) }\n" + " trimEnd() { trim_(\"\\t\\r\\n \", false, true) }\n" + " trimEnd(chars) { trim_(chars, false, true) }\n" + " trimStart() { trim_(\"\\t\\r\\n \", true, false) }\n" + " trimStart(chars) { trim_(chars, true, false) }\n" + "\n" + " trim_(chars, trimStart, trimEnd) {\n" + " if (!(chars is String)) {\n" + " Fiber.abort(\"Characters must be a string.\")\n" + " }\n" + "\n" + " var codePoints = chars.codePoints.toList\n" + "\n" + " var start\n" + " if (trimStart) {\n" + " while (start = iterate(start)) {\n" + " if (!codePoints.contains(codePointAt_(start))) break\n" + " }\n" + "\n" + " if (start == false) return \"\"\n" + " } else {\n" + " start = 0\n" + " }\n" + "\n" + " var end\n" + " if (trimEnd) {\n" + " end = byteCount_ - 1\n" + " while (end >= start) {\n" + " var codePoint = codePointAt_(end)\n" + " if (codePoint != -1 && !codePoints.contains(codePoint)) break\n" + " end = end - 1\n" + " }\n" + "\n" + " if (end < start) return \"\"\n" + " } else {\n" + " end = -1\n" + " }\n" + "\n" + " return this[start..end]\n" + " }\n" + "\n" + " *(count) {\n" + " if (!(count is Num) || !count.isInteger || count < 0) {\n" + " Fiber.abort(\"Count must be a non-negative integer.\")\n" + " }\n" + "\n" + " var result = \"\"\n" + " for (i in 0...count) {\n" + " result = result + this\n" + " }\n" + " return result\n" + " }\n" + "}\n" + "\n" + "class StringByteSequence is Sequence {\n" + " construct new(string) {\n" + " _string = string\n" + " }\n" + "\n" + " [index] { _string.byteAt_(index) }\n" + " iterate(iterator) { _string.iterateByte_(iterator) }\n" + " iteratorValue(iterator) { _string.byteAt_(iterator) }\n" + "\n" + " count { _string.byteCount_ }\n" + "}\n" + "\n" + "class StringCodePointSequence is Sequence {\n" + " construct new(string) {\n" + " _string = string\n" + " }\n" + "\n" + " [index] { _string.codePointAt_(index) }\n" + " iterate(iterator) { _string.iterate(iterator) }\n" + " iteratorValue(iterator) { _string.codePointAt_(iterator) }\n" + "\n" + " count { _string.count }\n" + "}\n" + "\n" + "class List is Sequence {\n" + " addAll(other) {\n" + " for (element in other) {\n" + " add(element)\n" + " }\n" + " return other\n" + " }\n" + "\n" + " sort() { sort {|low, high| low < high } }\n" + "\n" + " sort(comparer) {\n" + " if (!(comparer is Fn)) {\n" + " Fiber.abort(\"Comparer must be a function.\")\n" + " }\n" + " quicksort_(0, count - 1, comparer)\n" + " return this\n" + " }\n" + "\n" + " quicksort_(low, high, comparer) {\n" + " if (low < high) {\n" + " var p = partition_(low, high, comparer)\n" + " quicksort_(low, p - 1, comparer)\n" + " quicksort_(p + 1, high, comparer)\n" + " }\n" + " }\n" + "\n" + " partition_(low, high, comparer) {\n" + " var p = this[high]\n" + " var i = low - 1\n" + " for (j in low..(high-1)) {\n" + " if (comparer.call(this[j], p)) { \n" + " i = i + 1\n" + " var t = this[i]\n" + " this[i] = this[j]\n" + " this[j] = t\n" + " }\n" + " }\n" + " var t = this[i+1]\n" + " this[i+1] = this[high]\n" + " this[high] = t\n" + " return i+1\n" + " }\n" + "\n" + " toString { \"[%(join(\", \"))]\" }\n" + "\n" + " +(other) {\n" + " var result = this[0..-1]\n" + " for (element in other) {\n" + " result.add(element)\n" + " }\n" + " return result\n" + " }\n" + "\n" + " *(count) {\n" + " if (!(count is Num) || !count.isInteger || count < 0) {\n" + " Fiber.abort(\"Count must be a non-negative integer.\")\n" + " }\n" + "\n" + " var result = []\n" + " for (i in 0...count) {\n" + " result.addAll(this)\n" + " }\n" + " return result\n" + " }\n" + "}\n" + "\n" + "class Map is Sequence {\n" + " keys { MapKeySequence.new(this) }\n" + " values { MapValueSequence.new(this) }\n" + "\n" + " toString {\n" + " var first = true\n" + " var result = \"{\"\n" + "\n" + " for (key in keys) {\n" + " if (!first) result = result + \", \"\n" + " first = false\n" + " result = result + \"%(key): %(this[key])\"\n" + " }\n" + "\n" + " return result + \"}\"\n" + " }\n" + "\n" + " iteratorValue(iterator) {\n" + " return MapEntry.new(\n" + " keyIteratorValue_(iterator),\n" + " valueIteratorValue_(iterator))\n" + " }\n" + "}\n" + "\n" + "class MapEntry {\n" + " construct new(key, value) {\n" + " _key = key\n" + " _value = value\n" + " }\n" + "\n" + " key { _key }\n" + " value { _value }\n" + "\n" + " toString { \"%(_key):%(_value)\" }\n" + "}\n" + "\n" + "class MapKeySequence is Sequence {\n" + " construct new(map) {\n" + " _map = map\n" + " }\n" + "\n" + " iterate(n) { _map.iterate(n) }\n" + " iteratorValue(iterator) { _map.keyIteratorValue_(iterator) }\n" + "}\n" + "\n" + "class MapValueSequence is Sequence {\n" + " construct new(map) {\n" + " _map = map\n" + " }\n" + "\n" + " iterate(n) { _map.iterate(n) }\n" + " iteratorValue(iterator) { _map.valueIteratorValue_(iterator) }\n" + "}\n" + "\n" + "class Range is Sequence {}\n" + "\n" + "class System {\n" + " static print() {\n" + " writeString_(\"\\n\")\n" + " }\n" + "\n" + " static print(obj) {\n" + " writeObject_(obj)\n" + " writeString_(\"\\n\")\n" + " return obj\n" + " }\n" + "\n" + " static printAll(sequence) {\n" + " for (object in sequence) writeObject_(object)\n" + " writeString_(\"\\n\")\n" + " }\n" + "\n" + " static write(obj) {\n" + " writeObject_(obj)\n" + " return obj\n" + " }\n" + "\n" + " static writeAll(sequence) {\n" + " for (object in sequence) writeObject_(object)\n" + " }\n" + "\n" + " static writeObject_(obj) {\n" + " var string = obj.toString\n" + " if (string is String) {\n" + " writeString_(string)\n" + " } else {\n" + " writeString_(\"[invalid toString]\")\n" + " }\n" + " }\n" + "}\n" + "\n" + "class ClassAttributes {\n" + " self { _attributes }\n" + " methods { _methods }\n" + " construct new(attributes, methods) {\n" + " _attributes = attributes\n" + " _methods = methods\n" + " }\n" + " toString { \"attributes:%(_attributes) methods:%(_methods)\" }\n" + "}\n"; // End file "wren_core.wren.inc" -DEF_PRIMITIVE(bool_not) -{ - RETURN_BOOL(!AS_BOOL(args[0])); +DEF_PRIMITIVE(bool_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(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_name) { + RETURN_OBJ(AS_CLASS(args[0])->name); } -DEF_PRIMITIVE(class_supertype) -{ - ObjClass* classObj = AS_CLASS(args[0]); +DEF_PRIMITIVE(class_supertype) { + ObjClass* classObj = AS_CLASS(args[0]); - // Object has no superclass. - if (classObj->superclass == NULL) RETURN_NULL; + // Object has no superclass. + if (classObj->superclass == NULL) + RETURN_NULL; - RETURN_OBJ(classObj->superclass); + RETURN_OBJ(classObj->superclass); } -DEF_PRIMITIVE(class_toString) -{ - RETURN_OBJ(AS_CLASS(args[0])->name); +DEF_PRIMITIVE(class_toString) { + RETURN_OBJ(AS_CLASS(args[0])->name); } -DEF_PRIMITIVE(class_attributes) -{ - RETURN_VAL(AS_CLASS(args[0])->attributes); +DEF_PRIMITIVE(class_attributes) { + RETURN_VAL(AS_CLASS(args[0])->attributes); } -DEF_PRIMITIVE(fiber_new) -{ - if (!validateFn(vm, args[1], "Argument")) return false; +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."); + } - 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)); + RETURN_OBJ(wrenNewFiber(vm, closure)); } -DEF_PRIMITIVE(fiber_abort) -{ - vm->fiber->error = args[1]; +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]); + // 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 @@ -3797,196 +3649,173 @@ DEF_PRIMITIVE(fiber_abort) // // [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) -{ +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 (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 (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->state == FIBER_ROOT) + RETURN_ERROR("Cannot call root fiber."); - if (fiber->numFrames == 0) - { - RETURN_ERROR_FMT("Cannot $ a finished fiber.", verb); - } + // Remember who ran it. + fiber->caller = vm->fiber; + } - // 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 == 0) { + RETURN_ERROR_FMT("Cannot $ a finished fiber.", verb); + } - 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; - } + // 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--; - vm->fiber = fiber; - return false; + 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_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_call1) { + return runFiber(vm, AS_FIBER(args[0]), args, true, true, "call"); } -DEF_PRIMITIVE(fiber_current) -{ - RETURN_OBJ(vm->fiber); +DEF_PRIMITIVE(fiber_current) { + RETURN_OBJ(vm->fiber); } -DEF_PRIMITIVE(fiber_error) -{ - RETURN_VAL(AS_FIBER(args[0])->error); +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_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_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_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_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_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_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_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; +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; + // 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; - } + if (vm->fiber != NULL) { + // Make the caller's run method return null. + vm->fiber->stackTop[-1] = NULL_VAL; + } - return false; + return false; } -DEF_PRIMITIVE(fiber_yield1) -{ - ObjFiber* current = vm->fiber; - vm->fiber = current->caller; +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; + // 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]; + 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--; - } + // 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; + return false; } -DEF_PRIMITIVE(fn_new) -{ - if (!validateFn(vm, args[1], "Argument")) 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]); + // 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); +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); +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; \ - } +#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) @@ -4006,1198 +3835,1151 @@ DEF_FN_CALL(14) DEF_FN_CALL(15) DEF_FN_CALL(16) -DEF_PRIMITIVE(fn_toString) -{ - RETURN_VAL(CONST_STRING(vm, "")); +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_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_new) { + RETURN_OBJ(wrenNewList(vm, 0)); } -DEF_PRIMITIVE(list_add) -{ - wrenValueBufferWrite(vm, &AS_LIST(args[0])->elements, args[1]); - RETURN_VAL(args[1]); +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_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_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_count) { + RETURN_NUM(AS_LIST(args[0])->elements.count); } -DEF_PRIMITIVE(list_insert) -{ - ObjList* list = AS_LIST(args[0]); +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; + // 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]); + wrenListInsert(vm, list, args[2], index); + RETURN_VAL(args[2]); } -DEF_PRIMITIVE(list_iterate) -{ - ObjList* list = AS_LIST(args[0]); +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 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; + 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; + // 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); + // 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; +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]); + 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; +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)); + 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)); + 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_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; +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; + Value a = list->elements.data[indexA]; + list->elements.data[indexA] = list->elements.data[indexB]; + list->elements.data[indexB] = a; - RETURN_NULL; + RETURN_NULL; } -DEF_PRIMITIVE(list_subscript) -{ - ObjList* list = AS_LIST(args[0]); +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; + 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]); - } + RETURN_VAL(list->elements.data[index]); + } - if (!IS_RANGE(args[1])) - { - RETURN_ERROR("Subscript must be a number or a range."); - } + 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; + 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]; - } + 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); + 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; +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]); + list->elements.data[index] = args[2]; + RETURN_VAL(args[2]); } -DEF_PRIMITIVE(map_new) -{ - RETURN_OBJ(wrenNewMap(vm)); +DEF_PRIMITIVE(map_new) { + RETURN_OBJ(wrenNewMap(vm)); } -DEF_PRIMITIVE(map_subscript) -{ - if (!validateKey(vm, args[1])) return false; +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; + ObjMap* map = AS_MAP(args[0]); + Value value = wrenMapGet(map, args[1]); + if (IS_UNDEFINED(value)) + RETURN_NULL; - RETURN_VAL(value); + RETURN_VAL(value); } -DEF_PRIMITIVE(map_subscriptSetter) -{ - if (!validateKey(vm, args[1])) return false; +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]); + 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_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_clear) { + wrenMapClear(vm, AS_MAP(args[0])); + RETURN_NULL; } -DEF_PRIMITIVE(map_containsKey) -{ - if (!validateKey(vm, args[1])) return false; +DEF_PRIMITIVE(map_containsKey) { + if (!validateKey(vm, args[1])) + return false; - RETURN_BOOL(!IS_UNDEFINED(wrenMapGet(AS_MAP(args[0]), args[1]))); + 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_count) { + RETURN_NUM(AS_MAP(args[0])->count); } -DEF_PRIMITIVE(map_iterate) -{ - ObjMap* map = AS_MAP(args[0]); +DEF_PRIMITIVE(map_iterate) { + ObjMap* map = AS_MAP(args[0]); - if (map->count == 0) RETURN_FALSE; + if (map->count == 0) + RETURN_FALSE; - // If we're starting the iteration, start at the first used entry. - uint32_t index = 0; + // 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; + // 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 (AS_NUM(args[1]) < 0) + RETURN_FALSE; + index = (uint32_t)AS_NUM(args[1]); - if (index >= map->capacity) RETURN_FALSE; + if (index >= map->capacity) + RETURN_FALSE; - // Advance the iterator. - index++; - } + // 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); - } + // 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; + // If we get here, walked all of the entries. + RETURN_FALSE; } -DEF_PRIMITIVE(map_remove) -{ - if (!validateKey(vm, args[1])) return false; +DEF_PRIMITIVE(map_remove) { + if (!validateKey(vm, args[1])) + return false; - RETURN_VAL(wrenMapRemoveKey(vm, AS_MAP(args[0]), args[1])); + 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; +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."); - } + MapEntry* entry = &map->entries[index]; + if (IS_UNDEFINED(entry->key)) { + RETURN_ERROR("Invalid map iterator."); + } - RETURN_VAL(entry->key); + 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; +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."); - } + MapEntry* entry = &map->entries[index]; + if (IS_UNDEFINED(entry->key)) { + RETURN_ERROR("Invalid map iterator."); + } - RETURN_VAL(entry->value); + RETURN_VAL(entry->value); } -DEF_PRIMITIVE(null_not) -{ - RETURN_VAL(TRUE_VAL); +DEF_PRIMITIVE(null_not) { + RETURN_VAL(TRUE_VAL); } -DEF_PRIMITIVE(null_toString) -{ - RETURN_VAL(CONST_STRING(vm, "null")); +DEF_PRIMITIVE(null_toString) { + RETURN_VAL(CONST_STRING(vm, "null")); } -DEF_PRIMITIVE(num_fromString) -{ - if (!validateString(vm, args[1], "Argument")) return false; +DEF_PRIMITIVE(num_fromString) { + if (!validateString(vm, args[1], "Argument")) + return false; - ObjString* string = AS_STRING(args[1]); + ObjString* string = AS_STRING(args[1]); - // Corner case: Can't parse an empty string. - if (string->length == 0) RETURN_NULL; + // Corner case: Can't parse an empty string. + if (string->length == 0) + RETURN_NULL; - errno = 0; - char* end; - double number = strtod(string->value, &end); + errno = 0; + char* end; + double number = strtod(string->value, &end); - // Skip past any trailing whitespace. - while (*end != '\0' && isspace((unsigned char)*end)) end++; + // Skip past any trailing whitespace. + while (*end != '\0' && isspace((unsigned char)*end)) + end++; - if (errno == ERANGE) RETURN_ERROR("Number literal is too large."); + 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; + // 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); + 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); \ - } +#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(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(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) +#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, <<) +#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]))); \ - } +#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_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_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_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_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_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; +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)); + 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; +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)); + 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; +DEF_PRIMITIVE(num_atan2) { + if (!validateNum(vm, args[1], "x value")) + return false; - RETURN_NUM(atan2(AS_NUM(args[0]), AS_NUM(args[1]))); + RETURN_NUM(atan2(AS_NUM(args[0]), AS_NUM(args[1]))); } -DEF_PRIMITIVE(num_min) -{ - if (!validateNum(vm, args[1], "Other value")) return false; +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); + 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; +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); + 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; +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); + 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; +DEF_PRIMITIVE(num_pow) { + if (!validateNum(vm, args[1], "Power value")) + return false; - RETURN_NUM(pow(AS_NUM(args[0]), AS_NUM(args[1]))); + 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_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_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_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_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_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_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(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_same) { + RETURN_BOOL(wrenValuesEqual(args[1], args[2])); } -DEF_PRIMITIVE(object_not) -{ - RETURN_VAL(FALSE_VAL); +DEF_PRIMITIVE(object_not) { + RETURN_VAL(FALSE_VAL); } -DEF_PRIMITIVE(object_eqeq) -{ - RETURN_BOOL(wrenValuesEqual(args[0], args[1])); +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_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."); - } +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]); + 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); + // Walk the superclass chain looking for the class. + do { + if (baseClassObj == classObj) + RETURN_BOOL(true); - classObj = classObj->superclass; - } - while (classObj != NULL); + classObj = classObj->superclass; + } while (classObj != NULL); - RETURN_BOOL(false); + 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_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(object_type) { + RETURN_OBJ(wrenGetClass(vm, args[0])); } -DEF_PRIMITIVE(range_from) -{ - RETURN_NUM(AS_RANGE(args[0])->from); +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_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_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_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_isInclusive) { + RETURN_BOOL(AS_RANGE(args[0])->isInclusive); } -DEF_PRIMITIVE(range_iterate) -{ - ObjRange* range = AS_RANGE(args[0]); +DEF_PRIMITIVE(range_iterate) { + ObjRange* range = AS_RANGE(args[0]); - // Special case: empty range. - if (range->from == range->to && !range->isInclusive) RETURN_FALSE; + // 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); + // Start the iteration. + if (IS_NULL(args[1])) + RETURN_NUM(range->from); - if (!validateNum(vm, args[1], "Iterator")) return false; + if (!validateNum(vm, args[1], "Iterator")) + return false; - double iterator = AS_NUM(args[1]); + 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; - } + // 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; + if (!range->isInclusive && iterator == range->to) + RETURN_FALSE; - RETURN_NUM(iterator); + 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_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]); +DEF_PRIMITIVE(range_toString) { + ObjRange* range = AS_RANGE(args[0]); - Value from = wrenNumToString(vm, range->from); - wrenPushRoot(vm, AS_OBJ(from)); + Value from = wrenNumToString(vm, range->from); + wrenPushRoot(vm, AS_OBJ(from)); - Value to = wrenNumToString(vm, range->to); - wrenPushRoot(vm, AS_OBJ(to)); + Value to = wrenNumToString(vm, range->to); + wrenPushRoot(vm, AS_OBJ(to)); - Value result = wrenStringFormat(vm, "@$@", from, - range->isInclusive ? ".." : "...", to); + Value result = wrenStringFormat(vm, "@$@", from, range->isInclusive ? ".." : "...", to); - wrenPopRoot(vm); - wrenPopRoot(vm); - RETURN_VAL(result); + wrenPopRoot(vm); + wrenPopRoot(vm); + RETURN_VAL(result); } -DEF_PRIMITIVE(string_fromCodePoint) -{ - if (!validateInt(vm, args[1], "Code point")) return false; +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."); - } + 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)); + 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_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]); +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; + uint32_t index = validateIndex(vm, args[1], string->length, "Index"); + if (index == UINT32_MAX) + return false; - RETURN_NUM((uint8_t)string->value[index]); + RETURN_NUM((uint8_t)string->value[index]); } -DEF_PRIMITIVE(string_byteCount) -{ - RETURN_NUM(AS_STRING(args[0])->length); +DEF_PRIMITIVE(string_byteCount) { + RETURN_NUM(AS_STRING(args[0])->length); } -DEF_PRIMITIVE(string_codePointAt) -{ - ObjString* string = AS_STRING(args[0]); +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; + 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); + // 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)); + // 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; +DEF_PRIMITIVE(string_contains) { + if (!validateString(vm, args[1], "Argument")) + return false; - ObjString* string = AS_STRING(args[0]); - ObjString* search = AS_STRING(args[1]); + ObjString* string = AS_STRING(args[0]); + ObjString* search = AS_STRING(args[1]); - RETURN_BOOL(wrenStringFind(string, search, 0) != UINT32_MAX); + RETURN_BOOL(wrenStringFind(string, search, 0) != UINT32_MAX); } -DEF_PRIMITIVE(string_endsWith) -{ - if (!validateString(vm, args[1], "Argument")) return false; +DEF_PRIMITIVE(string_endsWith) { + if (!validateString(vm, args[1], "Argument")) + return false; - ObjString* string = AS_STRING(args[0]); - ObjString* search = AS_STRING(args[1]); + 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; + // 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); + 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; +DEF_PRIMITIVE(string_indexOf1) { + if (!validateString(vm, args[1], "Argument")) + return false; - ObjString* string = AS_STRING(args[0]); - ObjString* search = AS_STRING(args[1]); + 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); + 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; +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; - 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); + 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]); +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 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 (!validateInt(vm, args[1], "Iterator")) + return false; - if (AS_NUM(args[1]) < 0) RETURN_FALSE; - uint32_t index = (uint32_t)AS_NUM(args[1]); + 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); + // 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); + RETURN_NUM(index); } -DEF_PRIMITIVE(string_iterateByte) -{ - ObjString* string = AS_STRING(args[0]); +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 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 (!validateInt(vm, args[1], "Iterator")) + return false; - if (AS_NUM(args[1]) < 0) RETURN_FALSE; - uint32_t index = (uint32_t)AS_NUM(args[1]); + 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; + // Advance to the next byte. + index++; + if (index >= string->length) + RETURN_FALSE; - RETURN_NUM(index); + 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; +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)); + RETURN_VAL(wrenStringCodePointAt(vm, string, index)); } -DEF_PRIMITIVE(string_startsWith) -{ - if (!validateString(vm, args[1], "Argument")) return false; +DEF_PRIMITIVE(string_startsWith) { + if (!validateString(vm, args[1], "Argument")) + return false; - ObjString* string = AS_STRING(args[0]); - ObjString* search = AS_STRING(args[1]); + 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; + // 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); + 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_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]); +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; + 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)); - } + RETURN_VAL(wrenStringCodePointAt(vm, string, index)); + } - if (!IS_RANGE(args[1])) - { - RETURN_ERROR("Subscript must be a number or a range."); - } + 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; + 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)); + RETURN_VAL(wrenNewStringFromRange(vm, string, start, count, step)); } -DEF_PRIMITIVE(string_toString) -{ - RETURN_VAL(args[0]); +DEF_PRIMITIVE(string_toString) { + RETURN_VAL(args[0]); } -DEF_PRIMITIVE(system_clock) -{ - RETURN_NUM((double)clock() / CLOCKS_PER_SEC); +DEF_PRIMITIVE(system_clock) { + RETURN_NUM((double)clock() / CLOCKS_PER_SEC); } -DEF_PRIMITIVE(system_gc) -{ - wrenCollectGarbage(vm); - RETURN_NULL; +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])); - } +DEF_PRIMITIVE(system_writeString) { + if (vm->config.writeFn != NULL) { + vm->config.writeFn(vm, AS_CSTRING(args[1])); + } - RETURN_VAL(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; - } +static ObjClass* defineClass(WrenVM* vm, ObjModule* module, const char* name) { + ObjString* nameString = AS_STRING(wrenNewString(vm, name)); + wrenPushRoot(vm, (Obj*)nameString); + + ObjClass* classObj = wrenNewSingleClass(vm, 0, nameString); + + wrenDefineVariable(vm, module, name, nameString->length, OBJ_VAL(classObj), NULL); + + wrenPopRoot(vm); + return classObj; +} + +void wrenInitializeCore(WrenVM* vm) { + ObjModule* coreModule = wrenNewModule(vm, NULL); + wrenPushRoot(vm, (Obj*)coreModule); + + // The core module's key is null in the module map. + wrenMapSet(vm, vm->modules, NULL_VAL, OBJ_VAL(coreModule)); + wrenPopRoot(vm); // coreModule. + + // Define the root Object class. This has to be done a little specially + // because it has no superclass. + vm->objectClass = defineClass(vm, coreModule, "Object"); + PRIMITIVE(vm->objectClass, "!", object_not); + PRIMITIVE(vm->objectClass, "==(_)", object_eqeq); + PRIMITIVE(vm->objectClass, "!=(_)", object_bangeq); + PRIMITIVE(vm->objectClass, "is(_)", object_is); + PRIMITIVE(vm->objectClass, "toString", object_toString); + PRIMITIVE(vm->objectClass, "type", object_type); + + // Now we can define Class, which is a subclass of Object. + vm->classClass = defineClass(vm, coreModule, "Class"); + wrenBindSuperclass(vm, vm->classClass, vm->objectClass); + PRIMITIVE(vm->classClass, "name", class_name); + PRIMITIVE(vm->classClass, "supertype", class_supertype); + PRIMITIVE(vm->classClass, "toString", class_toString); + PRIMITIVE(vm->classClass, "attributes", class_attributes); + + // Finally, we can define Object's metaclass which is a subclass of Class. + ObjClass* objectMetaclass = defineClass(vm, coreModule, "Object metaclass"); + + // Wire up the metaclass relationships now that all three classes are built. + vm->objectClass->obj.classObj = objectMetaclass; + objectMetaclass->obj.classObj = vm->classClass; + vm->classClass->obj.classObj = vm->classClass; + + // Do this after wiring up the metaclasses so objectMetaclass doesn't get + // collected. + wrenBindSuperclass(vm, objectMetaclass, vm->classClass); + + PRIMITIVE(objectMetaclass, "same(_,_)", object_same); + + // The core class diagram ends up looking like this, where single lines point + // to a class's superclass, and double lines point to its metaclass: + // + // .------------------------------------. .====. + // | .---------------. | # # + // v | v | v # + // .---------. .-------------------. .-------. # + // | Object |==>| Object metaclass |==>| Class |==" + // '---------' '-------------------' '-------' + // ^ ^ ^ ^ ^ + // | .--------------' # | # + // | | # | # + // .---------. .-------------------. # | # -. + // | Base |==>| Base metaclass |======" | # | + // '---------' '-------------------' | # | + // ^ | # | + // | .------------------' # | Example classes + // | | # | + // .---------. .-------------------. # | + // | Derived |==>| Derived metaclass |==========" | + // '---------' '-------------------' -' + + // The rest of the classes can now be defined normally. + wrenInterpret(vm, NULL, coreModuleSource); + + vm->boolClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Bool")); + PRIMITIVE(vm->boolClass, "toString", bool_toString); + PRIMITIVE(vm->boolClass, "!", bool_not); + + vm->fiberClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Fiber")); + PRIMITIVE(vm->fiberClass->obj.classObj, "new(_)", fiber_new); + PRIMITIVE(vm->fiberClass->obj.classObj, "abort(_)", fiber_abort); + PRIMITIVE(vm->fiberClass->obj.classObj, "current", fiber_current); + PRIMITIVE(vm->fiberClass->obj.classObj, "suspend()", fiber_suspend); + PRIMITIVE(vm->fiberClass->obj.classObj, "yield()", fiber_yield); + PRIMITIVE(vm->fiberClass->obj.classObj, "yield(_)", fiber_yield1); + PRIMITIVE(vm->fiberClass, "call()", fiber_call); + PRIMITIVE(vm->fiberClass, "call(_)", fiber_call1); + PRIMITIVE(vm->fiberClass, "error", fiber_error); + PRIMITIVE(vm->fiberClass, "isDone", fiber_isDone); + PRIMITIVE(vm->fiberClass, "transfer()", fiber_transfer); + PRIMITIVE(vm->fiberClass, "transfer(_)", fiber_transfer1); + PRIMITIVE(vm->fiberClass, "transferError(_)", fiber_transferError); + PRIMITIVE(vm->fiberClass, "try()", fiber_try); + PRIMITIVE(vm->fiberClass, "try(_)", fiber_try1); + + vm->fnClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Fn")); + PRIMITIVE(vm->fnClass->obj.classObj, "new(_)", fn_new); + + PRIMITIVE(vm->fnClass, "arity", fn_arity); + + FUNCTION_CALL(vm->fnClass, "call()", fn_call0); + FUNCTION_CALL(vm->fnClass, "call(_)", fn_call1); + FUNCTION_CALL(vm->fnClass, "call(_,_)", fn_call2); + FUNCTION_CALL(vm->fnClass, "call(_,_,_)", fn_call3); + FUNCTION_CALL(vm->fnClass, "call(_,_,_,_)", fn_call4); + FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_)", fn_call5); + FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_)", fn_call6); + FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_)", fn_call7); + FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_)", fn_call8); + FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_)", fn_call9); + FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_)", fn_call10); + FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_)", fn_call11); + FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_)", fn_call12); + FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_,_)", fn_call13); + FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_,_,_)", fn_call14); + FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_,_,_,_)", fn_call15); + FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_)", fn_call16); + + PRIMITIVE(vm->fnClass, "toString", fn_toString); + + vm->nullClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Null")); + PRIMITIVE(vm->nullClass, "!", null_not); + PRIMITIVE(vm->nullClass, "toString", null_toString); + + vm->numClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Num")); + PRIMITIVE(vm->numClass->obj.classObj, "fromString(_)", num_fromString); + PRIMITIVE(vm->numClass->obj.classObj, "infinity", num_infinity); + PRIMITIVE(vm->numClass->obj.classObj, "nan", num_nan); + PRIMITIVE(vm->numClass->obj.classObj, "pi", num_pi); + PRIMITIVE(vm->numClass->obj.classObj, "tau", num_tau); + PRIMITIVE(vm->numClass->obj.classObj, "largest", num_largest); + PRIMITIVE(vm->numClass->obj.classObj, "smallest", num_smallest); + PRIMITIVE(vm->numClass->obj.classObj, "maxSafeInteger", num_maxSafeInteger); + PRIMITIVE(vm->numClass->obj.classObj, "minSafeInteger", num_minSafeInteger); + PRIMITIVE(vm->numClass, "-(_)", num_minus); + PRIMITIVE(vm->numClass, "+(_)", num_plus); + PRIMITIVE(vm->numClass, "*(_)", num_multiply); + PRIMITIVE(vm->numClass, "/(_)", num_divide); + PRIMITIVE(vm->numClass, "<(_)", num_lt); + PRIMITIVE(vm->numClass, ">(_)", num_gt); + PRIMITIVE(vm->numClass, "<=(_)", num_lte); + PRIMITIVE(vm->numClass, ">=(_)", num_gte); + PRIMITIVE(vm->numClass, "&(_)", num_bitwiseAnd); + PRIMITIVE(vm->numClass, "|(_)", num_bitwiseOr); + PRIMITIVE(vm->numClass, "^(_)", num_bitwiseXor); + PRIMITIVE(vm->numClass, "<<(_)", num_bitwiseLeftShift); + PRIMITIVE(vm->numClass, ">>(_)", num_bitwiseRightShift); + PRIMITIVE(vm->numClass, "abs", num_abs); + PRIMITIVE(vm->numClass, "acos", num_acos); + PRIMITIVE(vm->numClass, "asin", num_asin); + PRIMITIVE(vm->numClass, "atan", num_atan); + PRIMITIVE(vm->numClass, "cbrt", num_cbrt); + PRIMITIVE(vm->numClass, "ceil", num_ceil); + PRIMITIVE(vm->numClass, "cos", num_cos); + PRIMITIVE(vm->numClass, "floor", num_floor); + PRIMITIVE(vm->numClass, "-", num_negate); + PRIMITIVE(vm->numClass, "round", num_round); + PRIMITIVE(vm->numClass, "min(_)", num_min); + PRIMITIVE(vm->numClass, "max(_)", num_max); + PRIMITIVE(vm->numClass, "clamp(_,_)", num_clamp); + PRIMITIVE(vm->numClass, "sin", num_sin); + PRIMITIVE(vm->numClass, "sqrt", num_sqrt); + PRIMITIVE(vm->numClass, "tan", num_tan); + PRIMITIVE(vm->numClass, "log", num_log); + PRIMITIVE(vm->numClass, "log2", num_log2); + PRIMITIVE(vm->numClass, "exp", num_exp); + PRIMITIVE(vm->numClass, "%(_)", num_mod); + PRIMITIVE(vm->numClass, "~", num_bitwiseNot); + PRIMITIVE(vm->numClass, "..(_)", num_dotDot); + PRIMITIVE(vm->numClass, "...(_)", num_dotDotDot); + PRIMITIVE(vm->numClass, "atan(_)", num_atan2); + PRIMITIVE(vm->numClass, "pow(_)", num_pow); + PRIMITIVE(vm->numClass, "fraction", num_fraction); + PRIMITIVE(vm->numClass, "isInfinity", num_isInfinity); + PRIMITIVE(vm->numClass, "isInteger", num_isInteger); + PRIMITIVE(vm->numClass, "isNan", num_isNan); + PRIMITIVE(vm->numClass, "sign", num_sign); + PRIMITIVE(vm->numClass, "toString", num_toString); + PRIMITIVE(vm->numClass, "truncate", num_truncate); + + // These are defined just so that 0 and -0 are equal, which is specified by + // IEEE 754 even though they have different bit representations. + PRIMITIVE(vm->numClass, "==(_)", num_eqeq); + PRIMITIVE(vm->numClass, "!=(_)", num_bangeq); + + vm->stringClass = AS_CLASS(wrenFindVariable(vm, coreModule, "String")); + PRIMITIVE(vm->stringClass->obj.classObj, "fromCodePoint(_)", string_fromCodePoint); + PRIMITIVE(vm->stringClass->obj.classObj, "fromByte(_)", string_fromByte); + PRIMITIVE(vm->stringClass, "+(_)", string_plus); + PRIMITIVE(vm->stringClass, "[_]", string_subscript); + PRIMITIVE(vm->stringClass, "byteAt_(_)", string_byteAt); + PRIMITIVE(vm->stringClass, "byteCount_", string_byteCount); + PRIMITIVE(vm->stringClass, "codePointAt_(_)", string_codePointAt); + PRIMITIVE(vm->stringClass, "contains(_)", string_contains); + PRIMITIVE(vm->stringClass, "endsWith(_)", string_endsWith); + PRIMITIVE(vm->stringClass, "indexOf(_)", string_indexOf1); + PRIMITIVE(vm->stringClass, "indexOf(_,_)", string_indexOf2); + PRIMITIVE(vm->stringClass, "iterate(_)", string_iterate); + PRIMITIVE(vm->stringClass, "iterateByte_(_)", string_iterateByte); + PRIMITIVE(vm->stringClass, "iteratorValue(_)", string_iteratorValue); + PRIMITIVE(vm->stringClass, "startsWith(_)", string_startsWith); + PRIMITIVE(vm->stringClass, "toString", string_toString); + + vm->listClass = AS_CLASS(wrenFindVariable(vm, coreModule, "List")); + PRIMITIVE(vm->listClass->obj.classObj, "filled(_,_)", list_filled); + PRIMITIVE(vm->listClass->obj.classObj, "new()", list_new); + PRIMITIVE(vm->listClass, "[_]", list_subscript); + PRIMITIVE(vm->listClass, "[_]=(_)", list_subscriptSetter); + PRIMITIVE(vm->listClass, "add(_)", list_add); + PRIMITIVE(vm->listClass, "addCore_(_)", list_addCore); + PRIMITIVE(vm->listClass, "clear()", list_clear); + PRIMITIVE(vm->listClass, "count", list_count); + PRIMITIVE(vm->listClass, "insert(_,_)", list_insert); + PRIMITIVE(vm->listClass, "iterate(_)", list_iterate); + PRIMITIVE(vm->listClass, "iteratorValue(_)", list_iteratorValue); + PRIMITIVE(vm->listClass, "removeAt(_)", list_removeAt); + PRIMITIVE(vm->listClass, "remove(_)", list_removeValue); + PRIMITIVE(vm->listClass, "indexOf(_)", list_indexOf); + PRIMITIVE(vm->listClass, "swap(_,_)", list_swap); + + vm->mapClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Map")); + PRIMITIVE(vm->mapClass->obj.classObj, "new()", map_new); + PRIMITIVE(vm->mapClass, "[_]", map_subscript); + PRIMITIVE(vm->mapClass, "[_]=(_)", map_subscriptSetter); + PRIMITIVE(vm->mapClass, "addCore_(_,_)", map_addCore); + PRIMITIVE(vm->mapClass, "clear()", map_clear); + PRIMITIVE(vm->mapClass, "containsKey(_)", map_containsKey); + PRIMITIVE(vm->mapClass, "count", map_count); + PRIMITIVE(vm->mapClass, "remove(_)", map_remove); + PRIMITIVE(vm->mapClass, "iterate(_)", map_iterate); + PRIMITIVE(vm->mapClass, "keyIteratorValue_(_)", map_keyIteratorValue); + PRIMITIVE(vm->mapClass, "valueIteratorValue_(_)", map_valueIteratorValue); + + vm->rangeClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Range")); + PRIMITIVE(vm->rangeClass, "from", range_from); + PRIMITIVE(vm->rangeClass, "to", range_to); + PRIMITIVE(vm->rangeClass, "min", range_min); + PRIMITIVE(vm->rangeClass, "max", range_max); + PRIMITIVE(vm->rangeClass, "isInclusive", range_isInclusive); + PRIMITIVE(vm->rangeClass, "iterate(_)", range_iterate); + PRIMITIVE(vm->rangeClass, "iteratorValue(_)", range_iteratorValue); + PRIMITIVE(vm->rangeClass, "toString", range_toString); + + ObjClass* systemClass = AS_CLASS(wrenFindVariable(vm, coreModule, "System")); + PRIMITIVE(systemClass->obj.classObj, "clock", system_clock); + PRIMITIVE(systemClass->obj.classObj, "gc()", system_gc); + PRIMITIVE(systemClass->obj.classObj, "writeString_(_)", system_writeString); + + // While bootstrapping the core types and running the core module, a number + // of string objects have been created, many of which were instantiated + // before stringClass was stored in the VM. Some of them *must* be created + // first -- the ObjClass for string itself has a reference to the ObjString + // for its name. + // + // These all currently have a NULL classObj pointer, so go back and assign + // them now that the string class is known. + for (Obj* obj = vm->first; obj != NULL; obj = obj->next) { + if (obj->type == OBJ_STRING) + obj->classObj = vm->stringClass; + } } // End file "wren_core.c" // Begin file "wren_compiler.c" @@ -5206,7 +4988,6 @@ void wrenInitializeCore(WrenVM* vm) #include #include - #if WREN_DEBUG_DUMP_COMPILED_CODE #endif @@ -5249,355 +5030,343 @@ void wrenInitializeCore(WrenVM* vm) // 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 +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; +typedef struct { + TokenType type; - // The beginning of the token, pointing directly into the source. - const char* start; + // The beginning of the token, pointing directly into the source. + const char* start; - // The length of the token in characters. - int length; + // 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; + // 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; +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; +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 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; + // 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; + // 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; +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; + // 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; +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 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; + // 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; + // 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; + // 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 +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; +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; +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; +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 +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; +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 @@ -5605,264 +5374,224 @@ 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); +static void copyMethodAttributes(Compiler* compiler, bool isForeign, bool isStatic, const char* fullSignature, int32_t length); // The stack effect of each opcode. The index in the array is the opcode, and // the value is the stack effect of that instruction. static const int stackEffects[] = { - #define OPCODE(_, effect) effect, -// Begin file "wren_opcodes.h" -// This defines the bytecode instructions used by the VM. It does so by invoking -// an OPCODE() macro which is expected to be defined at the point that this is -// included. (See: http://en.wikipedia.org/wiki/X_Macro for more.) -// -// The first argument is the name of the opcode. The second is its "stack -// effect" -- the amount that the op code changes the size of the stack. A -// stack effect of 1 means it pushes a value and the stack grows one larger. -// -2 means it pops two values, etc. -// -// Note that the order of instructions here affects the order of the dispatch -// table in the VM's interpreter loop. That in turn affects caching which -// affects overall performance. Take care to run benchmarks if you change the -// order here. - -// Load the constant at index [arg]. -OPCODE(CONSTANT, 1) - -// Push null onto the stack. -OPCODE(NULL, 1) - -// Push false onto the stack. -OPCODE(FALSE, 1) - -// Push true onto the stack. -OPCODE(TRUE, 1) - -// Pushes the value in the given local slot. -OPCODE(LOAD_LOCAL_0, 1) -OPCODE(LOAD_LOCAL_1, 1) -OPCODE(LOAD_LOCAL_2, 1) -OPCODE(LOAD_LOCAL_3, 1) -OPCODE(LOAD_LOCAL_4, 1) -OPCODE(LOAD_LOCAL_5, 1) -OPCODE(LOAD_LOCAL_6, 1) -OPCODE(LOAD_LOCAL_7, 1) -OPCODE(LOAD_LOCAL_8, 1) - -// Note: The compiler assumes the following _STORE instructions always -// immediately follow their corresponding _LOAD ones. - -// Pushes the value in local slot [arg]. -OPCODE(LOAD_LOCAL, 1) - -// Stores the top of stack in local slot [arg]. Does not pop it. -OPCODE(STORE_LOCAL, 0) - -// Pushes the value in upvalue [arg]. -OPCODE(LOAD_UPVALUE, 1) - -// Stores the top of stack in upvalue [arg]. Does not pop it. -OPCODE(STORE_UPVALUE, 0) - -// Pushes the value of the top-level variable in slot [arg]. -OPCODE(LOAD_MODULE_VAR, 1) - -// Stores the top of stack in top-level variable slot [arg]. Does not pop it. -OPCODE(STORE_MODULE_VAR, 0) - -// Pushes the value of the field in slot [arg] of the receiver of the current -// function. This is used for regular field accesses on "this" directly in -// methods. This instruction is faster than the more general CODE_LOAD_FIELD -// instruction. -OPCODE(LOAD_FIELD_THIS, 1) - -// Stores the top of the stack in field slot [arg] in the receiver of the -// current value. Does not pop the value. This instruction is faster than the -// more general CODE_LOAD_FIELD instruction. -OPCODE(STORE_FIELD_THIS, 0) - -// Pops an instance and pushes the value of the field in slot [arg] of it. -OPCODE(LOAD_FIELD, 0) - -// Pops an instance and stores the subsequent top of stack in field slot -// [arg] in it. Does not pop the value. -OPCODE(STORE_FIELD, -1) - -// Pop and discard the top of stack. -OPCODE(POP, -1) - -// Invoke the method with symbol [arg]. The number indicates the number of -// arguments (not including the receiver). -OPCODE(CALL_0, 0) -OPCODE(CALL_1, -1) -OPCODE(CALL_2, -2) -OPCODE(CALL_3, -3) -OPCODE(CALL_4, -4) -OPCODE(CALL_5, -5) -OPCODE(CALL_6, -6) -OPCODE(CALL_7, -7) -OPCODE(CALL_8, -8) -OPCODE(CALL_9, -9) -OPCODE(CALL_10, -10) -OPCODE(CALL_11, -11) -OPCODE(CALL_12, -12) -OPCODE(CALL_13, -13) -OPCODE(CALL_14, -14) -OPCODE(CALL_15, -15) -OPCODE(CALL_16, -16) - -// Invoke a superclass method with symbol [arg]. The number indicates the -// number of arguments (not including the receiver). -OPCODE(SUPER_0, 0) -OPCODE(SUPER_1, -1) -OPCODE(SUPER_2, -2) -OPCODE(SUPER_3, -3) -OPCODE(SUPER_4, -4) -OPCODE(SUPER_5, -5) -OPCODE(SUPER_6, -6) -OPCODE(SUPER_7, -7) -OPCODE(SUPER_8, -8) -OPCODE(SUPER_9, -9) -OPCODE(SUPER_10, -10) -OPCODE(SUPER_11, -11) -OPCODE(SUPER_12, -12) -OPCODE(SUPER_13, -13) -OPCODE(SUPER_14, -14) -OPCODE(SUPER_15, -15) -OPCODE(SUPER_16, -16) - -// Jump the instruction pointer [arg] forward. -OPCODE(JUMP, 0) - -// Jump the instruction pointer [arg] backward. -OPCODE(LOOP, 0) - -// Pop and if not truthy then jump the instruction pointer [arg] forward. -OPCODE(JUMP_IF, -1) - -// If the top of the stack is false, jump [arg] forward. Otherwise, pop and -// continue. -OPCODE(AND, -1) - -// If the top of the stack is non-false, jump [arg] forward. Otherwise, pop -// and continue. -OPCODE(OR, -1) - -// Close the upvalue for the local on the top of the stack, then pop it. -OPCODE(CLOSE_UPVALUE, -1) - -// Exit from the current function and return the value on the top of the -// stack. -OPCODE(RETURN, 0) - -// Creates a closure for the function stored at [arg] in the constant table. -// -// Following the function argument is a number of arguments, two for each -// upvalue. The first is true if the variable being captured is a local (as -// opposed to an upvalue), and the second is the index of the local or -// upvalue being captured. -// -// Pushes the created closure. -OPCODE(CLOSURE, 1) - -// Creates a new instance of a class. -// -// Assumes the class object is in slot zero, and replaces it with the new -// uninitialized instance of that class. This opcode is only emitted by the -// compiler-generated constructor metaclass methods. -OPCODE(CONSTRUCT, 0) - -// Creates a new instance of a foreign class. -// -// Assumes the class object is in slot zero, and replaces it with the new -// uninitialized instance of that class. This opcode is only emitted by the -// compiler-generated constructor metaclass methods. -OPCODE(FOREIGN_CONSTRUCT, 0) - -// Creates a class. Top of stack is the superclass. Below that is a string for -// the name of the class. Byte [arg] is the number of fields in the class. -OPCODE(CLASS, -1) - -// Ends a class. -// Atm the stack contains the class and the ClassAttributes (or null). -OPCODE(END_CLASS, -2) - -// Creates a foreign class. Top of stack is the superclass. Below that is a -// string for the name of the class. -OPCODE(FOREIGN_CLASS, -1) - -// Define a method for symbol [arg]. The class receiving the method is popped -// off the stack, then the function defining the body is popped. -// -// If a foreign method is being defined, the "function" will be a string -// identifying the foreign method. Otherwise, it will be a function or -// closure. -OPCODE(METHOD_INSTANCE, -2) - -// Define a method for symbol [arg]. The class whose metaclass will receive -// the method is popped off the stack, then the function defining the body is -// popped. -// -// If a foreign method is being defined, the "function" will be a string -// identifying the foreign method. Otherwise, it will be a function or -// closure. -OPCODE(METHOD_STATIC, -2) - -// This is executed at the end of the module's body. Pushes NULL onto the stack -// as the "return value" of the import statement and stores the module as the -// most recently imported one. -OPCODE(END_MODULE, 1) - -// Import a module whose name is the string stored at [arg] in the constant -// table. -// -// Pushes null onto the stack so that the fiber for the imported module can -// replace that with a dummy value when it returns. (Fibers always return a -// value when resuming a caller.) -OPCODE(IMPORT_MODULE, 1) - -// Import a variable from the most recently imported module. The name of the -// variable to import is at [arg] in the constant table. Pushes the loaded -// variable's value. -OPCODE(IMPORT_VARIABLE, 1) - -// This pseudo-instruction indicates the end of the bytecode. It should -// always be preceded by a `CODE_RETURN`, so is never actually executed. -OPCODE(END, 0) +#define OPCODE(_, effect) effect, + // Begin file "wren_opcodes.h" + // This defines the bytecode instructions used by the VM. It does so by invoking + // an OPCODE() macro which is expected to be defined at the point that this is + // included. (See: http://en.wikipedia.org/wiki/X_Macro for more.) + // + // The first argument is the name of the opcode. The second is its "stack + // effect" -- the amount that the op code changes the size of the stack. A + // stack effect of 1 means it pushes a value and the stack grows one larger. + // -2 means it pops two values, etc. + // + // Note that the order of instructions here affects the order of the dispatch + // table in the VM's interpreter loop. That in turn affects caching which + // affects overall performance. Take care to run benchmarks if you change the + // order here. + + // Load the constant at index [arg]. + OPCODE(CONSTANT, 1) + + // Push null onto the stack. + OPCODE(NULL, 1) + + // Push false onto the stack. + OPCODE(FALSE, 1) + + // Push true onto the stack. + OPCODE(TRUE, 1) + + // Pushes the value in the given local slot. + OPCODE(LOAD_LOCAL_0, 1) OPCODE(LOAD_LOCAL_1, 1) OPCODE(LOAD_LOCAL_2, 1) OPCODE(LOAD_LOCAL_3, 1) OPCODE(LOAD_LOCAL_4, 1) OPCODE(LOAD_LOCAL_5, 1) OPCODE(LOAD_LOCAL_6, 1) OPCODE(LOAD_LOCAL_7, 1) + OPCODE(LOAD_LOCAL_8, 1) + + // Note: The compiler assumes the following _STORE instructions always + // immediately follow their corresponding _LOAD ones. + + // Pushes the value in local slot [arg]. + OPCODE(LOAD_LOCAL, 1) + + // Stores the top of stack in local slot [arg]. Does not pop it. + OPCODE(STORE_LOCAL, 0) + + // Pushes the value in upvalue [arg]. + OPCODE(LOAD_UPVALUE, 1) + + // Stores the top of stack in upvalue [arg]. Does not pop it. + OPCODE(STORE_UPVALUE, 0) + + // Pushes the value of the top-level variable in slot [arg]. + OPCODE(LOAD_MODULE_VAR, 1) + + // Stores the top of stack in top-level variable slot [arg]. Does not pop it. + OPCODE(STORE_MODULE_VAR, 0) + + // Pushes the value of the field in slot [arg] of the receiver of the current + // function. This is used for regular field accesses on "this" directly in + // methods. This instruction is faster than the more general CODE_LOAD_FIELD + // instruction. + OPCODE(LOAD_FIELD_THIS, 1) + + // Stores the top of the stack in field slot [arg] in the receiver of the + // current value. Does not pop the value. This instruction is faster than the + // more general CODE_LOAD_FIELD instruction. + OPCODE(STORE_FIELD_THIS, 0) + + // Pops an instance and pushes the value of the field in slot [arg] of it. + OPCODE(LOAD_FIELD, 0) + + // Pops an instance and stores the subsequent top of stack in field slot + // [arg] in it. Does not pop the value. + OPCODE(STORE_FIELD, -1) + + // Pop and discard the top of stack. + OPCODE(POP, -1) + + // Invoke the method with symbol [arg]. The number indicates the number of + // arguments (not including the receiver). + OPCODE(CALL_0, 0) OPCODE(CALL_1, -1) OPCODE(CALL_2, -2) OPCODE(CALL_3, -3) OPCODE(CALL_4, -4) OPCODE(CALL_5, -5) OPCODE(CALL_6, -6) OPCODE(CALL_7, -7) OPCODE(CALL_8, -8) OPCODE(CALL_9, -9) + OPCODE(CALL_10, -10) OPCODE(CALL_11, -11) OPCODE(CALL_12, -12) OPCODE(CALL_13, -13) OPCODE(CALL_14, -14) OPCODE(CALL_15, -15) OPCODE(CALL_16, -16) + + // Invoke a superclass method with symbol [arg]. The number indicates the + // number of arguments (not including the receiver). + OPCODE(SUPER_0, 0) OPCODE(SUPER_1, -1) OPCODE(SUPER_2, -2) OPCODE(SUPER_3, -3) OPCODE(SUPER_4, -4) OPCODE(SUPER_5, -5) OPCODE(SUPER_6, -6) OPCODE(SUPER_7, -7) OPCODE(SUPER_8, -8) + OPCODE(SUPER_9, -9) OPCODE(SUPER_10, -10) OPCODE(SUPER_11, -11) OPCODE(SUPER_12, -12) OPCODE(SUPER_13, -13) OPCODE(SUPER_14, -14) OPCODE(SUPER_15, -15) OPCODE(SUPER_16, -16) + + // Jump the instruction pointer [arg] forward. + OPCODE(JUMP, 0) + + // Jump the instruction pointer [arg] backward. + OPCODE(LOOP, 0) + + // Pop and if not truthy then jump the instruction pointer [arg] forward. + OPCODE(JUMP_IF, -1) + + // If the top of the stack is false, jump [arg] forward. Otherwise, pop and + // continue. + OPCODE(AND, -1) + + // If the top of the stack is non-false, jump [arg] forward. Otherwise, pop + // and continue. + OPCODE(OR, -1) + + // Close the upvalue for the local on the top of the stack, then pop it. + OPCODE(CLOSE_UPVALUE, -1) + + // Exit from the current function and return the value on the top of the + // stack. + OPCODE(RETURN, 0) + + // Creates a closure for the function stored at [arg] in the constant table. + // + // Following the function argument is a number of arguments, two for each + // upvalue. The first is true if the variable being captured is a local (as + // opposed to an upvalue), and the second is the index of the local or + // upvalue being captured. + // + // Pushes the created closure. + OPCODE(CLOSURE, 1) + + // Creates a new instance of a class. + // + // Assumes the class object is in slot zero, and replaces it with the new + // uninitialized instance of that class. This opcode is only emitted by the + // compiler-generated constructor metaclass methods. + OPCODE(CONSTRUCT, 0) + + // Creates a new instance of a foreign class. + // + // Assumes the class object is in slot zero, and replaces it with the new + // uninitialized instance of that class. This opcode is only emitted by the + // compiler-generated constructor metaclass methods. + OPCODE(FOREIGN_CONSTRUCT, 0) + + // Creates a class. Top of stack is the superclass. Below that is a string for + // the name of the class. Byte [arg] is the number of fields in the class. + OPCODE(CLASS, -1) + + // Ends a class. + // Atm the stack contains the class and the ClassAttributes (or null). + OPCODE(END_CLASS, -2) + + // Creates a foreign class. Top of stack is the superclass. Below that is a + // string for the name of the class. + OPCODE(FOREIGN_CLASS, -1) + + // Define a method for symbol [arg]. The class receiving the method is popped + // off the stack, then the function defining the body is popped. + // + // If a foreign method is being defined, the "function" will be a string + // identifying the foreign method. Otherwise, it will be a function or + // closure. + OPCODE(METHOD_INSTANCE, -2) + + // Define a method for symbol [arg]. The class whose metaclass will receive + // the method is popped off the stack, then the function defining the body is + // popped. + // + // If a foreign method is being defined, the "function" will be a string + // identifying the foreign method. Otherwise, it will be a function or + // closure. + OPCODE(METHOD_STATIC, -2) + + // This is executed at the end of the module's body. Pushes NULL onto the stack + // as the "return value" of the import statement and stores the module as the + // most recently imported one. + OPCODE(END_MODULE, 1) + + // Import a module whose name is the string stored at [arg] in the constant + // table. + // + // Pushes null onto the stack so that the fiber for the imported module can + // replace that with a dummy value when it returns. (Fibers always return a + // value when resuming a caller.) + OPCODE(IMPORT_MODULE, 1) + + // Import a variable from the most recently imported module. The name of the + // variable to import is at [arg] in the constant table. Pushes the loaded + // variable's value. + OPCODE(IMPORT_VARIABLE, 1) + + // This pseudo-instruction indicates the end of the bytecode. It should + // always be preceded by a `CODE_RETURN`, so is never actually executed. + OPCODE(END, 0) // End file "wren_opcodes.h" - #undef OPCODE +#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; +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; + // 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."); + // 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 : ""; + 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); + 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); +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 @@ -5873,1030 +5602,954 @@ static void lexError(Parser* parser, const char* format, ...) // 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); +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; +static int addConstant(Compiler* compiler, Value constant) { + if (compiler->parser->hasError) + return -1; + + // See if we already have a constant for the value. If so, reuse it. + if (compiler->constants != NULL) { + Value existing = wrenMapGet(compiler->constants, constant); + if (IS_NUM(existing)) + return (int)AS_NUM(existing); + } + + // It's a new constant. + if (compiler->fn->constants.count < MAX_CONSTANTS) { + if (IS_OBJ(constant)) + wrenPushRoot(compiler->parser->vm, AS_OBJ(constant)); + wrenValueBufferWrite(compiler->parser->vm, &compiler->fn->constants, constant); + if (IS_OBJ(constant)) + wrenPopRoot(compiler->parser->vm); + + if (compiler->constants == NULL) { + compiler->constants = wrenNewMap(compiler->parser->vm); + } + wrenMapSet(compiler->parser->vm, compiler->constants, constant, NUM_VAL(compiler->fn->constants.count - 1)); + } else { + error(compiler, "A function may only contain %d unique constants.", MAX_CONSTANTS); + } + + return compiler->fn->constants.count - 1; } // Initializes [compiler]. -static void initCompiler(Compiler* compiler, Parser* parser, Compiler* parent, - bool isMethod) -{ - compiler->parser = parser; - compiler->parent = parent; - compiler->loop = NULL; - compiler->enclosingClass = NULL; - compiler->isInitializer = false; - - // Initialize these to NULL before allocating in case a GC gets triggered in - // the middle of initializing the compiler. - compiler->fn = NULL; - compiler->constants = NULL; - compiler->attributes = NULL; - - parser->vm->compiler = compiler; - - // Declare a local slot for either the closure or method receiver so that we - // don't try to reuse that slot for a user-defined local variable. For - // methods, we name it "this", so that we can resolve references to that like - // a normal variable. For functions, they have no explicit "this", so we use - // an empty name. That way references to "this" inside a function walks up - // the parent chain to find a method enclosing the function whose "this" we - // can close over. - compiler->numLocals = 1; - compiler->numSlots = compiler->numLocals; - - if (isMethod) - { - compiler->locals[0].name = "this"; - compiler->locals[0].length = 4; - } - else - { - compiler->locals[0].name = NULL; - compiler->locals[0].length = 0; - } - - compiler->locals[0].depth = -1; - compiler->locals[0].isUpvalue = false; - - if (parent == NULL) - { - // Compiling top-level code, so the initial scope is module-level. - compiler->scopeDepth = -1; - } - else - { - // The initial scope for functions and methods is local scope. - compiler->scopeDepth = 0; - } - - compiler->numAttributes = 0; - compiler->attributes = wrenNewMap(parser->vm); - compiler->fn = wrenNewFunction(parser->vm, parser->module, - compiler->numLocals); +static void initCompiler(Compiler* compiler, Parser* parser, Compiler* parent, bool isMethod) { + compiler->parser = parser; + compiler->parent = parent; + compiler->loop = NULL; + compiler->enclosingClass = NULL; + compiler->isInitializer = false; + + // Initialize these to NULL before allocating in case a GC gets triggered in + // the middle of initializing the compiler. + compiler->fn = NULL; + compiler->constants = NULL; + compiler->attributes = NULL; + + parser->vm->compiler = compiler; + + // Declare a local slot for either the closure or method receiver so that we + // don't try to reuse that slot for a user-defined local variable. For + // methods, we name it "this", so that we can resolve references to that like + // a normal variable. For functions, they have no explicit "this", so we use + // an empty name. That way references to "this" inside a function walks up + // the parent chain to find a method enclosing the function whose "this" we + // can close over. + compiler->numLocals = 1; + compiler->numSlots = compiler->numLocals; + + if (isMethod) { + compiler->locals[0].name = "this"; + compiler->locals[0].length = 4; + } else { + compiler->locals[0].name = NULL; + compiler->locals[0].length = 0; + } + + compiler->locals[0].depth = -1; + compiler->locals[0].isUpvalue = false; + + if (parent == NULL) { + // Compiling top-level code, so the initial scope is module-level. + compiler->scopeDepth = -1; + } else { + // The initial scope for functions and methods is local scope. + compiler->scopeDepth = 0; + } + + compiler->numAttributes = 0; + compiler->attributes = wrenNewMap(parser->vm); + compiler->fn = wrenNewFunction(parser->vm, parser->module, compiler->numLocals); } // Lexing ---------------------------------------------------------------------- -typedef struct -{ - const char* identifier; - size_t length; - TokenType tokenType; +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. +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 == '_'; +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'; +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; +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); +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; +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; +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--; +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); +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); - } +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); - } +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; +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; + // 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); +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); +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; + // Iterate over all the valid hexadecimal digits found. + while (readHexDigit(parser) != -1) + continue; - makeNumber(parser, true); + 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); +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); +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; +static int readHexEscape(Parser* parser, int digits, const char* description) { + int value = 0; + for (int i = 0; i < digits; i++) { + if (peekChar(parser) == '"' || peekChar(parser) == '\0') { + lexError(parser, "Incomplete %s escape sequence.", description); + + // Don't consume it if it isn't expected. Keeps us from reading past the + // end of an unterminated string. + parser->currentChar--; + break; + } + + int digit = readHexDigit(parser); + if (digit == -1) { + lexError(parser, "Invalid %s escape sequence.", description); + break; + } + + value = (value * 16) | digit; + } + + return value; } // Reads a hex digit Unicode escape sequence in a string literal. -static void readUnicodeEscape(Parser* parser, ByteBuffer* string, int length) -{ - int value = readHexEscape(parser, length, "Unicode"); - - // Grow the buffer enough for the encoded result. - int numBytes = wrenUtf8EncodeNumBytes(value); - if (numBytes != 0) - { - wrenByteBufferFill(parser->vm, string, 0, numBytes); - wrenUtf8Encode(value, string->data + string->count - numBytes); - } -} - -static void readRawString(Parser* parser) -{ - ByteBuffer string; - wrenByteBufferInit(&string); - TokenType type = TOKEN_STRING; - - //consume the second and third " - nextChar(parser); - nextChar(parser); - - int skipStart = 0; - int firstNewline = -1; - - int skipEnd = -1; - int lastNewline = -1; - - for (;;) - { - char c = nextChar(parser); - char c1 = peekChar(parser); - char c2 = peekNextChar(parser); - - if (c == '\r') continue; - - if (c == '\n') { - lastNewline = string.count; - skipEnd = lastNewline; - firstNewline = firstNewline == -1 ? string.count : firstNewline; - } - - if (c == '"' && c1 == '"' && c2 == '"') break; - - bool isWhitespace = c == ' ' || c == '\t'; - skipEnd = c == '\n' || isWhitespace ? skipEnd : -1; - - // If we haven't seen a newline or other character yet, - // and still seeing whitespace, count the characters - // as skippable till we know otherwise - bool skippable = skipStart != -1 && isWhitespace && firstNewline == -1; - skipStart = skippable ? string.count + 1 : skipStart; - - // We've counted leading whitespace till we hit something else, - // but it's not a newline, so we reset skipStart since we need these characters - if (firstNewline == -1 && !isWhitespace && c != '\n') skipStart = -1; - - if (c == '\0' || c1 == '\0' || c2 == '\0') - { - lexError(parser, "Unterminated raw string."); - - // Don't consume it if it isn't expected. Keeps us from reading past the - // end of an unterminated string. - parser->currentChar--; - break; - } - - wrenByteBufferWrite(parser->vm, &string, c); - } - - //consume the second and third " - nextChar(parser); - nextChar(parser); - - int offset = 0; - int count = string.count; - - if(firstNewline != -1 && skipStart == firstNewline) offset = firstNewline + 1; - if(lastNewline != -1 && skipEnd == lastNewline) count = lastNewline; - - count -= (offset > count) ? count : offset; - - parser->next.value = wrenNewStringLength(parser->vm, - ((char*)string.data) + offset, count); - - wrenByteBufferClear(parser->vm, &string); - makeToken(parser, type); +static void readUnicodeEscape(Parser* parser, ByteBuffer* string, int length) { + int value = readHexEscape(parser, length, "Unicode"); + + // Grow the buffer enough for the encoded result. + int numBytes = wrenUtf8EncodeNumBytes(value); + if (numBytes != 0) { + wrenByteBufferFill(parser->vm, string, 0, numBytes); + wrenUtf8Encode(value, string->data + string->count - numBytes); + } +} + +static void readRawString(Parser* parser) { + ByteBuffer string; + wrenByteBufferInit(&string); + TokenType type = TOKEN_STRING; + + // consume the second and third " + nextChar(parser); + nextChar(parser); + + int skipStart = 0; + int firstNewline = -1; + + int skipEnd = -1; + int lastNewline = -1; + + for (;;) { + char c = nextChar(parser); + char c1 = peekChar(parser); + char c2 = peekNextChar(parser); + + if (c == '\r') + continue; + + if (c == '\n') { + lastNewline = string.count; + skipEnd = lastNewline; + firstNewline = firstNewline == -1 ? string.count : firstNewline; + } + + if (c == '"' && c1 == '"' && c2 == '"') + break; + + bool isWhitespace = c == ' ' || c == '\t'; + skipEnd = c == '\n' || isWhitespace ? skipEnd : -1; + + // If we haven't seen a newline or other character yet, + // and still seeing whitespace, count the characters + // as skippable till we know otherwise + bool skippable = skipStart != -1 && isWhitespace && firstNewline == -1; + skipStart = skippable ? string.count + 1 : skipStart; + + // We've counted leading whitespace till we hit something else, + // but it's not a newline, so we reset skipStart since we need these characters + if (firstNewline == -1 && !isWhitespace && c != '\n') + skipStart = -1; + + if (c == '\0' || c1 == '\0' || c2 == '\0') { + lexError(parser, "Unterminated raw string."); + + // Don't consume it if it isn't expected. Keeps us from reading past the + // end of an unterminated string. + parser->currentChar--; + break; + } + + wrenByteBufferWrite(parser->vm, &string, c); + } + + // consume the second and third " + nextChar(parser); + nextChar(parser); + + int offset = 0; + int count = string.count; + + if (firstNewline != -1 && skipStart == firstNewline) + offset = firstNewline + 1; + if (lastNewline != -1 && skipEnd == lastNewline) + count = lastNewline; + + count -= (offset > count) ? count : offset; + + parser->next.value = wrenNewStringLength(parser->vm, ((char*)string.data) + offset, count); + + wrenByteBufferClear(parser->vm, &string); + makeToken(parser, type); } // Finishes lexing a string literal. -static void readString(Parser* parser) -{ - ByteBuffer string; - TokenType type = TOKEN_STRING; - wrenByteBufferInit(&string); - - for (;;) - { - char c = nextChar(parser); - if (c == '"') break; - if (c == '\r') continue; - - if (c == '\0') - { - lexError(parser, "Unterminated string."); - - // Don't consume it if it isn't expected. Keeps us from reading past the - // end of an unterminated string. - parser->currentChar--; - break; - } - - if (c == '%') - { - if (parser->numParens < MAX_INTERPOLATION_NESTING) - { - // TODO: Allow format string. - if (nextChar(parser) != '(') lexError(parser, "Expect '(' after '%%'."); - - parser->parens[parser->numParens++] = 1; - type = TOKEN_INTERPOLATION; - break; - } - - lexError(parser, "Interpolation may only nest %d levels deep.", - MAX_INTERPOLATION_NESTING); - } - - if (c == '\\') - { - switch (nextChar(parser)) - { - case '"': wrenByteBufferWrite(parser->vm, &string, '"'); break; - case '\\': wrenByteBufferWrite(parser->vm, &string, '\\'); break; - case '%': wrenByteBufferWrite(parser->vm, &string, '%'); break; - case '0': wrenByteBufferWrite(parser->vm, &string, '\0'); break; - case 'a': wrenByteBufferWrite(parser->vm, &string, '\a'); break; - case 'b': wrenByteBufferWrite(parser->vm, &string, '\b'); break; - case 'e': wrenByteBufferWrite(parser->vm, &string, '\33'); break; - case 'f': wrenByteBufferWrite(parser->vm, &string, '\f'); break; - case 'n': wrenByteBufferWrite(parser->vm, &string, '\n'); break; - case 'r': wrenByteBufferWrite(parser->vm, &string, '\r'); break; - case 't': wrenByteBufferWrite(parser->vm, &string, '\t'); break; - case 'u': readUnicodeEscape(parser, &string, 4); break; - case 'U': readUnicodeEscape(parser, &string, 8); break; - case 'v': wrenByteBufferWrite(parser->vm, &string, '\v'); break; - case 'x': - wrenByteBufferWrite(parser->vm, &string, - (uint8_t)readHexEscape(parser, 2, "byte")); - break; - - default: - lexError(parser, "Invalid escape character '%c'.", - *(parser->currentChar - 1)); - break; - } - } - else - { - wrenByteBufferWrite(parser->vm, &string, c); - } - } - - parser->next.value = wrenNewStringLength(parser->vm, - (char*)string.data, string.count); - - wrenByteBufferClear(parser->vm, &string); - makeToken(parser, type); +static void readString(Parser* parser) { + ByteBuffer string; + TokenType type = TOKEN_STRING; + wrenByteBufferInit(&string); + + for (;;) { + char c = nextChar(parser); + if (c == '"') + break; + if (c == '\r') + continue; + + if (c == '\0') { + lexError(parser, "Unterminated string."); + + // Don't consume it if it isn't expected. Keeps us from reading past the + // end of an unterminated string. + parser->currentChar--; + break; + } + + if (c == '%') { + if (parser->numParens < MAX_INTERPOLATION_NESTING) { + // TODO: Allow format string. + if (nextChar(parser) != '(') + lexError(parser, "Expect '(' after '%%'."); + + parser->parens[parser->numParens++] = 1; + type = TOKEN_INTERPOLATION; + break; + } + + lexError(parser, "Interpolation may only nest %d levels deep.", MAX_INTERPOLATION_NESTING); + } + + if (c == '\\') { + switch (nextChar(parser)) { + case '"': + wrenByteBufferWrite(parser->vm, &string, '"'); + break; + case '\\': + wrenByteBufferWrite(parser->vm, &string, '\\'); + break; + case '%': + wrenByteBufferWrite(parser->vm, &string, '%'); + break; + case '0': + wrenByteBufferWrite(parser->vm, &string, '\0'); + break; + case 'a': + wrenByteBufferWrite(parser->vm, &string, '\a'); + break; + case 'b': + wrenByteBufferWrite(parser->vm, &string, '\b'); + break; + case 'e': + wrenByteBufferWrite(parser->vm, &string, '\33'); + break; + case 'f': + wrenByteBufferWrite(parser->vm, &string, '\f'); + break; + case 'n': + wrenByteBufferWrite(parser->vm, &string, '\n'); + break; + case 'r': + wrenByteBufferWrite(parser->vm, &string, '\r'); + break; + case 't': + wrenByteBufferWrite(parser->vm, &string, '\t'); + break; + case 'u': + readUnicodeEscape(parser, &string, 4); + break; + case 'U': + readUnicodeEscape(parser, &string, 8); + break; + case 'v': + wrenByteBufferWrite(parser->vm, &string, '\v'); + break; + case 'x': + wrenByteBufferWrite(parser->vm, &string, (uint8_t)readHexEscape(parser, 2, "byte")); + break; + + default: + lexError(parser, "Invalid escape character '%c'.", *(parser->currentChar - 1)); + break; + } + } else { + wrenByteBufferWrite(parser->vm, &string, c); + } + } + + parser->next.value = wrenNewStringLength(parser->vm, (char*)string.data, string.count); + + wrenByteBufferClear(parser->vm, &string); + makeToken(parser, type); } // Lex the next token and store it in [parser.next]. -static void nextToken(Parser* parser) -{ - parser->previous = parser->current; - parser->current = parser->next; - - // If we are out of tokens, don't try to tokenize any more. We *do* still - // copy the TOKEN_EOF to previous so that code that expects it to be consumed - // will still work. - if (parser->next.type == TOKEN_EOF) return; - if (parser->current.type == TOKEN_EOF) return; - - while (peekChar(parser) != '\0') - { - parser->tokenStart = parser->currentChar; - - char c = nextChar(parser); - switch (c) - { - case '(': - // If we are inside an interpolated expression, count the unmatched "(". - if (parser->numParens > 0) parser->parens[parser->numParens - 1]++; - makeToken(parser, TOKEN_LEFT_PAREN); - return; - - case ')': - // If we are inside an interpolated expression, count the ")". - if (parser->numParens > 0 && - --parser->parens[parser->numParens - 1] == 0) - { - // This is the final ")", so the interpolation expression has ended. - // This ")" now begins the next section of the template string. - parser->numParens--; - readString(parser); - return; - } - - makeToken(parser, TOKEN_RIGHT_PAREN); - return; - - case '[': makeToken(parser, TOKEN_LEFT_BRACKET); return; - case ']': makeToken(parser, TOKEN_RIGHT_BRACKET); return; - case '{': makeToken(parser, TOKEN_LEFT_BRACE); return; - case '}': makeToken(parser, TOKEN_RIGHT_BRACE); return; - case ':': makeToken(parser, TOKEN_COLON); return; - case ',': makeToken(parser, TOKEN_COMMA); return; - case '*': makeToken(parser, TOKEN_STAR); return; - case '%': makeToken(parser, TOKEN_PERCENT); return; - case '#': { - // Ignore shebang on the first line. - if (parser->currentLine == 1 && peekChar(parser) == '!' && peekNextChar(parser) == '/') - { - skipLineComment(parser); - break; - } - // Otherwise we treat it as a token - makeToken(parser, TOKEN_HASH); - return; - } - case '^': makeToken(parser, TOKEN_CARET); return; - case '+': makeToken(parser, TOKEN_PLUS); return; - case '-': makeToken(parser, TOKEN_MINUS); return; - case '~': makeToken(parser, TOKEN_TILDE); return; - case '?': makeToken(parser, TOKEN_QUESTION); return; - - case '|': twoCharToken(parser, '|', TOKEN_PIPEPIPE, TOKEN_PIPE); return; - case '&': twoCharToken(parser, '&', TOKEN_AMPAMP, TOKEN_AMP); return; - case '=': twoCharToken(parser, '=', TOKEN_EQEQ, TOKEN_EQ); return; - case '!': twoCharToken(parser, '=', TOKEN_BANGEQ, TOKEN_BANG); return; - - case '.': - if (matchChar(parser, '.')) - { - twoCharToken(parser, '.', TOKEN_DOTDOTDOT, TOKEN_DOTDOT); - return; - } - - makeToken(parser, TOKEN_DOT); - return; - - case '/': - if (matchChar(parser, '/')) - { - skipLineComment(parser); - break; - } - - if (matchChar(parser, '*')) - { - skipBlockComment(parser); - break; - } - - makeToken(parser, TOKEN_SLASH); - return; - - case '<': - if (matchChar(parser, '<')) - { - makeToken(parser, TOKEN_LTLT); - } - else - { - twoCharToken(parser, '=', TOKEN_LTEQ, TOKEN_LT); - } - return; - - case '>': - if (matchChar(parser, '>')) - { - makeToken(parser, TOKEN_GTGT); - } - else - { - twoCharToken(parser, '=', TOKEN_GTEQ, TOKEN_GT); - } - return; - - case '\n': - makeToken(parser, TOKEN_LINE); - return; - - case ' ': - case '\r': - case '\t': - // Skip forward until we run out of whitespace. - while (peekChar(parser) == ' ' || - peekChar(parser) == '\r' || - peekChar(parser) == '\t') - { - nextChar(parser); - } - break; - - case '"': { - if(peekChar(parser) == '"' && peekNextChar(parser) == '"') { - readRawString(parser); - return; - } - readString(parser); return; - } - case '_': - readName(parser, - peekChar(parser) == '_' ? TOKEN_STATIC_FIELD : TOKEN_FIELD, c); - return; - - case '0': - if (peekChar(parser) == 'x') - { - readHexNumber(parser); - return; - } - - readNumber(parser); - return; - - default: - if (isName(c)) - { - readName(parser, TOKEN_NAME, c); - } - else if (isDigit(c)) - { - readNumber(parser); - } - else - { - if (c >= 32 && c <= 126) - { - lexError(parser, "Invalid character '%c'.", c); - } - else - { - // Don't show non-ASCII values since we didn't UTF-8 decode the - // bytes. Since there are no non-ASCII byte values that are - // meaningful code units in Wren, the lexer works on raw bytes, - // even though the source code and console output are UTF-8. - lexError(parser, "Invalid byte 0x%x.", (uint8_t)c); - } - parser->next.type = TOKEN_ERROR; - parser->next.length = 0; - } - return; - } - } - - // If we get here, we're out of source, so just make EOF tokens. - parser->tokenStart = parser->currentChar; - makeToken(parser, TOKEN_EOF); +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; +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; +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; +static bool match(Compiler* compiler, TokenType expected) { + if (peek(compiler) != expected) + return false; - nextToken(compiler->parser); - return true; + 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); +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); - } + // 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; +static bool matchLine(Compiler* compiler) { + if (!match(compiler, TOKEN_LINE)) + return false; - while (match(compiler, TOKEN_LINE)); - return true; + while (match(compiler, TOKEN_LINE)) + ; + return true; } // Discards any newlines starting at the current token. -static void ignoreNewlines(Compiler* compiler) -{ - matchLine(compiler); +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 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); - } + 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; +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; - } +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); +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); +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); +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; +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); +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++; +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); +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); +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; +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); + // 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++; +static void pushScope(Compiler* compiler) { + compiler->scopeDepth++; } // Generates code to discard local variables at [depth] or greater. Does *not* @@ -6906,78 +6559,66 @@ static void pushScope(Compiler* compiler) // 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; +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--; +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; +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; - } +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++; + // 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 @@ -6991,174 +6632,161 @@ static int addUpvalue(Compiler* compiler, bool isLocal, int index) // // 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; +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; +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; + // 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; +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; + 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; - } +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); + 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; +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 +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; +typedef struct { + GrammarFn prefix; + GrammarFn infix; + SignatureFn method; + Precedence precedence; + const char* name; } GrammarRule; // Forward declarations since the grammar is recursive. @@ -7170,14 +6798,14 @@ 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."); +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; + 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. @@ -7185,659 +6813,570 @@ static void patchJump(Compiler* compiler, int offset) // 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; +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); +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); + 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); - } + // 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); + 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); - } +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); +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)); + // 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); +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; +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'; +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); +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); + 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; +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)); +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); + // 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)); - } +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); +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); +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); - } +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(); - } +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)); +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); +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."); +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."); -} +static void list(Compiler* compiler, bool canAssign) { + // Instantiate a new list. + loadCoreVariable(compiler, "List"); + callMethod(compiler, 0, "new()", 5); -// A map literal. -static void map(Compiler* compiler, bool canAssign) -{ - // Instantiate a new map. - loadCoreVariable(compiler, "Map"); - callMethod(compiler, 0, "new()", 5); + // Compile the list elements. Each one compiles to a ".add()" call. + do { + ignoreNewlines(compiler); - // 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 list. + if (peek(compiler) == TOKEN_RIGHT_BRACKET) + break; - // Stop if we hit the end of the map. - if (peek(compiler) == TOKEN_RIGHT_BRACE) break; + // The element. + expression(compiler); + callMethod(compiler, 1, "addCore_(_)", 11); + } while (match(compiler, TOKEN_COMMA)); - // The key. - parsePrecedence(compiler, PREC_UNARY); - consume(compiler, TOKEN_COLON, "Expect ':' after map key."); - ignoreNewlines(compiler); + // 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)); + // 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."); + // 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); +static void unaryOp(Compiler* compiler, bool canAssign) { + GrammarRule* rule = getRule(compiler->parser->previous.type); - ignoreNewlines(compiler); + ignoreNewlines(compiler); - // Compile the argument. - parsePrecedence(compiler, (Precedence)(PREC_UNARY + 1)); + // Compile the argument. + parsePrecedence(compiler, (Precedence)(PREC_UNARY + 1)); - // Call the operator method on the left-hand side. - callMethod(compiler, 0, rule->name, 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); +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; - } +static Compiler* getEnclosingClassCompiler(Compiler* compiler) { + while (compiler != NULL) { + if (compiler->enclosingClass != NULL) + return compiler; + compiler = compiler->parent; + } - return NULL; + 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); +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); +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); +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); +static void literal(Compiler* compiler, bool canAssign) { + emitConstant(compiler, compiler->parser->previous.value); } // A string literal that contains interpolated expressions. @@ -7850,737 +7389,690 @@ static void literal(Compiler* compiler, bool canAssign) // 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); +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 }; +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."); + // Parse the argument list. + finishArgumentList(compiler, &signature); + consume(compiler, TOKEN_RIGHT_BRACKET, "Expect ']' after arguments."); - allowLineBeforeDot(compiler); + allowLineBeforeDot(compiler); - if (canAssign && match(compiler, TOKEN_EQ)) - { - signature.type = SIG_SUBSCRIPT_SETTER; + if (canAssign && match(compiler, TOKEN_EQ)) { + signature.type = SIG_SUBSCRIPT_SETTER; - // Compile the assigned value. - validateNumParameters(compiler, ++signature.arity); - expression(compiler); - } + // Compile the assigned value. + validateNumParameters(compiler, ++signature.arity); + expression(compiler); + } - callSignature(compiler, CODE_CALL_0, &signature); + 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 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); +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); + // 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); +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); + // 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); +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); + // 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); + // Compile the then branch. + parsePrecedence(compiler, PREC_CONDITIONAL); - consume(compiler, TOKEN_COLON, - "Expect ':' after then branch of conditional operator."); - ignoreNewlines(compiler); + 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); + // Jump over the else branch when the if branch is taken. + int elseJump = emitJump(compiler, CODE_JUMP); - // Compile the else branch. - patchJump(compiler, ifJump); + // Compile the else branch. + patchJump(compiler, ifJump); - parsePrecedence(compiler, PREC_ASSIGNMENT); + parsePrecedence(compiler, PREC_ASSIGNMENT); - // Patch the jump over the else. - patchJump(compiler, elseJump); + // Patch the jump over the else. + patchJump(compiler, elseJump); } -void infixOp(Compiler* compiler, bool canAssign) -{ - GrammarRule* rule = getRule(compiler->parser->previous.type); +void infixOp(Compiler* compiler, bool canAssign) { + GrammarRule* rule = getRule(compiler->parser->previous.type); - // An infix operator cannot end an expression. - ignoreNewlines(compiler); + // An infix operator cannot end an expression. + ignoreNewlines(compiler); - // Compile the right-hand side. - parsePrecedence(compiler, (Precedence)(rule->precedence + 1)); + // 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); + // 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; +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."); + // 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; +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; +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; + // 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."); - } + // 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; +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; - } + // 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."); + // Parse the value parameter. + consume(compiler, TOKEN_LEFT_PAREN, "Expect '(' after '='."); + declareNamedVariable(compiler); + consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after parameter name."); - signature->arity++; + signature->arity++; - return true; + return true; } // Compiles a method signature for a subscript operator. -void subscriptSignature(Compiler* compiler, Signature* signature) -{ - signature->type = SIG_SUBSCRIPT; +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; + // 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."); + // Parse the parameters inside the subscript. + finishParameterList(compiler, signature); + consume(compiler, TOKEN_RIGHT_BRACKET, "Expect ']' after parameters."); - maybeSetter(compiler, signature); + 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); +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; + // Allow an empty parameter list. + if (match(compiler, TOKEN_RIGHT_PAREN)) + return; - finishParameterList(compiler, signature); - consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after parameters."); + 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; +void namedSignature(Compiler* compiler, Signature* signature) { + signature->type = SIG_GETTER; - // Regular named method with an optional parameter list. - parameterList(compiler, signature); + // 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."); +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 +#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]; +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); - } +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); +void expression(Compiler* compiler) { + parsePrecedence(compiler, PREC_LOWEST); } -// Returns the number of bytes for the arguments to the instruction +// 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; +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; +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); +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); +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); +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 @@ -8589,94 +8081,71 @@ static void whileStatement(Compiler* compiler) // 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); - } +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] @@ -8693,354 +8162,313 @@ void statement(Compiler* compiler) // 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); +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); +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); + // 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 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; + 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; +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); +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. @@ -9057,497 +8485,448 @@ static void classDefinition(Compiler* compiler, bool isForeign) // * 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)); +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); +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); +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, +// 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; - } +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); +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 +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); - } +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"); +static void emitClassAttributes(Compiler* compiler, ClassInfo* classInfo) { + loadCoreVariable(compiler, "ClassAttributes"); - classInfo->classAttributes - ? emitAttributes(compiler, classInfo->classAttributes) - : null(compiler, false); + classInfo->classAttributes ? emitAttributes(compiler, classInfo->classAttributes) : null(compiler, false); - classInfo->methodAttributes - ? emitAttributeMethods(compiler, classInfo->methodAttributes) - : null(compiler, false); + classInfo->methodAttributes ? emitAttributeMethods(compiler, classInfo->methodAttributes) : null(compiler, false); - callMethod(compiler, 2, "new(_,_)", 8); + 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); +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); +static void copyMethodAttributes(Compiler* compiler, bool isForeign, bool isStatic, const char* fullSignature, int32_t length) { + compiler->numAttributes = 0; + + if (compiler->attributes->count == 0) + return; + + WrenVM* vm = compiler->parser->vm; + + // Make a map for this method to copy into + ObjMap* methodAttr = wrenNewMap(vm); + wrenPushRoot(vm, (Obj*)methodAttr); + copyAttributes(compiler, methodAttr); + + // Include 'foreign static ' in front as needed + int32_t fullLength = length; + if (isForeign) + fullLength += 8; + if (isStatic) + fullLength += 7; + char fullSignatureWithPrefix[MAX_METHOD_SIGNATURE + 8 + 7]; + const char* foreignPrefix = isForeign ? "foreign " : ""; + const char* staticPrefix = isStatic ? "static " : ""; + sprintf(fullSignatureWithPrefix, "%s%s%.*s", foreignPrefix, staticPrefix, length, fullSignature); + fullSignatureWithPrefix[fullLength] = '\0'; + + if (compiler->enclosingClass->methodAttributes == NULL) { + compiler->enclosingClass->methodAttributes = wrenNewMap(vm); + } + + // Store the method attributes in the class map + Value key = wrenNewStringLength(vm, fullSignatureWithPrefix, fullLength); + wrenMapSet(vm, compiler->enclosingClass->methodAttributes, key, OBJ_VAL(methodAttr)); + + wrenPopRoot(vm); } // End file "wren_compiler.c" // Begin file "wren_value.c" @@ -9556,7 +8935,6 @@ static void copyMethodAttributes(Compiler* compiler, bool isForeign, #include #include - #if WREN_DEBUG_TRACE_MEMORY #endif @@ -9583,420 +8961,379 @@ static void copyMethodAttributes(Compiler* compiler, bool isForeign, 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; +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; +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); + wrenPushRoot(vm, (Obj*)classObj); + wrenMethodBufferInit(&classObj->methods); + wrenPopRoot(vm); - return classObj; + return classObj; } -void wrenBindSuperclass(WrenVM* vm, ObjClass* subclass, ObjClass* superclass) -{ - ASSERT(superclass != NULL, "Must have superclass."); +void wrenBindSuperclass(WrenVM* vm, ObjClass* subclass, ObjClass* superclass) { + ASSERT(superclass != NULL, "Must have superclass."); + + subclass->superclass = 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."); + } - // 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); + // 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)); +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; - } +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. +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)); + if (IS_OBJ(value)) + return hashObject(AS_OBJ(value)); - // Hash the raw bits of the unboxed value. - return hashBits(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; + 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 } @@ -10005,219 +9342,197 @@ static uint32_t hashValue(Value value) // 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; +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; - } +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; -} +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); + } + } -Value wrenMapGet(ObjMap* map, Value key) -{ - MapEntry* entry; - if (findEntry(map->entries, map->capacity, key, &entry)) return 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; + 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; +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); - } + resizeMap(vm, map, capacity); + } - if (insertEntry(map->entries, map->capacity, key, value)) - { - // A new key was added. - map->count++; - } + 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; +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; +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; + // 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)); + if (IS_OBJ(value)) + wrenPushRoot(vm, AS_OBJ(value)); - map->count--; + 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; + 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); - } + // 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; + if (IS_OBJ(value)) + wrenPopRoot(vm); + return value; } -ObjModule* wrenNewModule(WrenVM* vm, ObjString* name) -{ - ObjModule* module = ALLOCATE(vm, ObjModule); +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); + // 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); + wrenPushRoot(vm, (Obj*)module); - wrenSymbolTableInit(&module->variableNames); - wrenValueBufferInit(&module->variables); + wrenSymbolTableInit(&module->variableNames); + wrenValueBufferInit(&module->variables); - module->name = name; + module->name = name; - wrenPopRoot(vm); - return module; + 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; +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); + return OBJ_VAL(range); } // Creates a new string object with a null-terminated buffer large enough to @@ -10225,678 +9540,631 @@ Value wrenNewRange(WrenVM* vm, double from, double to, bool isInclusive) // // 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'; +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; + 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); +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; +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); - } +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); - } + // 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; - } + // 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); + // 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); + // 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); +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? + // 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 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); +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]); - } + // 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; + // 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); +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; + // 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; +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); - } + 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; + // 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]); - } +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); + wrenBlackenSymbolTable(vm, &module->variableNames); - wrenGrayObj(vm, (Obj*)module->name); + wrenGrayObj(vm, (Obj*)module->name); - // Keep track of how much memory is still in use. - vm->bytesAllocated += sizeof(ObjModule); + // 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 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 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); +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); + // Keep track of how much memory is still in use. + vm->bytesAllocated += sizeof(ObjUpvalue); } -static void blackenObject(WrenVM* vm, Obj* obj) -{ +static void blackenObject(WrenVM* vm, Obj* obj) { #if WREN_DEBUG_TRACE_MEMORY - printf("mark "); - wrenDumpValue(OBJ_VAL(obj)); - printf(" @ %p\n", obj); + 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) -{ + // 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); + 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; - } + switch (obj->type) { + case OBJ_CLASS: + wrenMethodBufferClear(vm, &((ObjClass*)obj)->methods); + break; + + case OBJ_FIBER: { + ObjFiber* fiber = (ObjFiber*)obj; + DEALLOCATE(vm, fiber->frames); + DEALLOCATE(vm, fiber->stack); + break; + } + + case OBJ_FN: { + ObjFn* fn = (ObjFn*)obj; + wrenValueBufferClear(vm, &fn->constants); + wrenByteBufferClear(vm, &fn->code); + wrenIntBufferClear(vm, &fn->debug->sourceLines); + DEALLOCATE(vm, fn->debug->name); + DEALLOCATE(vm, fn->debug); + break; + } + + case OBJ_FOREIGN: + wrenFinalizeForeign(vm, (ObjForeign*)obj); + break; + + case OBJ_LIST: + wrenValueBufferClear(vm, &((ObjList*)obj)->elements); + break; + + case OBJ_MAP: + DEALLOCATE(vm, ((ObjMap*)obj)->entries); + break; + + case OBJ_MODULE: + wrenSymbolTableClear(vm, &((ObjModule*)obj)->variableNames); + wrenValueBufferClear(vm, &((ObjModule*)obj)->variables); + break; + + case OBJ_CLOSURE: + case OBJ_INSTANCE: + case OBJ_RANGE: + case OBJ_STRING: + case OBJ_UPVALUE: + break; + } + + DEALLOCATE(vm, obj); +} + +ObjClass* wrenGetClass(WrenVM* vm, Value value) { + return wrenGetClassInline(vm, value); +} + +bool wrenValuesEqual(Value a, Value b) { + if (wrenValuesSame(a, b)) + return true; + + // If we get here, it's only possible for two heap-allocated immutable objects + // to be equal. + if (!IS_OBJ(a) || !IS_OBJ(b)) + return false; + + Obj* aObj = AS_OBJ(a); + Obj* bObj = AS_OBJ(b); + + // Must be the same type. + if (aObj->type != bObj->type) + return false; + + switch (aObj->type) { + case OBJ_RANGE: { + ObjRange* aRange = (ObjRange*)aObj; + ObjRange* bRange = (ObjRange*)bObj; + return aRange->from == bRange->from && aRange->to == bRange->to && aRange->isInclusive == bRange->isInclusive; + } + + case OBJ_STRING: { + ObjString* aString = (ObjString*)aObj; + ObjString* bString = (ObjString*)bObj; + return aString->hash == bString->hash && wrenStringEqualsCString(aString, bString->value, bString->length); + } + + default: + // All other types are only equal if they are same, which they aren't if + // we get here. + return false; + } } // End file "wren_value.c" // Begin file "wren_vm.c" #include #include - #if WREN_OPT_META // Begin file "wren_opt_meta.h" #ifndef wren_opt_meta_h #define wren_opt_meta_h - // This module defines the Meta class and its associated methods. #if WREN_OPT_META const char* wrenMetaSource(); -WrenForeignMethodFn wrenMetaBindForeignMethod(WrenVM* vm, - const char* className, - bool isStatic, - const char* signature); +WrenForeignMethodFn wrenMetaBindForeignMethod(WrenVM* vm, const char* className, bool isStatic, const char* signature); #endif @@ -10908,17 +10176,11 @@ WrenForeignMethodFn wrenMetaBindForeignMethod(WrenVM* vm, #ifndef wren_opt_random_h #define wren_opt_random_h - #if WREN_OPT_RANDOM const char* wrenRandomSource(); -WrenForeignClassMethods wrenRandomBindForeignClass(WrenVM* vm, - const char* module, - const char* className); -WrenForeignMethodFn wrenRandomBindForeignMethod(WrenVM* vm, - const char* className, - bool isStatic, - const char* signature); +WrenForeignClassMethods wrenRandomBindForeignClass(WrenVM* vm, const char* module, const char* className); +WrenForeignMethodFn wrenRandomBindForeignMethod(WrenVM* vm, const char* className, bool isStatic, const char* signature); #endif @@ -10927,223 +10189,202 @@ WrenForeignMethodFn wrenRandomBindForeignMethod(WrenVM* vm, #endif #if WREN_DEBUG_TRACE_MEMORY || WREN_DEBUG_TRACE_GC - #include - #include +#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) -{ +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"); + printf("-- gc --\n"); - size_t before = vm->bytesAllocated; - double startTime = (double)clock() / CLOCKS_PER_SEC; + 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; + // 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); + 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) -{ +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); + // 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 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); + // 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); + if (newSize > 0 && vm->bytesAllocated > vm->nextGC) + wrenCollectGarbage(vm); #endif - return vm->config.reallocateFn(memory, newSize, vm->config.userData); + return vm->config.reallocateFn(memory, newSize, vm->config.userData); } // Captures the local variable [local] into an [Upvalue]. If that local is @@ -11151,101 +10392,83 @@ void* wrenReallocate(WrenVM* vm, void* memory, size_t oldSize, size_t newSize) // 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; +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; +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; + // 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; - } + // 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) - { +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); - } + 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); - } + if (strcmp(moduleName, "random") == 0) { + method = wrenRandomBindForeignMethod(vm, className, isStatic, signature); + } #endif - } + } - return method; + return method; } // Defines [methodValue] as a method on [classObj]. @@ -11255,155 +10478,129 @@ static WrenForeignMethodFn findForeignMethod(WrenVM* vm, // // 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; +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; +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); +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; +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 @@ -11414,128 +10611,98 @@ static ObjClosure* compileInModule(WrenVM* vm, Value name, const char* source, // // 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) - { +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); - } + 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); - } + } + + 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. +// 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]; +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; + // Remove the stack items + vm->fiber->stackTop -= 2; - ObjClass* classObj = AS_CLASS(classValue); - classObj->attributes = attributes; + ObjClass* classObj = AS_CLASS(classValue); + classObj->attributes = attributes; } // Creates a new class. @@ -11545,1578 +10712,1479 @@ static void endClass(WrenVM* vm) // 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]; +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--; + // 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; + 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); + 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); + 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."); +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."); + // 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."); + 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; + // 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); + method->as.foreign(vm); - vm->apiStack = NULL; + 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."); +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 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; + // 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; + Method* method = &classObj->methods.data[symbol]; + if (method->type == METHOD_NONE) + return; - ASSERT(method->type == METHOD_FOREIGN, "Finalizer should be foreign."); + ASSERT(method->type == METHOD_FOREIGN, "Finalizer should be foreign."); - WrenFinalizerFn finalizer = (WrenFinalizerFn)method->as.foreign; - finalizer(foreign->data); + 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); +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(); + if (strcmp(nameString->value, "meta") == 0) + result.source = wrenMetaSource(); #endif #if WREN_OPT_RANDOM - if (strcmp(nameString->value, "random") == 0) result.source = wrenRandomSource(); + 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; + } + + if (result.source == NULL) { + vm->fiber->error = wrenStringFormat(vm, "Could not load module '@'.", name); + wrenPopRoot(vm); // name. + return NULL_VAL; + } + + ObjClosure* moduleClosure = compileInModule(vm, name, result.source, false, true); + + // Now that we're done, give the result back in case there's cleanup to do. + if (result.onComplete) + result.onComplete(vm, AS_CSTRING(name), result); + + if (moduleClosure == NULL) { + vm->fiber->error = wrenStringFormat(vm, "Could not compile module '@'.", name); + wrenPopRoot(vm); // name. + return NULL_VAL; + } + + wrenPopRoot(vm); // name. + + // Return the closure that executes the module. + return OBJ_VAL(moduleClosure); } +static Value getModuleVariable(WrenVM* vm, ObjModule* module, Value variableName) { + ObjString* variable = AS_STRING(variableName); + uint32_t variableEntry = wrenSymbolTableFind(&module->variableNames, variable->value, variable->length); + + // It's a runtime error if the imported variable does not exist. + if (variableEntry != UINT32_MAX) { + return module->variables.data[variableEntry]; + } + + vm->fiber->error = wrenStringFormat(vm, "Could not find a variable named '@' in module '@'.", variableName, OBJ_VAL(module->name)); + return NULL_VAL; +} + +inline static bool checkArity(WrenVM* vm, Value value, int numArgs) { + ASSERT(IS_CLOSURE(value), "Receiver must be a closure."); + ObjFn* fn = AS_CLOSURE(value)->fn; + + // We only care about missing arguments, not extras. The "- 1" is because + // numArgs includes the receiver, the function itself, which we don't want to + // count. + if (numArgs - 1 >= fn->arity) + return true; + + vm->fiber->error = CONST_STRING(vm, "Function expects more arguments."); + return false; +} // The main bytecode interpreter loop. This is where the magic happens. It is // also, as you can imagine, highly performance critical. -static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber) -{ - // Remember the current fiber so we can find it if a GC happens. - vm->fiber = fiber; - fiber->state = FIBER_ROOT; - - // Hoist these into local variables. They are accessed frequently in the loop - // but assigned less frequently. Keeping them in locals and updating them when - // a call frame has been pushed or popped gives a large speed boost. - register CallFrame* frame; - register Value* stackStart; - register uint8_t* ip; - register ObjFn* fn; - - // These macros are designed to only be invoked within this function. - #define PUSH(value) (*fiber->stackTop++ = value) - #define POP() (*(--fiber->stackTop)) - #define DROP() (fiber->stackTop--) - #define PEEK() (*(fiber->stackTop - 1)) - #define PEEK2() (*(fiber->stackTop - 2)) - #define READ_BYTE() (*ip++) - #define READ_SHORT() (ip += 2, (uint16_t)((ip[-2] << 8) | ip[-1])) - - // Use this before a CallFrame is pushed to store the local variables back - // into the current one. - #define STORE_FRAME() frame->ip = ip - - // Use this after a CallFrame has been pushed or popped to refresh the local - // variables. - #define LOAD_FRAME() \ - do \ - { \ - frame = &fiber->frames[fiber->numFrames - 1]; \ - stackStart = frame->stackStart; \ - ip = frame->ip; \ - fn = frame->closure->fn; \ - } while (false) - - // Terminates the current fiber with error string [error]. If another calling - // fiber is willing to catch the error, transfers control to it, otherwise - // exits the interpreter. - #define RUNTIME_ERROR() \ - do \ - { \ - STORE_FRAME(); \ - runtimeError(vm); \ - if (vm->fiber == NULL) return WREN_RESULT_RUNTIME_ERROR; \ - fiber = vm->fiber; \ - LOAD_FRAME(); \ - DISPATCH(); \ - } while (false) - - #if WREN_DEBUG_TRACE_INSTRUCTIONS - // Prints the stack and instruction before each instruction is executed. - #define DEBUG_TRACE_INSTRUCTIONS() \ - do \ - { \ - wrenDumpStack(fiber); \ - wrenDumpInstruction(vm, fn, (int)(ip - fn->code.data)); \ - } while (false) - #else - #define DEBUG_TRACE_INSTRUCTIONS() do { } while (false) - #endif - - #if WREN_COMPUTED_GOTO - - static void* dispatchTable[] = { - #define OPCODE(name, _) &&code_##name, -// Begin file "wren_opcodes.h" -// This defines the bytecode instructions used by the VM. It does so by invoking -// an OPCODE() macro which is expected to be defined at the point that this is -// included. (See: http://en.wikipedia.org/wiki/X_Macro for more.) -// -// The first argument is the name of the opcode. The second is its "stack -// effect" -- the amount that the op code changes the size of the stack. A -// stack effect of 1 means it pushes a value and the stack grows one larger. -// -2 means it pops two values, etc. -// -// Note that the order of instructions here affects the order of the dispatch -// table in the VM's interpreter loop. That in turn affects caching which -// affects overall performance. Take care to run benchmarks if you change the -// order here. - -// Load the constant at index [arg]. -OPCODE(CONSTANT, 1) - -// Push null onto the stack. -OPCODE(NULL, 1) - -// Push false onto the stack. -OPCODE(FALSE, 1) - -// Push true onto the stack. -OPCODE(TRUE, 1) - -// Pushes the value in the given local slot. -OPCODE(LOAD_LOCAL_0, 1) -OPCODE(LOAD_LOCAL_1, 1) -OPCODE(LOAD_LOCAL_2, 1) -OPCODE(LOAD_LOCAL_3, 1) -OPCODE(LOAD_LOCAL_4, 1) -OPCODE(LOAD_LOCAL_5, 1) -OPCODE(LOAD_LOCAL_6, 1) -OPCODE(LOAD_LOCAL_7, 1) -OPCODE(LOAD_LOCAL_8, 1) - -// Note: The compiler assumes the following _STORE instructions always -// immediately follow their corresponding _LOAD ones. - -// Pushes the value in local slot [arg]. -OPCODE(LOAD_LOCAL, 1) - -// Stores the top of stack in local slot [arg]. Does not pop it. -OPCODE(STORE_LOCAL, 0) - -// Pushes the value in upvalue [arg]. -OPCODE(LOAD_UPVALUE, 1) - -// Stores the top of stack in upvalue [arg]. Does not pop it. -OPCODE(STORE_UPVALUE, 0) - -// Pushes the value of the top-level variable in slot [arg]. -OPCODE(LOAD_MODULE_VAR, 1) - -// Stores the top of stack in top-level variable slot [arg]. Does not pop it. -OPCODE(STORE_MODULE_VAR, 0) - -// Pushes the value of the field in slot [arg] of the receiver of the current -// function. This is used for regular field accesses on "this" directly in -// methods. This instruction is faster than the more general CODE_LOAD_FIELD -// instruction. -OPCODE(LOAD_FIELD_THIS, 1) - -// Stores the top of the stack in field slot [arg] in the receiver of the -// current value. Does not pop the value. This instruction is faster than the -// more general CODE_LOAD_FIELD instruction. -OPCODE(STORE_FIELD_THIS, 0) - -// Pops an instance and pushes the value of the field in slot [arg] of it. -OPCODE(LOAD_FIELD, 0) - -// Pops an instance and stores the subsequent top of stack in field slot -// [arg] in it. Does not pop the value. -OPCODE(STORE_FIELD, -1) - -// Pop and discard the top of stack. -OPCODE(POP, -1) - -// Invoke the method with symbol [arg]. The number indicates the number of -// arguments (not including the receiver). -OPCODE(CALL_0, 0) -OPCODE(CALL_1, -1) -OPCODE(CALL_2, -2) -OPCODE(CALL_3, -3) -OPCODE(CALL_4, -4) -OPCODE(CALL_5, -5) -OPCODE(CALL_6, -6) -OPCODE(CALL_7, -7) -OPCODE(CALL_8, -8) -OPCODE(CALL_9, -9) -OPCODE(CALL_10, -10) -OPCODE(CALL_11, -11) -OPCODE(CALL_12, -12) -OPCODE(CALL_13, -13) -OPCODE(CALL_14, -14) -OPCODE(CALL_15, -15) -OPCODE(CALL_16, -16) - -// Invoke a superclass method with symbol [arg]. The number indicates the -// number of arguments (not including the receiver). -OPCODE(SUPER_0, 0) -OPCODE(SUPER_1, -1) -OPCODE(SUPER_2, -2) -OPCODE(SUPER_3, -3) -OPCODE(SUPER_4, -4) -OPCODE(SUPER_5, -5) -OPCODE(SUPER_6, -6) -OPCODE(SUPER_7, -7) -OPCODE(SUPER_8, -8) -OPCODE(SUPER_9, -9) -OPCODE(SUPER_10, -10) -OPCODE(SUPER_11, -11) -OPCODE(SUPER_12, -12) -OPCODE(SUPER_13, -13) -OPCODE(SUPER_14, -14) -OPCODE(SUPER_15, -15) -OPCODE(SUPER_16, -16) - -// Jump the instruction pointer [arg] forward. -OPCODE(JUMP, 0) - -// Jump the instruction pointer [arg] backward. -OPCODE(LOOP, 0) - -// Pop and if not truthy then jump the instruction pointer [arg] forward. -OPCODE(JUMP_IF, -1) - -// If the top of the stack is false, jump [arg] forward. Otherwise, pop and -// continue. -OPCODE(AND, -1) - -// If the top of the stack is non-false, jump [arg] forward. Otherwise, pop -// and continue. -OPCODE(OR, -1) - -// Close the upvalue for the local on the top of the stack, then pop it. -OPCODE(CLOSE_UPVALUE, -1) - -// Exit from the current function and return the value on the top of the -// stack. -OPCODE(RETURN, 0) +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 -// 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) +#if WREN_COMPUTED_GOTO -// 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) + static void* dispatchTable[] = { +#define OPCODE(name, _) &&code_##name, + // Begin file "wren_opcodes.h" + // This defines the bytecode instructions used by the VM. It does so by invoking + // an OPCODE() macro which is expected to be defined at the point that this is + // included. (See: http://en.wikipedia.org/wiki/X_Macro for more.) + // + // The first argument is the name of the opcode. The second is its "stack + // effect" -- the amount that the op code changes the size of the stack. A + // stack effect of 1 means it pushes a value and the stack grows one larger. + // -2 means it pops two values, etc. + // + // Note that the order of instructions here affects the order of the dispatch + // table in the VM's interpreter loop. That in turn affects caching which + // affects overall performance. Take care to run benchmarks if you change the + // order here. + + // Load the constant at index [arg]. + OPCODE(CONSTANT, 1) -// 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) + // Push null onto the stack. + OPCODE(NULL, 1) + + // Push false onto the stack. + OPCODE(FALSE, 1) + + // Push true onto the stack. + OPCODE(TRUE, 1) + + // Pushes the value in the given local slot. + OPCODE(LOAD_LOCAL_0, 1) OPCODE(LOAD_LOCAL_1, 1) OPCODE(LOAD_LOCAL_2, 1) OPCODE(LOAD_LOCAL_3, 1) OPCODE(LOAD_LOCAL_4, 1) OPCODE(LOAD_LOCAL_5, 1) OPCODE(LOAD_LOCAL_6, 1) OPCODE(LOAD_LOCAL_7, 1) + OPCODE(LOAD_LOCAL_8, 1) + + // Note: The compiler assumes the following _STORE instructions always + // immediately follow their corresponding _LOAD ones. + + // Pushes the value in local slot [arg]. + OPCODE(LOAD_LOCAL, 1) + + // Stores the top of stack in local slot [arg]. Does not pop it. + OPCODE(STORE_LOCAL, 0) + + // Pushes the value in upvalue [arg]. + OPCODE(LOAD_UPVALUE, 1) + + // Stores the top of stack in upvalue [arg]. Does not pop it. + OPCODE(STORE_UPVALUE, 0) + + // Pushes the value of the top-level variable in slot [arg]. + OPCODE(LOAD_MODULE_VAR, 1) + + // Stores the top of stack in top-level variable slot [arg]. Does not pop it. + OPCODE(STORE_MODULE_VAR, 0) + + // Pushes the value of the field in slot [arg] of the receiver of the current + // function. This is used for regular field accesses on "this" directly in + // methods. This instruction is faster than the more general CODE_LOAD_FIELD + // instruction. + OPCODE(LOAD_FIELD_THIS, 1) + + // Stores the top of the stack in field slot [arg] in the receiver of the + // current value. Does not pop the value. This instruction is faster than the + // more general CODE_LOAD_FIELD instruction. + OPCODE(STORE_FIELD_THIS, 0) + + // Pops an instance and pushes the value of the field in slot [arg] of it. + OPCODE(LOAD_FIELD, 0) + + // Pops an instance and stores the subsequent top of stack in field slot + // [arg] in it. Does not pop the value. + OPCODE(STORE_FIELD, -1) + + // Pop and discard the top of stack. + OPCODE(POP, -1) + + // Invoke the method with symbol [arg]. The number indicates the number of + // arguments (not including the receiver). + OPCODE(CALL_0, 0) OPCODE(CALL_1, -1) OPCODE(CALL_2, -2) OPCODE(CALL_3, -3) OPCODE(CALL_4, -4) OPCODE(CALL_5, -5) OPCODE(CALL_6, -6) OPCODE(CALL_7, -7) OPCODE(CALL_8, -8) OPCODE(CALL_9, -9) + OPCODE(CALL_10, -10) OPCODE(CALL_11, -11) OPCODE(CALL_12, -12) OPCODE(CALL_13, -13) OPCODE(CALL_14, -14) OPCODE(CALL_15, -15) OPCODE(CALL_16, -16) + + // Invoke a superclass method with symbol [arg]. The number indicates the + // number of arguments (not including the receiver). + OPCODE(SUPER_0, 0) OPCODE(SUPER_1, -1) OPCODE(SUPER_2, -2) OPCODE(SUPER_3, -3) OPCODE(SUPER_4, -4) OPCODE(SUPER_5, -5) OPCODE(SUPER_6, -6) OPCODE(SUPER_7, -7) OPCODE(SUPER_8, -8) + OPCODE(SUPER_9, -9) OPCODE(SUPER_10, -10) OPCODE(SUPER_11, -11) OPCODE(SUPER_12, -12) OPCODE(SUPER_13, -13) OPCODE(SUPER_14, -14) OPCODE(SUPER_15, -15) OPCODE(SUPER_16, -16) + + // Jump the instruction pointer [arg] forward. + OPCODE(JUMP, 0) + + // Jump the instruction pointer [arg] backward. + OPCODE(LOOP, 0) + + // Pop and if not truthy then jump the instruction pointer [arg] forward. + OPCODE(JUMP_IF, -1) + + // If the top of the stack is false, jump [arg] forward. Otherwise, pop and + // continue. + OPCODE(AND, -1) + + // If the top of the stack is non-false, jump [arg] forward. Otherwise, pop + // and continue. + OPCODE(OR, -1) + + // Close the upvalue for the local on the top of the stack, then pop it. + OPCODE(CLOSE_UPVALUE, -1) + + // Exit from the current function and return the value on the top of the + // stack. + OPCODE(RETURN, 0) + + // Creates a closure for the function stored at [arg] in the constant table. + // + // Following the function argument is a number of arguments, two for each + // upvalue. The first is true if the variable being captured is a local (as + // opposed to an upvalue), and the second is the index of the local or + // upvalue being captured. + // + // Pushes the created closure. + OPCODE(CLOSURE, 1) + + // Creates a new instance of a class. + // + // Assumes the class object is in slot zero, and replaces it with the new + // uninitialized instance of that class. This opcode is only emitted by the + // compiler-generated constructor metaclass methods. + OPCODE(CONSTRUCT, 0) + + // Creates a new instance of a foreign class. + // + // Assumes the class object is in slot zero, and replaces it with the new + // uninitialized instance of that class. This opcode is only emitted by the + // compiler-generated constructor metaclass methods. + OPCODE(FOREIGN_CONSTRUCT, 0) + + // Creates a class. Top of stack is the superclass. Below that is a string for + // the name of the class. Byte [arg] is the number of fields in the class. + OPCODE(CLASS, -1) + + // Ends a class. + // Atm the stack contains the class and the ClassAttributes (or null). + OPCODE(END_CLASS, -2) + + // Creates a foreign class. Top of stack is the superclass. Below that is a + // string for the name of the class. + OPCODE(FOREIGN_CLASS, -1) + + // Define a method for symbol [arg]. The class receiving the method is popped + // off the stack, then the function defining the body is popped. + // + // If a foreign method is being defined, the "function" will be a string + // identifying the foreign method. Otherwise, it will be a function or + // closure. + OPCODE(METHOD_INSTANCE, -2) + + // Define a method for symbol [arg]. The class whose metaclass will receive + // the method is popped off the stack, then the function defining the body is + // popped. + // + // If a foreign method is being defined, the "function" will be a string + // identifying the foreign method. Otherwise, it will be a function or + // closure. + OPCODE(METHOD_STATIC, -2) + + // This is executed at the end of the module's body. Pushes NULL onto the stack + // as the "return value" of the import statement and stores the module as the + // most recently imported one. + OPCODE(END_MODULE, 1) + + // Import a module whose name is the string stored at [arg] in the constant + // table. + // + // Pushes null onto the stack so that the fiber for the imported module can + // replace that with a dummy value when it returns. (Fibers always return a + // value when resuming a caller.) + OPCODE(IMPORT_MODULE, 1) + + // Import a variable from the most recently imported module. The name of the + // variable to import is at [arg] in the constant table. Pushes the loaded + // variable's value. + OPCODE(IMPORT_VARIABLE, 1) + + // This pseudo-instruction indicates the end of the bytecode. It should + // always be preceded by a `CODE_RETURN`, so is never actually executed. + OPCODE(END, 0) + // End file "wren_opcodes.h" +#undef OPCODE + }; + +#define INTERPRET_LOOP DISPATCH(); +#define CASE_CODE(name) code_##name + +#define DISPATCH() \ + do { \ + DEBUG_TRACE_INSTRUCTIONS(); \ + goto* dispatchTable[instruction = (Code)READ_BYTE()]; \ + } while (false) -// 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) +#else -// Ends a class. -// Atm the stack contains the class and the ClassAttributes (or null). -OPCODE(END_CLASS, -2) +#define INTERPRET_LOOP \ + loop : DEBUG_TRACE_INSTRUCTIONS(); \ + switch (instruction = (Code)READ_BYTE()) -// 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 CASE_CODE(name) case CODE_##name +#define DISPATCH() goto loop -// 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) +#endif -// 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) + 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."); -// 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) + // 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; -// 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) + // 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]; -// 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) + wrenCallFunction(vm, vm->fiber, closure, 0); + WrenInterpretResult result = runInterpreter(vm, vm->fiber); -// This pseudo-instruction indicates the end of the bytecode. It should -// always be preceded by a `CODE_RETURN`, so is never actually executed. -OPCODE(END, 0) -// End file "wren_opcodes.h" - #undef OPCODE - }; - - #define INTERPRET_LOOP DISPATCH(); - #define CASE_CODE(name) code_##name - - #define DISPATCH() \ - do \ - { \ - DEBUG_TRACE_INSTRUCTIONS(); \ - goto *dispatchTable[instruction = (Code)READ_BYTE()]; \ - } while (false) - - #else - - #define INTERPRET_LOOP \ - loop: \ - DEBUG_TRACE_INSTRUCTIONS(); \ - switch (instruction = (Code)READ_BYTE()) - - #define CASE_CODE(name) case CODE_##name - #define DISPATCH() goto loop - - #endif - - LOAD_FRAME(); - - Code instruction; - INTERPRET_LOOP - { - CASE_CODE(LOAD_LOCAL_0): - CASE_CODE(LOAD_LOCAL_1): - CASE_CODE(LOAD_LOCAL_2): - CASE_CODE(LOAD_LOCAL_3): - CASE_CODE(LOAD_LOCAL_4): - CASE_CODE(LOAD_LOCAL_5): - CASE_CODE(LOAD_LOCAL_6): - CASE_CODE(LOAD_LOCAL_7): - CASE_CODE(LOAD_LOCAL_8): - PUSH(stackStart[instruction - CODE_LOAD_LOCAL_0]); - DISPATCH(); - - CASE_CODE(LOAD_LOCAL): - PUSH(stackStart[READ_BYTE()]); - DISPATCH(); - - CASE_CODE(LOAD_FIELD_THIS): - { - uint8_t field = READ_BYTE(); - Value receiver = stackStart[0]; - ASSERT(IS_INSTANCE(receiver), "Receiver should be instance."); - ObjInstance* instance = AS_INSTANCE(receiver); - ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field."); - PUSH(instance->fields[field]); - DISPATCH(); - } - - CASE_CODE(POP): DROP(); DISPATCH(); - CASE_CODE(NULL): PUSH(NULL_VAL); DISPATCH(); - CASE_CODE(FALSE): PUSH(FALSE_VAL); DISPATCH(); - CASE_CODE(TRUE): PUSH(TRUE_VAL); DISPATCH(); - - CASE_CODE(STORE_LOCAL): - stackStart[READ_BYTE()] = PEEK(); - DISPATCH(); - - CASE_CODE(CONSTANT): - PUSH(fn->constants.data[READ_SHORT()]); - DISPATCH(); - - { - // The opcodes for doing method and superclass calls share a lot of code. - // However, doing an if() test in the middle of the instruction sequence - // to handle the bit that is special to super calls makes the non-super - // call path noticeably slower. - // - // Instead, we do this old school using an explicit goto to share code for - // everything at the tail end of the call-handling code that is the same - // between normal and superclass calls. - int numArgs; - int symbol; - - Value* args; - ObjClass* classObj; - - Method* method; - - CASE_CODE(CALL_0): - CASE_CODE(CALL_1): - CASE_CODE(CALL_2): - CASE_CODE(CALL_3): - CASE_CODE(CALL_4): - CASE_CODE(CALL_5): - CASE_CODE(CALL_6): - CASE_CODE(CALL_7): - CASE_CODE(CALL_8): - CASE_CODE(CALL_9): - CASE_CODE(CALL_10): - CASE_CODE(CALL_11): - CASE_CODE(CALL_12): - CASE_CODE(CALL_13): - CASE_CODE(CALL_14): - CASE_CODE(CALL_15): - CASE_CODE(CALL_16): - // Add one for the implicit receiver argument. - numArgs = instruction - CODE_CALL_0 + 1; - symbol = READ_SHORT(); - - // The receiver is the first argument. - args = fiber->stackTop - numArgs; - classObj = wrenGetClassInline(vm, args[0]); - goto completeCall; - - CASE_CODE(SUPER_0): - CASE_CODE(SUPER_1): - CASE_CODE(SUPER_2): - CASE_CODE(SUPER_3): - CASE_CODE(SUPER_4): - CASE_CODE(SUPER_5): - CASE_CODE(SUPER_6): - CASE_CODE(SUPER_7): - CASE_CODE(SUPER_8): - CASE_CODE(SUPER_9): - CASE_CODE(SUPER_10): - CASE_CODE(SUPER_11): - CASE_CODE(SUPER_12): - CASE_CODE(SUPER_13): - CASE_CODE(SUPER_14): - CASE_CODE(SUPER_15): - CASE_CODE(SUPER_16): - // Add one for the implicit receiver argument. - numArgs = instruction - CODE_SUPER_0 + 1; - symbol = READ_SHORT(); - - // The receiver is the first argument. - args = fiber->stackTop - numArgs; - - // The superclass is stored in a constant. - classObj = AS_CLASS(fn->constants.data[READ_SHORT()]); - goto completeCall; - - completeCall: - // If the class's method table doesn't include the symbol, bail. - if (symbol >= classObj->methods.count || - (method = &classObj->methods.data[symbol])->type == METHOD_NONE) - { - methodNotFound(vm, classObj, symbol); - RUNTIME_ERROR(); - } - - switch (method->type) - { - case METHOD_PRIMITIVE: - if (method->as.primitive(vm, args)) - { - // The result is now in the first arg slot. Discard the other - // stack slots. - fiber->stackTop -= numArgs - 1; - } else { - // An error, fiber switch, or call frame change occurred. - STORE_FRAME(); - - // If we don't have a fiber to switch to, stop interpreting. - fiber = vm->fiber; - if (fiber == NULL) return WREN_RESULT_SUCCESS; - if (wrenHasError(fiber)) RUNTIME_ERROR(); - LOAD_FRAME(); - } - break; - - case METHOD_FUNCTION_CALL: - if (!checkArity(vm, args[0], numArgs)) { - RUNTIME_ERROR(); - break; - } - - STORE_FRAME(); - method->as.primitive(vm, args); - LOAD_FRAME(); - break; - - case METHOD_FOREIGN: - callForeign(vm, fiber, method->as.foreign, numArgs); - if (wrenHasError(fiber)) RUNTIME_ERROR(); - break; - - case METHOD_BLOCK: - STORE_FRAME(); - wrenCallFunction(vm, fiber, (ObjClosure*)method->as.closure, numArgs); - LOAD_FRAME(); - break; - - case METHOD_NONE: - UNREACHABLE(); - break; - } - DISPATCH(); - } - - CASE_CODE(LOAD_UPVALUE): - { - ObjUpvalue** upvalues = frame->closure->upvalues; - PUSH(*upvalues[READ_BYTE()]->value); - DISPATCH(); - } - - CASE_CODE(STORE_UPVALUE): - { - ObjUpvalue** upvalues = frame->closure->upvalues; - *upvalues[READ_BYTE()]->value = PEEK(); - DISPATCH(); - } - - CASE_CODE(LOAD_MODULE_VAR): - PUSH(fn->module->variables.data[READ_SHORT()]); - DISPATCH(); - - CASE_CODE(STORE_MODULE_VAR): - fn->module->variables.data[READ_SHORT()] = PEEK(); - DISPATCH(); - - CASE_CODE(STORE_FIELD_THIS): - { - uint8_t field = READ_BYTE(); - Value receiver = stackStart[0]; - ASSERT(IS_INSTANCE(receiver), "Receiver should be instance."); - ObjInstance* instance = AS_INSTANCE(receiver); - ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field."); - instance->fields[field] = PEEK(); - DISPATCH(); - } - - CASE_CODE(LOAD_FIELD): - { - uint8_t field = READ_BYTE(); - Value receiver = POP(); - ASSERT(IS_INSTANCE(receiver), "Receiver should be instance."); - ObjInstance* instance = AS_INSTANCE(receiver); - ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field."); - PUSH(instance->fields[field]); - DISPATCH(); - } - - CASE_CODE(STORE_FIELD): - { - uint8_t field = READ_BYTE(); - Value receiver = POP(); - ASSERT(IS_INSTANCE(receiver), "Receiver should be instance."); - ObjInstance* instance = AS_INSTANCE(receiver); - ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field."); - instance->fields[field] = PEEK(); - DISPATCH(); - } - - CASE_CODE(JUMP): - { - uint16_t offset = READ_SHORT(); - ip += offset; - DISPATCH(); - } - - CASE_CODE(LOOP): - { - // Jump back to the top of the loop. - uint16_t offset = READ_SHORT(); - ip -= offset; - DISPATCH(); - } - - CASE_CODE(JUMP_IF): - { - uint16_t offset = READ_SHORT(); - Value condition = POP(); - - if (wrenIsFalsyValue(condition)) ip += offset; - DISPATCH(); - } - - CASE_CODE(AND): - { - uint16_t offset = READ_SHORT(); - Value condition = PEEK(); - - if (wrenIsFalsyValue(condition)) - { - // Short-circuit the right hand side. - ip += offset; - } - else - { - // Discard the condition and evaluate the right hand side. - DROP(); - } - DISPATCH(); - } - - CASE_CODE(OR): - { - uint16_t offset = READ_SHORT(); - Value condition = PEEK(); - - if (wrenIsFalsyValue(condition)) - { - // Discard the condition and evaluate the right hand side. - DROP(); - } - else - { - // Short-circuit the right hand side. - ip += offset; - } - DISPATCH(); - } - - CASE_CODE(CLOSE_UPVALUE): - // Close the upvalue for the local if we have one. - closeUpvalues(fiber, fiber->stackTop - 1); - DROP(); - DISPATCH(); - - CASE_CODE(RETURN): - { - Value result = POP(); - fiber->numFrames--; - - // Close any upvalues still in scope. - closeUpvalues(fiber, stackStart); - - // If the fiber is complete, end it. - if (fiber->numFrames == 0) - { - // See if there's another fiber to return to. If not, we're done. - if (fiber->caller == NULL) - { - // Store the final result value at the beginning of the stack so the - // C API can get it. - fiber->stack[0] = result; - fiber->stackTop = fiber->stack + 1; - return WREN_RESULT_SUCCESS; - } - - ObjFiber* resumingFiber = fiber->caller; - fiber->caller = NULL; - fiber = resumingFiber; - vm->fiber = resumingFiber; - - // Store the result in the resuming fiber. - fiber->stackTop[-1] = result; - } - else - { - // Store the result of the block in the first slot, which is where the - // caller expects it. - stackStart[0] = result; - - // Discard the stack slots for the call frame (leaving one slot for the - // result). - fiber->stackTop = frame->stackStart + 1; - } - - LOAD_FRAME(); - DISPATCH(); - } - - CASE_CODE(CONSTRUCT): - ASSERT(IS_CLASS(stackStart[0]), "'this' should be a class."); - stackStart[0] = wrenNewInstance(vm, AS_CLASS(stackStart[0])); - DISPATCH(); - - CASE_CODE(FOREIGN_CONSTRUCT): - ASSERT(IS_CLASS(stackStart[0]), "'this' should be a class."); - createForeign(vm, fiber, stackStart); - if (wrenHasError(fiber)) RUNTIME_ERROR(); - DISPATCH(); - - CASE_CODE(CLOSURE): - { - // Create the closure and push it on the stack before creating upvalues - // so that it doesn't get collected. - ObjFn* function = AS_FN(fn->constants.data[READ_SHORT()]); - ObjClosure* closure = wrenNewClosure(vm, function); - PUSH(OBJ_VAL(closure)); - - // Capture upvalues, if any. - for (int i = 0; i < function->numUpvalues; i++) - { - uint8_t isLocal = READ_BYTE(); - uint8_t index = READ_BYTE(); - if (isLocal) - { - // Make an new upvalue to close over the parent's local variable. - closure->upvalues[i] = captureUpvalue(vm, fiber, - frame->stackStart + index); - } - else - { - // Use the same upvalue as the current call frame. - closure->upvalues[i] = frame->closure->upvalues[index]; - } - } - DISPATCH(); - } - - CASE_CODE(END_CLASS): - { - endClass(vm); - if (wrenHasError(fiber)) RUNTIME_ERROR(); - DISPATCH(); - } - - CASE_CODE(CLASS): - { - createClass(vm, READ_BYTE(), NULL); - if (wrenHasError(fiber)) RUNTIME_ERROR(); - DISPATCH(); - } - - CASE_CODE(FOREIGN_CLASS): - { - createClass(vm, -1, fn->module); - if (wrenHasError(fiber)) RUNTIME_ERROR(); - DISPATCH(); - } - - CASE_CODE(METHOD_INSTANCE): - CASE_CODE(METHOD_STATIC): - { - uint16_t symbol = READ_SHORT(); - ObjClass* classObj = AS_CLASS(PEEK()); - Value method = PEEK2(); - bindMethod(vm, instruction, symbol, fn->module, classObj, method); - if (wrenHasError(fiber)) RUNTIME_ERROR(); - DROP(); - DROP(); - DISPATCH(); - } - - CASE_CODE(END_MODULE): - { - vm->lastModule = fn->module; - PUSH(NULL_VAL); - DISPATCH(); - } - - CASE_CODE(IMPORT_MODULE): - { - // Make a slot on the stack for the module's fiber to place the return - // value. It will be popped after this fiber is resumed. Store the - // imported module's closure in the slot in case a GC happens when - // invoking the closure. - PUSH(importModule(vm, fn->constants.data[READ_SHORT()])); - if (wrenHasError(fiber)) RUNTIME_ERROR(); - - // If we get a closure, call it to execute the module body. - if (IS_CLOSURE(PEEK())) - { - STORE_FRAME(); - ObjClosure* closure = AS_CLOSURE(PEEK()); - wrenCallFunction(vm, fiber, closure, 1); - LOAD_FRAME(); - } - else - { - // The module has already been loaded. Remember it so we can import - // variables from it if needed. - vm->lastModule = AS_MODULE(PEEK()); - } - - DISPATCH(); - } - - CASE_CODE(IMPORT_VARIABLE): - { - Value variable = fn->constants.data[READ_SHORT()]; - ASSERT(vm->lastModule != NULL, "Should have already imported module."); - Value result = getModuleVariable(vm, vm->lastModule, variable); - if (wrenHasError(fiber)) RUNTIME_ERROR(); - - PUSH(result); - DISPATCH(); - } - - CASE_CODE(END): - // A CODE_END should always be preceded by a CODE_RETURN. If we get here, - // the compiler generated wrong code. - UNREACHABLE(); - } - - // We should only exit this function from an explicit return from CODE_RETURN - // or a runtime error. - UNREACHABLE(); - return WREN_RESULT_RUNTIME_ERROR; - - #undef READ_BYTE - #undef READ_SHORT -} - -WrenHandle* wrenMakeCallHandle(WrenVM* vm, const char* signature) -{ - ASSERT(signature != NULL, "Signature cannot be NULL."); - - int signatureLength = (int)strlen(signature); - ASSERT(signatureLength > 0, "Signature cannot be empty."); - - // Count the number parameters the method expects. - int numParams = 0; - if (signature[signatureLength - 1] == ')') - { - for (int i = signatureLength - 1; i > 0 && signature[i] != '('; i--) - { - if (signature[i] == '_') numParams++; - } - } - - // Count subscript arguments. - if (signature[0] == '[') - { - for (int i = 0; i < signatureLength && signature[i] != ']'; i++) - { - if (signature[i] == '_') numParams++; - } - } - - // Add the signatue to the method table. - int method = wrenSymbolTableEnsure(vm, &vm->methodNames, - signature, signatureLength); - - // Create a little stub function that assumes the arguments are on the stack - // and calls the method. - ObjFn* fn = wrenNewFunction(vm, NULL, numParams + 1); - - // Wrap the function in a closure and then in a handle. Do this here so it - // doesn't get collected as we fill it in. - WrenHandle* value = wrenMakeHandle(vm, OBJ_VAL(fn)); - value->value = OBJ_VAL(wrenNewClosure(vm, fn)); - - wrenByteBufferWrite(vm, &fn->code, (uint8_t)(CODE_CALL_0 + numParams)); - wrenByteBufferWrite(vm, &fn->code, (method >> 8) & 0xff); - wrenByteBufferWrite(vm, &fn->code, method & 0xff); - wrenByteBufferWrite(vm, &fn->code, CODE_RETURN); - wrenByteBufferWrite(vm, &fn->code, CODE_END); - wrenIntBufferFill(vm, &fn->debug->sourceLines, 0, 5); - wrenFunctionBindName(vm, fn, signature, signatureLength); - - return value; -} - -WrenInterpretResult wrenCall(WrenVM* vm, WrenHandle* method) -{ - ASSERT(method != NULL, "Method cannot be NULL."); - ASSERT(IS_CLOSURE(method->value), "Method must be a method handle."); - ASSERT(vm->fiber != NULL, "Must set up arguments for call first."); - ASSERT(vm->apiStack != NULL, "Must set up arguments for call first."); - ASSERT(vm->fiber->numFrames == 0, "Can not call from a foreign method."); - - ObjClosure* closure = AS_CLOSURE(method->value); - - ASSERT(vm->fiber->stackTop - vm->fiber->stack >= closure->fn->arity, - "Stack must have enough arguments for method."); - - // Clear the API stack. Now that wrenCall() has control, we no longer need - // it. We use this being non-null to tell if re-entrant calls to foreign - // methods are happening, so it's important to clear it out now so that you - // can call foreign methods from within calls to wrenCall(). - vm->apiStack = NULL; - - // Discard any extra temporary slots. We take for granted that the stub - // function has exactly one slot for each argument. - vm->fiber->stackTop = &vm->fiber->stack[closure->fn->maxSlots]; - - wrenCallFunction(vm, vm->fiber, closure, 0); - WrenInterpretResult result = runInterpreter(vm, vm->fiber); - - // If the call didn't abort, then set up the API stack to point to the - // beginning of the stack so the host can access the call's return value. - if (vm->fiber != NULL) vm->apiStack = vm->fiber->stack; - - return result; -} - -WrenHandle* wrenMakeHandle(WrenVM* vm, Value value) -{ - if (IS_OBJ(value)) wrenPushRoot(vm, AS_OBJ(value)); - - // Make a handle for it. - WrenHandle* handle = ALLOCATE(vm, WrenHandle); - handle->value = value; - - if (IS_OBJ(value)) wrenPopRoot(vm); - - // Add it to the front of the linked list of handles. - if (vm->handles != NULL) vm->handles->prev = handle; - handle->prev = NULL; - handle->next = vm->handles; - vm->handles = handle; - - return handle; -} - -void wrenReleaseHandle(WrenVM* vm, WrenHandle* handle) -{ - ASSERT(handle != NULL, "Handle cannot be NULL."); - - // Update the VM's head pointer if we're releasing the first handle. - if (vm->handles == handle) vm->handles = handle->next; - - // Unlink it from the list. - if (handle->prev != NULL) handle->prev->next = handle->next; - if (handle->next != NULL) handle->next->prev = handle->prev; - - // Clear it out. This isn't strictly necessary since we're going to free it, - // but it makes for easier debugging. - handle->prev = NULL; - handle->next = NULL; - handle->value = NULL_VAL; - DEALLOCATE(vm, handle); -} - -WrenInterpretResult wrenInterpret(WrenVM* vm, const char* module, - const char* source) -{ - ObjClosure* closure = wrenCompileSource(vm, module, source, false, true); - if (closure == NULL) return WREN_RESULT_COMPILE_ERROR; - - wrenPushRoot(vm, (Obj*)closure); - ObjFiber* fiber = wrenNewFiber(vm, closure); - wrenPopRoot(vm); // closure. - vm->apiStack = NULL; - - return runInterpreter(vm, fiber); -} - -ObjClosure* wrenCompileSource(WrenVM* vm, const char* module, const char* source, - bool isExpression, bool printErrors) -{ - Value nameValue = NULL_VAL; - if (module != NULL) - { - nameValue = wrenNewString(vm, module); - wrenPushRoot(vm, AS_OBJ(nameValue)); - } - - ObjClosure* closure = compileInModule(vm, nameValue, source, - isExpression, printErrors); - - if (module != NULL) wrenPopRoot(vm); // nameValue. - return closure; -} - -Value wrenGetModuleVariable(WrenVM* vm, Value moduleName, Value variableName) -{ - ObjModule* module = getModule(vm, moduleName); - if (module == NULL) - { - vm->fiber->error = wrenStringFormat(vm, "Module '@' is not loaded.", - moduleName); - return NULL_VAL; - } - - return getModuleVariable(vm, module, variableName); -} - -Value wrenFindVariable(WrenVM* vm, ObjModule* module, const char* name) -{ - int symbol = wrenSymbolTableFind(&module->variableNames, name, strlen(name)); - return module->variables.data[symbol]; -} - -int wrenDeclareVariable(WrenVM* vm, ObjModule* module, const char* name, - size_t length, int line) -{ - if (module->variables.count == MAX_MODULE_VARS) return -2; - - // Implicitly defined variables get a "value" that is the line where the - // variable is first used. We'll use that later to report an error on the - // right line. - wrenValueBufferWrite(vm, &module->variables, NUM_VAL(line)); - return wrenSymbolTableAdd(vm, &module->variableNames, name, length); -} - -int wrenDefineVariable(WrenVM* vm, ObjModule* module, const char* name, - size_t length, Value value, int* line) -{ - if (module->variables.count == MAX_MODULE_VARS) return -2; - - if (IS_OBJ(value)) wrenPushRoot(vm, AS_OBJ(value)); - - // See if the variable is already explicitly or implicitly declared. - int symbol = wrenSymbolTableFind(&module->variableNames, name, length); - - if (symbol == -1) - { - // Brand new variable. - symbol = wrenSymbolTableAdd(vm, &module->variableNames, name, length); - wrenValueBufferWrite(vm, &module->variables, value); - } - else if (IS_NUM(module->variables.data[symbol])) - { - // An implicitly declared variable's value will always be a number. - // Now we have a real definition. - if(line) *line = (int)AS_NUM(module->variables.data[symbol]); - module->variables.data[symbol] = value; - - // If this was a localname we want to error if it was - // referenced before this definition. - if (wrenIsLocalName(name)) symbol = -3; - } - else - { - // Already explicitly declared. - symbol = -1; - } - - if (IS_OBJ(value)) wrenPopRoot(vm); - - return symbol; + // 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; +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."); +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; +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."); +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]); + 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; +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."); +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]); + 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."); +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; + 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."); +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]); + return AS_CSTRING(vm->apiStack[slot]); } -WrenHandle* wrenGetSlotHandle(WrenVM* vm, int slot) -{ - validateApiSlot(vm, slot); - return wrenMakeHandle(vm, 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; +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 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 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 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* 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 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 wrenSetSlotNewMap(WrenVM* vm, int slot) { + setSlot(vm, slot, OBJ_VAL(wrenNewMap(vm))); } -void wrenSetSlotNull(WrenVM* vm, int slot) -{ - setSlot(vm, slot, NULL_VAL); +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 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."); +void wrenSetSlotHandle(WrenVM* vm, int slot, WrenHandle* handle) { + ASSERT(handle != NULL, "Handle cannot be NULL."); - setSlot(vm, slot, handle->value); + 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; +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."); +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; + ValueBuffer elements = AS_LIST(vm->apiStack[listSlot])->elements; - uint32_t usedIndex = wrenValidateIndex(elements.count, index); - ASSERT(usedIndex != UINT32_MAX, "Index out of bounds."); + uint32_t usedIndex = wrenValidateIndex(elements.count, index); + ASSERT(usedIndex != UINT32_MAX, "Index out of bounds."); - vm->apiStack[elementSlot] = elements.data[usedIndex]; + 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."); +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]); - ObjList* list = AS_LIST(vm->apiStack[listSlot]); + uint32_t usedIndex = wrenValidateIndex(list->elements.count, index); + ASSERT(usedIndex != UINT32_MAX, "Index out of bounds."); - uint32_t usedIndex = wrenValidateIndex(list->elements.count, index); - ASSERT(usedIndex != UINT32_MAX, "Index out of bounds."); - - list->elements.data[usedIndex] = vm->apiStack[elementSlot]; + 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); +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."); +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; + 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."); +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; + 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); + ObjMap* map = AS_MAP(vm->apiStack[mapSlot]); + Value value = wrenMapGet(map, key); - return !IS_UNDEFINED(value); + 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."); +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; - } + 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; + 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"); +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; + } - if (!validateKey(vm, key)) { - return; - } + Value value = vm->apiStack[valueSlot]; + ObjMap* map = AS_MAP(vm->apiStack[mapSlot]); - Value value = vm->apiStack[valueSlot]; - ObjMap* map = AS_MAP(vm->apiStack[mapSlot]); - - wrenMapSet(vm, map, key, value); + 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."); +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; - } + 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); + 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."); +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. + Value moduleName = wrenStringFormat(vm, "$", module); + wrenPushRoot(vm, AS_OBJ(moduleName)); - int variableSlot = wrenSymbolTableFind(&moduleObj->variableNames, - name, strlen(name)); - ASSERT(variableSlot != -1, "Could not find variable."); - - setSlot(vm, slot, moduleObj->variables.data[variableSlot]); + 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."); +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)); + 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."); + // 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. + wrenPopRoot(vm); // moduleName. - int variableSlot = wrenSymbolTableFind(&moduleObj->variableNames, - name, strlen(name)); + int variableSlot = wrenSymbolTableFind(&moduleObj->variableNames, name, strlen(name)); - return variableSlot != -1; + 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)); +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); - ObjModule* moduleObj = getModule(vm, moduleName); - - wrenPopRoot(vm); // moduleName. + wrenPopRoot(vm); // moduleName. - return moduleObj != NULL; + return moduleObj != NULL; } -void wrenAbortFiber(WrenVM* vm, int slot) -{ - validateApiSlot(vm, slot); - vm->fiber->error = vm->apiStack[slot]; +void wrenAbortFiber(WrenVM* vm, int slot) { + validateApiSlot(vm, slot); + vm->fiber->error = vm->apiStack[slot]; } -void* wrenGetUserData(WrenVM* vm) -{ +void* wrenGetUserData(WrenVM* vm) { return vm->config.userData; } -void wrenSetUserData(WrenVM* vm, void* userData) -{ +void wrenSetUserData(WrenVM* vm, void* userData) { vm->config.userData = userData; } // End file "wren_vm.c" @@ -13127,236 +12195,219 @@ void wrenSetUserData(WrenVM* vm, void* userData) #include #include - // Begin file "wren_opt_random.wren.inc" // Generated automatically from src/optional/wren_opt_random.wren. Do not edit. -static const char* randomModuleSource = -"foreign class Random {\n" -" construct new() {\n" -" seed_()\n" -" }\n" -"\n" -" construct new(seed) {\n" -" if (seed is Num) {\n" -" seed_(seed)\n" -" } else if (seed is Sequence) {\n" -" if (seed.isEmpty) Fiber.abort(\"Sequence cannot be empty.\")\n" -"\n" -" // TODO: Empty sequence.\n" -" var seeds = []\n" -" for (element in seed) {\n" -" if (!(element is Num)) Fiber.abort(\"Sequence elements must all be numbers.\")\n" -"\n" -" seeds.add(element)\n" -" if (seeds.count == 16) break\n" -" }\n" -"\n" -" // Cycle the values to fill in any missing slots.\n" -" var i = 0\n" -" while (seeds.count < 16) {\n" -" seeds.add(seeds[i])\n" -" i = i + 1\n" -" }\n" -"\n" -" seed_(\n" -" seeds[0], seeds[1], seeds[2], seeds[3],\n" -" seeds[4], seeds[5], seeds[6], seeds[7],\n" -" seeds[8], seeds[9], seeds[10], seeds[11],\n" -" seeds[12], seeds[13], seeds[14], seeds[15])\n" -" } else {\n" -" Fiber.abort(\"Seed must be a number or a sequence of numbers.\")\n" -" }\n" -" }\n" -"\n" -" foreign seed_()\n" -" foreign seed_(seed)\n" -" foreign seed_(n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12, n13, n14, n15, n16)\n" -"\n" -" foreign float()\n" -" float(end) { float() * end }\n" -" float(start, end) { float() * (end - start) + start }\n" -"\n" -" foreign int()\n" -" int(end) { (float() * end).floor }\n" -" int(start, end) { (float() * (end - start)).floor + start }\n" -"\n" -" sample(list) {\n" -" if (list.count == 0) Fiber.abort(\"Not enough elements to sample.\")\n" -" return list[int(list.count)]\n" -" }\n" -" sample(list, count) {\n" -" if (count > list.count) Fiber.abort(\"Not enough elements to sample.\")\n" -"\n" -" var result = []\n" -"\n" -" // The algorithm described in \"Programming pearls: a sample of brilliance\".\n" -" // Use a hash map for sample sizes less than 1/4 of the population size and\n" -" // an array of booleans for larger samples. This simple heuristic improves\n" -" // performance for large sample sizes as well as reduces memory usage.\n" -" if (count * 4 < list.count) {\n" -" var picked = {}\n" -" for (i in list.count - count...list.count) {\n" -" var index = int(i + 1)\n" -" if (picked.containsKey(index)) index = i\n" -" picked[index] = true\n" -" result.add(list[index])\n" -" }\n" -" } else {\n" -" var picked = List.filled(list.count, false)\n" -" for (i in list.count - count...list.count) {\n" -" var index = int(i + 1)\n" -" if (picked[index]) index = i\n" -" picked[index] = true\n" -" result.add(list[index])\n" -" }\n" -" }\n" -"\n" -" return result\n" -" }\n" -"\n" -" shuffle(list) {\n" -" if (list.isEmpty) return\n" -"\n" -" // Fisher-Yates shuffle.\n" -" for (i in 0...list.count - 1) {\n" -" var from = int(i, list.count)\n" -" var temp = list[from]\n" -" list[from] = list[i]\n" -" list[i] = temp\n" -" }\n" -" }\n" -"}\n"; +static const char* randomModuleSource = "foreign class Random {\n" + " construct new() {\n" + " seed_()\n" + " }\n" + "\n" + " construct new(seed) {\n" + " if (seed is Num) {\n" + " seed_(seed)\n" + " } else if (seed is Sequence) {\n" + " if (seed.isEmpty) Fiber.abort(\"Sequence cannot be empty.\")\n" + "\n" + " // TODO: Empty sequence.\n" + " var seeds = []\n" + " for (element in seed) {\n" + " if (!(element is Num)) Fiber.abort(\"Sequence elements must all be numbers.\")\n" + "\n" + " seeds.add(element)\n" + " if (seeds.count == 16) break\n" + " }\n" + "\n" + " // Cycle the values to fill in any missing slots.\n" + " var i = 0\n" + " while (seeds.count < 16) {\n" + " seeds.add(seeds[i])\n" + " i = i + 1\n" + " }\n" + "\n" + " seed_(\n" + " seeds[0], seeds[1], seeds[2], seeds[3],\n" + " seeds[4], seeds[5], seeds[6], seeds[7],\n" + " seeds[8], seeds[9], seeds[10], seeds[11],\n" + " seeds[12], seeds[13], seeds[14], seeds[15])\n" + " } else {\n" + " Fiber.abort(\"Seed must be a number or a sequence of numbers.\")\n" + " }\n" + " }\n" + "\n" + " foreign seed_()\n" + " foreign seed_(seed)\n" + " foreign seed_(n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12, n13, n14, n15, n16)\n" + "\n" + " foreign float()\n" + " float(end) { float() * end }\n" + " float(start, end) { float() * (end - start) + start }\n" + "\n" + " foreign int()\n" + " int(end) { (float() * end).floor }\n" + " int(start, end) { (float() * (end - start)).floor + start }\n" + "\n" + " sample(list) {\n" + " if (list.count == 0) Fiber.abort(\"Not enough elements to sample.\")\n" + " return list[int(list.count)]\n" + " }\n" + " sample(list, count) {\n" + " if (count > list.count) Fiber.abort(\"Not enough elements to sample.\")\n" + "\n" + " var result = []\n" + "\n" + " // The algorithm described in \"Programming pearls: a sample of brilliance\".\n" + " // Use a hash map for sample sizes less than 1/4 of the population size and\n" + " // an array of booleans for larger samples. This simple heuristic improves\n" + " // performance for large sample sizes as well as reduces memory usage.\n" + " if (count * 4 < list.count) {\n" + " var picked = {}\n" + " for (i in list.count - count...list.count) {\n" + " var index = int(i + 1)\n" + " if (picked.containsKey(index)) index = i\n" + " picked[index] = true\n" + " result.add(list[index])\n" + " }\n" + " } else {\n" + " var picked = List.filled(list.count, false)\n" + " for (i in list.count - count...list.count) {\n" + " var index = int(i + 1)\n" + " if (picked[index]) index = i\n" + " picked[index] = true\n" + " result.add(list[index])\n" + " }\n" + " }\n" + "\n" + " return result\n" + " }\n" + "\n" + " shuffle(list) {\n" + " if (list.isEmpty) return\n" + "\n" + " // Fisher-Yates shuffle.\n" + " for (i in 0...list.count - 1) {\n" + " var from = int(i, list.count)\n" + " var temp = list[from]\n" + " list[from] = list[i]\n" + " list[i] = temp\n" + " }\n" + " }\n" + "}\n"; // End file "wren_opt_random.wren.inc" // Implements the well equidistributed long-period linear PRNG (WELL512a). // // https://en.wikipedia.org/wiki/Well_equidistributed_long-period_linear -typedef struct -{ - uint32_t state[16]; - uint32_t index; +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); +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]; +} - 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 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); +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(); - } + 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); +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(); - } + 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); +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); - } + 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); +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. + // 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); + // 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)); + // 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; + // 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); + wrenSetSlotDouble(vm, 0, result); } -static void randomInt0(WrenVM* vm) -{ - Well512* well = (Well512*)wrenGetSlotForeign(vm, 0); +static void randomInt0(WrenVM* vm) { + Well512* well = (Well512*)wrenGetSlotForeign(vm, 0); - wrenSetSlotDouble(vm, 0, (double)advanceState(well)); + wrenSetSlotDouble(vm, 0, (double)advanceState(well)); } -const char* wrenRandomSource() -{ - return randomModuleSource; +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; +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; +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 @@ -13369,125 +12420,108 @@ WrenForeignMethodFn wrenRandomBindForeignMethod(WrenVM* vm, // Begin file "wren_opt_meta.wren.inc" // Generated automatically from src/optional/wren_opt_meta.wren. Do not edit. -static const char* metaModuleSource = -"class Meta {\n" -" static getModuleVariables(module) {\n" -" if (!(module is String)) Fiber.abort(\"Module name must be a string.\")\n" -" var result = getModuleVariables_(module)\n" -" if (result != null) return result\n" -"\n" -" Fiber.abort(\"Could not find a module named '%(module)'.\")\n" -" }\n" -"\n" -" static eval(source) {\n" -" if (!(source is String)) Fiber.abort(\"Source code must be a string.\")\n" -"\n" -" var closure = compile_(source, false, false)\n" -" // TODO: Include compile errors.\n" -" if (closure == null) Fiber.abort(\"Could not compile source code.\")\n" -"\n" -" closure.call()\n" -" }\n" -"\n" -" static compileExpression(source) {\n" -" if (!(source is String)) Fiber.abort(\"Source code must be a string.\")\n" -" return compile_(source, true, true)\n" -" }\n" -"\n" -" static compile(source) {\n" -" if (!(source is String)) Fiber.abort(\"Source code must be a string.\")\n" -" return compile_(source, false, true)\n" -" }\n" -"\n" -" foreign static compile_(source, isExpression, printErrors)\n" -" foreign static getModuleVariables_(module)\n" -"}\n"; +static const char* metaModuleSource = "class Meta {\n" + " static getModuleVariables(module) {\n" + " if (!(module is String)) Fiber.abort(\"Module name must be a string.\")\n" + " var result = getModuleVariables_(module)\n" + " if (result != null) return result\n" + "\n" + " Fiber.abort(\"Could not find a module named '%(module)'.\")\n" + " }\n" + "\n" + " static eval(source) {\n" + " if (!(source is String)) Fiber.abort(\"Source code must be a string.\")\n" + "\n" + " var closure = compile_(source, false, false)\n" + " // TODO: Include compile errors.\n" + " if (closure == null) Fiber.abort(\"Could not compile source code.\")\n" + "\n" + " closure.call()\n" + " }\n" + "\n" + " static compileExpression(source) {\n" + " if (!(source is String)) Fiber.abort(\"Source code must be a string.\")\n" + " return compile_(source, true, true)\n" + " }\n" + "\n" + " static compile(source) {\n" + " if (!(source is String)) Fiber.abort(\"Source code must be a string.\")\n" + " return compile_(source, false, true)\n" + " }\n" + "\n" + " foreign static compile_(source, isExpression, printErrors)\n" + " foreign static getModuleVariables_(module)\n" + "}\n"; // End file "wren_opt_meta.wren.inc" -void metaCompile(WrenVM* vm) -{ - const char* source = wrenGetSlotString(vm, 1); - bool isExpression = wrenGetSlotBool(vm, 2); - bool printErrors = wrenGetSlotBool(vm, 3); - - // TODO: Allow passing in module? - // Look up the module surrounding the callsite. This is brittle. The -2 walks - // up the callstack assuming that the meta module has one level of - // indirection before hitting the user's code. Any change to meta may require - // this constant to be tweaked. - ObjFiber* currentFiber = vm->fiber; - ObjFn* fn = currentFiber->frames[currentFiber->numFrames - 2].closure->fn; - ObjString* module = fn->module->name; - - ObjClosure* closure = wrenCompileSource(vm, module->value, source, - isExpression, printErrors); - - // Return the result. We can't use the public API for this since we have a - // bare ObjClosure*. - if (closure == NULL) - { - vm->apiStack[0] = NULL_VAL; - } - else - { - vm->apiStack[0] = OBJ_VAL(closure); - } -} - -void metaGetModuleVariables(WrenVM* vm) -{ - wrenEnsureSlots(vm, 3); - - Value moduleValue = wrenMapGet(vm->modules, vm->apiStack[1]); - if (IS_UNDEFINED(moduleValue)) - { - vm->apiStack[0] = NULL_VAL; - return; - } - - ObjModule* module = AS_MODULE(moduleValue); - ObjList* names = wrenNewList(vm, module->variableNames.count); - vm->apiStack[0] = OBJ_VAL(names); - - // Initialize the elements to null in case a collection happens when we - // allocate the strings below. - for (int i = 0; i < names->elements.count; i++) - { - names->elements.data[i] = NULL_VAL; - } - - for (int i = 0; i < names->elements.count; i++) - { - names->elements.data[i] = OBJ_VAL(module->variableNames.data[i]); - } -} - -const char* wrenMetaSource() -{ - return metaModuleSource; -} - -WrenForeignMethodFn wrenMetaBindForeignMethod(WrenVM* vm, - const char* className, - bool isStatic, - const char* signature) -{ - // There is only one foreign method in the meta module. - ASSERT(strcmp(className, "Meta") == 0, "Should be in Meta class."); - ASSERT(isStatic, "Should be static."); - - if (strcmp(signature, "compile_(_,_,_)") == 0) - { - return metaCompile; - } - - if (strcmp(signature, "getModuleVariables_(_)") == 0) - { - return metaGetModuleVariables; - } - - ASSERT(false, "Unknown method."); - return NULL; +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