// MIT License // // Copyright (c) 2013-2021 Robert Nystrom and Wren Contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // Begin file "wren.h" #ifndef wren_h #define wren_h #include #include #include // The Wren semantic version number components. #define WREN_VERSION_MAJOR 0 #define WREN_VERSION_MINOR 4 #define WREN_VERSION_PATCH 0 // A human-friendly string representation of the version. #define WREN_VERSION_STRING "0.4.0" // A monotonically increasing numeric representation of the version number. Use // this if you want to do range checks over versions. #define WREN_VERSION_NUMBER (WREN_VERSION_MAJOR * 1000000 + \ WREN_VERSION_MINOR * 1000 + \ WREN_VERSION_PATCH) #ifndef WREN_API #if defined(_MSC_VER) && defined(WREN_API_DLLEXPORT) #define WREN_API __declspec( dllexport ) #else #define WREN_API #endif #endif //WREN_API // A single virtual machine for executing Wren code. // // Wren has no global state, so all state stored by a running interpreter lives // here. typedef struct WrenVM WrenVM; // A handle to a Wren object. // // This lets code outside of the VM hold a persistent reference to an object. // After a handle is acquired, and until it is released, this ensures the // garbage collector will not reclaim the object it references. typedef struct WrenHandle WrenHandle; // A generic allocation function that handles all explicit memory management // used by Wren. It's used like so: // // - To allocate new memory, [memory] is NULL and [newSize] is the desired // size. It should return the allocated memory or NULL on failure. // // - To attempt to grow an existing allocation, [memory] is the memory, and // [newSize] is the desired size. It should return [memory] if it was able to // grow it in place, or a new pointer if it had to move it. // // - To shrink memory, [memory] and [newSize] are the same as above but it will // always return [memory]. // // - To free memory, [memory] will be the memory to free and [newSize] will be // zero. It should return NULL. typedef void* (*WrenReallocateFn)(void* memory, size_t newSize, void* userData); // A function callable from Wren code, but implemented in C. typedef void (*WrenForeignMethodFn)(WrenVM* vm); // A finalizer function for freeing resources owned by an instance of a foreign // class. Unlike most foreign methods, finalizers do not have access to the VM // and should not interact with it since it's in the middle of a garbage // collection. typedef void (*WrenFinalizerFn)(void* data); // Gives the host a chance to canonicalize the imported module name, // potentially taking into account the (previously resolved) name of the module // that contains the import. Typically, this is used to implement relative // imports. typedef const char* (*WrenResolveModuleFn)(WrenVM* vm, const char* importer, const char* name); // Forward declare struct WrenLoadModuleResult; // Called after loadModuleFn is called for module [name]. The original returned result // is handed back to you in this callback, so that you can free memory if appropriate. typedef void (*WrenLoadModuleCompleteFn)(WrenVM* vm, const char* name, struct WrenLoadModuleResult result); // The result of a loadModuleFn call. // [source] is the source code for the module, or NULL if the module is not found. // [onComplete] an optional callback that will be called once Wren is done with the result. typedef struct WrenLoadModuleResult { const char* source; WrenLoadModuleCompleteFn onComplete; void* userData; } WrenLoadModuleResult; // Loads and returns the source code for the module [name]. typedef WrenLoadModuleResult (*WrenLoadModuleFn)(WrenVM* vm, const char* name); // Returns a pointer to a foreign method on [className] in [module] with // [signature]. typedef WrenForeignMethodFn (*WrenBindForeignMethodFn)(WrenVM* vm, const char* module, const char* className, bool isStatic, const char* signature); // Displays a string of text to the user. typedef void (*WrenWriteFn)(WrenVM* vm, const char* text); typedef enum { // A syntax or resolution error detected at compile time. WREN_ERROR_COMPILE, // The error message for a runtime error. WREN_ERROR_RUNTIME, // One entry of a runtime error's stack trace. WREN_ERROR_STACK_TRACE } WrenErrorType; // Reports an error to the user. // // An error detected during compile time is reported by calling this once with // [type] `WREN_ERROR_COMPILE`, the resolved name of the [module] and [line] // where the error occurs, and the compiler's error [message]. // // A runtime error is reported by calling this once with [type] // `WREN_ERROR_RUNTIME`, no [module] or [line], and the runtime error's // [message]. After that, a series of [type] `WREN_ERROR_STACK_TRACE` calls are // made for each line in the stack trace. Each of those has the resolved // [module] and [line] where the method or function is defined and [message] is // the name of the method or function. typedef void (*WrenErrorFn)( WrenVM* vm, WrenErrorType type, const char* module, int line, const char* message); typedef struct { // The callback invoked when the foreign object is created. // // This must be provided. Inside the body of this, it must call // [wrenSetSlotNewForeign()] exactly once. WrenForeignMethodFn allocate; // The callback invoked when the garbage collector is about to collect a // foreign object's memory. // // This may be `NULL` if the foreign class does not need to finalize. WrenFinalizerFn finalize; } WrenForeignClassMethods; // Returns a pair of pointers to the foreign methods used to allocate and // finalize the data for instances of [className] in resolved [module]. typedef WrenForeignClassMethods (*WrenBindForeignClassFn)( WrenVM* vm, const char* module, const char* className); typedef struct { // The callback Wren will use to allocate, reallocate, and deallocate memory. // // If `NULL`, defaults to a built-in function that uses `realloc` and `free`. WrenReallocateFn reallocateFn; // The callback Wren uses to resolve a module name. // // Some host applications may wish to support "relative" imports, where the // meaning of an import string depends on the module that contains it. To // support that without baking any policy into Wren itself, the VM gives the // host a chance to resolve an import string. // // Before an import is loaded, it calls this, passing in the name of the // module that contains the import and the import string. The host app can // look at both of those and produce a new "canonical" string that uniquely // identifies the module. This string is then used as the name of the module // going forward. It is what is passed to [loadModuleFn], how duplicate // imports of the same module are detected, and how the module is reported in // stack traces. // // If you leave this function NULL, then the original import string is // treated as the resolved string. // // If an import cannot be resolved by the embedder, it should return NULL and // Wren will report that as a runtime error. // // Wren will take ownership of the string you return and free it for you, so // it should be allocated using the same allocation function you provide // above. WrenResolveModuleFn resolveModuleFn; // The callback Wren uses to load a module. // // Since Wren does not talk directly to the file system, it relies on the // embedder to physically locate and read the source code for a module. The // first time an import appears, Wren will call this and pass in the name of // the module being imported. The method will return a result, which contains // the source code for that module. Memory for the source is owned by the // host application, and can be freed using the onComplete callback. // // This will only be called once for any given module name. Wren caches the // result internally so subsequent imports of the same module will use the // previous source and not call this. // // If a module with the given name could not be found by the embedder, it // should return NULL and Wren will report that as a runtime error. WrenLoadModuleFn loadModuleFn; // The callback Wren uses to find a foreign method and bind it to a class. // // When a foreign method is declared in a class, this will be called with the // foreign method's module, class, and signature when the class body is // executed. It should return a pointer to the foreign function that will be // bound to that method. // // If the foreign function could not be found, this should return NULL and // Wren will report it as runtime error. WrenBindForeignMethodFn bindForeignMethodFn; // The callback Wren uses to find a foreign class and get its foreign methods. // // When a foreign class is declared, this will be called with the class's // module and name when the class body is executed. It should return the // foreign functions uses to allocate and (optionally) finalize the bytes // stored in the foreign object when an instance is created. WrenBindForeignClassFn bindForeignClassFn; // The callback Wren uses to display text when `System.print()` or the other // related functions are called. // // If this is `NULL`, Wren discards any printed text. WrenWriteFn writeFn; // The callback Wren uses to report errors. // // When an error occurs, this will be called with the module name, line // number, and an error message. If this is `NULL`, Wren doesn't report any // errors. WrenErrorFn errorFn; // The number of bytes Wren will allocate before triggering the first garbage // collection. // // If zero, defaults to 10MB. size_t initialHeapSize; // After a collection occurs, the threshold for the next collection is // determined based on the number of bytes remaining in use. This allows Wren // to shrink its memory usage automatically after reclaiming a large amount // of memory. // // This can be used to ensure that the heap does not get too small, which can // in turn lead to a large number of collections afterwards as the heap grows // back to a usable size. // // If zero, defaults to 1MB. size_t minHeapSize; // Wren will resize the heap automatically as the number of bytes // remaining in use after a collection changes. This number determines the // amount of additional memory Wren will use after a collection, as a // percentage of the current heap size. // // For example, say that this is 50. After a garbage collection, when there // are 400 bytes of memory still in use, the next collection will be triggered // after a total of 600 bytes are allocated (including the 400 already in // use.) // // Setting this to a smaller number wastes less memory, but triggers more // frequent garbage collections. // // If zero, defaults to 50. int heapGrowthPercent; // User-defined data associated with the VM. void* userData; } WrenConfiguration; typedef enum { WREN_RESULT_SUCCESS, WREN_RESULT_COMPILE_ERROR, WREN_RESULT_RUNTIME_ERROR } WrenInterpretResult; // The type of an object stored in a slot. // // This is not necessarily the object's *class*, but instead its low level // representation type. typedef enum { WREN_TYPE_BOOL, WREN_TYPE_NUM, WREN_TYPE_FOREIGN, WREN_TYPE_LIST, WREN_TYPE_MAP, WREN_TYPE_NULL, WREN_TYPE_STRING, // The object is of a type that isn't accessible by the C API. WREN_TYPE_UNKNOWN } WrenType; // Get the current wren version number. // // Can be used to range checks over versions. WREN_API int wrenGetVersionNumber(); // Initializes [configuration] with all of its default values. // // Call this before setting the particular fields you care about. WREN_API void wrenInitConfiguration(WrenConfiguration* configuration); // Creates a new Wren virtual machine using the given [configuration]. Wren // will copy the configuration data, so the argument passed to this can be // freed after calling this. If [configuration] is `NULL`, uses a default // configuration. WREN_API WrenVM* wrenNewVM(WrenConfiguration* configuration); // Disposes of all resources is use by [vm], which was previously created by a // call to [wrenNewVM]. WREN_API void wrenFreeVM(WrenVM* vm); // Immediately run the garbage collector to free unused memory. WREN_API void wrenCollectGarbage(WrenVM* vm); // Runs [source], a string of Wren source code in a new fiber in [vm] in the // context of resolved [module]. WREN_API WrenInterpretResult wrenInterpret(WrenVM* vm, const char* module, const char* source); // Creates a handle that can be used to invoke a method with [signature] on // using a receiver and arguments that are set up on the stack. // // This handle can be used repeatedly to directly invoke that method from C // code using [wrenCall]. // // When you are done with this handle, it must be released using // [wrenReleaseHandle]. WREN_API WrenHandle* wrenMakeCallHandle(WrenVM* vm, const char* signature); // Calls [method], using the receiver and arguments previously set up on the // stack. // // [method] must have been created by a call to [wrenMakeCallHandle]. The // arguments to the method must be already on the stack. The receiver should be // in slot 0 with the remaining arguments following it, in order. It is an // error if the number of arguments provided does not match the method's // signature. // // After this returns, you can access the return value from slot 0 on the stack. WREN_API WrenInterpretResult wrenCall(WrenVM* vm, WrenHandle* method); // Releases the reference stored in [handle]. After calling this, [handle] can // no longer be used. WREN_API void wrenReleaseHandle(WrenVM* vm, WrenHandle* handle); // The following functions are intended to be called from foreign methods or // finalizers. The interface Wren provides to a foreign method is like a // register machine: you are given a numbered array of slots that values can be // read from and written to. Values always live in a slot (unless explicitly // captured using wrenGetSlotHandle(), which ensures the garbage collector can // find them. // // When your foreign function is called, you are given one slot for the receiver // and each argument to the method. The receiver is in slot 0 and the arguments // are in increasingly numbered slots after that. You are free to read and // write to those slots as you want. If you want more slots to use as scratch // space, you can call wrenEnsureSlots() to add more. // // When your function returns, every slot except slot zero is discarded and the // value in slot zero is used as the return value of the method. If you don't // store a return value in that slot yourself, it will retain its previous // value, the receiver. // // While Wren is dynamically typed, C is not. This means the C interface has to // support the various types of primitive values a Wren variable can hold: bool, // double, string, etc. If we supported this for every operation in the C API, // there would be a combinatorial explosion of functions, like "get a // double-valued element from a list", "insert a string key and double value // into a map", etc. // // To avoid that, the only way to convert to and from a raw C value is by going // into and out of a slot. All other functions work with values already in a // slot. So, to add an element to a list, you put the list in one slot, and the // element in another. Then there is a single API function wrenInsertInList() // that takes the element out of that slot and puts it into the list. // // The goal of this API is to be easy to use while not compromising performance. // The latter means it does not do type or bounds checking at runtime except // using assertions which are generally removed from release builds. C is an // unsafe language, so it's up to you to be careful to use it correctly. In // return, you get a very fast FFI. // Returns the number of slots available to the current foreign method. WREN_API int wrenGetSlotCount(WrenVM* vm); // Ensures that the foreign method stack has at least [numSlots] available for // use, growing the stack if needed. // // Does not shrink the stack if it has more than enough slots. // // It is an error to call this from a finalizer. WREN_API void wrenEnsureSlots(WrenVM* vm, int numSlots); // Gets the type of the object in [slot]. WREN_API WrenType wrenGetSlotType(WrenVM* vm, int slot); // Reads a boolean value from [slot]. // // It is an error to call this if the slot does not contain a boolean value. WREN_API bool wrenGetSlotBool(WrenVM* vm, int slot); // Reads a byte array from [slot]. // // The memory for the returned string is owned by Wren. You can inspect it // while in your foreign method, but cannot keep a pointer to it after the // function returns, since the garbage collector may reclaim it. // // Returns a pointer to the first byte of the array and fill [length] with the // number of bytes in the array. // // It is an error to call this if the slot does not contain a string. WREN_API const char* wrenGetSlotBytes(WrenVM* vm, int slot, int* length); // Reads a number from [slot]. // // It is an error to call this if the slot does not contain a number. WREN_API double wrenGetSlotDouble(WrenVM* vm, int slot); // Reads a foreign object from [slot] and returns a pointer to the foreign data // stored with it. // // It is an error to call this if the slot does not contain an instance of a // foreign class. WREN_API void* wrenGetSlotForeign(WrenVM* vm, int slot); // Reads a string from [slot]. // // The memory for the returned string is owned by Wren. You can inspect it // while in your foreign method, but cannot keep a pointer to it after the // function returns, since the garbage collector may reclaim it. // // It is an error to call this if the slot does not contain a string. WREN_API const char* wrenGetSlotString(WrenVM* vm, int slot); // Creates a handle for the value stored in [slot]. // // This will prevent the object that is referred to from being garbage collected // until the handle is released by calling [wrenReleaseHandle()]. WREN_API WrenHandle* wrenGetSlotHandle(WrenVM* vm, int slot); // Stores the boolean [value] in [slot]. WREN_API void wrenSetSlotBool(WrenVM* vm, int slot, bool value); // Stores the array [length] of [bytes] in [slot]. // // The bytes are copied to a new string within Wren's heap, so you can free // memory used by them after this is called. WREN_API void wrenSetSlotBytes(WrenVM* vm, int slot, const char* bytes, size_t length); // Stores the numeric [value] in [slot]. WREN_API void wrenSetSlotDouble(WrenVM* vm, int slot, double value); // Creates a new instance of the foreign class stored in [classSlot] with [size] // bytes of raw storage and places the resulting object in [slot]. // // This does not invoke the foreign class's constructor on the new instance. If // you need that to happen, call the constructor from Wren, which will then // call the allocator foreign method. In there, call this to create the object // and then the constructor will be invoked when the allocator returns. // // Returns a pointer to the foreign object's data. WREN_API void* wrenSetSlotNewForeign(WrenVM* vm, int slot, int classSlot, size_t size); // Stores a new empty list in [slot]. WREN_API void wrenSetSlotNewList(WrenVM* vm, int slot); // Stores a new empty map in [slot]. WREN_API void wrenSetSlotNewMap(WrenVM* vm, int slot); // Stores null in [slot]. WREN_API void wrenSetSlotNull(WrenVM* vm, int slot); // Stores the string [text] in [slot]. // // The [text] is copied to a new string within Wren's heap, so you can free // memory used by it after this is called. The length is calculated using // [strlen()]. If the string may contain any null bytes in the middle, then you // should use [wrenSetSlotBytes()] instead. WREN_API void wrenSetSlotString(WrenVM* vm, int slot, const char* text); // Stores the value captured in [handle] in [slot]. // // This does not release the handle for the value. WREN_API void wrenSetSlotHandle(WrenVM* vm, int slot, WrenHandle* handle); // Returns the number of elements in the list stored in [slot]. WREN_API int wrenGetListCount(WrenVM* vm, int slot); // Reads element [index] from the list in [listSlot] and stores it in // [elementSlot]. WREN_API void wrenGetListElement(WrenVM* vm, int listSlot, int index, int elementSlot); // Sets the value stored at [index] in the list at [listSlot], // to the value from [elementSlot]. WREN_API void wrenSetListElement(WrenVM* vm, int listSlot, int index, int elementSlot); // Takes the value stored at [elementSlot] and inserts it into the list stored // at [listSlot] at [index]. // // As in Wren, negative indexes can be used to insert from the end. To append // an element, use `-1` for the index. WREN_API void wrenInsertInList(WrenVM* vm, int listSlot, int index, int elementSlot); // Returns the number of entries in the map stored in [slot]. WREN_API int wrenGetMapCount(WrenVM* vm, int slot); // Returns true if the key in [keySlot] is found in the map placed in [mapSlot]. WREN_API bool wrenGetMapContainsKey(WrenVM* vm, int mapSlot, int keySlot); // Retrieves a value with the key in [keySlot] from the map in [mapSlot] and // stores it in [valueSlot]. WREN_API void wrenGetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot); // Takes the value stored at [valueSlot] and inserts it into the map stored // at [mapSlot] with key [keySlot]. WREN_API void wrenSetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot); // Removes a value from the map in [mapSlot], with the key from [keySlot], // and place it in [removedValueSlot]. If not found, [removedValueSlot] is // set to null, the same behaviour as the Wren Map API. WREN_API void wrenRemoveMapValue(WrenVM* vm, int mapSlot, int keySlot, int removedValueSlot); // Looks up the top level variable with [name] in resolved [module] and stores // it in [slot]. WREN_API void wrenGetVariable(WrenVM* vm, const char* module, const char* name, int slot); // Looks up the top level variable with [name] in resolved [module], // returns false if not found. The module must be imported at the time, // use wrenHasModule to ensure that before calling. WREN_API bool wrenHasVariable(WrenVM* vm, const char* module, const char* name); // Returns true if [module] has been imported/resolved before, false if not. WREN_API bool wrenHasModule(WrenVM* vm, const char* module); // Sets the current fiber to be aborted, and uses the value in [slot] as the // runtime error object. WREN_API void wrenAbortFiber(WrenVM* vm, int slot); // Returns the user data associated with the WrenVM. WREN_API void* wrenGetUserData(WrenVM* vm); // Sets user data associated with the WrenVM. WREN_API void wrenSetUserData(WrenVM* vm, void* userData); #endif // End file "wren.h" // Begin file "wren_debug.h" #ifndef wren_debug_h #define wren_debug_h // Begin file "wren_value.h" #ifndef wren_value_h #define wren_value_h #include #include // Begin file "wren_common.h" #ifndef wren_common_h #define wren_common_h // This header contains macros and defines used across the entire Wren // implementation. In particular, it contains "configuration" defines that // control how Wren works. Some of these are only used while hacking on Wren // itself. // // This header is *not* intended to be included by code outside of Wren itself. // Wren pervasively uses the C99 integer types (uint16_t, etc.) along with some // of the associated limit constants (UINT32_MAX, etc.). The constants are not // part of standard C++, so aren't included by default by C++ compilers when you // include unless __STDC_LIMIT_MACROS is defined. #define __STDC_LIMIT_MACROS #include // These flags let you control some details of the interpreter's implementation. // Usually they trade-off a bit of portability for speed. They default to the // most efficient behavior. // If true, then Wren uses a NaN-tagged double for its core value // representation. Otherwise, it uses a larger more conventional struct. The // former is significantly faster and more compact. The latter is useful for // debugging and may be more portable. // // Defaults to on. #ifndef WREN_NAN_TAGGING #define WREN_NAN_TAGGING 1 #endif // If true, the VM's interpreter loop uses computed gotos. See this for more: // http://gcc.gnu.org/onlinedocs/gcc-3.1.1/gcc/Labels-as-Values.html // Enabling this speeds up the main dispatch loop a bit, but requires compiler // support. // see https://bullno1.com/blog/switched-goto for alternative // Defaults to true on supported compilers. #ifndef WREN_COMPUTED_GOTO #if defined(_MSC_VER) && !defined(__clang__) // No computed gotos in Visual Studio. #define WREN_COMPUTED_GOTO 0 #else #define WREN_COMPUTED_GOTO 1 #endif #endif // The VM includes a number of optional modules. You can choose to include // these or not. By default, they are all available. To disable one, set the // corresponding `WREN_OPT_` define to `0`. #ifndef WREN_OPT_META #define WREN_OPT_META 1 #endif #ifndef WREN_OPT_RANDOM #define WREN_OPT_RANDOM 1 #endif // These flags are useful for debugging and hacking on Wren itself. They are not // intended to be used for production code. They default to off. // Set this to true to stress test the GC. It will perform a collection before // every allocation. This is useful to ensure that memory is always correctly // reachable. #define WREN_DEBUG_GC_STRESS 0 // Set this to true to log memory operations as they occur. #define WREN_DEBUG_TRACE_MEMORY 0 // Set this to true to log garbage collections as they occur. #define WREN_DEBUG_TRACE_GC 0 // Set this to true to print out the compiled bytecode of each function. #define WREN_DEBUG_DUMP_COMPILED_CODE 0 // Set this to trace each instruction as it's executed. #define WREN_DEBUG_TRACE_INSTRUCTIONS 0 // The maximum number of module-level variables that may be defined at one time. // This limitation comes from the 16 bits used for the arguments to // `CODE_LOAD_MODULE_VAR` and `CODE_STORE_MODULE_VAR`. #define MAX_MODULE_VARS 65536 // The maximum number of arguments that can be passed to a method. Note that // this limitation is hardcoded in other places in the VM, in particular, the // `CODE_CALL_XX` instructions assume a certain maximum number. #define MAX_PARAMETERS 16 // The maximum name of a method, not including the signature. This is an // arbitrary but enforced maximum just so we know how long the method name // strings need to be in the parser. #define MAX_METHOD_NAME 64 // The maximum length of a method signature. Signatures look like: // // foo // Getter. // foo() // No-argument method. // foo(_) // One-argument method. // foo(_,_) // Two-argument method. // init foo() // Constructor initializer. // // The maximum signature length takes into account the longest method name, the // maximum number of parameters with separators between them, "init ", and "()". #define MAX_METHOD_SIGNATURE (MAX_METHOD_NAME + (MAX_PARAMETERS * 2) + 6) // The maximum length of an identifier. The only real reason for this limitation // is so that error messages mentioning variables can be stack allocated. #define MAX_VARIABLE_NAME 64 // The maximum number of fields a class can have, including inherited fields. // This is explicit in the bytecode since `CODE_CLASS` and `CODE_SUBCLASS` take // a single byte for the number of fields. Note that it's 255 and not 256 // because creating a class takes the *number* of fields, not the *highest // field index*. #define MAX_FIELDS 255 // Use the VM's allocator to allocate an object of [type]. #define ALLOCATE(vm, type) \ ((type*)wrenReallocate(vm, NULL, 0, sizeof(type))) // Use the VM's allocator to allocate an object of [mainType] containing a // flexible array of [count] objects of [arrayType]. #define ALLOCATE_FLEX(vm, mainType, arrayType, count) \ ((mainType*)wrenReallocate(vm, NULL, 0, \ sizeof(mainType) + sizeof(arrayType) * (count))) // Use the VM's allocator to allocate an array of [count] elements of [type]. #define ALLOCATE_ARRAY(vm, type, count) \ ((type*)wrenReallocate(vm, NULL, 0, sizeof(type) * (count))) // Use the VM's allocator to free the previously allocated memory at [pointer]. #define DEALLOCATE(vm, pointer) wrenReallocate(vm, pointer, 0, 0) // The Microsoft compiler does not support the "inline" modifier when compiling // as plain C. #if defined( _MSC_VER ) && !defined(__cplusplus) #define inline _inline #endif // This is used to clearly mark flexible-sized arrays that appear at the end of // some dynamically-allocated structs, known as the "struct hack". #if __STDC_VERSION__ >= 199901L // In C99, a flexible array member is just "[]". #define FLEXIBLE_ARRAY #else // Elsewhere, use a zero-sized array. It's technically undefined behavior, // but works reliably in most known compilers. #define FLEXIBLE_ARRAY 0 #endif // Assertions are used to validate program invariants. They indicate things the // program expects to be true about its internal state during execution. If an // assertion fails, there is a bug in Wren. // // Assertions add significant overhead, so are only enabled in debug builds. #ifdef DEBUG #include #define ASSERT(condition, message) \ do \ { \ if (!(condition)) \ { \ fprintf(stderr, "[%s:%d] Assert failed in %s(): %s\n", \ __FILE__, __LINE__, __func__, message); \ abort(); \ } \ } while (false) // Indicates that we know execution should never reach this point in the // program. In debug mode, we assert this fact because it's a bug to get here. // // In release mode, we use compiler-specific built in functions to tell the // compiler the code can't be reached. This avoids "missing return" warnings // in some cases and also lets it perform some optimizations by assuming the // code is never reached. #define UNREACHABLE() \ do \ { \ fprintf(stderr, "[%s:%d] This code should not be reached in %s()\n", \ __FILE__, __LINE__, __func__); \ abort(); \ } while (false) #else #define ASSERT(condition, message) do { } while (false) // Tell the compiler that this part of the code will never be reached. #if defined( _MSC_VER ) #define UNREACHABLE() __assume(0) #elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)) #define UNREACHABLE() __builtin_unreachable() #else #define UNREACHABLE() #endif #endif #endif // End file "wren_common.h" // Begin file "wren_math.h" #ifndef wren_math_h #define wren_math_h #include #include // A union to let us reinterpret a double as raw bits and back. typedef union { uint64_t bits64; uint32_t bits32[2]; double num; } WrenDoubleBits; #define WREN_DOUBLE_QNAN_POS_MIN_BITS (UINT64_C(0x7FF8000000000000)) #define WREN_DOUBLE_QNAN_POS_MAX_BITS (UINT64_C(0x7FFFFFFFFFFFFFFF)) #define WREN_DOUBLE_NAN (wrenDoubleFromBits(WREN_DOUBLE_QNAN_POS_MIN_BITS)) static inline double wrenDoubleFromBits(uint64_t bits) { WrenDoubleBits data; data.bits64 = bits; return data.num; } static inline uint64_t wrenDoubleToBits(double num) { WrenDoubleBits data; data.num = num; return data.bits64; } #endif // End file "wren_math.h" // Begin file "wren_utils.h" #ifndef wren_utils_h #define wren_utils_h // Reusable data structures and other utility functions. // Forward declare this here to break a cycle between wren_utils.h and // wren_value.h. typedef struct sObjString ObjString; // We need buffers of a few different types. To avoid lots of casting between // void* and back, we'll use the preprocessor as a poor man's generics and let // it generate a few type-specific ones. #define DECLARE_BUFFER(name, type) \ typedef struct \ { \ type* data; \ int count; \ int capacity; \ } name##Buffer; \ void wren##name##BufferInit(name##Buffer* buffer); \ void wren##name##BufferClear(WrenVM* vm, name##Buffer* buffer); \ void wren##name##BufferFill(WrenVM* vm, name##Buffer* buffer, type data, \ int count); \ void wren##name##BufferWrite(WrenVM* vm, name##Buffer* buffer, type data) // This should be used once for each type instantiation, somewhere in a .c file. #define DEFINE_BUFFER(name, type) \ void wren##name##BufferInit(name##Buffer* buffer) \ { \ buffer->data = NULL; \ buffer->capacity = 0; \ buffer->count = 0; \ } \ \ void wren##name##BufferClear(WrenVM* vm, name##Buffer* buffer) \ { \ wrenReallocate(vm, buffer->data, 0, 0); \ wren##name##BufferInit(buffer); \ } \ \ void wren##name##BufferFill(WrenVM* vm, name##Buffer* buffer, type data, \ int count) \ { \ if (buffer->capacity < buffer->count + count) \ { \ int capacity = wrenPowerOf2Ceil(buffer->count + count); \ buffer->data = (type*)wrenReallocate(vm, buffer->data, \ buffer->capacity * sizeof(type), capacity * sizeof(type)); \ buffer->capacity = capacity; \ } \ \ for (int i = 0; i < count; i++) \ { \ buffer->data[buffer->count++] = data; \ } \ } \ \ void wren##name##BufferWrite(WrenVM* vm, name##Buffer* buffer, type data) \ { \ wren##name##BufferFill(vm, buffer, data, 1); \ } DECLARE_BUFFER(Byte, uint8_t); DECLARE_BUFFER(Int, int); DECLARE_BUFFER(String, ObjString*); // TODO: Change this to use a map. typedef StringBuffer SymbolTable; // Initializes the symbol table. void wrenSymbolTableInit(SymbolTable* symbols); // Frees all dynamically allocated memory used by the symbol table, but not the // SymbolTable itself. void wrenSymbolTableClear(WrenVM* vm, SymbolTable* symbols); // Adds name to the symbol table. Returns the index of it in the table. int wrenSymbolTableAdd(WrenVM* vm, SymbolTable* symbols, const char* name, size_t length); // Adds name to the symbol table. Returns the index of it in the table. Will // use an existing symbol if already present. int wrenSymbolTableEnsure(WrenVM* vm, SymbolTable* symbols, const char* name, size_t length); // Looks up name in the symbol table. Returns its index if found or -1 if not. int wrenSymbolTableFind(const SymbolTable* symbols, const char* name, size_t length); void wrenBlackenSymbolTable(WrenVM* vm, SymbolTable* symbolTable); // Returns the number of bytes needed to encode [value] in UTF-8. // // Returns 0 if [value] is too large to encode. int wrenUtf8EncodeNumBytes(int value); // Encodes value as a series of bytes in [bytes], which is assumed to be large // enough to hold the encoded result. // // Returns the number of written bytes. int wrenUtf8Encode(int value, uint8_t* bytes); // Decodes the UTF-8 sequence starting at [bytes] (which has max [length]), // returning the code point. // // Returns -1 if the bytes are not a valid UTF-8 sequence. int wrenUtf8Decode(const uint8_t* bytes, uint32_t length); // Returns the number of bytes in the UTF-8 sequence starting with [byte]. // // If the character at that index is not the beginning of a UTF-8 sequence, // returns 0. int wrenUtf8DecodeNumBytes(uint8_t byte); // Returns the smallest power of two that is equal to or greater than [n]. int wrenPowerOf2Ceil(int n); // Validates that [value] is within `[0, count)`. Also allows // negative indices which map backwards from the end. Returns the valid positive // index value. If invalid, returns `UINT32_MAX`. uint32_t wrenValidateIndex(uint32_t count, int64_t value); #endif // End file "wren_utils.h" // This defines the built-in types and their core representations in memory. // Since Wren is dynamically typed, any variable can hold a value of any type, // and the type can change at runtime. Implementing this efficiently is // critical for performance. // // The main type exposed by this is [Value]. A C variable of that type is a // storage location that can hold any Wren value. The stack, module variables, // and instance fields are all implemented in C as variables of type Value. // // The built-in types for booleans, numbers, and null are unboxed: their value // is stored directly in the Value, and copying a Value copies the value. Other // types--classes, instances of classes, functions, lists, and strings--are all // reference types. They are stored on the heap and the Value just stores a // pointer to it. Copying the Value copies a reference to the same object. The // Wren implementation calls these "Obj", or objects, though to a user, all // values are objects. // // There is also a special singleton value "undefined". It is used internally // but never appears as a real value to a user. It has two uses: // // - It is used to identify module variables that have been implicitly declared // by use in a forward reference but not yet explicitly declared. These only // exist during compilation and do not appear at runtime. // // - It is used to represent unused map entries in an ObjMap. // // There are two supported Value representations. The main one uses a technique // called "NaN tagging" (explained in detail below) to store a number, any of // the value types, or a pointer, all inside one double-precision floating // point number. A larger, slower, Value type that uses a struct to store these // is also supported, and is useful for debugging the VM. // // The representation is controlled by the `WREN_NAN_TAGGING` define. If that's // defined, Nan tagging is used. // These macros cast a Value to one of the specific object types. These do *not* // perform any validation, so must only be used after the Value has been // ensured to be the right type. #define AS_CLASS(value) ((ObjClass*)AS_OBJ(value)) // ObjClass* #define AS_CLOSURE(value) ((ObjClosure*)AS_OBJ(value)) // ObjClosure* #define AS_FIBER(v) ((ObjFiber*)AS_OBJ(v)) // ObjFiber* #define AS_FN(value) ((ObjFn*)AS_OBJ(value)) // ObjFn* #define AS_FOREIGN(v) ((ObjForeign*)AS_OBJ(v)) // ObjForeign* #define AS_INSTANCE(value) ((ObjInstance*)AS_OBJ(value)) // ObjInstance* #define AS_LIST(value) ((ObjList*)AS_OBJ(value)) // ObjList* #define AS_MAP(value) ((ObjMap*)AS_OBJ(value)) // ObjMap* #define AS_MODULE(value) ((ObjModule*)AS_OBJ(value)) // ObjModule* #define AS_NUM(value) (wrenValueToNum(value)) // double #define AS_RANGE(v) ((ObjRange*)AS_OBJ(v)) // ObjRange* #define AS_STRING(v) ((ObjString*)AS_OBJ(v)) // ObjString* #define AS_CSTRING(v) (AS_STRING(v)->value) // const char* // These macros promote a primitive C value to a full Wren Value. There are // more defined below that are specific to the Nan tagged or other // representation. #define BOOL_VAL(boolean) ((boolean) ? TRUE_VAL : FALSE_VAL) // boolean #define NUM_VAL(num) (wrenNumToValue(num)) // double #define OBJ_VAL(obj) (wrenObjectToValue((Obj*)(obj))) // Any Obj___* // These perform type tests on a Value, returning `true` if the Value is of the // given type. #define IS_BOOL(value) (wrenIsBool(value)) // Bool #define IS_CLASS(value) (wrenIsObjType(value, OBJ_CLASS)) // ObjClass #define IS_CLOSURE(value) (wrenIsObjType(value, OBJ_CLOSURE)) // ObjClosure #define IS_FIBER(value) (wrenIsObjType(value, OBJ_FIBER)) // ObjFiber #define IS_FN(value) (wrenIsObjType(value, OBJ_FN)) // ObjFn #define IS_FOREIGN(value) (wrenIsObjType(value, OBJ_FOREIGN)) // ObjForeign #define IS_INSTANCE(value) (wrenIsObjType(value, OBJ_INSTANCE)) // ObjInstance #define IS_LIST(value) (wrenIsObjType(value, OBJ_LIST)) // ObjList #define IS_MAP(value) (wrenIsObjType(value, OBJ_MAP)) // ObjMap #define IS_RANGE(value) (wrenIsObjType(value, OBJ_RANGE)) // ObjRange #define IS_STRING(value) (wrenIsObjType(value, OBJ_STRING)) // ObjString // Creates a new string object from [text], which should be a bare C string // literal. This determines the length of the string automatically at compile // time based on the size of the character array (-1 for the terminating '\0'). #define CONST_STRING(vm, text) wrenNewStringLength((vm), (text), sizeof(text) - 1) // Identifies which specific type a heap-allocated object is. typedef enum { OBJ_CLASS, OBJ_CLOSURE, OBJ_FIBER, OBJ_FN, OBJ_FOREIGN, OBJ_INSTANCE, OBJ_LIST, OBJ_MAP, OBJ_MODULE, OBJ_RANGE, OBJ_STRING, OBJ_UPVALUE } ObjType; typedef struct sObjClass ObjClass; // Base struct for all heap-allocated objects. typedef struct sObj Obj; struct sObj { ObjType type; bool isDark; // The object's class. ObjClass* classObj; // The next object in the linked list of all currently allocated objects. struct sObj* next; }; #if WREN_NAN_TAGGING typedef uint64_t Value; #else typedef enum { VAL_FALSE, VAL_NULL, VAL_NUM, VAL_TRUE, VAL_UNDEFINED, VAL_OBJ } ValueType; typedef struct { ValueType type; union { double num; Obj* obj; } as; } Value; #endif DECLARE_BUFFER(Value, Value); // A heap-allocated string object. struct sObjString { Obj obj; // Number of bytes in the string, not including the null terminator. uint32_t length; // The hash value of the string's contents. uint32_t hash; // Inline array of the string's bytes followed by a null terminator. char value[FLEXIBLE_ARRAY]; }; // The dynamically allocated data structure for a variable that has been used // by a closure. Whenever a function accesses a variable declared in an // enclosing function, it will get to it through this. // // An upvalue can be either "closed" or "open". An open upvalue points directly // to a [Value] that is still stored on the fiber's stack because the local // variable is still in scope in the function where it's declared. // // When that local variable goes out of scope, the upvalue pointing to it will // be closed. When that happens, the value gets copied off the stack into the // upvalue itself. That way, it can have a longer lifetime than the stack // variable. typedef struct sObjUpvalue { // The object header. Note that upvalues have this because they are garbage // collected, but they are not first class Wren objects. Obj obj; // Pointer to the variable this upvalue is referencing. Value* value; // If the upvalue is closed (i.e. the local variable it was pointing to has // been popped off the stack) then the closed-over value will be hoisted out // of the stack into here. [value] will then be changed to point to this. Value closed; // Open upvalues are stored in a linked list by the fiber. This points to the // next upvalue in that list. struct sObjUpvalue* next; } ObjUpvalue; // The type of a primitive function. // // Primitives are similar to foreign functions, but have more direct access to // VM internals. It is passed the arguments in [args]. If it returns a value, // it places it in `args[0]` and returns `true`. If it causes a runtime error // or modifies the running fiber, it returns `false`. typedef bool (*Primitive)(WrenVM* vm, Value* args); // TODO: See if it's actually a perf improvement to have this in a separate // struct instead of in ObjFn. // Stores debugging information for a function used for things like stack // traces. typedef struct { // The name of the function. Heap allocated and owned by the FnDebug. char* name; // An array of line numbers. There is one element in this array for each // bytecode in the function's bytecode array. The value of that element is // the line in the source code that generated that instruction. IntBuffer sourceLines; } FnDebug; // A loaded module and the top-level variables it defines. // // While this is an Obj and is managed by the GC, it never appears as a // first-class object in Wren. typedef struct { Obj obj; // The currently defined top-level variables. ValueBuffer variables; // Symbol table for the names of all module variables. Indexes here directly // correspond to entries in [variables]. SymbolTable variableNames; // The name of the module. ObjString* name; } ObjModule; // A function object. It wraps and owns the bytecode and other debug information // for a callable chunk of code. // // Function objects are not passed around and invoked directly. Instead, they // are always referenced by an [ObjClosure] which is the real first-class // representation of a function. This isn't strictly necessary if they function // has no upvalues, but lets the rest of the VM assume all called objects will // be closures. typedef struct { Obj obj; ByteBuffer code; ValueBuffer constants; // The module where this function was defined. ObjModule* module; // The maximum number of stack slots this function may use. int maxSlots; // The number of upvalues this function closes over. int numUpvalues; // The number of parameters this function expects. Used to ensure that .call // handles a mismatch between number of parameters and arguments. This will // only be set for fns, and not ObjFns that represent methods or scripts. int arity; FnDebug* debug; } ObjFn; // An instance of a first-class function and the environment it has closed over. // Unlike [ObjFn], this has captured the upvalues that the function accesses. typedef struct { Obj obj; // The function that this closure is an instance of. ObjFn* fn; // The upvalues this function has closed over. ObjUpvalue* upvalues[FLEXIBLE_ARRAY]; } ObjClosure; typedef struct { // Pointer to the current (really next-to-be-executed) instruction in the // function's bytecode. uint8_t* ip; // The closure being executed. ObjClosure* closure; // Pointer to the first stack slot used by this call frame. This will contain // the receiver, followed by the function's parameters, then local variables // and temporaries. Value* stackStart; } CallFrame; // Tracks how this fiber has been invoked, aside from the ways that can be // detected from the state of other fields in the fiber. typedef enum { // The fiber is being run from another fiber using a call to `try()`. FIBER_TRY, // The fiber was directly invoked by `runInterpreter()`. This means it's the // initial fiber used by a call to `wrenCall()` or `wrenInterpret()`. FIBER_ROOT, // The fiber is invoked some other way. If [caller] is `NULL` then the fiber // was invoked using `call()`. If [numFrames] is zero, then the fiber has // finished running and is done. If [numFrames] is one and that frame's `ip` // points to the first byte of code, the fiber has not been started yet. FIBER_OTHER, } FiberState; typedef struct sObjFiber { Obj obj; // The stack of value slots. This is used for holding local variables and // temporaries while the fiber is executing. It is heap-allocated and grown // as needed. Value* stack; // A pointer to one past the top-most value on the stack. Value* stackTop; // The number of allocated slots in the stack array. int stackCapacity; // The stack of call frames. This is a dynamic array that grows as needed but // never shrinks. CallFrame* frames; // The number of frames currently in use in [frames]. int numFrames; // The number of [frames] allocated. int frameCapacity; // Pointer to the first node in the linked list of open upvalues that are // pointing to values still on the stack. The head of the list will be the // upvalue closest to the top of the stack, and then the list works downwards. ObjUpvalue* openUpvalues; // The fiber that ran this one. If this fiber is yielded, control will resume // to this one. May be `NULL`. struct sObjFiber* caller; // If the fiber failed because of a runtime error, this will contain the // error object. Otherwise, it will be null. Value error; FiberState state; } ObjFiber; typedef enum { // A primitive method implemented in C in the VM. Unlike foreign methods, // this can directly manipulate the fiber's stack. METHOD_PRIMITIVE, // A primitive that handles .call on Fn. METHOD_FUNCTION_CALL, // A externally-defined C method. METHOD_FOREIGN, // A normal user-defined method. METHOD_BLOCK, // No method for the given symbol. METHOD_NONE } MethodType; typedef struct { MethodType type; // The method function itself. The [type] determines which field of the union // is used. union { Primitive primitive; WrenForeignMethodFn foreign; ObjClosure* closure; } as; } Method; DECLARE_BUFFER(Method, Method); struct sObjClass { Obj obj; ObjClass* superclass; // The number of fields needed for an instance of this class, including all // of its superclass fields. int numFields; // The table of methods that are defined in or inherited by this class. // Methods are called by symbol, and the symbol directly maps to an index in // this table. This makes method calls fast at the expense of empty cells in // the list for methods the class doesn't support. // // You can think of it as a hash table that never has collisions but has a // really low load factor. Since methods are pretty small (just a type and a // pointer), this should be a worthwhile trade-off. MethodBuffer methods; // The name of the class. ObjString* name; // The ClassAttribute for the class, if any Value attributes; }; typedef struct { Obj obj; uint8_t data[FLEXIBLE_ARRAY]; } ObjForeign; typedef struct { Obj obj; Value fields[FLEXIBLE_ARRAY]; } ObjInstance; typedef struct { Obj obj; // The elements in the list. ValueBuffer elements; } ObjList; typedef struct { // The entry's key, or UNDEFINED_VAL if the entry is not in use. Value key; // The value associated with the key. If the key is UNDEFINED_VAL, this will // be false to indicate an open available entry or true to indicate a // tombstone -- an entry that was previously in use but was then deleted. Value value; } MapEntry; // A hash table mapping keys to values. // // We use something very simple: open addressing with linear probing. The hash // table is an array of entries. Each entry is a key-value pair. If the key is // the special UNDEFINED_VAL, it indicates no value is currently in that slot. // Otherwise, it's a valid key, and the value is the value associated with it. // // When entries are added, the array is dynamically scaled by GROW_FACTOR to // keep the number of filled slots under MAP_LOAD_PERCENT. Likewise, if the map // gets empty enough, it will be resized to a smaller array. When this happens, // all existing entries are rehashed and re-added to the new array. // // When an entry is removed, its slot is replaced with a "tombstone". This is an // entry whose key is UNDEFINED_VAL and whose value is TRUE_VAL. When probing // for a key, we will continue past tombstones, because the desired key may be // found after them if the key that was removed was part of a prior collision. // When the array gets resized, all tombstones are discarded. typedef struct { Obj obj; // The number of entries allocated. uint32_t capacity; // The number of entries in the map. uint32_t count; // Pointer to a contiguous array of [capacity] entries. MapEntry* entries; } ObjMap; typedef struct { Obj obj; // The beginning of the range. double from; // The end of the range. May be greater or less than [from]. double to; // True if [to] is included in the range. bool isInclusive; } ObjRange; // An IEEE 754 double-precision float is a 64-bit value with bits laid out like: // // 1 Sign bit // | 11 Exponent bits // | | 52 Mantissa (i.e. fraction) bits // | | | // S[Exponent-][Mantissa------------------------------------------] // // The details of how these are used to represent numbers aren't really // relevant here as long we don't interfere with them. The important bit is NaN. // // An IEEE double can represent a few magical values like NaN ("not a number"), // Infinity, and -Infinity. A NaN is any value where all exponent bits are set: // // v--NaN bits // -11111111111---------------------------------------------------- // // Here, "-" means "doesn't matter". Any bit sequence that matches the above is // a NaN. With all of those "-", it obvious there are a *lot* of different // bit patterns that all mean the same thing. NaN tagging takes advantage of // this. We'll use those available bit patterns to represent things other than // numbers without giving up any valid numeric values. // // NaN values come in two flavors: "signalling" and "quiet". The former are // intended to halt execution, while the latter just flow through arithmetic // operations silently. We want the latter. Quiet NaNs are indicated by setting // the highest mantissa bit: // // v--Highest mantissa bit // -[NaN ]1--------------------------------------------------- // // If all of the NaN bits are set, it's not a number. Otherwise, it is. // That leaves all of the remaining bits as available for us to play with. We // stuff a few different kinds of things here: special singleton values like // "true", "false", and "null", and pointers to objects allocated on the heap. // We'll use the sign bit to distinguish singleton values from pointers. If // it's set, it's a pointer. // // v--Pointer or singleton? // S[NaN ]1--------------------------------------------------- // // For singleton values, we just enumerate the different values. We'll use the // low bits of the mantissa for that, and only need a few: // // 3 Type bits--v // 0[NaN ]1------------------------------------------------[T] // // For pointers, we are left with 51 bits of mantissa to store an address. // That's more than enough room for a 32-bit address. Even 64-bit machines // only actually use 48 bits for addresses, so we've got plenty. We just stuff // the address right into the mantissa. // // Ta-da, double precision numbers, pointers, and a bunch of singleton values, // all stuffed into a single 64-bit sequence. Even better, we don't have to // do any masking or work to extract number values: they are unmodified. This // means math on numbers is fast. #if WREN_NAN_TAGGING // A mask that selects the sign bit. #define SIGN_BIT ((uint64_t)1 << 63) // The bits that must be set to indicate a quiet NaN. #define QNAN ((uint64_t)0x7ffc000000000000) // If the NaN bits are set, it's not a number. #define IS_NUM(value) (((value) & QNAN) != QNAN) // An object pointer is a NaN with a set sign bit. #define IS_OBJ(value) (((value) & (QNAN | SIGN_BIT)) == (QNAN | SIGN_BIT)) #define IS_FALSE(value) ((value) == FALSE_VAL) #define IS_NULL(value) ((value) == NULL_VAL) #define IS_UNDEFINED(value) ((value) == UNDEFINED_VAL) // Masks out the tag bits used to identify the singleton value. #define MASK_TAG (7) // Tag values for the different singleton values. #define TAG_NAN (0) #define TAG_NULL (1) #define TAG_FALSE (2) #define TAG_TRUE (3) #define TAG_UNDEFINED (4) #define TAG_UNUSED2 (5) #define TAG_UNUSED3 (6) #define TAG_UNUSED4 (7) // Value -> 0 or 1. #define AS_BOOL(value) ((value) == TRUE_VAL) // Value -> Obj*. #define AS_OBJ(value) ((Obj*)(uintptr_t)((value) & ~(SIGN_BIT | QNAN))) // Singleton values. #define NULL_VAL ((Value)(uint64_t)(QNAN | TAG_NULL)) #define FALSE_VAL ((Value)(uint64_t)(QNAN | TAG_FALSE)) #define TRUE_VAL ((Value)(uint64_t)(QNAN | TAG_TRUE)) #define UNDEFINED_VAL ((Value)(uint64_t)(QNAN | TAG_UNDEFINED)) // Gets the singleton type tag for a Value (which must be a singleton). #define GET_TAG(value) ((int)((value) & MASK_TAG)) #else // Value -> 0 or 1. #define AS_BOOL(value) ((value).type == VAL_TRUE) // Value -> Obj*. #define AS_OBJ(v) ((v).as.obj) // Determines if [value] is a garbage-collected object or not. #define IS_OBJ(value) ((value).type == VAL_OBJ) #define IS_FALSE(value) ((value).type == VAL_FALSE) #define IS_NULL(value) ((value).type == VAL_NULL) #define IS_NUM(value) ((value).type == VAL_NUM) #define IS_UNDEFINED(value) ((value).type == VAL_UNDEFINED) // Singleton values. #define FALSE_VAL ((Value){ VAL_FALSE, { 0 } }) #define NULL_VAL ((Value){ VAL_NULL, { 0 } }) #define TRUE_VAL ((Value){ VAL_TRUE, { 0 } }) #define UNDEFINED_VAL ((Value){ VAL_UNDEFINED, { 0 } }) #endif // Creates a new "raw" class. It has no metaclass or superclass whatsoever. // This is only used for bootstrapping the initial Object and Class classes, // which are a little special. ObjClass* wrenNewSingleClass(WrenVM* vm, int numFields, ObjString* name); // Makes [superclass] the superclass of [subclass], and causes subclass to // inherit its methods. This should be called before any methods are defined // on subclass. void wrenBindSuperclass(WrenVM* vm, ObjClass* subclass, ObjClass* superclass); // Creates a new class object as well as its associated metaclass. ObjClass* wrenNewClass(WrenVM* vm, ObjClass* superclass, int numFields, ObjString* name); void wrenBindMethod(WrenVM* vm, ObjClass* classObj, int symbol, Method method); // Creates a new closure object that invokes [fn]. Allocates room for its // upvalues, but assumes outside code will populate it. ObjClosure* wrenNewClosure(WrenVM* vm, ObjFn* fn); // Creates a new fiber object that will invoke [closure]. ObjFiber* wrenNewFiber(WrenVM* vm, ObjClosure* closure); // Adds a new [CallFrame] to [fiber] invoking [closure] whose stack starts at // [stackStart]. static inline void wrenAppendCallFrame(WrenVM* vm, ObjFiber* fiber, ObjClosure* closure, Value* stackStart) { // The caller should have ensured we already have enough capacity. ASSERT(fiber->frameCapacity > fiber->numFrames, "No memory for call frame."); CallFrame* frame = &fiber->frames[fiber->numFrames++]; frame->stackStart = stackStart; frame->closure = closure; frame->ip = closure->fn->code.data; } // Ensures [fiber]'s stack has at least [needed] slots. void wrenEnsureStack(WrenVM* vm, ObjFiber* fiber, int needed); static inline bool wrenHasError(const ObjFiber* fiber) { return !IS_NULL(fiber->error); } ObjForeign* wrenNewForeign(WrenVM* vm, ObjClass* classObj, size_t size); // Creates a new empty function. Before being used, it must have code, // constants, etc. added to it. ObjFn* wrenNewFunction(WrenVM* vm, ObjModule* module, int maxSlots); void wrenFunctionBindName(WrenVM* vm, ObjFn* fn, const char* name, int length); // Creates a new instance of the given [classObj]. Value wrenNewInstance(WrenVM* vm, ObjClass* classObj); // Creates a new list with [numElements] elements (which are left // uninitialized.) ObjList* wrenNewList(WrenVM* vm, uint32_t numElements); // Inserts [value] in [list] at [index], shifting down the other elements. void wrenListInsert(WrenVM* vm, ObjList* list, Value value, uint32_t index); // Removes and returns the item at [index] from [list]. Value wrenListRemoveAt(WrenVM* vm, ObjList* list, uint32_t index); // Searches for [value] in [list], returns the index or -1 if not found. int wrenListIndexOf(WrenVM* vm, ObjList* list, Value value); // Creates a new empty map. ObjMap* wrenNewMap(WrenVM* vm); // Validates that [arg] is a valid object for use as a map key. Returns true if // it is and returns false otherwise. Use validateKey usually, for a runtime error. // This separation exists to aid the API in surfacing errors to the developer as well. static inline bool wrenMapIsValidKey(Value arg); // Looks up [key] in [map]. If found, returns the value. Otherwise, returns // `UNDEFINED_VAL`. Value wrenMapGet(ObjMap* map, Value key); // Associates [key] with [value] in [map]. void wrenMapSet(WrenVM* vm, ObjMap* map, Value key, Value value); void wrenMapClear(WrenVM* vm, ObjMap* map); // Removes [key] from [map], if present. Returns the value for the key if found // or `NULL_VAL` otherwise. Value wrenMapRemoveKey(WrenVM* vm, ObjMap* map, Value key); // Creates a new module. ObjModule* wrenNewModule(WrenVM* vm, ObjString* name); // Creates a new range from [from] to [to]. Value wrenNewRange(WrenVM* vm, double from, double to, bool isInclusive); // Creates a new string object and copies [text] into it. // // [text] must be non-NULL. Value wrenNewString(WrenVM* vm, const char* text); // Creates a new string object of [length] and copies [text] into it. // // [text] may be NULL if [length] is zero. Value wrenNewStringLength(WrenVM* vm, const char* text, size_t length); // Creates a new string object by taking a range of characters from [source]. // The range starts at [start], contains [count] bytes, and increments by // [step]. Value wrenNewStringFromRange(WrenVM* vm, ObjString* source, int start, uint32_t count, int step); // Produces a string representation of [value]. Value wrenNumToString(WrenVM* vm, double value); // Creates a new formatted string from [format] and any additional arguments // used in the format string. // // This is a very restricted flavor of formatting, intended only for internal // use by the VM. Two formatting characters are supported, each of which reads // the next argument as a certain type: // // $ - A C string. // @ - A Wren string object. Value wrenStringFormat(WrenVM* vm, const char* format, ...); // Creates a new string containing the UTF-8 encoding of [value]. Value wrenStringFromCodePoint(WrenVM* vm, int value); // Creates a new string from the integer representation of a byte Value wrenStringFromByte(WrenVM* vm, uint8_t value); // Creates a new string containing the code point in [string] starting at byte // [index]. If [index] points into the middle of a UTF-8 sequence, returns an // empty string. Value wrenStringCodePointAt(WrenVM* vm, ObjString* string, uint32_t index); // Search for the first occurence of [needle] within [haystack] and returns its // zero-based offset. Returns `UINT32_MAX` if [haystack] does not contain // [needle]. uint32_t wrenStringFind(ObjString* haystack, ObjString* needle, uint32_t startIndex); // Returns true if [a] and [b] represent the same string. static inline bool wrenStringEqualsCString(const ObjString* a, const char* b, size_t length) { return a->length == length && memcmp(a->value, b, length) == 0; } // Creates a new open upvalue pointing to [value] on the stack. ObjUpvalue* wrenNewUpvalue(WrenVM* vm, Value* value); // Mark [obj] as reachable and still in use. This should only be called // during the sweep phase of a garbage collection. void wrenGrayObj(WrenVM* vm, Obj* obj); // Mark [value] as reachable and still in use. This should only be called // during the sweep phase of a garbage collection. void wrenGrayValue(WrenVM* vm, Value value); // Mark the values in [buffer] as reachable and still in use. This should only // be called during the sweep phase of a garbage collection. void wrenGrayBuffer(WrenVM* vm, ValueBuffer* buffer); // Processes every object in the gray stack until all reachable objects have // been marked. After that, all objects are either white (freeable) or black // (in use and fully traversed). void wrenBlackenObjects(WrenVM* vm); // Releases all memory owned by [obj], including [obj] itself. void wrenFreeObj(WrenVM* vm, Obj* obj); // Returns the class of [value]. // // Unlike wrenGetClassInline in wren_vm.h, this is not inlined. Inlining helps // performance (significantly) in some cases, but degrades it in others. The // ones used by the implementation were chosen to give the best results in the // benchmarks. ObjClass* wrenGetClass(WrenVM* vm, Value value); // Returns true if [a] and [b] are strictly the same value. This is identity // for object values, and value equality for unboxed values. static inline bool wrenValuesSame(Value a, Value b) { #if WREN_NAN_TAGGING // Value types have unique bit representations and we compare object types // by identity (i.e. pointer), so all we need to do is compare the bits. return a == b; #else if (a.type != b.type) return false; if (a.type == VAL_NUM) return a.as.num == b.as.num; return a.as.obj == b.as.obj; #endif } // Returns true if [a] and [b] are equivalent. Immutable values (null, bools, // numbers, ranges, and strings) are equal if they have the same data. All // other values are equal if they are identical objects. bool wrenValuesEqual(Value a, Value b); // Returns true if [value] is a bool. Do not call this directly, instead use // [IS_BOOL]. static inline bool wrenIsBool(Value value) { #if WREN_NAN_TAGGING return value == TRUE_VAL || value == FALSE_VAL; #else return value.type == VAL_FALSE || value.type == VAL_TRUE; #endif } // Returns true if [value] is an object of type [type]. Do not call this // directly, instead use the [IS___] macro for the type in question. static inline bool wrenIsObjType(Value value, ObjType type) { return IS_OBJ(value) && AS_OBJ(value)->type == type; } // Converts the raw object pointer [obj] to a [Value]. static inline Value wrenObjectToValue(Obj* obj) { #if WREN_NAN_TAGGING // The triple casting is necessary here to satisfy some compilers: // 1. (uintptr_t) Convert the pointer to a number of the right size. // 2. (uint64_t) Pad it up to 64 bits in 32-bit builds. // 3. Or in the bits to make a tagged Nan. // 4. Cast to a typedef'd value. return (Value)(SIGN_BIT | QNAN | (uint64_t)(uintptr_t)(obj)); #else Value value; value.type = VAL_OBJ; value.as.obj = obj; return value; #endif } // Interprets [value] as a [double]. static inline double wrenValueToNum(Value value) { #if WREN_NAN_TAGGING return wrenDoubleFromBits(value); #else return value.as.num; #endif } // Converts [num] to a [Value]. static inline Value wrenNumToValue(double num) { #if WREN_NAN_TAGGING return wrenDoubleToBits(num); #else Value value; value.type = VAL_NUM; value.as.num = num; return value; #endif } static inline bool wrenMapIsValidKey(Value arg) { return IS_BOOL(arg) || IS_CLASS(arg) || IS_NULL(arg) || IS_NUM(arg) || IS_RANGE(arg) || IS_STRING(arg); } #endif // End file "wren_value.h" // Begin file "wren_vm.h" #ifndef wren_vm_h #define wren_vm_h // Begin file "wren_compiler.h" #ifndef wren_compiler_h #define wren_compiler_h typedef struct sCompiler Compiler; // This module defines the compiler for Wren. It takes a string of source code // and lexes, parses, and compiles it. Wren uses a single-pass compiler. It // does not build an actual AST during parsing and then consume that to // generate code. Instead, the parser directly emits bytecode. // // This forces a few restrictions on the grammar and semantics of the language. // Things like forward references and arbitrary lookahead are much harder. We // get a lot in return for that, though. // // The implementation is much simpler since we don't need to define a bunch of // AST data structures. More so, we don't have to deal with managing memory for // AST objects. The compiler does almost no dynamic allocation while running. // // Compilation is also faster since we don't create a bunch of temporary data // structures and destroy them after generating code. // Compiles [source], a string of Wren source code located in [module], to an // [ObjFn] that will execute that code when invoked. Returns `NULL` if the // source contains any syntax errors. // // If [isExpression] is `true`, [source] should be a single expression, and // this compiles it to a function that evaluates and returns that expression. // Otherwise, [source] should be a series of top level statements. // // If [printErrors] is `true`, any compile errors are output to stderr. // Otherwise, they are silently discarded. ObjFn* wrenCompile(WrenVM* vm, ObjModule* module, const char* source, bool isExpression, bool printErrors); // When a class is defined, its superclass is not known until runtime since // class definitions are just imperative statements. Most of the bytecode for a // a method doesn't care, but there are two places where it matters: // // - To load or store a field, we need to know the index of the field in the // instance's field array. We need to adjust this so that subclass fields // are positioned after superclass fields, and we don't know this until the // superclass is known. // // - Superclass calls need to know which superclass to dispatch to. // // We could handle this dynamically, but that adds overhead. Instead, when a // method is bound, we walk the bytecode for the function and patch it up. void wrenBindMethodCode(ObjClass* classObj, ObjFn* fn); // Reaches all of the heap-allocated objects in use by [compiler] (and all of // its parents) so that they are not collected by the GC. void wrenMarkCompiler(WrenVM* vm, Compiler* compiler); #endif // End file "wren_compiler.h" // The maximum number of temporary objects that can be made visible to the GC // at one time. #define WREN_MAX_TEMP_ROOTS 8 typedef enum { #define OPCODE(name, _) CODE_##name, // Begin file "wren_opcodes.h" // This defines the bytecode instructions used by the VM. It does so by invoking // an OPCODE() macro which is expected to be defined at the point that this is // included. (See: http://en.wikipedia.org/wiki/X_Macro for more.) // // The first argument is the name of the opcode. The second is its "stack // effect" -- the amount that the op code changes the size of the stack. A // stack effect of 1 means it pushes a value and the stack grows one larger. // -2 means it pops two values, etc. // // Note that the order of instructions here affects the order of the dispatch // table in the VM's interpreter loop. That in turn affects caching which // affects overall performance. Take care to run benchmarks if you change the // order here. // Load the constant at index [arg]. OPCODE(CONSTANT, 1) // Push null onto the stack. OPCODE(NULL, 1) // Push false onto the stack. OPCODE(FALSE, 1) // Push true onto the stack. OPCODE(TRUE, 1) // Pushes the value in the given local slot. OPCODE(LOAD_LOCAL_0, 1) OPCODE(LOAD_LOCAL_1, 1) OPCODE(LOAD_LOCAL_2, 1) OPCODE(LOAD_LOCAL_3, 1) OPCODE(LOAD_LOCAL_4, 1) OPCODE(LOAD_LOCAL_5, 1) OPCODE(LOAD_LOCAL_6, 1) OPCODE(LOAD_LOCAL_7, 1) OPCODE(LOAD_LOCAL_8, 1) // Note: The compiler assumes the following _STORE instructions always // immediately follow their corresponding _LOAD ones. // Pushes the value in local slot [arg]. OPCODE(LOAD_LOCAL, 1) // Stores the top of stack in local slot [arg]. Does not pop it. OPCODE(STORE_LOCAL, 0) // Pushes the value in upvalue [arg]. OPCODE(LOAD_UPVALUE, 1) // Stores the top of stack in upvalue [arg]. Does not pop it. OPCODE(STORE_UPVALUE, 0) // Pushes the value of the top-level variable in slot [arg]. OPCODE(LOAD_MODULE_VAR, 1) // Stores the top of stack in top-level variable slot [arg]. Does not pop it. OPCODE(STORE_MODULE_VAR, 0) // Pushes the value of the field in slot [arg] of the receiver of the current // function. This is used for regular field accesses on "this" directly in // methods. This instruction is faster than the more general CODE_LOAD_FIELD // instruction. OPCODE(LOAD_FIELD_THIS, 1) // Stores the top of the stack in field slot [arg] in the receiver of the // current value. Does not pop the value. This instruction is faster than the // more general CODE_LOAD_FIELD instruction. OPCODE(STORE_FIELD_THIS, 0) // Pops an instance and pushes the value of the field in slot [arg] of it. OPCODE(LOAD_FIELD, 0) // Pops an instance and stores the subsequent top of stack in field slot // [arg] in it. Does not pop the value. OPCODE(STORE_FIELD, -1) // Pop and discard the top of stack. OPCODE(POP, -1) // Invoke the method with symbol [arg]. The number indicates the number of // arguments (not including the receiver). OPCODE(CALL_0, 0) OPCODE(CALL_1, -1) OPCODE(CALL_2, -2) OPCODE(CALL_3, -3) OPCODE(CALL_4, -4) OPCODE(CALL_5, -5) OPCODE(CALL_6, -6) OPCODE(CALL_7, -7) OPCODE(CALL_8, -8) OPCODE(CALL_9, -9) OPCODE(CALL_10, -10) OPCODE(CALL_11, -11) OPCODE(CALL_12, -12) OPCODE(CALL_13, -13) OPCODE(CALL_14, -14) OPCODE(CALL_15, -15) OPCODE(CALL_16, -16) // Invoke a superclass method with symbol [arg]. The number indicates the // number of arguments (not including the receiver). OPCODE(SUPER_0, 0) OPCODE(SUPER_1, -1) OPCODE(SUPER_2, -2) OPCODE(SUPER_3, -3) OPCODE(SUPER_4, -4) OPCODE(SUPER_5, -5) OPCODE(SUPER_6, -6) OPCODE(SUPER_7, -7) OPCODE(SUPER_8, -8) OPCODE(SUPER_9, -9) OPCODE(SUPER_10, -10) OPCODE(SUPER_11, -11) OPCODE(SUPER_12, -12) OPCODE(SUPER_13, -13) OPCODE(SUPER_14, -14) OPCODE(SUPER_15, -15) OPCODE(SUPER_16, -16) // Jump the instruction pointer [arg] forward. OPCODE(JUMP, 0) // Jump the instruction pointer [arg] backward. OPCODE(LOOP, 0) // Pop and if not truthy then jump the instruction pointer [arg] forward. OPCODE(JUMP_IF, -1) // If the top of the stack is false, jump [arg] forward. Otherwise, pop and // continue. OPCODE(AND, -1) // If the top of the stack is non-false, jump [arg] forward. Otherwise, pop // and continue. OPCODE(OR, -1) // Close the upvalue for the local on the top of the stack, then pop it. OPCODE(CLOSE_UPVALUE, -1) // Exit from the current function and return the value on the top of the // stack. OPCODE(RETURN, 0) // Creates a closure for the function stored at [arg] in the constant table. // // Following the function argument is a number of arguments, two for each // upvalue. The first is true if the variable being captured is a local (as // opposed to an upvalue), and the second is the index of the local or // upvalue being captured. // // Pushes the created closure. OPCODE(CLOSURE, 1) // Creates a new instance of a class. // // Assumes the class object is in slot zero, and replaces it with the new // uninitialized instance of that class. This opcode is only emitted by the // compiler-generated constructor metaclass methods. OPCODE(CONSTRUCT, 0) // Creates a new instance of a foreign class. // // Assumes the class object is in slot zero, and replaces it with the new // uninitialized instance of that class. This opcode is only emitted by the // compiler-generated constructor metaclass methods. OPCODE(FOREIGN_CONSTRUCT, 0) // Creates a class. Top of stack is the superclass. Below that is a string for // the name of the class. Byte [arg] is the number of fields in the class. OPCODE(CLASS, -1) // Ends a class. // Atm the stack contains the class and the ClassAttributes (or null). OPCODE(END_CLASS, -2) // Creates a foreign class. Top of stack is the superclass. Below that is a // string for the name of the class. OPCODE(FOREIGN_CLASS, -1) // Define a method for symbol [arg]. The class receiving the method is popped // off the stack, then the function defining the body is popped. // // If a foreign method is being defined, the "function" will be a string // identifying the foreign method. Otherwise, it will be a function or // closure. OPCODE(METHOD_INSTANCE, -2) // Define a method for symbol [arg]. The class whose metaclass will receive // the method is popped off the stack, then the function defining the body is // popped. // // If a foreign method is being defined, the "function" will be a string // identifying the foreign method. Otherwise, it will be a function or // closure. OPCODE(METHOD_STATIC, -2) // This is executed at the end of the module's body. Pushes NULL onto the stack // as the "return value" of the import statement and stores the module as the // most recently imported one. OPCODE(END_MODULE, 1) // Import a module whose name is the string stored at [arg] in the constant // table. // // Pushes null onto the stack so that the fiber for the imported module can // replace that with a dummy value when it returns. (Fibers always return a // value when resuming a caller.) OPCODE(IMPORT_MODULE, 1) // Import a variable from the most recently imported module. The name of the // variable to import is at [arg] in the constant table. Pushes the loaded // variable's value. OPCODE(IMPORT_VARIABLE, 1) // This pseudo-instruction indicates the end of the bytecode. It should // always be preceded by a `CODE_RETURN`, so is never actually executed. OPCODE(END, 0) // End file "wren_opcodes.h" #undef OPCODE } Code; // A handle to a value, basically just a linked list of extra GC roots. // // Note that even non-heap-allocated values can be stored here. struct WrenHandle { Value value; WrenHandle* prev; WrenHandle* next; }; struct WrenVM { ObjClass* boolClass; ObjClass* classClass; ObjClass* fiberClass; ObjClass* fnClass; ObjClass* listClass; ObjClass* mapClass; ObjClass* nullClass; ObjClass* numClass; ObjClass* objectClass; ObjClass* rangeClass; ObjClass* stringClass; // The fiber that is currently running. ObjFiber* fiber; // The loaded modules. Each key is an ObjString (except for the main module, // whose key is null) for the module's name and the value is the ObjModule // for the module. ObjMap* modules; // The most recently imported module. More specifically, the module whose // code has most recently finished executing. // // Not treated like a GC root since the module is already in [modules]. ObjModule* lastModule; // Memory management data: // The number of bytes that are known to be currently allocated. Includes all // memory that was proven live after the last GC, as well as any new bytes // that were allocated since then. Does *not* include bytes for objects that // were freed since the last GC. size_t bytesAllocated; // The number of total allocated bytes that will trigger the next GC. size_t nextGC; // The first object in the linked list of all currently allocated objects. Obj* first; // The "gray" set for the garbage collector. This is the stack of unprocessed // objects while a garbage collection pass is in process. Obj** gray; int grayCount; int grayCapacity; // The list of temporary roots. This is for temporary or new objects that are // not otherwise reachable but should not be collected. // // They are organized as a stack of pointers stored in this array. This // implies that temporary roots need to have stack semantics: only the most // recently pushed object can be released. Obj* tempRoots[WREN_MAX_TEMP_ROOTS]; int numTempRoots; // Pointer to the first node in the linked list of active handles or NULL if // there are none. WrenHandle* handles; // Pointer to the bottom of the range of stack slots available for use from // the C API. During a foreign method, this will be in the stack of the fiber // that is executing a method. // // If not in a foreign method, this is initially NULL. If the user requests // slots by calling wrenEnsureSlots(), a stack is created and this is // initialized. Value* apiStack; WrenConfiguration config; // Compiler and debugger data: // The compiler that is currently compiling code. This is used so that heap // allocated objects used by the compiler can be found if a GC is kicked off // in the middle of a compile. Compiler* compiler; // There is a single global symbol table for all method names on all classes. // Method calls are dispatched directly by index in this table. SymbolTable methodNames; }; // A generic allocation function that handles all explicit memory management. // It's used like so: // // - To allocate new memory, [memory] is NULL and [oldSize] is zero. It should // return the allocated memory or NULL on failure. // // - To attempt to grow an existing allocation, [memory] is the memory, // [oldSize] is its previous size, and [newSize] is the desired size. // It should return [memory] if it was able to grow it in place, or a new // pointer if it had to move it. // // - To shrink memory, [memory], [oldSize], and [newSize] are the same as above // but it will always return [memory]. // // - To free memory, [memory] will be the memory to free and [newSize] and // [oldSize] will be zero. It should return NULL. void* wrenReallocate(WrenVM* vm, void* memory, size_t oldSize, size_t newSize); // Invoke the finalizer for the foreign object referenced by [foreign]. void wrenFinalizeForeign(WrenVM* vm, ObjForeign* foreign); // Creates a new [WrenHandle] for [value]. WrenHandle* wrenMakeHandle(WrenVM* vm, Value value); // Compile [source] in the context of [module] and wrap in a fiber that can // execute it. // // Returns NULL if a compile error occurred. ObjClosure* wrenCompileSource(WrenVM* vm, const char* module, const char* source, bool isExpression, bool printErrors); // Looks up a variable from a previously-loaded module. // // Aborts the current fiber if the module or variable could not be found. Value wrenGetModuleVariable(WrenVM* vm, Value moduleName, Value variableName); // Returns the value of the module-level variable named [name] in the main // module. Value wrenFindVariable(WrenVM* vm, ObjModule* module, const char* name); // Adds a new implicitly declared top-level variable named [name] to [module] // based on a use site occurring on [line]. // // Does not check to see if a variable with that name is already declared or // defined. Returns the symbol for the new variable or -2 if there are too many // variables defined. int wrenDeclareVariable(WrenVM* vm, ObjModule* module, const char* name, size_t length, int line); // Adds a new top-level variable named [name] to [module], and optionally // populates line with the line of the implicit first use (line can be NULL). // // Returns the symbol for the new variable, -1 if a variable with the given name // is already defined, or -2 if there are too many variables defined. // Returns -3 if this is a top-level lowercase variable (localname) that was // used before being defined. int wrenDefineVariable(WrenVM* vm, ObjModule* module, const char* name, size_t length, Value value, int* line); // Pushes [closure] onto [fiber]'s callstack to invoke it. Expects [numArgs] // arguments (including the receiver) to be on the top of the stack already. static inline void wrenCallFunction(WrenVM* vm, ObjFiber* fiber, ObjClosure* closure, int numArgs) { // Grow the call frame array if needed. if (fiber->numFrames + 1 > fiber->frameCapacity) { int max = fiber->frameCapacity * 2; fiber->frames = (CallFrame*)wrenReallocate(vm, fiber->frames, sizeof(CallFrame) * fiber->frameCapacity, sizeof(CallFrame) * max); fiber->frameCapacity = max; } // Grow the stack if needed. int stackSize = (int)(fiber->stackTop - fiber->stack); int needed = stackSize + closure->fn->maxSlots; wrenEnsureStack(vm, fiber, needed); wrenAppendCallFrame(vm, fiber, closure, fiber->stackTop - numArgs); } // Marks [obj] as a GC root so that it doesn't get collected. void wrenPushRoot(WrenVM* vm, Obj* obj); // Removes the most recently pushed temporary root. void wrenPopRoot(WrenVM* vm); // Returns the class of [value]. // // Defined here instead of in wren_value.h because it's critical that this be // inlined. That means it must be defined in the header, but the wren_value.h // header doesn't have a full definitely of WrenVM yet. static inline ObjClass* wrenGetClassInline(WrenVM* vm, Value value) { if (IS_NUM(value)) return vm->numClass; if (IS_OBJ(value)) return AS_OBJ(value)->classObj; #if WREN_NAN_TAGGING switch (GET_TAG(value)) { case TAG_FALSE: return vm->boolClass; break; case TAG_NAN: return vm->numClass; break; case TAG_NULL: return vm->nullClass; break; case TAG_TRUE: return vm->boolClass; break; case TAG_UNDEFINED: UNREACHABLE(); } #else switch (value.type) { case VAL_FALSE: return vm->boolClass; case VAL_NULL: return vm->nullClass; case VAL_NUM: return vm->numClass; case VAL_TRUE: return vm->boolClass; case VAL_OBJ: return AS_OBJ(value)->classObj; case VAL_UNDEFINED: UNREACHABLE(); } #endif UNREACHABLE(); return NULL; } // Returns `true` if [name] is a local variable name (starts with a lowercase // letter). static inline bool wrenIsLocalName(const char* name) { return name[0] >= 'a' && name[0] <= 'z'; } static inline bool wrenIsFalsyValue(Value value) { return IS_FALSE(value) || IS_NULL(value); } #endif // End file "wren_vm.h" // Prints the stack trace for the current fiber. // // Used when a fiber throws a runtime error which is not caught. void wrenDebugPrintStackTrace(WrenVM* vm); // The "dump" functions are used for debugging Wren itself. Normal code paths // will not call them unless one of the various DEBUG_ flags is enabled. // Prints a representation of [value] to stdout. void wrenDumpValue(Value value); // Prints a representation of the bytecode for [fn] at instruction [i]. int wrenDumpInstruction(WrenVM* vm, ObjFn* fn, int i); // Prints the disassembled code for [fn] to stdout. void wrenDumpCode(WrenVM* vm, ObjFn* fn); // Prints the contents of the current stack for [fiber] to stdout. void wrenDumpStack(ObjFiber* fiber); #endif // End file "wren_debug.h" // Begin file "wren_debug.c" #include void wrenDebugPrintStackTrace(WrenVM* vm) { // Bail if the host doesn't enable printing errors. if (vm->config.errorFn == NULL) return; ObjFiber* fiber = vm->fiber; if (IS_STRING(fiber->error)) { vm->config.errorFn(vm, WREN_ERROR_RUNTIME, NULL, -1, AS_CSTRING(fiber->error)); } else { // TODO: Print something a little useful here. Maybe the name of the error's // class? vm->config.errorFn(vm, WREN_ERROR_RUNTIME, NULL, -1, "[error object]"); } for (int i = fiber->numFrames - 1; i >= 0; i--) { CallFrame* frame = &fiber->frames[i]; ObjFn* fn = frame->closure->fn; // Skip over stub functions for calling methods from the C API. if (fn->module == NULL) continue; // The built-in core module has no name. We explicitly omit it from stack // traces since we don't want to highlight to a user the implementation // detail of what part of the core module is written in C and what is Wren. if (fn->module->name == NULL) continue; // -1 because IP has advanced past the instruction that it just executed. int line = fn->debug->sourceLines.data[frame->ip - fn->code.data - 1]; vm->config.errorFn(vm, WREN_ERROR_STACK_TRACE, fn->module->name->value, line, fn->debug->name); } } static void dumpObject(Obj* obj) { switch (obj->type) { case OBJ_CLASS: printf("[class %s %p]", ((ObjClass*)obj)->name->value, obj); break; case OBJ_CLOSURE: printf("[closure %p]", obj); break; case OBJ_FIBER: printf("[fiber %p]", obj); break; case OBJ_FN: printf("[fn %p]", obj); break; case OBJ_FOREIGN: printf("[foreign %p]", obj); break; case OBJ_INSTANCE: printf("[instance %p]", obj); break; case OBJ_LIST: printf("[list %p]", obj); break; case OBJ_MAP: printf("[map %p]", obj); break; case OBJ_MODULE: printf("[module %p]", obj); break; case OBJ_RANGE: printf("[range %p]", obj); break; case OBJ_STRING: printf("%s", ((ObjString*)obj)->value); break; case OBJ_UPVALUE: printf("[upvalue %p]", obj); break; default: printf("[unknown object %d]", obj->type); break; } } void wrenDumpValue(Value value) { #if WREN_NAN_TAGGING if (IS_NUM(value)) { printf("%.14g", AS_NUM(value)); } else if (IS_OBJ(value)) { dumpObject(AS_OBJ(value)); } else { switch (GET_TAG(value)) { case TAG_FALSE: printf("false"); break; case TAG_NAN: printf("NaN"); break; case TAG_NULL: printf("null"); break; case TAG_TRUE: printf("true"); break; case TAG_UNDEFINED: UNREACHABLE(); } } #else switch (value.type) { case VAL_FALSE: printf("false"); break; case VAL_NULL: printf("null"); break; case VAL_NUM: printf("%.14g", AS_NUM(value)); break; case VAL_TRUE: printf("true"); break; case VAL_OBJ: dumpObject(AS_OBJ(value)); break; case VAL_UNDEFINED: UNREACHABLE(); } #endif } static int dumpInstruction(WrenVM* vm, ObjFn* fn, int i, int* lastLine) { int start = i; uint8_t* bytecode = fn->code.data; Code code = (Code)bytecode[i]; int line = fn->debug->sourceLines.data[i]; if (lastLine == NULL || *lastLine != line) { printf("%4d:", line); if (lastLine != NULL) *lastLine = line; } else { printf(" "); } printf(" %04d ", i++); #define READ_BYTE() (bytecode[i++]) #define READ_SHORT() (i += 2, (bytecode[i - 2] << 8) | bytecode[i - 1]) #define BYTE_INSTRUCTION(name) \ printf("%-16s %5d\n", name, READ_BYTE()); \ break switch (code) { case CODE_CONSTANT: { int constant = READ_SHORT(); printf("%-16s %5d '", "CONSTANT", constant); wrenDumpValue(fn->constants.data[constant]); printf("'\n"); break; } case CODE_NULL: printf("NULL\n"); break; case CODE_FALSE: printf("FALSE\n"); break; case CODE_TRUE: printf("TRUE\n"); break; case CODE_LOAD_LOCAL_0: printf("LOAD_LOCAL_0\n"); break; case CODE_LOAD_LOCAL_1: printf("LOAD_LOCAL_1\n"); break; case CODE_LOAD_LOCAL_2: printf("LOAD_LOCAL_2\n"); break; case CODE_LOAD_LOCAL_3: printf("LOAD_LOCAL_3\n"); break; case CODE_LOAD_LOCAL_4: printf("LOAD_LOCAL_4\n"); break; case CODE_LOAD_LOCAL_5: printf("LOAD_LOCAL_5\n"); break; case CODE_LOAD_LOCAL_6: printf("LOAD_LOCAL_6\n"); break; case CODE_LOAD_LOCAL_7: printf("LOAD_LOCAL_7\n"); break; case CODE_LOAD_LOCAL_8: printf("LOAD_LOCAL_8\n"); break; case CODE_LOAD_LOCAL: BYTE_INSTRUCTION("LOAD_LOCAL"); case CODE_STORE_LOCAL: BYTE_INSTRUCTION("STORE_LOCAL"); case CODE_LOAD_UPVALUE: BYTE_INSTRUCTION("LOAD_UPVALUE"); case CODE_STORE_UPVALUE: BYTE_INSTRUCTION("STORE_UPVALUE"); case CODE_LOAD_MODULE_VAR: { int slot = READ_SHORT(); printf("%-16s %5d '%s'\n", "LOAD_MODULE_VAR", slot, fn->module->variableNames.data[slot]->value); break; } case CODE_STORE_MODULE_VAR: { int slot = READ_SHORT(); printf("%-16s %5d '%s'\n", "STORE_MODULE_VAR", slot, fn->module->variableNames.data[slot]->value); break; } case CODE_LOAD_FIELD_THIS: BYTE_INSTRUCTION("LOAD_FIELD_THIS"); case CODE_STORE_FIELD_THIS: BYTE_INSTRUCTION("STORE_FIELD_THIS"); case CODE_LOAD_FIELD: BYTE_INSTRUCTION("LOAD_FIELD"); case CODE_STORE_FIELD: BYTE_INSTRUCTION("STORE_FIELD"); case CODE_POP: printf("POP\n"); break; case CODE_CALL_0: case CODE_CALL_1: case CODE_CALL_2: case CODE_CALL_3: case CODE_CALL_4: case CODE_CALL_5: case CODE_CALL_6: case CODE_CALL_7: case CODE_CALL_8: case CODE_CALL_9: case CODE_CALL_10: case CODE_CALL_11: case CODE_CALL_12: case CODE_CALL_13: case CODE_CALL_14: case CODE_CALL_15: case CODE_CALL_16: { int numArgs = bytecode[i - 1] - CODE_CALL_0; int symbol = READ_SHORT(); printf("CALL_%-11d %5d '%s'\n", numArgs, symbol, vm->methodNames.data[symbol]->value); break; } case CODE_SUPER_0: case CODE_SUPER_1: case CODE_SUPER_2: case CODE_SUPER_3: case CODE_SUPER_4: case CODE_SUPER_5: case CODE_SUPER_6: case CODE_SUPER_7: case CODE_SUPER_8: case CODE_SUPER_9: case CODE_SUPER_10: case CODE_SUPER_11: case CODE_SUPER_12: case CODE_SUPER_13: case CODE_SUPER_14: case CODE_SUPER_15: case CODE_SUPER_16: { int numArgs = bytecode[i - 1] - CODE_SUPER_0; int symbol = READ_SHORT(); int superclass = READ_SHORT(); printf("SUPER_%-10d %5d '%s' %5d\n", numArgs, symbol, vm->methodNames.data[symbol]->value, superclass); break; } case CODE_JUMP: { int offset = READ_SHORT(); printf("%-16s %5d to %d\n", "JUMP", offset, i + offset); break; } case CODE_LOOP: { int offset = READ_SHORT(); printf("%-16s %5d to %d\n", "LOOP", offset, i - offset); break; } case CODE_JUMP_IF: { int offset = READ_SHORT(); printf("%-16s %5d to %d\n", "JUMP_IF", offset, i + offset); break; } case CODE_AND: { int offset = READ_SHORT(); printf("%-16s %5d to %d\n", "AND", offset, i + offset); break; } case CODE_OR: { int offset = READ_SHORT(); printf("%-16s %5d to %d\n", "OR", offset, i + offset); break; } case CODE_CLOSE_UPVALUE: printf("CLOSE_UPVALUE\n"); break; case CODE_RETURN: printf("RETURN\n"); break; case CODE_CLOSURE: { int constant = READ_SHORT(); printf("%-16s %5d ", "CLOSURE", constant); wrenDumpValue(fn->constants.data[constant]); printf(" "); ObjFn* loadedFn = AS_FN(fn->constants.data[constant]); for (int j = 0; j < loadedFn->numUpvalues; j++) { int isLocal = READ_BYTE(); int index = READ_BYTE(); if (j > 0) printf(", "); printf("%s %d", isLocal ? "local" : "upvalue", index); } printf("\n"); break; } case CODE_CONSTRUCT: printf("CONSTRUCT\n"); break; case CODE_FOREIGN_CONSTRUCT: printf("FOREIGN_CONSTRUCT\n"); break; case CODE_CLASS: { int numFields = READ_BYTE(); printf("%-16s %5d fields\n", "CLASS", numFields); break; } case CODE_FOREIGN_CLASS: printf("FOREIGN_CLASS\n"); break; case CODE_END_CLASS: printf("END_CLASS\n"); break; case CODE_METHOD_INSTANCE: { int symbol = READ_SHORT(); printf("%-16s %5d '%s'\n", "METHOD_INSTANCE", symbol, vm->methodNames.data[symbol]->value); break; } case CODE_METHOD_STATIC: { int symbol = READ_SHORT(); printf("%-16s %5d '%s'\n", "METHOD_STATIC", symbol, vm->methodNames.data[symbol]->value); break; } case CODE_END_MODULE: printf("END_MODULE\n"); break; case CODE_IMPORT_MODULE: { int name = READ_SHORT(); printf("%-16s %5d '", "IMPORT_MODULE", name); wrenDumpValue(fn->constants.data[name]); printf("'\n"); break; } case CODE_IMPORT_VARIABLE: { int variable = READ_SHORT(); printf("%-16s %5d '", "IMPORT_VARIABLE", variable); wrenDumpValue(fn->constants.data[variable]); printf("'\n"); break; } case CODE_END: printf("END\n"); break; default: printf("UKNOWN! [%d]\n", bytecode[i - 1]); break; } // Return how many bytes this instruction takes, or -1 if it's an END. if (code == CODE_END) return -1; return i - start; #undef READ_BYTE #undef READ_SHORT } int wrenDumpInstruction(WrenVM* vm, ObjFn* fn, int i) { return dumpInstruction(vm, fn, i, NULL); } void wrenDumpCode(WrenVM* vm, ObjFn* fn) { printf("%s: %s\n", fn->module->name == NULL ? "" : fn->module->name->value, fn->debug->name); int i = 0; int lastLine = -1; for (;;) { int offset = dumpInstruction(vm, fn, i, &lastLine); if (offset == -1) break; i += offset; } printf("\n"); } void wrenDumpStack(ObjFiber* fiber) { printf("(fiber %p) ", fiber); for (Value* slot = fiber->stack; slot < fiber->stackTop; slot++) { wrenDumpValue(*slot); printf(" | "); } printf("\n"); } // End file "wren_debug.c" // Begin file "wren_compiler.c" #include #include #include #include #if WREN_DEBUG_DUMP_COMPILED_CODE #endif // This is written in bottom-up order, so the tokenization comes first, then // parsing/code generation. This minimizes the number of explicit forward // declarations needed. // The maximum number of local (i.e. not module level) variables that can be // declared in a single function, method, or chunk of top level code. This is // the maximum number of variables in scope at one time, and spans block scopes. // // Note that this limitation is also explicit in the bytecode. Since // `CODE_LOAD_LOCAL` and `CODE_STORE_LOCAL` use a single argument byte to // identify the local, only 256 can be in scope at one time. #define MAX_LOCALS 256 // The maximum number of upvalues (i.e. variables from enclosing functions) // that a function can close over. #define MAX_UPVALUES 256 // The maximum number of distinct constants that a function can contain. This // value is explicit in the bytecode since `CODE_CONSTANT` only takes a single // two-byte argument. #define MAX_CONSTANTS (1 << 16) // The maximum distance a CODE_JUMP or CODE_JUMP_IF instruction can move the // instruction pointer. #define MAX_JUMP (1 << 16) // The maximum depth that interpolation can nest. For example, this string has // three levels: // // "outside %(one + "%(two + "%(three)")")" #define MAX_INTERPOLATION_NESTING 8 // The buffer size used to format a compile error message, excluding the header // with the module name and error location. Using a hardcoded buffer for this // is kind of hairy, but fortunately we can control what the longest possible // message is and handle that. Ideally, we'd use `snprintf()`, but that's not // available in standard C++98. #define ERROR_MESSAGE_SIZE (80 + MAX_VARIABLE_NAME + 15) typedef enum { TOKEN_LEFT_PAREN, TOKEN_RIGHT_PAREN, TOKEN_LEFT_BRACKET, TOKEN_RIGHT_BRACKET, TOKEN_LEFT_BRACE, TOKEN_RIGHT_BRACE, TOKEN_COLON, TOKEN_DOT, TOKEN_DOTDOT, TOKEN_DOTDOTDOT, TOKEN_COMMA, TOKEN_STAR, TOKEN_SLASH, TOKEN_PERCENT, TOKEN_HASH, TOKEN_PLUS, TOKEN_MINUS, TOKEN_LTLT, TOKEN_GTGT, TOKEN_PIPE, TOKEN_PIPEPIPE, TOKEN_CARET, TOKEN_AMP, TOKEN_AMPAMP, TOKEN_BANG, TOKEN_TILDE, TOKEN_QUESTION, TOKEN_EQ, TOKEN_LT, TOKEN_GT, TOKEN_LTEQ, TOKEN_GTEQ, TOKEN_EQEQ, TOKEN_BANGEQ, TOKEN_BREAK, TOKEN_CONTINUE, TOKEN_CLASS, TOKEN_CONSTRUCT, TOKEN_ELSE, TOKEN_FALSE, TOKEN_FOR, TOKEN_FOREIGN, TOKEN_IF, TOKEN_IMPORT, TOKEN_AS, TOKEN_IN, TOKEN_IS, TOKEN_NULL, TOKEN_RETURN, TOKEN_STATIC, TOKEN_SUPER, TOKEN_THIS, TOKEN_TRUE, TOKEN_VAR, TOKEN_WHILE, TOKEN_FIELD, TOKEN_STATIC_FIELD, TOKEN_NAME, TOKEN_NUMBER, // A string literal without any interpolation, or the last section of a // string following the last interpolated expression. TOKEN_STRING, // A portion of a string literal preceding an interpolated expression. This // string: // // "a %(b) c %(d) e" // // is tokenized to: // // TOKEN_INTERPOLATION "a " // TOKEN_NAME b // TOKEN_INTERPOLATION " c " // TOKEN_NAME d // TOKEN_STRING " e" TOKEN_INTERPOLATION, TOKEN_LINE, TOKEN_ERROR, TOKEN_EOF } TokenType; typedef struct { TokenType type; // The beginning of the token, pointing directly into the source. const char* start; // The length of the token in characters. int length; // The 1-based line where the token appears. int line; // The parsed value if the token is a literal. Value value; } Token; typedef struct { WrenVM* vm; // The module being parsed. ObjModule* module; // The source code being parsed. const char* source; // The beginning of the currently-being-lexed token in [source]. const char* tokenStart; // The current character being lexed in [source]. const char* currentChar; // The 1-based line number of [currentChar]. int currentLine; // The upcoming token. Token next; // The most recently lexed token. Token current; // The most recently consumed/advanced token. Token previous; // Tracks the lexing state when tokenizing interpolated strings. // // Interpolated strings make the lexer not strictly regular: we don't know // whether a ")" should be treated as a RIGHT_PAREN token or as ending an // interpolated expression unless we know whether we are inside a string // interpolation and how many unmatched "(" there are. This is particularly // complex because interpolation can nest: // // " %( " %( inner ) " ) " // // This tracks that state. The parser maintains a stack of ints, one for each // level of current interpolation nesting. Each value is the number of // unmatched "(" that are waiting to be closed. int parens[MAX_INTERPOLATION_NESTING]; int numParens; // Whether compile errors should be printed to stderr or discarded. bool printErrors; // If a syntax or compile error has occurred. bool hasError; } Parser; typedef struct { // The name of the local variable. This points directly into the original // source code string. const char* name; // The length of the local variable's name. int length; // The depth in the scope chain that this variable was declared at. Zero is // the outermost scope--parameters for a method, or the first local block in // top level code. One is the scope within that, etc. int depth; // If this local variable is being used as an upvalue. bool isUpvalue; } Local; typedef struct { // True if this upvalue is capturing a local variable from the enclosing // function. False if it's capturing an upvalue. bool isLocal; // The index of the local or upvalue being captured in the enclosing function. int index; } CompilerUpvalue; // Bookkeeping information for the current loop being compiled. typedef struct sLoop { // Index of the instruction that the loop should jump back to. int start; // Index of the argument for the CODE_JUMP_IF instruction used to exit the // loop. Stored so we can patch it once we know where the loop ends. int exitJump; // Index of the first instruction of the body of the loop. int body; // Depth of the scope(s) that need to be exited if a break is hit inside the // loop. int scopeDepth; // The loop enclosing this one, or NULL if this is the outermost loop. struct sLoop* enclosing; } Loop; // The different signature syntaxes for different kinds of methods. typedef enum { // A name followed by a (possibly empty) parenthesized parameter list. Also // used for binary operators. SIG_METHOD, // Just a name. Also used for unary operators. SIG_GETTER, // A name followed by "=". SIG_SETTER, // A square bracketed parameter list. SIG_SUBSCRIPT, // A square bracketed parameter list followed by "=". SIG_SUBSCRIPT_SETTER, // A constructor initializer function. This has a distinct signature to // prevent it from being invoked directly outside of the constructor on the // metaclass. SIG_INITIALIZER } SignatureType; typedef struct { const char* name; int length; SignatureType type; int arity; } Signature; // Bookkeeping information for compiling a class definition. typedef struct { // The name of the class. ObjString* name; // Attributes for the class itself ObjMap* classAttributes; // Attributes for methods in this class ObjMap* methodAttributes; // Symbol table for the fields of the class. SymbolTable fields; // Symbols for the methods defined by the class. Used to detect duplicate // method definitions. IntBuffer methods; IntBuffer staticMethods; // True if the class being compiled is a foreign class. bool isForeign; // True if the current method being compiled is static. bool inStatic; // The signature of the method being compiled. Signature* signature; } ClassInfo; struct sCompiler { Parser* parser; // The compiler for the function enclosing this one, or NULL if it's the // top level. struct sCompiler* parent; // The currently in scope local variables. Local locals[MAX_LOCALS]; // The number of local variables currently in scope. int numLocals; // The upvalues that this function has captured from outer scopes. The count // of them is stored in [numUpvalues]. CompilerUpvalue upvalues[MAX_UPVALUES]; // The current level of block scope nesting, where zero is no nesting. A -1 // here means top-level code is being compiled and there is no block scope // in effect at all. Any variables declared will be module-level. int scopeDepth; // The current number of slots (locals and temporaries) in use. // // We use this and maxSlots to track the maximum number of additional slots // a function may need while executing. When the function is called, the // fiber will check to ensure its stack has enough room to cover that worst // case and grow the stack if needed. // // This value here doesn't include parameters to the function. Since those // are already pushed onto the stack by the caller and tracked there, we // don't need to double count them here. int numSlots; // The current innermost loop being compiled, or NULL if not in a loop. Loop* loop; // If this is a compiler for a method, keeps track of the class enclosing it. ClassInfo* enclosingClass; // The function being compiled. ObjFn* fn; // The constants for the function being compiled. ObjMap* constants; // Whether or not the compiler is for a constructor initializer bool isInitializer; // The number of attributes seen while parsing. // We track this separately as compile time attributes // are not stored, so we can't rely on attributes->count // to enforce an error message when attributes are used // anywhere other than methods or classes. int numAttributes; // Attributes for the next class or method. ObjMap* attributes; }; // Describes where a variable is declared. typedef enum { // A local variable in the current function. SCOPE_LOCAL, // A local variable declared in an enclosing function. SCOPE_UPVALUE, // A top-level module variable. SCOPE_MODULE } Scope; // A reference to a variable and the scope where it is defined. This contains // enough information to emit correct code to load or store the variable. typedef struct { // The stack slot, upvalue slot, or module symbol defining the variable. int index; // Where the variable is declared. Scope scope; } Variable; // Forward declarations static void disallowAttributes(Compiler* compiler); static void addToAttributeGroup(Compiler* compiler, Value group, Value key, Value value); static void emitClassAttributes(Compiler* compiler, ClassInfo* classInfo); static void copyAttributes(Compiler* compiler, ObjMap* into); static void copyMethodAttributes(Compiler* compiler, bool isForeign, bool isStatic, const char* fullSignature, int32_t length); // The stack effect of each opcode. The index in the array is the opcode, and // the value is the stack effect of that instruction. static const int stackEffects[] = { #define OPCODE(_, effect) effect, // Begin file "wren_opcodes.h" // This defines the bytecode instructions used by the VM. It does so by invoking // an OPCODE() macro which is expected to be defined at the point that this is // included. (See: http://en.wikipedia.org/wiki/X_Macro for more.) // // The first argument is the name of the opcode. The second is its "stack // effect" -- the amount that the op code changes the size of the stack. A // stack effect of 1 means it pushes a value and the stack grows one larger. // -2 means it pops two values, etc. // // Note that the order of instructions here affects the order of the dispatch // table in the VM's interpreter loop. That in turn affects caching which // affects overall performance. Take care to run benchmarks if you change the // order here. // Load the constant at index [arg]. OPCODE(CONSTANT, 1) // Push null onto the stack. OPCODE(NULL, 1) // Push false onto the stack. OPCODE(FALSE, 1) // Push true onto the stack. OPCODE(TRUE, 1) // Pushes the value in the given local slot. OPCODE(LOAD_LOCAL_0, 1) OPCODE(LOAD_LOCAL_1, 1) OPCODE(LOAD_LOCAL_2, 1) OPCODE(LOAD_LOCAL_3, 1) OPCODE(LOAD_LOCAL_4, 1) OPCODE(LOAD_LOCAL_5, 1) OPCODE(LOAD_LOCAL_6, 1) OPCODE(LOAD_LOCAL_7, 1) OPCODE(LOAD_LOCAL_8, 1) // Note: The compiler assumes the following _STORE instructions always // immediately follow their corresponding _LOAD ones. // Pushes the value in local slot [arg]. OPCODE(LOAD_LOCAL, 1) // Stores the top of stack in local slot [arg]. Does not pop it. OPCODE(STORE_LOCAL, 0) // Pushes the value in upvalue [arg]. OPCODE(LOAD_UPVALUE, 1) // Stores the top of stack in upvalue [arg]. Does not pop it. OPCODE(STORE_UPVALUE, 0) // Pushes the value of the top-level variable in slot [arg]. OPCODE(LOAD_MODULE_VAR, 1) // Stores the top of stack in top-level variable slot [arg]. Does not pop it. OPCODE(STORE_MODULE_VAR, 0) // Pushes the value of the field in slot [arg] of the receiver of the current // function. This is used for regular field accesses on "this" directly in // methods. This instruction is faster than the more general CODE_LOAD_FIELD // instruction. OPCODE(LOAD_FIELD_THIS, 1) // Stores the top of the stack in field slot [arg] in the receiver of the // current value. Does not pop the value. This instruction is faster than the // more general CODE_LOAD_FIELD instruction. OPCODE(STORE_FIELD_THIS, 0) // Pops an instance and pushes the value of the field in slot [arg] of it. OPCODE(LOAD_FIELD, 0) // Pops an instance and stores the subsequent top of stack in field slot // [arg] in it. Does not pop the value. OPCODE(STORE_FIELD, -1) // Pop and discard the top of stack. OPCODE(POP, -1) // Invoke the method with symbol [arg]. The number indicates the number of // arguments (not including the receiver). OPCODE(CALL_0, 0) OPCODE(CALL_1, -1) OPCODE(CALL_2, -2) OPCODE(CALL_3, -3) OPCODE(CALL_4, -4) OPCODE(CALL_5, -5) OPCODE(CALL_6, -6) OPCODE(CALL_7, -7) OPCODE(CALL_8, -8) OPCODE(CALL_9, -9) OPCODE(CALL_10, -10) OPCODE(CALL_11, -11) OPCODE(CALL_12, -12) OPCODE(CALL_13, -13) OPCODE(CALL_14, -14) OPCODE(CALL_15, -15) OPCODE(CALL_16, -16) // Invoke a superclass method with symbol [arg]. The number indicates the // number of arguments (not including the receiver). OPCODE(SUPER_0, 0) OPCODE(SUPER_1, -1) OPCODE(SUPER_2, -2) OPCODE(SUPER_3, -3) OPCODE(SUPER_4, -4) OPCODE(SUPER_5, -5) OPCODE(SUPER_6, -6) OPCODE(SUPER_7, -7) OPCODE(SUPER_8, -8) OPCODE(SUPER_9, -9) OPCODE(SUPER_10, -10) OPCODE(SUPER_11, -11) OPCODE(SUPER_12, -12) OPCODE(SUPER_13, -13) OPCODE(SUPER_14, -14) OPCODE(SUPER_15, -15) OPCODE(SUPER_16, -16) // Jump the instruction pointer [arg] forward. OPCODE(JUMP, 0) // Jump the instruction pointer [arg] backward. OPCODE(LOOP, 0) // Pop and if not truthy then jump the instruction pointer [arg] forward. OPCODE(JUMP_IF, -1) // If the top of the stack is false, jump [arg] forward. Otherwise, pop and // continue. OPCODE(AND, -1) // If the top of the stack is non-false, jump [arg] forward. Otherwise, pop // and continue. OPCODE(OR, -1) // Close the upvalue for the local on the top of the stack, then pop it. OPCODE(CLOSE_UPVALUE, -1) // Exit from the current function and return the value on the top of the // stack. OPCODE(RETURN, 0) // Creates a closure for the function stored at [arg] in the constant table. // // Following the function argument is a number of arguments, two for each // upvalue. The first is true if the variable being captured is a local (as // opposed to an upvalue), and the second is the index of the local or // upvalue being captured. // // Pushes the created closure. OPCODE(CLOSURE, 1) // Creates a new instance of a class. // // Assumes the class object is in slot zero, and replaces it with the new // uninitialized instance of that class. This opcode is only emitted by the // compiler-generated constructor metaclass methods. OPCODE(CONSTRUCT, 0) // Creates a new instance of a foreign class. // // Assumes the class object is in slot zero, and replaces it with the new // uninitialized instance of that class. This opcode is only emitted by the // compiler-generated constructor metaclass methods. OPCODE(FOREIGN_CONSTRUCT, 0) // Creates a class. Top of stack is the superclass. Below that is a string for // the name of the class. Byte [arg] is the number of fields in the class. OPCODE(CLASS, -1) // Ends a class. // Atm the stack contains the class and the ClassAttributes (or null). OPCODE(END_CLASS, -2) // Creates a foreign class. Top of stack is the superclass. Below that is a // string for the name of the class. OPCODE(FOREIGN_CLASS, -1) // Define a method for symbol [arg]. The class receiving the method is popped // off the stack, then the function defining the body is popped. // // If a foreign method is being defined, the "function" will be a string // identifying the foreign method. Otherwise, it will be a function or // closure. OPCODE(METHOD_INSTANCE, -2) // Define a method for symbol [arg]. The class whose metaclass will receive // the method is popped off the stack, then the function defining the body is // popped. // // If a foreign method is being defined, the "function" will be a string // identifying the foreign method. Otherwise, it will be a function or // closure. OPCODE(METHOD_STATIC, -2) // This is executed at the end of the module's body. Pushes NULL onto the stack // as the "return value" of the import statement and stores the module as the // most recently imported one. OPCODE(END_MODULE, 1) // Import a module whose name is the string stored at [arg] in the constant // table. // // Pushes null onto the stack so that the fiber for the imported module can // replace that with a dummy value when it returns. (Fibers always return a // value when resuming a caller.) OPCODE(IMPORT_MODULE, 1) // Import a variable from the most recently imported module. The name of the // variable to import is at [arg] in the constant table. Pushes the loaded // variable's value. OPCODE(IMPORT_VARIABLE, 1) // This pseudo-instruction indicates the end of the bytecode. It should // always be preceded by a `CODE_RETURN`, so is never actually executed. OPCODE(END, 0) // End file "wren_opcodes.h" #undef OPCODE }; static void printError(Parser* parser, int line, const char* label, const char* format, va_list args) { parser->hasError = true; if (!parser->printErrors) return; // Only report errors if there is a WrenErrorFn to handle them. if (parser->vm->config.errorFn == NULL) return; // Format the label and message. char message[ERROR_MESSAGE_SIZE]; int length = sprintf(message, "%s: ", label); length += vsprintf(message + length, format, args); ASSERT(length < ERROR_MESSAGE_SIZE, "Error should not exceed buffer."); ObjString* module = parser->module->name; const char* module_name = module ? module->value : ""; parser->vm->config.errorFn(parser->vm, WREN_ERROR_COMPILE, module_name, line, message); } // Outputs a lexical error. static void lexError(Parser* parser, const char* format, ...) { va_list args; va_start(args, format); printError(parser, parser->currentLine, "Error", format, args); va_end(args); } // Outputs a compile or syntax error. This also marks the compilation as having // an error, which ensures that the resulting code will be discarded and never // run. This means that after calling error(), it's fine to generate whatever // invalid bytecode you want since it won't be used. // // You'll note that most places that call error() continue to parse and compile // after that. That's so that we can try to find as many compilation errors in // one pass as possible instead of just bailing at the first one. static void error(Compiler* compiler, const char* format, ...) { Token* token = &compiler->parser->previous; // If the parse error was caused by an error token, the lexer has already // reported it. if (token->type == TOKEN_ERROR) return; va_list args; va_start(args, format); if (token->type == TOKEN_LINE) { printError(compiler->parser, token->line, "Error at newline", format, args); } else if (token->type == TOKEN_EOF) { printError(compiler->parser, token->line, "Error at end of file", format, args); } else { // Make sure we don't exceed the buffer with a very long token. char label[10 + MAX_VARIABLE_NAME + 4 + 1]; if (token->length <= MAX_VARIABLE_NAME) { sprintf(label, "Error at '%.*s'", token->length, token->start); } else { sprintf(label, "Error at '%.*s...'", MAX_VARIABLE_NAME, token->start); } printError(compiler->parser, token->line, label, format, args); } va_end(args); } // Adds [constant] to the constant pool and returns its index. static int addConstant(Compiler* compiler, Value constant) { if (compiler->parser->hasError) return -1; // See if we already have a constant for the value. If so, reuse it. if (compiler->constants != NULL) { Value existing = wrenMapGet(compiler->constants, constant); if (IS_NUM(existing)) return (int)AS_NUM(existing); } // It's a new constant. if (compiler->fn->constants.count < MAX_CONSTANTS) { if (IS_OBJ(constant)) wrenPushRoot(compiler->parser->vm, AS_OBJ(constant)); wrenValueBufferWrite(compiler->parser->vm, &compiler->fn->constants, constant); if (IS_OBJ(constant)) wrenPopRoot(compiler->parser->vm); if (compiler->constants == NULL) { compiler->constants = wrenNewMap(compiler->parser->vm); } wrenMapSet(compiler->parser->vm, compiler->constants, constant, NUM_VAL(compiler->fn->constants.count - 1)); } else { error(compiler, "A function may only contain %d unique constants.", MAX_CONSTANTS); } return compiler->fn->constants.count - 1; } // Initializes [compiler]. static void initCompiler(Compiler* compiler, Parser* parser, Compiler* parent, bool isMethod) { compiler->parser = parser; compiler->parent = parent; compiler->loop = NULL; compiler->enclosingClass = NULL; compiler->isInitializer = false; // Initialize these to NULL before allocating in case a GC gets triggered in // the middle of initializing the compiler. compiler->fn = NULL; compiler->constants = NULL; compiler->attributes = NULL; parser->vm->compiler = compiler; // Declare a local slot for either the closure or method receiver so that we // don't try to reuse that slot for a user-defined local variable. For // methods, we name it "this", so that we can resolve references to that like // a normal variable. For functions, they have no explicit "this", so we use // an empty name. That way references to "this" inside a function walks up // the parent chain to find a method enclosing the function whose "this" we // can close over. compiler->numLocals = 1; compiler->numSlots = compiler->numLocals; if (isMethod) { compiler->locals[0].name = "this"; compiler->locals[0].length = 4; } else { compiler->locals[0].name = NULL; compiler->locals[0].length = 0; } compiler->locals[0].depth = -1; compiler->locals[0].isUpvalue = false; if (parent == NULL) { // Compiling top-level code, so the initial scope is module-level. compiler->scopeDepth = -1; } else { // The initial scope for functions and methods is local scope. compiler->scopeDepth = 0; } compiler->numAttributes = 0; compiler->attributes = wrenNewMap(parser->vm); compiler->fn = wrenNewFunction(parser->vm, parser->module, compiler->numLocals); } // Lexing ---------------------------------------------------------------------- typedef struct { const char* identifier; size_t length; TokenType tokenType; } Keyword; // The table of reserved words and their associated token types. static Keyword keywords[] = { {"break", 5, TOKEN_BREAK}, {"continue", 8, TOKEN_CONTINUE}, {"class", 5, TOKEN_CLASS}, {"construct", 9, TOKEN_CONSTRUCT}, {"else", 4, TOKEN_ELSE}, {"false", 5, TOKEN_FALSE}, {"for", 3, TOKEN_FOR}, {"foreign", 7, TOKEN_FOREIGN}, {"if", 2, TOKEN_IF}, {"import", 6, TOKEN_IMPORT}, {"as", 2, TOKEN_AS}, {"in", 2, TOKEN_IN}, {"is", 2, TOKEN_IS}, {"null", 4, TOKEN_NULL}, {"return", 6, TOKEN_RETURN}, {"static", 6, TOKEN_STATIC}, {"super", 5, TOKEN_SUPER}, {"this", 4, TOKEN_THIS}, {"true", 4, TOKEN_TRUE}, {"var", 3, TOKEN_VAR}, {"while", 5, TOKEN_WHILE}, {NULL, 0, TOKEN_EOF} // Sentinel to mark the end of the array. }; // Returns true if [c] is a valid (non-initial) identifier character. static bool isName(char c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'; } // Returns true if [c] is a digit. static bool isDigit(char c) { return c >= '0' && c <= '9'; } // Returns the current character the parser is sitting on. static char peekChar(Parser* parser) { return *parser->currentChar; } // Returns the character after the current character. static char peekNextChar(Parser* parser) { // If we're at the end of the source, don't read past it. if (peekChar(parser) == '\0') return '\0'; return *(parser->currentChar + 1); } // Advances the parser forward one character. static char nextChar(Parser* parser) { char c = peekChar(parser); parser->currentChar++; if (c == '\n') parser->currentLine++; return c; } // If the current character is [c], consumes it and returns `true`. static bool matchChar(Parser* parser, char c) { if (peekChar(parser) != c) return false; nextChar(parser); return true; } // Sets the parser's current token to the given [type] and current character // range. static void makeToken(Parser* parser, TokenType type) { parser->next.type = type; parser->next.start = parser->tokenStart; parser->next.length = (int)(parser->currentChar - parser->tokenStart); parser->next.line = parser->currentLine; // Make line tokens appear on the line containing the "\n". if (type == TOKEN_LINE) parser->next.line--; } // If the current character is [c], then consumes it and makes a token of type // [two]. Otherwise makes a token of type [one]. static void twoCharToken(Parser* parser, char c, TokenType two, TokenType one) { makeToken(parser, matchChar(parser, c) ? two : one); } // Skips the rest of the current line. static void skipLineComment(Parser* parser) { while (peekChar(parser) != '\n' && peekChar(parser) != '\0') { nextChar(parser); } } // Skips the rest of a block comment. static void skipBlockComment(Parser* parser) { int nesting = 1; while (nesting > 0) { if (peekChar(parser) == '\0') { lexError(parser, "Unterminated block comment."); return; } if (peekChar(parser) == '/' && peekNextChar(parser) == '*') { nextChar(parser); nextChar(parser); nesting++; continue; } if (peekChar(parser) == '*' && peekNextChar(parser) == '/') { nextChar(parser); nextChar(parser); nesting--; continue; } // Regular comment character. nextChar(parser); } } // Reads the next character, which should be a hex digit (0-9, a-f, or A-F) and // returns its numeric value. If the character isn't a hex digit, returns -1. static int readHexDigit(Parser* parser) { char c = nextChar(parser); if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'f') return c - 'a' + 10; if (c >= 'A' && c <= 'F') return c - 'A' + 10; // Don't consume it if it isn't expected. Keeps us from reading past the end // of an unterminated string. parser->currentChar--; return -1; } // Parses the numeric value of the current token. static void makeNumber(Parser* parser, bool isHex) { errno = 0; if (isHex) { parser->next.value = NUM_VAL((double)strtoll(parser->tokenStart, NULL, 16)); } else { parser->next.value = NUM_VAL(strtod(parser->tokenStart, NULL)); } if (errno == ERANGE) { lexError(parser, "Number literal was too large (%d).", sizeof(long int)); parser->next.value = NUM_VAL(0); } // We don't check that the entire token is consumed after calling strtoll() // or strtod() because we've already scanned it ourselves and know it's valid. makeToken(parser, TOKEN_NUMBER); } // Finishes lexing a hexadecimal number literal. static void readHexNumber(Parser* parser) { // Skip past the `x` used to denote a hexadecimal literal. nextChar(parser); // Iterate over all the valid hexadecimal digits found. while (readHexDigit(parser) != -1) continue; makeNumber(parser, true); } // Finishes lexing a number literal. static void readNumber(Parser* parser) { while (isDigit(peekChar(parser))) nextChar(parser); // See if it has a floating point. Make sure there is a digit after the "." // so we don't get confused by method calls on number literals. if (peekChar(parser) == '.' && isDigit(peekNextChar(parser))) { nextChar(parser); while (isDigit(peekChar(parser))) nextChar(parser); } // See if the number is in scientific notation. if (matchChar(parser, 'e') || matchChar(parser, 'E')) { // Allow a single positive/negative exponent symbol. if(!matchChar(parser, '+')) { matchChar(parser, '-'); } if (!isDigit(peekChar(parser))) { lexError(parser, "Unterminated scientific notation."); } while (isDigit(peekChar(parser))) nextChar(parser); } makeNumber(parser, false); } // Finishes lexing an identifier. Handles reserved words. static void readName(Parser* parser, TokenType type, char firstChar) { ByteBuffer string; wrenByteBufferInit(&string); wrenByteBufferWrite(parser->vm, &string, firstChar); while (isName(peekChar(parser)) || isDigit(peekChar(parser))) { char c = nextChar(parser); wrenByteBufferWrite(parser->vm, &string, c); } // Update the type if it's a keyword. size_t length = parser->currentChar - parser->tokenStart; for (int i = 0; keywords[i].identifier != NULL; i++) { if (length == keywords[i].length && memcmp(parser->tokenStart, keywords[i].identifier, length) == 0) { type = keywords[i].tokenType; break; } } parser->next.value = wrenNewStringLength(parser->vm, (char*)string.data, string.count); wrenByteBufferClear(parser->vm, &string); makeToken(parser, type); } // Reads [digits] hex digits in a string literal and returns their number value. static int readHexEscape(Parser* parser, int digits, const char* description) { int value = 0; for (int i = 0; i < digits; i++) { if (peekChar(parser) == '"' || peekChar(parser) == '\0') { lexError(parser, "Incomplete %s escape sequence.", description); // Don't consume it if it isn't expected. Keeps us from reading past the // end of an unterminated string. parser->currentChar--; break; } int digit = readHexDigit(parser); if (digit == -1) { lexError(parser, "Invalid %s escape sequence.", description); break; } value = (value * 16) | digit; } return value; } // Reads a hex digit Unicode escape sequence in a string literal. static void readUnicodeEscape(Parser* parser, ByteBuffer* string, int length) { int value = readHexEscape(parser, length, "Unicode"); // Grow the buffer enough for the encoded result. int numBytes = wrenUtf8EncodeNumBytes(value); if (numBytes != 0) { wrenByteBufferFill(parser->vm, string, 0, numBytes); wrenUtf8Encode(value, string->data + string->count - numBytes); } } static void readRawString(Parser* parser) { ByteBuffer string; wrenByteBufferInit(&string); TokenType type = TOKEN_STRING; //consume the second and third " nextChar(parser); nextChar(parser); int skipStart = 0; int firstNewline = -1; int skipEnd = -1; int lastNewline = -1; for (;;) { char c = nextChar(parser); char c1 = peekChar(parser); char c2 = peekNextChar(parser); if (c == '\r') continue; if (c == '\n') { lastNewline = string.count; skipEnd = lastNewline; firstNewline = firstNewline == -1 ? string.count : firstNewline; } if (c == '"' && c1 == '"' && c2 == '"') break; bool isWhitespace = c == ' ' || c == '\t'; skipEnd = c == '\n' || isWhitespace ? skipEnd : -1; // If we haven't seen a newline or other character yet, // and still seeing whitespace, count the characters // as skippable till we know otherwise bool skippable = skipStart != -1 && isWhitespace && firstNewline == -1; skipStart = skippable ? string.count + 1 : skipStart; // We've counted leading whitespace till we hit something else, // but it's not a newline, so we reset skipStart since we need these characters if (firstNewline == -1 && !isWhitespace && c != '\n') skipStart = -1; if (c == '\0' || c1 == '\0' || c2 == '\0') { lexError(parser, "Unterminated raw string."); // Don't consume it if it isn't expected. Keeps us from reading past the // end of an unterminated string. parser->currentChar--; break; } wrenByteBufferWrite(parser->vm, &string, c); } //consume the second and third " nextChar(parser); nextChar(parser); int offset = 0; int count = string.count; if(firstNewline != -1 && skipStart == firstNewline) offset = firstNewline + 1; if(lastNewline != -1 && skipEnd == lastNewline) count = lastNewline; count -= (offset > count) ? count : offset; parser->next.value = wrenNewStringLength(parser->vm, ((char*)string.data) + offset, count); wrenByteBufferClear(parser->vm, &string); makeToken(parser, type); } // Finishes lexing a string literal. static void readString(Parser* parser) { ByteBuffer string; TokenType type = TOKEN_STRING; wrenByteBufferInit(&string); for (;;) { char c = nextChar(parser); if (c == '"') break; if (c == '\r') continue; if (c == '\0') { lexError(parser, "Unterminated string."); // Don't consume it if it isn't expected. Keeps us from reading past the // end of an unterminated string. parser->currentChar--; break; } if (c == '%') { if (parser->numParens < MAX_INTERPOLATION_NESTING) { // TODO: Allow format string. if (nextChar(parser) != '(') lexError(parser, "Expect '(' after '%%'."); parser->parens[parser->numParens++] = 1; type = TOKEN_INTERPOLATION; break; } lexError(parser, "Interpolation may only nest %d levels deep.", MAX_INTERPOLATION_NESTING); } if (c == '\\') { switch (nextChar(parser)) { case '"': wrenByteBufferWrite(parser->vm, &string, '"'); break; case '\\': wrenByteBufferWrite(parser->vm, &string, '\\'); break; case '%': wrenByteBufferWrite(parser->vm, &string, '%'); break; case '0': wrenByteBufferWrite(parser->vm, &string, '\0'); break; case 'a': wrenByteBufferWrite(parser->vm, &string, '\a'); break; case 'b': wrenByteBufferWrite(parser->vm, &string, '\b'); break; case 'e': wrenByteBufferWrite(parser->vm, &string, '\33'); break; case 'f': wrenByteBufferWrite(parser->vm, &string, '\f'); break; case 'n': wrenByteBufferWrite(parser->vm, &string, '\n'); break; case 'r': wrenByteBufferWrite(parser->vm, &string, '\r'); break; case 't': wrenByteBufferWrite(parser->vm, &string, '\t'); break; case 'u': readUnicodeEscape(parser, &string, 4); break; case 'U': readUnicodeEscape(parser, &string, 8); break; case 'v': wrenByteBufferWrite(parser->vm, &string, '\v'); break; case 'x': wrenByteBufferWrite(parser->vm, &string, (uint8_t)readHexEscape(parser, 2, "byte")); break; default: lexError(parser, "Invalid escape character '%c'.", *(parser->currentChar - 1)); break; } } else { wrenByteBufferWrite(parser->vm, &string, c); } } parser->next.value = wrenNewStringLength(parser->vm, (char*)string.data, string.count); wrenByteBufferClear(parser->vm, &string); makeToken(parser, type); } // Lex the next token and store it in [parser.next]. static void nextToken(Parser* parser) { parser->previous = parser->current; parser->current = parser->next; // If we are out of tokens, don't try to tokenize any more. We *do* still // copy the TOKEN_EOF to previous so that code that expects it to be consumed // will still work. if (parser->next.type == TOKEN_EOF) return; if (parser->current.type == TOKEN_EOF) return; while (peekChar(parser) != '\0') { parser->tokenStart = parser->currentChar; char c = nextChar(parser); switch (c) { case '(': // If we are inside an interpolated expression, count the unmatched "(". if (parser->numParens > 0) parser->parens[parser->numParens - 1]++; makeToken(parser, TOKEN_LEFT_PAREN); return; case ')': // If we are inside an interpolated expression, count the ")". if (parser->numParens > 0 && --parser->parens[parser->numParens - 1] == 0) { // This is the final ")", so the interpolation expression has ended. // This ")" now begins the next section of the template string. parser->numParens--; readString(parser); return; } makeToken(parser, TOKEN_RIGHT_PAREN); return; case '[': makeToken(parser, TOKEN_LEFT_BRACKET); return; case ']': makeToken(parser, TOKEN_RIGHT_BRACKET); return; case '{': makeToken(parser, TOKEN_LEFT_BRACE); return; case '}': makeToken(parser, TOKEN_RIGHT_BRACE); return; case ':': makeToken(parser, TOKEN_COLON); return; case ',': makeToken(parser, TOKEN_COMMA); return; case '*': makeToken(parser, TOKEN_STAR); return; case '%': makeToken(parser, TOKEN_PERCENT); return; case '#': { // Ignore shebang on the first line. if (parser->currentLine == 1 && peekChar(parser) == '!' && peekNextChar(parser) == '/') { skipLineComment(parser); break; } // Otherwise we treat it as a token makeToken(parser, TOKEN_HASH); return; } case '^': makeToken(parser, TOKEN_CARET); return; case '+': makeToken(parser, TOKEN_PLUS); return; case '-': makeToken(parser, TOKEN_MINUS); return; case '~': makeToken(parser, TOKEN_TILDE); return; case '?': makeToken(parser, TOKEN_QUESTION); return; case '|': twoCharToken(parser, '|', TOKEN_PIPEPIPE, TOKEN_PIPE); return; case '&': twoCharToken(parser, '&', TOKEN_AMPAMP, TOKEN_AMP); return; case '=': twoCharToken(parser, '=', TOKEN_EQEQ, TOKEN_EQ); return; case '!': twoCharToken(parser, '=', TOKEN_BANGEQ, TOKEN_BANG); return; case '.': if (matchChar(parser, '.')) { twoCharToken(parser, '.', TOKEN_DOTDOTDOT, TOKEN_DOTDOT); return; } makeToken(parser, TOKEN_DOT); return; case '/': if (matchChar(parser, '/')) { skipLineComment(parser); break; } if (matchChar(parser, '*')) { skipBlockComment(parser); break; } makeToken(parser, TOKEN_SLASH); return; case '<': if (matchChar(parser, '<')) { makeToken(parser, TOKEN_LTLT); } else { twoCharToken(parser, '=', TOKEN_LTEQ, TOKEN_LT); } return; case '>': if (matchChar(parser, '>')) { makeToken(parser, TOKEN_GTGT); } else { twoCharToken(parser, '=', TOKEN_GTEQ, TOKEN_GT); } return; case '\n': makeToken(parser, TOKEN_LINE); return; case ' ': case '\r': case '\t': // Skip forward until we run out of whitespace. while (peekChar(parser) == ' ' || peekChar(parser) == '\r' || peekChar(parser) == '\t') { nextChar(parser); } break; case '"': { if(peekChar(parser) == '"' && peekNextChar(parser) == '"') { readRawString(parser); return; } readString(parser); return; } case '_': readName(parser, peekChar(parser) == '_' ? TOKEN_STATIC_FIELD : TOKEN_FIELD, c); return; case '0': if (peekChar(parser) == 'x') { readHexNumber(parser); return; } readNumber(parser); return; default: if (isName(c)) { readName(parser, TOKEN_NAME, c); } else if (isDigit(c)) { readNumber(parser); } else { if (c >= 32 && c <= 126) { lexError(parser, "Invalid character '%c'.", c); } else { // Don't show non-ASCII values since we didn't UTF-8 decode the // bytes. Since there are no non-ASCII byte values that are // meaningful code units in Wren, the lexer works on raw bytes, // even though the source code and console output are UTF-8. lexError(parser, "Invalid byte 0x%x.", (uint8_t)c); } parser->next.type = TOKEN_ERROR; parser->next.length = 0; } return; } } // If we get here, we're out of source, so just make EOF tokens. parser->tokenStart = parser->currentChar; makeToken(parser, TOKEN_EOF); } // Parsing --------------------------------------------------------------------- // Returns the type of the current token. static TokenType peek(Compiler* compiler) { return compiler->parser->current.type; } // Returns the type of the current token. static TokenType peekNext(Compiler* compiler) { return compiler->parser->next.type; } // Consumes the current token if its type is [expected]. Returns true if a // token was consumed. static bool match(Compiler* compiler, TokenType expected) { if (peek(compiler) != expected) return false; nextToken(compiler->parser); return true; } // Consumes the current token. Emits an error if its type is not [expected]. static void consume(Compiler* compiler, TokenType expected, const char* errorMessage) { nextToken(compiler->parser); if (compiler->parser->previous.type != expected) { error(compiler, errorMessage); // If the next token is the one we want, assume the current one is just a // spurious error and discard it to minimize the number of cascaded errors. if (compiler->parser->current.type == expected) nextToken(compiler->parser); } } // Matches one or more newlines. Returns true if at least one was found. static bool matchLine(Compiler* compiler) { if (!match(compiler, TOKEN_LINE)) return false; while (match(compiler, TOKEN_LINE)); return true; } // Discards any newlines starting at the current token. static void ignoreNewlines(Compiler* compiler) { matchLine(compiler); } // Consumes the current token. Emits an error if it is not a newline. Then // discards any duplicate newlines following it. static void consumeLine(Compiler* compiler, const char* errorMessage) { consume(compiler, TOKEN_LINE, errorMessage); ignoreNewlines(compiler); } static void allowLineBeforeDot(Compiler* compiler) { if (peek(compiler) == TOKEN_LINE && peekNext(compiler) == TOKEN_DOT) { nextToken(compiler->parser); } } // Variables and scopes -------------------------------------------------------- // Emits one single-byte argument. Returns its index. static int emitByte(Compiler* compiler, int byte) { wrenByteBufferWrite(compiler->parser->vm, &compiler->fn->code, (uint8_t)byte); // Assume the instruction is associated with the most recently consumed token. wrenIntBufferWrite(compiler->parser->vm, &compiler->fn->debug->sourceLines, compiler->parser->previous.line); return compiler->fn->code.count - 1; } // Emits one bytecode instruction. static void emitOp(Compiler* compiler, Code instruction) { emitByte(compiler, instruction); // Keep track of the stack's high water mark. compiler->numSlots += stackEffects[instruction]; if (compiler->numSlots > compiler->fn->maxSlots) { compiler->fn->maxSlots = compiler->numSlots; } } // Emits one 16-bit argument, which will be written big endian. static void emitShort(Compiler* compiler, int arg) { emitByte(compiler, (arg >> 8) & 0xff); emitByte(compiler, arg & 0xff); } // Emits one bytecode instruction followed by a 8-bit argument. Returns the // index of the argument in the bytecode. static int emitByteArg(Compiler* compiler, Code instruction, int arg) { emitOp(compiler, instruction); return emitByte(compiler, arg); } // Emits one bytecode instruction followed by a 16-bit argument, which will be // written big endian. static void emitShortArg(Compiler* compiler, Code instruction, int arg) { emitOp(compiler, instruction); emitShort(compiler, arg); } // Emits [instruction] followed by a placeholder for a jump offset. The // placeholder can be patched by calling [jumpPatch]. Returns the index of the // placeholder. static int emitJump(Compiler* compiler, Code instruction) { emitOp(compiler, instruction); emitByte(compiler, 0xff); return emitByte(compiler, 0xff) - 1; } // Creates a new constant for the current value and emits the bytecode to load // it from the constant table. static void emitConstant(Compiler* compiler, Value value) { int constant = addConstant(compiler, value); // Compile the code to load the constant. emitShortArg(compiler, CODE_CONSTANT, constant); } // Create a new local variable with [name]. Assumes the current scope is local // and the name is unique. static int addLocal(Compiler* compiler, const char* name, int length) { Local* local = &compiler->locals[compiler->numLocals]; local->name = name; local->length = length; local->depth = compiler->scopeDepth; local->isUpvalue = false; return compiler->numLocals++; } // Declares a variable in the current scope whose name is the given token. // // If [token] is `NULL`, uses the previously consumed token. Returns its symbol. static int declareVariable(Compiler* compiler, Token* token) { if (token == NULL) token = &compiler->parser->previous; if (token->length > MAX_VARIABLE_NAME) { error(compiler, "Variable name cannot be longer than %d characters.", MAX_VARIABLE_NAME); } // Top-level module scope. if (compiler->scopeDepth == -1) { int line = -1; int symbol = wrenDefineVariable(compiler->parser->vm, compiler->parser->module, token->start, token->length, NULL_VAL, &line); if (symbol == -1) { error(compiler, "Module variable is already defined."); } else if (symbol == -2) { error(compiler, "Too many module variables defined."); } else if (symbol == -3) { error(compiler, "Variable '%.*s' referenced before this definition (first use at line %d).", token->length, token->start, line); } return symbol; } // See if there is already a variable with this name declared in the current // scope. (Outer scopes are OK: those get shadowed.) for (int i = compiler->numLocals - 1; i >= 0; i--) { Local* local = &compiler->locals[i]; // Once we escape this scope and hit an outer one, we can stop. if (local->depth < compiler->scopeDepth) break; if (local->length == token->length && memcmp(local->name, token->start, token->length) == 0) { error(compiler, "Variable is already declared in this scope."); return i; } } if (compiler->numLocals == MAX_LOCALS) { error(compiler, "Cannot declare more than %d variables in one scope.", MAX_LOCALS); return -1; } return addLocal(compiler, token->start, token->length); } // Parses a name token and declares a variable in the current scope with that // name. Returns its slot. static int declareNamedVariable(Compiler* compiler) { consume(compiler, TOKEN_NAME, "Expect variable name."); return declareVariable(compiler, NULL); } // Stores a variable with the previously defined symbol in the current scope. static void defineVariable(Compiler* compiler, int symbol) { // Store the variable. If it's a local, the result of the initializer is // in the correct slot on the stack already so we're done. if (compiler->scopeDepth >= 0) return; // It's a module-level variable, so store the value in the module slot and // then discard the temporary for the initializer. emitShortArg(compiler, CODE_STORE_MODULE_VAR, symbol); emitOp(compiler, CODE_POP); } // Starts a new local block scope. static void pushScope(Compiler* compiler) { compiler->scopeDepth++; } // Generates code to discard local variables at [depth] or greater. Does *not* // actually undeclare variables or pop any scopes, though. This is called // directly when compiling "break" statements to ditch the local variables // before jumping out of the loop even though they are still in scope *past* // the break instruction. // // Returns the number of local variables that were eliminated. static int discardLocals(Compiler* compiler, int depth) { ASSERT(compiler->scopeDepth > -1, "Cannot exit top-level scope."); int local = compiler->numLocals - 1; while (local >= 0 && compiler->locals[local].depth >= depth) { // If the local was closed over, make sure the upvalue gets closed when it // goes out of scope on the stack. We use emitByte() and not emitOp() here // because we don't want to track that stack effect of these pops since the // variables are still in scope after the break. if (compiler->locals[local].isUpvalue) { emitByte(compiler, CODE_CLOSE_UPVALUE); } else { emitByte(compiler, CODE_POP); } local--; } return compiler->numLocals - local - 1; } // Closes the last pushed block scope and discards any local variables declared // in that scope. This should only be called in a statement context where no // temporaries are still on the stack. static void popScope(Compiler* compiler) { int popped = discardLocals(compiler, compiler->scopeDepth); compiler->numLocals -= popped; compiler->numSlots -= popped; compiler->scopeDepth--; } // Attempts to look up the name in the local variables of [compiler]. If found, // returns its index, otherwise returns -1. static int resolveLocal(Compiler* compiler, const char* name, int length) { // Look it up in the local scopes. Look in reverse order so that the most // nested variable is found first and shadows outer ones. for (int i = compiler->numLocals - 1; i >= 0; i--) { if (compiler->locals[i].length == length && memcmp(name, compiler->locals[i].name, length) == 0) { return i; } } return -1; } // Adds an upvalue to [compiler]'s function with the given properties. Does not // add one if an upvalue for that variable is already in the list. Returns the // index of the upvalue. static int addUpvalue(Compiler* compiler, bool isLocal, int index) { // Look for an existing one. for (int i = 0; i < compiler->fn->numUpvalues; i++) { CompilerUpvalue* upvalue = &compiler->upvalues[i]; if (upvalue->index == index && upvalue->isLocal == isLocal) return i; } // If we got here, it's a new upvalue. compiler->upvalues[compiler->fn->numUpvalues].isLocal = isLocal; compiler->upvalues[compiler->fn->numUpvalues].index = index; return compiler->fn->numUpvalues++; } // Attempts to look up [name] in the functions enclosing the one being compiled // by [compiler]. If found, it adds an upvalue for it to this compiler's list // of upvalues (unless it's already in there) and returns its index. If not // found, returns -1. // // If the name is found outside of the immediately enclosing function, this // will flatten the closure and add upvalues to all of the intermediate // functions so that it gets walked down to this one. // // If it reaches a method boundary, this stops and returns -1 since methods do // not close over local variables. static int findUpvalue(Compiler* compiler, const char* name, int length) { // If we are at the top level, we didn't find it. if (compiler->parent == NULL) return -1; // If we hit the method boundary (and the name isn't a static field), then // stop looking for it. We'll instead treat it as a self send. if (name[0] != '_' && compiler->parent->enclosingClass != NULL) return -1; // See if it's a local variable in the immediately enclosing function. int local = resolveLocal(compiler->parent, name, length); if (local != -1) { // Mark the local as an upvalue so we know to close it when it goes out of // scope. compiler->parent->locals[local].isUpvalue = true; return addUpvalue(compiler, true, local); } // See if it's an upvalue in the immediately enclosing function. In other // words, if it's a local variable in a non-immediately enclosing function. // This "flattens" closures automatically: it adds upvalues to all of the // intermediate functions to get from the function where a local is declared // all the way into the possibly deeply nested function that is closing over // it. int upvalue = findUpvalue(compiler->parent, name, length); if (upvalue != -1) { return addUpvalue(compiler, false, upvalue); } // If we got here, we walked all the way up the parent chain and couldn't // find it. return -1; } // Look up [name] in the current scope to see what variable it refers to. // Returns the variable either in local scope, or the enclosing function's // upvalue list. Does not search the module scope. Returns a variable with // index -1 if not found. static Variable resolveNonmodule(Compiler* compiler, const char* name, int length) { // Look it up in the local scopes. Variable variable; variable.scope = SCOPE_LOCAL; variable.index = resolveLocal(compiler, name, length); if (variable.index != -1) return variable; // Tt's not a local, so guess that it's an upvalue. variable.scope = SCOPE_UPVALUE; variable.index = findUpvalue(compiler, name, length); return variable; } // Look up [name] in the current scope to see what variable it refers to. // Returns the variable either in module scope, local scope, or the enclosing // function's upvalue list. Returns a variable with index -1 if not found. static Variable resolveName(Compiler* compiler, const char* name, int length) { Variable variable = resolveNonmodule(compiler, name, length); if (variable.index != -1) return variable; variable.scope = SCOPE_MODULE; variable.index = wrenSymbolTableFind(&compiler->parser->module->variableNames, name, length); return variable; } static void loadLocal(Compiler* compiler, int slot) { if (slot <= 8) { emitOp(compiler, (Code)(CODE_LOAD_LOCAL_0 + slot)); return; } emitByteArg(compiler, CODE_LOAD_LOCAL, slot); } // Finishes [compiler], which is compiling a function, method, or chunk of top // level code. If there is a parent compiler, then this emits code in the // parent compiler to load the resulting function. static ObjFn* endCompiler(Compiler* compiler, const char* debugName, int debugNameLength) { // If we hit an error, don't finish the function since it's borked anyway. if (compiler->parser->hasError) { compiler->parser->vm->compiler = compiler->parent; return NULL; } // Mark the end of the bytecode. Since it may contain multiple early returns, // we can't rely on CODE_RETURN to tell us we're at the end. emitOp(compiler, CODE_END); wrenFunctionBindName(compiler->parser->vm, compiler->fn, debugName, debugNameLength); // In the function that contains this one, load the resulting function object. if (compiler->parent != NULL) { int constant = addConstant(compiler->parent, OBJ_VAL(compiler->fn)); // Wrap the function in a closure. We do this even if it has no upvalues so // that the VM can uniformly assume all called objects are closures. This // makes creating a function a little slower, but makes invoking them // faster. Given that functions are invoked more often than they are // created, this is a win. emitShortArg(compiler->parent, CODE_CLOSURE, constant); // Emit arguments for each upvalue to know whether to capture a local or // an upvalue. for (int i = 0; i < compiler->fn->numUpvalues; i++) { emitByte(compiler->parent, compiler->upvalues[i].isLocal ? 1 : 0); emitByte(compiler->parent, compiler->upvalues[i].index); } } // Pop this compiler off the stack. compiler->parser->vm->compiler = compiler->parent; #if WREN_DEBUG_DUMP_COMPILED_CODE wrenDumpCode(compiler->parser->vm, compiler->fn); #endif return compiler->fn; } // Grammar --------------------------------------------------------------------- typedef enum { PREC_NONE, PREC_LOWEST, PREC_ASSIGNMENT, // = PREC_CONDITIONAL, // ?: PREC_LOGICAL_OR, // || PREC_LOGICAL_AND, // && PREC_EQUALITY, // == != PREC_IS, // is PREC_COMPARISON, // < > <= >= PREC_BITWISE_OR, // | PREC_BITWISE_XOR, // ^ PREC_BITWISE_AND, // & PREC_BITWISE_SHIFT, // << >> PREC_RANGE, // .. ... PREC_TERM, // + - PREC_FACTOR, // * / % PREC_UNARY, // unary - ! ~ PREC_CALL, // . () [] PREC_PRIMARY } Precedence; typedef void (*GrammarFn)(Compiler*, bool canAssign); typedef void (*SignatureFn)(Compiler* compiler, Signature* signature); typedef struct { GrammarFn prefix; GrammarFn infix; SignatureFn method; Precedence precedence; const char* name; } GrammarRule; // Forward declarations since the grammar is recursive. static GrammarRule* getRule(TokenType type); static void expression(Compiler* compiler); static void statement(Compiler* compiler); static void definition(Compiler* compiler); static void parsePrecedence(Compiler* compiler, Precedence precedence); // Replaces the placeholder argument for a previous CODE_JUMP or CODE_JUMP_IF // instruction with an offset that jumps to the current end of bytecode. static void patchJump(Compiler* compiler, int offset) { // -2 to adjust for the bytecode for the jump offset itself. int jump = compiler->fn->code.count - offset - 2; if (jump > MAX_JUMP) error(compiler, "Too much code to jump over."); compiler->fn->code.data[offset] = (jump >> 8) & 0xff; compiler->fn->code.data[offset + 1] = jump & 0xff; } // Parses a block body, after the initial "{" has been consumed. // // Returns true if it was a expression body, false if it was a statement body. // (More precisely, returns true if a value was left on the stack. An empty // block returns false.) static bool finishBlock(Compiler* compiler) { // Empty blocks do nothing. if (match(compiler, TOKEN_RIGHT_BRACE)) return false; // If there's no line after the "{", it's a single-expression body. if (!matchLine(compiler)) { expression(compiler); consume(compiler, TOKEN_RIGHT_BRACE, "Expect '}' at end of block."); return true; } // Empty blocks (with just a newline inside) do nothing. if (match(compiler, TOKEN_RIGHT_BRACE)) return false; // Compile the definition list. do { definition(compiler); consumeLine(compiler, "Expect newline after statement."); } while (peek(compiler) != TOKEN_RIGHT_BRACE && peek(compiler) != TOKEN_EOF); consume(compiler, TOKEN_RIGHT_BRACE, "Expect '}' at end of block."); return false; } // Parses a method or function body, after the initial "{" has been consumed. // // If [Compiler->isInitializer] is `true`, this is the body of a constructor // initializer. In that case, this adds the code to ensure it returns `this`. static void finishBody(Compiler* compiler) { bool isExpressionBody = finishBlock(compiler); if (compiler->isInitializer) { // If the initializer body evaluates to a value, discard it. if (isExpressionBody) emitOp(compiler, CODE_POP); // The receiver is always stored in the first local slot. emitOp(compiler, CODE_LOAD_LOCAL_0); } else if (!isExpressionBody) { // Implicitly return null in statement bodies. emitOp(compiler, CODE_NULL); } emitOp(compiler, CODE_RETURN); } // The VM can only handle a certain number of parameters, so check that we // haven't exceeded that and give a usable error. static void validateNumParameters(Compiler* compiler, int numArgs) { if (numArgs == MAX_PARAMETERS + 1) { // Only show an error at exactly max + 1 so that we can keep parsing the // parameters and minimize cascaded errors. error(compiler, "Methods cannot have more than %d parameters.", MAX_PARAMETERS); } } // Parses the rest of a comma-separated parameter list after the opening // delimeter. Updates `arity` in [signature] with the number of parameters. static void finishParameterList(Compiler* compiler, Signature* signature) { do { ignoreNewlines(compiler); validateNumParameters(compiler, ++signature->arity); // Define a local variable in the method for the parameter. declareNamedVariable(compiler); } while (match(compiler, TOKEN_COMMA)); } // Gets the symbol for a method [name] with [length]. static int methodSymbol(Compiler* compiler, const char* name, int length) { return wrenSymbolTableEnsure(compiler->parser->vm, &compiler->parser->vm->methodNames, name, length); } // Appends characters to [name] (and updates [length]) for [numParams] "_" // surrounded by [leftBracket] and [rightBracket]. static void signatureParameterList(char name[MAX_METHOD_SIGNATURE], int* length, int numParams, char leftBracket, char rightBracket) { name[(*length)++] = leftBracket; // This function may be called with too many parameters. When that happens, // a compile error has already been reported, but we need to make sure we // don't overflow the string too, hence the MAX_PARAMETERS check. for (int i = 0; i < numParams && i < MAX_PARAMETERS; i++) { if (i > 0) name[(*length)++] = ','; name[(*length)++] = '_'; } name[(*length)++] = rightBracket; } // Fills [name] with the stringified version of [signature] and updates // [length] to the resulting length. static void signatureToString(Signature* signature, char name[MAX_METHOD_SIGNATURE], int* length) { *length = 0; // Build the full name from the signature. memcpy(name + *length, signature->name, signature->length); *length += signature->length; switch (signature->type) { case SIG_METHOD: signatureParameterList(name, length, signature->arity, '(', ')'); break; case SIG_GETTER: // The signature is just the name. break; case SIG_SETTER: name[(*length)++] = '='; signatureParameterList(name, length, 1, '(', ')'); break; case SIG_SUBSCRIPT: signatureParameterList(name, length, signature->arity, '[', ']'); break; case SIG_SUBSCRIPT_SETTER: signatureParameterList(name, length, signature->arity - 1, '[', ']'); name[(*length)++] = '='; signatureParameterList(name, length, 1, '(', ')'); break; case SIG_INITIALIZER: memcpy(name, "init ", 5); memcpy(name + 5, signature->name, signature->length); *length = 5 + signature->length; signatureParameterList(name, length, signature->arity, '(', ')'); break; } name[*length] = '\0'; } // Gets the symbol for a method with [signature]. static int signatureSymbol(Compiler* compiler, Signature* signature) { // Build the full name from the signature. char name[MAX_METHOD_SIGNATURE]; int length; signatureToString(signature, name, &length); return methodSymbol(compiler, name, length); } // Returns a signature with [type] whose name is from the last consumed token. static Signature signatureFromToken(Compiler* compiler, SignatureType type) { Signature signature; // Get the token for the method name. Token* token = &compiler->parser->previous; signature.name = token->start; signature.length = token->length; signature.type = type; signature.arity = 0; if (signature.length > MAX_METHOD_NAME) { error(compiler, "Method names cannot be longer than %d characters.", MAX_METHOD_NAME); signature.length = MAX_METHOD_NAME; } return signature; } // Parses a comma-separated list of arguments. Modifies [signature] to include // the arity of the argument list. static void finishArgumentList(Compiler* compiler, Signature* signature) { do { ignoreNewlines(compiler); validateNumParameters(compiler, ++signature->arity); expression(compiler); } while (match(compiler, TOKEN_COMMA)); // Allow a newline before the closing delimiter. ignoreNewlines(compiler); } // Compiles a method call with [signature] using [instruction]. static void callSignature(Compiler* compiler, Code instruction, Signature* signature) { int symbol = signatureSymbol(compiler, signature); emitShortArg(compiler, (Code)(instruction + signature->arity), symbol); if (instruction == CODE_SUPER_0) { // Super calls need to be statically bound to the class's superclass. This // ensures we call the right method even when a method containing a super // call is inherited by another subclass. // // We bind it at class definition time by storing a reference to the // superclass in a constant. So, here, we create a slot in the constant // table and store NULL in it. When the method is bound, we'll look up the // superclass then and store it in the constant slot. emitShort(compiler, addConstant(compiler, NULL_VAL)); } } // Compiles a method call with [numArgs] for a method with [name] with [length]. static void callMethod(Compiler* compiler, int numArgs, const char* name, int length) { int symbol = methodSymbol(compiler, name, length); emitShortArg(compiler, (Code)(CODE_CALL_0 + numArgs), symbol); } // Compiles an (optional) argument list for a method call with [methodSignature] // and then calls it. static void methodCall(Compiler* compiler, Code instruction, Signature* signature) { // Make a new signature that contains the updated arity and type based on // the arguments we find. Signature called = { signature->name, signature->length, SIG_GETTER, 0 }; // Parse the argument list, if any. if (match(compiler, TOKEN_LEFT_PAREN)) { called.type = SIG_METHOD; // Allow new line before an empty argument list ignoreNewlines(compiler); // Allow empty an argument list. if (peek(compiler) != TOKEN_RIGHT_PAREN) { finishArgumentList(compiler, &called); } consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after arguments."); } // Parse the block argument, if any. if (match(compiler, TOKEN_LEFT_BRACE)) { // Include the block argument in the arity. called.type = SIG_METHOD; called.arity++; Compiler fnCompiler; initCompiler(&fnCompiler, compiler->parser, compiler, false); // Make a dummy signature to track the arity. Signature fnSignature = { "", 0, SIG_METHOD, 0 }; // Parse the parameter list, if any. if (match(compiler, TOKEN_PIPE)) { finishParameterList(&fnCompiler, &fnSignature); consume(compiler, TOKEN_PIPE, "Expect '|' after function parameters."); } fnCompiler.fn->arity = fnSignature.arity; finishBody(&fnCompiler); // Name the function based on the method its passed to. char blockName[MAX_METHOD_SIGNATURE + 15]; int blockLength; signatureToString(&called, blockName, &blockLength); memmove(blockName + blockLength, " block argument", 16); endCompiler(&fnCompiler, blockName, blockLength + 15); } // TODO: Allow Grace-style mixfix methods? // If this is a super() call for an initializer, make sure we got an actual // argument list. if (signature->type == SIG_INITIALIZER) { if (called.type != SIG_METHOD) { error(compiler, "A superclass constructor must have an argument list."); } called.type = SIG_INITIALIZER; } callSignature(compiler, instruction, &called); } // Compiles a call whose name is the previously consumed token. This includes // getters, method calls with arguments, and setter calls. static void namedCall(Compiler* compiler, bool canAssign, Code instruction) { // Get the token for the method name. Signature signature = signatureFromToken(compiler, SIG_GETTER); if (canAssign && match(compiler, TOKEN_EQ)) { ignoreNewlines(compiler); // Build the setter signature. signature.type = SIG_SETTER; signature.arity = 1; // Compile the assigned value. expression(compiler); callSignature(compiler, instruction, &signature); } else { methodCall(compiler, instruction, &signature); allowLineBeforeDot(compiler); } } // Emits the code to load [variable] onto the stack. static void loadVariable(Compiler* compiler, Variable variable) { switch (variable.scope) { case SCOPE_LOCAL: loadLocal(compiler, variable.index); break; case SCOPE_UPVALUE: emitByteArg(compiler, CODE_LOAD_UPVALUE, variable.index); break; case SCOPE_MODULE: emitShortArg(compiler, CODE_LOAD_MODULE_VAR, variable.index); break; default: UNREACHABLE(); } } // Loads the receiver of the currently enclosing method. Correctly handles // functions defined inside methods. static void loadThis(Compiler* compiler) { loadVariable(compiler, resolveNonmodule(compiler, "this", 4)); } // Pushes the value for a module-level variable implicitly imported from core. static void loadCoreVariable(Compiler* compiler, const char* name) { int symbol = wrenSymbolTableFind(&compiler->parser->module->variableNames, name, strlen(name)); ASSERT(symbol != -1, "Should have already defined core name."); emitShortArg(compiler, CODE_LOAD_MODULE_VAR, symbol); } // A parenthesized expression. static void grouping(Compiler* compiler, bool canAssign) { expression(compiler); consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after expression."); } // A list literal. static void list(Compiler* compiler, bool canAssign) { // Instantiate a new list. loadCoreVariable(compiler, "List"); callMethod(compiler, 0, "new()", 5); // Compile the list elements. Each one compiles to a ".add()" call. do { ignoreNewlines(compiler); // Stop if we hit the end of the list. if (peek(compiler) == TOKEN_RIGHT_BRACKET) break; // The element. expression(compiler); callMethod(compiler, 1, "addCore_(_)", 11); } while (match(compiler, TOKEN_COMMA)); // Allow newlines before the closing ']'. ignoreNewlines(compiler); consume(compiler, TOKEN_RIGHT_BRACKET, "Expect ']' after list elements."); } // A map literal. static void map(Compiler* compiler, bool canAssign) { // Instantiate a new map. loadCoreVariable(compiler, "Map"); callMethod(compiler, 0, "new()", 5); // Compile the map elements. Each one is compiled to just invoke the // subscript setter on the map. do { ignoreNewlines(compiler); // Stop if we hit the end of the map. if (peek(compiler) == TOKEN_RIGHT_BRACE) break; // The key. parsePrecedence(compiler, PREC_UNARY); consume(compiler, TOKEN_COLON, "Expect ':' after map key."); ignoreNewlines(compiler); // The value. expression(compiler); callMethod(compiler, 2, "addCore_(_,_)", 13); } while (match(compiler, TOKEN_COMMA)); // Allow newlines before the closing '}'. ignoreNewlines(compiler); consume(compiler, TOKEN_RIGHT_BRACE, "Expect '}' after map entries."); } // Unary operators like `-foo`. static void unaryOp(Compiler* compiler, bool canAssign) { GrammarRule* rule = getRule(compiler->parser->previous.type); ignoreNewlines(compiler); // Compile the argument. parsePrecedence(compiler, (Precedence)(PREC_UNARY + 1)); // Call the operator method on the left-hand side. callMethod(compiler, 0, rule->name, 1); } static void boolean(Compiler* compiler, bool canAssign) { emitOp(compiler, compiler->parser->previous.type == TOKEN_FALSE ? CODE_FALSE : CODE_TRUE); } // Walks the compiler chain to find the compiler for the nearest class // enclosing this one. Returns NULL if not currently inside a class definition. static Compiler* getEnclosingClassCompiler(Compiler* compiler) { while (compiler != NULL) { if (compiler->enclosingClass != NULL) return compiler; compiler = compiler->parent; } return NULL; } // Walks the compiler chain to find the nearest class enclosing this one. // Returns NULL if not currently inside a class definition. static ClassInfo* getEnclosingClass(Compiler* compiler) { compiler = getEnclosingClassCompiler(compiler); return compiler == NULL ? NULL : compiler->enclosingClass; } static void field(Compiler* compiler, bool canAssign) { // Initialize it with a fake value so we can keep parsing and minimize the // number of cascaded errors. int field = MAX_FIELDS; ClassInfo* enclosingClass = getEnclosingClass(compiler); if (enclosingClass == NULL) { error(compiler, "Cannot reference a field outside of a class definition."); } else if (enclosingClass->isForeign) { error(compiler, "Cannot define fields in a foreign class."); } else if (enclosingClass->inStatic) { error(compiler, "Cannot use an instance field in a static method."); } else { // Look up the field, or implicitly define it. field = wrenSymbolTableEnsure(compiler->parser->vm, &enclosingClass->fields, compiler->parser->previous.start, compiler->parser->previous.length); if (field >= MAX_FIELDS) { error(compiler, "A class can only have %d fields.", MAX_FIELDS); } } // If there's an "=" after a field name, it's an assignment. bool isLoad = true; if (canAssign && match(compiler, TOKEN_EQ)) { // Compile the right-hand side. expression(compiler); isLoad = false; } // If we're directly inside a method, use a more optimal instruction. if (compiler->parent != NULL && compiler->parent->enclosingClass == enclosingClass) { emitByteArg(compiler, isLoad ? CODE_LOAD_FIELD_THIS : CODE_STORE_FIELD_THIS, field); } else { loadThis(compiler); emitByteArg(compiler, isLoad ? CODE_LOAD_FIELD : CODE_STORE_FIELD, field); } allowLineBeforeDot(compiler); } // Compiles a read or assignment to [variable]. static void bareName(Compiler* compiler, bool canAssign, Variable variable) { // If there's an "=" after a bare name, it's a variable assignment. if (canAssign && match(compiler, TOKEN_EQ)) { // Compile the right-hand side. expression(compiler); // Emit the store instruction. switch (variable.scope) { case SCOPE_LOCAL: emitByteArg(compiler, CODE_STORE_LOCAL, variable.index); break; case SCOPE_UPVALUE: emitByteArg(compiler, CODE_STORE_UPVALUE, variable.index); break; case SCOPE_MODULE: emitShortArg(compiler, CODE_STORE_MODULE_VAR, variable.index); break; default: UNREACHABLE(); } return; } // Emit the load instruction. loadVariable(compiler, variable); allowLineBeforeDot(compiler); } static void staticField(Compiler* compiler, bool canAssign) { Compiler* classCompiler = getEnclosingClassCompiler(compiler); if (classCompiler == NULL) { error(compiler, "Cannot use a static field outside of a class definition."); return; } // Look up the name in the scope chain. Token* token = &compiler->parser->previous; // If this is the first time we've seen this static field, implicitly // define it as a variable in the scope surrounding the class definition. if (resolveLocal(classCompiler, token->start, token->length) == -1) { int symbol = declareVariable(classCompiler, NULL); // Implicitly initialize it to null. emitOp(classCompiler, CODE_NULL); defineVariable(classCompiler, symbol); } // It definitely exists now, so resolve it properly. This is different from // the above resolveLocal() call because we may have already closed over it // as an upvalue. Variable variable = resolveName(compiler, token->start, token->length); bareName(compiler, canAssign, variable); } // Compiles a variable name or method call with an implicit receiver. static void name(Compiler* compiler, bool canAssign) { // Look for the name in the scope chain up to the nearest enclosing method. Token* token = &compiler->parser->previous; Variable variable = resolveNonmodule(compiler, token->start, token->length); if (variable.index != -1) { bareName(compiler, canAssign, variable); return; } // TODO: The fact that we return above here if the variable is known and parse // an optional argument list below if not means that the grammar is not // context-free. A line of code in a method like "someName(foo)" is a parse // error if "someName" is a defined variable in the surrounding scope and not // if it isn't. Fix this. One option is to have "someName(foo)" always // resolve to a self-call if there is an argument list, but that makes // getters a little confusing. // If we're inside a method and the name is lowercase, treat it as a method // on this. if (wrenIsLocalName(token->start) && getEnclosingClass(compiler) != NULL) { loadThis(compiler); namedCall(compiler, canAssign, CODE_CALL_0); return; } // Otherwise, look for a module-level variable with the name. variable.scope = SCOPE_MODULE; variable.index = wrenSymbolTableFind(&compiler->parser->module->variableNames, token->start, token->length); if (variable.index == -1) { // Implicitly define a module-level variable in // the hopes that we get a real definition later. variable.index = wrenDeclareVariable(compiler->parser->vm, compiler->parser->module, token->start, token->length, token->line); if (variable.index == -2) { error(compiler, "Too many module variables defined."); } } bareName(compiler, canAssign, variable); } static void null(Compiler* compiler, bool canAssign) { emitOp(compiler, CODE_NULL); } // A number or string literal. static void literal(Compiler* compiler, bool canAssign) { emitConstant(compiler, compiler->parser->previous.value); } // A string literal that contains interpolated expressions. // // Interpolation is syntactic sugar for calling ".join()" on a list. So the // string: // // "a %(b + c) d" // // is compiled roughly like: // // ["a ", b + c, " d"].join() static void stringInterpolation(Compiler* compiler, bool canAssign) { // Instantiate a new list. loadCoreVariable(compiler, "List"); callMethod(compiler, 0, "new()", 5); do { // The opening string part. literal(compiler, false); callMethod(compiler, 1, "addCore_(_)", 11); // The interpolated expression. ignoreNewlines(compiler); expression(compiler); callMethod(compiler, 1, "addCore_(_)", 11); ignoreNewlines(compiler); } while (match(compiler, TOKEN_INTERPOLATION)); // The trailing string part. consume(compiler, TOKEN_STRING, "Expect end of string interpolation."); literal(compiler, false); callMethod(compiler, 1, "addCore_(_)", 11); // The list of interpolated parts. callMethod(compiler, 0, "join()", 6); } static void super_(Compiler* compiler, bool canAssign) { ClassInfo* enclosingClass = getEnclosingClass(compiler); if (enclosingClass == NULL) { error(compiler, "Cannot use 'super' outside of a method."); } loadThis(compiler); // TODO: Super operator calls. // TODO: There's no syntax for invoking a superclass constructor with a // different name from the enclosing one. Figure that out. // See if it's a named super call, or an unnamed one. if (match(compiler, TOKEN_DOT)) { // Compile the superclass call. consume(compiler, TOKEN_NAME, "Expect method name after 'super.'."); namedCall(compiler, canAssign, CODE_SUPER_0); } else if (enclosingClass != NULL) { // No explicit name, so use the name of the enclosing method. Make sure we // check that enclosingClass isn't NULL first. We've already reported the // error, but we don't want to crash here. methodCall(compiler, CODE_SUPER_0, enclosingClass->signature); } } static void this_(Compiler* compiler, bool canAssign) { if (getEnclosingClass(compiler) == NULL) { error(compiler, "Cannot use 'this' outside of a method."); return; } loadThis(compiler); } // Subscript or "array indexing" operator like `foo[bar]`. static void subscript(Compiler* compiler, bool canAssign) { Signature signature = { "", 0, SIG_SUBSCRIPT, 0 }; // Parse the argument list. finishArgumentList(compiler, &signature); consume(compiler, TOKEN_RIGHT_BRACKET, "Expect ']' after arguments."); allowLineBeforeDot(compiler); if (canAssign && match(compiler, TOKEN_EQ)) { signature.type = SIG_SUBSCRIPT_SETTER; // Compile the assigned value. validateNumParameters(compiler, ++signature.arity); expression(compiler); } callSignature(compiler, CODE_CALL_0, &signature); } static void call(Compiler* compiler, bool canAssign) { ignoreNewlines(compiler); consume(compiler, TOKEN_NAME, "Expect method name after '.'."); namedCall(compiler, canAssign, CODE_CALL_0); } static void and_(Compiler* compiler, bool canAssign) { ignoreNewlines(compiler); // Skip the right argument if the left is false. int jump = emitJump(compiler, CODE_AND); parsePrecedence(compiler, PREC_LOGICAL_AND); patchJump(compiler, jump); } static void or_(Compiler* compiler, bool canAssign) { ignoreNewlines(compiler); // Skip the right argument if the left is true. int jump = emitJump(compiler, CODE_OR); parsePrecedence(compiler, PREC_LOGICAL_OR); patchJump(compiler, jump); } static void conditional(Compiler* compiler, bool canAssign) { // Ignore newline after '?'. ignoreNewlines(compiler); // Jump to the else branch if the condition is false. int ifJump = emitJump(compiler, CODE_JUMP_IF); // Compile the then branch. parsePrecedence(compiler, PREC_CONDITIONAL); consume(compiler, TOKEN_COLON, "Expect ':' after then branch of conditional operator."); ignoreNewlines(compiler); // Jump over the else branch when the if branch is taken. int elseJump = emitJump(compiler, CODE_JUMP); // Compile the else branch. patchJump(compiler, ifJump); parsePrecedence(compiler, PREC_ASSIGNMENT); // Patch the jump over the else. patchJump(compiler, elseJump); } void infixOp(Compiler* compiler, bool canAssign) { GrammarRule* rule = getRule(compiler->parser->previous.type); // An infix operator cannot end an expression. ignoreNewlines(compiler); // Compile the right-hand side. parsePrecedence(compiler, (Precedence)(rule->precedence + 1)); // Call the operator method on the left-hand side. Signature signature = { rule->name, (int)strlen(rule->name), SIG_METHOD, 1 }; callSignature(compiler, CODE_CALL_0, &signature); } // Compiles a method signature for an infix operator. void infixSignature(Compiler* compiler, Signature* signature) { // Add the RHS parameter. signature->type = SIG_METHOD; signature->arity = 1; // Parse the parameter name. consume(compiler, TOKEN_LEFT_PAREN, "Expect '(' after operator name."); declareNamedVariable(compiler); consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after parameter name."); } // Compiles a method signature for an unary operator (i.e. "!"). void unarySignature(Compiler* compiler, Signature* signature) { // Do nothing. The name is already complete. signature->type = SIG_GETTER; } // Compiles a method signature for an operator that can either be unary or // infix (i.e. "-"). void mixedSignature(Compiler* compiler, Signature* signature) { signature->type = SIG_GETTER; // If there is a parameter, it's an infix operator, otherwise it's unary. if (match(compiler, TOKEN_LEFT_PAREN)) { // Add the RHS parameter. signature->type = SIG_METHOD; signature->arity = 1; // Parse the parameter name. declareNamedVariable(compiler); consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after parameter name."); } } // Compiles an optional setter parameter in a method [signature]. // // Returns `true` if it was a setter. static bool maybeSetter(Compiler* compiler, Signature* signature) { // See if it's a setter. if (!match(compiler, TOKEN_EQ)) return false; // It's a setter. if (signature->type == SIG_SUBSCRIPT) { signature->type = SIG_SUBSCRIPT_SETTER; } else { signature->type = SIG_SETTER; } // Parse the value parameter. consume(compiler, TOKEN_LEFT_PAREN, "Expect '(' after '='."); declareNamedVariable(compiler); consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after parameter name."); signature->arity++; return true; } // Compiles a method signature for a subscript operator. void subscriptSignature(Compiler* compiler, Signature* signature) { signature->type = SIG_SUBSCRIPT; // The signature currently has "[" as its name since that was the token that // matched it. Clear that out. signature->length = 0; // Parse the parameters inside the subscript. finishParameterList(compiler, signature); consume(compiler, TOKEN_RIGHT_BRACKET, "Expect ']' after parameters."); maybeSetter(compiler, signature); } // Parses an optional parenthesized parameter list. Updates `type` and `arity` // in [signature] to match what was parsed. static void parameterList(Compiler* compiler, Signature* signature) { // The parameter list is optional. if (!match(compiler, TOKEN_LEFT_PAREN)) return; signature->type = SIG_METHOD; // Allow new line before an empty argument list ignoreNewlines(compiler); // Allow an empty parameter list. if (match(compiler, TOKEN_RIGHT_PAREN)) return; finishParameterList(compiler, signature); consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after parameters."); } // Compiles a method signature for a named method or setter. void namedSignature(Compiler* compiler, Signature* signature) { signature->type = SIG_GETTER; // If it's a setter, it can't also have a parameter list. if (maybeSetter(compiler, signature)) return; // Regular named method with an optional parameter list. parameterList(compiler, signature); } // Compiles a method signature for a constructor. void constructorSignature(Compiler* compiler, Signature* signature) { consume(compiler, TOKEN_NAME, "Expect constructor name after 'construct'."); // Capture the name. *signature = signatureFromToken(compiler, SIG_INITIALIZER); if (match(compiler, TOKEN_EQ)) { error(compiler, "A constructor cannot be a setter."); } if (!match(compiler, TOKEN_LEFT_PAREN)) { error(compiler, "A constructor cannot be a getter."); return; } // Allow an empty parameter list. if (match(compiler, TOKEN_RIGHT_PAREN)) return; finishParameterList(compiler, signature); consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after parameters."); } // This table defines all of the parsing rules for the prefix and infix // expressions in the grammar. Expressions are parsed using a Pratt parser. // // See: http://journal.stuffwithstuff.com/2011/03/19/pratt-parsers-expression-parsing-made-easy/ #define UNUSED { NULL, NULL, NULL, PREC_NONE, NULL } #define PREFIX(fn) { fn, NULL, NULL, PREC_NONE, NULL } #define INFIX(prec, fn) { NULL, fn, NULL, prec, NULL } #define INFIX_OPERATOR(prec, name) { NULL, infixOp, infixSignature, prec, name } #define PREFIX_OPERATOR(name) { unaryOp, NULL, unarySignature, PREC_NONE, name } #define OPERATOR(name) { unaryOp, infixOp, mixedSignature, PREC_TERM, name } GrammarRule rules[] = { /* TOKEN_LEFT_PAREN */ PREFIX(grouping), /* TOKEN_RIGHT_PAREN */ UNUSED, /* TOKEN_LEFT_BRACKET */ { list, subscript, subscriptSignature, PREC_CALL, NULL }, /* TOKEN_RIGHT_BRACKET */ UNUSED, /* TOKEN_LEFT_BRACE */ PREFIX(map), /* TOKEN_RIGHT_BRACE */ UNUSED, /* TOKEN_COLON */ UNUSED, /* TOKEN_DOT */ INFIX(PREC_CALL, call), /* TOKEN_DOTDOT */ INFIX_OPERATOR(PREC_RANGE, ".."), /* TOKEN_DOTDOTDOT */ INFIX_OPERATOR(PREC_RANGE, "..."), /* TOKEN_COMMA */ UNUSED, /* TOKEN_STAR */ INFIX_OPERATOR(PREC_FACTOR, "*"), /* TOKEN_SLASH */ INFIX_OPERATOR(PREC_FACTOR, "/"), /* TOKEN_PERCENT */ INFIX_OPERATOR(PREC_FACTOR, "%"), /* TOKEN_HASH */ UNUSED, /* TOKEN_PLUS */ INFIX_OPERATOR(PREC_TERM, "+"), /* TOKEN_MINUS */ OPERATOR("-"), /* TOKEN_LTLT */ INFIX_OPERATOR(PREC_BITWISE_SHIFT, "<<"), /* TOKEN_GTGT */ INFIX_OPERATOR(PREC_BITWISE_SHIFT, ">>"), /* TOKEN_PIPE */ INFIX_OPERATOR(PREC_BITWISE_OR, "|"), /* TOKEN_PIPEPIPE */ INFIX(PREC_LOGICAL_OR, or_), /* TOKEN_CARET */ INFIX_OPERATOR(PREC_BITWISE_XOR, "^"), /* TOKEN_AMP */ INFIX_OPERATOR(PREC_BITWISE_AND, "&"), /* TOKEN_AMPAMP */ INFIX(PREC_LOGICAL_AND, and_), /* TOKEN_BANG */ PREFIX_OPERATOR("!"), /* TOKEN_TILDE */ PREFIX_OPERATOR("~"), /* TOKEN_QUESTION */ INFIX(PREC_ASSIGNMENT, conditional), /* TOKEN_EQ */ UNUSED, /* TOKEN_LT */ INFIX_OPERATOR(PREC_COMPARISON, "<"), /* TOKEN_GT */ INFIX_OPERATOR(PREC_COMPARISON, ">"), /* TOKEN_LTEQ */ INFIX_OPERATOR(PREC_COMPARISON, "<="), /* TOKEN_GTEQ */ INFIX_OPERATOR(PREC_COMPARISON, ">="), /* TOKEN_EQEQ */ INFIX_OPERATOR(PREC_EQUALITY, "=="), /* TOKEN_BANGEQ */ INFIX_OPERATOR(PREC_EQUALITY, "!="), /* TOKEN_BREAK */ UNUSED, /* TOKEN_CONTINUE */ UNUSED, /* TOKEN_CLASS */ UNUSED, /* TOKEN_CONSTRUCT */ { NULL, NULL, constructorSignature, PREC_NONE, NULL }, /* TOKEN_ELSE */ UNUSED, /* TOKEN_FALSE */ PREFIX(boolean), /* TOKEN_FOR */ UNUSED, /* TOKEN_FOREIGN */ UNUSED, /* TOKEN_IF */ UNUSED, /* TOKEN_IMPORT */ UNUSED, /* TOKEN_AS */ UNUSED, /* TOKEN_IN */ UNUSED, /* TOKEN_IS */ INFIX_OPERATOR(PREC_IS, "is"), /* TOKEN_NULL */ PREFIX(null), /* TOKEN_RETURN */ UNUSED, /* TOKEN_STATIC */ UNUSED, /* TOKEN_SUPER */ PREFIX(super_), /* TOKEN_THIS */ PREFIX(this_), /* TOKEN_TRUE */ PREFIX(boolean), /* TOKEN_VAR */ UNUSED, /* TOKEN_WHILE */ UNUSED, /* TOKEN_FIELD */ PREFIX(field), /* TOKEN_STATIC_FIELD */ PREFIX(staticField), /* TOKEN_NAME */ { name, NULL, namedSignature, PREC_NONE, NULL }, /* TOKEN_NUMBER */ PREFIX(literal), /* TOKEN_STRING */ PREFIX(literal), /* TOKEN_INTERPOLATION */ PREFIX(stringInterpolation), /* TOKEN_LINE */ UNUSED, /* TOKEN_ERROR */ UNUSED, /* TOKEN_EOF */ UNUSED }; // Gets the [GrammarRule] associated with tokens of [type]. static GrammarRule* getRule(TokenType type) { return &rules[type]; } // The main entrypoint for the top-down operator precedence parser. void parsePrecedence(Compiler* compiler, Precedence precedence) { nextToken(compiler->parser); GrammarFn prefix = rules[compiler->parser->previous.type].prefix; if (prefix == NULL) { error(compiler, "Expected expression."); return; } // Track if the precendence of the surrounding expression is low enough to // allow an assignment inside this one. We can't compile an assignment like // a normal expression because it requires us to handle the LHS specially -- // it needs to be an lvalue, not an rvalue. So, for each of the kinds of // expressions that are valid lvalues -- names, subscripts, fields, etc. -- // we pass in whether or not it appears in a context loose enough to allow // "=". If so, it will parse the "=" itself and handle it appropriately. bool canAssign = precedence <= PREC_CONDITIONAL; prefix(compiler, canAssign); while (precedence <= rules[compiler->parser->current.type].precedence) { nextToken(compiler->parser); GrammarFn infix = rules[compiler->parser->previous.type].infix; infix(compiler, canAssign); } } // Parses an expression. Unlike statements, expressions leave a resulting value // on the stack. void expression(Compiler* compiler) { parsePrecedence(compiler, PREC_LOWEST); } // Returns the number of bytes for the arguments to the instruction // at [ip] in [fn]'s bytecode. static int getByteCountForArguments(const uint8_t* bytecode, const Value* constants, int ip) { Code instruction = (Code)bytecode[ip]; switch (instruction) { case CODE_NULL: case CODE_FALSE: case CODE_TRUE: case CODE_POP: case CODE_CLOSE_UPVALUE: case CODE_RETURN: case CODE_END: case CODE_LOAD_LOCAL_0: case CODE_LOAD_LOCAL_1: case CODE_LOAD_LOCAL_2: case CODE_LOAD_LOCAL_3: case CODE_LOAD_LOCAL_4: case CODE_LOAD_LOCAL_5: case CODE_LOAD_LOCAL_6: case CODE_LOAD_LOCAL_7: case CODE_LOAD_LOCAL_8: case CODE_CONSTRUCT: case CODE_FOREIGN_CONSTRUCT: case CODE_FOREIGN_CLASS: case CODE_END_MODULE: case CODE_END_CLASS: return 0; case CODE_LOAD_LOCAL: case CODE_STORE_LOCAL: case CODE_LOAD_UPVALUE: case CODE_STORE_UPVALUE: case CODE_LOAD_FIELD_THIS: case CODE_STORE_FIELD_THIS: case CODE_LOAD_FIELD: case CODE_STORE_FIELD: case CODE_CLASS: return 1; case CODE_CONSTANT: case CODE_LOAD_MODULE_VAR: case CODE_STORE_MODULE_VAR: case CODE_CALL_0: case CODE_CALL_1: case CODE_CALL_2: case CODE_CALL_3: case CODE_CALL_4: case CODE_CALL_5: case CODE_CALL_6: case CODE_CALL_7: case CODE_CALL_8: case CODE_CALL_9: case CODE_CALL_10: case CODE_CALL_11: case CODE_CALL_12: case CODE_CALL_13: case CODE_CALL_14: case CODE_CALL_15: case CODE_CALL_16: case CODE_JUMP: case CODE_LOOP: case CODE_JUMP_IF: case CODE_AND: case CODE_OR: case CODE_METHOD_INSTANCE: case CODE_METHOD_STATIC: case CODE_IMPORT_MODULE: case CODE_IMPORT_VARIABLE: return 2; case CODE_SUPER_0: case CODE_SUPER_1: case CODE_SUPER_2: case CODE_SUPER_3: case CODE_SUPER_4: case CODE_SUPER_5: case CODE_SUPER_6: case CODE_SUPER_7: case CODE_SUPER_8: case CODE_SUPER_9: case CODE_SUPER_10: case CODE_SUPER_11: case CODE_SUPER_12: case CODE_SUPER_13: case CODE_SUPER_14: case CODE_SUPER_15: case CODE_SUPER_16: return 4; case CODE_CLOSURE: { int constant = (bytecode[ip + 1] << 8) | bytecode[ip + 2]; ObjFn* loadedFn = AS_FN(constants[constant]); // There are two bytes for the constant, then two for each upvalue. return 2 + (loadedFn->numUpvalues * 2); } } UNREACHABLE(); return 0; } // Marks the beginning of a loop. Keeps track of the current instruction so we // know what to loop back to at the end of the body. static void startLoop(Compiler* compiler, Loop* loop) { loop->enclosing = compiler->loop; loop->start = compiler->fn->code.count - 1; loop->scopeDepth = compiler->scopeDepth; compiler->loop = loop; } // Emits the [CODE_JUMP_IF] instruction used to test the loop condition and // potentially exit the loop. Keeps track of the instruction so we can patch it // later once we know where the end of the body is. static void testExitLoop(Compiler* compiler) { compiler->loop->exitJump = emitJump(compiler, CODE_JUMP_IF); } // Compiles the body of the loop and tracks its extent so that contained "break" // statements can be handled correctly. static void loopBody(Compiler* compiler) { compiler->loop->body = compiler->fn->code.count; statement(compiler); } // Ends the current innermost loop. Patches up all jumps and breaks now that // we know where the end of the loop is. static void endLoop(Compiler* compiler) { // We don't check for overflow here since the forward jump over the loop body // will report an error for the same problem. int loopOffset = compiler->fn->code.count - compiler->loop->start + 2; emitShortArg(compiler, CODE_LOOP, loopOffset); patchJump(compiler, compiler->loop->exitJump); // Find any break placeholder instructions (which will be CODE_END in the // bytecode) and replace them with real jumps. int i = compiler->loop->body; while (i < compiler->fn->code.count) { if (compiler->fn->code.data[i] == CODE_END) { compiler->fn->code.data[i] = CODE_JUMP; patchJump(compiler, i + 1); i += 3; } else { // Skip this instruction and its arguments. i += 1 + getByteCountForArguments(compiler->fn->code.data, compiler->fn->constants.data, i); } } compiler->loop = compiler->loop->enclosing; } static void forStatement(Compiler* compiler) { // A for statement like: // // for (i in sequence.expression) { // System.print(i) // } // // Is compiled to bytecode almost as if the source looked like this: // // { // var seq_ = sequence.expression // var iter_ // while (iter_ = seq_.iterate(iter_)) { // var i = seq_.iteratorValue(iter_) // System.print(i) // } // } // // It's not exactly this, because the synthetic variables `seq_` and `iter_` // actually get names that aren't valid Wren identfiers, but that's the basic // idea. // // The important parts are: // - The sequence expression is only evaluated once. // - The .iterate() method is used to advance the iterator and determine if // it should exit the loop. // - The .iteratorValue() method is used to get the value at the current // iterator position. // Create a scope for the hidden local variables used for the iterator. pushScope(compiler); consume(compiler, TOKEN_LEFT_PAREN, "Expect '(' after 'for'."); consume(compiler, TOKEN_NAME, "Expect for loop variable name."); // Remember the name of the loop variable. const char* name = compiler->parser->previous.start; int length = compiler->parser->previous.length; consume(compiler, TOKEN_IN, "Expect 'in' after loop variable."); ignoreNewlines(compiler); // Evaluate the sequence expression and store it in a hidden local variable. // The space in the variable name ensures it won't collide with a user-defined // variable. expression(compiler); // Verify that there is space to hidden local variables. // Note that we expect only two addLocal calls next to each other in the // following code. if (compiler->numLocals + 2 > MAX_LOCALS) { error(compiler, "Cannot declare more than %d variables in one scope. (Not enough space for for-loops internal variables)", MAX_LOCALS); return; } int seqSlot = addLocal(compiler, "seq ", 4); // Create another hidden local for the iterator object. null(compiler, false); int iterSlot = addLocal(compiler, "iter ", 5); consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after loop expression."); Loop loop; startLoop(compiler, &loop); // Advance the iterator by calling the ".iterate" method on the sequence. loadLocal(compiler, seqSlot); loadLocal(compiler, iterSlot); // Update and test the iterator. callMethod(compiler, 1, "iterate(_)", 10); emitByteArg(compiler, CODE_STORE_LOCAL, iterSlot); testExitLoop(compiler); // Get the current value in the sequence by calling ".iteratorValue". loadLocal(compiler, seqSlot); loadLocal(compiler, iterSlot); callMethod(compiler, 1, "iteratorValue(_)", 16); // Bind the loop variable in its own scope. This ensures we get a fresh // variable each iteration so that closures for it don't all see the same one. pushScope(compiler); addLocal(compiler, name, length); loopBody(compiler); // Loop variable. popScope(compiler); endLoop(compiler); // Hidden variables. popScope(compiler); } static void ifStatement(Compiler* compiler) { // Compile the condition. consume(compiler, TOKEN_LEFT_PAREN, "Expect '(' after 'if'."); expression(compiler); consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after if condition."); // Jump to the else branch if the condition is false. int ifJump = emitJump(compiler, CODE_JUMP_IF); // Compile the then branch. statement(compiler); // Compile the else branch if there is one. if (match(compiler, TOKEN_ELSE)) { // Jump over the else branch when the if branch is taken. int elseJump = emitJump(compiler, CODE_JUMP); patchJump(compiler, ifJump); statement(compiler); // Patch the jump over the else. patchJump(compiler, elseJump); } else { patchJump(compiler, ifJump); } } static void whileStatement(Compiler* compiler) { Loop loop; startLoop(compiler, &loop); // Compile the condition. consume(compiler, TOKEN_LEFT_PAREN, "Expect '(' after 'while'."); expression(compiler); consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after while condition."); testExitLoop(compiler); loopBody(compiler); endLoop(compiler); } // Compiles a simple statement. These can only appear at the top-level or // within curly blocks. Simple statements exclude variable binding statements // like "var" and "class" which are not allowed directly in places like the // branches of an "if" statement. // // Unlike expressions, statements do not leave a value on the stack. void statement(Compiler* compiler) { if (match(compiler, TOKEN_BREAK)) { if (compiler->loop == NULL) { error(compiler, "Cannot use 'break' outside of a loop."); return; } // Since we will be jumping out of the scope, make sure any locals in it // are discarded first. discardLocals(compiler, compiler->loop->scopeDepth + 1); // Emit a placeholder instruction for the jump to the end of the body. When // we're done compiling the loop body and know where the end is, we'll // replace these with `CODE_JUMP` instructions with appropriate offsets. // We use `CODE_END` here because that can't occur in the middle of // bytecode. emitJump(compiler, CODE_END); } else if (match(compiler, TOKEN_CONTINUE)) { if (compiler->loop == NULL) { error(compiler, "Cannot use 'continue' outside of a loop."); return; } // Since we will be jumping out of the scope, make sure any locals in it // are discarded first. discardLocals(compiler, compiler->loop->scopeDepth + 1); // emit a jump back to the top of the loop int loopOffset = compiler->fn->code.count - compiler->loop->start + 2; emitShortArg(compiler, CODE_LOOP, loopOffset); } else if (match(compiler, TOKEN_FOR)) { forStatement(compiler); } else if (match(compiler, TOKEN_IF)) { ifStatement(compiler); } else if (match(compiler, TOKEN_RETURN)) { // Compile the return value. if (peek(compiler) == TOKEN_LINE) { // If there's no expression after return, initializers should // return 'this' and regular methods should return null Code result = compiler->isInitializer ? CODE_LOAD_LOCAL_0 : CODE_NULL; emitOp(compiler, result); } else { if (compiler->isInitializer) { error(compiler, "A constructor cannot return a value."); } expression(compiler); } emitOp(compiler, CODE_RETURN); } else if (match(compiler, TOKEN_WHILE)) { whileStatement(compiler); } else if (match(compiler, TOKEN_LEFT_BRACE)) { // Block statement. pushScope(compiler); if (finishBlock(compiler)) { // Block was an expression, so discard it. emitOp(compiler, CODE_POP); } popScope(compiler); } else { // Expression statement. expression(compiler); emitOp(compiler, CODE_POP); } } // Creates a matching constructor method for an initializer with [signature] // and [initializerSymbol]. // // Construction is a two-stage process in Wren that involves two separate // methods. There is a static method that allocates a new instance of the class. // It then invokes an initializer method on the new instance, forwarding all of // the constructor arguments to it. // // The allocator method always has a fixed implementation: // // CODE_CONSTRUCT - Replace the class in slot 0 with a new instance of it. // CODE_CALL - Invoke the initializer on the new instance. // // This creates that method and calls the initializer with [initializerSymbol]. static void createConstructor(Compiler* compiler, Signature* signature, int initializerSymbol) { Compiler methodCompiler; initCompiler(&methodCompiler, compiler->parser, compiler, true); // Allocate the instance. emitOp(&methodCompiler, compiler->enclosingClass->isForeign ? CODE_FOREIGN_CONSTRUCT : CODE_CONSTRUCT); // Run its initializer. emitShortArg(&methodCompiler, (Code)(CODE_CALL_0 + signature->arity), initializerSymbol); // Return the instance. emitOp(&methodCompiler, CODE_RETURN); endCompiler(&methodCompiler, "", 0); } // Loads the enclosing class onto the stack and then binds the function already // on the stack as a method on that class. static void defineMethod(Compiler* compiler, Variable classVariable, bool isStatic, int methodSymbol) { // Load the class. We have to do this for each method because we can't // keep the class on top of the stack. If there are static fields, they // will be locals above the initial variable slot for the class on the // stack. To skip past those, we just load the class each time right before // defining a method. loadVariable(compiler, classVariable); // Define the method. Code instruction = isStatic ? CODE_METHOD_STATIC : CODE_METHOD_INSTANCE; emitShortArg(compiler, instruction, methodSymbol); } // Declares a method in the enclosing class with [signature]. // // Reports an error if a method with that signature is already declared. // Returns the symbol for the method. static int declareMethod(Compiler* compiler, Signature* signature, const char* name, int length) { int symbol = signatureSymbol(compiler, signature); // See if the class has already declared method with this signature. ClassInfo* classInfo = compiler->enclosingClass; IntBuffer* methods = classInfo->inStatic ? &classInfo->staticMethods : &classInfo->methods; for (int i = 0; i < methods->count; i++) { if (methods->data[i] == symbol) { const char* staticPrefix = classInfo->inStatic ? "static " : ""; error(compiler, "Class %s already defines a %smethod '%s'.", &compiler->enclosingClass->name->value, staticPrefix, name); break; } } wrenIntBufferWrite(compiler->parser->vm, methods, symbol); return symbol; } static Value consumeLiteral(Compiler* compiler, const char* message) { if(match(compiler, TOKEN_FALSE)) return FALSE_VAL; if(match(compiler, TOKEN_TRUE)) return TRUE_VAL; if(match(compiler, TOKEN_NUMBER)) return compiler->parser->previous.value; if(match(compiler, TOKEN_STRING)) return compiler->parser->previous.value; if(match(compiler, TOKEN_NAME)) return compiler->parser->previous.value; error(compiler, message); nextToken(compiler->parser); return NULL_VAL; } static bool matchAttribute(Compiler* compiler) { if(match(compiler, TOKEN_HASH)) { compiler->numAttributes++; bool runtimeAccess = match(compiler, TOKEN_BANG); if(match(compiler, TOKEN_NAME)) { Value group = compiler->parser->previous.value; TokenType ahead = peek(compiler); if(ahead == TOKEN_EQ || ahead == TOKEN_LINE) { Value key = group; Value value = NULL_VAL; if(match(compiler, TOKEN_EQ)) { value = consumeLiteral(compiler, "Expect a Bool, Num, String or Identifier literal for an attribute value."); } if(runtimeAccess) addToAttributeGroup(compiler, NULL_VAL, key, value); } else if(match(compiler, TOKEN_LEFT_PAREN)) { ignoreNewlines(compiler); if(match(compiler, TOKEN_RIGHT_PAREN)) { error(compiler, "Expected attributes in group, group cannot be empty."); } else { while(peek(compiler) != TOKEN_RIGHT_PAREN) { consume(compiler, TOKEN_NAME, "Expect name for attribute key."); Value key = compiler->parser->previous.value; Value value = NULL_VAL; if(match(compiler, TOKEN_EQ)) { value = consumeLiteral(compiler, "Expect a Bool, Num, String or Identifier literal for an attribute value."); } if(runtimeAccess) addToAttributeGroup(compiler, group, key, value); ignoreNewlines(compiler); if(!match(compiler, TOKEN_COMMA)) break; ignoreNewlines(compiler); } ignoreNewlines(compiler); consume(compiler, TOKEN_RIGHT_PAREN, "Expected ')' after grouped attributes."); } } else { error(compiler, "Expect an equal, newline or grouping after an attribute key."); } } else { error(compiler, "Expect an attribute definition after #."); } consumeLine(compiler, "Expect newline after attribute."); return true; } return false; } // Compiles a method definition inside a class body. // // Returns `true` if it compiled successfully, or `false` if the method couldn't // be parsed. static bool method(Compiler* compiler, Variable classVariable) { // Parse any attributes before the method and store them if(matchAttribute(compiler)) { return method(compiler, classVariable); } // TODO: What about foreign constructors? bool isForeign = match(compiler, TOKEN_FOREIGN); bool isStatic = match(compiler, TOKEN_STATIC); compiler->enclosingClass->inStatic = isStatic; SignatureFn signatureFn = rules[compiler->parser->current.type].method; nextToken(compiler->parser); if (signatureFn == NULL) { error(compiler, "Expect method definition."); return false; } // Build the method signature. Signature signature = signatureFromToken(compiler, SIG_GETTER); compiler->enclosingClass->signature = &signature; Compiler methodCompiler; initCompiler(&methodCompiler, compiler->parser, compiler, true); // Compile the method signature. signatureFn(&methodCompiler, &signature); methodCompiler.isInitializer = signature.type == SIG_INITIALIZER; if (isStatic && signature.type == SIG_INITIALIZER) { error(compiler, "A constructor cannot be static."); } // Include the full signature in debug messages in stack traces. char fullSignature[MAX_METHOD_SIGNATURE]; int length; signatureToString(&signature, fullSignature, &length); // Copy any attributes the compiler collected into the enclosing class copyMethodAttributes(compiler, isForeign, isStatic, fullSignature, length); // Check for duplicate methods. Doesn't matter that it's already been // defined, error will discard bytecode anyway. // Check if the method table already contains this symbol int methodSymbol = declareMethod(compiler, &signature, fullSignature, length); if (isForeign) { // Define a constant for the signature. emitConstant(compiler, wrenNewStringLength(compiler->parser->vm, fullSignature, length)); // We don't need the function we started compiling in the parameter list // any more. methodCompiler.parser->vm->compiler = methodCompiler.parent; } else { consume(compiler, TOKEN_LEFT_BRACE, "Expect '{' to begin method body."); finishBody(&methodCompiler); endCompiler(&methodCompiler, fullSignature, length); } // Define the method. For a constructor, this defines the instance // initializer method. defineMethod(compiler, classVariable, isStatic, methodSymbol); if (signature.type == SIG_INITIALIZER) { // Also define a matching constructor method on the metaclass. signature.type = SIG_METHOD; int constructorSymbol = signatureSymbol(compiler, &signature); createConstructor(compiler, &signature, methodSymbol); defineMethod(compiler, classVariable, true, constructorSymbol); } return true; } // Compiles a class definition. Assumes the "class" token has already been // consumed (along with a possibly preceding "foreign" token). static void classDefinition(Compiler* compiler, bool isForeign) { // Create a variable to store the class in. Variable classVariable; classVariable.scope = compiler->scopeDepth == -1 ? SCOPE_MODULE : SCOPE_LOCAL; classVariable.index = declareNamedVariable(compiler); // Create shared class name value Value classNameString = wrenNewStringLength(compiler->parser->vm, compiler->parser->previous.start, compiler->parser->previous.length); // Create class name string to track method duplicates ObjString* className = AS_STRING(classNameString); // Make a string constant for the name. emitConstant(compiler, classNameString); // Load the superclass (if there is one). if (match(compiler, TOKEN_IS)) { parsePrecedence(compiler, PREC_CALL); } else { // Implicitly inherit from Object. loadCoreVariable(compiler, "Object"); } // Store a placeholder for the number of fields argument. We don't know the // count until we've compiled all the methods to see which fields are used. int numFieldsInstruction = -1; if (isForeign) { emitOp(compiler, CODE_FOREIGN_CLASS); } else { numFieldsInstruction = emitByteArg(compiler, CODE_CLASS, 255); } // Store it in its name. defineVariable(compiler, classVariable.index); // Push a local variable scope. Static fields in a class body are hoisted out // into local variables declared in this scope. Methods that use them will // have upvalues referencing them. pushScope(compiler); ClassInfo classInfo; classInfo.isForeign = isForeign; classInfo.name = className; // Allocate attribute maps if necessary. // A method will allocate the methods one if needed classInfo.classAttributes = compiler->attributes->count > 0 ? wrenNewMap(compiler->parser->vm) : NULL; classInfo.methodAttributes = NULL; // Copy any existing attributes into the class copyAttributes(compiler, classInfo.classAttributes); // Set up a symbol table for the class's fields. We'll initially compile // them to slots starting at zero. When the method is bound to the class, the // bytecode will be adjusted by [wrenBindMethod] to take inherited fields // into account. wrenSymbolTableInit(&classInfo.fields); // Set up symbol buffers to track duplicate static and instance methods. wrenIntBufferInit(&classInfo.methods); wrenIntBufferInit(&classInfo.staticMethods); compiler->enclosingClass = &classInfo; // Compile the method definitions. consume(compiler, TOKEN_LEFT_BRACE, "Expect '{' after class declaration."); matchLine(compiler); while (!match(compiler, TOKEN_RIGHT_BRACE)) { if (!method(compiler, classVariable)) break; // Don't require a newline after the last definition. if (match(compiler, TOKEN_RIGHT_BRACE)) break; consumeLine(compiler, "Expect newline after definition in class."); } // If any attributes are present, // instantiate a ClassAttributes instance for the class // and send it over to CODE_END_CLASS bool hasAttr = classInfo.classAttributes != NULL || classInfo.methodAttributes != NULL; if(hasAttr) { emitClassAttributes(compiler, &classInfo); loadVariable(compiler, classVariable); // At the moment, we don't have other uses for CODE_END_CLASS, // so we put it inside this condition. Later, we can always // emit it and use it as needed. emitOp(compiler, CODE_END_CLASS); } // Update the class with the number of fields. if (!isForeign) { compiler->fn->code.data[numFieldsInstruction] = (uint8_t)classInfo.fields.count; } // Clear symbol tables for tracking field and method names. wrenSymbolTableClear(compiler->parser->vm, &classInfo.fields); wrenIntBufferClear(compiler->parser->vm, &classInfo.methods); wrenIntBufferClear(compiler->parser->vm, &classInfo.staticMethods); compiler->enclosingClass = NULL; popScope(compiler); } // Compiles an "import" statement. // // An import compiles to a series of instructions. Given: // // import "foo" for Bar, Baz // // We compile a single IMPORT_MODULE "foo" instruction to load the module // itself. When that finishes executing the imported module, it leaves the // ObjModule in vm->lastModule. Then, for Bar and Baz, we: // // * Declare a variable in the current scope with that name. // * Emit an IMPORT_VARIABLE instruction to load the variable's value from the // other module. // * Compile the code to store that value in the variable in this scope. static void import(Compiler* compiler) { ignoreNewlines(compiler); consume(compiler, TOKEN_STRING, "Expect a string after 'import'."); int moduleConstant = addConstant(compiler, compiler->parser->previous.value); // Load the module. emitShortArg(compiler, CODE_IMPORT_MODULE, moduleConstant); // Discard the unused result value from calling the module body's closure. emitOp(compiler, CODE_POP); // The for clause is optional. if (!match(compiler, TOKEN_FOR)) return; // Compile the comma-separated list of variables to import. do { ignoreNewlines(compiler); consume(compiler, TOKEN_NAME, "Expect variable name."); // We need to hold onto the source variable, // in order to reference it in the import later Token sourceVariableToken = compiler->parser->previous; // Define a string constant for the original variable name. int sourceVariableConstant = addConstant(compiler, wrenNewStringLength(compiler->parser->vm, sourceVariableToken.start, sourceVariableToken.length)); // Store the symbol we care about for the variable int slot = -1; if(match(compiler, TOKEN_AS)) { //import "module" for Source as Dest //Use 'Dest' as the name by declaring a new variable for it. //This parses a name after the 'as' and defines it. slot = declareNamedVariable(compiler); } else { //import "module" for Source //Uses 'Source' as the name directly slot = declareVariable(compiler, &sourceVariableToken); } // Load the variable from the other module. emitShortArg(compiler, CODE_IMPORT_VARIABLE, sourceVariableConstant); // Store the result in the variable here. defineVariable(compiler, slot); } while (match(compiler, TOKEN_COMMA)); } // Compiles a "var" variable definition statement. static void variableDefinition(Compiler* compiler) { // Grab its name, but don't declare it yet. A (local) variable shouldn't be // in scope in its own initializer. consume(compiler, TOKEN_NAME, "Expect variable name."); Token nameToken = compiler->parser->previous; // Compile the initializer. if (match(compiler, TOKEN_EQ)) { ignoreNewlines(compiler); expression(compiler); } else { // Default initialize it to null. null(compiler, false); } // Now put it in scope. int symbol = declareVariable(compiler, &nameToken); defineVariable(compiler, symbol); } // Compiles a "definition". These are the statements that bind new variables. // They can only appear at the top level of a block and are prohibited in places // like the non-curly body of an if or while. void definition(Compiler* compiler) { if(matchAttribute(compiler)) { definition(compiler); return; } if (match(compiler, TOKEN_CLASS)) { classDefinition(compiler, false); return; } else if (match(compiler, TOKEN_FOREIGN)) { consume(compiler, TOKEN_CLASS, "Expect 'class' after 'foreign'."); classDefinition(compiler, true); return; } disallowAttributes(compiler); if (match(compiler, TOKEN_IMPORT)) { import(compiler); } else if (match(compiler, TOKEN_VAR)) { variableDefinition(compiler); } else { statement(compiler); } } ObjFn* wrenCompile(WrenVM* vm, ObjModule* module, const char* source, bool isExpression, bool printErrors) { // Skip the UTF-8 BOM if there is one. if (strncmp(source, "\xEF\xBB\xBF", 3) == 0) source += 3; Parser parser; parser.vm = vm; parser.module = module; parser.source = source; parser.tokenStart = source; parser.currentChar = source; parser.currentLine = 1; parser.numParens = 0; // Zero-init the current token. This will get copied to previous when // nextToken() is called below. parser.next.type = TOKEN_ERROR; parser.next.start = source; parser.next.length = 0; parser.next.line = 0; parser.next.value = UNDEFINED_VAL; parser.printErrors = printErrors; parser.hasError = false; // Read the first token into next nextToken(&parser); // Copy next -> current nextToken(&parser); int numExistingVariables = module->variables.count; Compiler compiler; initCompiler(&compiler, &parser, NULL, false); ignoreNewlines(&compiler); if (isExpression) { expression(&compiler); consume(&compiler, TOKEN_EOF, "Expect end of expression."); } else { while (!match(&compiler, TOKEN_EOF)) { definition(&compiler); // If there is no newline, it must be the end of file on the same line. if (!matchLine(&compiler)) { consume(&compiler, TOKEN_EOF, "Expect end of file."); break; } } emitOp(&compiler, CODE_END_MODULE); } emitOp(&compiler, CODE_RETURN); // See if there are any implicitly declared module-level variables that never // got an explicit definition. They will have values that are numbers // indicating the line where the variable was first used. for (int i = numExistingVariables; i < parser.module->variables.count; i++) { if (IS_NUM(parser.module->variables.data[i])) { // Synthesize a token for the original use site. parser.previous.type = TOKEN_NAME; parser.previous.start = parser.module->variableNames.data[i]->value; parser.previous.length = parser.module->variableNames.data[i]->length; parser.previous.line = (int)AS_NUM(parser.module->variables.data[i]); error(&compiler, "Variable is used but not defined."); } } return endCompiler(&compiler, "(script)", 8); } void wrenBindMethodCode(ObjClass* classObj, ObjFn* fn) { int ip = 0; for (;;) { Code instruction = (Code)fn->code.data[ip]; switch (instruction) { case CODE_LOAD_FIELD: case CODE_STORE_FIELD: case CODE_LOAD_FIELD_THIS: case CODE_STORE_FIELD_THIS: // Shift this class's fields down past the inherited ones. We don't // check for overflow here because we'll see if the number of fields // overflows when the subclass is created. fn->code.data[ip + 1] += classObj->superclass->numFields; break; case CODE_SUPER_0: case CODE_SUPER_1: case CODE_SUPER_2: case CODE_SUPER_3: case CODE_SUPER_4: case CODE_SUPER_5: case CODE_SUPER_6: case CODE_SUPER_7: case CODE_SUPER_8: case CODE_SUPER_9: case CODE_SUPER_10: case CODE_SUPER_11: case CODE_SUPER_12: case CODE_SUPER_13: case CODE_SUPER_14: case CODE_SUPER_15: case CODE_SUPER_16: { // Fill in the constant slot with a reference to the superclass. int constant = (fn->code.data[ip + 3] << 8) | fn->code.data[ip + 4]; fn->constants.data[constant] = OBJ_VAL(classObj->superclass); break; } case CODE_CLOSURE: { // Bind the nested closure too. int constant = (fn->code.data[ip + 1] << 8) | fn->code.data[ip + 2]; wrenBindMethodCode(classObj, AS_FN(fn->constants.data[constant])); break; } case CODE_END: return; default: // Other instructions are unaffected, so just skip over them. break; } ip += 1 + getByteCountForArguments(fn->code.data, fn->constants.data, ip); } } void wrenMarkCompiler(WrenVM* vm, Compiler* compiler) { wrenGrayValue(vm, compiler->parser->current.value); wrenGrayValue(vm, compiler->parser->previous.value); wrenGrayValue(vm, compiler->parser->next.value); // Walk up the parent chain to mark the outer compilers too. The VM only // tracks the innermost one. do { wrenGrayObj(vm, (Obj*)compiler->fn); wrenGrayObj(vm, (Obj*)compiler->constants); wrenGrayObj(vm, (Obj*)compiler->attributes); if (compiler->enclosingClass != NULL) { wrenBlackenSymbolTable(vm, &compiler->enclosingClass->fields); if(compiler->enclosingClass->methodAttributes != NULL) { wrenGrayObj(vm, (Obj*)compiler->enclosingClass->methodAttributes); } if(compiler->enclosingClass->classAttributes != NULL) { wrenGrayObj(vm, (Obj*)compiler->enclosingClass->classAttributes); } } compiler = compiler->parent; } while (compiler != NULL); } // Helpers for Attributes // Throw an error if any attributes were found preceding, // and clear the attributes so the error doesn't keep happening. static void disallowAttributes(Compiler* compiler) { if (compiler->numAttributes > 0) { error(compiler, "Attributes can only specified before a class or a method"); wrenMapClear(compiler->parser->vm, compiler->attributes); compiler->numAttributes = 0; } } // Add an attribute to a given group in the compiler attribues map static void addToAttributeGroup(Compiler* compiler, Value group, Value key, Value value) { WrenVM* vm = compiler->parser->vm; if(IS_OBJ(group)) wrenPushRoot(vm, AS_OBJ(group)); if(IS_OBJ(key)) wrenPushRoot(vm, AS_OBJ(key)); if(IS_OBJ(value)) wrenPushRoot(vm, AS_OBJ(value)); Value groupMapValue = wrenMapGet(compiler->attributes, group); if(IS_UNDEFINED(groupMapValue)) { groupMapValue = OBJ_VAL(wrenNewMap(vm)); wrenMapSet(vm, compiler->attributes, group, groupMapValue); } //we store them as a map per so we can maintain duplicate keys //group = { key:[value, ...], } ObjMap* groupMap = AS_MAP(groupMapValue); //var keyItems = group[key] //if(!keyItems) keyItems = group[key] = [] Value keyItemsValue = wrenMapGet(groupMap, key); if(IS_UNDEFINED(keyItemsValue)) { keyItemsValue = OBJ_VAL(wrenNewList(vm, 0)); wrenMapSet(vm, groupMap, key, keyItemsValue); } //keyItems.add(value) ObjList* keyItems = AS_LIST(keyItemsValue); wrenValueBufferWrite(vm, &keyItems->elements, value); if(IS_OBJ(group)) wrenPopRoot(vm); if(IS_OBJ(key)) wrenPopRoot(vm); if(IS_OBJ(value)) wrenPopRoot(vm); } // Emit the attributes in the give map onto the stack static void emitAttributes(Compiler* compiler, ObjMap* attributes) { // Instantiate a new map for the attributes loadCoreVariable(compiler, "Map"); callMethod(compiler, 0, "new()", 5); // The attributes are stored as group = { key:[value, value, ...] } // so our first level is the group map for(uint32_t groupIdx = 0; groupIdx < attributes->capacity; groupIdx++) { const MapEntry* groupEntry = &attributes->entries[groupIdx]; if(IS_UNDEFINED(groupEntry->key)) continue; //group key emitConstant(compiler, groupEntry->key); //group value is gonna be a map loadCoreVariable(compiler, "Map"); callMethod(compiler, 0, "new()", 5); ObjMap* groupItems = AS_MAP(groupEntry->value); for(uint32_t itemIdx = 0; itemIdx < groupItems->capacity; itemIdx++) { const MapEntry* itemEntry = &groupItems->entries[itemIdx]; if(IS_UNDEFINED(itemEntry->key)) continue; emitConstant(compiler, itemEntry->key); // Attribute key value, key = [] loadCoreVariable(compiler, "List"); callMethod(compiler, 0, "new()", 5); // Add the items to the key list ObjList* items = AS_LIST(itemEntry->value); for(int itemIdx = 0; itemIdx < items->elements.count; ++itemIdx) { emitConstant(compiler, items->elements.data[itemIdx]); callMethod(compiler, 1, "addCore_(_)", 11); } // Add the list to the map callMethod(compiler, 2, "addCore_(_,_)", 13); } // Add the key/value to the map callMethod(compiler, 2, "addCore_(_,_)", 13); } } // Methods are stored as method <-> attributes, so we have to have // an indirection to resolve for methods static void emitAttributeMethods(Compiler* compiler, ObjMap* attributes) { // Instantiate a new map for the attributes loadCoreVariable(compiler, "Map"); callMethod(compiler, 0, "new()", 5); for(uint32_t methodIdx = 0; methodIdx < attributes->capacity; methodIdx++) { const MapEntry* methodEntry = &attributes->entries[methodIdx]; if(IS_UNDEFINED(methodEntry->key)) continue; emitConstant(compiler, methodEntry->key); ObjMap* attributeMap = AS_MAP(methodEntry->value); emitAttributes(compiler, attributeMap); callMethod(compiler, 2, "addCore_(_,_)", 13); } } // Emit the final ClassAttributes that exists at runtime static void emitClassAttributes(Compiler* compiler, ClassInfo* classInfo) { loadCoreVariable(compiler, "ClassAttributes"); classInfo->classAttributes ? emitAttributes(compiler, classInfo->classAttributes) : null(compiler, false); classInfo->methodAttributes ? emitAttributeMethods(compiler, classInfo->methodAttributes) : null(compiler, false); callMethod(compiler, 2, "new(_,_)", 8); } // Copy the current attributes stored in the compiler into a destination map // This also resets the counter, since the intent is to consume the attributes static void copyAttributes(Compiler* compiler, ObjMap* into) { compiler->numAttributes = 0; if(compiler->attributes->count == 0) return; if(into == NULL) return; WrenVM* vm = compiler->parser->vm; // Note we copy the actual values as is since we'll take ownership // and clear the original map for(uint32_t attrIdx = 0; attrIdx < compiler->attributes->capacity; attrIdx++) { const MapEntry* attrEntry = &compiler->attributes->entries[attrIdx]; if(IS_UNDEFINED(attrEntry->key)) continue; wrenMapSet(vm, into, attrEntry->key, attrEntry->value); } wrenMapClear(vm, compiler->attributes); } // Copy the current attributes stored in the compiler into the method specific // attributes for the current enclosingClass. // This also resets the counter, since the intent is to consume the attributes static void copyMethodAttributes(Compiler* compiler, bool isForeign, bool isStatic, const char* fullSignature, int32_t length) { compiler->numAttributes = 0; if(compiler->attributes->count == 0) return; WrenVM* vm = compiler->parser->vm; // Make a map for this method to copy into ObjMap* methodAttr = wrenNewMap(vm); wrenPushRoot(vm, (Obj*)methodAttr); copyAttributes(compiler, methodAttr); // Include 'foreign static ' in front as needed int32_t fullLength = length; if(isForeign) fullLength += 8; if(isStatic) fullLength += 7; char fullSignatureWithPrefix[MAX_METHOD_SIGNATURE + 8 + 7]; const char* foreignPrefix = isForeign ? "foreign " : ""; const char* staticPrefix = isStatic ? "static " : ""; sprintf(fullSignatureWithPrefix, "%s%s%.*s", foreignPrefix, staticPrefix, length, fullSignature); fullSignatureWithPrefix[fullLength] = '\0'; if(compiler->enclosingClass->methodAttributes == NULL) { compiler->enclosingClass->methodAttributes = wrenNewMap(vm); } // Store the method attributes in the class map Value key = wrenNewStringLength(vm, fullSignatureWithPrefix, fullLength); wrenMapSet(vm, compiler->enclosingClass->methodAttributes, key, OBJ_VAL(methodAttr)); wrenPopRoot(vm); } // End file "wren_compiler.c" // Begin file "wren_primitive.c" // Begin file "wren_primitive.h" #ifndef wren_primitive_h #define wren_primitive_h // Binds a primitive method named [name] (in Wren) implemented using C function // [fn] to `ObjClass` [cls]. #define PRIMITIVE(cls, name, function) \ do \ { \ int symbol = wrenSymbolTableEnsure(vm, \ &vm->methodNames, name, strlen(name)); \ Method method; \ method.type = METHOD_PRIMITIVE; \ method.as.primitive = prim_##function; \ wrenBindMethod(vm, cls, symbol, method); \ } while (false) // Binds a primitive method named [name] (in Wren) implemented using C function // [fn] to `ObjClass` [cls], but as a FN call. #define FUNCTION_CALL(cls, name, function) \ do \ { \ int symbol = wrenSymbolTableEnsure(vm, \ &vm->methodNames, name, strlen(name)); \ Method method; \ method.type = METHOD_FUNCTION_CALL; \ method.as.primitive = prim_##function; \ wrenBindMethod(vm, cls, symbol, method); \ } while (false) // Defines a primitive method whose C function name is [name]. This abstracts // the actual type signature of a primitive function and makes it clear which C // functions are invoked as primitives. #define DEF_PRIMITIVE(name) \ static bool prim_##name(WrenVM* vm, Value* args) #define RETURN_VAL(value) \ do \ { \ args[0] = value; \ return true; \ } while (false) #define RETURN_OBJ(obj) RETURN_VAL(OBJ_VAL(obj)) #define RETURN_BOOL(value) RETURN_VAL(BOOL_VAL(value)) #define RETURN_FALSE RETURN_VAL(FALSE_VAL) #define RETURN_NULL RETURN_VAL(NULL_VAL) #define RETURN_NUM(value) RETURN_VAL(NUM_VAL(value)) #define RETURN_TRUE RETURN_VAL(TRUE_VAL) #define RETURN_ERROR(msg) \ do \ { \ vm->fiber->error = wrenNewStringLength(vm, msg, sizeof(msg) - 1); \ return false; \ } while (false) #define RETURN_ERROR_FMT(...) \ do \ { \ vm->fiber->error = wrenStringFormat(vm, __VA_ARGS__); \ return false; \ } while (false) // Validates that the given [arg] is a function. Returns true if it is. If not, // reports an error and returns false. bool validateFn(WrenVM* vm, Value arg, const char* argName); // Validates that the given [arg] is a Num. Returns true if it is. If not, // reports an error and returns false. bool validateNum(WrenVM* vm, Value arg, const char* argName); // Validates that [value] is an integer. Returns true if it is. If not, reports // an error and returns false. bool validateIntValue(WrenVM* vm, double value, const char* argName); // Validates that the given [arg] is an integer. Returns true if it is. If not, // reports an error and returns false. bool validateInt(WrenVM* vm, Value arg, const char* argName); // Validates that [arg] is a valid object for use as a map key. Returns true if // it is. If not, reports an error and returns false. bool validateKey(WrenVM* vm, Value arg); // Validates that the argument at [argIndex] is an integer within `[0, count)`. // Also allows negative indices which map backwards from the end. Returns the // valid positive index value. If invalid, reports an error and returns // `UINT32_MAX`. uint32_t validateIndex(WrenVM* vm, Value arg, uint32_t count, const char* argName); // Validates that the given [arg] is a String. Returns true if it is. If not, // reports an error and returns false. bool validateString(WrenVM* vm, Value arg, const char* argName); // Given a [range] and the [length] of the object being operated on, determines // the series of elements that should be chosen from the underlying object. // Handles ranges that count backwards from the end as well as negative ranges. // // Returns the index from which the range should start or `UINT32_MAX` if the // range is invalid. After calling, [length] will be updated with the number of // elements in the resulting sequence. [step] will be direction that the range // is going: `1` if the range is increasing from the start index or `-1` if the // range is decreasing. uint32_t calculateRange(WrenVM* vm, ObjRange* range, uint32_t* length, int* step); #endif // End file "wren_primitive.h" #include // Validates that [value] is an integer within `[0, count)`. Also allows // negative indices which map backwards from the end. Returns the valid positive // index value. If invalid, reports an error and returns `UINT32_MAX`. static uint32_t validateIndexValue(WrenVM* vm, uint32_t count, double value, const char* argName) { if (!validateIntValue(vm, value, argName)) return UINT32_MAX; // Negative indices count from the end. if (value < 0) value = count + value; // Check bounds. if (value >= 0 && value < count) return (uint32_t)value; vm->fiber->error = wrenStringFormat(vm, "$ out of bounds.", argName); return UINT32_MAX; } bool validateFn(WrenVM* vm, Value arg, const char* argName) { if (IS_CLOSURE(arg)) return true; RETURN_ERROR_FMT("$ must be a function.", argName); } bool validateNum(WrenVM* vm, Value arg, const char* argName) { if (IS_NUM(arg)) return true; RETURN_ERROR_FMT("$ must be a number.", argName); } bool validateIntValue(WrenVM* vm, double value, const char* argName) { if (trunc(value) == value) return true; RETURN_ERROR_FMT("$ must be an integer.", argName); } bool validateInt(WrenVM* vm, Value arg, const char* argName) { // Make sure it's a number first. if (!validateNum(vm, arg, argName)) return false; return validateIntValue(vm, AS_NUM(arg), argName); } bool validateKey(WrenVM* vm, Value arg) { if (wrenMapIsValidKey(arg)) return true; RETURN_ERROR("Key must be a value type."); } uint32_t validateIndex(WrenVM* vm, Value arg, uint32_t count, const char* argName) { if (!validateNum(vm, arg, argName)) return UINT32_MAX; return validateIndexValue(vm, count, AS_NUM(arg), argName); } bool validateString(WrenVM* vm, Value arg, const char* argName) { if (IS_STRING(arg)) return true; RETURN_ERROR_FMT("$ must be a string.", argName); } uint32_t calculateRange(WrenVM* vm, ObjRange* range, uint32_t* length, int* step) { *step = 0; // Edge case: an empty range is allowed at the end of a sequence. This way, // list[0..-1] and list[0...list.count] can be used to copy a list even when // empty. if (range->from == *length && range->to == (range->isInclusive ? -1.0 : (double)*length)) { *length = 0; return 0; } uint32_t from = validateIndexValue(vm, *length, range->from, "Range start"); if (from == UINT32_MAX) return UINT32_MAX; // Bounds check the end manually to handle exclusive ranges. double value = range->to; if (!validateIntValue(vm, value, "Range end")) return UINT32_MAX; // Negative indices count from the end. if (value < 0) value = *length + value; // Convert the exclusive range to an inclusive one. if (!range->isInclusive) { // An exclusive range with the same start and end points is empty. if (value == from) { *length = 0; return from; } // Shift the endpoint to make it inclusive, handling both increasing and // decreasing ranges. value += value >= from ? -1 : 1; } // Check bounds. if (value < 0 || value >= *length) { vm->fiber->error = CONST_STRING(vm, "Range end out of bounds."); return UINT32_MAX; } uint32_t to = (uint32_t)value; *length = abs((int)(from - to)) + 1; *step = from < to ? 1 : -1; return from; } // End file "wren_primitive.c" // Begin file "wren_core.c" #include #include #include #include #include #include // Begin file "wren_core.h" #ifndef wren_core_h #define wren_core_h // This module defines the built-in classes and their primitives methods that // are implemented directly in C code. Some languages try to implement as much // of the core module itself in the primary language instead of in the host // language. // // With Wren, we try to do as much of it in C as possible. Primitive methods // are always faster than code written in Wren, and it minimizes startup time // since we don't have to parse, compile, and execute Wren code. // // There is one limitation, though. Methods written in C cannot call Wren ones. // They can only be the top of the callstack, and immediately return. This // makes it difficult to have primitive methods that rely on polymorphic // behavior. For example, `System.print` should call `toString` on its argument, // including user-defined `toString` methods on user-defined classes. void wrenInitializeCore(WrenVM* vm); #endif // End file "wren_core.h" // Begin file "wren_core.wren.inc" // Generated automatically from src/vm/wren_core.wren. Do not edit. static const char* coreModuleSource = "class Bool {}\n" "class Fiber {}\n" "class Fn {}\n" "class Null {}\n" "class Num {}\n" "\n" "class Sequence {\n" " all(f) {\n" " var result = true\n" " for (element in this) {\n" " result = f.call(element)\n" " if (!result) return result\n" " }\n" " return result\n" " }\n" "\n" " any(f) {\n" " var result = false\n" " for (element in this) {\n" " result = f.call(element)\n" " if (result) return result\n" " }\n" " return result\n" " }\n" "\n" " contains(element) {\n" " for (item in this) {\n" " if (element == item) return true\n" " }\n" " return false\n" " }\n" "\n" " count {\n" " var result = 0\n" " for (element in this) {\n" " result = result + 1\n" " }\n" " return result\n" " }\n" "\n" " count(f) {\n" " var result = 0\n" " for (element in this) {\n" " if (f.call(element)) result = result + 1\n" " }\n" " return result\n" " }\n" "\n" " each(f) {\n" " for (element in this) {\n" " f.call(element)\n" " }\n" " }\n" "\n" " isEmpty { iterate(null) ? false : true }\n" "\n" " map(transformation) { MapSequence.new(this, transformation) }\n" "\n" " skip(count) {\n" " if (!(count is Num) || !count.isInteger || count < 0) {\n" " Fiber.abort(\"Count must be a non-negative integer.\")\n" " }\n" "\n" " return SkipSequence.new(this, count)\n" " }\n" "\n" " take(count) {\n" " if (!(count is Num) || !count.isInteger || count < 0) {\n" " Fiber.abort(\"Count must be a non-negative integer.\")\n" " }\n" "\n" " return TakeSequence.new(this, count)\n" " }\n" "\n" " where(predicate) { WhereSequence.new(this, predicate) }\n" "\n" " reduce(acc, f) {\n" " for (element in this) {\n" " acc = f.call(acc, element)\n" " }\n" " return acc\n" " }\n" "\n" " reduce(f) {\n" " var iter = iterate(null)\n" " if (!iter) Fiber.abort(\"Can't reduce an empty sequence.\")\n" "\n" " // Seed with the first element.\n" " var result = iteratorValue(iter)\n" " while (iter = iterate(iter)) {\n" " result = f.call(result, iteratorValue(iter))\n" " }\n" "\n" " return result\n" " }\n" "\n" " join() { join(\"\") }\n" "\n" " join(sep) {\n" " var first = true\n" " var result = \"\"\n" "\n" " for (element in this) {\n" " if (!first) result = result + sep\n" " first = false\n" " result = result + element.toString\n" " }\n" "\n" " return result\n" " }\n" "\n" " toList {\n" " var result = List.new()\n" " for (element in this) {\n" " result.add(element)\n" " }\n" " return result\n" " }\n" "}\n" "\n" "class MapSequence is Sequence {\n" " construct new(sequence, fn) {\n" " _sequence = sequence\n" " _fn = fn\n" " }\n" "\n" " iterate(iterator) { _sequence.iterate(iterator) }\n" " iteratorValue(iterator) { _fn.call(_sequence.iteratorValue(iterator)) }\n" "}\n" "\n" "class SkipSequence is Sequence {\n" " construct new(sequence, count) {\n" " _sequence = sequence\n" " _count = count\n" " }\n" "\n" " iterate(iterator) {\n" " if (iterator) {\n" " return _sequence.iterate(iterator)\n" " } else {\n" " iterator = _sequence.iterate(iterator)\n" " var count = _count\n" " while (count > 0 && iterator) {\n" " iterator = _sequence.iterate(iterator)\n" " count = count - 1\n" " }\n" " return iterator\n" " }\n" " }\n" "\n" " iteratorValue(iterator) { _sequence.iteratorValue(iterator) }\n" "}\n" "\n" "class TakeSequence is Sequence {\n" " construct new(sequence, count) {\n" " _sequence = sequence\n" " _count = count\n" " }\n" "\n" " iterate(iterator) {\n" " if (!iterator) _taken = 1 else _taken = _taken + 1\n" " return _taken > _count ? null : _sequence.iterate(iterator)\n" " }\n" "\n" " iteratorValue(iterator) { _sequence.iteratorValue(iterator) }\n" "}\n" "\n" "class WhereSequence is Sequence {\n" " construct new(sequence, fn) {\n" " _sequence = sequence\n" " _fn = fn\n" " }\n" "\n" " iterate(iterator) {\n" " while (iterator = _sequence.iterate(iterator)) {\n" " if (_fn.call(_sequence.iteratorValue(iterator))) break\n" " }\n" " return iterator\n" " }\n" "\n" " iteratorValue(iterator) { _sequence.iteratorValue(iterator) }\n" "}\n" "\n" "class String is Sequence {\n" " bytes { StringByteSequence.new(this) }\n" " codePoints { StringCodePointSequence.new(this) }\n" "\n" " split(delimiter) {\n" " if (!(delimiter is String) || delimiter.isEmpty) {\n" " Fiber.abort(\"Delimiter must be a non-empty string.\")\n" " }\n" "\n" " var result = []\n" "\n" " var last = 0\n" " var index = 0\n" "\n" " var delimSize = delimiter.byteCount_\n" " var size = byteCount_\n" "\n" " while (last < size && (index = indexOf(delimiter, last)) != -1) {\n" " result.add(this[last...index])\n" " last = index + delimSize\n" " }\n" "\n" " if (last < size) {\n" " result.add(this[last..-1])\n" " } else {\n" " result.add(\"\")\n" " }\n" " return result\n" " }\n" "\n" " replace(from, to) {\n" " if (!(from is String) || from.isEmpty) {\n" " Fiber.abort(\"From must be a non-empty string.\")\n" " } else if (!(to is String)) {\n" " Fiber.abort(\"To must be a string.\")\n" " }\n" "\n" " var result = \"\"\n" "\n" " var last = 0\n" " var index = 0\n" "\n" " var fromSize = from.byteCount_\n" " var size = byteCount_\n" "\n" " while (last < size && (index = indexOf(from, last)) != -1) {\n" " result = result + this[last...index] + to\n" " last = index + fromSize\n" " }\n" "\n" " if (last < size) result = result + this[last..-1]\n" "\n" " return result\n" " }\n" "\n" " trim() { trim_(\"\\t\\r\\n \", true, true) }\n" " trim(chars) { trim_(chars, true, true) }\n" " trimEnd() { trim_(\"\\t\\r\\n \", false, true) }\n" " trimEnd(chars) { trim_(chars, false, true) }\n" " trimStart() { trim_(\"\\t\\r\\n \", true, false) }\n" " trimStart(chars) { trim_(chars, true, false) }\n" "\n" " trim_(chars, trimStart, trimEnd) {\n" " if (!(chars is String)) {\n" " Fiber.abort(\"Characters must be a string.\")\n" " }\n" "\n" " var codePoints = chars.codePoints.toList\n" "\n" " var start\n" " if (trimStart) {\n" " while (start = iterate(start)) {\n" " if (!codePoints.contains(codePointAt_(start))) break\n" " }\n" "\n" " if (start == false) return \"\"\n" " } else {\n" " start = 0\n" " }\n" "\n" " var end\n" " if (trimEnd) {\n" " end = byteCount_ - 1\n" " while (end >= start) {\n" " var codePoint = codePointAt_(end)\n" " if (codePoint != -1 && !codePoints.contains(codePoint)) break\n" " end = end - 1\n" " }\n" "\n" " if (end < start) return \"\"\n" " } else {\n" " end = -1\n" " }\n" "\n" " return this[start..end]\n" " }\n" "\n" " *(count) {\n" " if (!(count is Num) || !count.isInteger || count < 0) {\n" " Fiber.abort(\"Count must be a non-negative integer.\")\n" " }\n" "\n" " var result = \"\"\n" " for (i in 0...count) {\n" " result = result + this\n" " }\n" " return result\n" " }\n" "}\n" "\n" "class StringByteSequence is Sequence {\n" " construct new(string) {\n" " _string = string\n" " }\n" "\n" " [index] { _string.byteAt_(index) }\n" " iterate(iterator) { _string.iterateByte_(iterator) }\n" " iteratorValue(iterator) { _string.byteAt_(iterator) }\n" "\n" " count { _string.byteCount_ }\n" "}\n" "\n" "class StringCodePointSequence is Sequence {\n" " construct new(string) {\n" " _string = string\n" " }\n" "\n" " [index] { _string.codePointAt_(index) }\n" " iterate(iterator) { _string.iterate(iterator) }\n" " iteratorValue(iterator) { _string.codePointAt_(iterator) }\n" "\n" " count { _string.count }\n" "}\n" "\n" "class List is Sequence {\n" " addAll(other) {\n" " for (element in other) {\n" " add(element)\n" " }\n" " return other\n" " }\n" "\n" " sort() { sort {|low, high| low < high } }\n" "\n" " sort(comparer) {\n" " if (!(comparer is Fn)) {\n" " Fiber.abort(\"Comparer must be a function.\")\n" " }\n" " quicksort_(0, count - 1, comparer)\n" " return this\n" " }\n" "\n" " quicksort_(low, high, comparer) {\n" " if (low < high) {\n" " var p = partition_(low, high, comparer)\n" " quicksort_(low, p - 1, comparer)\n" " quicksort_(p + 1, high, comparer)\n" " }\n" " }\n" "\n" " partition_(low, high, comparer) {\n" " var p = this[high]\n" " var i = low - 1\n" " for (j in low..(high-1)) {\n" " if (comparer.call(this[j], p)) { \n" " i = i + 1\n" " var t = this[i]\n" " this[i] = this[j]\n" " this[j] = t\n" " }\n" " }\n" " var t = this[i+1]\n" " this[i+1] = this[high]\n" " this[high] = t\n" " return i+1\n" " }\n" "\n" " toString { \"[%(join(\", \"))]\" }\n" "\n" " +(other) {\n" " var result = this[0..-1]\n" " for (element in other) {\n" " result.add(element)\n" " }\n" " return result\n" " }\n" "\n" " *(count) {\n" " if (!(count is Num) || !count.isInteger || count < 0) {\n" " Fiber.abort(\"Count must be a non-negative integer.\")\n" " }\n" "\n" " var result = []\n" " for (i in 0...count) {\n" " result.addAll(this)\n" " }\n" " return result\n" " }\n" "}\n" "\n" "class Map is Sequence {\n" " keys { MapKeySequence.new(this) }\n" " values { MapValueSequence.new(this) }\n" "\n" " toString {\n" " var first = true\n" " var result = \"{\"\n" "\n" " for (key in keys) {\n" " if (!first) result = result + \", \"\n" " first = false\n" " result = result + \"%(key): %(this[key])\"\n" " }\n" "\n" " return result + \"}\"\n" " }\n" "\n" " iteratorValue(iterator) {\n" " return MapEntry.new(\n" " keyIteratorValue_(iterator),\n" " valueIteratorValue_(iterator))\n" " }\n" "}\n" "\n" "class MapEntry {\n" " construct new(key, value) {\n" " _key = key\n" " _value = value\n" " }\n" "\n" " key { _key }\n" " value { _value }\n" "\n" " toString { \"%(_key):%(_value)\" }\n" "}\n" "\n" "class MapKeySequence is Sequence {\n" " construct new(map) {\n" " _map = map\n" " }\n" "\n" " iterate(n) { _map.iterate(n) }\n" " iteratorValue(iterator) { _map.keyIteratorValue_(iterator) }\n" "}\n" "\n" "class MapValueSequence is Sequence {\n" " construct new(map) {\n" " _map = map\n" " }\n" "\n" " iterate(n) { _map.iterate(n) }\n" " iteratorValue(iterator) { _map.valueIteratorValue_(iterator) }\n" "}\n" "\n" "class Range is Sequence {}\n" "\n" "class System {\n" " static print() {\n" " writeString_(\"\\n\")\n" " }\n" "\n" " static print(obj) {\n" " writeObject_(obj)\n" " writeString_(\"\\n\")\n" " return obj\n" " }\n" "\n" " static printAll(sequence) {\n" " for (object in sequence) writeObject_(object)\n" " writeString_(\"\\n\")\n" " }\n" "\n" " static write(obj) {\n" " writeObject_(obj)\n" " return obj\n" " }\n" "\n" " static writeAll(sequence) {\n" " for (object in sequence) writeObject_(object)\n" " }\n" "\n" " static writeObject_(obj) {\n" " var string = obj.toString\n" " if (string is String) {\n" " writeString_(string)\n" " } else {\n" " writeString_(\"[invalid toString]\")\n" " }\n" " }\n" "}\n" "\n" "class ClassAttributes {\n" " self { _attributes }\n" " methods { _methods }\n" " construct new(attributes, methods) {\n" " _attributes = attributes\n" " _methods = methods\n" " }\n" " toString { \"attributes:%(_attributes) methods:%(_methods)\" }\n" "}\n"; // End file "wren_core.wren.inc" DEF_PRIMITIVE(bool_not) { RETURN_BOOL(!AS_BOOL(args[0])); } DEF_PRIMITIVE(bool_toString) { if (AS_BOOL(args[0])) { RETURN_VAL(CONST_STRING(vm, "true")); } else { RETURN_VAL(CONST_STRING(vm, "false")); } } DEF_PRIMITIVE(class_name) { RETURN_OBJ(AS_CLASS(args[0])->name); } DEF_PRIMITIVE(class_supertype) { ObjClass* classObj = AS_CLASS(args[0]); // Object has no superclass. if (classObj->superclass == NULL) RETURN_NULL; RETURN_OBJ(classObj->superclass); } DEF_PRIMITIVE(class_toString) { RETURN_OBJ(AS_CLASS(args[0])->name); } DEF_PRIMITIVE(class_attributes) { RETURN_VAL(AS_CLASS(args[0])->attributes); } DEF_PRIMITIVE(fiber_new) { if (!validateFn(vm, args[1], "Argument")) return false; ObjClosure* closure = AS_CLOSURE(args[1]); if (closure->fn->arity > 1) { RETURN_ERROR("Function cannot take more than one parameter."); } RETURN_OBJ(wrenNewFiber(vm, closure)); } DEF_PRIMITIVE(fiber_abort) { vm->fiber->error = args[1]; // If the error is explicitly null, it's not really an abort. return IS_NULL(args[1]); } // Transfer execution to [fiber] coming from the current fiber whose stack has // [args]. // // [isCall] is true if [fiber] is being called and not transferred. // // [hasValue] is true if a value in [args] is being passed to the new fiber. // Otherwise, `null` is implicitly being passed. static bool runFiber(WrenVM* vm, ObjFiber* fiber, Value* args, bool isCall, bool hasValue, const char* verb) { if (wrenHasError(fiber)) { RETURN_ERROR_FMT("Cannot $ an aborted fiber.", verb); } if (isCall) { // You can't call a called fiber, but you can transfer directly to it, // which is why this check is gated on `isCall`. This way, after resuming a // suspended fiber, it will run and then return to the fiber that called it // and so on. if (fiber->caller != NULL) RETURN_ERROR("Fiber has already been called."); if (fiber->state == FIBER_ROOT) RETURN_ERROR("Cannot call root fiber."); // Remember who ran it. fiber->caller = vm->fiber; } if (fiber->numFrames == 0) { RETURN_ERROR_FMT("Cannot $ a finished fiber.", verb); } // When the calling fiber resumes, we'll store the result of the call in its // stack. If the call has two arguments (the fiber and the value), we only // need one slot for the result, so discard the other slot now. if (hasValue) vm->fiber->stackTop--; if (fiber->numFrames == 1 && fiber->frames[0].ip == fiber->frames[0].closure->fn->code.data) { // The fiber is being started for the first time. If its function takes a // parameter, bind an argument to it. if (fiber->frames[0].closure->fn->arity == 1) { fiber->stackTop[0] = hasValue ? args[1] : NULL_VAL; fiber->stackTop++; } } else { // The fiber is being resumed, make yield() or transfer() return the result. fiber->stackTop[-1] = hasValue ? args[1] : NULL_VAL; } vm->fiber = fiber; return false; } DEF_PRIMITIVE(fiber_call) { return runFiber(vm, AS_FIBER(args[0]), args, true, false, "call"); } DEF_PRIMITIVE(fiber_call1) { return runFiber(vm, AS_FIBER(args[0]), args, true, true, "call"); } DEF_PRIMITIVE(fiber_current) { RETURN_OBJ(vm->fiber); } DEF_PRIMITIVE(fiber_error) { RETURN_VAL(AS_FIBER(args[0])->error); } DEF_PRIMITIVE(fiber_isDone) { ObjFiber* runFiber = AS_FIBER(args[0]); RETURN_BOOL(runFiber->numFrames == 0 || wrenHasError(runFiber)); } DEF_PRIMITIVE(fiber_suspend) { // Switching to a null fiber tells the interpreter to stop and exit. vm->fiber = NULL; vm->apiStack = NULL; return false; } DEF_PRIMITIVE(fiber_transfer) { return runFiber(vm, AS_FIBER(args[0]), args, false, false, "transfer to"); } DEF_PRIMITIVE(fiber_transfer1) { return runFiber(vm, AS_FIBER(args[0]), args, false, true, "transfer to"); } DEF_PRIMITIVE(fiber_transferError) { runFiber(vm, AS_FIBER(args[0]), args, false, true, "transfer to"); vm->fiber->error = args[1]; return false; } DEF_PRIMITIVE(fiber_try) { runFiber(vm, AS_FIBER(args[0]), args, true, false, "try"); // If we're switching to a valid fiber to try, remember that we're trying it. if (!wrenHasError(vm->fiber)) vm->fiber->state = FIBER_TRY; return false; } DEF_PRIMITIVE(fiber_try1) { runFiber(vm, AS_FIBER(args[0]), args, true, true, "try"); // If we're switching to a valid fiber to try, remember that we're trying it. if (!wrenHasError(vm->fiber)) vm->fiber->state = FIBER_TRY; return false; } DEF_PRIMITIVE(fiber_yield) { ObjFiber* current = vm->fiber; vm->fiber = current->caller; // Unhook this fiber from the one that called it. current->caller = NULL; current->state = FIBER_OTHER; if (vm->fiber != NULL) { // Make the caller's run method return null. vm->fiber->stackTop[-1] = NULL_VAL; } return false; } DEF_PRIMITIVE(fiber_yield1) { ObjFiber* current = vm->fiber; vm->fiber = current->caller; // Unhook this fiber from the one that called it. current->caller = NULL; current->state = FIBER_OTHER; if (vm->fiber != NULL) { // Make the caller's run method return the argument passed to yield. vm->fiber->stackTop[-1] = args[1]; // When the yielding fiber resumes, we'll store the result of the yield // call in its stack. Since Fiber.yield(value) has two arguments (the Fiber // class and the value) and we only need one slot for the result, discard // the other slot now. current->stackTop--; } return false; } DEF_PRIMITIVE(fn_new) { if (!validateFn(vm, args[1], "Argument")) return false; // The block argument is already a function, so just return it. RETURN_VAL(args[1]); } DEF_PRIMITIVE(fn_arity) { RETURN_NUM(AS_CLOSURE(args[0])->fn->arity); } static void call_fn(WrenVM* vm, Value* args, int numArgs) { // +1 to include the function itself. wrenCallFunction(vm, vm->fiber, AS_CLOSURE(args[0]), numArgs + 1); } #define DEF_FN_CALL(numArgs) \ DEF_PRIMITIVE(fn_call##numArgs) \ { \ call_fn(vm, args, numArgs); \ return false; \ } DEF_FN_CALL(0) DEF_FN_CALL(1) DEF_FN_CALL(2) DEF_FN_CALL(3) DEF_FN_CALL(4) DEF_FN_CALL(5) DEF_FN_CALL(6) DEF_FN_CALL(7) DEF_FN_CALL(8) DEF_FN_CALL(9) DEF_FN_CALL(10) DEF_FN_CALL(11) DEF_FN_CALL(12) DEF_FN_CALL(13) DEF_FN_CALL(14) DEF_FN_CALL(15) DEF_FN_CALL(16) DEF_PRIMITIVE(fn_toString) { RETURN_VAL(CONST_STRING(vm, "")); } // Creates a new list of size args[1], with all elements initialized to args[2]. DEF_PRIMITIVE(list_filled) { if (!validateInt(vm, args[1], "Size")) return false; if (AS_NUM(args[1]) < 0) RETURN_ERROR("Size cannot be negative."); uint32_t size = (uint32_t)AS_NUM(args[1]); ObjList* list = wrenNewList(vm, size); for (uint32_t i = 0; i < size; i++) { list->elements.data[i] = args[2]; } RETURN_OBJ(list); } DEF_PRIMITIVE(list_new) { RETURN_OBJ(wrenNewList(vm, 0)); } DEF_PRIMITIVE(list_add) { wrenValueBufferWrite(vm, &AS_LIST(args[0])->elements, args[1]); RETURN_VAL(args[1]); } // Adds an element to the list and then returns the list itself. This is called // by the compiler when compiling list literals instead of using add() to // minimize stack churn. DEF_PRIMITIVE(list_addCore) { wrenValueBufferWrite(vm, &AS_LIST(args[0])->elements, args[1]); // Return the list. RETURN_VAL(args[0]); } DEF_PRIMITIVE(list_clear) { wrenValueBufferClear(vm, &AS_LIST(args[0])->elements); RETURN_NULL; } DEF_PRIMITIVE(list_count) { RETURN_NUM(AS_LIST(args[0])->elements.count); } DEF_PRIMITIVE(list_insert) { ObjList* list = AS_LIST(args[0]); // count + 1 here so you can "insert" at the very end. uint32_t index = validateIndex(vm, args[1], list->elements.count + 1, "Index"); if (index == UINT32_MAX) return false; wrenListInsert(vm, list, args[2], index); RETURN_VAL(args[2]); } DEF_PRIMITIVE(list_iterate) { ObjList* list = AS_LIST(args[0]); // If we're starting the iteration, return the first index. if (IS_NULL(args[1])) { if (list->elements.count == 0) RETURN_FALSE; RETURN_NUM(0); } if (!validateInt(vm, args[1], "Iterator")) return false; // Stop if we're out of bounds. double index = AS_NUM(args[1]); if (index < 0 || index >= list->elements.count - 1) RETURN_FALSE; // Otherwise, move to the next index. RETURN_NUM(index + 1); } DEF_PRIMITIVE(list_iteratorValue) { ObjList* list = AS_LIST(args[0]); uint32_t index = validateIndex(vm, args[1], list->elements.count, "Iterator"); if (index == UINT32_MAX) return false; RETURN_VAL(list->elements.data[index]); } DEF_PRIMITIVE(list_removeAt) { ObjList* list = AS_LIST(args[0]); uint32_t index = validateIndex(vm, args[1], list->elements.count, "Index"); if (index == UINT32_MAX) return false; RETURN_VAL(wrenListRemoveAt(vm, list, index)); } DEF_PRIMITIVE(list_removeValue) { ObjList* list = AS_LIST(args[0]); int index = wrenListIndexOf(vm, list, args[1]); if(index == -1) RETURN_NULL; RETURN_VAL(wrenListRemoveAt(vm, list, index)); } DEF_PRIMITIVE(list_indexOf) { ObjList* list = AS_LIST(args[0]); RETURN_NUM(wrenListIndexOf(vm, list, args[1])); } DEF_PRIMITIVE(list_swap) { ObjList* list = AS_LIST(args[0]); uint32_t indexA = validateIndex(vm, args[1], list->elements.count, "Index 0"); if (indexA == UINT32_MAX) return false; uint32_t indexB = validateIndex(vm, args[2], list->elements.count, "Index 1"); if (indexB == UINT32_MAX) return false; Value a = list->elements.data[indexA]; list->elements.data[indexA] = list->elements.data[indexB]; list->elements.data[indexB] = a; RETURN_NULL; } DEF_PRIMITIVE(list_subscript) { ObjList* list = AS_LIST(args[0]); if (IS_NUM(args[1])) { uint32_t index = validateIndex(vm, args[1], list->elements.count, "Subscript"); if (index == UINT32_MAX) return false; RETURN_VAL(list->elements.data[index]); } if (!IS_RANGE(args[1])) { RETURN_ERROR("Subscript must be a number or a range."); } int step; uint32_t count = list->elements.count; uint32_t start = calculateRange(vm, AS_RANGE(args[1]), &count, &step); if (start == UINT32_MAX) return false; ObjList* result = wrenNewList(vm, count); for (uint32_t i = 0; i < count; i++) { result->elements.data[i] = list->elements.data[start + i * step]; } RETURN_OBJ(result); } DEF_PRIMITIVE(list_subscriptSetter) { ObjList* list = AS_LIST(args[0]); uint32_t index = validateIndex(vm, args[1], list->elements.count, "Subscript"); if (index == UINT32_MAX) return false; list->elements.data[index] = args[2]; RETURN_VAL(args[2]); } DEF_PRIMITIVE(map_new) { RETURN_OBJ(wrenNewMap(vm)); } DEF_PRIMITIVE(map_subscript) { if (!validateKey(vm, args[1])) return false; ObjMap* map = AS_MAP(args[0]); Value value = wrenMapGet(map, args[1]); if (IS_UNDEFINED(value)) RETURN_NULL; RETURN_VAL(value); } DEF_PRIMITIVE(map_subscriptSetter) { if (!validateKey(vm, args[1])) return false; wrenMapSet(vm, AS_MAP(args[0]), args[1], args[2]); RETURN_VAL(args[2]); } // Adds an entry to the map and then returns the map itself. This is called by // the compiler when compiling map literals instead of using [_]=(_) to // minimize stack churn. DEF_PRIMITIVE(map_addCore) { if (!validateKey(vm, args[1])) return false; wrenMapSet(vm, AS_MAP(args[0]), args[1], args[2]); // Return the map itself. RETURN_VAL(args[0]); } DEF_PRIMITIVE(map_clear) { wrenMapClear(vm, AS_MAP(args[0])); RETURN_NULL; } DEF_PRIMITIVE(map_containsKey) { if (!validateKey(vm, args[1])) return false; RETURN_BOOL(!IS_UNDEFINED(wrenMapGet(AS_MAP(args[0]), args[1]))); } DEF_PRIMITIVE(map_count) { RETURN_NUM(AS_MAP(args[0])->count); } DEF_PRIMITIVE(map_iterate) { ObjMap* map = AS_MAP(args[0]); if (map->count == 0) RETURN_FALSE; // If we're starting the iteration, start at the first used entry. uint32_t index = 0; // Otherwise, start one past the last entry we stopped at. if (!IS_NULL(args[1])) { if (!validateInt(vm, args[1], "Iterator")) return false; if (AS_NUM(args[1]) < 0) RETURN_FALSE; index = (uint32_t)AS_NUM(args[1]); if (index >= map->capacity) RETURN_FALSE; // Advance the iterator. index++; } // Find a used entry, if any. for (; index < map->capacity; index++) { if (!IS_UNDEFINED(map->entries[index].key)) RETURN_NUM(index); } // If we get here, walked all of the entries. RETURN_FALSE; } DEF_PRIMITIVE(map_remove) { if (!validateKey(vm, args[1])) return false; RETURN_VAL(wrenMapRemoveKey(vm, AS_MAP(args[0]), args[1])); } DEF_PRIMITIVE(map_keyIteratorValue) { ObjMap* map = AS_MAP(args[0]); uint32_t index = validateIndex(vm, args[1], map->capacity, "Iterator"); if (index == UINT32_MAX) return false; MapEntry* entry = &map->entries[index]; if (IS_UNDEFINED(entry->key)) { RETURN_ERROR("Invalid map iterator."); } RETURN_VAL(entry->key); } DEF_PRIMITIVE(map_valueIteratorValue) { ObjMap* map = AS_MAP(args[0]); uint32_t index = validateIndex(vm, args[1], map->capacity, "Iterator"); if (index == UINT32_MAX) return false; MapEntry* entry = &map->entries[index]; if (IS_UNDEFINED(entry->key)) { RETURN_ERROR("Invalid map iterator."); } RETURN_VAL(entry->value); } DEF_PRIMITIVE(null_not) { RETURN_VAL(TRUE_VAL); } DEF_PRIMITIVE(null_toString) { RETURN_VAL(CONST_STRING(vm, "null")); } DEF_PRIMITIVE(num_fromString) { if (!validateString(vm, args[1], "Argument")) return false; ObjString* string = AS_STRING(args[1]); // Corner case: Can't parse an empty string. if (string->length == 0) RETURN_NULL; errno = 0; char* end; double number = strtod(string->value, &end); // Skip past any trailing whitespace. while (*end != '\0' && isspace((unsigned char)*end)) end++; if (errno == ERANGE) RETURN_ERROR("Number literal is too large."); // We must have consumed the entire string. Otherwise, it contains non-number // characters and we can't parse it. if (end < string->value + string->length) RETURN_NULL; RETURN_NUM(number); } // Defines a primitive on Num that calls infix [op] and returns [type]. #define DEF_NUM_CONSTANT(name, value) \ DEF_PRIMITIVE(num_##name) \ { \ RETURN_NUM(value); \ } DEF_NUM_CONSTANT(infinity, INFINITY) DEF_NUM_CONSTANT(nan, WREN_DOUBLE_NAN) DEF_NUM_CONSTANT(pi, 3.14159265358979323846264338327950288) DEF_NUM_CONSTANT(tau, 6.28318530717958647692528676655900577) DEF_NUM_CONSTANT(largest, DBL_MAX) DEF_NUM_CONSTANT(smallest, DBL_MIN) DEF_NUM_CONSTANT(maxSafeInteger, 9007199254740991.0) DEF_NUM_CONSTANT(minSafeInteger, -9007199254740991.0) // Defines a primitive on Num that calls infix [op] and returns [type]. #define DEF_NUM_INFIX(name, op, type) \ DEF_PRIMITIVE(num_##name) \ { \ if (!validateNum(vm, args[1], "Right operand")) return false; \ RETURN_##type(AS_NUM(args[0]) op AS_NUM(args[1])); \ } DEF_NUM_INFIX(minus, -, NUM) DEF_NUM_INFIX(plus, +, NUM) DEF_NUM_INFIX(multiply, *, NUM) DEF_NUM_INFIX(divide, /, NUM) DEF_NUM_INFIX(lt, <, BOOL) DEF_NUM_INFIX(gt, >, BOOL) DEF_NUM_INFIX(lte, <=, BOOL) DEF_NUM_INFIX(gte, >=, BOOL) // Defines a primitive on Num that call infix bitwise [op]. #define DEF_NUM_BITWISE(name, op) \ DEF_PRIMITIVE(num_bitwise##name) \ { \ if (!validateNum(vm, args[1], "Right operand")) return false; \ uint32_t left = (uint32_t)AS_NUM(args[0]); \ uint32_t right = (uint32_t)AS_NUM(args[1]); \ RETURN_NUM(left op right); \ } DEF_NUM_BITWISE(And, &) DEF_NUM_BITWISE(Or, |) DEF_NUM_BITWISE(Xor, ^) DEF_NUM_BITWISE(LeftShift, <<) DEF_NUM_BITWISE(RightShift, >>) // Defines a primitive method on Num that returns the result of [fn]. #define DEF_NUM_FN(name, fn) \ DEF_PRIMITIVE(num_##name) \ { \ RETURN_NUM(fn(AS_NUM(args[0]))); \ } DEF_NUM_FN(abs, fabs) DEF_NUM_FN(acos, acos) DEF_NUM_FN(asin, asin) DEF_NUM_FN(atan, atan) DEF_NUM_FN(cbrt, cbrt) DEF_NUM_FN(ceil, ceil) DEF_NUM_FN(cos, cos) DEF_NUM_FN(floor, floor) DEF_NUM_FN(negate, -) DEF_NUM_FN(round, round) DEF_NUM_FN(sin, sin) DEF_NUM_FN(sqrt, sqrt) DEF_NUM_FN(tan, tan) DEF_NUM_FN(log, log) DEF_NUM_FN(log2, log2) DEF_NUM_FN(exp, exp) DEF_PRIMITIVE(num_mod) { if (!validateNum(vm, args[1], "Right operand")) return false; RETURN_NUM(fmod(AS_NUM(args[0]), AS_NUM(args[1]))); } DEF_PRIMITIVE(num_eqeq) { if (!IS_NUM(args[1])) RETURN_FALSE; RETURN_BOOL(AS_NUM(args[0]) == AS_NUM(args[1])); } DEF_PRIMITIVE(num_bangeq) { if (!IS_NUM(args[1])) RETURN_TRUE; RETURN_BOOL(AS_NUM(args[0]) != AS_NUM(args[1])); } DEF_PRIMITIVE(num_bitwiseNot) { // Bitwise operators always work on 32-bit unsigned ints. RETURN_NUM(~(uint32_t)AS_NUM(args[0])); } DEF_PRIMITIVE(num_dotDot) { if (!validateNum(vm, args[1], "Right hand side of range")) return false; double from = AS_NUM(args[0]); double to = AS_NUM(args[1]); RETURN_VAL(wrenNewRange(vm, from, to, true)); } DEF_PRIMITIVE(num_dotDotDot) { if (!validateNum(vm, args[1], "Right hand side of range")) return false; double from = AS_NUM(args[0]); double to = AS_NUM(args[1]); RETURN_VAL(wrenNewRange(vm, from, to, false)); } DEF_PRIMITIVE(num_atan2) { if (!validateNum(vm, args[1], "x value")) return false; RETURN_NUM(atan2(AS_NUM(args[0]), AS_NUM(args[1]))); } DEF_PRIMITIVE(num_min) { if (!validateNum(vm, args[1], "Other value")) return false; double value = AS_NUM(args[0]); double other = AS_NUM(args[1]); RETURN_NUM(value <= other ? value : other); } DEF_PRIMITIVE(num_max) { if (!validateNum(vm, args[1], "Other value")) return false; double value = AS_NUM(args[0]); double other = AS_NUM(args[1]); RETURN_NUM(value > other ? value : other); } DEF_PRIMITIVE(num_clamp) { if (!validateNum(vm, args[1], "Min value")) return false; if (!validateNum(vm, args[2], "Max value")) return false; double value = AS_NUM(args[0]); double min = AS_NUM(args[1]); double max = AS_NUM(args[2]); double result = (value < min) ? min : ((value > max) ? max : value); RETURN_NUM(result); } DEF_PRIMITIVE(num_pow) { if (!validateNum(vm, args[1], "Power value")) return false; RETURN_NUM(pow(AS_NUM(args[0]), AS_NUM(args[1]))); } DEF_PRIMITIVE(num_fraction) { double unused; RETURN_NUM(modf(AS_NUM(args[0]) , &unused)); } DEF_PRIMITIVE(num_isInfinity) { RETURN_BOOL(isinf(AS_NUM(args[0]))); } DEF_PRIMITIVE(num_isInteger) { double value = AS_NUM(args[0]); if (isnan(value) || isinf(value)) RETURN_FALSE; RETURN_BOOL(trunc(value) == value); } DEF_PRIMITIVE(num_isNan) { RETURN_BOOL(isnan(AS_NUM(args[0]))); } DEF_PRIMITIVE(num_sign) { double value = AS_NUM(args[0]); if (value > 0) { RETURN_NUM(1); } else if (value < 0) { RETURN_NUM(-1); } else { RETURN_NUM(0); } } DEF_PRIMITIVE(num_toString) { RETURN_VAL(wrenNumToString(vm, AS_NUM(args[0]))); } DEF_PRIMITIVE(num_truncate) { double integer; modf(AS_NUM(args[0]) , &integer); RETURN_NUM(integer); } DEF_PRIMITIVE(object_same) { RETURN_BOOL(wrenValuesEqual(args[1], args[2])); } DEF_PRIMITIVE(object_not) { RETURN_VAL(FALSE_VAL); } DEF_PRIMITIVE(object_eqeq) { RETURN_BOOL(wrenValuesEqual(args[0], args[1])); } DEF_PRIMITIVE(object_bangeq) { RETURN_BOOL(!wrenValuesEqual(args[0], args[1])); } DEF_PRIMITIVE(object_is) { if (!IS_CLASS(args[1])) { RETURN_ERROR("Right operand must be a class."); } ObjClass *classObj = wrenGetClass(vm, args[0]); ObjClass *baseClassObj = AS_CLASS(args[1]); // Walk the superclass chain looking for the class. do { if (baseClassObj == classObj) RETURN_BOOL(true); classObj = classObj->superclass; } while (classObj != NULL); RETURN_BOOL(false); } DEF_PRIMITIVE(object_toString) { Obj* obj = AS_OBJ(args[0]); Value name = OBJ_VAL(obj->classObj->name); RETURN_VAL(wrenStringFormat(vm, "instance of @", name)); } DEF_PRIMITIVE(object_type) { RETURN_OBJ(wrenGetClass(vm, args[0])); } DEF_PRIMITIVE(range_from) { RETURN_NUM(AS_RANGE(args[0])->from); } DEF_PRIMITIVE(range_to) { RETURN_NUM(AS_RANGE(args[0])->to); } DEF_PRIMITIVE(range_min) { ObjRange* range = AS_RANGE(args[0]); RETURN_NUM(fmin(range->from, range->to)); } DEF_PRIMITIVE(range_max) { ObjRange* range = AS_RANGE(args[0]); RETURN_NUM(fmax(range->from, range->to)); } DEF_PRIMITIVE(range_isInclusive) { RETURN_BOOL(AS_RANGE(args[0])->isInclusive); } DEF_PRIMITIVE(range_iterate) { ObjRange* range = AS_RANGE(args[0]); // Special case: empty range. if (range->from == range->to && !range->isInclusive) RETURN_FALSE; // Start the iteration. if (IS_NULL(args[1])) RETURN_NUM(range->from); if (!validateNum(vm, args[1], "Iterator")) return false; double iterator = AS_NUM(args[1]); // Iterate towards [to] from [from]. if (range->from < range->to) { iterator++; if (iterator > range->to) RETURN_FALSE; } else { iterator--; if (iterator < range->to) RETURN_FALSE; } if (!range->isInclusive && iterator == range->to) RETURN_FALSE; RETURN_NUM(iterator); } DEF_PRIMITIVE(range_iteratorValue) { // Assume the iterator is a number so that is the value of the range. RETURN_VAL(args[1]); } DEF_PRIMITIVE(range_toString) { ObjRange* range = AS_RANGE(args[0]); Value from = wrenNumToString(vm, range->from); wrenPushRoot(vm, AS_OBJ(from)); Value to = wrenNumToString(vm, range->to); wrenPushRoot(vm, AS_OBJ(to)); Value result = wrenStringFormat(vm, "@$@", from, range->isInclusive ? ".." : "...", to); wrenPopRoot(vm); wrenPopRoot(vm); RETURN_VAL(result); } DEF_PRIMITIVE(string_fromCodePoint) { if (!validateInt(vm, args[1], "Code point")) return false; int codePoint = (int)AS_NUM(args[1]); if (codePoint < 0) { RETURN_ERROR("Code point cannot be negative."); } else if (codePoint > 0x10ffff) { RETURN_ERROR("Code point cannot be greater than 0x10ffff."); } RETURN_VAL(wrenStringFromCodePoint(vm, codePoint)); } DEF_PRIMITIVE(string_fromByte) { if (!validateInt(vm, args[1], "Byte")) return false; int byte = (int) AS_NUM(args[1]); if (byte < 0) { RETURN_ERROR("Byte cannot be negative."); } else if (byte > 0xff) { RETURN_ERROR("Byte cannot be greater than 0xff."); } RETURN_VAL(wrenStringFromByte(vm, (uint8_t) byte)); } DEF_PRIMITIVE(string_byteAt) { ObjString* string = AS_STRING(args[0]); uint32_t index = validateIndex(vm, args[1], string->length, "Index"); if (index == UINT32_MAX) return false; RETURN_NUM((uint8_t)string->value[index]); } DEF_PRIMITIVE(string_byteCount) { RETURN_NUM(AS_STRING(args[0])->length); } DEF_PRIMITIVE(string_codePointAt) { ObjString* string = AS_STRING(args[0]); uint32_t index = validateIndex(vm, args[1], string->length, "Index"); if (index == UINT32_MAX) return false; // If we are in the middle of a UTF-8 sequence, indicate that. const uint8_t* bytes = (uint8_t*)string->value; if ((bytes[index] & 0xc0) == 0x80) RETURN_NUM(-1); // Decode the UTF-8 sequence. RETURN_NUM(wrenUtf8Decode((uint8_t*)string->value + index, string->length - index)); } DEF_PRIMITIVE(string_contains) { if (!validateString(vm, args[1], "Argument")) return false; ObjString* string = AS_STRING(args[0]); ObjString* search = AS_STRING(args[1]); RETURN_BOOL(wrenStringFind(string, search, 0) != UINT32_MAX); } DEF_PRIMITIVE(string_endsWith) { if (!validateString(vm, args[1], "Argument")) return false; ObjString* string = AS_STRING(args[0]); ObjString* search = AS_STRING(args[1]); // Edge case: If the search string is longer then return false right away. if (search->length > string->length) RETURN_FALSE; RETURN_BOOL(memcmp(string->value + string->length - search->length, search->value, search->length) == 0); } DEF_PRIMITIVE(string_indexOf1) { if (!validateString(vm, args[1], "Argument")) return false; ObjString* string = AS_STRING(args[0]); ObjString* search = AS_STRING(args[1]); uint32_t index = wrenStringFind(string, search, 0); RETURN_NUM(index == UINT32_MAX ? -1 : (int)index); } DEF_PRIMITIVE(string_indexOf2) { if (!validateString(vm, args[1], "Argument")) return false; ObjString* string = AS_STRING(args[0]); ObjString* search = AS_STRING(args[1]); uint32_t start = validateIndex(vm, args[2], string->length, "Start"); if (start == UINT32_MAX) return false; uint32_t index = wrenStringFind(string, search, start); RETURN_NUM(index == UINT32_MAX ? -1 : (int)index); } DEF_PRIMITIVE(string_iterate) { ObjString* string = AS_STRING(args[0]); // If we're starting the iteration, return the first index. if (IS_NULL(args[1])) { if (string->length == 0) RETURN_FALSE; RETURN_NUM(0); } if (!validateInt(vm, args[1], "Iterator")) return false; if (AS_NUM(args[1]) < 0) RETURN_FALSE; uint32_t index = (uint32_t)AS_NUM(args[1]); // Advance to the beginning of the next UTF-8 sequence. do { index++; if (index >= string->length) RETURN_FALSE; } while ((string->value[index] & 0xc0) == 0x80); RETURN_NUM(index); } DEF_PRIMITIVE(string_iterateByte) { ObjString* string = AS_STRING(args[0]); // If we're starting the iteration, return the first index. if (IS_NULL(args[1])) { if (string->length == 0) RETURN_FALSE; RETURN_NUM(0); } if (!validateInt(vm, args[1], "Iterator")) return false; if (AS_NUM(args[1]) < 0) RETURN_FALSE; uint32_t index = (uint32_t)AS_NUM(args[1]); // Advance to the next byte. index++; if (index >= string->length) RETURN_FALSE; RETURN_NUM(index); } DEF_PRIMITIVE(string_iteratorValue) { ObjString* string = AS_STRING(args[0]); uint32_t index = validateIndex(vm, args[1], string->length, "Iterator"); if (index == UINT32_MAX) return false; RETURN_VAL(wrenStringCodePointAt(vm, string, index)); } DEF_PRIMITIVE(string_startsWith) { if (!validateString(vm, args[1], "Argument")) return false; ObjString* string = AS_STRING(args[0]); ObjString* search = AS_STRING(args[1]); // Edge case: If the search string is longer then return false right away. if (search->length > string->length) RETURN_FALSE; RETURN_BOOL(memcmp(string->value, search->value, search->length) == 0); } DEF_PRIMITIVE(string_plus) { if (!validateString(vm, args[1], "Right operand")) return false; RETURN_VAL(wrenStringFormat(vm, "@@", args[0], args[1])); } DEF_PRIMITIVE(string_subscript) { ObjString* string = AS_STRING(args[0]); if (IS_NUM(args[1])) { int index = validateIndex(vm, args[1], string->length, "Subscript"); if (index == -1) return false; RETURN_VAL(wrenStringCodePointAt(vm, string, index)); } if (!IS_RANGE(args[1])) { RETURN_ERROR("Subscript must be a number or a range."); } int step; uint32_t count = string->length; int start = calculateRange(vm, AS_RANGE(args[1]), &count, &step); if (start == -1) return false; RETURN_VAL(wrenNewStringFromRange(vm, string, start, count, step)); } DEF_PRIMITIVE(string_toString) { RETURN_VAL(args[0]); } DEF_PRIMITIVE(system_clock) { RETURN_NUM((double)clock() / CLOCKS_PER_SEC); } DEF_PRIMITIVE(system_gc) { wrenCollectGarbage(vm); RETURN_NULL; } DEF_PRIMITIVE(system_writeString) { if (vm->config.writeFn != NULL) { vm->config.writeFn(vm, AS_CSTRING(args[1])); } RETURN_VAL(args[1]); } // Creates either the Object or Class class in the core module with [name]. static ObjClass* defineClass(WrenVM* vm, ObjModule* module, const char* name) { ObjString* nameString = AS_STRING(wrenNewString(vm, name)); wrenPushRoot(vm, (Obj*)nameString); ObjClass* classObj = wrenNewSingleClass(vm, 0, nameString); wrenDefineVariable(vm, module, name, nameString->length, OBJ_VAL(classObj), NULL); wrenPopRoot(vm); return classObj; } void wrenInitializeCore(WrenVM* vm) { ObjModule* coreModule = wrenNewModule(vm, NULL); wrenPushRoot(vm, (Obj*)coreModule); // The core module's key is null in the module map. wrenMapSet(vm, vm->modules, NULL_VAL, OBJ_VAL(coreModule)); wrenPopRoot(vm); // coreModule. // Define the root Object class. This has to be done a little specially // because it has no superclass. vm->objectClass = defineClass(vm, coreModule, "Object"); PRIMITIVE(vm->objectClass, "!", object_not); PRIMITIVE(vm->objectClass, "==(_)", object_eqeq); PRIMITIVE(vm->objectClass, "!=(_)", object_bangeq); PRIMITIVE(vm->objectClass, "is(_)", object_is); PRIMITIVE(vm->objectClass, "toString", object_toString); PRIMITIVE(vm->objectClass, "type", object_type); // Now we can define Class, which is a subclass of Object. vm->classClass = defineClass(vm, coreModule, "Class"); wrenBindSuperclass(vm, vm->classClass, vm->objectClass); PRIMITIVE(vm->classClass, "name", class_name); PRIMITIVE(vm->classClass, "supertype", class_supertype); PRIMITIVE(vm->classClass, "toString", class_toString); PRIMITIVE(vm->classClass, "attributes", class_attributes); // Finally, we can define Object's metaclass which is a subclass of Class. ObjClass* objectMetaclass = defineClass(vm, coreModule, "Object metaclass"); // Wire up the metaclass relationships now that all three classes are built. vm->objectClass->obj.classObj = objectMetaclass; objectMetaclass->obj.classObj = vm->classClass; vm->classClass->obj.classObj = vm->classClass; // Do this after wiring up the metaclasses so objectMetaclass doesn't get // collected. wrenBindSuperclass(vm, objectMetaclass, vm->classClass); PRIMITIVE(objectMetaclass, "same(_,_)", object_same); // The core class diagram ends up looking like this, where single lines point // to a class's superclass, and double lines point to its metaclass: // // .------------------------------------. .====. // | .---------------. | # # // v | v | v # // .---------. .-------------------. .-------. # // | Object |==>| Object metaclass |==>| Class |==" // '---------' '-------------------' '-------' // ^ ^ ^ ^ ^ // | .--------------' # | # // | | # | # // .---------. .-------------------. # | # -. // | Base |==>| Base metaclass |======" | # | // '---------' '-------------------' | # | // ^ | # | // | .------------------' # | Example classes // | | # | // .---------. .-------------------. # | // | Derived |==>| Derived metaclass |==========" | // '---------' '-------------------' -' // The rest of the classes can now be defined normally. wrenInterpret(vm, NULL, coreModuleSource); vm->boolClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Bool")); PRIMITIVE(vm->boolClass, "toString", bool_toString); PRIMITIVE(vm->boolClass, "!", bool_not); vm->fiberClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Fiber")); PRIMITIVE(vm->fiberClass->obj.classObj, "new(_)", fiber_new); PRIMITIVE(vm->fiberClass->obj.classObj, "abort(_)", fiber_abort); PRIMITIVE(vm->fiberClass->obj.classObj, "current", fiber_current); PRIMITIVE(vm->fiberClass->obj.classObj, "suspend()", fiber_suspend); PRIMITIVE(vm->fiberClass->obj.classObj, "yield()", fiber_yield); PRIMITIVE(vm->fiberClass->obj.classObj, "yield(_)", fiber_yield1); PRIMITIVE(vm->fiberClass, "call()", fiber_call); PRIMITIVE(vm->fiberClass, "call(_)", fiber_call1); PRIMITIVE(vm->fiberClass, "error", fiber_error); PRIMITIVE(vm->fiberClass, "isDone", fiber_isDone); PRIMITIVE(vm->fiberClass, "transfer()", fiber_transfer); PRIMITIVE(vm->fiberClass, "transfer(_)", fiber_transfer1); PRIMITIVE(vm->fiberClass, "transferError(_)", fiber_transferError); PRIMITIVE(vm->fiberClass, "try()", fiber_try); PRIMITIVE(vm->fiberClass, "try(_)", fiber_try1); vm->fnClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Fn")); PRIMITIVE(vm->fnClass->obj.classObj, "new(_)", fn_new); PRIMITIVE(vm->fnClass, "arity", fn_arity); FUNCTION_CALL(vm->fnClass, "call()", fn_call0); FUNCTION_CALL(vm->fnClass, "call(_)", fn_call1); FUNCTION_CALL(vm->fnClass, "call(_,_)", fn_call2); FUNCTION_CALL(vm->fnClass, "call(_,_,_)", fn_call3); FUNCTION_CALL(vm->fnClass, "call(_,_,_,_)", fn_call4); FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_)", fn_call5); FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_)", fn_call6); FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_)", fn_call7); FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_)", fn_call8); FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_)", fn_call9); FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_)", fn_call10); FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_)", fn_call11); FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_)", fn_call12); FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_,_)", fn_call13); FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_,_,_)", fn_call14); FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_,_,_,_)", fn_call15); FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_)", fn_call16); PRIMITIVE(vm->fnClass, "toString", fn_toString); vm->nullClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Null")); PRIMITIVE(vm->nullClass, "!", null_not); PRIMITIVE(vm->nullClass, "toString", null_toString); vm->numClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Num")); PRIMITIVE(vm->numClass->obj.classObj, "fromString(_)", num_fromString); PRIMITIVE(vm->numClass->obj.classObj, "infinity", num_infinity); PRIMITIVE(vm->numClass->obj.classObj, "nan", num_nan); PRIMITIVE(vm->numClass->obj.classObj, "pi", num_pi); PRIMITIVE(vm->numClass->obj.classObj, "tau", num_tau); PRIMITIVE(vm->numClass->obj.classObj, "largest", num_largest); PRIMITIVE(vm->numClass->obj.classObj, "smallest", num_smallest); PRIMITIVE(vm->numClass->obj.classObj, "maxSafeInteger", num_maxSafeInteger); PRIMITIVE(vm->numClass->obj.classObj, "minSafeInteger", num_minSafeInteger); PRIMITIVE(vm->numClass, "-(_)", num_minus); PRIMITIVE(vm->numClass, "+(_)", num_plus); PRIMITIVE(vm->numClass, "*(_)", num_multiply); PRIMITIVE(vm->numClass, "/(_)", num_divide); PRIMITIVE(vm->numClass, "<(_)", num_lt); PRIMITIVE(vm->numClass, ">(_)", num_gt); PRIMITIVE(vm->numClass, "<=(_)", num_lte); PRIMITIVE(vm->numClass, ">=(_)", num_gte); PRIMITIVE(vm->numClass, "&(_)", num_bitwiseAnd); PRIMITIVE(vm->numClass, "|(_)", num_bitwiseOr); PRIMITIVE(vm->numClass, "^(_)", num_bitwiseXor); PRIMITIVE(vm->numClass, "<<(_)", num_bitwiseLeftShift); PRIMITIVE(vm->numClass, ">>(_)", num_bitwiseRightShift); PRIMITIVE(vm->numClass, "abs", num_abs); PRIMITIVE(vm->numClass, "acos", num_acos); PRIMITIVE(vm->numClass, "asin", num_asin); PRIMITIVE(vm->numClass, "atan", num_atan); PRIMITIVE(vm->numClass, "cbrt", num_cbrt); PRIMITIVE(vm->numClass, "ceil", num_ceil); PRIMITIVE(vm->numClass, "cos", num_cos); PRIMITIVE(vm->numClass, "floor", num_floor); PRIMITIVE(vm->numClass, "-", num_negate); PRIMITIVE(vm->numClass, "round", num_round); PRIMITIVE(vm->numClass, "min(_)", num_min); PRIMITIVE(vm->numClass, "max(_)", num_max); PRIMITIVE(vm->numClass, "clamp(_,_)", num_clamp); PRIMITIVE(vm->numClass, "sin", num_sin); PRIMITIVE(vm->numClass, "sqrt", num_sqrt); PRIMITIVE(vm->numClass, "tan", num_tan); PRIMITIVE(vm->numClass, "log", num_log); PRIMITIVE(vm->numClass, "log2", num_log2); PRIMITIVE(vm->numClass, "exp", num_exp); PRIMITIVE(vm->numClass, "%(_)", num_mod); PRIMITIVE(vm->numClass, "~", num_bitwiseNot); PRIMITIVE(vm->numClass, "..(_)", num_dotDot); PRIMITIVE(vm->numClass, "...(_)", num_dotDotDot); PRIMITIVE(vm->numClass, "atan(_)", num_atan2); PRIMITIVE(vm->numClass, "pow(_)", num_pow); PRIMITIVE(vm->numClass, "fraction", num_fraction); PRIMITIVE(vm->numClass, "isInfinity", num_isInfinity); PRIMITIVE(vm->numClass, "isInteger", num_isInteger); PRIMITIVE(vm->numClass, "isNan", num_isNan); PRIMITIVE(vm->numClass, "sign", num_sign); PRIMITIVE(vm->numClass, "toString", num_toString); PRIMITIVE(vm->numClass, "truncate", num_truncate); // These are defined just so that 0 and -0 are equal, which is specified by // IEEE 754 even though they have different bit representations. PRIMITIVE(vm->numClass, "==(_)", num_eqeq); PRIMITIVE(vm->numClass, "!=(_)", num_bangeq); vm->stringClass = AS_CLASS(wrenFindVariable(vm, coreModule, "String")); PRIMITIVE(vm->stringClass->obj.classObj, "fromCodePoint(_)", string_fromCodePoint); PRIMITIVE(vm->stringClass->obj.classObj, "fromByte(_)", string_fromByte); PRIMITIVE(vm->stringClass, "+(_)", string_plus); PRIMITIVE(vm->stringClass, "[_]", string_subscript); PRIMITIVE(vm->stringClass, "byteAt_(_)", string_byteAt); PRIMITIVE(vm->stringClass, "byteCount_", string_byteCount); PRIMITIVE(vm->stringClass, "codePointAt_(_)", string_codePointAt); PRIMITIVE(vm->stringClass, "contains(_)", string_contains); PRIMITIVE(vm->stringClass, "endsWith(_)", string_endsWith); PRIMITIVE(vm->stringClass, "indexOf(_)", string_indexOf1); PRIMITIVE(vm->stringClass, "indexOf(_,_)", string_indexOf2); PRIMITIVE(vm->stringClass, "iterate(_)", string_iterate); PRIMITIVE(vm->stringClass, "iterateByte_(_)", string_iterateByte); PRIMITIVE(vm->stringClass, "iteratorValue(_)", string_iteratorValue); PRIMITIVE(vm->stringClass, "startsWith(_)", string_startsWith); PRIMITIVE(vm->stringClass, "toString", string_toString); vm->listClass = AS_CLASS(wrenFindVariable(vm, coreModule, "List")); PRIMITIVE(vm->listClass->obj.classObj, "filled(_,_)", list_filled); PRIMITIVE(vm->listClass->obj.classObj, "new()", list_new); PRIMITIVE(vm->listClass, "[_]", list_subscript); PRIMITIVE(vm->listClass, "[_]=(_)", list_subscriptSetter); PRIMITIVE(vm->listClass, "add(_)", list_add); PRIMITIVE(vm->listClass, "addCore_(_)", list_addCore); PRIMITIVE(vm->listClass, "clear()", list_clear); PRIMITIVE(vm->listClass, "count", list_count); PRIMITIVE(vm->listClass, "insert(_,_)", list_insert); PRIMITIVE(vm->listClass, "iterate(_)", list_iterate); PRIMITIVE(vm->listClass, "iteratorValue(_)", list_iteratorValue); PRIMITIVE(vm->listClass, "removeAt(_)", list_removeAt); PRIMITIVE(vm->listClass, "remove(_)", list_removeValue); PRIMITIVE(vm->listClass, "indexOf(_)", list_indexOf); PRIMITIVE(vm->listClass, "swap(_,_)", list_swap); vm->mapClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Map")); PRIMITIVE(vm->mapClass->obj.classObj, "new()", map_new); PRIMITIVE(vm->mapClass, "[_]", map_subscript); PRIMITIVE(vm->mapClass, "[_]=(_)", map_subscriptSetter); PRIMITIVE(vm->mapClass, "addCore_(_,_)", map_addCore); PRIMITIVE(vm->mapClass, "clear()", map_clear); PRIMITIVE(vm->mapClass, "containsKey(_)", map_containsKey); PRIMITIVE(vm->mapClass, "count", map_count); PRIMITIVE(vm->mapClass, "remove(_)", map_remove); PRIMITIVE(vm->mapClass, "iterate(_)", map_iterate); PRIMITIVE(vm->mapClass, "keyIteratorValue_(_)", map_keyIteratorValue); PRIMITIVE(vm->mapClass, "valueIteratorValue_(_)", map_valueIteratorValue); vm->rangeClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Range")); PRIMITIVE(vm->rangeClass, "from", range_from); PRIMITIVE(vm->rangeClass, "to", range_to); PRIMITIVE(vm->rangeClass, "min", range_min); PRIMITIVE(vm->rangeClass, "max", range_max); PRIMITIVE(vm->rangeClass, "isInclusive", range_isInclusive); PRIMITIVE(vm->rangeClass, "iterate(_)", range_iterate); PRIMITIVE(vm->rangeClass, "iteratorValue(_)", range_iteratorValue); PRIMITIVE(vm->rangeClass, "toString", range_toString); ObjClass* systemClass = AS_CLASS(wrenFindVariable(vm, coreModule, "System")); PRIMITIVE(systemClass->obj.classObj, "clock", system_clock); PRIMITIVE(systemClass->obj.classObj, "gc()", system_gc); PRIMITIVE(systemClass->obj.classObj, "writeString_(_)", system_writeString); // While bootstrapping the core types and running the core module, a number // of string objects have been created, many of which were instantiated // before stringClass was stored in the VM. Some of them *must* be created // first -- the ObjClass for string itself has a reference to the ObjString // for its name. // // These all currently have a NULL classObj pointer, so go back and assign // them now that the string class is known. for (Obj* obj = vm->first; obj != NULL; obj = obj->next) { if (obj->type == OBJ_STRING) obj->classObj = vm->stringClass; } } // End file "wren_core.c" // Begin file "wren_value.c" #include #include #include #include #if WREN_DEBUG_TRACE_MEMORY #endif // TODO: Tune these. // The initial (and minimum) capacity of a non-empty list or map object. #define MIN_CAPACITY 16 // The rate at which a collection's capacity grows when the size exceeds the // current capacity. The new capacity will be determined by *multiplying* the // old capacity by this. Growing geometrically is necessary to ensure that // adding to a collection has O(1) amortized complexity. #define GROW_FACTOR 2 // The maximum percentage of map entries that can be filled before the map is // grown. A lower load takes more memory but reduces collisions which makes // lookup faster. #define MAP_LOAD_PERCENT 75 // The number of call frames initially allocated when a fiber is created. Making // this smaller makes fibers use less memory (at first) but spends more time // reallocating when the call stack grows. #define INITIAL_CALL_FRAMES 4 DEFINE_BUFFER(Value, Value); DEFINE_BUFFER(Method, Method); static void initObj(WrenVM* vm, Obj* obj, ObjType type, ObjClass* classObj) { obj->type = type; obj->isDark = false; obj->classObj = classObj; obj->next = vm->first; vm->first = obj; } ObjClass* wrenNewSingleClass(WrenVM* vm, int numFields, ObjString* name) { ObjClass* classObj = ALLOCATE(vm, ObjClass); initObj(vm, &classObj->obj, OBJ_CLASS, NULL); classObj->superclass = NULL; classObj->numFields = numFields; classObj->name = name; classObj->attributes = NULL_VAL; wrenPushRoot(vm, (Obj*)classObj); wrenMethodBufferInit(&classObj->methods); wrenPopRoot(vm); return classObj; } void wrenBindSuperclass(WrenVM* vm, ObjClass* subclass, ObjClass* superclass) { ASSERT(superclass != NULL, "Must have superclass."); subclass->superclass = superclass; // Include the superclass in the total number of fields. if (subclass->numFields != -1) { subclass->numFields += superclass->numFields; } else { ASSERT(superclass->numFields == 0, "A foreign class cannot inherit from a class with fields."); } // Inherit methods from its superclass. for (int i = 0; i < superclass->methods.count; i++) { wrenBindMethod(vm, subclass, i, superclass->methods.data[i]); } } ObjClass* wrenNewClass(WrenVM* vm, ObjClass* superclass, int numFields, ObjString* name) { // Create the metaclass. Value metaclassName = wrenStringFormat(vm, "@ metaclass", OBJ_VAL(name)); wrenPushRoot(vm, AS_OBJ(metaclassName)); ObjClass* metaclass = wrenNewSingleClass(vm, 0, AS_STRING(metaclassName)); metaclass->obj.classObj = vm->classClass; wrenPopRoot(vm); // Make sure the metaclass isn't collected when we allocate the class. wrenPushRoot(vm, (Obj*)metaclass); // Metaclasses always inherit Class and do not parallel the non-metaclass // hierarchy. wrenBindSuperclass(vm, metaclass, vm->classClass); ObjClass* classObj = wrenNewSingleClass(vm, numFields, name); // Make sure the class isn't collected while the inherited methods are being // bound. wrenPushRoot(vm, (Obj*)classObj); classObj->obj.classObj = metaclass; wrenBindSuperclass(vm, classObj, superclass); wrenPopRoot(vm); wrenPopRoot(vm); return classObj; } void wrenBindMethod(WrenVM* vm, ObjClass* classObj, int symbol, Method method) { // Make sure the buffer is big enough to contain the symbol's index. if (symbol >= classObj->methods.count) { Method noMethod; noMethod.type = METHOD_NONE; wrenMethodBufferFill(vm, &classObj->methods, noMethod, symbol - classObj->methods.count + 1); } classObj->methods.data[symbol] = method; } ObjClosure* wrenNewClosure(WrenVM* vm, ObjFn* fn) { ObjClosure* closure = ALLOCATE_FLEX(vm, ObjClosure, ObjUpvalue*, fn->numUpvalues); initObj(vm, &closure->obj, OBJ_CLOSURE, vm->fnClass); closure->fn = fn; // Clear the upvalue array. We need to do this in case a GC is triggered // after the closure is created but before the upvalue array is populated. for (int i = 0; i < fn->numUpvalues; i++) closure->upvalues[i] = NULL; return closure; } ObjFiber* wrenNewFiber(WrenVM* vm, ObjClosure* closure) { // Allocate the arrays before the fiber in case it triggers a GC. CallFrame* frames = ALLOCATE_ARRAY(vm, CallFrame, INITIAL_CALL_FRAMES); // Add one slot for the unused implicit receiver slot that the compiler // assumes all functions have. int stackCapacity = closure == NULL ? 1 : wrenPowerOf2Ceil(closure->fn->maxSlots + 1); Value* stack = ALLOCATE_ARRAY(vm, Value, stackCapacity); ObjFiber* fiber = ALLOCATE(vm, ObjFiber); initObj(vm, &fiber->obj, OBJ_FIBER, vm->fiberClass); fiber->stack = stack; fiber->stackTop = fiber->stack; fiber->stackCapacity = stackCapacity; fiber->frames = frames; fiber->frameCapacity = INITIAL_CALL_FRAMES; fiber->numFrames = 0; fiber->openUpvalues = NULL; fiber->caller = NULL; fiber->error = NULL_VAL; fiber->state = FIBER_OTHER; if (closure != NULL) { // Initialize the first call frame. wrenAppendCallFrame(vm, fiber, closure, fiber->stack); // The first slot always holds the closure. fiber->stackTop[0] = OBJ_VAL(closure); fiber->stackTop++; } return fiber; } void wrenEnsureStack(WrenVM* vm, ObjFiber* fiber, int needed) { if (fiber->stackCapacity >= needed) return; int capacity = wrenPowerOf2Ceil(needed); Value* oldStack = fiber->stack; fiber->stack = (Value*)wrenReallocate(vm, fiber->stack, sizeof(Value) * fiber->stackCapacity, sizeof(Value) * capacity); fiber->stackCapacity = capacity; // If the reallocation moves the stack, then we need to recalculate every // pointer that points into the old stack to into the same relative distance // in the new stack. We have to be a little careful about how these are // calculated because pointer subtraction is only well-defined within a // single array, hence the slightly redundant-looking arithmetic below. if (fiber->stack != oldStack) { // Top of the stack. if (vm->apiStack >= oldStack && vm->apiStack <= fiber->stackTop) { vm->apiStack = fiber->stack + (vm->apiStack - oldStack); } // Stack pointer for each call frame. for (int i = 0; i < fiber->numFrames; i++) { CallFrame* frame = &fiber->frames[i]; frame->stackStart = fiber->stack + (frame->stackStart - oldStack); } // Open upvalues. for (ObjUpvalue* upvalue = fiber->openUpvalues; upvalue != NULL; upvalue = upvalue->next) { upvalue->value = fiber->stack + (upvalue->value - oldStack); } fiber->stackTop = fiber->stack + (fiber->stackTop - oldStack); } } ObjForeign* wrenNewForeign(WrenVM* vm, ObjClass* classObj, size_t size) { ObjForeign* object = ALLOCATE_FLEX(vm, ObjForeign, uint8_t, size); initObj(vm, &object->obj, OBJ_FOREIGN, classObj); // Zero out the bytes. memset(object->data, 0, size); return object; } ObjFn* wrenNewFunction(WrenVM* vm, ObjModule* module, int maxSlots) { FnDebug* debug = ALLOCATE(vm, FnDebug); debug->name = NULL; wrenIntBufferInit(&debug->sourceLines); ObjFn* fn = ALLOCATE(vm, ObjFn); initObj(vm, &fn->obj, OBJ_FN, vm->fnClass); wrenValueBufferInit(&fn->constants); wrenByteBufferInit(&fn->code); fn->module = module; fn->maxSlots = maxSlots; fn->numUpvalues = 0; fn->arity = 0; fn->debug = debug; return fn; } void wrenFunctionBindName(WrenVM* vm, ObjFn* fn, const char* name, int length) { fn->debug->name = ALLOCATE_ARRAY(vm, char, length + 1); memcpy(fn->debug->name, name, length); fn->debug->name[length] = '\0'; } Value wrenNewInstance(WrenVM* vm, ObjClass* classObj) { ObjInstance* instance = ALLOCATE_FLEX(vm, ObjInstance, Value, classObj->numFields); initObj(vm, &instance->obj, OBJ_INSTANCE, classObj); // Initialize fields to null. for (int i = 0; i < classObj->numFields; i++) { instance->fields[i] = NULL_VAL; } return OBJ_VAL(instance); } ObjList* wrenNewList(WrenVM* vm, uint32_t numElements) { // Allocate this before the list object in case it triggers a GC which would // free the list. Value* elements = NULL; if (numElements > 0) { elements = ALLOCATE_ARRAY(vm, Value, numElements); } ObjList* list = ALLOCATE(vm, ObjList); initObj(vm, &list->obj, OBJ_LIST, vm->listClass); list->elements.capacity = numElements; list->elements.count = numElements; list->elements.data = elements; return list; } void wrenListInsert(WrenVM* vm, ObjList* list, Value value, uint32_t index) { if (IS_OBJ(value)) wrenPushRoot(vm, AS_OBJ(value)); // Add a slot at the end of the list. wrenValueBufferWrite(vm, &list->elements, NULL_VAL); if (IS_OBJ(value)) wrenPopRoot(vm); // Shift the existing elements down. for (uint32_t i = list->elements.count - 1; i > index; i--) { list->elements.data[i] = list->elements.data[i - 1]; } // Store the new element. list->elements.data[index] = value; } int wrenListIndexOf(WrenVM* vm, ObjList* list, Value value) { int count = list->elements.count; for (int i = 0; i < count; i++) { Value item = list->elements.data[i]; if(wrenValuesEqual(item, value)) { return i; } } return -1; } Value wrenListRemoveAt(WrenVM* vm, ObjList* list, uint32_t index) { Value removed = list->elements.data[index]; if (IS_OBJ(removed)) wrenPushRoot(vm, AS_OBJ(removed)); // Shift items up. for (int i = index; i < list->elements.count - 1; i++) { list->elements.data[i] = list->elements.data[i + 1]; } // If we have too much excess capacity, shrink it. if (list->elements.capacity / GROW_FACTOR >= list->elements.count) { list->elements.data = (Value*)wrenReallocate(vm, list->elements.data, sizeof(Value) * list->elements.capacity, sizeof(Value) * (list->elements.capacity / GROW_FACTOR)); list->elements.capacity /= GROW_FACTOR; } if (IS_OBJ(removed)) wrenPopRoot(vm); list->elements.count--; return removed; } ObjMap* wrenNewMap(WrenVM* vm) { ObjMap* map = ALLOCATE(vm, ObjMap); initObj(vm, &map->obj, OBJ_MAP, vm->mapClass); map->capacity = 0; map->count = 0; map->entries = NULL; return map; } static inline uint32_t hashBits(uint64_t hash) { // From v8's ComputeLongHash() which in turn cites: // Thomas Wang, Integer Hash Functions. // http://www.concentric.net/~Ttwang/tech/inthash.htm hash = ~hash + (hash << 18); // hash = (hash << 18) - hash - 1; hash = hash ^ (hash >> 31); hash = hash * 21; // hash = (hash + (hash << 2)) + (hash << 4); hash = hash ^ (hash >> 11); hash = hash + (hash << 6); hash = hash ^ (hash >> 22); return (uint32_t)(hash & 0x3fffffff); } // Generates a hash code for [num]. static inline uint32_t hashNumber(double num) { // Hash the raw bits of the value. return hashBits(wrenDoubleToBits(num)); } // Generates a hash code for [object]. static uint32_t hashObject(Obj* object) { switch (object->type) { case OBJ_CLASS: // Classes just use their name. return hashObject((Obj*)((ObjClass*)object)->name); // Allow bare (non-closure) functions so that we can use a map to find // existing constants in a function's constant table. This is only used // internally. Since user code never sees a non-closure function, they // cannot use them as map keys. case OBJ_FN: { ObjFn* fn = (ObjFn*)object; return hashNumber(fn->arity) ^ hashNumber(fn->code.count); } case OBJ_RANGE: { ObjRange* range = (ObjRange*)object; return hashNumber(range->from) ^ hashNumber(range->to); } case OBJ_STRING: return ((ObjString*)object)->hash; default: ASSERT(false, "Only immutable objects can be hashed."); return 0; } } // Generates a hash code for [value], which must be one of the built-in // immutable types: null, bool, class, num, range, or string. static uint32_t hashValue(Value value) { // TODO: We'll probably want to randomize this at some point. #if WREN_NAN_TAGGING if (IS_OBJ(value)) return hashObject(AS_OBJ(value)); // Hash the raw bits of the unboxed value. return hashBits(value); #else switch (value.type) { case VAL_FALSE: return 0; case VAL_NULL: return 1; case VAL_NUM: return hashNumber(AS_NUM(value)); case VAL_TRUE: return 2; case VAL_OBJ: return hashObject(AS_OBJ(value)); default: UNREACHABLE(); } return 0; #endif } // Looks for an entry with [key] in an array of [capacity] [entries]. // // If found, sets [result] to point to it and returns `true`. Otherwise, // returns `false` and points [result] to the entry where the key/value pair // should be inserted. static bool findEntry(MapEntry* entries, uint32_t capacity, Value key, MapEntry** result) { // If there is no entry array (an empty map), we definitely won't find it. if (capacity == 0) return false; // Figure out where to insert it in the table. Use open addressing and // basic linear probing. uint32_t startIndex = hashValue(key) % capacity; uint32_t index = startIndex; // If we pass a tombstone and don't end up finding the key, its entry will // be re-used for the insert. MapEntry* tombstone = NULL; // Walk the probe sequence until we've tried every slot. do { MapEntry* entry = &entries[index]; if (IS_UNDEFINED(entry->key)) { // If we found an empty slot, the key is not in the table. If we found a // slot that contains a deleted key, we have to keep looking. if (IS_FALSE(entry->value)) { // We found an empty slot, so we've reached the end of the probe // sequence without finding the key. If we passed a tombstone, then // that's where we should insert the item, otherwise, put it here at // the end of the sequence. *result = tombstone != NULL ? tombstone : entry; return false; } else { // We found a tombstone. We need to keep looking in case the key is // after it, but we'll use this entry as the insertion point if the // key ends up not being found. if (tombstone == NULL) tombstone = entry; } } else if (wrenValuesEqual(entry->key, key)) { // We found the key. *result = entry; return true; } // Try the next slot. index = (index + 1) % capacity; } while (index != startIndex); // If we get here, the table is full of tombstones. Return the first one we // found. ASSERT(tombstone != NULL, "Map should have tombstones or empty entries."); *result = tombstone; return false; } // Inserts [key] and [value] in the array of [entries] with the given // [capacity]. // // Returns `true` if this is the first time [key] was added to the map. static bool insertEntry(MapEntry* entries, uint32_t capacity, Value key, Value value) { ASSERT(entries != NULL, "Should ensure capacity before inserting."); MapEntry* entry; if (findEntry(entries, capacity, key, &entry)) { // Already present, so just replace the value. entry->value = value; return false; } else { entry->key = key; entry->value = value; return true; } } // Updates [map]'s entry array to [capacity]. static void resizeMap(WrenVM* vm, ObjMap* map, uint32_t capacity) { // Create the new empty hash table. MapEntry* entries = ALLOCATE_ARRAY(vm, MapEntry, capacity); for (uint32_t i = 0; i < capacity; i++) { entries[i].key = UNDEFINED_VAL; entries[i].value = FALSE_VAL; } // Re-add the existing entries. if (map->capacity > 0) { for (uint32_t i = 0; i < map->capacity; i++) { MapEntry* entry = &map->entries[i]; // Don't copy empty entries or tombstones. if (IS_UNDEFINED(entry->key)) continue; insertEntry(entries, capacity, entry->key, entry->value); } } // Replace the array. DEALLOCATE(vm, map->entries); map->entries = entries; map->capacity = capacity; } Value wrenMapGet(ObjMap* map, Value key) { MapEntry* entry; if (findEntry(map->entries, map->capacity, key, &entry)) return entry->value; return UNDEFINED_VAL; } void wrenMapSet(WrenVM* vm, ObjMap* map, Value key, Value value) { // If the map is getting too full, make room first. if (map->count + 1 > map->capacity * MAP_LOAD_PERCENT / 100) { // Figure out the new hash table size. uint32_t capacity = map->capacity * GROW_FACTOR; if (capacity < MIN_CAPACITY) capacity = MIN_CAPACITY; resizeMap(vm, map, capacity); } if (insertEntry(map->entries, map->capacity, key, value)) { // A new key was added. map->count++; } } void wrenMapClear(WrenVM* vm, ObjMap* map) { DEALLOCATE(vm, map->entries); map->entries = NULL; map->capacity = 0; map->count = 0; } Value wrenMapRemoveKey(WrenVM* vm, ObjMap* map, Value key) { MapEntry* entry; if (!findEntry(map->entries, map->capacity, key, &entry)) return NULL_VAL; // Remove the entry from the map. Set this value to true, which marks it as a // deleted slot. When searching for a key, we will stop on empty slots, but // continue past deleted slots. Value value = entry->value; entry->key = UNDEFINED_VAL; entry->value = TRUE_VAL; if (IS_OBJ(value)) wrenPushRoot(vm, AS_OBJ(value)); map->count--; if (map->count == 0) { // Removed the last item, so free the array. wrenMapClear(vm, map); } else if (map->capacity > MIN_CAPACITY && map->count < map->capacity / GROW_FACTOR * MAP_LOAD_PERCENT / 100) { uint32_t capacity = map->capacity / GROW_FACTOR; if (capacity < MIN_CAPACITY) capacity = MIN_CAPACITY; // The map is getting empty, so shrink the entry array back down. // TODO: Should we do this less aggressively than we grow? resizeMap(vm, map, capacity); } if (IS_OBJ(value)) wrenPopRoot(vm); return value; } ObjModule* wrenNewModule(WrenVM* vm, ObjString* name) { ObjModule* module = ALLOCATE(vm, ObjModule); // Modules are never used as first-class objects, so don't need a class. initObj(vm, (Obj*)module, OBJ_MODULE, NULL); wrenPushRoot(vm, (Obj*)module); wrenSymbolTableInit(&module->variableNames); wrenValueBufferInit(&module->variables); module->name = name; wrenPopRoot(vm); return module; } Value wrenNewRange(WrenVM* vm, double from, double to, bool isInclusive) { ObjRange* range = ALLOCATE(vm, ObjRange); initObj(vm, &range->obj, OBJ_RANGE, vm->rangeClass); range->from = from; range->to = to; range->isInclusive = isInclusive; return OBJ_VAL(range); } // Creates a new string object with a null-terminated buffer large enough to // hold a string of [length] but does not fill in the bytes. // // The caller is expected to fill in the buffer and then calculate the string's // hash. static ObjString* allocateString(WrenVM* vm, size_t length) { ObjString* string = ALLOCATE_FLEX(vm, ObjString, char, length + 1); initObj(vm, &string->obj, OBJ_STRING, vm->stringClass); string->length = (int)length; string->value[length] = '\0'; return string; } // Calculates and stores the hash code for [string]. static void hashString(ObjString* string) { // FNV-1a hash. See: http://www.isthe.com/chongo/tech/comp/fnv/ uint32_t hash = 2166136261u; // This is O(n) on the length of the string, but we only call this when a new // string is created. Since the creation is also O(n) (to copy/initialize all // the bytes), we allow this here. for (uint32_t i = 0; i < string->length; i++) { hash ^= string->value[i]; hash *= 16777619; } string->hash = hash; } Value wrenNewString(WrenVM* vm, const char* text) { return wrenNewStringLength(vm, text, strlen(text)); } Value wrenNewStringLength(WrenVM* vm, const char* text, size_t length) { // Allow NULL if the string is empty since byte buffers don't allocate any // characters for a zero-length string. ASSERT(length == 0 || text != NULL, "Unexpected NULL string."); ObjString* string = allocateString(vm, length); // Copy the string (if given one). if (length > 0 && text != NULL) memcpy(string->value, text, length); hashString(string); return OBJ_VAL(string); } Value wrenNewStringFromRange(WrenVM* vm, ObjString* source, int start, uint32_t count, int step) { uint8_t* from = (uint8_t*)source->value; int length = 0; for (uint32_t i = 0; i < count; i++) { length += wrenUtf8DecodeNumBytes(from[start + i * step]); } ObjString* result = allocateString(vm, length); result->value[length] = '\0'; uint8_t* to = (uint8_t*)result->value; for (uint32_t i = 0; i < count; i++) { int index = start + i * step; int codePoint = wrenUtf8Decode(from + index, source->length - index); if (codePoint != -1) { to += wrenUtf8Encode(codePoint, to); } } hashString(result); return OBJ_VAL(result); } Value wrenNumToString(WrenVM* vm, double value) { // Edge case: If the value is NaN or infinity, different versions of libc // produce different outputs (some will format it signed and some won't). To // get reliable output, handle it ourselves. if (isnan(value)) return CONST_STRING(vm, "nan"); if (isinf(value)) { if (value > 0.0) { return CONST_STRING(vm, "infinity"); } else { return CONST_STRING(vm, "-infinity"); } } // This is large enough to hold any double converted to a string using // "%.14g". Example: // // -1.12345678901234e-1022 // // So we have: // // + 1 char for sign // + 1 char for digit // + 1 char for "." // + 14 chars for decimal digits // + 1 char for "e" // + 1 char for "-" or "+" // + 4 chars for exponent // + 1 char for "\0" // = 24 char buffer[24]; int length = sprintf(buffer, "%.14g", value); return wrenNewStringLength(vm, buffer, length); } Value wrenStringFromCodePoint(WrenVM* vm, int value) { int length = wrenUtf8EncodeNumBytes(value); ASSERT(length != 0, "Value out of range."); ObjString* string = allocateString(vm, length); wrenUtf8Encode(value, (uint8_t*)string->value); hashString(string); return OBJ_VAL(string); } Value wrenStringFromByte(WrenVM *vm, uint8_t value) { int length = 1; ObjString* string = allocateString(vm, length); string->value[0] = value; hashString(string); return OBJ_VAL(string); } Value wrenStringFormat(WrenVM* vm, const char* format, ...) { va_list argList; // Calculate the length of the result string. Do this up front so we can // create the final string with a single allocation. va_start(argList, format); size_t totalLength = 0; for (const char* c = format; *c != '\0'; c++) { switch (*c) { case '$': totalLength += strlen(va_arg(argList, const char*)); break; case '@': totalLength += AS_STRING(va_arg(argList, Value))->length; break; default: // Any other character is interpreted literally. totalLength++; } } va_end(argList); // Concatenate the string. ObjString* result = allocateString(vm, totalLength); va_start(argList, format); char* start = result->value; for (const char* c = format; *c != '\0'; c++) { switch (*c) { case '$': { const char* string = va_arg(argList, const char*); size_t length = strlen(string); memcpy(start, string, length); start += length; break; } case '@': { ObjString* string = AS_STRING(va_arg(argList, Value)); memcpy(start, string->value, string->length); start += string->length; break; } default: // Any other character is interpreted literally. *start++ = *c; } } va_end(argList); hashString(result); return OBJ_VAL(result); } Value wrenStringCodePointAt(WrenVM* vm, ObjString* string, uint32_t index) { ASSERT(index < string->length, "Index out of bounds."); int codePoint = wrenUtf8Decode((uint8_t*)string->value + index, string->length - index); if (codePoint == -1) { // If it isn't a valid UTF-8 sequence, treat it as a single raw byte. char bytes[2]; bytes[0] = string->value[index]; bytes[1] = '\0'; return wrenNewStringLength(vm, bytes, 1); } return wrenStringFromCodePoint(vm, codePoint); } // Uses the Boyer-Moore-Horspool string matching algorithm. uint32_t wrenStringFind(ObjString* haystack, ObjString* needle, uint32_t start) { // Edge case: An empty needle is always found. if (needle->length == 0) return start; // If the needle goes past the haystack it won't be found. if (start + needle->length > haystack->length) return UINT32_MAX; // If the startIndex is too far it also won't be found. if (start >= haystack->length) return UINT32_MAX; // Pre-calculate the shift table. For each character (8-bit value), we // determine how far the search window can be advanced if that character is // the last character in the haystack where we are searching for the needle // and the needle doesn't match there. uint32_t shift[UINT8_MAX]; uint32_t needleEnd = needle->length - 1; // By default, we assume the character is not the needle at all. In that case // case, if a match fails on that character, we can advance one whole needle // width since. for (uint32_t index = 0; index < UINT8_MAX; index++) { shift[index] = needle->length; } // Then, for every character in the needle, determine how far it is from the // end. If a match fails on that character, we can advance the window such // that it the last character in it lines up with the last place we could // find it in the needle. for (uint32_t index = 0; index < needleEnd; index++) { char c = needle->value[index]; shift[(uint8_t)c] = needleEnd - index; } // Slide the needle across the haystack, looking for the first match or // stopping if the needle goes off the end. char lastChar = needle->value[needleEnd]; uint32_t range = haystack->length - needle->length; for (uint32_t index = start; index <= range; ) { // Compare the last character in the haystack's window to the last character // in the needle. If it matches, see if the whole needle matches. char c = haystack->value[index + needleEnd]; if (lastChar == c && memcmp(haystack->value + index, needle->value, needleEnd) == 0) { // Found a match. return index; } // Otherwise, slide the needle forward. index += shift[(uint8_t)c]; } // Not found. return UINT32_MAX; } ObjUpvalue* wrenNewUpvalue(WrenVM* vm, Value* value) { ObjUpvalue* upvalue = ALLOCATE(vm, ObjUpvalue); // Upvalues are never used as first-class objects, so don't need a class. initObj(vm, &upvalue->obj, OBJ_UPVALUE, NULL); upvalue->value = value; upvalue->closed = NULL_VAL; upvalue->next = NULL; return upvalue; } void wrenGrayObj(WrenVM* vm, Obj* obj) { if (obj == NULL) return; // Stop if the object is already darkened so we don't get stuck in a cycle. if (obj->isDark) return; // It's been reached. obj->isDark = true; // Add it to the gray list so it can be recursively explored for // more marks later. if (vm->grayCount >= vm->grayCapacity) { vm->grayCapacity = vm->grayCount * 2; vm->gray = (Obj**)vm->config.reallocateFn(vm->gray, vm->grayCapacity * sizeof(Obj*), vm->config.userData); } vm->gray[vm->grayCount++] = obj; } void wrenGrayValue(WrenVM* vm, Value value) { if (!IS_OBJ(value)) return; wrenGrayObj(vm, AS_OBJ(value)); } void wrenGrayBuffer(WrenVM* vm, ValueBuffer* buffer) { for (int i = 0; i < buffer->count; i++) { wrenGrayValue(vm, buffer->data[i]); } } static void blackenClass(WrenVM* vm, ObjClass* classObj) { // The metaclass. wrenGrayObj(vm, (Obj*)classObj->obj.classObj); // The superclass. wrenGrayObj(vm, (Obj*)classObj->superclass); // Method function objects. for (int i = 0; i < classObj->methods.count; i++) { if (classObj->methods.data[i].type == METHOD_BLOCK) { wrenGrayObj(vm, (Obj*)classObj->methods.data[i].as.closure); } } wrenGrayObj(vm, (Obj*)classObj->name); if(!IS_NULL(classObj->attributes)) wrenGrayObj(vm, AS_OBJ(classObj->attributes)); // Keep track of how much memory is still in use. vm->bytesAllocated += sizeof(ObjClass); vm->bytesAllocated += classObj->methods.capacity * sizeof(Method); } static void blackenClosure(WrenVM* vm, ObjClosure* closure) { // Mark the function. wrenGrayObj(vm, (Obj*)closure->fn); // Mark the upvalues. for (int i = 0; i < closure->fn->numUpvalues; i++) { wrenGrayObj(vm, (Obj*)closure->upvalues[i]); } // Keep track of how much memory is still in use. vm->bytesAllocated += sizeof(ObjClosure); vm->bytesAllocated += sizeof(ObjUpvalue*) * closure->fn->numUpvalues; } static void blackenFiber(WrenVM* vm, ObjFiber* fiber) { // Stack functions. for (int i = 0; i < fiber->numFrames; i++) { wrenGrayObj(vm, (Obj*)fiber->frames[i].closure); } // Stack variables. for (Value* slot = fiber->stack; slot < fiber->stackTop; slot++) { wrenGrayValue(vm, *slot); } // Open upvalues. ObjUpvalue* upvalue = fiber->openUpvalues; while (upvalue != NULL) { wrenGrayObj(vm, (Obj*)upvalue); upvalue = upvalue->next; } // The caller. wrenGrayObj(vm, (Obj*)fiber->caller); wrenGrayValue(vm, fiber->error); // Keep track of how much memory is still in use. vm->bytesAllocated += sizeof(ObjFiber); vm->bytesAllocated += fiber->frameCapacity * sizeof(CallFrame); vm->bytesAllocated += fiber->stackCapacity * sizeof(Value); } static void blackenFn(WrenVM* vm, ObjFn* fn) { // Mark the constants. wrenGrayBuffer(vm, &fn->constants); // Mark the module it belongs to, in case it's been unloaded. wrenGrayObj(vm, (Obj*)fn->module); // Keep track of how much memory is still in use. vm->bytesAllocated += sizeof(ObjFn); vm->bytesAllocated += sizeof(uint8_t) * fn->code.capacity; vm->bytesAllocated += sizeof(Value) * fn->constants.capacity; // The debug line number buffer. vm->bytesAllocated += sizeof(int) * fn->code.capacity; // TODO: What about the function name? } static void blackenForeign(WrenVM* vm, ObjForeign* foreign) { // TODO: Keep track of how much memory the foreign object uses. We can store // this in each foreign object, but it will balloon the size. We may not want // that much overhead. One option would be to let the foreign class register // a C function that returns a size for the object. That way the VM doesn't // always have to explicitly store it. } static void blackenInstance(WrenVM* vm, ObjInstance* instance) { wrenGrayObj(vm, (Obj*)instance->obj.classObj); // Mark the fields. for (int i = 0; i < instance->obj.classObj->numFields; i++) { wrenGrayValue(vm, instance->fields[i]); } // Keep track of how much memory is still in use. vm->bytesAllocated += sizeof(ObjInstance); vm->bytesAllocated += sizeof(Value) * instance->obj.classObj->numFields; } static void blackenList(WrenVM* vm, ObjList* list) { // Mark the elements. wrenGrayBuffer(vm, &list->elements); // Keep track of how much memory is still in use. vm->bytesAllocated += sizeof(ObjList); vm->bytesAllocated += sizeof(Value) * list->elements.capacity; } static void blackenMap(WrenVM* vm, ObjMap* map) { // Mark the entries. for (uint32_t i = 0; i < map->capacity; i++) { MapEntry* entry = &map->entries[i]; if (IS_UNDEFINED(entry->key)) continue; wrenGrayValue(vm, entry->key); wrenGrayValue(vm, entry->value); } // Keep track of how much memory is still in use. vm->bytesAllocated += sizeof(ObjMap); vm->bytesAllocated += sizeof(MapEntry) * map->capacity; } static void blackenModule(WrenVM* vm, ObjModule* module) { // Top-level variables. for (int i = 0; i < module->variables.count; i++) { wrenGrayValue(vm, module->variables.data[i]); } wrenBlackenSymbolTable(vm, &module->variableNames); wrenGrayObj(vm, (Obj*)module->name); // Keep track of how much memory is still in use. vm->bytesAllocated += sizeof(ObjModule); } static void blackenRange(WrenVM* vm, ObjRange* range) { // Keep track of how much memory is still in use. vm->bytesAllocated += sizeof(ObjRange); } static void blackenString(WrenVM* vm, ObjString* string) { // Keep track of how much memory is still in use. vm->bytesAllocated += sizeof(ObjString) + string->length + 1; } static void blackenUpvalue(WrenVM* vm, ObjUpvalue* upvalue) { // Mark the closed-over object (in case it is closed). wrenGrayValue(vm, upvalue->closed); // Keep track of how much memory is still in use. vm->bytesAllocated += sizeof(ObjUpvalue); } static void blackenObject(WrenVM* vm, Obj* obj) { #if WREN_DEBUG_TRACE_MEMORY printf("mark "); wrenDumpValue(OBJ_VAL(obj)); printf(" @ %p\n", obj); #endif // Traverse the object's fields. switch (obj->type) { case OBJ_CLASS: blackenClass( vm, (ObjClass*) obj); break; case OBJ_CLOSURE: blackenClosure( vm, (ObjClosure*) obj); break; case OBJ_FIBER: blackenFiber( vm, (ObjFiber*) obj); break; case OBJ_FN: blackenFn( vm, (ObjFn*) obj); break; case OBJ_FOREIGN: blackenForeign( vm, (ObjForeign*) obj); break; case OBJ_INSTANCE: blackenInstance(vm, (ObjInstance*)obj); break; case OBJ_LIST: blackenList( vm, (ObjList*) obj); break; case OBJ_MAP: blackenMap( vm, (ObjMap*) obj); break; case OBJ_MODULE: blackenModule( vm, (ObjModule*) obj); break; case OBJ_RANGE: blackenRange( vm, (ObjRange*) obj); break; case OBJ_STRING: blackenString( vm, (ObjString*) obj); break; case OBJ_UPVALUE: blackenUpvalue( vm, (ObjUpvalue*) obj); break; } } void wrenBlackenObjects(WrenVM* vm) { while (vm->grayCount > 0) { // Pop an item from the gray stack. Obj* obj = vm->gray[--vm->grayCount]; blackenObject(vm, obj); } } void wrenFreeObj(WrenVM* vm, Obj* obj) { #if WREN_DEBUG_TRACE_MEMORY printf("free "); wrenDumpValue(OBJ_VAL(obj)); printf(" @ %p\n", obj); #endif switch (obj->type) { case OBJ_CLASS: wrenMethodBufferClear(vm, &((ObjClass*)obj)->methods); break; case OBJ_FIBER: { ObjFiber* fiber = (ObjFiber*)obj; DEALLOCATE(vm, fiber->frames); DEALLOCATE(vm, fiber->stack); break; } case OBJ_FN: { ObjFn* fn = (ObjFn*)obj; wrenValueBufferClear(vm, &fn->constants); wrenByteBufferClear(vm, &fn->code); wrenIntBufferClear(vm, &fn->debug->sourceLines); DEALLOCATE(vm, fn->debug->name); DEALLOCATE(vm, fn->debug); break; } case OBJ_FOREIGN: wrenFinalizeForeign(vm, (ObjForeign*)obj); break; case OBJ_LIST: wrenValueBufferClear(vm, &((ObjList*)obj)->elements); break; case OBJ_MAP: DEALLOCATE(vm, ((ObjMap*)obj)->entries); break; case OBJ_MODULE: wrenSymbolTableClear(vm, &((ObjModule*)obj)->variableNames); wrenValueBufferClear(vm, &((ObjModule*)obj)->variables); break; case OBJ_CLOSURE: case OBJ_INSTANCE: case OBJ_RANGE: case OBJ_STRING: case OBJ_UPVALUE: break; } DEALLOCATE(vm, obj); } ObjClass* wrenGetClass(WrenVM* vm, Value value) { return wrenGetClassInline(vm, value); } bool wrenValuesEqual(Value a, Value b) { if (wrenValuesSame(a, b)) return true; // If we get here, it's only possible for two heap-allocated immutable objects // to be equal. if (!IS_OBJ(a) || !IS_OBJ(b)) return false; Obj* aObj = AS_OBJ(a); Obj* bObj = AS_OBJ(b); // Must be the same type. if (aObj->type != bObj->type) return false; switch (aObj->type) { case OBJ_RANGE: { ObjRange* aRange = (ObjRange*)aObj; ObjRange* bRange = (ObjRange*)bObj; return aRange->from == bRange->from && aRange->to == bRange->to && aRange->isInclusive == bRange->isInclusive; } case OBJ_STRING: { ObjString* aString = (ObjString*)aObj; ObjString* bString = (ObjString*)bObj; return aString->hash == bString->hash && wrenStringEqualsCString(aString, bString->value, bString->length); } default: // All other types are only equal if they are same, which they aren't if // we get here. return false; } } // End file "wren_value.c" // Begin file "wren_utils.c" #include DEFINE_BUFFER(Byte, uint8_t); DEFINE_BUFFER(Int, int); DEFINE_BUFFER(String, ObjString*); void wrenSymbolTableInit(SymbolTable* symbols) { wrenStringBufferInit(symbols); } void wrenSymbolTableClear(WrenVM* vm, SymbolTable* symbols) { wrenStringBufferClear(vm, symbols); } int wrenSymbolTableAdd(WrenVM* vm, SymbolTable* symbols, const char* name, size_t length) { ObjString* symbol = AS_STRING(wrenNewStringLength(vm, name, length)); wrenPushRoot(vm, &symbol->obj); wrenStringBufferWrite(vm, symbols, symbol); wrenPopRoot(vm); return symbols->count - 1; } int wrenSymbolTableEnsure(WrenVM* vm, SymbolTable* symbols, const char* name, size_t length) { // See if the symbol is already defined. int existing = wrenSymbolTableFind(symbols, name, length); if (existing != -1) return existing; // New symbol, so add it. return wrenSymbolTableAdd(vm, symbols, name, length); } int wrenSymbolTableFind(const SymbolTable* symbols, const char* name, size_t length) { // See if the symbol is already defined. // TODO: O(n). Do something better. for (int i = 0; i < symbols->count; i++) { if (wrenStringEqualsCString(symbols->data[i], name, length)) return i; } return -1; } void wrenBlackenSymbolTable(WrenVM* vm, SymbolTable* symbolTable) { for (int i = 0; i < symbolTable->count; i++) { wrenGrayObj(vm, &symbolTable->data[i]->obj); } // Keep track of how much memory is still in use. vm->bytesAllocated += symbolTable->capacity * sizeof(*symbolTable->data); } int wrenUtf8EncodeNumBytes(int value) { ASSERT(value >= 0, "Cannot encode a negative value."); if (value <= 0x7f) return 1; if (value <= 0x7ff) return 2; if (value <= 0xffff) return 3; if (value <= 0x10ffff) return 4; return 0; } int wrenUtf8Encode(int value, uint8_t* bytes) { if (value <= 0x7f) { // Single byte (i.e. fits in ASCII). *bytes = value & 0x7f; return 1; } else if (value <= 0x7ff) { // Two byte sequence: 110xxxxx 10xxxxxx. *bytes = 0xc0 | ((value & 0x7c0) >> 6); bytes++; *bytes = 0x80 | (value & 0x3f); return 2; } else if (value <= 0xffff) { // Three byte sequence: 1110xxxx 10xxxxxx 10xxxxxx. *bytes = 0xe0 | ((value & 0xf000) >> 12); bytes++; *bytes = 0x80 | ((value & 0xfc0) >> 6); bytes++; *bytes = 0x80 | (value & 0x3f); return 3; } else if (value <= 0x10ffff) { // Four byte sequence: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx. *bytes = 0xf0 | ((value & 0x1c0000) >> 18); bytes++; *bytes = 0x80 | ((value & 0x3f000) >> 12); bytes++; *bytes = 0x80 | ((value & 0xfc0) >> 6); bytes++; *bytes = 0x80 | (value & 0x3f); return 4; } // Invalid Unicode value. See: http://tools.ietf.org/html/rfc3629 UNREACHABLE(); return 0; } int wrenUtf8Decode(const uint8_t* bytes, uint32_t length) { // Single byte (i.e. fits in ASCII). if (*bytes <= 0x7f) return *bytes; int value; uint32_t remainingBytes; if ((*bytes & 0xe0) == 0xc0) { // Two byte sequence: 110xxxxx 10xxxxxx. value = *bytes & 0x1f; remainingBytes = 1; } else if ((*bytes & 0xf0) == 0xe0) { // Three byte sequence: 1110xxxx 10xxxxxx 10xxxxxx. value = *bytes & 0x0f; remainingBytes = 2; } else if ((*bytes & 0xf8) == 0xf0) { // Four byte sequence: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx. value = *bytes & 0x07; remainingBytes = 3; } else { // Invalid UTF-8 sequence. return -1; } // Don't read past the end of the buffer on truncated UTF-8. if (remainingBytes > length - 1) return -1; while (remainingBytes > 0) { bytes++; remainingBytes--; // Remaining bytes must be of form 10xxxxxx. if ((*bytes & 0xc0) != 0x80) return -1; value = value << 6 | (*bytes & 0x3f); } return value; } int wrenUtf8DecodeNumBytes(uint8_t byte) { // If the byte starts with 10xxxxx, it's the middle of a UTF-8 sequence, so // don't count it at all. if ((byte & 0xc0) == 0x80) return 0; // The first byte's high bits tell us how many bytes are in the UTF-8 // sequence. if ((byte & 0xf8) == 0xf0) return 4; if ((byte & 0xf0) == 0xe0) return 3; if ((byte & 0xe0) == 0xc0) return 2; return 1; } // From: http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2Float int wrenPowerOf2Ceil(int n) { n--; n |= n >> 1; n |= n >> 2; n |= n >> 4; n |= n >> 8; n |= n >> 16; n++; return n; } uint32_t wrenValidateIndex(uint32_t count, int64_t value) { // Negative indices count from the end. if (value < 0) value = count + value; // Check bounds. if (value >= 0 && value < count) return (uint32_t)value; return UINT32_MAX; } // End file "wren_utils.c" // Begin file "wren_vm.c" #include #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); #endif #endif // End file "wren_opt_meta.h" #endif #if WREN_OPT_RANDOM // Begin file "wren_opt_random.h" #ifndef wren_opt_random_h #define wren_opt_random_h #if WREN_OPT_RANDOM const char* wrenRandomSource(); WrenForeignClassMethods wrenRandomBindForeignClass(WrenVM* vm, const char* module, const char* className); WrenForeignMethodFn wrenRandomBindForeignMethod(WrenVM* vm, const char* className, bool isStatic, const char* signature); #endif #endif // End file "wren_opt_random.h" #endif #if WREN_DEBUG_TRACE_MEMORY || WREN_DEBUG_TRACE_GC #include #include #endif // The behavior of realloc() when the size is 0 is implementation defined. It // may return a non-NULL pointer which must not be dereferenced but nevertheless // should be freed. To prevent that, we avoid calling realloc() with a zero // size. static void* defaultReallocate(void* ptr, size_t newSize, void* _) { if (newSize == 0) { free(ptr); return NULL; } return realloc(ptr, newSize); } int wrenGetVersionNumber() { return WREN_VERSION_NUMBER; } void wrenInitConfiguration(WrenConfiguration* config) { config->reallocateFn = defaultReallocate; config->resolveModuleFn = NULL; config->loadModuleFn = NULL; config->bindForeignMethodFn = NULL; config->bindForeignClassFn = NULL; config->writeFn = NULL; config->errorFn = NULL; config->initialHeapSize = 1024 * 1024 * 10; config->minHeapSize = 1024 * 1024; config->heapGrowthPercent = 50; config->userData = NULL; } WrenVM* wrenNewVM(WrenConfiguration* config) { WrenReallocateFn reallocate = defaultReallocate; void* userData = NULL; if (config != NULL) { userData = config->userData; reallocate = config->reallocateFn ? config->reallocateFn : defaultReallocate; } WrenVM* vm = (WrenVM*)reallocate(NULL, sizeof(*vm), userData); memset(vm, 0, sizeof(WrenVM)); // Copy the configuration if given one. if (config != NULL) { memcpy(&vm->config, config, sizeof(WrenConfiguration)); // We choose to set this after copying, // rather than modifying the user config pointer vm->config.reallocateFn = reallocate; } else { wrenInitConfiguration(&vm->config); } // TODO: Should we allocate and free this during a GC? vm->grayCount = 0; // TODO: Tune this. vm->grayCapacity = 4; vm->gray = (Obj**)reallocate(NULL, vm->grayCapacity * sizeof(Obj*), userData); vm->nextGC = vm->config.initialHeapSize; wrenSymbolTableInit(&vm->methodNames); vm->modules = wrenNewMap(vm); wrenInitializeCore(vm); return vm; } void wrenFreeVM(WrenVM* vm) { ASSERT(vm->methodNames.count > 0, "VM appears to have already been freed."); // Free all of the GC objects. Obj* obj = vm->first; while (obj != NULL) { Obj* next = obj->next; wrenFreeObj(vm, obj); obj = next; } // Free up the GC gray set. vm->gray = (Obj**)vm->config.reallocateFn(vm->gray, 0, vm->config.userData); // Tell the user if they didn't free any handles. We don't want to just free // them here because the host app may still have pointers to them that they // may try to use. Better to tell them about the bug early. ASSERT(vm->handles == NULL, "All handles have not been released."); wrenSymbolTableClear(vm, &vm->methodNames); DEALLOCATE(vm, vm); } void wrenCollectGarbage(WrenVM* vm) { #if WREN_DEBUG_TRACE_MEMORY || WREN_DEBUG_TRACE_GC printf("-- gc --\n"); size_t before = vm->bytesAllocated; double startTime = (double)clock() / CLOCKS_PER_SEC; #endif // Mark all reachable objects. // Reset this. As we mark objects, their size will be counted again so that // we can track how much memory is in use without needing to know the size // of each *freed* object. // // This is important because when freeing an unmarked object, we don't always // know how much memory it is using. For example, when freeing an instance, // we need to know its class to know how big it is, but its class may have // already been freed. vm->bytesAllocated = 0; wrenGrayObj(vm, (Obj*)vm->modules); // Temporary roots. for (int i = 0; i < vm->numTempRoots; i++) { wrenGrayObj(vm, vm->tempRoots[i]); } // The current fiber. wrenGrayObj(vm, (Obj*)vm->fiber); // The handles. for (WrenHandle* handle = vm->handles; handle != NULL; handle = handle->next) { wrenGrayValue(vm, handle->value); } // Any object the compiler is using (if there is one). if (vm->compiler != NULL) wrenMarkCompiler(vm, vm->compiler); // Method names. wrenBlackenSymbolTable(vm, &vm->methodNames); // Now that we have grayed the roots, do a depth-first search over all of the // reachable objects. wrenBlackenObjects(vm); // Collect the white objects. Obj** obj = &vm->first; while (*obj != NULL) { if (!((*obj)->isDark)) { // This object wasn't reached, so remove it from the list and free it. Obj* unreached = *obj; *obj = unreached->next; wrenFreeObj(vm, unreached); } else { // This object was reached, so unmark it (for the next GC) and move on to // the next. (*obj)->isDark = false; obj = &(*obj)->next; } } // Calculate the next gc point, this is the current allocation plus // a configured percentage of the current allocation. vm->nextGC = vm->bytesAllocated + ((vm->bytesAllocated * vm->config.heapGrowthPercent) / 100); if (vm->nextGC < vm->config.minHeapSize) vm->nextGC = vm->config.minHeapSize; #if WREN_DEBUG_TRACE_MEMORY || WREN_DEBUG_TRACE_GC double elapsed = ((double)clock() / CLOCKS_PER_SEC) - startTime; // Explicit cast because size_t has different sizes on 32-bit and 64-bit and // we need a consistent type for the format string. printf("GC %lu before, %lu after (%lu collected), next at %lu. Took %.3fms.\n", (unsigned long)before, (unsigned long)vm->bytesAllocated, (unsigned long)(before - vm->bytesAllocated), (unsigned long)vm->nextGC, elapsed*1000.0); #endif } void* wrenReallocate(WrenVM* vm, void* memory, size_t oldSize, size_t newSize) { #if WREN_DEBUG_TRACE_MEMORY // Explicit cast because size_t has different sizes on 32-bit and 64-bit and // we need a consistent type for the format string. printf("reallocate %p %lu -> %lu\n", memory, (unsigned long)oldSize, (unsigned long)newSize); #endif // If new bytes are being allocated, add them to the total count. If objects // are being completely deallocated, we don't track that (since we don't // track the original size). Instead, that will be handled while marking // during the next GC. vm->bytesAllocated += newSize - oldSize; #if WREN_DEBUG_GC_STRESS // Since collecting calls this function to free things, make sure we don't // recurse. if (newSize > 0) wrenCollectGarbage(vm); #else if (newSize > 0 && vm->bytesAllocated > vm->nextGC) wrenCollectGarbage(vm); #endif return vm->config.reallocateFn(memory, newSize, vm->config.userData); } // Captures the local variable [local] into an [Upvalue]. If that local is // already in an upvalue, the existing one will be used. (This is important to // ensure that multiple closures closing over the same variable actually see // the same variable.) Otherwise, it will create a new open upvalue and add it // the fiber's list of upvalues. static ObjUpvalue* captureUpvalue(WrenVM* vm, ObjFiber* fiber, Value* local) { // If there are no open upvalues at all, we must need a new one. if (fiber->openUpvalues == NULL) { fiber->openUpvalues = wrenNewUpvalue(vm, local); return fiber->openUpvalues; } ObjUpvalue* prevUpvalue = NULL; ObjUpvalue* upvalue = fiber->openUpvalues; // Walk towards the bottom of the stack until we find a previously existing // upvalue or pass where it should be. while (upvalue != NULL && upvalue->value > local) { prevUpvalue = upvalue; upvalue = upvalue->next; } // Found an existing upvalue for this local. if (upvalue != NULL && upvalue->value == local) return upvalue; // We've walked past this local on the stack, so there must not be an // upvalue for it already. Make a new one and link it in in the right // place to keep the list sorted. ObjUpvalue* createdUpvalue = wrenNewUpvalue(vm, local); if (prevUpvalue == NULL) { // The new one is the first one in the list. fiber->openUpvalues = createdUpvalue; } else { prevUpvalue->next = createdUpvalue; } createdUpvalue->next = upvalue; return createdUpvalue; } // Closes any open upvalues that have been created for stack slots at [last] // and above. static void closeUpvalues(ObjFiber* fiber, Value* last) { while (fiber->openUpvalues != NULL && fiber->openUpvalues->value >= last) { ObjUpvalue* upvalue = fiber->openUpvalues; // Move the value into the upvalue itself and point the upvalue to it. upvalue->closed = *upvalue->value; upvalue->value = &upvalue->closed; // Remove it from the open upvalue list. fiber->openUpvalues = upvalue->next; } } // Looks up a foreign method in [moduleName] on [className] with [signature]. // // This will try the host's foreign method binder first. If that fails, it // falls back to handling the built-in modules. static WrenForeignMethodFn findForeignMethod(WrenVM* vm, const char* moduleName, const char* className, bool isStatic, const char* signature) { WrenForeignMethodFn method = NULL; if (vm->config.bindForeignMethodFn != NULL) { method = vm->config.bindForeignMethodFn(vm, moduleName, className, isStatic, signature); } // If the host didn't provide it, see if it's an optional one. if (method == NULL) { #if WREN_OPT_META if (strcmp(moduleName, "meta") == 0) { method = wrenMetaBindForeignMethod(vm, className, isStatic, signature); } #endif #if WREN_OPT_RANDOM if (strcmp(moduleName, "random") == 0) { method = wrenRandomBindForeignMethod(vm, className, isStatic, signature); } #endif } return method; } // Defines [methodValue] as a method on [classObj]. // // Handles both foreign methods where [methodValue] is a string containing the // method's signature and Wren methods where [methodValue] is a function. // // Aborts the current fiber if the method is a foreign method that could not be // found. static void bindMethod(WrenVM* vm, int methodType, int symbol, ObjModule* module, ObjClass* classObj, Value methodValue) { const char* className = classObj->name->value; if (methodType == CODE_METHOD_STATIC) classObj = classObj->obj.classObj; Method method; if (IS_STRING(methodValue)) { const char* name = AS_CSTRING(methodValue); method.type = METHOD_FOREIGN; method.as.foreign = findForeignMethod(vm, module->name->value, className, methodType == CODE_METHOD_STATIC, name); if (method.as.foreign == NULL) { vm->fiber->error = wrenStringFormat(vm, "Could not find foreign method '@' for class $ in module '$'.", methodValue, classObj->name->value, module->name->value); return; } } else { method.as.closure = AS_CLOSURE(methodValue); method.type = METHOD_BLOCK; // Patch up the bytecode now that we know the superclass. wrenBindMethodCode(classObj, method.as.closure->fn); } wrenBindMethod(vm, classObj, symbol, method); } static void callForeign(WrenVM* vm, ObjFiber* fiber, WrenForeignMethodFn foreign, int numArgs) { ASSERT(vm->apiStack == NULL, "Cannot already be in foreign call."); vm->apiStack = fiber->stackTop - numArgs; foreign(vm); // Discard the stack slots for the arguments and temporaries but leave one // for the result. fiber->stackTop = vm->apiStack + 1; vm->apiStack = NULL; } // Handles the current fiber having aborted because of an error. // // Walks the call chain of fibers, aborting each one until it hits a fiber that // handles the error. If none do, tells the VM to stop. static void runtimeError(WrenVM* vm) { ASSERT(wrenHasError(vm->fiber), "Should only call this after an error."); ObjFiber* current = vm->fiber; Value error = current->error; while (current != NULL) { // Every fiber along the call chain gets aborted with the same error. current->error = error; // If the caller ran this fiber using "try", give it the error and stop. if (current->state == FIBER_TRY) { // Make the caller's try method return the error message. current->caller->stackTop[-1] = vm->fiber->error; vm->fiber = current->caller; return; } // Otherwise, unhook the caller since we will never resume and return to it. ObjFiber* caller = current->caller; current->caller = NULL; current = caller; } // If we got here, nothing caught the error, so show the stack trace. wrenDebugPrintStackTrace(vm); vm->fiber = NULL; vm->apiStack = NULL; } // Aborts the current fiber with an appropriate method not found error for a // method with [symbol] on [classObj]. static void methodNotFound(WrenVM* vm, ObjClass* classObj, int symbol) { vm->fiber->error = wrenStringFormat(vm, "@ does not implement '$'.", OBJ_VAL(classObj->name), vm->methodNames.data[symbol]->value); } // Looks up the previously loaded module with [name]. // // Returns `NULL` if no module with that name has been loaded. static ObjModule* getModule(WrenVM* vm, Value name) { Value moduleValue = wrenMapGet(vm->modules, name); return !IS_UNDEFINED(moduleValue) ? AS_MODULE(moduleValue) : NULL; } static ObjClosure* compileInModule(WrenVM* vm, Value name, const char* source, bool isExpression, bool printErrors) { // See if the module has already been loaded. ObjModule* module = getModule(vm, name); if (module == NULL) { module = wrenNewModule(vm, AS_STRING(name)); // It's possible for the wrenMapSet below to resize the modules map, // and trigger a GC while doing so. When this happens it will collect // the module we've just created. Once in the map it is safe. wrenPushRoot(vm, (Obj*)module); // Store it in the VM's module registry so we don't load the same module // multiple times. wrenMapSet(vm, vm->modules, name, OBJ_VAL(module)); wrenPopRoot(vm); // Implicitly import the core module. ObjModule* coreModule = getModule(vm, NULL_VAL); for (int i = 0; i < coreModule->variables.count; i++) { wrenDefineVariable(vm, module, coreModule->variableNames.data[i]->value, coreModule->variableNames.data[i]->length, coreModule->variables.data[i], NULL); } } ObjFn* fn = wrenCompile(vm, module, source, isExpression, printErrors); if (fn == NULL) { // TODO: Should we still store the module even if it didn't compile? return NULL; } // Functions are always wrapped in closures. wrenPushRoot(vm, (Obj*)fn); ObjClosure* closure = wrenNewClosure(vm, fn); wrenPopRoot(vm); // fn. return closure; } // Verifies that [superclassValue] is a valid object to inherit from. That // means it must be a class and cannot be the class of any built-in type. // // Also validates that it doesn't result in a class with too many fields and // the other limitations foreign classes have. // // If successful, returns `null`. Otherwise, returns a string for the runtime // error message. static Value validateSuperclass(WrenVM* vm, Value name, Value superclassValue, int numFields) { // Make sure the superclass is a class. if (!IS_CLASS(superclassValue)) { return wrenStringFormat(vm, "Class '@' cannot inherit from a non-class object.", name); } // Make sure it doesn't inherit from a sealed built-in type. Primitive methods // on these classes assume the instance is one of the other Obj___ types and // will fail horribly if it's actually an ObjInstance. ObjClass* superclass = AS_CLASS(superclassValue); if (superclass == vm->classClass || superclass == vm->fiberClass || superclass == vm->fnClass || // Includes OBJ_CLOSURE. superclass == vm->listClass || superclass == vm->mapClass || superclass == vm->rangeClass || superclass == vm->stringClass || superclass == vm->boolClass || superclass == vm->nullClass || superclass == vm->numClass) { return wrenStringFormat(vm, "Class '@' cannot inherit from built-in class '@'.", name, OBJ_VAL(superclass->name)); } if (superclass->numFields == -1) { return wrenStringFormat(vm, "Class '@' cannot inherit from foreign class '@'.", name, OBJ_VAL(superclass->name)); } if (numFields == -1 && superclass->numFields > 0) { return wrenStringFormat(vm, "Foreign class '@' may not inherit from a class with fields.", name); } if (superclass->numFields + numFields > MAX_FIELDS) { return wrenStringFormat(vm, "Class '@' may not have more than 255 fields, including inherited " "ones.", name); } return NULL_VAL; } static void bindForeignClass(WrenVM* vm, ObjClass* classObj, ObjModule* module) { WrenForeignClassMethods methods; methods.allocate = NULL; methods.finalize = NULL; // Check the optional built-in module first so the host can override it. if (vm->config.bindForeignClassFn != NULL) { methods = vm->config.bindForeignClassFn(vm, module->name->value, classObj->name->value); } // If the host didn't provide it, see if it's a built in optional module. if (methods.allocate == NULL && methods.finalize == NULL) { #if WREN_OPT_RANDOM if (strcmp(module->name->value, "random") == 0) { methods = wrenRandomBindForeignClass(vm, module->name->value, classObj->name->value); } #endif } Method method; method.type = METHOD_FOREIGN; // Add the symbol even if there is no allocator so we can ensure that the // symbol itself is always in the symbol table. int symbol = wrenSymbolTableEnsure(vm, &vm->methodNames, "", 10); if (methods.allocate != NULL) { method.as.foreign = methods.allocate; wrenBindMethod(vm, classObj, symbol, method); } // Add the symbol even if there is no finalizer so we can ensure that the // symbol itself is always in the symbol table. symbol = wrenSymbolTableEnsure(vm, &vm->methodNames, "", 10); if (methods.finalize != NULL) { method.as.foreign = (WrenForeignMethodFn)methods.finalize; wrenBindMethod(vm, classObj, symbol, method); } } // Completes the process for creating a new class. // // The class attributes instance and the class itself should be on the // top of the fiber's stack. // // This process handles moving the attribute data for a class from // compile time to runtime, since it now has all the attributes associated // with a class, including for methods. static void endClass(WrenVM* vm) { // Pull the attributes and class off the stack Value attributes = vm->fiber->stackTop[-2]; Value classValue = vm->fiber->stackTop[-1]; // Remove the stack items vm->fiber->stackTop -= 2; ObjClass* classObj = AS_CLASS(classValue); classObj->attributes = attributes; } // Creates a new class. // // If [numFields] is -1, the class is a foreign class. The name and superclass // should be on top of the fiber's stack. After calling this, the top of the // stack will contain the new class. // // Aborts the current fiber if an error occurs. static void createClass(WrenVM* vm, int numFields, ObjModule* module) { // Pull the name and superclass off the stack. Value name = vm->fiber->stackTop[-2]; Value superclass = vm->fiber->stackTop[-1]; // We have two values on the stack and we are going to leave one, so discard // the other slot. vm->fiber->stackTop--; vm->fiber->error = validateSuperclass(vm, name, superclass, numFields); if (wrenHasError(vm->fiber)) return; ObjClass* classObj = wrenNewClass(vm, AS_CLASS(superclass), numFields, AS_STRING(name)); vm->fiber->stackTop[-1] = OBJ_VAL(classObj); if (numFields == -1) bindForeignClass(vm, classObj, module); } static void createForeign(WrenVM* vm, ObjFiber* fiber, Value* stack) { ObjClass* classObj = AS_CLASS(stack[0]); ASSERT(classObj->numFields == -1, "Class must be a foreign class."); // TODO: Don't look up every time. int symbol = wrenSymbolTableFind(&vm->methodNames, "", 10); ASSERT(symbol != -1, "Should have defined symbol."); ASSERT(classObj->methods.count > symbol, "Class should have allocator."); Method* method = &classObj->methods.data[symbol]; ASSERT(method->type == METHOD_FOREIGN, "Allocator should be foreign."); // Pass the constructor arguments to the allocator as well. ASSERT(vm->apiStack == NULL, "Cannot already be in foreign call."); vm->apiStack = stack; method->as.foreign(vm); vm->apiStack = NULL; } void wrenFinalizeForeign(WrenVM* vm, ObjForeign* foreign) { // TODO: Don't look up every time. int symbol = wrenSymbolTableFind(&vm->methodNames, "", 10); ASSERT(symbol != -1, "Should have defined symbol."); // If there are no finalizers, don't finalize it. if (symbol == -1) return; // If the class doesn't have a finalizer, bail out. ObjClass* classObj = foreign->obj.classObj; if (symbol >= classObj->methods.count) return; Method* method = &classObj->methods.data[symbol]; if (method->type == METHOD_NONE) return; ASSERT(method->type == METHOD_FOREIGN, "Finalizer should be foreign."); WrenFinalizerFn finalizer = (WrenFinalizerFn)method->as.foreign; finalizer(foreign->data); } // Let the host resolve an imported module name if it wants to. static Value resolveModule(WrenVM* vm, Value name) { // If the host doesn't care to resolve, leave the name alone. if (vm->config.resolveModuleFn == NULL) return name; ObjFiber* fiber = vm->fiber; ObjFn* fn = fiber->frames[fiber->numFrames - 1].closure->fn; ObjString* importer = fn->module->name; const char* resolved = vm->config.resolveModuleFn(vm, importer->value, AS_CSTRING(name)); if (resolved == NULL) { vm->fiber->error = wrenStringFormat(vm, "Could not resolve module '@' imported from '@'.", name, OBJ_VAL(importer)); return NULL_VAL; } // If they resolved to the exact same string, we don't need to copy it. if (resolved == AS_CSTRING(name)) return name; // Copy the string into a Wren String object. name = wrenNewString(vm, resolved); DEALLOCATE(vm, (char*)resolved); return name; } static Value importModule(WrenVM* vm, Value name) { name = resolveModule(vm, name); // If the module is already loaded, we don't need to do anything. Value existing = wrenMapGet(vm->modules, name); if (!IS_UNDEFINED(existing)) return existing; wrenPushRoot(vm, AS_OBJ(name)); WrenLoadModuleResult result = {0}; const char* source = NULL; // Let the host try to provide the module. if (vm->config.loadModuleFn != NULL) { result = vm->config.loadModuleFn(vm, AS_CSTRING(name)); } // If the host didn't provide it, see if it's a built in optional module. if (result.source == NULL) { result.onComplete = NULL; ObjString* nameString = AS_STRING(name); #if WREN_OPT_META if (strcmp(nameString->value, "meta") == 0) result.source = wrenMetaSource(); #endif #if WREN_OPT_RANDOM if (strcmp(nameString->value, "random") == 0) result.source = wrenRandomSource(); #endif } if (result.source == NULL) { vm->fiber->error = wrenStringFormat(vm, "Could not load module '@'.", name); wrenPopRoot(vm); // name. return NULL_VAL; } ObjClosure* moduleClosure = compileInModule(vm, name, result.source, false, true); // Now that we're done, give the result back in case there's cleanup to do. if(result.onComplete) result.onComplete(vm, AS_CSTRING(name), result); if (moduleClosure == NULL) { vm->fiber->error = wrenStringFormat(vm, "Could not compile module '@'.", name); wrenPopRoot(vm); // name. return NULL_VAL; } wrenPopRoot(vm); // name. // Return the closure that executes the module. return OBJ_VAL(moduleClosure); } static Value getModuleVariable(WrenVM* vm, ObjModule* module, Value variableName) { ObjString* variable = AS_STRING(variableName); uint32_t variableEntry = wrenSymbolTableFind(&module->variableNames, variable->value, variable->length); // It's a runtime error if the imported variable does not exist. if (variableEntry != UINT32_MAX) { return module->variables.data[variableEntry]; } vm->fiber->error = wrenStringFormat(vm, "Could not find a variable named '@' in module '@'.", variableName, OBJ_VAL(module->name)); return NULL_VAL; } inline static bool checkArity(WrenVM* vm, Value value, int numArgs) { ASSERT(IS_CLOSURE(value), "Receiver must be a closure."); ObjFn* fn = AS_CLOSURE(value)->fn; // We only care about missing arguments, not extras. The "- 1" is because // numArgs includes the receiver, the function itself, which we don't want to // count. if (numArgs - 1 >= fn->arity) return true; vm->fiber->error = CONST_STRING(vm, "Function expects more arguments."); return false; } // The main bytecode interpreter loop. This is where the magic happens. It is // also, as you can imagine, highly performance critical. static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber) { // Remember the current fiber so we can find it if a GC happens. vm->fiber = fiber; fiber->state = FIBER_ROOT; // Hoist these into local variables. They are accessed frequently in the loop // but assigned less frequently. Keeping them in locals and updating them when // a call frame has been pushed or popped gives a large speed boost. register CallFrame* frame; register Value* stackStart; register uint8_t* ip; register ObjFn* fn; // These macros are designed to only be invoked within this function. #define PUSH(value) (*fiber->stackTop++ = value) #define POP() (*(--fiber->stackTop)) #define DROP() (fiber->stackTop--) #define PEEK() (*(fiber->stackTop - 1)) #define PEEK2() (*(fiber->stackTop - 2)) #define READ_BYTE() (*ip++) #define READ_SHORT() (ip += 2, (uint16_t)((ip[-2] << 8) | ip[-1])) // Use this before a CallFrame is pushed to store the local variables back // into the current one. #define STORE_FRAME() frame->ip = ip // Use this after a CallFrame has been pushed or popped to refresh the local // variables. #define LOAD_FRAME() \ do \ { \ frame = &fiber->frames[fiber->numFrames - 1]; \ stackStart = frame->stackStart; \ ip = frame->ip; \ fn = frame->closure->fn; \ } while (false) // Terminates the current fiber with error string [error]. If another calling // fiber is willing to catch the error, transfers control to it, otherwise // exits the interpreter. #define RUNTIME_ERROR() \ do \ { \ STORE_FRAME(); \ runtimeError(vm); \ if (vm->fiber == NULL) return WREN_RESULT_RUNTIME_ERROR; \ fiber = vm->fiber; \ LOAD_FRAME(); \ DISPATCH(); \ } while (false) #if WREN_DEBUG_TRACE_INSTRUCTIONS // Prints the stack and instruction before each instruction is executed. #define DEBUG_TRACE_INSTRUCTIONS() \ do \ { \ wrenDumpStack(fiber); \ wrenDumpInstruction(vm, fn, (int)(ip - fn->code.data)); \ } while (false) #else #define DEBUG_TRACE_INSTRUCTIONS() do { } while (false) #endif #if WREN_COMPUTED_GOTO static void* dispatchTable[] = { #define OPCODE(name, _) &&code_##name, // Begin file "wren_opcodes.h" // This defines the bytecode instructions used by the VM. It does so by invoking // an OPCODE() macro which is expected to be defined at the point that this is // included. (See: http://en.wikipedia.org/wiki/X_Macro for more.) // // The first argument is the name of the opcode. The second is its "stack // effect" -- the amount that the op code changes the size of the stack. A // stack effect of 1 means it pushes a value and the stack grows one larger. // -2 means it pops two values, etc. // // Note that the order of instructions here affects the order of the dispatch // table in the VM's interpreter loop. That in turn affects caching which // affects overall performance. Take care to run benchmarks if you change the // order here. // Load the constant at index [arg]. OPCODE(CONSTANT, 1) // Push null onto the stack. OPCODE(NULL, 1) // Push false onto the stack. OPCODE(FALSE, 1) // Push true onto the stack. OPCODE(TRUE, 1) // Pushes the value in the given local slot. OPCODE(LOAD_LOCAL_0, 1) OPCODE(LOAD_LOCAL_1, 1) OPCODE(LOAD_LOCAL_2, 1) OPCODE(LOAD_LOCAL_3, 1) OPCODE(LOAD_LOCAL_4, 1) OPCODE(LOAD_LOCAL_5, 1) OPCODE(LOAD_LOCAL_6, 1) OPCODE(LOAD_LOCAL_7, 1) OPCODE(LOAD_LOCAL_8, 1) // Note: The compiler assumes the following _STORE instructions always // immediately follow their corresponding _LOAD ones. // Pushes the value in local slot [arg]. OPCODE(LOAD_LOCAL, 1) // Stores the top of stack in local slot [arg]. Does not pop it. OPCODE(STORE_LOCAL, 0) // Pushes the value in upvalue [arg]. OPCODE(LOAD_UPVALUE, 1) // Stores the top of stack in upvalue [arg]. Does not pop it. OPCODE(STORE_UPVALUE, 0) // Pushes the value of the top-level variable in slot [arg]. OPCODE(LOAD_MODULE_VAR, 1) // Stores the top of stack in top-level variable slot [arg]. Does not pop it. OPCODE(STORE_MODULE_VAR, 0) // Pushes the value of the field in slot [arg] of the receiver of the current // function. This is used for regular field accesses on "this" directly in // methods. This instruction is faster than the more general CODE_LOAD_FIELD // instruction. OPCODE(LOAD_FIELD_THIS, 1) // Stores the top of the stack in field slot [arg] in the receiver of the // current value. Does not pop the value. This instruction is faster than the // more general CODE_LOAD_FIELD instruction. OPCODE(STORE_FIELD_THIS, 0) // Pops an instance and pushes the value of the field in slot [arg] of it. OPCODE(LOAD_FIELD, 0) // Pops an instance and stores the subsequent top of stack in field slot // [arg] in it. Does not pop the value. OPCODE(STORE_FIELD, -1) // Pop and discard the top of stack. OPCODE(POP, -1) // Invoke the method with symbol [arg]. The number indicates the number of // arguments (not including the receiver). OPCODE(CALL_0, 0) OPCODE(CALL_1, -1) OPCODE(CALL_2, -2) OPCODE(CALL_3, -3) OPCODE(CALL_4, -4) OPCODE(CALL_5, -5) OPCODE(CALL_6, -6) OPCODE(CALL_7, -7) OPCODE(CALL_8, -8) OPCODE(CALL_9, -9) OPCODE(CALL_10, -10) OPCODE(CALL_11, -11) OPCODE(CALL_12, -12) OPCODE(CALL_13, -13) OPCODE(CALL_14, -14) OPCODE(CALL_15, -15) OPCODE(CALL_16, -16) // Invoke a superclass method with symbol [arg]. The number indicates the // number of arguments (not including the receiver). OPCODE(SUPER_0, 0) OPCODE(SUPER_1, -1) OPCODE(SUPER_2, -2) OPCODE(SUPER_3, -3) OPCODE(SUPER_4, -4) OPCODE(SUPER_5, -5) OPCODE(SUPER_6, -6) OPCODE(SUPER_7, -7) OPCODE(SUPER_8, -8) OPCODE(SUPER_9, -9) OPCODE(SUPER_10, -10) OPCODE(SUPER_11, -11) OPCODE(SUPER_12, -12) OPCODE(SUPER_13, -13) OPCODE(SUPER_14, -14) OPCODE(SUPER_15, -15) OPCODE(SUPER_16, -16) // Jump the instruction pointer [arg] forward. OPCODE(JUMP, 0) // Jump the instruction pointer [arg] backward. OPCODE(LOOP, 0) // Pop and if not truthy then jump the instruction pointer [arg] forward. OPCODE(JUMP_IF, -1) // If the top of the stack is false, jump [arg] forward. Otherwise, pop and // continue. OPCODE(AND, -1) // If the top of the stack is non-false, jump [arg] forward. Otherwise, pop // and continue. OPCODE(OR, -1) // Close the upvalue for the local on the top of the stack, then pop it. OPCODE(CLOSE_UPVALUE, -1) // Exit from the current function and return the value on the top of the // stack. OPCODE(RETURN, 0) // Creates a closure for the function stored at [arg] in the constant table. // // Following the function argument is a number of arguments, two for each // upvalue. The first is true if the variable being captured is a local (as // opposed to an upvalue), and the second is the index of the local or // upvalue being captured. // // Pushes the created closure. OPCODE(CLOSURE, 1) // Creates a new instance of a class. // // Assumes the class object is in slot zero, and replaces it with the new // uninitialized instance of that class. This opcode is only emitted by the // compiler-generated constructor metaclass methods. OPCODE(CONSTRUCT, 0) // Creates a new instance of a foreign class. // // Assumes the class object is in slot zero, and replaces it with the new // uninitialized instance of that class. This opcode is only emitted by the // compiler-generated constructor metaclass methods. OPCODE(FOREIGN_CONSTRUCT, 0) // Creates a class. Top of stack is the superclass. Below that is a string for // the name of the class. Byte [arg] is the number of fields in the class. OPCODE(CLASS, -1) // Ends a class. // Atm the stack contains the class and the ClassAttributes (or null). OPCODE(END_CLASS, -2) // Creates a foreign class. Top of stack is the superclass. Below that is a // string for the name of the class. OPCODE(FOREIGN_CLASS, -1) // Define a method for symbol [arg]. The class receiving the method is popped // off the stack, then the function defining the body is popped. // // If a foreign method is being defined, the "function" will be a string // identifying the foreign method. Otherwise, it will be a function or // closure. OPCODE(METHOD_INSTANCE, -2) // Define a method for symbol [arg]. The class whose metaclass will receive // the method is popped off the stack, then the function defining the body is // popped. // // If a foreign method is being defined, the "function" will be a string // identifying the foreign method. Otherwise, it will be a function or // closure. OPCODE(METHOD_STATIC, -2) // This is executed at the end of the module's body. Pushes NULL onto the stack // as the "return value" of the import statement and stores the module as the // most recently imported one. OPCODE(END_MODULE, 1) // Import a module whose name is the string stored at [arg] in the constant // table. // // Pushes null onto the stack so that the fiber for the imported module can // replace that with a dummy value when it returns. (Fibers always return a // value when resuming a caller.) OPCODE(IMPORT_MODULE, 1) // Import a variable from the most recently imported module. The name of the // variable to import is at [arg] in the constant table. Pushes the loaded // variable's value. OPCODE(IMPORT_VARIABLE, 1) // This pseudo-instruction indicates the end of the bytecode. It should // always be preceded by a `CODE_RETURN`, so is never actually executed. OPCODE(END, 0) // End file "wren_opcodes.h" #undef OPCODE }; #define INTERPRET_LOOP DISPATCH(); #define CASE_CODE(name) code_##name #define DISPATCH() \ do \ { \ DEBUG_TRACE_INSTRUCTIONS(); \ goto *dispatchTable[instruction = (Code)READ_BYTE()]; \ } while (false) #else #define INTERPRET_LOOP \ loop: \ DEBUG_TRACE_INSTRUCTIONS(); \ switch (instruction = (Code)READ_BYTE()) #define CASE_CODE(name) case CODE_##name #define DISPATCH() goto loop #endif LOAD_FRAME(); Code instruction; INTERPRET_LOOP { CASE_CODE(LOAD_LOCAL_0): CASE_CODE(LOAD_LOCAL_1): CASE_CODE(LOAD_LOCAL_2): CASE_CODE(LOAD_LOCAL_3): CASE_CODE(LOAD_LOCAL_4): CASE_CODE(LOAD_LOCAL_5): CASE_CODE(LOAD_LOCAL_6): CASE_CODE(LOAD_LOCAL_7): CASE_CODE(LOAD_LOCAL_8): PUSH(stackStart[instruction - CODE_LOAD_LOCAL_0]); DISPATCH(); CASE_CODE(LOAD_LOCAL): PUSH(stackStart[READ_BYTE()]); DISPATCH(); CASE_CODE(LOAD_FIELD_THIS): { uint8_t field = READ_BYTE(); Value receiver = stackStart[0]; ASSERT(IS_INSTANCE(receiver), "Receiver should be instance."); ObjInstance* instance = AS_INSTANCE(receiver); ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field."); PUSH(instance->fields[field]); DISPATCH(); } CASE_CODE(POP): DROP(); DISPATCH(); CASE_CODE(NULL): PUSH(NULL_VAL); DISPATCH(); CASE_CODE(FALSE): PUSH(FALSE_VAL); DISPATCH(); CASE_CODE(TRUE): PUSH(TRUE_VAL); DISPATCH(); CASE_CODE(STORE_LOCAL): stackStart[READ_BYTE()] = PEEK(); DISPATCH(); CASE_CODE(CONSTANT): PUSH(fn->constants.data[READ_SHORT()]); DISPATCH(); { // The opcodes for doing method and superclass calls share a lot of code. // However, doing an if() test in the middle of the instruction sequence // to handle the bit that is special to super calls makes the non-super // call path noticeably slower. // // Instead, we do this old school using an explicit goto to share code for // everything at the tail end of the call-handling code that is the same // between normal and superclass calls. int numArgs; int symbol; Value* args; ObjClass* classObj; Method* method; CASE_CODE(CALL_0): CASE_CODE(CALL_1): CASE_CODE(CALL_2): CASE_CODE(CALL_3): CASE_CODE(CALL_4): CASE_CODE(CALL_5): CASE_CODE(CALL_6): CASE_CODE(CALL_7): CASE_CODE(CALL_8): CASE_CODE(CALL_9): CASE_CODE(CALL_10): CASE_CODE(CALL_11): CASE_CODE(CALL_12): CASE_CODE(CALL_13): CASE_CODE(CALL_14): CASE_CODE(CALL_15): CASE_CODE(CALL_16): // Add one for the implicit receiver argument. numArgs = instruction - CODE_CALL_0 + 1; symbol = READ_SHORT(); // The receiver is the first argument. args = fiber->stackTop - numArgs; classObj = wrenGetClassInline(vm, args[0]); goto completeCall; CASE_CODE(SUPER_0): CASE_CODE(SUPER_1): CASE_CODE(SUPER_2): CASE_CODE(SUPER_3): CASE_CODE(SUPER_4): CASE_CODE(SUPER_5): CASE_CODE(SUPER_6): CASE_CODE(SUPER_7): CASE_CODE(SUPER_8): CASE_CODE(SUPER_9): CASE_CODE(SUPER_10): CASE_CODE(SUPER_11): CASE_CODE(SUPER_12): CASE_CODE(SUPER_13): CASE_CODE(SUPER_14): CASE_CODE(SUPER_15): CASE_CODE(SUPER_16): // Add one for the implicit receiver argument. numArgs = instruction - CODE_SUPER_0 + 1; symbol = READ_SHORT(); // The receiver is the first argument. args = fiber->stackTop - numArgs; // The superclass is stored in a constant. classObj = AS_CLASS(fn->constants.data[READ_SHORT()]); goto completeCall; completeCall: // If the class's method table doesn't include the symbol, bail. if (symbol >= classObj->methods.count || (method = &classObj->methods.data[symbol])->type == METHOD_NONE) { methodNotFound(vm, classObj, symbol); RUNTIME_ERROR(); } switch (method->type) { case METHOD_PRIMITIVE: if (method->as.primitive(vm, args)) { // The result is now in the first arg slot. Discard the other // stack slots. fiber->stackTop -= numArgs - 1; } else { // An error, fiber switch, or call frame change occurred. STORE_FRAME(); // If we don't have a fiber to switch to, stop interpreting. fiber = vm->fiber; if (fiber == NULL) return WREN_RESULT_SUCCESS; if (wrenHasError(fiber)) RUNTIME_ERROR(); LOAD_FRAME(); } break; case METHOD_FUNCTION_CALL: if (!checkArity(vm, args[0], numArgs)) { RUNTIME_ERROR(); break; } STORE_FRAME(); method->as.primitive(vm, args); LOAD_FRAME(); break; case METHOD_FOREIGN: callForeign(vm, fiber, method->as.foreign, numArgs); if (wrenHasError(fiber)) RUNTIME_ERROR(); break; case METHOD_BLOCK: STORE_FRAME(); wrenCallFunction(vm, fiber, (ObjClosure*)method->as.closure, numArgs); LOAD_FRAME(); break; case METHOD_NONE: UNREACHABLE(); break; } DISPATCH(); } CASE_CODE(LOAD_UPVALUE): { ObjUpvalue** upvalues = frame->closure->upvalues; PUSH(*upvalues[READ_BYTE()]->value); DISPATCH(); } CASE_CODE(STORE_UPVALUE): { ObjUpvalue** upvalues = frame->closure->upvalues; *upvalues[READ_BYTE()]->value = PEEK(); DISPATCH(); } CASE_CODE(LOAD_MODULE_VAR): PUSH(fn->module->variables.data[READ_SHORT()]); DISPATCH(); CASE_CODE(STORE_MODULE_VAR): fn->module->variables.data[READ_SHORT()] = PEEK(); DISPATCH(); CASE_CODE(STORE_FIELD_THIS): { uint8_t field = READ_BYTE(); Value receiver = stackStart[0]; ASSERT(IS_INSTANCE(receiver), "Receiver should be instance."); ObjInstance* instance = AS_INSTANCE(receiver); ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field."); instance->fields[field] = PEEK(); DISPATCH(); } CASE_CODE(LOAD_FIELD): { uint8_t field = READ_BYTE(); Value receiver = POP(); ASSERT(IS_INSTANCE(receiver), "Receiver should be instance."); ObjInstance* instance = AS_INSTANCE(receiver); ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field."); PUSH(instance->fields[field]); DISPATCH(); } CASE_CODE(STORE_FIELD): { uint8_t field = READ_BYTE(); Value receiver = POP(); ASSERT(IS_INSTANCE(receiver), "Receiver should be instance."); ObjInstance* instance = AS_INSTANCE(receiver); ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field."); instance->fields[field] = PEEK(); DISPATCH(); } CASE_CODE(JUMP): { uint16_t offset = READ_SHORT(); ip += offset; DISPATCH(); } CASE_CODE(LOOP): { // Jump back to the top of the loop. uint16_t offset = READ_SHORT(); ip -= offset; DISPATCH(); } CASE_CODE(JUMP_IF): { uint16_t offset = READ_SHORT(); Value condition = POP(); if (wrenIsFalsyValue(condition)) ip += offset; DISPATCH(); } CASE_CODE(AND): { uint16_t offset = READ_SHORT(); Value condition = PEEK(); if (wrenIsFalsyValue(condition)) { // Short-circuit the right hand side. ip += offset; } else { // Discard the condition and evaluate the right hand side. DROP(); } DISPATCH(); } CASE_CODE(OR): { uint16_t offset = READ_SHORT(); Value condition = PEEK(); if (wrenIsFalsyValue(condition)) { // Discard the condition and evaluate the right hand side. DROP(); } else { // Short-circuit the right hand side. ip += offset; } DISPATCH(); } CASE_CODE(CLOSE_UPVALUE): // Close the upvalue for the local if we have one. closeUpvalues(fiber, fiber->stackTop - 1); DROP(); DISPATCH(); CASE_CODE(RETURN): { Value result = POP(); fiber->numFrames--; // Close any upvalues still in scope. closeUpvalues(fiber, stackStart); // If the fiber is complete, end it. if (fiber->numFrames == 0) { // See if there's another fiber to return to. If not, we're done. if (fiber->caller == NULL) { // Store the final result value at the beginning of the stack so the // C API can get it. fiber->stack[0] = result; fiber->stackTop = fiber->stack + 1; return WREN_RESULT_SUCCESS; } ObjFiber* resumingFiber = fiber->caller; fiber->caller = NULL; fiber = resumingFiber; vm->fiber = resumingFiber; // Store the result in the resuming fiber. fiber->stackTop[-1] = result; } else { // Store the result of the block in the first slot, which is where the // caller expects it. stackStart[0] = result; // Discard the stack slots for the call frame (leaving one slot for the // result). fiber->stackTop = frame->stackStart + 1; } LOAD_FRAME(); DISPATCH(); } CASE_CODE(CONSTRUCT): ASSERT(IS_CLASS(stackStart[0]), "'this' should be a class."); stackStart[0] = wrenNewInstance(vm, AS_CLASS(stackStart[0])); DISPATCH(); CASE_CODE(FOREIGN_CONSTRUCT): ASSERT(IS_CLASS(stackStart[0]), "'this' should be a class."); createForeign(vm, fiber, stackStart); if (wrenHasError(fiber)) RUNTIME_ERROR(); DISPATCH(); CASE_CODE(CLOSURE): { // Create the closure and push it on the stack before creating upvalues // so that it doesn't get collected. ObjFn* function = AS_FN(fn->constants.data[READ_SHORT()]); ObjClosure* closure = wrenNewClosure(vm, function); PUSH(OBJ_VAL(closure)); // Capture upvalues, if any. for (int i = 0; i < function->numUpvalues; i++) { uint8_t isLocal = READ_BYTE(); uint8_t index = READ_BYTE(); if (isLocal) { // Make an new upvalue to close over the parent's local variable. closure->upvalues[i] = captureUpvalue(vm, fiber, frame->stackStart + index); } else { // Use the same upvalue as the current call frame. closure->upvalues[i] = frame->closure->upvalues[index]; } } DISPATCH(); } CASE_CODE(END_CLASS): { endClass(vm); if (wrenHasError(fiber)) RUNTIME_ERROR(); DISPATCH(); } CASE_CODE(CLASS): { createClass(vm, READ_BYTE(), NULL); if (wrenHasError(fiber)) RUNTIME_ERROR(); DISPATCH(); } CASE_CODE(FOREIGN_CLASS): { createClass(vm, -1, fn->module); if (wrenHasError(fiber)) RUNTIME_ERROR(); DISPATCH(); } CASE_CODE(METHOD_INSTANCE): CASE_CODE(METHOD_STATIC): { uint16_t symbol = READ_SHORT(); ObjClass* classObj = AS_CLASS(PEEK()); Value method = PEEK2(); bindMethod(vm, instruction, symbol, fn->module, classObj, method); if (wrenHasError(fiber)) RUNTIME_ERROR(); DROP(); DROP(); DISPATCH(); } CASE_CODE(END_MODULE): { vm->lastModule = fn->module; PUSH(NULL_VAL); DISPATCH(); } CASE_CODE(IMPORT_MODULE): { // Make a slot on the stack for the module's fiber to place the return // value. It will be popped after this fiber is resumed. Store the // imported module's closure in the slot in case a GC happens when // invoking the closure. PUSH(importModule(vm, fn->constants.data[READ_SHORT()])); if (wrenHasError(fiber)) RUNTIME_ERROR(); // If we get a closure, call it to execute the module body. if (IS_CLOSURE(PEEK())) { STORE_FRAME(); ObjClosure* closure = AS_CLOSURE(PEEK()); wrenCallFunction(vm, fiber, closure, 1); LOAD_FRAME(); } else { // The module has already been loaded. Remember it so we can import // variables from it if needed. vm->lastModule = AS_MODULE(PEEK()); } DISPATCH(); } CASE_CODE(IMPORT_VARIABLE): { Value variable = fn->constants.data[READ_SHORT()]; ASSERT(vm->lastModule != NULL, "Should have already imported module."); Value result = getModuleVariable(vm, vm->lastModule, variable); if (wrenHasError(fiber)) RUNTIME_ERROR(); PUSH(result); DISPATCH(); } CASE_CODE(END): // A CODE_END should always be preceded by a CODE_RETURN. If we get here, // the compiler generated wrong code. UNREACHABLE(); } // We should only exit this function from an explicit return from CODE_RETURN // or a runtime error. UNREACHABLE(); return WREN_RESULT_RUNTIME_ERROR; #undef READ_BYTE #undef READ_SHORT } WrenHandle* wrenMakeCallHandle(WrenVM* vm, const char* signature) { ASSERT(signature != NULL, "Signature cannot be NULL."); int signatureLength = (int)strlen(signature); ASSERT(signatureLength > 0, "Signature cannot be empty."); // Count the number parameters the method expects. int numParams = 0; if (signature[signatureLength - 1] == ')') { for (int i = signatureLength - 1; i > 0 && signature[i] != '('; i--) { if (signature[i] == '_') numParams++; } } // Count subscript arguments. if (signature[0] == '[') { for (int i = 0; i < signatureLength && signature[i] != ']'; i++) { if (signature[i] == '_') numParams++; } } // Add the signatue to the method table. int method = wrenSymbolTableEnsure(vm, &vm->methodNames, signature, signatureLength); // Create a little stub function that assumes the arguments are on the stack // and calls the method. ObjFn* fn = wrenNewFunction(vm, NULL, numParams + 1); // Wrap the function in a closure and then in a handle. Do this here so it // doesn't get collected as we fill it in. WrenHandle* value = wrenMakeHandle(vm, OBJ_VAL(fn)); value->value = OBJ_VAL(wrenNewClosure(vm, fn)); wrenByteBufferWrite(vm, &fn->code, (uint8_t)(CODE_CALL_0 + numParams)); wrenByteBufferWrite(vm, &fn->code, (method >> 8) & 0xff); wrenByteBufferWrite(vm, &fn->code, method & 0xff); wrenByteBufferWrite(vm, &fn->code, CODE_RETURN); wrenByteBufferWrite(vm, &fn->code, CODE_END); wrenIntBufferFill(vm, &fn->debug->sourceLines, 0, 5); wrenFunctionBindName(vm, fn, signature, signatureLength); return value; } WrenInterpretResult wrenCall(WrenVM* vm, WrenHandle* method) { ASSERT(method != NULL, "Method cannot be NULL."); ASSERT(IS_CLOSURE(method->value), "Method must be a method handle."); ASSERT(vm->fiber != NULL, "Must set up arguments for call first."); ASSERT(vm->apiStack != NULL, "Must set up arguments for call first."); ASSERT(vm->fiber->numFrames == 0, "Can not call from a foreign method."); ObjClosure* closure = AS_CLOSURE(method->value); ASSERT(vm->fiber->stackTop - vm->fiber->stack >= closure->fn->arity, "Stack must have enough arguments for method."); // Clear the API stack. Now that wrenCall() has control, we no longer need // it. We use this being non-null to tell if re-entrant calls to foreign // methods are happening, so it's important to clear it out now so that you // can call foreign methods from within calls to wrenCall(). vm->apiStack = NULL; // Discard any extra temporary slots. We take for granted that the stub // function has exactly one slot for each argument. vm->fiber->stackTop = &vm->fiber->stack[closure->fn->maxSlots]; wrenCallFunction(vm, vm->fiber, closure, 0); WrenInterpretResult result = runInterpreter(vm, vm->fiber); // If the call didn't abort, then set up the API stack to point to the // beginning of the stack so the host can access the call's return value. if (vm->fiber != NULL) vm->apiStack = vm->fiber->stack; return result; } WrenHandle* wrenMakeHandle(WrenVM* vm, Value value) { if (IS_OBJ(value)) wrenPushRoot(vm, AS_OBJ(value)); // Make a handle for it. WrenHandle* handle = ALLOCATE(vm, WrenHandle); handle->value = value; if (IS_OBJ(value)) wrenPopRoot(vm); // Add it to the front of the linked list of handles. if (vm->handles != NULL) vm->handles->prev = handle; handle->prev = NULL; handle->next = vm->handles; vm->handles = handle; return handle; } void wrenReleaseHandle(WrenVM* vm, WrenHandle* handle) { ASSERT(handle != NULL, "Handle cannot be NULL."); // Update the VM's head pointer if we're releasing the first handle. if (vm->handles == handle) vm->handles = handle->next; // Unlink it from the list. if (handle->prev != NULL) handle->prev->next = handle->next; if (handle->next != NULL) handle->next->prev = handle->prev; // Clear it out. This isn't strictly necessary since we're going to free it, // but it makes for easier debugging. handle->prev = NULL; handle->next = NULL; handle->value = NULL_VAL; DEALLOCATE(vm, handle); } WrenInterpretResult wrenInterpret(WrenVM* vm, const char* module, const char* source) { ObjClosure* closure = wrenCompileSource(vm, module, source, false, true); if (closure == NULL) return WREN_RESULT_COMPILE_ERROR; wrenPushRoot(vm, (Obj*)closure); ObjFiber* fiber = wrenNewFiber(vm, closure); wrenPopRoot(vm); // closure. vm->apiStack = NULL; return runInterpreter(vm, fiber); } ObjClosure* wrenCompileSource(WrenVM* vm, const char* module, const char* source, bool isExpression, bool printErrors) { Value nameValue = NULL_VAL; if (module != NULL) { nameValue = wrenNewString(vm, module); wrenPushRoot(vm, AS_OBJ(nameValue)); } ObjClosure* closure = compileInModule(vm, nameValue, source, isExpression, printErrors); if (module != NULL) wrenPopRoot(vm); // nameValue. return closure; } Value wrenGetModuleVariable(WrenVM* vm, Value moduleName, Value variableName) { ObjModule* module = getModule(vm, moduleName); if (module == NULL) { vm->fiber->error = wrenStringFormat(vm, "Module '@' is not loaded.", moduleName); return NULL_VAL; } return getModuleVariable(vm, module, variableName); } Value wrenFindVariable(WrenVM* vm, ObjModule* module, const char* name) { int symbol = wrenSymbolTableFind(&module->variableNames, name, strlen(name)); return module->variables.data[symbol]; } int wrenDeclareVariable(WrenVM* vm, ObjModule* module, const char* name, size_t length, int line) { if (module->variables.count == MAX_MODULE_VARS) return -2; // Implicitly defined variables get a "value" that is the line where the // variable is first used. We'll use that later to report an error on the // right line. wrenValueBufferWrite(vm, &module->variables, NUM_VAL(line)); return wrenSymbolTableAdd(vm, &module->variableNames, name, length); } int wrenDefineVariable(WrenVM* vm, ObjModule* module, const char* name, size_t length, Value value, int* line) { if (module->variables.count == MAX_MODULE_VARS) return -2; if (IS_OBJ(value)) wrenPushRoot(vm, AS_OBJ(value)); // See if the variable is already explicitly or implicitly declared. int symbol = wrenSymbolTableFind(&module->variableNames, name, length); if (symbol == -1) { // Brand new variable. symbol = wrenSymbolTableAdd(vm, &module->variableNames, name, length); wrenValueBufferWrite(vm, &module->variables, value); } else if (IS_NUM(module->variables.data[symbol])) { // An implicitly declared variable's value will always be a number. // Now we have a real definition. if(line) *line = (int)AS_NUM(module->variables.data[symbol]); module->variables.data[symbol] = value; // If this was a localname we want to error if it was // referenced before this definition. if (wrenIsLocalName(name)) symbol = -3; } else { // Already explicitly declared. symbol = -1; } if (IS_OBJ(value)) wrenPopRoot(vm); return symbol; } // TODO: Inline? void wrenPushRoot(WrenVM* vm, Obj* obj) { ASSERT(obj != NULL, "Can't root NULL."); ASSERT(vm->numTempRoots < WREN_MAX_TEMP_ROOTS, "Too many temporary roots."); vm->tempRoots[vm->numTempRoots++] = obj; } void wrenPopRoot(WrenVM* vm) { ASSERT(vm->numTempRoots > 0, "No temporary roots to release."); vm->numTempRoots--; } int wrenGetSlotCount(WrenVM* vm) { if (vm->apiStack == NULL) return 0; return (int)(vm->fiber->stackTop - vm->apiStack); } void wrenEnsureSlots(WrenVM* vm, int numSlots) { // If we don't have a fiber accessible, create one for the API to use. if (vm->apiStack == NULL) { vm->fiber = wrenNewFiber(vm, NULL); vm->apiStack = vm->fiber->stack; } int currentSize = (int)(vm->fiber->stackTop - vm->apiStack); if (currentSize >= numSlots) return; // Grow the stack if needed. int needed = (int)(vm->apiStack - vm->fiber->stack) + numSlots; wrenEnsureStack(vm, vm->fiber, needed); vm->fiber->stackTop = vm->apiStack + numSlots; } // Ensures that [slot] is a valid index into the API's stack of slots. static void validateApiSlot(WrenVM* vm, int slot) { ASSERT(slot >= 0, "Slot cannot be negative."); ASSERT(slot < wrenGetSlotCount(vm), "Not that many slots."); } // Gets the type of the object in [slot]. WrenType wrenGetSlotType(WrenVM* vm, int slot) { validateApiSlot(vm, slot); if (IS_BOOL(vm->apiStack[slot])) return WREN_TYPE_BOOL; if (IS_NUM(vm->apiStack[slot])) return WREN_TYPE_NUM; if (IS_FOREIGN(vm->apiStack[slot])) return WREN_TYPE_FOREIGN; if (IS_LIST(vm->apiStack[slot])) return WREN_TYPE_LIST; if (IS_MAP(vm->apiStack[slot])) return WREN_TYPE_MAP; if (IS_NULL(vm->apiStack[slot])) return WREN_TYPE_NULL; if (IS_STRING(vm->apiStack[slot])) return WREN_TYPE_STRING; return WREN_TYPE_UNKNOWN; } bool wrenGetSlotBool(WrenVM* vm, int slot) { validateApiSlot(vm, slot); ASSERT(IS_BOOL(vm->apiStack[slot]), "Slot must hold a bool."); return AS_BOOL(vm->apiStack[slot]); } const char* wrenGetSlotBytes(WrenVM* vm, int slot, int* length) { validateApiSlot(vm, slot); ASSERT(IS_STRING(vm->apiStack[slot]), "Slot must hold a string."); ObjString* string = AS_STRING(vm->apiStack[slot]); *length = string->length; return string->value; } double wrenGetSlotDouble(WrenVM* vm, int slot) { validateApiSlot(vm, slot); ASSERT(IS_NUM(vm->apiStack[slot]), "Slot must hold a number."); return AS_NUM(vm->apiStack[slot]); } void* wrenGetSlotForeign(WrenVM* vm, int slot) { validateApiSlot(vm, slot); ASSERT(IS_FOREIGN(vm->apiStack[slot]), "Slot must hold a foreign instance."); return AS_FOREIGN(vm->apiStack[slot])->data; } const char* wrenGetSlotString(WrenVM* vm, int slot) { validateApiSlot(vm, slot); ASSERT(IS_STRING(vm->apiStack[slot]), "Slot must hold a string."); return AS_CSTRING(vm->apiStack[slot]); } WrenHandle* wrenGetSlotHandle(WrenVM* vm, int slot) { validateApiSlot(vm, slot); return wrenMakeHandle(vm, vm->apiStack[slot]); } // Stores [value] in [slot] in the foreign call stack. static void setSlot(WrenVM* vm, int slot, Value value) { validateApiSlot(vm, slot); vm->apiStack[slot] = value; } void wrenSetSlotBool(WrenVM* vm, int slot, bool value) { setSlot(vm, slot, BOOL_VAL(value)); } void wrenSetSlotBytes(WrenVM* vm, int slot, const char* bytes, size_t length) { ASSERT(bytes != NULL, "Byte array cannot be NULL."); setSlot(vm, slot, wrenNewStringLength(vm, bytes, length)); } void wrenSetSlotDouble(WrenVM* vm, int slot, double value) { setSlot(vm, slot, NUM_VAL(value)); } void* wrenSetSlotNewForeign(WrenVM* vm, int slot, int classSlot, size_t size) { validateApiSlot(vm, slot); validateApiSlot(vm, classSlot); ASSERT(IS_CLASS(vm->apiStack[classSlot]), "Slot must hold a class."); ObjClass* classObj = AS_CLASS(vm->apiStack[classSlot]); ASSERT(classObj->numFields == -1, "Class must be a foreign class."); ObjForeign* foreign = wrenNewForeign(vm, classObj, size); vm->apiStack[slot] = OBJ_VAL(foreign); return (void*)foreign->data; } void wrenSetSlotNewList(WrenVM* vm, int slot) { setSlot(vm, slot, OBJ_VAL(wrenNewList(vm, 0))); } void wrenSetSlotNewMap(WrenVM* vm, int slot) { setSlot(vm, slot, OBJ_VAL(wrenNewMap(vm))); } void wrenSetSlotNull(WrenVM* vm, int slot) { setSlot(vm, slot, NULL_VAL); } void wrenSetSlotString(WrenVM* vm, int slot, const char* text) { ASSERT(text != NULL, "String cannot be NULL."); setSlot(vm, slot, wrenNewString(vm, text)); } void wrenSetSlotHandle(WrenVM* vm, int slot, WrenHandle* handle) { ASSERT(handle != NULL, "Handle cannot be NULL."); setSlot(vm, slot, handle->value); } int wrenGetListCount(WrenVM* vm, int slot) { validateApiSlot(vm, slot); ASSERT(IS_LIST(vm->apiStack[slot]), "Slot must hold a list."); ValueBuffer elements = AS_LIST(vm->apiStack[slot])->elements; return elements.count; } void wrenGetListElement(WrenVM* vm, int listSlot, int index, int elementSlot) { validateApiSlot(vm, listSlot); validateApiSlot(vm, elementSlot); ASSERT(IS_LIST(vm->apiStack[listSlot]), "Slot must hold a list."); ValueBuffer elements = AS_LIST(vm->apiStack[listSlot])->elements; uint32_t usedIndex = wrenValidateIndex(elements.count, index); ASSERT(usedIndex != UINT32_MAX, "Index out of bounds."); vm->apiStack[elementSlot] = elements.data[usedIndex]; } void wrenSetListElement(WrenVM* vm, int listSlot, int index, int elementSlot) { validateApiSlot(vm, listSlot); validateApiSlot(vm, elementSlot); ASSERT(IS_LIST(vm->apiStack[listSlot]), "Slot must hold a list."); ObjList* list = AS_LIST(vm->apiStack[listSlot]); uint32_t usedIndex = wrenValidateIndex(list->elements.count, index); ASSERT(usedIndex != UINT32_MAX, "Index out of bounds."); list->elements.data[usedIndex] = vm->apiStack[elementSlot]; } void wrenInsertInList(WrenVM* vm, int listSlot, int index, int elementSlot) { validateApiSlot(vm, listSlot); validateApiSlot(vm, elementSlot); ASSERT(IS_LIST(vm->apiStack[listSlot]), "Must insert into a list."); ObjList* list = AS_LIST(vm->apiStack[listSlot]); // Negative indices count from the end. // We don't use wrenValidateIndex here because insert allows 1 past the end. if (index < 0) index = list->elements.count + 1 + index; ASSERT(index <= list->elements.count, "Index out of bounds."); wrenListInsert(vm, list, vm->apiStack[elementSlot], index); } int wrenGetMapCount(WrenVM* vm, int slot) { validateApiSlot(vm, slot); ASSERT(IS_MAP(vm->apiStack[slot]), "Slot must hold a map."); ObjMap* map = AS_MAP(vm->apiStack[slot]); return map->count; } bool wrenGetMapContainsKey(WrenVM* vm, int mapSlot, int keySlot) { validateApiSlot(vm, mapSlot); validateApiSlot(vm, keySlot); ASSERT(IS_MAP(vm->apiStack[mapSlot]), "Slot must hold a map."); Value key = vm->apiStack[keySlot]; ASSERT(wrenMapIsValidKey(key), "Key must be a value type"); if (!validateKey(vm, key)) return false; ObjMap* map = AS_MAP(vm->apiStack[mapSlot]); Value value = wrenMapGet(map, key); return !IS_UNDEFINED(value); } void wrenGetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot) { validateApiSlot(vm, mapSlot); validateApiSlot(vm, keySlot); validateApiSlot(vm, valueSlot); ASSERT(IS_MAP(vm->apiStack[mapSlot]), "Slot must hold a map."); ObjMap* map = AS_MAP(vm->apiStack[mapSlot]); Value value = wrenMapGet(map, vm->apiStack[keySlot]); if (IS_UNDEFINED(value)) { value = NULL_VAL; } vm->apiStack[valueSlot] = value; } void wrenSetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot) { validateApiSlot(vm, mapSlot); validateApiSlot(vm, keySlot); validateApiSlot(vm, valueSlot); ASSERT(IS_MAP(vm->apiStack[mapSlot]), "Must insert into a map."); Value key = vm->apiStack[keySlot]; ASSERT(wrenMapIsValidKey(key), "Key must be a value type"); if (!validateKey(vm, key)) { return; } Value value = vm->apiStack[valueSlot]; ObjMap* map = AS_MAP(vm->apiStack[mapSlot]); wrenMapSet(vm, map, key, value); } void wrenRemoveMapValue(WrenVM* vm, int mapSlot, int keySlot, int removedValueSlot) { validateApiSlot(vm, mapSlot); validateApiSlot(vm, keySlot); ASSERT(IS_MAP(vm->apiStack[mapSlot]), "Slot must hold a map."); Value key = vm->apiStack[keySlot]; if (!validateKey(vm, key)) { return; } ObjMap* map = AS_MAP(vm->apiStack[mapSlot]); Value removed = wrenMapRemoveKey(vm, map, key); setSlot(vm, removedValueSlot, removed); } void wrenGetVariable(WrenVM* vm, const char* module, const char* name, int slot) { ASSERT(module != NULL, "Module cannot be NULL."); ASSERT(name != NULL, "Variable name cannot be NULL."); Value moduleName = wrenStringFormat(vm, "$", module); wrenPushRoot(vm, AS_OBJ(moduleName)); ObjModule* moduleObj = getModule(vm, moduleName); ASSERT(moduleObj != NULL, "Could not find module."); wrenPopRoot(vm); // moduleName. int variableSlot = wrenSymbolTableFind(&moduleObj->variableNames, name, strlen(name)); ASSERT(variableSlot != -1, "Could not find variable."); setSlot(vm, slot, moduleObj->variables.data[variableSlot]); } bool wrenHasVariable(WrenVM* vm, const char* module, const char* name) { ASSERT(module != NULL, "Module cannot be NULL."); ASSERT(name != NULL, "Variable name cannot be NULL."); Value moduleName = wrenStringFormat(vm, "$", module); wrenPushRoot(vm, AS_OBJ(moduleName)); //We don't use wrenHasModule since we want to use the module object. ObjModule* moduleObj = getModule(vm, moduleName); ASSERT(moduleObj != NULL, "Could not find module."); wrenPopRoot(vm); // moduleName. int variableSlot = wrenSymbolTableFind(&moduleObj->variableNames, name, strlen(name)); return variableSlot != -1; } bool wrenHasModule(WrenVM* vm, const char* module) { ASSERT(module != NULL, "Module cannot be NULL."); Value moduleName = wrenStringFormat(vm, "$", module); wrenPushRoot(vm, AS_OBJ(moduleName)); ObjModule* moduleObj = getModule(vm, moduleName); wrenPopRoot(vm); // moduleName. return moduleObj != NULL; } void wrenAbortFiber(WrenVM* vm, int slot) { validateApiSlot(vm, slot); vm->fiber->error = vm->apiStack[slot]; } void* wrenGetUserData(WrenVM* vm) { return vm->config.userData; } void wrenSetUserData(WrenVM* vm, void* userData) { vm->config.userData = userData; } // End file "wren_vm.c" // Begin file "wren_opt_random.c" #if WREN_OPT_RANDOM #include #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"; // End file "wren_opt_random.wren.inc" // Implements the well equidistributed long-period linear PRNG (WELL512a). // // https://en.wikipedia.org/wiki/Well_equidistributed_long-period_linear typedef struct { uint32_t state[16]; uint32_t index; } Well512; // Code from: http://www.lomont.org/Math/Papers/2008/Lomont_PRNG_2008.pdf static uint32_t advanceState(Well512* well) { uint32_t a, b, c, d; a = well->state[well->index]; c = well->state[(well->index + 13) & 15]; b = a ^ c ^ (a << 16) ^ (c << 15); c = well->state[(well->index + 9) & 15]; c ^= (c >> 11); a = well->state[well->index] = b ^ c; d = a ^ ((a << 5) & 0xda442d24U); well->index = (well->index + 15) & 15; a = well->state[well->index]; well->state[well->index] = a ^ b ^ d ^ (a << 2) ^ (b << 18) ^ (c << 28); return well->state[well->index]; } static void randomAllocate(WrenVM* vm) { Well512* well = (Well512*)wrenSetSlotNewForeign(vm, 0, 0, sizeof(Well512)); well->index = 0; } static void randomSeed0(WrenVM* vm) { Well512* well = (Well512*)wrenGetSlotForeign(vm, 0); srand((uint32_t)time(NULL)); for (int i = 0; i < 16; i++) { well->state[i] = rand(); } } static void randomSeed1(WrenVM* vm) { Well512* well = (Well512*)wrenGetSlotForeign(vm, 0); srand((uint32_t)wrenGetSlotDouble(vm, 1)); for (int i = 0; i < 16; i++) { well->state[i] = rand(); } } static void randomSeed16(WrenVM* vm) { Well512* well = (Well512*)wrenGetSlotForeign(vm, 0); for (int i = 0; i < 16; i++) { well->state[i] = (uint32_t)wrenGetSlotDouble(vm, i + 1); } } static void randomFloat(WrenVM* vm) { Well512* well = (Well512*)wrenGetSlotForeign(vm, 0); // A double has 53 bits of precision in its mantissa, and we'd like to take // full advantage of that, so we need 53 bits of random source data. // First, start with 32 random bits, shifted to the left 21 bits. double result = (double)advanceState(well) * (1 << 21); // Then add another 21 random bits. result += (double)(advanceState(well) & ((1 << 21) - 1)); // Now we have a number from 0 - (2^53). Divide be the range to get a double // from 0 to 1.0 (half-inclusive). result /= 9007199254740992.0; wrenSetSlotDouble(vm, 0, result); } static void randomInt0(WrenVM* vm) { Well512* well = (Well512*)wrenGetSlotForeign(vm, 0); wrenSetSlotDouble(vm, 0, (double)advanceState(well)); } const char* wrenRandomSource() { return randomModuleSource; } WrenForeignClassMethods wrenRandomBindForeignClass(WrenVM* vm, const char* module, const char* className) { ASSERT(strcmp(className, "Random") == 0, "Should be in Random class."); WrenForeignClassMethods methods; methods.allocate = randomAllocate; methods.finalize = NULL; return methods; } WrenForeignMethodFn wrenRandomBindForeignMethod(WrenVM* vm, const char* className, bool isStatic, const char* signature) { ASSERT(strcmp(className, "Random") == 0, "Should be in Random class."); if (strcmp(signature, "") == 0) return randomAllocate; if (strcmp(signature, "seed_()") == 0) return randomSeed0; if (strcmp(signature, "seed_(_)") == 0) return randomSeed1; if (strcmp(signature, "seed_(_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_)") == 0) { return randomSeed16; } if (strcmp(signature, "float()") == 0) return randomFloat; if (strcmp(signature, "int()") == 0) return randomInt0; ASSERT(false, "Unknown method."); return NULL; } #endif // End file "wren_opt_random.c" // Begin file "wren_opt_meta.c" #if WREN_OPT_META #include // Begin file "wren_opt_meta.wren.inc" // Generated automatically from src/optional/wren_opt_meta.wren. Do not edit. static const char* metaModuleSource = "class Meta {\n" " static getModuleVariables(module) {\n" " if (!(module is String)) Fiber.abort(\"Module name must be a string.\")\n" " var result = getModuleVariables_(module)\n" " if (result != null) return result\n" "\n" " Fiber.abort(\"Could not find a module named '%(module)'.\")\n" " }\n" "\n" " static eval(source) {\n" " if (!(source is String)) Fiber.abort(\"Source code must be a string.\")\n" "\n" " var closure = compile_(source, false, false)\n" " // TODO: Include compile errors.\n" " if (closure == null) Fiber.abort(\"Could not compile source code.\")\n" "\n" " closure.call()\n" " }\n" "\n" " static compileExpression(source) {\n" " if (!(source is String)) Fiber.abort(\"Source code must be a string.\")\n" " return compile_(source, true, true)\n" " }\n" "\n" " static compile(source) {\n" " if (!(source is String)) Fiber.abort(\"Source code must be a string.\")\n" " return compile_(source, false, true)\n" " }\n" "\n" " foreign static compile_(source, isExpression, printErrors)\n" " foreign static getModuleVariables_(module)\n" "}\n"; // End file "wren_opt_meta.wren.inc" void metaCompile(WrenVM* vm) { const char* source = wrenGetSlotString(vm, 1); bool isExpression = wrenGetSlotBool(vm, 2); bool printErrors = wrenGetSlotBool(vm, 3); // TODO: Allow passing in module? // Look up the module surrounding the callsite. This is brittle. The -2 walks // up the callstack assuming that the meta module has one level of // indirection before hitting the user's code. Any change to meta may require // this constant to be tweaked. ObjFiber* currentFiber = vm->fiber; ObjFn* fn = currentFiber->frames[currentFiber->numFrames - 2].closure->fn; ObjString* module = fn->module->name; ObjClosure* closure = wrenCompileSource(vm, module->value, source, isExpression, printErrors); // Return the result. We can't use the public API for this since we have a // bare ObjClosure*. if (closure == NULL) { vm->apiStack[0] = NULL_VAL; } else { vm->apiStack[0] = OBJ_VAL(closure); } } void metaGetModuleVariables(WrenVM* vm) { wrenEnsureSlots(vm, 3); Value moduleValue = wrenMapGet(vm->modules, vm->apiStack[1]); if (IS_UNDEFINED(moduleValue)) { vm->apiStack[0] = NULL_VAL; return; } ObjModule* module = AS_MODULE(moduleValue); ObjList* names = wrenNewList(vm, module->variableNames.count); vm->apiStack[0] = OBJ_VAL(names); // Initialize the elements to null in case a collection happens when we // allocate the strings below. for (int i = 0; i < names->elements.count; i++) { names->elements.data[i] = NULL_VAL; } for (int i = 0; i < names->elements.count; i++) { names->elements.data[i] = OBJ_VAL(module->variableNames.data[i]); } } const char* wrenMetaSource() { return metaModuleSource; } WrenForeignMethodFn wrenMetaBindForeignMethod(WrenVM* vm, const char* className, bool isStatic, const char* signature) { // There is only one foreign method in the meta module. ASSERT(strcmp(className, "Meta") == 0, "Should be in Meta class."); ASSERT(isStatic, "Should be static."); if (strcmp(signature, "compile_(_,_,_)") == 0) { return metaCompile; } if (strcmp(signature, "getModuleVariables_(_)") == 0) { return metaGetModuleVariables; } ASSERT(false, "Unknown method."); return NULL; } #endif // End file "wren_opt_meta.c"