update wren to 0.4.0

This commit is contained in:
ruby0x1
2021-04-25 20:52:02 -07:00
parent 0a5f58309d
commit 9fb4c1684b
29 changed files with 12888 additions and 11839 deletions
+107 -41
View File
@@ -7,18 +7,26 @@
// The Wren semantic version number components. // The Wren semantic version number components.
#define WREN_VERSION_MAJOR 0 #define WREN_VERSION_MAJOR 0
#define WREN_VERSION_MINOR 3 #define WREN_VERSION_MINOR 4
#define WREN_VERSION_PATCH 0 #define WREN_VERSION_PATCH 0
// A human-friendly string representation of the version. // A human-friendly string representation of the version.
#define WREN_VERSION_STRING "0.3.0" #define WREN_VERSION_STRING "0.4.0"
// A monotonically increasing numeric representation of the version number. Use // A monotonically increasing numeric representation of the version number. Use
// this if you want to do range checks over versions. // this if you want to do range checks over versions.
#define WREN_VERSION_NUMBER (WREN_VERSION_MAJOR * 1000000 + \ #define WREN_VERSION_NUMBER (WREN_VERSION_MAJOR * 1000000 + \
WREN_VERSION_MINOR * 1000 + \ WREN_VERSION_MINOR * 1000 + \
WREN_VERSION_PATCH) 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. // A single virtual machine for executing Wren code.
// //
// Wren has no global state, so all state stored by a running interpreter lives // Wren has no global state, so all state stored by a running interpreter lives
@@ -47,7 +55,7 @@ typedef struct WrenHandle WrenHandle;
// //
// - To free memory, [memory] will be the memory to free and [newSize] will be // - To free memory, [memory] will be the memory to free and [newSize] will be
// zero. It should return NULL. // zero. It should return NULL.
typedef void* (*WrenReallocateFn)(void* memory, size_t newSize); typedef void* (*WrenReallocateFn)(void* memory, size_t newSize, void* userData);
// A function callable from Wren code, but implemented in C. // A function callable from Wren code, but implemented in C.
typedef void (*WrenForeignMethodFn)(WrenVM* vm); typedef void (*WrenForeignMethodFn)(WrenVM* vm);
@@ -65,8 +73,25 @@ typedef void (*WrenFinalizerFn)(void* data);
typedef const char* (*WrenResolveModuleFn)(WrenVM* vm, typedef const char* (*WrenResolveModuleFn)(WrenVM* vm,
const char* importer, const char* name); 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]. // Loads and returns the source code for the module [name].
typedef char* (*WrenLoadModuleFn)(WrenVM* vm, const char* name); typedef WrenLoadModuleResult (*WrenLoadModuleFn)(WrenVM* vm, const char* name);
// Returns a pointer to a foreign method on [className] in [module] with // Returns a pointer to a foreign method on [className] in [module] with
// [signature]. // [signature].
@@ -163,9 +188,9 @@ typedef struct
// Since Wren does not talk directly to the file system, it relies on the // 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 // 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 // first time an import appears, Wren will call this and pass in the name of
// the module being imported. The VM should return the soure code for that // the module being imported. The method will return a result, which contains
// module. Memory for the source should be allocated using [reallocateFn] and // the source code for that module. Memory for the source is owned by the
// Wren will take ownership over it. // host application, and can be freed using the onComplete callback.
// //
// This will only be called once for any given module name. Wren caches the // 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 // result internally so subsequent imports of the same module will use the
@@ -263,6 +288,7 @@ typedef enum
WREN_TYPE_NUM, WREN_TYPE_NUM,
WREN_TYPE_FOREIGN, WREN_TYPE_FOREIGN,
WREN_TYPE_LIST, WREN_TYPE_LIST,
WREN_TYPE_MAP,
WREN_TYPE_NULL, WREN_TYPE_NULL,
WREN_TYPE_STRING, WREN_TYPE_STRING,
@@ -270,27 +296,32 @@ typedef enum
WREN_TYPE_UNKNOWN WREN_TYPE_UNKNOWN
} WrenType; } 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. // Initializes [configuration] with all of its default values.
// //
// Call this before setting the particular fields you care about. // Call this before setting the particular fields you care about.
void wrenInitConfiguration(WrenConfiguration* configuration); WREN_API void wrenInitConfiguration(WrenConfiguration* configuration);
// Creates a new Wren virtual machine using the given [configuration]. Wren // Creates a new Wren virtual machine using the given [configuration]. Wren
// will copy the configuration data, so the argument passed to this can be // will copy the configuration data, so the argument passed to this can be
// freed after calling this. If [configuration] is `NULL`, uses a default // freed after calling this. If [configuration] is `NULL`, uses a default
// configuration. // configuration.
WrenVM* wrenNewVM(WrenConfiguration* configuration); WREN_API WrenVM* wrenNewVM(WrenConfiguration* configuration);
// Disposes of all resources is use by [vm], which was previously created by a // Disposes of all resources is use by [vm], which was previously created by a
// call to [wrenNewVM]. // call to [wrenNewVM].
void wrenFreeVM(WrenVM* vm); WREN_API void wrenFreeVM(WrenVM* vm);
// Immediately run the garbage collector to free unused memory. // Immediately run the garbage collector to free unused memory.
void wrenCollectGarbage(WrenVM* vm); WREN_API void wrenCollectGarbage(WrenVM* vm);
// Runs [source], a string of Wren source code in a new fiber in [vm] in the // Runs [source], a string of Wren source code in a new fiber in [vm] in the
// context of resolved [module]. // context of resolved [module].
WrenInterpretResult wrenInterpret(WrenVM* vm, const char* module, WREN_API WrenInterpretResult wrenInterpret(WrenVM* vm, const char* module,
const char* source); const char* source);
// Creates a handle that can be used to invoke a method with [signature] on // Creates a handle that can be used to invoke a method with [signature] on
@@ -301,7 +332,7 @@ WrenInterpretResult wrenInterpret(WrenVM* vm, const char* module,
// //
// When you are done with this handle, it must be released using // When you are done with this handle, it must be released using
// [wrenReleaseHandle]. // [wrenReleaseHandle].
WrenHandle* wrenMakeCallHandle(WrenVM* vm, const char* signature); WREN_API WrenHandle* wrenMakeCallHandle(WrenVM* vm, const char* signature);
// Calls [method], using the receiver and arguments previously set up on the // Calls [method], using the receiver and arguments previously set up on the
// stack. // stack.
@@ -313,11 +344,11 @@ WrenHandle* wrenMakeCallHandle(WrenVM* vm, const char* signature);
// signature. // signature.
// //
// After this returns, you can access the return value from slot 0 on the stack. // After this returns, you can access the return value from slot 0 on the stack.
WrenInterpretResult wrenCall(WrenVM* vm, WrenHandle* method); WREN_API WrenInterpretResult wrenCall(WrenVM* vm, WrenHandle* method);
// Releases the reference stored in [handle]. After calling this, [handle] can // Releases the reference stored in [handle]. After calling this, [handle] can
// no longer be used. // no longer be used.
void wrenReleaseHandle(WrenVM* vm, WrenHandle* handle); WREN_API void wrenReleaseHandle(WrenVM* vm, WrenHandle* handle);
// The following functions are intended to be called from foreign methods or // The following functions are intended to be called from foreign methods or
// finalizers. The interface Wren provides to a foreign method is like a // finalizers. The interface Wren provides to a foreign method is like a
@@ -357,7 +388,7 @@ void wrenReleaseHandle(WrenVM* vm, WrenHandle* handle);
// return, you get a very fast FFI. // return, you get a very fast FFI.
// Returns the number of slots available to the current foreign method. // Returns the number of slots available to the current foreign method.
int wrenGetSlotCount(WrenVM* vm); WREN_API int wrenGetSlotCount(WrenVM* vm);
// Ensures that the foreign method stack has at least [numSlots] available for // Ensures that the foreign method stack has at least [numSlots] available for
// use, growing the stack if needed. // use, growing the stack if needed.
@@ -365,15 +396,15 @@ int wrenGetSlotCount(WrenVM* vm);
// Does not shrink the stack if it has more than enough slots. // Does not shrink the stack if it has more than enough slots.
// //
// It is an error to call this from a finalizer. // It is an error to call this from a finalizer.
void wrenEnsureSlots(WrenVM* vm, int numSlots); WREN_API void wrenEnsureSlots(WrenVM* vm, int numSlots);
// Gets the type of the object in [slot]. // Gets the type of the object in [slot].
WrenType wrenGetSlotType(WrenVM* vm, int slot); WREN_API WrenType wrenGetSlotType(WrenVM* vm, int slot);
// Reads a boolean value from [slot]. // Reads a boolean value from [slot].
// //
// It is an error to call this if the slot does not contain a boolean value. // It is an error to call this if the slot does not contain a boolean value.
bool wrenGetSlotBool(WrenVM* vm, int slot); WREN_API bool wrenGetSlotBool(WrenVM* vm, int slot);
// Reads a byte array from [slot]. // Reads a byte array from [slot].
// //
@@ -385,19 +416,19 @@ bool wrenGetSlotBool(WrenVM* vm, int slot);
// number of bytes in the array. // number of bytes in the array.
// //
// It is an error to call this if the slot does not contain a string. // It is an error to call this if the slot does not contain a string.
const char* wrenGetSlotBytes(WrenVM* vm, int slot, int* length); WREN_API const char* wrenGetSlotBytes(WrenVM* vm, int slot, int* length);
// Reads a number from [slot]. // Reads a number from [slot].
// //
// It is an error to call this if the slot does not contain a number. // It is an error to call this if the slot does not contain a number.
double wrenGetSlotDouble(WrenVM* vm, int slot); WREN_API double wrenGetSlotDouble(WrenVM* vm, int slot);
// Reads a foreign object from [slot] and returns a pointer to the foreign data // Reads a foreign object from [slot] and returns a pointer to the foreign data
// stored with it. // stored with it.
// //
// It is an error to call this if the slot does not contain an instance of a // It is an error to call this if the slot does not contain an instance of a
// foreign class. // foreign class.
void* wrenGetSlotForeign(WrenVM* vm, int slot); WREN_API void* wrenGetSlotForeign(WrenVM* vm, int slot);
// Reads a string from [slot]. // Reads a string from [slot].
// //
@@ -406,25 +437,25 @@ void* wrenGetSlotForeign(WrenVM* vm, int slot);
// function returns, since the garbage collector may reclaim it. // function returns, since the garbage collector may reclaim it.
// //
// It is an error to call this if the slot does not contain a string. // It is an error to call this if the slot does not contain a string.
const char* wrenGetSlotString(WrenVM* vm, int slot); WREN_API const char* wrenGetSlotString(WrenVM* vm, int slot);
// Creates a handle for the value stored in [slot]. // Creates a handle for the value stored in [slot].
// //
// This will prevent the object that is referred to from being garbage collected // This will prevent the object that is referred to from being garbage collected
// until the handle is released by calling [wrenReleaseHandle()]. // until the handle is released by calling [wrenReleaseHandle()].
WrenHandle* wrenGetSlotHandle(WrenVM* vm, int slot); WREN_API WrenHandle* wrenGetSlotHandle(WrenVM* vm, int slot);
// Stores the boolean [value] in [slot]. // Stores the boolean [value] in [slot].
void wrenSetSlotBool(WrenVM* vm, int slot, bool value); WREN_API void wrenSetSlotBool(WrenVM* vm, int slot, bool value);
// Stores the array [length] of [bytes] in [slot]. // Stores the array [length] of [bytes] in [slot].
// //
// The bytes are copied to a new string within Wren's heap, so you can free // The bytes are copied to a new string within Wren's heap, so you can free
// memory used by them after this is called. // memory used by them after this is called.
void wrenSetSlotBytes(WrenVM* vm, int slot, const char* bytes, size_t length); WREN_API void wrenSetSlotBytes(WrenVM* vm, int slot, const char* bytes, size_t length);
// Stores the numeric [value] in [slot]. // Stores the numeric [value] in [slot].
void wrenSetSlotDouble(WrenVM* vm, int slot, double value); WREN_API void wrenSetSlotDouble(WrenVM* vm, int slot, double value);
// Creates a new instance of the foreign class stored in [classSlot] with [size] // Creates a new instance of the foreign class stored in [classSlot] with [size]
// bytes of raw storage and places the resulting object in [slot]. // bytes of raw storage and places the resulting object in [slot].
@@ -435,13 +466,16 @@ void wrenSetSlotDouble(WrenVM* vm, int slot, double value);
// and then the constructor will be invoked when the allocator returns. // and then the constructor will be invoked when the allocator returns.
// //
// Returns a pointer to the foreign object's data. // Returns a pointer to the foreign object's data.
void* wrenSetSlotNewForeign(WrenVM* vm, int slot, int classSlot, size_t size); WREN_API void* wrenSetSlotNewForeign(WrenVM* vm, int slot, int classSlot, size_t size);
// Stores a new empty list in [slot]. // Stores a new empty list in [slot].
void wrenSetSlotNewList(WrenVM* vm, int slot); WREN_API void wrenSetSlotNewList(WrenVM* vm, int slot);
// Stores a new empty map in [slot].
WREN_API void wrenSetSlotNewMap(WrenVM* vm, int slot);
// Stores null in [slot]. // Stores null in [slot].
void wrenSetSlotNull(WrenVM* vm, int slot); WREN_API void wrenSetSlotNull(WrenVM* vm, int slot);
// Stores the string [text] in [slot]. // Stores the string [text] in [slot].
// //
@@ -449,40 +483,72 @@ void wrenSetSlotNull(WrenVM* vm, int slot);
// memory used by it after this is called. The length is calculated using // 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 // [strlen()]. If the string may contain any null bytes in the middle, then you
// should use [wrenSetSlotBytes()] instead. // should use [wrenSetSlotBytes()] instead.
void wrenSetSlotString(WrenVM* vm, int slot, const char* text); WREN_API void wrenSetSlotString(WrenVM* vm, int slot, const char* text);
// Stores the value captured in [handle] in [slot]. // Stores the value captured in [handle] in [slot].
// //
// This does not release the handle for the value. // This does not release the handle for the value.
void wrenSetSlotHandle(WrenVM* vm, int slot, WrenHandle* handle); WREN_API void wrenSetSlotHandle(WrenVM* vm, int slot, WrenHandle* handle);
// Returns the number of elements in the list stored in [slot]. // Returns the number of elements in the list stored in [slot].
int wrenGetListCount(WrenVM* vm, int slot); WREN_API int wrenGetListCount(WrenVM* vm, int slot);
// Reads element [index] from the list in [listSlot] and stores it in // Reads element [index] from the list in [listSlot] and stores it in
// [elementSlot]. // [elementSlot].
void wrenGetListElement(WrenVM* vm, int listSlot, int index, int elementSlot); WREN_API void wrenGetListElement(WrenVM* vm, int listSlot, int index, int elementSlot);
// Sets the value stored at [index] in the list at [listSlot],
// to the value from [elementSlot].
WREN_API void wrenSetListElement(WrenVM* vm, int listSlot, int index, int elementSlot);
// Takes the value stored at [elementSlot] and inserts it into the list stored // Takes the value stored at [elementSlot] and inserts it into the list stored
// at [listSlot] at [index]. // at [listSlot] at [index].
// //
// As in Wren, negative indexes can be used to insert from the end. To append // As in Wren, negative indexes can be used to insert from the end. To append
// an element, use `-1` for the index. // an element, use `-1` for the index.
void wrenInsertInList(WrenVM* vm, int listSlot, int index, int elementSlot); WREN_API void wrenInsertInList(WrenVM* vm, int listSlot, int index, int elementSlot);
// Returns the number of entries in the map stored in [slot].
WREN_API int wrenGetMapCount(WrenVM* vm, int slot);
// Returns true if the key in [keySlot] is found in the map placed in [mapSlot].
WREN_API bool wrenGetMapContainsKey(WrenVM* vm, int mapSlot, int keySlot);
// Retrieves a value with the key in [keySlot] from the map in [mapSlot] and
// stores it in [valueSlot].
WREN_API void wrenGetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot);
// Takes the value stored at [valueSlot] and inserts it into the map stored
// at [mapSlot] with key [keySlot].
WREN_API void wrenSetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot);
// Removes a value from the map in [mapSlot], with the key from [keySlot],
// and place it in [removedValueSlot]. If not found, [removedValueSlot] is
// set to null, the same behaviour as the Wren Map API.
WREN_API void wrenRemoveMapValue(WrenVM* vm, int mapSlot, int keySlot,
int removedValueSlot);
// Looks up the top level variable with [name] in resolved [module] and stores // Looks up the top level variable with [name] in resolved [module] and stores
// it in [slot]. // it in [slot].
void wrenGetVariable(WrenVM* vm, const char* module, const char* name, WREN_API void wrenGetVariable(WrenVM* vm, const char* module, const char* name,
int slot); 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 // Sets the current fiber to be aborted, and uses the value in [slot] as the
// runtime error object. // runtime error object.
void wrenAbortFiber(WrenVM* vm, int slot); WREN_API void wrenAbortFiber(WrenVM* vm, int slot);
// Returns the user data associated with the WrenVM. // Returns the user data associated with the WrenVM.
void* wrenGetUserData(WrenVM* vm); WREN_API void* wrenGetUserData(WrenVM* vm);
// Sets user data associated with the WrenVM. // Sets user data associated with the WrenVM.
void wrenSetUserData(WrenVM* vm, void* userData); WREN_API void wrenSetUserData(WrenVM* vm, void* userData);
#endif #endif
+11 -11
View File
@@ -1,11 +1,11 @@
#ifndef wren_hpp #ifndef wren_hpp
#define wren_hpp #define wren_hpp
// This is a convenience header for users that want to compile Wren as C and // This is a convenience header for users that want to compile Wren as C and
// link to it from a C++ application. // link to it from a C++ application.
extern "C" { extern "C" {
#include "wren.h" #include "wren.h"
} }
#endif #endif
+96 -96
View File
@@ -1,96 +1,96 @@
#include "wren_opt_meta.h" #include "wren_opt_meta.h"
#if WREN_OPT_META #if WREN_OPT_META
#include <string.h> #include <string.h>
#include "wren_vm.h" #include "wren_vm.h"
#include "wren_opt_meta.wren.inc" #include "wren_opt_meta.wren.inc"
void metaCompile(WrenVM* vm) void metaCompile(WrenVM* vm)
{ {
const char* source = wrenGetSlotString(vm, 1); const char* source = wrenGetSlotString(vm, 1);
bool isExpression = wrenGetSlotBool(vm, 2); bool isExpression = wrenGetSlotBool(vm, 2);
bool printErrors = wrenGetSlotBool(vm, 3); bool printErrors = wrenGetSlotBool(vm, 3);
// TODO: Allow passing in module? // TODO: Allow passing in module?
// Look up the module surrounding the callsite. This is brittle. The -2 walks // 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 // 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 // indirection before hitting the user's code. Any change to meta may require
// this constant to be tweaked. // this constant to be tweaked.
ObjFiber* currentFiber = vm->fiber; ObjFiber* currentFiber = vm->fiber;
ObjFn* fn = currentFiber->frames[currentFiber->numFrames - 2].closure->fn; ObjFn* fn = currentFiber->frames[currentFiber->numFrames - 2].closure->fn;
ObjString* module = fn->module->name; ObjString* module = fn->module->name;
ObjClosure* closure = wrenCompileSource(vm, module->value, source, ObjClosure* closure = wrenCompileSource(vm, module->value, source,
isExpression, printErrors); isExpression, printErrors);
// Return the result. We can't use the public API for this since we have a // Return the result. We can't use the public API for this since we have a
// bare ObjClosure*. // bare ObjClosure*.
if (closure == NULL) if (closure == NULL)
{ {
vm->apiStack[0] = NULL_VAL; vm->apiStack[0] = NULL_VAL;
} }
else else
{ {
vm->apiStack[0] = OBJ_VAL(closure); vm->apiStack[0] = OBJ_VAL(closure);
} }
} }
void metaGetModuleVariables(WrenVM* vm) void metaGetModuleVariables(WrenVM* vm)
{ {
wrenEnsureSlots(vm, 3); wrenEnsureSlots(vm, 3);
Value moduleValue = wrenMapGet(vm->modules, vm->apiStack[1]); Value moduleValue = wrenMapGet(vm->modules, vm->apiStack[1]);
if (IS_UNDEFINED(moduleValue)) if (IS_UNDEFINED(moduleValue))
{ {
vm->apiStack[0] = NULL_VAL; vm->apiStack[0] = NULL_VAL;
return; return;
} }
ObjModule* module = AS_MODULE(moduleValue); ObjModule* module = AS_MODULE(moduleValue);
ObjList* names = wrenNewList(vm, module->variableNames.count); ObjList* names = wrenNewList(vm, module->variableNames.count);
vm->apiStack[0] = OBJ_VAL(names); vm->apiStack[0] = OBJ_VAL(names);
// Initialize the elements to null in case a collection happens when we // Initialize the elements to null in case a collection happens when we
// allocate the strings below. // allocate the strings below.
for (int i = 0; i < names->elements.count; i++) for (int i = 0; i < names->elements.count; i++)
{ {
names->elements.data[i] = NULL_VAL; names->elements.data[i] = NULL_VAL;
} }
for (int i = 0; i < names->elements.count; i++) for (int i = 0; i < names->elements.count; i++)
{ {
names->elements.data[i] = OBJ_VAL(module->variableNames.data[i]); names->elements.data[i] = OBJ_VAL(module->variableNames.data[i]);
} }
} }
const char* wrenMetaSource() const char* wrenMetaSource()
{ {
return metaModuleSource; return metaModuleSource;
} }
WrenForeignMethodFn wrenMetaBindForeignMethod(WrenVM* vm, WrenForeignMethodFn wrenMetaBindForeignMethod(WrenVM* vm,
const char* className, const char* className,
bool isStatic, bool isStatic,
const char* signature) const char* signature)
{ {
// There is only one foreign method in the meta module. // There is only one foreign method in the meta module.
ASSERT(strcmp(className, "Meta") == 0, "Should be in Meta class."); ASSERT(strcmp(className, "Meta") == 0, "Should be in Meta class.");
ASSERT(isStatic, "Should be static."); ASSERT(isStatic, "Should be static.");
if (strcmp(signature, "compile_(_,_,_)") == 0) if (strcmp(signature, "compile_(_,_,_)") == 0)
{ {
return metaCompile; return metaCompile;
} }
if (strcmp(signature, "getModuleVariables_(_)") == 0) if (strcmp(signature, "getModuleVariables_(_)") == 0)
{ {
return metaGetModuleVariables; return metaGetModuleVariables;
} }
ASSERT(false, "Unknown method."); ASSERT(false, "Unknown method.");
return NULL; return NULL;
} }
#endif #endif
+18 -18
View File
@@ -1,18 +1,18 @@
#ifndef wren_opt_meta_h #ifndef wren_opt_meta_h
#define wren_opt_meta_h #define wren_opt_meta_h
#include "wren_common.h" #include "wren_common.h"
#include "wren.h" #include "wren.h"
// This module defines the Meta class and its associated methods. // This module defines the Meta class and its associated methods.
#if WREN_OPT_META #if WREN_OPT_META
const char* wrenMetaSource(); const char* wrenMetaSource();
WrenForeignMethodFn wrenMetaBindForeignMethod(WrenVM* vm, WrenForeignMethodFn wrenMetaBindForeignMethod(WrenVM* vm,
const char* className, const char* className,
bool isStatic, bool isStatic,
const char* signature); const char* signature);
#endif #endif
#endif #endif
+32 -32
View File
@@ -1,32 +1,32 @@
class Meta { class Meta {
static getModuleVariables(module) { static getModuleVariables(module) {
if (!(module is String)) Fiber.abort("Module name must be a string.") if (!(module is String)) Fiber.abort("Module name must be a string.")
var result = getModuleVariables_(module) var result = getModuleVariables_(module)
if (result != null) return result if (result != null) return result
Fiber.abort("Could not find a module named '%(module)'.") Fiber.abort("Could not find a module named '%(module)'.")
} }
static eval(source) { static eval(source) {
if (!(source is String)) Fiber.abort("Source code must be a string.") if (!(source is String)) Fiber.abort("Source code must be a string.")
var closure = compile_(source, false, false) var closure = compile_(source, false, false)
// TODO: Include compile errors. // TODO: Include compile errors.
if (closure == null) Fiber.abort("Could not compile source code.") if (closure == null) Fiber.abort("Could not compile source code.")
closure.call() closure.call()
} }
static compileExpression(source) { static compileExpression(source) {
if (!(source is String)) Fiber.abort("Source code must be a string.") if (!(source is String)) Fiber.abort("Source code must be a string.")
return compile_(source, true, true) return compile_(source, true, true)
} }
static compile(source) { static compile(source) {
if (!(source is String)) Fiber.abort("Source code must be a string.") if (!(source is String)) Fiber.abort("Source code must be a string.")
return compile_(source, false, true) return compile_(source, false, true)
} }
foreign static compile_(source, isExpression, printErrors) foreign static compile_(source, isExpression, printErrors)
foreign static getModuleVariables_(module) foreign static getModuleVariables_(module)
} }
+34 -34
View File
@@ -1,34 +1,34 @@
// Generated automatically from src/optional/wren_opt_meta.wren. Do not edit. // Generated automatically from src/optional/wren_opt_meta.wren. Do not edit.
static const char* metaModuleSource = static const char* metaModuleSource =
"class Meta {\n" "class Meta {\n"
" static getModuleVariables(module) {\n" " static getModuleVariables(module) {\n"
" if (!(module is String)) Fiber.abort(\"Module name must be a string.\")\n" " if (!(module is String)) Fiber.abort(\"Module name must be a string.\")\n"
" var result = getModuleVariables_(module)\n" " var result = getModuleVariables_(module)\n"
" if (result != null) return result\n" " if (result != null) return result\n"
"\n" "\n"
" Fiber.abort(\"Could not find a module named '%(module)'.\")\n" " Fiber.abort(\"Could not find a module named '%(module)'.\")\n"
" }\n" " }\n"
"\n" "\n"
" static eval(source) {\n" " static eval(source) {\n"
" if (!(source is String)) Fiber.abort(\"Source code must be a string.\")\n" " if (!(source is String)) Fiber.abort(\"Source code must be a string.\")\n"
"\n" "\n"
" var closure = compile_(source, false, false)\n" " var closure = compile_(source, false, false)\n"
" // TODO: Include compile errors.\n" " // TODO: Include compile errors.\n"
" if (closure == null) Fiber.abort(\"Could not compile source code.\")\n" " if (closure == null) Fiber.abort(\"Could not compile source code.\")\n"
"\n" "\n"
" closure.call()\n" " closure.call()\n"
" }\n" " }\n"
"\n" "\n"
" static compileExpression(source) {\n" " static compileExpression(source) {\n"
" if (!(source is String)) Fiber.abort(\"Source code must be a string.\")\n" " if (!(source is String)) Fiber.abort(\"Source code must be a string.\")\n"
" return compile_(source, true, true)\n" " return compile_(source, true, true)\n"
" }\n" " }\n"
"\n" "\n"
" static compile(source) {\n" " static compile(source) {\n"
" if (!(source is String)) Fiber.abort(\"Source code must be a string.\")\n" " if (!(source is String)) Fiber.abort(\"Source code must be a string.\")\n"
" return compile_(source, false, true)\n" " return compile_(source, false, true)\n"
" }\n" " }\n"
"\n" "\n"
" foreign static compile_(source, isExpression, printErrors)\n" " foreign static compile_(source, isExpression, printErrors)\n"
" foreign static getModuleVariables_(module)\n" " foreign static getModuleVariables_(module)\n"
"}\n"; "}\n";
+144 -144
View File
@@ -1,144 +1,144 @@
#include "wren_opt_random.h" #include "wren_opt_random.h"
#if WREN_OPT_RANDOM #if WREN_OPT_RANDOM
#include <string.h> #include <string.h>
#include <time.h> #include <time.h>
#include "wren.h" #include "wren.h"
#include "wren_vm.h" #include "wren_vm.h"
#include "wren_opt_random.wren.inc" #include "wren_opt_random.wren.inc"
// Implements the well equidistributed long-period linear PRNG (WELL512a). // Implements the well equidistributed long-period linear PRNG (WELL512a).
// //
// https://en.wikipedia.org/wiki/Well_equidistributed_long-period_linear // https://en.wikipedia.org/wiki/Well_equidistributed_long-period_linear
typedef struct typedef struct
{ {
uint32_t state[16]; uint32_t state[16];
uint32_t index; uint32_t index;
} Well512; } Well512;
// Code from: http://www.lomont.org/Math/Papers/2008/Lomont_PRNG_2008.pdf // Code from: http://www.lomont.org/Math/Papers/2008/Lomont_PRNG_2008.pdf
static uint32_t advanceState(Well512* well) static uint32_t advanceState(Well512* well)
{ {
uint32_t a, b, c, d; uint32_t a, b, c, d;
a = well->state[well->index]; a = well->state[well->index];
c = well->state[(well->index + 13) & 15]; c = well->state[(well->index + 13) & 15];
b = a ^ c ^ (a << 16) ^ (c << 15); b = a ^ c ^ (a << 16) ^ (c << 15);
c = well->state[(well->index + 9) & 15]; c = well->state[(well->index + 9) & 15];
c ^= (c >> 11); c ^= (c >> 11);
a = well->state[well->index] = b ^ c; a = well->state[well->index] = b ^ c;
d = a ^ ((a << 5) & 0xda442d24U); d = a ^ ((a << 5) & 0xda442d24U);
well->index = (well->index + 15) & 15; well->index = (well->index + 15) & 15;
a = well->state[well->index]; a = well->state[well->index];
well->state[well->index] = a ^ b ^ d ^ (a << 2) ^ (b << 18) ^ (c << 28); well->state[well->index] = a ^ b ^ d ^ (a << 2) ^ (b << 18) ^ (c << 28);
return well->state[well->index]; return well->state[well->index];
} }
static void randomAllocate(WrenVM* vm) static void randomAllocate(WrenVM* vm)
{ {
Well512* well = (Well512*)wrenSetSlotNewForeign(vm, 0, 0, sizeof(Well512)); Well512* well = (Well512*)wrenSetSlotNewForeign(vm, 0, 0, sizeof(Well512));
well->index = 0; well->index = 0;
} }
static void randomSeed0(WrenVM* vm) static void randomSeed0(WrenVM* vm)
{ {
Well512* well = (Well512*)wrenGetSlotForeign(vm, 0); Well512* well = (Well512*)wrenGetSlotForeign(vm, 0);
srand((uint32_t)time(NULL)); srand((uint32_t)time(NULL));
for (int i = 0; i < 16; i++) for (int i = 0; i < 16; i++)
{ {
well->state[i] = rand(); well->state[i] = rand();
} }
} }
static void randomSeed1(WrenVM* vm) static void randomSeed1(WrenVM* vm)
{ {
Well512* well = (Well512*)wrenGetSlotForeign(vm, 0); Well512* well = (Well512*)wrenGetSlotForeign(vm, 0);
srand((uint32_t)wrenGetSlotDouble(vm, 1)); srand((uint32_t)wrenGetSlotDouble(vm, 1));
for (int i = 0; i < 16; i++) for (int i = 0; i < 16; i++)
{ {
well->state[i] = rand(); well->state[i] = rand();
} }
} }
static void randomSeed16(WrenVM* vm) static void randomSeed16(WrenVM* vm)
{ {
Well512* well = (Well512*)wrenGetSlotForeign(vm, 0); Well512* well = (Well512*)wrenGetSlotForeign(vm, 0);
for (int i = 0; i < 16; i++) for (int i = 0; i < 16; i++)
{ {
well->state[i] = (uint32_t)wrenGetSlotDouble(vm, i + 1); well->state[i] = (uint32_t)wrenGetSlotDouble(vm, i + 1);
} }
} }
static void randomFloat(WrenVM* vm) static void randomFloat(WrenVM* vm)
{ {
Well512* well = (Well512*)wrenGetSlotForeign(vm, 0); Well512* well = (Well512*)wrenGetSlotForeign(vm, 0);
// A double has 53 bits of precision in its mantissa, and we'd like to take // 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. // 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. // First, start with 32 random bits, shifted to the left 21 bits.
double result = (double)advanceState(well) * (1 << 21); double result = (double)advanceState(well) * (1 << 21);
// Then add another 21 random bits. // Then add another 21 random bits.
result += (double)(advanceState(well) & ((1 << 21) - 1)); result += (double)(advanceState(well) & ((1 << 21) - 1));
// Now we have a number from 0 - (2^53). Divide be the range to get a double // Now we have a number from 0 - (2^53). Divide be the range to get a double
// from 0 to 1.0 (half-inclusive). // from 0 to 1.0 (half-inclusive).
result /= 9007199254740992.0; result /= 9007199254740992.0;
wrenSetSlotDouble(vm, 0, result); wrenSetSlotDouble(vm, 0, result);
} }
static void randomInt0(WrenVM* vm) static void randomInt0(WrenVM* vm)
{ {
Well512* well = (Well512*)wrenGetSlotForeign(vm, 0); Well512* well = (Well512*)wrenGetSlotForeign(vm, 0);
wrenSetSlotDouble(vm, 0, (double)advanceState(well)); wrenSetSlotDouble(vm, 0, (double)advanceState(well));
} }
const char* wrenRandomSource() const char* wrenRandomSource()
{ {
return randomModuleSource; return randomModuleSource;
} }
WrenForeignClassMethods wrenRandomBindForeignClass(WrenVM* vm, WrenForeignClassMethods wrenRandomBindForeignClass(WrenVM* vm,
const char* module, const char* module,
const char* className) const char* className)
{ {
ASSERT(strcmp(className, "Random") == 0, "Should be in Random class."); ASSERT(strcmp(className, "Random") == 0, "Should be in Random class.");
WrenForeignClassMethods methods; WrenForeignClassMethods methods;
methods.allocate = randomAllocate; methods.allocate = randomAllocate;
methods.finalize = NULL; methods.finalize = NULL;
return methods; return methods;
} }
WrenForeignMethodFn wrenRandomBindForeignMethod(WrenVM* vm, WrenForeignMethodFn wrenRandomBindForeignMethod(WrenVM* vm,
const char* className, const char* className,
bool isStatic, bool isStatic,
const char* signature) const char* signature)
{ {
ASSERT(strcmp(className, "Random") == 0, "Should be in Random class."); ASSERT(strcmp(className, "Random") == 0, "Should be in Random class.");
if (strcmp(signature, "<allocate>") == 0) return randomAllocate; if (strcmp(signature, "<allocate>") == 0) return randomAllocate;
if (strcmp(signature, "seed_()") == 0) return randomSeed0; if (strcmp(signature, "seed_()") == 0) return randomSeed0;
if (strcmp(signature, "seed_(_)") == 0) return randomSeed1; if (strcmp(signature, "seed_(_)") == 0) return randomSeed1;
if (strcmp(signature, "seed_(_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_)") == 0) if (strcmp(signature, "seed_(_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_)") == 0)
{ {
return randomSeed16; return randomSeed16;
} }
if (strcmp(signature, "float()") == 0) return randomFloat; if (strcmp(signature, "float()") == 0) return randomFloat;
if (strcmp(signature, "int()") == 0) return randomInt0; if (strcmp(signature, "int()") == 0) return randomInt0;
ASSERT(false, "Unknown method."); ASSERT(false, "Unknown method.");
return NULL; return NULL;
} }
#endif #endif
+20 -20
View File
@@ -1,20 +1,20 @@
#ifndef wren_opt_random_h #ifndef wren_opt_random_h
#define wren_opt_random_h #define wren_opt_random_h
#include "wren_common.h" #include "wren_common.h"
#include "wren.h" #include "wren.h"
#if WREN_OPT_RANDOM #if WREN_OPT_RANDOM
const char* wrenRandomSource(); const char* wrenRandomSource();
WrenForeignClassMethods wrenRandomBindForeignClass(WrenVM* vm, WrenForeignClassMethods wrenRandomBindForeignClass(WrenVM* vm,
const char* module, const char* module,
const char* className); const char* className);
WrenForeignMethodFn wrenRandomBindForeignMethod(WrenVM* vm, WrenForeignMethodFn wrenRandomBindForeignMethod(WrenVM* vm,
const char* className, const char* className,
bool isStatic, bool isStatic,
const char* signature); const char* signature);
#endif #endif
#endif #endif
+22 -49
View File
@@ -47,65 +47,38 @@ foreign class Random {
int(end) { (float() * end).floor } int(end) { (float() * end).floor }
int(start, end) { (float() * (end - start)).floor + start } int(start, end) { (float() * (end - start)).floor + start }
sample(list) { sample(list, 1)[0] } sample(list) {
if (list.count == 0) Fiber.abort("Not enough elements to sample.")
return list[int(list.count)]
}
sample(list, count) { sample(list, count) {
if (count > list.count) Fiber.abort("Not enough elements to sample.") if (count > list.count) Fiber.abort("Not enough elements to sample.")
// There are (at least) two simple algorithms for choosing a number of var result = []
// samples from a list without replacement -- where we don't pick the same
// element more than once.
//
// The first is faster when the number of samples is small relative to the
// size of the collection. In many cases, it avoids scanning the entire
// list. In the common case of just wanting one sample, it's a single
// random index lookup.
//
// However, its performance degrades badly as the sample size increases.
// Vitter's algorithm always scans the entire list, but it's also always
// O(n).
//
// The cutoff point between the two follows a quadratic curve on the same
// size. Based on some empirical testing, scaling that by 5 seems to fit
// pretty closely and chooses the fastest one for the given sample and
// collection size.
if (count * count * 5 < list.count) {
// Pick random elements and retry if you hit a previously chosen one.
var picked = {}
var result = []
for (i in 0...count) {
// Find an index that we haven't already selected.
var index
while (true) {
index = int(list.count)
if (!picked.containsKey(index)) break
}
// The algorithm described in "Programming pearls: a sample of brilliance".
// Use a hash map for sample sizes less than 1/4 of the population size and
// an array of booleans for larger samples. This simple heuristic improves
// performance for large sample sizes as well as reduces memory usage.
if (count * 4 < list.count) {
var picked = {}
for (i in list.count - count...list.count) {
var index = int(i + 1)
if (picked.containsKey(index)) index = i
picked[index] = true picked[index] = true
result.add(list[index]) result.add(list[index])
} }
return result
} else { } else {
// Jeffrey Vitter's Algorithm R. var picked = List.filled(list.count, false)
for (i in list.count - count...list.count) {
// Fill the reservoir with the first elements in the list. var index = int(i + 1)
var result = list[0...count] if (picked[index]) index = i
picked[index] = true
// We want to ensure the results are always in random order, so shuffle result.add(list[index])
// them. In cases where the sample size is the entire collection, this
// devolves to running Fisher-Yates on a copy of the list.
shuffle(result)
// Now walk the rest of the list. For each element, randomly consider
// replacing one of the reservoir elements with it. The probability here
// works out such that it does this uniformly.
for (i in count...list.count) {
var slot = int(0, i + 1)
if (slot < count) result[slot] = list[i]
} }
return result
} }
return result
} }
shuffle(list) { shuffle(list) {
+22 -49
View File
@@ -49,65 +49,38 @@ static const char* randomModuleSource =
" int(end) { (float() * end).floor }\n" " int(end) { (float() * end).floor }\n"
" int(start, end) { (float() * (end - start)).floor + start }\n" " int(start, end) { (float() * (end - start)).floor + start }\n"
"\n" "\n"
" sample(list) { sample(list, 1)[0] }\n" " sample(list) {\n"
" if (list.count == 0) Fiber.abort(\"Not enough elements to sample.\")\n"
" return list[int(list.count)]\n"
" }\n"
" sample(list, count) {\n" " sample(list, count) {\n"
" if (count > list.count) Fiber.abort(\"Not enough elements to sample.\")\n" " if (count > list.count) Fiber.abort(\"Not enough elements to sample.\")\n"
"\n" "\n"
" // There are (at least) two simple algorithms for choosing a number of\n" " var result = []\n"
" // samples from a list without replacement -- where we don't pick the same\n"
" // element more than once.\n"
" //\n"
" // The first is faster when the number of samples is small relative to the\n"
" // size of the collection. In many cases, it avoids scanning the entire\n"
" // list. In the common case of just wanting one sample, it's a single\n"
" // random index lookup.\n"
" //\n"
" // However, its performance degrades badly as the sample size increases.\n"
" // Vitter's algorithm always scans the entire list, but it's also always\n"
" // O(n).\n"
" //\n"
" // The cutoff point between the two follows a quadratic curve on the same\n"
" // size. Based on some empirical testing, scaling that by 5 seems to fit\n"
" // pretty closely and chooses the fastest one for the given sample and\n"
" // collection size.\n"
" if (count * count * 5 < list.count) {\n"
" // Pick random elements and retry if you hit a previously chosen one.\n"
" var picked = {}\n"
" var result = []\n"
" for (i in 0...count) {\n"
" // Find an index that we haven't already selected.\n"
" var index\n"
" while (true) {\n"
" index = int(list.count)\n"
" if (!picked.containsKey(index)) break\n"
" }\n"
"\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" " picked[index] = true\n"
" result.add(list[index])\n" " result.add(list[index])\n"
" }\n" " }\n"
"\n"
" return result\n"
" } else {\n" " } else {\n"
" // Jeffrey Vitter's Algorithm R.\n" " var picked = List.filled(list.count, false)\n"
"\n" " for (i in list.count - count...list.count) {\n"
" // Fill the reservoir with the first elements in the list.\n" " var index = int(i + 1)\n"
" var result = list[0...count]\n" " if (picked[index]) index = i\n"
"\n" " picked[index] = true\n"
" // We want to ensure the results are always in random order, so shuffle\n" " result.add(list[index])\n"
" // them. In cases where the sample size is the entire collection, this\n"
" // devolves to running Fisher-Yates on a copy of the list.\n"
" shuffle(result)\n"
"\n"
" // Now walk the rest of the list. For each element, randomly consider\n"
" // replacing one of the reservoir elements with it. The probability here\n"
" // works out such that it does this uniformly.\n"
" for (i in count...list.count) {\n"
" var slot = int(0, i + 1)\n"
" if (slot < count) result[slot] = list[i]\n"
" }\n" " }\n"
"\n"
" return result\n"
" }\n" " }\n"
"\n"
" return result\n"
" }\n" " }\n"
"\n" "\n"
" shuffle(list) {\n" " shuffle(list) {\n"
+25 -27
View File
@@ -33,10 +33,10 @@
// http://gcc.gnu.org/onlinedocs/gcc-3.1.1/gcc/Labels-as-Values.html // 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 // Enabling this speeds up the main dispatch loop a bit, but requires compiler
// support. // support.
// // see https://bullno1.com/blog/switched-goto for alternative
// Defaults to true on supported compilers. // Defaults to true on supported compilers.
#ifndef WREN_COMPUTED_GOTO #ifndef WREN_COMPUTED_GOTO
#ifdef _MSC_VER #if defined(_MSC_VER) && !defined(__clang__)
// No computed gotos in Visual Studio. // No computed gotos in Visual Studio.
#define WREN_COMPUTED_GOTO 0 #define WREN_COMPUTED_GOTO 0
#else #else
@@ -114,17 +114,17 @@
#define MAX_FIELDS 255 #define MAX_FIELDS 255
// Use the VM's allocator to allocate an object of [type]. // Use the VM's allocator to allocate an object of [type].
#define ALLOCATE(vm, type) \ #define ALLOCATE(vm, type) \
((type*)wrenReallocate(vm, NULL, 0, sizeof(type))) ((type*)wrenReallocate(vm, NULL, 0, sizeof(type)))
// Use the VM's allocator to allocate an object of [mainType] containing a // Use the VM's allocator to allocate an object of [mainType] containing a
// flexible array of [count] objects of [arrayType]. // flexible array of [count] objects of [arrayType].
#define ALLOCATE_FLEX(vm, mainType, arrayType, count) \ #define ALLOCATE_FLEX(vm, mainType, arrayType, count) \
((mainType*)wrenReallocate(vm, NULL, 0, \ ((mainType*)wrenReallocate(vm, NULL, 0, \
sizeof(mainType) + sizeof(arrayType) * (count))) sizeof(mainType) + sizeof(arrayType) * (count)))
// Use the VM's allocator to allocate an array of [count] elements of [type]. // Use the VM's allocator to allocate an array of [count] elements of [type].
#define ALLOCATE_ARRAY(vm, type, count) \ #define ALLOCATE_ARRAY(vm, type, count) \
((type*)wrenReallocate(vm, NULL, 0, sizeof(type) * (count))) ((type*)wrenReallocate(vm, NULL, 0, sizeof(type) * (count)))
// Use the VM's allocator to free the previously allocated memory at [pointer]. // Use the VM's allocator to free the previously allocated memory at [pointer].
@@ -156,17 +156,16 @@
#include <stdio.h> #include <stdio.h>
#define ASSERT(condition, message) \ #define ASSERT(condition, message) \
do \ do \
{ \ { \
if (!(condition)) \ if (!(condition)) \
{ \ { \
fprintf(stderr, "[%s:%d] Assert failed in %s(): %s\n", \ fprintf(stderr, "[%s:%d] Assert failed in %s(): %s\n", \
__FILE__, __LINE__, __func__, message); \ __FILE__, __LINE__, __func__, message); \
abort(); \ abort(); \
} \ } \
} \ } while (false)
while(0)
// Indicates that we know execution should never reach this point in the // 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. // program. In debug mode, we assert this fact because it's a bug to get here.
@@ -175,18 +174,17 @@
// compiler the code can't be reached. This avoids "missing return" warnings // 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 // in some cases and also lets it perform some optimizations by assuming the
// code is never reached. // code is never reached.
#define UNREACHABLE() \ #define UNREACHABLE() \
do \ do \
{ \ { \
fprintf(stderr, "[%s:%d] This code should not be reached in %s()\n", \ fprintf(stderr, "[%s:%d] This code should not be reached in %s()\n", \
__FILE__, __LINE__, __func__); \ __FILE__, __LINE__, __func__); \
abort(); \ abort(); \
} \ } while (false)
while (0)
#else #else
#define ASSERT(condition, message) do {} while (0) #define ASSERT(condition, message) do { } while (false)
// Tell the compiler that this part of the code will never be reached. // Tell the compiler that this part of the code will never be reached.
#if defined( _MSC_VER ) #if defined( _MSC_VER )
+4130 -3590
View File
File diff suppressed because it is too large Load Diff
+57 -57
View File
@@ -1,57 +1,57 @@
#ifndef wren_compiler_h #ifndef wren_compiler_h
#define wren_compiler_h #define wren_compiler_h
#include "wren.h" #include "wren.h"
#include "wren_value.h" #include "wren_value.h"
typedef struct sCompiler Compiler; typedef struct sCompiler Compiler;
// This module defines the compiler for Wren. It takes a string of source code // 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 // 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 // does not build an actual AST during parsing and then consume that to
// generate code. Instead, the parser directly emits bytecode. // generate code. Instead, the parser directly emits bytecode.
// //
// This forces a few restrictions on the grammar and semantics of the language. // This forces a few restrictions on the grammar and semantics of the language.
// Things like forward references and arbitrary lookahead are much harder. We // Things like forward references and arbitrary lookahead are much harder. We
// get a lot in return for that, though. // get a lot in return for that, though.
// //
// The implementation is much simpler since we don't need to define a bunch of // 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 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. // 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 // Compilation is also faster since we don't create a bunch of temporary data
// structures and destroy them after generating code. // structures and destroy them after generating code.
// Compiles [source], a string of Wren source code located in [module], to an // 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 // [ObjFn] that will execute that code when invoked. Returns `NULL` if the
// source contains any syntax errors. // source contains any syntax errors.
// //
// If [isExpression] is `true`, [source] should be a single expression, and // If [isExpression] is `true`, [source] should be a single expression, and
// this compiles it to a function that evaluates and returns that expression. // this compiles it to a function that evaluates and returns that expression.
// Otherwise, [source] should be a series of top level statements. // Otherwise, [source] should be a series of top level statements.
// //
// If [printErrors] is `true`, any compile errors are output to stderr. // If [printErrors] is `true`, any compile errors are output to stderr.
// Otherwise, they are silently discarded. // Otherwise, they are silently discarded.
ObjFn* wrenCompile(WrenVM* vm, ObjModule* module, const char* source, ObjFn* wrenCompile(WrenVM* vm, ObjModule* module, const char* source,
bool isExpression, bool printErrors); bool isExpression, bool printErrors);
// When a class is defined, its superclass is not known until runtime since // 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 // 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: // 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 // - 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 // 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 // are positioned after superclass fields, and we don't know this until the
// superclass is known. // superclass is known.
// //
// - Superclass calls need to know which superclass to dispatch to. // - Superclass calls need to know which superclass to dispatch to.
// //
// We could handle this dynamically, but that adds overhead. Instead, when a // 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. // method is bound, we walk the bytecode for the function and patch it up.
void wrenBindMethodCode(ObjClass* classObj, ObjFn* fn); void wrenBindMethodCode(ObjClass* classObj, ObjFn* fn);
// Reaches all of the heap-allocated objects in use by [compiler] (and all of // 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. // its parents) so that they are not collected by the GC.
void wrenMarkCompiler(WrenVM* vm, Compiler* compiler); void wrenMarkCompiler(WrenVM* vm, Compiler* compiler);
#endif #endif
+1487 -1393
View File
File diff suppressed because it is too large Load Diff
+23 -23
View File
@@ -1,23 +1,23 @@
#ifndef wren_core_h #ifndef wren_core_h
#define wren_core_h #define wren_core_h
#include "wren_vm.h" #include "wren_vm.h"
// This module defines the built-in classes and their primitives methods that // 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 // 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 // of the core module itself in the primary language instead of in the host
// language. // language.
// //
// With Wren, we try to do as much of it in C as possible. Primitive methods // 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 // 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. // 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. // 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 // They can only be the top of the callstack, and immediately return. This
// makes it difficult to have primitive methods that rely on polymorphic // makes it difficult to have primitive methods that rely on polymorphic
// behavior. For example, `IO.write` should call `toString` on its argument, // behavior. For example, `IO.write` should call `toString` on its argument,
// including user-defined `toString` methods on user-defined classes. // including user-defined `toString` methods on user-defined classes.
void wrenInitializeCore(WrenVM* vm); void wrenInitializeCore(WrenVM* vm);
#endif #endif
+483 -438
View File
@@ -1,438 +1,483 @@
class Bool {} class Bool {}
class Fiber {} class Fiber {}
class Fn {} class Fn {}
class Null {} class Null {}
class Num {} class Num {}
class Sequence { class Sequence {
all(f) { all(f) {
var result = true var result = true
for (element in this) { for (element in this) {
result = f.call(element) result = f.call(element)
if (!result) return result if (!result) return result
} }
return result return result
} }
any(f) { any(f) {
var result = false var result = false
for (element in this) { for (element in this) {
result = f.call(element) result = f.call(element)
if (result) return result if (result) return result
} }
return result return result
} }
contains(element) { contains(element) {
for (item in this) { for (item in this) {
if (element == item) return true if (element == item) return true
} }
return false return false
} }
count { count {
var result = 0 var result = 0
for (element in this) { for (element in this) {
result = result + 1 result = result + 1
} }
return result return result
} }
count(f) { count(f) {
var result = 0 var result = 0
for (element in this) { for (element in this) {
if (f.call(element)) result = result + 1 if (f.call(element)) result = result + 1
} }
return result return result
} }
each(f) { each(f) {
for (element in this) { for (element in this) {
f.call(element) f.call(element)
} }
} }
isEmpty { iterate(null) ? false : true } isEmpty { iterate(null) ? false : true }
map(transformation) { MapSequence.new(this, transformation) } map(transformation) { MapSequence.new(this, transformation) }
skip(count) { skip(count) {
if (!(count is Num) || !count.isInteger || count < 0) { if (!(count is Num) || !count.isInteger || count < 0) {
Fiber.abort("Count must be a non-negative integer.") Fiber.abort("Count must be a non-negative integer.")
} }
return SkipSequence.new(this, count) return SkipSequence.new(this, count)
} }
take(count) { take(count) {
if (!(count is Num) || !count.isInteger || count < 0) { if (!(count is Num) || !count.isInteger || count < 0) {
Fiber.abort("Count must be a non-negative integer.") Fiber.abort("Count must be a non-negative integer.")
} }
return TakeSequence.new(this, count) return TakeSequence.new(this, count)
} }
where(predicate) { WhereSequence.new(this, predicate) } where(predicate) { WhereSequence.new(this, predicate) }
reduce(acc, f) { reduce(acc, f) {
for (element in this) { for (element in this) {
acc = f.call(acc, element) acc = f.call(acc, element)
} }
return acc return acc
} }
reduce(f) { reduce(f) {
var iter = iterate(null) var iter = iterate(null)
if (!iter) Fiber.abort("Can't reduce an empty sequence.") if (!iter) Fiber.abort("Can't reduce an empty sequence.")
// Seed with the first element. // Seed with the first element.
var result = iteratorValue(iter) var result = iteratorValue(iter)
while (iter = iterate(iter)) { while (iter = iterate(iter)) {
result = f.call(result, iteratorValue(iter)) result = f.call(result, iteratorValue(iter))
} }
return result return result
} }
join() { join("") } join() { join("") }
join(sep) { join(sep) {
var first = true var first = true
var result = "" var result = ""
for (element in this) { for (element in this) {
if (!first) result = result + sep if (!first) result = result + sep
first = false first = false
result = result + element.toString result = result + element.toString
} }
return result return result
} }
toList { toList {
var result = List.new() var result = List.new()
for (element in this) { for (element in this) {
result.add(element) result.add(element)
} }
return result return result
} }
} }
class MapSequence is Sequence { class MapSequence is Sequence {
construct new(sequence, fn) { construct new(sequence, fn) {
_sequence = sequence _sequence = sequence
_fn = fn _fn = fn
} }
iterate(iterator) { _sequence.iterate(iterator) } iterate(iterator) { _sequence.iterate(iterator) }
iteratorValue(iterator) { _fn.call(_sequence.iteratorValue(iterator)) } iteratorValue(iterator) { _fn.call(_sequence.iteratorValue(iterator)) }
} }
class SkipSequence is Sequence { class SkipSequence is Sequence {
construct new(sequence, count) { construct new(sequence, count) {
_sequence = sequence _sequence = sequence
_count = count _count = count
} }
iterate(iterator) { iterate(iterator) {
if (iterator) { if (iterator) {
return _sequence.iterate(iterator) return _sequence.iterate(iterator)
} else { } else {
iterator = _sequence.iterate(iterator) iterator = _sequence.iterate(iterator)
var count = _count var count = _count
while (count > 0 && iterator) { while (count > 0 && iterator) {
iterator = _sequence.iterate(iterator) iterator = _sequence.iterate(iterator)
count = count - 1 count = count - 1
} }
return iterator return iterator
} }
} }
iteratorValue(iterator) { _sequence.iteratorValue(iterator) } iteratorValue(iterator) { _sequence.iteratorValue(iterator) }
} }
class TakeSequence is Sequence { class TakeSequence is Sequence {
construct new(sequence, count) { construct new(sequence, count) {
_sequence = sequence _sequence = sequence
_count = count _count = count
} }
iterate(iterator) { iterate(iterator) {
if (!iterator) _taken = 1 else _taken = _taken + 1 if (!iterator) _taken = 1 else _taken = _taken + 1
return _taken > _count ? null : _sequence.iterate(iterator) return _taken > _count ? null : _sequence.iterate(iterator)
} }
iteratorValue(iterator) { _sequence.iteratorValue(iterator) } iteratorValue(iterator) { _sequence.iteratorValue(iterator) }
} }
class WhereSequence is Sequence { class WhereSequence is Sequence {
construct new(sequence, fn) { construct new(sequence, fn) {
_sequence = sequence _sequence = sequence
_fn = fn _fn = fn
} }
iterate(iterator) { iterate(iterator) {
while (iterator = _sequence.iterate(iterator)) { while (iterator = _sequence.iterate(iterator)) {
if (_fn.call(_sequence.iteratorValue(iterator))) break if (_fn.call(_sequence.iteratorValue(iterator))) break
} }
return iterator return iterator
} }
iteratorValue(iterator) { _sequence.iteratorValue(iterator) } iteratorValue(iterator) { _sequence.iteratorValue(iterator) }
} }
class String is Sequence { class String is Sequence {
bytes { StringByteSequence.new(this) } bytes { StringByteSequence.new(this) }
codePoints { StringCodePointSequence.new(this) } codePoints { StringCodePointSequence.new(this) }
split(delimiter) { split(delimiter) {
if (!(delimiter is String) || delimiter.isEmpty) { if (!(delimiter is String) || delimiter.isEmpty) {
Fiber.abort("Delimiter must be a non-empty string.") Fiber.abort("Delimiter must be a non-empty string.")
} }
var result = [] var result = []
var last = 0 var last = 0
var index = 0 var index = 0
var delimSize = delimiter.byteCount_ var delimSize = delimiter.byteCount_
var size = byteCount_ var size = byteCount_
while (last < size && (index = indexOf(delimiter, last)) != -1) { while (last < size && (index = indexOf(delimiter, last)) != -1) {
result.add(this[last...index]) result.add(this[last...index])
last = index + delimSize last = index + delimSize
} }
if (last < size) { if (last < size) {
result.add(this[last..-1]) result.add(this[last..-1])
} else { } else {
result.add("") result.add("")
} }
return result return result
} }
replace(from, to) { replace(from, to) {
if (!(from is String) || from.isEmpty) { if (!(from is String) || from.isEmpty) {
Fiber.abort("From must be a non-empty string.") Fiber.abort("From must be a non-empty string.")
} else if (!(to is String)) { } else if (!(to is String)) {
Fiber.abort("To must be a string.") Fiber.abort("To must be a string.")
} }
var result = "" var result = ""
var last = 0 var last = 0
var index = 0 var index = 0
var fromSize = from.byteCount_ var fromSize = from.byteCount_
var size = byteCount_ var size = byteCount_
while (last < size && (index = indexOf(from, last)) != -1) { while (last < size && (index = indexOf(from, last)) != -1) {
result = result + this[last...index] + to result = result + this[last...index] + to
last = index + fromSize last = index + fromSize
} }
if (last < size) result = result + this[last..-1] if (last < size) result = result + this[last..-1]
return result return result
} }
trim() { trim_("\t\r\n ", true, true) } trim() { trim_("\t\r\n ", true, true) }
trim(chars) { trim_(chars, true, true) } trim(chars) { trim_(chars, true, true) }
trimEnd() { trim_("\t\r\n ", false, true) } trimEnd() { trim_("\t\r\n ", false, true) }
trimEnd(chars) { trim_(chars, false, true) } trimEnd(chars) { trim_(chars, false, true) }
trimStart() { trim_("\t\r\n ", true, false) } trimStart() { trim_("\t\r\n ", true, false) }
trimStart(chars) { trim_(chars, true, false) } trimStart(chars) { trim_(chars, true, false) }
trim_(chars, trimStart, trimEnd) { trim_(chars, trimStart, trimEnd) {
if (!(chars is String)) { if (!(chars is String)) {
Fiber.abort("Characters must be a string.") Fiber.abort("Characters must be a string.")
} }
var codePoints = chars.codePoints.toList var codePoints = chars.codePoints.toList
var start var start
if (trimStart) { if (trimStart) {
while (start = iterate(start)) { while (start = iterate(start)) {
if (!codePoints.contains(codePointAt_(start))) break if (!codePoints.contains(codePointAt_(start))) break
} }
if (start == false) return "" if (start == false) return ""
} else { } else {
start = 0 start = 0
} }
var end var end
if (trimEnd) { if (trimEnd) {
end = byteCount_ - 1 end = byteCount_ - 1
while (end >= start) { while (end >= start) {
var codePoint = codePointAt_(end) var codePoint = codePointAt_(end)
if (codePoint != -1 && !codePoints.contains(codePoint)) break if (codePoint != -1 && !codePoints.contains(codePoint)) break
end = end - 1 end = end - 1
} }
if (end < start) return "" if (end < start) return ""
} else { } else {
end = -1 end = -1
} }
return this[start..end] return this[start..end]
} }
*(count) { *(count) {
if (!(count is Num) || !count.isInteger || count < 0) { if (!(count is Num) || !count.isInteger || count < 0) {
Fiber.abort("Count must be a non-negative integer.") Fiber.abort("Count must be a non-negative integer.")
} }
var result = "" var result = ""
for (i in 0...count) { for (i in 0...count) {
result = result + this result = result + this
} }
return result return result
} }
} }
class StringByteSequence is Sequence { class StringByteSequence is Sequence {
construct new(string) { construct new(string) {
_string = string _string = string
} }
[index] { _string.byteAt_(index) } [index] { _string.byteAt_(index) }
iterate(iterator) { _string.iterateByte_(iterator) } iterate(iterator) { _string.iterateByte_(iterator) }
iteratorValue(iterator) { _string.byteAt_(iterator) } iteratorValue(iterator) { _string.byteAt_(iterator) }
count { _string.byteCount_ } count { _string.byteCount_ }
} }
class StringCodePointSequence is Sequence { class StringCodePointSequence is Sequence {
construct new(string) { construct new(string) {
_string = string _string = string
} }
[index] { _string.codePointAt_(index) } [index] { _string.codePointAt_(index) }
iterate(iterator) { _string.iterate(iterator) } iterate(iterator) { _string.iterate(iterator) }
iteratorValue(iterator) { _string.codePointAt_(iterator) } iteratorValue(iterator) { _string.codePointAt_(iterator) }
count { _string.count } count { _string.count }
} }
class List is Sequence { class List is Sequence {
addAll(other) { addAll(other) {
for (element in other) { for (element in other) {
add(element) add(element)
} }
return other return other
} }
toString { "[%(join(", "))]" } sort() { sort {|low, high| low < high } }
+(other) { sort(comparer) {
var result = this[0..-1] if (!(comparer is Fn)) {
for (element in other) { Fiber.abort("Comparer must be a function.")
result.add(element) }
} quicksort_(0, count - 1, comparer)
return result return this
} }
*(count) { quicksort_(low, high, comparer) {
if (!(count is Num) || !count.isInteger || count < 0) { if (low < high) {
Fiber.abort("Count must be a non-negative integer.") var p = partition_(low, high, comparer)
} quicksort_(low, p - 1, comparer)
quicksort_(p + 1, high, comparer)
var result = [] }
for (i in 0...count) { }
result.addAll(this)
} partition_(low, high, comparer) {
return result var p = this[high]
} var i = low - 1
} for (j in low..(high-1)) {
if (comparer.call(this[j], p)) {
class Map is Sequence { i = i + 1
keys { MapKeySequence.new(this) } var t = this[i]
values { MapValueSequence.new(this) } this[i] = this[j]
this[j] = t
toString { }
var first = true }
var result = "{" var t = this[i+1]
this[i+1] = this[high]
for (key in keys) { this[high] = t
if (!first) result = result + ", " return i+1
first = false }
result = result + "%(key): %(this[key])"
} toString { "[%(join(", "))]" }
return result + "}" +(other) {
} var result = this[0..-1]
for (element in other) {
iteratorValue(iterator) { result.add(element)
return MapEntry.new( }
keyIteratorValue_(iterator), return result
valueIteratorValue_(iterator)) }
}
} *(count) {
if (!(count is Num) || !count.isInteger || count < 0) {
class MapEntry { Fiber.abort("Count must be a non-negative integer.")
construct new(key, value) { }
_key = key
_value = value var result = []
} for (i in 0...count) {
result.addAll(this)
key { _key } }
value { _value } return result
}
toString { "%(_key):%(_value)" } }
}
class Map is Sequence {
class MapKeySequence is Sequence { keys { MapKeySequence.new(this) }
construct new(map) { values { MapValueSequence.new(this) }
_map = map
} toString {
var first = true
iterate(n) { _map.iterate(n) } var result = "{"
iteratorValue(iterator) { _map.keyIteratorValue_(iterator) }
} for (key in keys) {
if (!first) result = result + ", "
class MapValueSequence is Sequence { first = false
construct new(map) { result = result + "%(key): %(this[key])"
_map = map }
}
return result + "}"
iterate(n) { _map.iterate(n) } }
iteratorValue(iterator) { _map.valueIteratorValue_(iterator) }
} iteratorValue(iterator) {
return MapEntry.new(
class Range is Sequence {} keyIteratorValue_(iterator),
valueIteratorValue_(iterator))
class System { }
static print() { }
writeString_("\n")
} class MapEntry {
construct new(key, value) {
static print(obj) { _key = key
writeObject_(obj) _value = value
writeString_("\n") }
return obj
} key { _key }
value { _value }
static printAll(sequence) {
for (object in sequence) writeObject_(object) toString { "%(_key):%(_value)" }
writeString_("\n") }
}
class MapKeySequence is Sequence {
static write(obj) { construct new(map) {
writeObject_(obj) _map = map
return obj }
}
iterate(n) { _map.iterate(n) }
static writeAll(sequence) { iteratorValue(iterator) { _map.keyIteratorValue_(iterator) }
for (object in sequence) writeObject_(object) }
}
class MapValueSequence is Sequence {
static writeObject_(obj) { construct new(map) {
var string = obj.toString _map = map
if (string is String) { }
writeString_(string)
} else { iterate(n) { _map.iterate(n) }
writeString_("[invalid toString]") iteratorValue(iterator) { _map.valueIteratorValue_(iterator) }
} }
}
} class Range is Sequence {}
class System {
static print() {
writeString_("\n")
}
static print(obj) {
writeObject_(obj)
writeString_("\n")
return obj
}
static printAll(sequence) {
for (object in sequence) writeObject_(object)
writeString_("\n")
}
static write(obj) {
writeObject_(obj)
return obj
}
static writeAll(sequence) {
for (object in sequence) writeObject_(object)
}
static writeObject_(obj) {
var string = obj.toString
if (string is String) {
writeString_(string)
} else {
writeString_("[invalid toString]")
}
}
}
class ClassAttributes {
self { _attributes }
methods { _methods }
construct new(attributes, methods) {
_attributes = attributes
_methods = methods
}
toString { "attributes:%(_attributes) methods:%(_methods)" }
}
+486 -440
View File
@@ -1,440 +1,486 @@
// Generated automatically from src/vm/wren_core.wren. Do not edit. // Generated automatically from src/vm/wren_core.wren. Do not edit.
static const char* coreModuleSource = static const char* coreModuleSource =
"class Bool {}\n" "class Bool {}\n"
"class Fiber {}\n" "class Fiber {}\n"
"class Fn {}\n" "class Fn {}\n"
"class Null {}\n" "class Null {}\n"
"class Num {}\n" "class Num {}\n"
"\n" "\n"
"class Sequence {\n" "class Sequence {\n"
" all(f) {\n" " all(f) {\n"
" var result = true\n" " var result = true\n"
" for (element in this) {\n" " for (element in this) {\n"
" result = f.call(element)\n" " result = f.call(element)\n"
" if (!result) return result\n" " if (!result) return result\n"
" }\n" " }\n"
" return result\n" " return result\n"
" }\n" " }\n"
"\n" "\n"
" any(f) {\n" " any(f) {\n"
" var result = false\n" " var result = false\n"
" for (element in this) {\n" " for (element in this) {\n"
" result = f.call(element)\n" " result = f.call(element)\n"
" if (result) return result\n" " if (result) return result\n"
" }\n" " }\n"
" return result\n" " return result\n"
" }\n" " }\n"
"\n" "\n"
" contains(element) {\n" " contains(element) {\n"
" for (item in this) {\n" " for (item in this) {\n"
" if (element == item) return true\n" " if (element == item) return true\n"
" }\n" " }\n"
" return false\n" " return false\n"
" }\n" " }\n"
"\n" "\n"
" count {\n" " count {\n"
" var result = 0\n" " var result = 0\n"
" for (element in this) {\n" " for (element in this) {\n"
" result = result + 1\n" " result = result + 1\n"
" }\n" " }\n"
" return result\n" " return result\n"
" }\n" " }\n"
"\n" "\n"
" count(f) {\n" " count(f) {\n"
" var result = 0\n" " var result = 0\n"
" for (element in this) {\n" " for (element in this) {\n"
" if (f.call(element)) result = result + 1\n" " if (f.call(element)) result = result + 1\n"
" }\n" " }\n"
" return result\n" " return result\n"
" }\n" " }\n"
"\n" "\n"
" each(f) {\n" " each(f) {\n"
" for (element in this) {\n" " for (element in this) {\n"
" f.call(element)\n" " f.call(element)\n"
" }\n" " }\n"
" }\n" " }\n"
"\n" "\n"
" isEmpty { iterate(null) ? false : true }\n" " isEmpty { iterate(null) ? false : true }\n"
"\n" "\n"
" map(transformation) { MapSequence.new(this, transformation) }\n" " map(transformation) { MapSequence.new(this, transformation) }\n"
"\n" "\n"
" skip(count) {\n" " skip(count) {\n"
" if (!(count is Num) || !count.isInteger || count < 0) {\n" " if (!(count is Num) || !count.isInteger || count < 0) {\n"
" Fiber.abort(\"Count must be a non-negative integer.\")\n" " Fiber.abort(\"Count must be a non-negative integer.\")\n"
" }\n" " }\n"
"\n" "\n"
" return SkipSequence.new(this, count)\n" " return SkipSequence.new(this, count)\n"
" }\n" " }\n"
"\n" "\n"
" take(count) {\n" " take(count) {\n"
" if (!(count is Num) || !count.isInteger || count < 0) {\n" " if (!(count is Num) || !count.isInteger || count < 0) {\n"
" Fiber.abort(\"Count must be a non-negative integer.\")\n" " Fiber.abort(\"Count must be a non-negative integer.\")\n"
" }\n" " }\n"
"\n" "\n"
" return TakeSequence.new(this, count)\n" " return TakeSequence.new(this, count)\n"
" }\n" " }\n"
"\n" "\n"
" where(predicate) { WhereSequence.new(this, predicate) }\n" " where(predicate) { WhereSequence.new(this, predicate) }\n"
"\n" "\n"
" reduce(acc, f) {\n" " reduce(acc, f) {\n"
" for (element in this) {\n" " for (element in this) {\n"
" acc = f.call(acc, element)\n" " acc = f.call(acc, element)\n"
" }\n" " }\n"
" return acc\n" " return acc\n"
" }\n" " }\n"
"\n" "\n"
" reduce(f) {\n" " reduce(f) {\n"
" var iter = iterate(null)\n" " var iter = iterate(null)\n"
" if (!iter) Fiber.abort(\"Can't reduce an empty sequence.\")\n" " if (!iter) Fiber.abort(\"Can't reduce an empty sequence.\")\n"
"\n" "\n"
" // Seed with the first element.\n" " // Seed with the first element.\n"
" var result = iteratorValue(iter)\n" " var result = iteratorValue(iter)\n"
" while (iter = iterate(iter)) {\n" " while (iter = iterate(iter)) {\n"
" result = f.call(result, iteratorValue(iter))\n" " result = f.call(result, iteratorValue(iter))\n"
" }\n" " }\n"
"\n" "\n"
" return result\n" " return result\n"
" }\n" " }\n"
"\n" "\n"
" join() { join(\"\") }\n" " join() { join(\"\") }\n"
"\n" "\n"
" join(sep) {\n" " join(sep) {\n"
" var first = true\n" " var first = true\n"
" var result = \"\"\n" " var result = \"\"\n"
"\n" "\n"
" for (element in this) {\n" " for (element in this) {\n"
" if (!first) result = result + sep\n" " if (!first) result = result + sep\n"
" first = false\n" " first = false\n"
" result = result + element.toString\n" " result = result + element.toString\n"
" }\n" " }\n"
"\n" "\n"
" return result\n" " return result\n"
" }\n" " }\n"
"\n" "\n"
" toList {\n" " toList {\n"
" var result = List.new()\n" " var result = List.new()\n"
" for (element in this) {\n" " for (element in this) {\n"
" result.add(element)\n" " result.add(element)\n"
" }\n" " }\n"
" return result\n" " return result\n"
" }\n" " }\n"
"}\n" "}\n"
"\n" "\n"
"class MapSequence is Sequence {\n" "class MapSequence is Sequence {\n"
" construct new(sequence, fn) {\n" " construct new(sequence, fn) {\n"
" _sequence = sequence\n" " _sequence = sequence\n"
" _fn = fn\n" " _fn = fn\n"
" }\n" " }\n"
"\n" "\n"
" iterate(iterator) { _sequence.iterate(iterator) }\n" " iterate(iterator) { _sequence.iterate(iterator) }\n"
" iteratorValue(iterator) { _fn.call(_sequence.iteratorValue(iterator)) }\n" " iteratorValue(iterator) { _fn.call(_sequence.iteratorValue(iterator)) }\n"
"}\n" "}\n"
"\n" "\n"
"class SkipSequence is Sequence {\n" "class SkipSequence is Sequence {\n"
" construct new(sequence, count) {\n" " construct new(sequence, count) {\n"
" _sequence = sequence\n" " _sequence = sequence\n"
" _count = count\n" " _count = count\n"
" }\n" " }\n"
"\n" "\n"
" iterate(iterator) {\n" " iterate(iterator) {\n"
" if (iterator) {\n" " if (iterator) {\n"
" return _sequence.iterate(iterator)\n" " return _sequence.iterate(iterator)\n"
" } else {\n" " } else {\n"
" iterator = _sequence.iterate(iterator)\n" " iterator = _sequence.iterate(iterator)\n"
" var count = _count\n" " var count = _count\n"
" while (count > 0 && iterator) {\n" " while (count > 0 && iterator) {\n"
" iterator = _sequence.iterate(iterator)\n" " iterator = _sequence.iterate(iterator)\n"
" count = count - 1\n" " count = count - 1\n"
" }\n" " }\n"
" return iterator\n" " return iterator\n"
" }\n" " }\n"
" }\n" " }\n"
"\n" "\n"
" iteratorValue(iterator) { _sequence.iteratorValue(iterator) }\n" " iteratorValue(iterator) { _sequence.iteratorValue(iterator) }\n"
"}\n" "}\n"
"\n" "\n"
"class TakeSequence is Sequence {\n" "class TakeSequence is Sequence {\n"
" construct new(sequence, count) {\n" " construct new(sequence, count) {\n"
" _sequence = sequence\n" " _sequence = sequence\n"
" _count = count\n" " _count = count\n"
" }\n" " }\n"
"\n" "\n"
" iterate(iterator) {\n" " iterate(iterator) {\n"
" if (!iterator) _taken = 1 else _taken = _taken + 1\n" " if (!iterator) _taken = 1 else _taken = _taken + 1\n"
" return _taken > _count ? null : _sequence.iterate(iterator)\n" " return _taken > _count ? null : _sequence.iterate(iterator)\n"
" }\n" " }\n"
"\n" "\n"
" iteratorValue(iterator) { _sequence.iteratorValue(iterator) }\n" " iteratorValue(iterator) { _sequence.iteratorValue(iterator) }\n"
"}\n" "}\n"
"\n" "\n"
"class WhereSequence is Sequence {\n" "class WhereSequence is Sequence {\n"
" construct new(sequence, fn) {\n" " construct new(sequence, fn) {\n"
" _sequence = sequence\n" " _sequence = sequence\n"
" _fn = fn\n" " _fn = fn\n"
" }\n" " }\n"
"\n" "\n"
" iterate(iterator) {\n" " iterate(iterator) {\n"
" while (iterator = _sequence.iterate(iterator)) {\n" " while (iterator = _sequence.iterate(iterator)) {\n"
" if (_fn.call(_sequence.iteratorValue(iterator))) break\n" " if (_fn.call(_sequence.iteratorValue(iterator))) break\n"
" }\n" " }\n"
" return iterator\n" " return iterator\n"
" }\n" " }\n"
"\n" "\n"
" iteratorValue(iterator) { _sequence.iteratorValue(iterator) }\n" " iteratorValue(iterator) { _sequence.iteratorValue(iterator) }\n"
"}\n" "}\n"
"\n" "\n"
"class String is Sequence {\n" "class String is Sequence {\n"
" bytes { StringByteSequence.new(this) }\n" " bytes { StringByteSequence.new(this) }\n"
" codePoints { StringCodePointSequence.new(this) }\n" " codePoints { StringCodePointSequence.new(this) }\n"
"\n" "\n"
" split(delimiter) {\n" " split(delimiter) {\n"
" if (!(delimiter is String) || delimiter.isEmpty) {\n" " if (!(delimiter is String) || delimiter.isEmpty) {\n"
" Fiber.abort(\"Delimiter must be a non-empty string.\")\n" " Fiber.abort(\"Delimiter must be a non-empty string.\")\n"
" }\n" " }\n"
"\n" "\n"
" var result = []\n" " var result = []\n"
"\n" "\n"
" var last = 0\n" " var last = 0\n"
" var index = 0\n" " var index = 0\n"
"\n" "\n"
" var delimSize = delimiter.byteCount_\n" " var delimSize = delimiter.byteCount_\n"
" var size = byteCount_\n" " var size = byteCount_\n"
"\n" "\n"
" while (last < size && (index = indexOf(delimiter, last)) != -1) {\n" " while (last < size && (index = indexOf(delimiter, last)) != -1) {\n"
" result.add(this[last...index])\n" " result.add(this[last...index])\n"
" last = index + delimSize\n" " last = index + delimSize\n"
" }\n" " }\n"
"\n" "\n"
" if (last < size) {\n" " if (last < size) {\n"
" result.add(this[last..-1])\n" " result.add(this[last..-1])\n"
" } else {\n" " } else {\n"
" result.add(\"\")\n" " result.add(\"\")\n"
" }\n" " }\n"
" return result\n" " return result\n"
" }\n" " }\n"
"\n" "\n"
" replace(from, to) {\n" " replace(from, to) {\n"
" if (!(from is String) || from.isEmpty) {\n" " if (!(from is String) || from.isEmpty) {\n"
" Fiber.abort(\"From must be a non-empty string.\")\n" " Fiber.abort(\"From must be a non-empty string.\")\n"
" } else if (!(to is String)) {\n" " } else if (!(to is String)) {\n"
" Fiber.abort(\"To must be a string.\")\n" " Fiber.abort(\"To must be a string.\")\n"
" }\n" " }\n"
"\n" "\n"
" var result = \"\"\n" " var result = \"\"\n"
"\n" "\n"
" var last = 0\n" " var last = 0\n"
" var index = 0\n" " var index = 0\n"
"\n" "\n"
" var fromSize = from.byteCount_\n" " var fromSize = from.byteCount_\n"
" var size = byteCount_\n" " var size = byteCount_\n"
"\n" "\n"
" while (last < size && (index = indexOf(from, last)) != -1) {\n" " while (last < size && (index = indexOf(from, last)) != -1) {\n"
" result = result + this[last...index] + to\n" " result = result + this[last...index] + to\n"
" last = index + fromSize\n" " last = index + fromSize\n"
" }\n" " }\n"
"\n" "\n"
" if (last < size) result = result + this[last..-1]\n" " if (last < size) result = result + this[last..-1]\n"
"\n" "\n"
" return result\n" " return result\n"
" }\n" " }\n"
"\n" "\n"
" trim() { trim_(\"\t\r\n \", true, true) }\n" " trim() { trim_(\"\t\r\n \", true, true) }\n"
" trim(chars) { trim_(chars, true, true) }\n" " trim(chars) { trim_(chars, true, true) }\n"
" trimEnd() { trim_(\"\t\r\n \", false, true) }\n" " trimEnd() { trim_(\"\t\r\n \", false, true) }\n"
" trimEnd(chars) { trim_(chars, false, true) }\n" " trimEnd(chars) { trim_(chars, false, true) }\n"
" trimStart() { trim_(\"\t\r\n \", true, false) }\n" " trimStart() { trim_(\"\t\r\n \", true, false) }\n"
" trimStart(chars) { trim_(chars, true, false) }\n" " trimStart(chars) { trim_(chars, true, false) }\n"
"\n" "\n"
" trim_(chars, trimStart, trimEnd) {\n" " trim_(chars, trimStart, trimEnd) {\n"
" if (!(chars is String)) {\n" " if (!(chars is String)) {\n"
" Fiber.abort(\"Characters must be a string.\")\n" " Fiber.abort(\"Characters must be a string.\")\n"
" }\n" " }\n"
"\n" "\n"
" var codePoints = chars.codePoints.toList\n" " var codePoints = chars.codePoints.toList\n"
"\n" "\n"
" var start\n" " var start\n"
" if (trimStart) {\n" " if (trimStart) {\n"
" while (start = iterate(start)) {\n" " while (start = iterate(start)) {\n"
" if (!codePoints.contains(codePointAt_(start))) break\n" " if (!codePoints.contains(codePointAt_(start))) break\n"
" }\n" " }\n"
"\n" "\n"
" if (start == false) return \"\"\n" " if (start == false) return \"\"\n"
" } else {\n" " } else {\n"
" start = 0\n" " start = 0\n"
" }\n" " }\n"
"\n" "\n"
" var end\n" " var end\n"
" if (trimEnd) {\n" " if (trimEnd) {\n"
" end = byteCount_ - 1\n" " end = byteCount_ - 1\n"
" while (end >= start) {\n" " while (end >= start) {\n"
" var codePoint = codePointAt_(end)\n" " var codePoint = codePointAt_(end)\n"
" if (codePoint != -1 && !codePoints.contains(codePoint)) break\n" " if (codePoint != -1 && !codePoints.contains(codePoint)) break\n"
" end = end - 1\n" " end = end - 1\n"
" }\n" " }\n"
"\n" "\n"
" if (end < start) return \"\"\n" " if (end < start) return \"\"\n"
" } else {\n" " } else {\n"
" end = -1\n" " end = -1\n"
" }\n" " }\n"
"\n" "\n"
" return this[start..end]\n" " return this[start..end]\n"
" }\n" " }\n"
"\n" "\n"
" *(count) {\n" " *(count) {\n"
" if (!(count is Num) || !count.isInteger || count < 0) {\n" " if (!(count is Num) || !count.isInteger || count < 0) {\n"
" Fiber.abort(\"Count must be a non-negative integer.\")\n" " Fiber.abort(\"Count must be a non-negative integer.\")\n"
" }\n" " }\n"
"\n" "\n"
" var result = \"\"\n" " var result = \"\"\n"
" for (i in 0...count) {\n" " for (i in 0...count) {\n"
" result = result + this\n" " result = result + this\n"
" }\n" " }\n"
" return result\n" " return result\n"
" }\n" " }\n"
"}\n" "}\n"
"\n" "\n"
"class StringByteSequence is Sequence {\n" "class StringByteSequence is Sequence {\n"
" construct new(string) {\n" " construct new(string) {\n"
" _string = string\n" " _string = string\n"
" }\n" " }\n"
"\n" "\n"
" [index] { _string.byteAt_(index) }\n" " [index] { _string.byteAt_(index) }\n"
" iterate(iterator) { _string.iterateByte_(iterator) }\n" " iterate(iterator) { _string.iterateByte_(iterator) }\n"
" iteratorValue(iterator) { _string.byteAt_(iterator) }\n" " iteratorValue(iterator) { _string.byteAt_(iterator) }\n"
"\n" "\n"
" count { _string.byteCount_ }\n" " count { _string.byteCount_ }\n"
"}\n" "}\n"
"\n" "\n"
"class StringCodePointSequence is Sequence {\n" "class StringCodePointSequence is Sequence {\n"
" construct new(string) {\n" " construct new(string) {\n"
" _string = string\n" " _string = string\n"
" }\n" " }\n"
"\n" "\n"
" [index] { _string.codePointAt_(index) }\n" " [index] { _string.codePointAt_(index) }\n"
" iterate(iterator) { _string.iterate(iterator) }\n" " iterate(iterator) { _string.iterate(iterator) }\n"
" iteratorValue(iterator) { _string.codePointAt_(iterator) }\n" " iteratorValue(iterator) { _string.codePointAt_(iterator) }\n"
"\n" "\n"
" count { _string.count }\n" " count { _string.count }\n"
"}\n" "}\n"
"\n" "\n"
"class List is Sequence {\n" "class List is Sequence {\n"
" addAll(other) {\n" " addAll(other) {\n"
" for (element in other) {\n" " for (element in other) {\n"
" add(element)\n" " add(element)\n"
" }\n" " }\n"
" return other\n" " return other\n"
" }\n" " }\n"
"\n" "\n"
" toString { \"[%(join(\", \"))]\" }\n" " sort() { sort {|low, high| low < high } }\n"
"\n" "\n"
" +(other) {\n" " sort(comparer) {\n"
" var result = this[0..-1]\n" " if (!(comparer is Fn)) {\n"
" for (element in other) {\n" " Fiber.abort(\"Comparer must be a function.\")\n"
" result.add(element)\n" " }\n"
" }\n" " quicksort_(0, count - 1, comparer)\n"
" return result\n" " return this\n"
" }\n" " }\n"
"\n" "\n"
" *(count) {\n" " quicksort_(low, high, comparer) {\n"
" if (!(count is Num) || !count.isInteger || count < 0) {\n" " if (low < high) {\n"
" Fiber.abort(\"Count must be a non-negative integer.\")\n" " var p = partition_(low, high, comparer)\n"
" }\n" " quicksort_(low, p - 1, comparer)\n"
"\n" " quicksort_(p + 1, high, comparer)\n"
" var result = []\n" " }\n"
" for (i in 0...count) {\n" " }\n"
" result.addAll(this)\n" "\n"
" }\n" " partition_(low, high, comparer) {\n"
" return result\n" " var p = this[high]\n"
" }\n" " var i = low - 1\n"
"}\n" " for (j in low..(high-1)) {\n"
"\n" " if (comparer.call(this[j], p)) { \n"
"class Map is Sequence {\n" " i = i + 1\n"
" keys { MapKeySequence.new(this) }\n" " var t = this[i]\n"
" values { MapValueSequence.new(this) }\n" " this[i] = this[j]\n"
"\n" " this[j] = t\n"
" toString {\n" " }\n"
" var first = true\n" " }\n"
" var result = \"{\"\n" " var t = this[i+1]\n"
"\n" " this[i+1] = this[high]\n"
" for (key in keys) {\n" " this[high] = t\n"
" if (!first) result = result + \", \"\n" " return i+1\n"
" first = false\n" " }\n"
" result = result + \"%(key): %(this[key])\"\n" "\n"
" }\n" " toString { \"[%(join(\", \"))]\" }\n"
"\n" "\n"
" return result + \"}\"\n" " +(other) {\n"
" }\n" " var result = this[0..-1]\n"
"\n" " for (element in other) {\n"
" iteratorValue(iterator) {\n" " result.add(element)\n"
" return MapEntry.new(\n" " }\n"
" keyIteratorValue_(iterator),\n" " return result\n"
" valueIteratorValue_(iterator))\n" " }\n"
" }\n" "\n"
"}\n" " *(count) {\n"
"\n" " if (!(count is Num) || !count.isInteger || count < 0) {\n"
"class MapEntry {\n" " Fiber.abort(\"Count must be a non-negative integer.\")\n"
" construct new(key, value) {\n" " }\n"
" _key = key\n" "\n"
" _value = value\n" " var result = []\n"
" }\n" " for (i in 0...count) {\n"
"\n" " result.addAll(this)\n"
" key { _key }\n" " }\n"
" value { _value }\n" " return result\n"
"\n" " }\n"
" toString { \"%(_key):%(_value)\" }\n" "}\n"
"}\n" "\n"
"\n" "class Map is Sequence {\n"
"class MapKeySequence is Sequence {\n" " keys { MapKeySequence.new(this) }\n"
" construct new(map) {\n" " values { MapValueSequence.new(this) }\n"
" _map = map\n" "\n"
" }\n" " toString {\n"
"\n" " var first = true\n"
" iterate(n) { _map.iterate(n) }\n" " var result = \"{\"\n"
" iteratorValue(iterator) { _map.keyIteratorValue_(iterator) }\n" "\n"
"}\n" " for (key in keys) {\n"
"\n" " if (!first) result = result + \", \"\n"
"class MapValueSequence is Sequence {\n" " first = false\n"
" construct new(map) {\n" " result = result + \"%(key): %(this[key])\"\n"
" _map = map\n" " }\n"
" }\n" "\n"
"\n" " return result + \"}\"\n"
" iterate(n) { _map.iterate(n) }\n" " }\n"
" iteratorValue(iterator) { _map.valueIteratorValue_(iterator) }\n" "\n"
"}\n" " iteratorValue(iterator) {\n"
"\n" " return MapEntry.new(\n"
"class Range is Sequence {}\n" " keyIteratorValue_(iterator),\n"
"\n" " valueIteratorValue_(iterator))\n"
"class System {\n" " }\n"
" static print() {\n" "}\n"
" writeString_(\"\n\")\n" "\n"
" }\n" "class MapEntry {\n"
"\n" " construct new(key, value) {\n"
" static print(obj) {\n" " _key = key\n"
" writeObject_(obj)\n" " _value = value\n"
" writeString_(\"\n\")\n" " }\n"
" return obj\n" "\n"
" }\n" " key { _key }\n"
"\n" " value { _value }\n"
" static printAll(sequence) {\n" "\n"
" for (object in sequence) writeObject_(object)\n" " toString { \"%(_key):%(_value)\" }\n"
" writeString_(\"\n\")\n" "}\n"
" }\n" "\n"
"\n" "class MapKeySequence is Sequence {\n"
" static write(obj) {\n" " construct new(map) {\n"
" writeObject_(obj)\n" " _map = map\n"
" return obj\n" " }\n"
" }\n" "\n"
"\n" " iterate(n) { _map.iterate(n) }\n"
" static writeAll(sequence) {\n" " iteratorValue(iterator) { _map.keyIteratorValue_(iterator) }\n"
" for (object in sequence) writeObject_(object)\n" "}\n"
" }\n" "\n"
"\n" "class MapValueSequence is Sequence {\n"
" static writeObject_(obj) {\n" " construct new(map) {\n"
" var string = obj.toString\n" " _map = map\n"
" if (string is String) {\n" " }\n"
" writeString_(string)\n" "\n"
" } else {\n" " iterate(n) { _map.iterate(n) }\n"
" writeString_(\"[invalid toString]\")\n" " iteratorValue(iterator) { _map.valueIteratorValue_(iterator) }\n"
" }\n" "}\n"
" }\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";
+388 -387
View File
@@ -1,387 +1,388 @@
#include <stdio.h> #include <stdio.h>
#include "wren_debug.h" #include "wren_debug.h"
void wrenDebugPrintStackTrace(WrenVM* vm) void wrenDebugPrintStackTrace(WrenVM* vm)
{ {
// Bail if the host doesn't enable printing errors. // Bail if the host doesn't enable printing errors.
if (vm->config.errorFn == NULL) return; if (vm->config.errorFn == NULL) return;
ObjFiber* fiber = vm->fiber; ObjFiber* fiber = vm->fiber;
if (IS_STRING(fiber->error)) if (IS_STRING(fiber->error))
{ {
vm->config.errorFn(vm, WREN_ERROR_RUNTIME, vm->config.errorFn(vm, WREN_ERROR_RUNTIME,
NULL, -1, AS_CSTRING(fiber->error)); NULL, -1, AS_CSTRING(fiber->error));
} }
else else
{ {
// TODO: Print something a little useful here. Maybe the name of the error's // TODO: Print something a little useful here. Maybe the name of the error's
// class? // class?
vm->config.errorFn(vm, WREN_ERROR_RUNTIME, vm->config.errorFn(vm, WREN_ERROR_RUNTIME,
NULL, -1, "[error object]"); NULL, -1, "[error object]");
} }
for (int i = fiber->numFrames - 1; i >= 0; i--) for (int i = fiber->numFrames - 1; i >= 0; i--)
{ {
CallFrame* frame = &fiber->frames[i]; CallFrame* frame = &fiber->frames[i];
ObjFn* fn = frame->closure->fn; ObjFn* fn = frame->closure->fn;
// Skip over stub functions for calling methods from the C API. // Skip over stub functions for calling methods from the C API.
if (fn->module == NULL) continue; if (fn->module == NULL) continue;
// The built-in core module has no name. We explicitly omit it from stack // 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 // 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. // detail of what part of the core module is written in C and what is Wren.
if (fn->module->name == NULL) continue; if (fn->module->name == NULL) continue;
// -1 because IP has advanced past the instruction that it just executed. // -1 because IP has advanced past the instruction that it just executed.
int line = fn->debug->sourceLines.data[frame->ip - fn->code.data - 1]; int line = fn->debug->sourceLines.data[frame->ip - fn->code.data - 1];
vm->config.errorFn(vm, WREN_ERROR_STACK_TRACE, vm->config.errorFn(vm, WREN_ERROR_STACK_TRACE,
fn->module->name->value, line, fn->module->name->value, line,
fn->debug->name); fn->debug->name);
} }
} }
static void dumpObject(Obj* obj) static void dumpObject(Obj* obj)
{ {
switch (obj->type) switch (obj->type)
{ {
case OBJ_CLASS: case OBJ_CLASS:
printf("[class %s %p]", ((ObjClass*)obj)->name->value, obj); printf("[class %s %p]", ((ObjClass*)obj)->name->value, obj);
break; break;
case OBJ_CLOSURE: printf("[closure %p]", obj); break; case OBJ_CLOSURE: printf("[closure %p]", obj); break;
case OBJ_FIBER: printf("[fiber %p]", obj); break; case OBJ_FIBER: printf("[fiber %p]", obj); break;
case OBJ_FN: printf("[fn %p]", obj); break; case OBJ_FN: printf("[fn %p]", obj); break;
case OBJ_FOREIGN: printf("[foreign %p]", obj); break; case OBJ_FOREIGN: printf("[foreign %p]", obj); break;
case OBJ_INSTANCE: printf("[instance %p]", obj); break; case OBJ_INSTANCE: printf("[instance %p]", obj); break;
case OBJ_LIST: printf("[list %p]", obj); break; case OBJ_LIST: printf("[list %p]", obj); break;
case OBJ_MAP: printf("[map %p]", obj); break; case OBJ_MAP: printf("[map %p]", obj); break;
case OBJ_MODULE: printf("[module %p]", obj); break; case OBJ_MODULE: printf("[module %p]", obj); break;
case OBJ_RANGE: printf("[range %p]", obj); break; case OBJ_RANGE: printf("[range %p]", obj); break;
case OBJ_STRING: printf("%s", ((ObjString*)obj)->value); break; case OBJ_STRING: printf("%s", ((ObjString*)obj)->value); break;
case OBJ_UPVALUE: printf("[upvalue %p]", obj); break; case OBJ_UPVALUE: printf("[upvalue %p]", obj); break;
default: printf("[unknown object %d]", obj->type); break; default: printf("[unknown object %d]", obj->type); break;
} }
} }
void wrenDumpValue(Value value) void wrenDumpValue(Value value)
{ {
#if WREN_NAN_TAGGING #if WREN_NAN_TAGGING
if (IS_NUM(value)) if (IS_NUM(value))
{ {
printf("%.14g", AS_NUM(value)); printf("%.14g", AS_NUM(value));
} }
else if (IS_OBJ(value)) else if (IS_OBJ(value))
{ {
dumpObject(AS_OBJ(value)); dumpObject(AS_OBJ(value));
} }
else else
{ {
switch (GET_TAG(value)) switch (GET_TAG(value))
{ {
case TAG_FALSE: printf("false"); break; case TAG_FALSE: printf("false"); break;
case TAG_NAN: printf("NaN"); break; case TAG_NAN: printf("NaN"); break;
case TAG_NULL: printf("null"); break; case TAG_NULL: printf("null"); break;
case TAG_TRUE: printf("true"); break; case TAG_TRUE: printf("true"); break;
case TAG_UNDEFINED: UNREACHABLE(); case TAG_UNDEFINED: UNREACHABLE();
} }
} }
#else #else
switch (value.type) switch (value.type)
{ {
case VAL_FALSE: printf("false"); break; case VAL_FALSE: printf("false"); break;
case VAL_NULL: printf("null"); break; case VAL_NULL: printf("null"); break;
case VAL_NUM: printf("%.14g", AS_NUM(value)); break; case VAL_NUM: printf("%.14g", AS_NUM(value)); break;
case VAL_TRUE: printf("true"); break; case VAL_TRUE: printf("true"); break;
case VAL_OBJ: dumpObject(AS_OBJ(value)); break; case VAL_OBJ: dumpObject(AS_OBJ(value)); break;
case VAL_UNDEFINED: UNREACHABLE(); case VAL_UNDEFINED: UNREACHABLE();
} }
#endif #endif
} }
static int dumpInstruction(WrenVM* vm, ObjFn* fn, int i, int* lastLine) static int dumpInstruction(WrenVM* vm, ObjFn* fn, int i, int* lastLine)
{ {
int start = i; int start = i;
uint8_t* bytecode = fn->code.data; uint8_t* bytecode = fn->code.data;
Code code = (Code)bytecode[i]; Code code = (Code)bytecode[i];
int line = fn->debug->sourceLines.data[i]; int line = fn->debug->sourceLines.data[i];
if (lastLine == NULL || *lastLine != line) if (lastLine == NULL || *lastLine != line)
{ {
printf("%4d:", line); printf("%4d:", line);
if (lastLine != NULL) *lastLine = line; if (lastLine != NULL) *lastLine = line;
} }
else else
{ {
printf(" "); printf(" ");
} }
printf(" %04d ", i++); printf(" %04d ", i++);
#define READ_BYTE() (bytecode[i++]) #define READ_BYTE() (bytecode[i++])
#define READ_SHORT() (i += 2, (bytecode[i - 2] << 8) | bytecode[i - 1]) #define READ_SHORT() (i += 2, (bytecode[i - 2] << 8) | bytecode[i - 1])
#define BYTE_INSTRUCTION(name) \ #define BYTE_INSTRUCTION(name) \
printf("%-16s %5d\n", name, READ_BYTE()); \ printf("%-16s %5d\n", name, READ_BYTE()); \
break; \ break
switch (code) switch (code)
{ {
case CODE_CONSTANT: case CODE_CONSTANT:
{ {
int constant = READ_SHORT(); int constant = READ_SHORT();
printf("%-16s %5d '", "CONSTANT", constant); printf("%-16s %5d '", "CONSTANT", constant);
wrenDumpValue(fn->constants.data[constant]); wrenDumpValue(fn->constants.data[constant]);
printf("'\n"); printf("'\n");
break; break;
} }
case CODE_NULL: printf("NULL\n"); break; case CODE_NULL: printf("NULL\n"); break;
case CODE_FALSE: printf("FALSE\n"); break; case CODE_FALSE: printf("FALSE\n"); break;
case CODE_TRUE: printf("TRUE\n"); break; case CODE_TRUE: printf("TRUE\n"); break;
case CODE_LOAD_LOCAL_0: printf("LOAD_LOCAL_0\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_1: printf("LOAD_LOCAL_1\n"); break;
case CODE_LOAD_LOCAL_2: printf("LOAD_LOCAL_2\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_3: printf("LOAD_LOCAL_3\n"); break;
case CODE_LOAD_LOCAL_4: printf("LOAD_LOCAL_4\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_5: printf("LOAD_LOCAL_5\n"); break;
case CODE_LOAD_LOCAL_6: printf("LOAD_LOCAL_6\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_7: printf("LOAD_LOCAL_7\n"); break;
case CODE_LOAD_LOCAL_8: printf("LOAD_LOCAL_8\n"); break; case CODE_LOAD_LOCAL_8: printf("LOAD_LOCAL_8\n"); break;
case CODE_LOAD_LOCAL: BYTE_INSTRUCTION("LOAD_LOCAL"); case CODE_LOAD_LOCAL: BYTE_INSTRUCTION("LOAD_LOCAL");
case CODE_STORE_LOCAL: BYTE_INSTRUCTION("STORE_LOCAL"); case CODE_STORE_LOCAL: BYTE_INSTRUCTION("STORE_LOCAL");
case CODE_LOAD_UPVALUE: BYTE_INSTRUCTION("LOAD_UPVALUE"); case CODE_LOAD_UPVALUE: BYTE_INSTRUCTION("LOAD_UPVALUE");
case CODE_STORE_UPVALUE: BYTE_INSTRUCTION("STORE_UPVALUE"); case CODE_STORE_UPVALUE: BYTE_INSTRUCTION("STORE_UPVALUE");
case CODE_LOAD_MODULE_VAR: case CODE_LOAD_MODULE_VAR:
{ {
int slot = READ_SHORT(); int slot = READ_SHORT();
printf("%-16s %5d '%s'\n", "LOAD_MODULE_VAR", slot, printf("%-16s %5d '%s'\n", "LOAD_MODULE_VAR", slot,
fn->module->variableNames.data[slot]->value); fn->module->variableNames.data[slot]->value);
break; break;
} }
case CODE_STORE_MODULE_VAR: case CODE_STORE_MODULE_VAR:
{ {
int slot = READ_SHORT(); int slot = READ_SHORT();
printf("%-16s %5d '%s'\n", "STORE_MODULE_VAR", slot, printf("%-16s %5d '%s'\n", "STORE_MODULE_VAR", slot,
fn->module->variableNames.data[slot]->value); fn->module->variableNames.data[slot]->value);
break; break;
} }
case CODE_LOAD_FIELD_THIS: BYTE_INSTRUCTION("LOAD_FIELD_THIS"); case CODE_LOAD_FIELD_THIS: BYTE_INSTRUCTION("LOAD_FIELD_THIS");
case CODE_STORE_FIELD_THIS: BYTE_INSTRUCTION("STORE_FIELD_THIS"); case CODE_STORE_FIELD_THIS: BYTE_INSTRUCTION("STORE_FIELD_THIS");
case CODE_LOAD_FIELD: BYTE_INSTRUCTION("LOAD_FIELD"); case CODE_LOAD_FIELD: BYTE_INSTRUCTION("LOAD_FIELD");
case CODE_STORE_FIELD: BYTE_INSTRUCTION("STORE_FIELD"); case CODE_STORE_FIELD: BYTE_INSTRUCTION("STORE_FIELD");
case CODE_POP: printf("POP\n"); break; case CODE_POP: printf("POP\n"); break;
case CODE_CALL_0: case CODE_CALL_0:
case CODE_CALL_1: case CODE_CALL_1:
case CODE_CALL_2: case CODE_CALL_2:
case CODE_CALL_3: case CODE_CALL_3:
case CODE_CALL_4: case CODE_CALL_4:
case CODE_CALL_5: case CODE_CALL_5:
case CODE_CALL_6: case CODE_CALL_6:
case CODE_CALL_7: case CODE_CALL_7:
case CODE_CALL_8: case CODE_CALL_8:
case CODE_CALL_9: case CODE_CALL_9:
case CODE_CALL_10: case CODE_CALL_10:
case CODE_CALL_11: case CODE_CALL_11:
case CODE_CALL_12: case CODE_CALL_12:
case CODE_CALL_13: case CODE_CALL_13:
case CODE_CALL_14: case CODE_CALL_14:
case CODE_CALL_15: case CODE_CALL_15:
case CODE_CALL_16: case CODE_CALL_16:
{ {
int numArgs = bytecode[i - 1] - CODE_CALL_0; int numArgs = bytecode[i - 1] - CODE_CALL_0;
int symbol = READ_SHORT(); int symbol = READ_SHORT();
printf("CALL_%-11d %5d '%s'\n", numArgs, symbol, printf("CALL_%-11d %5d '%s'\n", numArgs, symbol,
vm->methodNames.data[symbol]->value); vm->methodNames.data[symbol]->value);
break; break;
} }
case CODE_SUPER_0: case CODE_SUPER_0:
case CODE_SUPER_1: case CODE_SUPER_1:
case CODE_SUPER_2: case CODE_SUPER_2:
case CODE_SUPER_3: case CODE_SUPER_3:
case CODE_SUPER_4: case CODE_SUPER_4:
case CODE_SUPER_5: case CODE_SUPER_5:
case CODE_SUPER_6: case CODE_SUPER_6:
case CODE_SUPER_7: case CODE_SUPER_7:
case CODE_SUPER_8: case CODE_SUPER_8:
case CODE_SUPER_9: case CODE_SUPER_9:
case CODE_SUPER_10: case CODE_SUPER_10:
case CODE_SUPER_11: case CODE_SUPER_11:
case CODE_SUPER_12: case CODE_SUPER_12:
case CODE_SUPER_13: case CODE_SUPER_13:
case CODE_SUPER_14: case CODE_SUPER_14:
case CODE_SUPER_15: case CODE_SUPER_15:
case CODE_SUPER_16: case CODE_SUPER_16:
{ {
int numArgs = bytecode[i - 1] - CODE_SUPER_0; int numArgs = bytecode[i - 1] - CODE_SUPER_0;
int symbol = READ_SHORT(); int symbol = READ_SHORT();
int superclass = READ_SHORT(); int superclass = READ_SHORT();
printf("SUPER_%-10d %5d '%s' %5d\n", numArgs, symbol, printf("SUPER_%-10d %5d '%s' %5d\n", numArgs, symbol,
vm->methodNames.data[symbol]->value, superclass); vm->methodNames.data[symbol]->value, superclass);
break; break;
} }
case CODE_JUMP: case CODE_JUMP:
{ {
int offset = READ_SHORT(); int offset = READ_SHORT();
printf("%-16s %5d to %d\n", "JUMP", offset, i + offset); printf("%-16s %5d to %d\n", "JUMP", offset, i + offset);
break; break;
} }
case CODE_LOOP: case CODE_LOOP:
{ {
int offset = READ_SHORT(); int offset = READ_SHORT();
printf("%-16s %5d to %d\n", "LOOP", offset, i - offset); printf("%-16s %5d to %d\n", "LOOP", offset, i - offset);
break; break;
} }
case CODE_JUMP_IF: case CODE_JUMP_IF:
{ {
int offset = READ_SHORT(); int offset = READ_SHORT();
printf("%-16s %5d to %d\n", "JUMP_IF", offset, i + offset); printf("%-16s %5d to %d\n", "JUMP_IF", offset, i + offset);
break; break;
} }
case CODE_AND: case CODE_AND:
{ {
int offset = READ_SHORT(); int offset = READ_SHORT();
printf("%-16s %5d to %d\n", "AND", offset, i + offset); printf("%-16s %5d to %d\n", "AND", offset, i + offset);
break; break;
} }
case CODE_OR: case CODE_OR:
{ {
int offset = READ_SHORT(); int offset = READ_SHORT();
printf("%-16s %5d to %d\n", "OR", offset, i + offset); printf("%-16s %5d to %d\n", "OR", offset, i + offset);
break; break;
} }
case CODE_CLOSE_UPVALUE: printf("CLOSE_UPVALUE\n"); break; case CODE_CLOSE_UPVALUE: printf("CLOSE_UPVALUE\n"); break;
case CODE_RETURN: printf("RETURN\n"); break; case CODE_RETURN: printf("RETURN\n"); break;
case CODE_CLOSURE: case CODE_CLOSURE:
{ {
int constant = READ_SHORT(); int constant = READ_SHORT();
printf("%-16s %5d ", "CLOSURE", constant); printf("%-16s %5d ", "CLOSURE", constant);
wrenDumpValue(fn->constants.data[constant]); wrenDumpValue(fn->constants.data[constant]);
printf(" "); printf(" ");
ObjFn* loadedFn = AS_FN(fn->constants.data[constant]); ObjFn* loadedFn = AS_FN(fn->constants.data[constant]);
for (int j = 0; j < loadedFn->numUpvalues; j++) for (int j = 0; j < loadedFn->numUpvalues; j++)
{ {
int isLocal = READ_BYTE(); int isLocal = READ_BYTE();
int index = READ_BYTE(); int index = READ_BYTE();
if (j > 0) printf(", "); if (j > 0) printf(", ");
printf("%s %d", isLocal ? "local" : "upvalue", index); printf("%s %d", isLocal ? "local" : "upvalue", index);
} }
printf("\n"); printf("\n");
break; break;
} }
case CODE_CONSTRUCT: printf("CONSTRUCT\n"); break; case CODE_CONSTRUCT: printf("CONSTRUCT\n"); break;
case CODE_FOREIGN_CONSTRUCT: printf("FOREIGN_CONSTRUCT\n"); break; case CODE_FOREIGN_CONSTRUCT: printf("FOREIGN_CONSTRUCT\n"); break;
case CODE_CLASS: case CODE_CLASS:
{ {
int numFields = READ_BYTE(); int numFields = READ_BYTE();
printf("%-16s %5d fields\n", "CLASS", numFields); printf("%-16s %5d fields\n", "CLASS", numFields);
break; break;
} }
case CODE_FOREIGN_CLASS: printf("FOREIGN_CLASS\n"); break; case CODE_FOREIGN_CLASS: printf("FOREIGN_CLASS\n"); break;
case CODE_END_CLASS: printf("END_CLASS\n"); break;
case CODE_METHOD_INSTANCE:
{ case CODE_METHOD_INSTANCE:
int symbol = READ_SHORT(); {
printf("%-16s %5d '%s'\n", "METHOD_INSTANCE", symbol, int symbol = READ_SHORT();
vm->methodNames.data[symbol]->value); printf("%-16s %5d '%s'\n", "METHOD_INSTANCE", symbol,
break; vm->methodNames.data[symbol]->value);
} break;
}
case CODE_METHOD_STATIC:
{ case CODE_METHOD_STATIC:
int symbol = READ_SHORT(); {
printf("%-16s %5d '%s'\n", "METHOD_STATIC", symbol, int symbol = READ_SHORT();
vm->methodNames.data[symbol]->value); printf("%-16s %5d '%s'\n", "METHOD_STATIC", symbol,
break; vm->methodNames.data[symbol]->value);
} break;
}
case CODE_END_MODULE:
printf("END_MODULE\n"); case CODE_END_MODULE:
break; printf("END_MODULE\n");
break;
case CODE_IMPORT_MODULE:
{ case CODE_IMPORT_MODULE:
int name = READ_SHORT(); {
printf("%-16s %5d '", "IMPORT_MODULE", name); int name = READ_SHORT();
wrenDumpValue(fn->constants.data[name]); printf("%-16s %5d '", "IMPORT_MODULE", name);
printf("'\n"); wrenDumpValue(fn->constants.data[name]);
break; printf("'\n");
} break;
}
case CODE_IMPORT_VARIABLE:
{ case CODE_IMPORT_VARIABLE:
int variable = READ_SHORT(); {
printf("%-16s %5d '", "IMPORT_VARIABLE", variable); int variable = READ_SHORT();
wrenDumpValue(fn->constants.data[variable]); printf("%-16s %5d '", "IMPORT_VARIABLE", variable);
printf("'\n"); wrenDumpValue(fn->constants.data[variable]);
break; printf("'\n");
} break;
}
case CODE_END:
printf("END\n"); case CODE_END:
break; printf("END\n");
break;
default:
printf("UKNOWN! [%d]\n", bytecode[i - 1]); default:
break; 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 how many bytes this instruction takes, or -1 if it's an END.
return i - start; if (code == CODE_END) return -1;
return i - start;
#undef READ_BYTE
#undef READ_SHORT #undef READ_BYTE
} #undef READ_SHORT
}
int wrenDumpInstruction(WrenVM* vm, ObjFn* fn, int i)
{ int wrenDumpInstruction(WrenVM* vm, ObjFn* fn, int i)
return dumpInstruction(vm, fn, i, NULL); {
} return dumpInstruction(vm, fn, i, NULL);
}
void wrenDumpCode(WrenVM* vm, ObjFn* fn)
{ void wrenDumpCode(WrenVM* vm, ObjFn* fn)
printf("%s: %s\n", {
fn->module->name == NULL ? "<core>" : fn->module->name->value, printf("%s: %s\n",
fn->debug->name); fn->module->name == NULL ? "<core>" : fn->module->name->value,
fn->debug->name);
int i = 0;
int lastLine = -1; int i = 0;
for (;;) int lastLine = -1;
{ for (;;)
int offset = dumpInstruction(vm, fn, i, &lastLine); {
if (offset == -1) break; int offset = dumpInstruction(vm, fn, i, &lastLine);
i += offset; if (offset == -1) break;
} i += offset;
}
printf("\n");
} printf("\n");
}
void wrenDumpStack(ObjFiber* fiber)
{ void wrenDumpStack(ObjFiber* fiber)
printf("(fiber %p) ", fiber); {
for (Value* slot = fiber->stack; slot < fiber->stackTop; slot++) printf("(fiber %p) ", fiber);
{ for (Value* slot = fiber->stack; slot < fiber->stackTop; slot++)
wrenDumpValue(*slot); {
printf(" | "); wrenDumpValue(*slot);
} printf(" | ");
printf("\n"); }
} printf("\n");
}
+27 -27
View File
@@ -1,27 +1,27 @@
#ifndef wren_debug_h #ifndef wren_debug_h
#define wren_debug_h #define wren_debug_h
#include "wren_value.h" #include "wren_value.h"
#include "wren_vm.h" #include "wren_vm.h"
// Prints the stack trace for the current fiber. // Prints the stack trace for the current fiber.
// //
// Used when a fiber throws a runtime error which is not caught. // Used when a fiber throws a runtime error which is not caught.
void wrenDebugPrintStackTrace(WrenVM* vm); void wrenDebugPrintStackTrace(WrenVM* vm);
// The "dump" functions are used for debugging Wren itself. Normal code paths // 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. // will not call them unless one of the various DEBUG_ flags is enabled.
// Prints a representation of [value] to stdout. // Prints a representation of [value] to stdout.
void wrenDumpValue(Value value); void wrenDumpValue(Value value);
// Prints a representation of the bytecode for [fn] at instruction [i]. // Prints a representation of the bytecode for [fn] at instruction [i].
int wrenDumpInstruction(WrenVM* vm, ObjFn* fn, int i); int wrenDumpInstruction(WrenVM* vm, ObjFn* fn, int i);
// Prints the disassembled code for [fn] to stdout. // Prints the disassembled code for [fn] to stdout.
void wrenDumpCode(WrenVM* vm, ObjFn* fn); void wrenDumpCode(WrenVM* vm, ObjFn* fn);
// Prints the contents of the current stack for [fiber] to stdout. // Prints the contents of the current stack for [fiber] to stdout.
void wrenDumpStack(ObjFiber* fiber); void wrenDumpStack(ObjFiber* fiber);
#endif #endif
+34
View File
@@ -0,0 +1,34 @@
#ifndef wren_math_h
#define wren_math_h
#include <math.h>
#include <stdint.h>
// A union to let us reinterpret a double as raw bits and back.
typedef union
{
uint64_t bits64;
uint32_t bits32[2];
double num;
} WrenDoubleBits;
#define WREN_DOUBLE_QNAN_POS_MIN_BITS (UINT64_C(0x7FF8000000000000))
#define WREN_DOUBLE_QNAN_POS_MAX_BITS (UINT64_C(0x7FFFFFFFFFFFFFFF))
#define WREN_DOUBLE_NAN (wrenDoubleFromBits(WREN_DOUBLE_QNAN_POS_MIN_BITS))
static inline double wrenDoubleFromBits(uint64_t bits)
{
WrenDoubleBits data;
data.bits64 = bits;
return data.num;
}
static inline uint64_t wrenDoubleToBits(double num)
{
WrenDoubleBits data;
data.num = num;
return data.bits64;
}
#endif
+217 -213
View File
@@ -1,213 +1,217 @@
// This defines the bytecode instructions used by the VM. It does so by invoking // 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 // 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.) // 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 // 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 // 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. // stack effect of 1 means it pushes a value and the stack grows one larger.
// -2 means it pops two values, etc. // -2 means it pops two values, etc.
// //
// Note that the order of instructions here affects the order of the dispatch // 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 // 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 // affects overall performance. Take care to run benchmarks if you change the
// order here. // order here.
// Load the constant at index [arg]. // Load the constant at index [arg].
OPCODE(CONSTANT, 1) OPCODE(CONSTANT, 1)
// Push null onto the stack. // Push null onto the stack.
OPCODE(NULL, 1) OPCODE(NULL, 1)
// Push false onto the stack. // Push false onto the stack.
OPCODE(FALSE, 1) OPCODE(FALSE, 1)
// Push true onto the stack. // Push true onto the stack.
OPCODE(TRUE, 1) OPCODE(TRUE, 1)
// Pushes the value in the given local slot. // Pushes the value in the given local slot.
OPCODE(LOAD_LOCAL_0, 1) OPCODE(LOAD_LOCAL_0, 1)
OPCODE(LOAD_LOCAL_1, 1) OPCODE(LOAD_LOCAL_1, 1)
OPCODE(LOAD_LOCAL_2, 1) OPCODE(LOAD_LOCAL_2, 1)
OPCODE(LOAD_LOCAL_3, 1) OPCODE(LOAD_LOCAL_3, 1)
OPCODE(LOAD_LOCAL_4, 1) OPCODE(LOAD_LOCAL_4, 1)
OPCODE(LOAD_LOCAL_5, 1) OPCODE(LOAD_LOCAL_5, 1)
OPCODE(LOAD_LOCAL_6, 1) OPCODE(LOAD_LOCAL_6, 1)
OPCODE(LOAD_LOCAL_7, 1) OPCODE(LOAD_LOCAL_7, 1)
OPCODE(LOAD_LOCAL_8, 1) OPCODE(LOAD_LOCAL_8, 1)
// Note: The compiler assumes the following _STORE instructions always // Note: The compiler assumes the following _STORE instructions always
// immediately follow their corresponding _LOAD ones. // immediately follow their corresponding _LOAD ones.
// Pushes the value in local slot [arg]. // Pushes the value in local slot [arg].
OPCODE(LOAD_LOCAL, 1) OPCODE(LOAD_LOCAL, 1)
// Stores the top of stack in local slot [arg]. Does not pop it. // Stores the top of stack in local slot [arg]. Does not pop it.
OPCODE(STORE_LOCAL, 0) OPCODE(STORE_LOCAL, 0)
// Pushes the value in upvalue [arg]. // Pushes the value in upvalue [arg].
OPCODE(LOAD_UPVALUE, 1) OPCODE(LOAD_UPVALUE, 1)
// Stores the top of stack in upvalue [arg]. Does not pop it. // Stores the top of stack in upvalue [arg]. Does not pop it.
OPCODE(STORE_UPVALUE, 0) OPCODE(STORE_UPVALUE, 0)
// Pushes the value of the top-level variable in slot [arg]. // Pushes the value of the top-level variable in slot [arg].
OPCODE(LOAD_MODULE_VAR, 1) OPCODE(LOAD_MODULE_VAR, 1)
// Stores the top of stack in top-level variable slot [arg]. Does not pop it. // Stores the top of stack in top-level variable slot [arg]. Does not pop it.
OPCODE(STORE_MODULE_VAR, 0) OPCODE(STORE_MODULE_VAR, 0)
// Pushes the value of the field in slot [arg] of the receiver of the current // 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 // function. This is used for regular field accesses on "this" directly in
// methods. This instruction is faster than the more general CODE_LOAD_FIELD // methods. This instruction is faster than the more general CODE_LOAD_FIELD
// instruction. // instruction.
OPCODE(LOAD_FIELD_THIS, 1) OPCODE(LOAD_FIELD_THIS, 1)
// Stores the top of the stack in field slot [arg] in the receiver of the // 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 // current value. Does not pop the value. This instruction is faster than the
// more general CODE_LOAD_FIELD instruction. // more general CODE_LOAD_FIELD instruction.
OPCODE(STORE_FIELD_THIS, 0) OPCODE(STORE_FIELD_THIS, 0)
// Pops an instance and pushes the value of the field in slot [arg] of it. // Pops an instance and pushes the value of the field in slot [arg] of it.
OPCODE(LOAD_FIELD, 0) OPCODE(LOAD_FIELD, 0)
// Pops an instance and stores the subsequent top of stack in field slot // Pops an instance and stores the subsequent top of stack in field slot
// [arg] in it. Does not pop the value. // [arg] in it. Does not pop the value.
OPCODE(STORE_FIELD, -1) OPCODE(STORE_FIELD, -1)
// Pop and discard the top of stack. // Pop and discard the top of stack.
OPCODE(POP, -1) OPCODE(POP, -1)
// Invoke the method with symbol [arg]. The number indicates the number of // Invoke the method with symbol [arg]. The number indicates the number of
// arguments (not including the receiver). // arguments (not including the receiver).
OPCODE(CALL_0, 0) OPCODE(CALL_0, 0)
OPCODE(CALL_1, -1) OPCODE(CALL_1, -1)
OPCODE(CALL_2, -2) OPCODE(CALL_2, -2)
OPCODE(CALL_3, -3) OPCODE(CALL_3, -3)
OPCODE(CALL_4, -4) OPCODE(CALL_4, -4)
OPCODE(CALL_5, -5) OPCODE(CALL_5, -5)
OPCODE(CALL_6, -6) OPCODE(CALL_6, -6)
OPCODE(CALL_7, -7) OPCODE(CALL_7, -7)
OPCODE(CALL_8, -8) OPCODE(CALL_8, -8)
OPCODE(CALL_9, -9) OPCODE(CALL_9, -9)
OPCODE(CALL_10, -10) OPCODE(CALL_10, -10)
OPCODE(CALL_11, -11) OPCODE(CALL_11, -11)
OPCODE(CALL_12, -12) OPCODE(CALL_12, -12)
OPCODE(CALL_13, -13) OPCODE(CALL_13, -13)
OPCODE(CALL_14, -14) OPCODE(CALL_14, -14)
OPCODE(CALL_15, -15) OPCODE(CALL_15, -15)
OPCODE(CALL_16, -16) OPCODE(CALL_16, -16)
// Invoke a superclass method with symbol [arg]. The number indicates the // Invoke a superclass method with symbol [arg]. The number indicates the
// number of arguments (not including the receiver). // number of arguments (not including the receiver).
OPCODE(SUPER_0, 0) OPCODE(SUPER_0, 0)
OPCODE(SUPER_1, -1) OPCODE(SUPER_1, -1)
OPCODE(SUPER_2, -2) OPCODE(SUPER_2, -2)
OPCODE(SUPER_3, -3) OPCODE(SUPER_3, -3)
OPCODE(SUPER_4, -4) OPCODE(SUPER_4, -4)
OPCODE(SUPER_5, -5) OPCODE(SUPER_5, -5)
OPCODE(SUPER_6, -6) OPCODE(SUPER_6, -6)
OPCODE(SUPER_7, -7) OPCODE(SUPER_7, -7)
OPCODE(SUPER_8, -8) OPCODE(SUPER_8, -8)
OPCODE(SUPER_9, -9) OPCODE(SUPER_9, -9)
OPCODE(SUPER_10, -10) OPCODE(SUPER_10, -10)
OPCODE(SUPER_11, -11) OPCODE(SUPER_11, -11)
OPCODE(SUPER_12, -12) OPCODE(SUPER_12, -12)
OPCODE(SUPER_13, -13) OPCODE(SUPER_13, -13)
OPCODE(SUPER_14, -14) OPCODE(SUPER_14, -14)
OPCODE(SUPER_15, -15) OPCODE(SUPER_15, -15)
OPCODE(SUPER_16, -16) OPCODE(SUPER_16, -16)
// Jump the instruction pointer [arg] forward. // Jump the instruction pointer [arg] forward.
OPCODE(JUMP, 0) OPCODE(JUMP, 0)
// Jump the instruction pointer [arg] backward. // Jump the instruction pointer [arg] backward.
OPCODE(LOOP, 0) OPCODE(LOOP, 0)
// Pop and if not truthy then jump the instruction pointer [arg] forward. // Pop and if not truthy then jump the instruction pointer [arg] forward.
OPCODE(JUMP_IF, -1) OPCODE(JUMP_IF, -1)
// If the top of the stack is false, jump [arg] forward. Otherwise, pop and // If the top of the stack is false, jump [arg] forward. Otherwise, pop and
// continue. // continue.
OPCODE(AND, -1) OPCODE(AND, -1)
// If the top of the stack is non-false, jump [arg] forward. Otherwise, pop // If the top of the stack is non-false, jump [arg] forward. Otherwise, pop
// and continue. // and continue.
OPCODE(OR, -1) OPCODE(OR, -1)
// Close the upvalue for the local on the top of the stack, then pop it. // Close the upvalue for the local on the top of the stack, then pop it.
OPCODE(CLOSE_UPVALUE, -1) OPCODE(CLOSE_UPVALUE, -1)
// Exit from the current function and return the value on the top of the // Exit from the current function and return the value on the top of the
// stack. // stack.
OPCODE(RETURN, 0) OPCODE(RETURN, 0)
// Creates a closure for the function stored at [arg] in the constant table. // 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 // 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 // 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 // opposed to an upvalue), and the second is the index of the local or
// upvalue being captured. // upvalue being captured.
// //
// Pushes the created closure. // Pushes the created closure.
OPCODE(CLOSURE, 1) OPCODE(CLOSURE, 1)
// Creates a new instance of a class. // Creates a new instance of a class.
// //
// Assumes the class object is in slot zero, and replaces it with the new // 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 // uninitialized instance of that class. This opcode is only emitted by the
// compiler-generated constructor metaclass methods. // compiler-generated constructor metaclass methods.
OPCODE(CONSTRUCT, 0) OPCODE(CONSTRUCT, 0)
// Creates a new instance of a foreign class. // Creates a new instance of a foreign class.
// //
// Assumes the class object is in slot zero, and replaces it with the new // 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 // uninitialized instance of that class. This opcode is only emitted by the
// compiler-generated constructor metaclass methods. // compiler-generated constructor metaclass methods.
OPCODE(FOREIGN_CONSTRUCT, 0) OPCODE(FOREIGN_CONSTRUCT, 0)
// Creates a class. Top of stack is the superclass. Below that is a string for // 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. // the name of the class. Byte [arg] is the number of fields in the class.
OPCODE(CLASS, -1) OPCODE(CLASS, -1)
// Creates a foreign class. Top of stack is the superclass. Below that is a // Ends a class.
// string for the name of the class. // Atm the stack contains the class and the ClassAttributes (or null).
OPCODE(FOREIGN_CLASS, -1) OPCODE(END_CLASS, -2)
// Define a method for symbol [arg]. The class receiving the method is popped // Creates a foreign class. Top of stack is the superclass. Below that is a
// off the stack, then the function defining the body is popped. // string for the name of the class.
// OPCODE(FOREIGN_CLASS, -1)
// If a foreign method is being defined, the "function" will be a string
// identifying the foreign method. Otherwise, it will be a function or // Define a method for symbol [arg]. The class receiving the method is popped
// closure. // off the stack, then the function defining the body is popped.
OPCODE(METHOD_INSTANCE, -2) //
// If a foreign method is being defined, the "function" will be a string
// Define a method for symbol [arg]. The class whose metaclass will receive // identifying the foreign method. Otherwise, it will be a function or
// the method is popped off the stack, then the function defining the body is // closure.
// popped. OPCODE(METHOD_INSTANCE, -2)
//
// If a foreign method is being defined, the "function" will be a string // Define a method for symbol [arg]. The class whose metaclass will receive
// identifying the foreign method. Otherwise, it will be a function or // the method is popped off the stack, then the function defining the body is
// closure. // popped.
OPCODE(METHOD_STATIC, -2) //
// If a foreign method is being defined, the "function" will be a string
// This is executed at the end of the module's body. Pushes NULL onto the stack // identifying the foreign method. Otherwise, it will be a function or
// as the "return value" of the import statement and stores the module as the // closure.
// most recently imported one. OPCODE(METHOD_STATIC, -2)
OPCODE(END_MODULE, 1)
// This is executed at the end of the module's body. Pushes NULL onto the stack
// Import a module whose name is the string stored at [arg] in the constant // as the "return value" of the import statement and stores the module as the
// table. // most recently imported one.
// OPCODE(END_MODULE, 1)
// 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 // Import a module whose name is the string stored at [arg] in the constant
// value when resuming a caller.) // table.
OPCODE(IMPORT_MODULE, 1) //
// Pushes null onto the stack so that the fiber for the imported module can
// Import a variable from the most recently imported module. The name of the // replace that with a dummy value when it returns. (Fibers always return a
// variable to import is at [arg] in the constant table. Pushes the loaded // value when resuming a caller.)
// variable's value. OPCODE(IMPORT_MODULE, 1)
OPCODE(IMPORT_VARIABLE, 1)
// Import a variable from the most recently imported module. The name of the
// This pseudo-instruction indicates the end of the bytecode. It should // variable to import is at [arg] in the constant table. Pushes the loaded
// always be preceded by a `CODE_RETURN`, so is never actually executed. // variable's value.
OPCODE(END, 0) 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)
+119 -125
View File
@@ -1,125 +1,119 @@
#include "wren_primitive.h" #include "wren_primitive.h"
#include <math.h> #include <math.h>
// Validates that [value] is an integer within `[0, count)`. Also allows // Validates that [value] is an integer within `[0, count)`. Also allows
// negative indices which map backwards from the end. Returns the valid positive // negative indices which map backwards from the end. Returns the valid positive
// index value. If invalid, reports an error and returns `UINT32_MAX`. // index value. If invalid, reports an error and returns `UINT32_MAX`.
static uint32_t validateIndexValue(WrenVM* vm, uint32_t count, double value, static uint32_t validateIndexValue(WrenVM* vm, uint32_t count, double value,
const char* argName) const char* argName)
{ {
if (!validateIntValue(vm, value, argName)) return UINT32_MAX; if (!validateIntValue(vm, value, argName)) return UINT32_MAX;
// Negative indices count from the end. // Negative indices count from the end.
if (value < 0) value = count + value; if (value < 0) value = count + value;
// Check bounds. // Check bounds.
if (value >= 0 && value < count) return (uint32_t)value; if (value >= 0 && value < count) return (uint32_t)value;
vm->fiber->error = wrenStringFormat(vm, "$ out of bounds.", argName); vm->fiber->error = wrenStringFormat(vm, "$ out of bounds.", argName);
return UINT32_MAX; return UINT32_MAX;
} }
bool validateFn(WrenVM* vm, Value arg, const char* argName) bool validateFn(WrenVM* vm, Value arg, const char* argName)
{ {
if (IS_CLOSURE(arg)) return true; if (IS_CLOSURE(arg)) return true;
RETURN_ERROR_FMT("$ must be a function.", argName);
vm->fiber->error = wrenStringFormat(vm, "$ must be a function.", argName); }
return false;
} bool validateNum(WrenVM* vm, Value arg, const char* argName)
{
bool validateNum(WrenVM* vm, Value arg, const char* argName) if (IS_NUM(arg)) return true;
{ RETURN_ERROR_FMT("$ must be a number.", argName);
if (IS_NUM(arg)) return true; }
RETURN_ERROR_FMT("$ must be a number.", argName);
} bool validateIntValue(WrenVM* vm, double value, const char* argName)
{
bool validateIntValue(WrenVM* vm, double value, const char* argName) if (trunc(value) == value) return true;
{ RETURN_ERROR_FMT("$ must be an integer.", argName);
if (trunc(value) == value) return true; }
RETURN_ERROR_FMT("$ must be an integer.", argName);
} bool validateInt(WrenVM* vm, Value arg, const char* argName)
{
bool validateInt(WrenVM* vm, Value arg, const char* argName) // Make sure it's a number first.
{ if (!validateNum(vm, arg, argName)) return false;
// Make sure it's a number first. return validateIntValue(vm, AS_NUM(arg), argName);
if (!validateNum(vm, arg, argName)) return false; }
return validateIntValue(vm, AS_NUM(arg), argName);
} bool validateKey(WrenVM* vm, Value arg)
{
bool validateKey(WrenVM* vm, Value arg) if (wrenMapIsValidKey(arg)) return true;
{
if (IS_BOOL(arg) || IS_CLASS(arg) || IS_NULL(arg) || RETURN_ERROR("Key must be a value type.");
IS_NUM(arg) || IS_RANGE(arg) || IS_STRING(arg)) }
{
return true; uint32_t validateIndex(WrenVM* vm, Value arg, uint32_t count,
} const char* argName)
{
RETURN_ERROR("Key must be a value type."); if (!validateNum(vm, arg, argName)) return UINT32_MAX;
} return validateIndexValue(vm, count, AS_NUM(arg), argName);
}
uint32_t validateIndex(WrenVM* vm, Value arg, uint32_t count,
const char* argName) bool validateString(WrenVM* vm, Value arg, const char* argName)
{ {
if (!validateNum(vm, arg, argName)) return UINT32_MAX; if (IS_STRING(arg)) return true;
return validateIndexValue(vm, count, AS_NUM(arg), argName); RETURN_ERROR_FMT("$ must be a string.", argName);
} }
bool validateString(WrenVM* vm, Value arg, const char* argName) uint32_t calculateRange(WrenVM* vm, ObjRange* range, uint32_t* length,
{ int* step)
if (IS_STRING(arg)) return true; {
RETURN_ERROR_FMT("$ must be a string.", argName); *step = 0;
}
// Edge case: an empty range is allowed at the end of a sequence. This way,
uint32_t calculateRange(WrenVM* vm, ObjRange* range, uint32_t* length, // list[0..-1] and list[0...list.count] can be used to copy a list even when
int* step) // empty.
{ if (range->from == *length &&
*step = 0; range->to == (range->isInclusive ? -1.0 : (double)*length))
{
// Edge case: an empty range is allowed at the end of a sequence. This way, *length = 0;
// list[0..-1] and list[0...list.count] can be used to copy a list even when return 0;
// empty. }
if (range->from == *length &&
range->to == (range->isInclusive ? -1.0 : (double)*length)) uint32_t from = validateIndexValue(vm, *length, range->from, "Range start");
{ if (from == UINT32_MAX) return UINT32_MAX;
*length = 0;
return 0; // Bounds check the end manually to handle exclusive ranges.
} double value = range->to;
if (!validateIntValue(vm, value, "Range end")) return UINT32_MAX;
uint32_t from = validateIndexValue(vm, *length, range->from, "Range start");
if (from == UINT32_MAX) return UINT32_MAX; // Negative indices count from the end.
if (value < 0) value = *length + value;
// Bounds check the end manually to handle exclusive ranges.
double value = range->to; // Convert the exclusive range to an inclusive one.
if (!validateIntValue(vm, value, "Range end")) return UINT32_MAX; if (!range->isInclusive)
{
// Negative indices count from the end. // An exclusive range with the same start and end points is empty.
if (value < 0) value = *length + value; if (value == from)
{
// Convert the exclusive range to an inclusive one. *length = 0;
if (!range->isInclusive) return from;
{ }
// An exclusive range with the same start and end points is empty.
if (value == from) // Shift the endpoint to make it inclusive, handling both increasing and
{ // decreasing ranges.
*length = 0; value += value >= from ? -1 : 1;
return from; }
}
// Check bounds.
// Shift the endpoint to make it inclusive, handling both increasing and if (value < 0 || value >= *length)
// decreasing ranges. {
value += value >= from ? -1 : 1; vm->fiber->error = CONST_STRING(vm, "Range end out of bounds.");
} return UINT32_MAX;
}
// Check bounds.
if (value < 0 || value >= *length) uint32_t to = (uint32_t)value;
{ *length = abs((int)(from - to)) + 1;
vm->fiber->error = CONST_STRING(vm, "Range end out of bounds."); *step = from < to ? 1 : -1;
return UINT32_MAX; return from;
} }
uint32_t to = (uint32_t)value;
*length = abs((int)(from - to)) + 1;
*step = from < to ? 1 : -1;
return from;
}
+109 -88
View File
@@ -1,88 +1,109 @@
#ifndef wren_primitive_h #ifndef wren_primitive_h
#define wren_primitive_h #define wren_primitive_h
#include "wren_vm.h" #include "wren_vm.h"
// Binds a primitive method named [name] (in Wren) implemented using C function // Binds a primitive method named [name] (in Wren) implemented using C function
// [fn] to `ObjClass` [cls]. // [fn] to `ObjClass` [cls].
#define PRIMITIVE(cls, name, function) \ #define PRIMITIVE(cls, name, function) \
{ \ do \
int symbol = wrenSymbolTableEnsure(vm, \ { \
&vm->methodNames, name, strlen(name)); \ int symbol = wrenSymbolTableEnsure(vm, \
Method method; \ &vm->methodNames, name, strlen(name)); \
method.type = METHOD_PRIMITIVE; \ Method method; \
method.as.primitive = prim_##function; \ method.type = METHOD_PRIMITIVE; \
wrenBindMethod(vm, cls, symbol, method); \ 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 // Binds a primitive method named [name] (in Wren) implemented using C function
// functions are invoked as primitives. // [fn] to `ObjClass` [cls], but as a FN call.
#define DEF_PRIMITIVE(name) \ #define FUNCTION_CALL(cls, name, function) \
static bool prim_##name(WrenVM* vm, Value* args) do \
{ \
#define RETURN_VAL(value) do { args[0] = value; return true; } while (0) int symbol = wrenSymbolTableEnsure(vm, \
&vm->methodNames, name, strlen(name)); \
#define RETURN_OBJ(obj) RETURN_VAL(OBJ_VAL(obj)) Method method; \
#define RETURN_BOOL(value) RETURN_VAL(BOOL_VAL(value)) method.type = METHOD_FUNCTION_CALL; \
#define RETURN_FALSE RETURN_VAL(FALSE_VAL) method.as.primitive = prim_##function; \
#define RETURN_NULL RETURN_VAL(NULL_VAL) wrenBindMethod(vm, cls, symbol, method); \
#define RETURN_NUM(value) RETURN_VAL(NUM_VAL(value)) } while (false)
#define RETURN_TRUE RETURN_VAL(TRUE_VAL)
// Defines a primitive method whose C function name is [name]. This abstracts
#define RETURN_ERROR(msg) \ // the actual type signature of a primitive function and makes it clear which C
do { \ // functions are invoked as primitives.
vm->fiber->error = wrenNewStringLength(vm, msg, sizeof(msg) - 1); \ #define DEF_PRIMITIVE(name) \
return false; \ static bool prim_##name(WrenVM* vm, Value* args)
} while (0);
#define RETURN_VAL(value) \
#define RETURN_ERROR_FMT(msg, arg) \ do \
do { \ { \
vm->fiber->error = wrenStringFormat(vm, msg, arg); \ args[0] = value; \
return false; \ return true; \
} while (0); } while (false)
// Validates that the given [arg] is a function. Returns true if it is. If not, #define RETURN_OBJ(obj) RETURN_VAL(OBJ_VAL(obj))
// reports an error and returns false. #define RETURN_BOOL(value) RETURN_VAL(BOOL_VAL(value))
bool validateFn(WrenVM* vm, Value arg, const char* argName); #define RETURN_FALSE RETURN_VAL(FALSE_VAL)
#define RETURN_NULL RETURN_VAL(NULL_VAL)
// Validates that the given [arg] is a Num. Returns true if it is. If not, #define RETURN_NUM(value) RETURN_VAL(NUM_VAL(value))
// reports an error and returns false. #define RETURN_TRUE RETURN_VAL(TRUE_VAL)
bool validateNum(WrenVM* vm, Value arg, const char* argName);
#define RETURN_ERROR(msg) \
// Validates that [value] is an integer. Returns true if it is. If not, reports do \
// an error and returns false. { \
bool validateIntValue(WrenVM* vm, double value, const char* argName); vm->fiber->error = wrenNewStringLength(vm, msg, sizeof(msg) - 1); \
return false; \
// Validates that the given [arg] is an integer. Returns true if it is. If not, } while (false)
// reports an error and returns false.
bool validateInt(WrenVM* vm, Value arg, const char* argName); #define RETURN_ERROR_FMT(...) \
do \
// 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. vm->fiber->error = wrenStringFormat(vm, __VA_ARGS__); \
bool validateKey(WrenVM* vm, Value arg); return false; \
} while (false)
// Validates that the argument at [argIndex] is an integer within `[0, count)`.
// Also allows negative indices which map backwards from the end. Returns the // Validates that the given [arg] is a function. Returns true if it is. If not,
// valid positive index value. If invalid, reports an error and returns // reports an error and returns false.
// `UINT32_MAX`. bool validateFn(WrenVM* vm, Value arg, const char* argName);
uint32_t validateIndex(WrenVM* vm, Value arg, uint32_t count,
const char* argName); // Validates that the given [arg] is a Num. Returns true if it is. If not,
// reports an error and returns false.
// Validates that the given [arg] is a String. Returns true if it is. If not, bool validateNum(WrenVM* vm, Value arg, const char* argName);
// reports an error and returns false.
bool validateString(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.
// Given a [range] and the [length] of the object being operated on, determines bool validateIntValue(WrenVM* vm, double value, const char* argName);
// 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. // Validates that the given [arg] is an integer. Returns true if it is. If not,
// // reports an error and returns false.
// Returns the index from which the range should start or `UINT32_MAX` if the bool validateInt(WrenVM* vm, Value arg, const char* argName);
// 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 // Validates that [arg] is a valid object for use as a map key. Returns true if
// is going: `1` if the range is increasing from the start index or `-1` if the // it is. If not, reports an error and returns false.
// range is decreasing. bool validateKey(WrenVM * vm, Value arg);
uint32_t calculateRange(WrenVM* vm, ObjRange* range, uint32_t* length,
int* step); // Validates that the argument at [argIndex] is an integer within `[0, count)`.
// Also allows negative indices which map backwards from the end. Returns the
#endif // 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
+207 -196
View File
@@ -1,196 +1,207 @@
#include <string.h> #include <string.h>
#include "wren_utils.h" #include "wren_utils.h"
#include "wren_vm.h" #include "wren_vm.h"
DEFINE_BUFFER(Byte, uint8_t); DEFINE_BUFFER(Byte, uint8_t);
DEFINE_BUFFER(Int, int); DEFINE_BUFFER(Int, int);
DEFINE_BUFFER(String, ObjString*); DEFINE_BUFFER(String, ObjString*);
void wrenSymbolTableInit(SymbolTable* symbols) void wrenSymbolTableInit(SymbolTable* symbols)
{ {
wrenStringBufferInit(symbols); wrenStringBufferInit(symbols);
} }
void wrenSymbolTableClear(WrenVM* vm, SymbolTable* symbols) void wrenSymbolTableClear(WrenVM* vm, SymbolTable* symbols)
{ {
wrenStringBufferClear(vm, symbols); wrenStringBufferClear(vm, symbols);
} }
int wrenSymbolTableAdd(WrenVM* vm, SymbolTable* symbols, int wrenSymbolTableAdd(WrenVM* vm, SymbolTable* symbols,
const char* name, size_t length) const char* name, size_t length)
{ {
ObjString* symbol = AS_STRING(wrenNewStringLength(vm, name, length)); ObjString* symbol = AS_STRING(wrenNewStringLength(vm, name, length));
wrenPushRoot(vm, &symbol->obj); wrenPushRoot(vm, &symbol->obj);
wrenStringBufferWrite(vm, symbols, symbol); wrenStringBufferWrite(vm, symbols, symbol);
wrenPopRoot(vm); wrenPopRoot(vm);
return symbols->count - 1; return symbols->count - 1;
} }
int wrenSymbolTableEnsure(WrenVM* vm, SymbolTable* symbols, int wrenSymbolTableEnsure(WrenVM* vm, SymbolTable* symbols,
const char* name, size_t length) const char* name, size_t length)
{ {
// See if the symbol is already defined. // See if the symbol is already defined.
int existing = wrenSymbolTableFind(symbols, name, length); int existing = wrenSymbolTableFind(symbols, name, length);
if (existing != -1) return existing; if (existing != -1) return existing;
// New symbol, so add it. // New symbol, so add it.
return wrenSymbolTableAdd(vm, symbols, name, length); return wrenSymbolTableAdd(vm, symbols, name, length);
} }
int wrenSymbolTableFind(const SymbolTable* symbols, int wrenSymbolTableFind(const SymbolTable* symbols,
const char* name, size_t length) const char* name, size_t length)
{ {
// See if the symbol is already defined. // See if the symbol is already defined.
// TODO: O(n). Do something better. // TODO: O(n). Do something better.
for (int i = 0; i < symbols->count; i++) for (int i = 0; i < symbols->count; i++)
{ {
if (wrenStringEqualsCString(symbols->data[i], name, length)) return i; if (wrenStringEqualsCString(symbols->data[i], name, length)) return i;
} }
return -1; return -1;
} }
void wrenBlackenSymbolTable(WrenVM* vm, SymbolTable* symbolTable) void wrenBlackenSymbolTable(WrenVM* vm, SymbolTable* symbolTable)
{ {
for (int i = 0; i < symbolTable->count; i++) for (int i = 0; i < symbolTable->count; i++)
{ {
wrenGrayObj(vm, &symbolTable->data[i]->obj); wrenGrayObj(vm, &symbolTable->data[i]->obj);
} }
// Keep track of how much memory is still in use. // Keep track of how much memory is still in use.
vm->bytesAllocated += symbolTable->capacity * sizeof(*symbolTable->data); vm->bytesAllocated += symbolTable->capacity * sizeof(*symbolTable->data);
} }
int wrenUtf8EncodeNumBytes(int value) int wrenUtf8EncodeNumBytes(int value)
{ {
ASSERT(value >= 0, "Cannot encode a negative value."); ASSERT(value >= 0, "Cannot encode a negative value.");
if (value <= 0x7f) return 1; if (value <= 0x7f) return 1;
if (value <= 0x7ff) return 2; if (value <= 0x7ff) return 2;
if (value <= 0xffff) return 3; if (value <= 0xffff) return 3;
if (value <= 0x10ffff) return 4; if (value <= 0x10ffff) return 4;
return 0; return 0;
} }
int wrenUtf8Encode(int value, uint8_t* bytes) int wrenUtf8Encode(int value, uint8_t* bytes)
{ {
if (value <= 0x7f) if (value <= 0x7f)
{ {
// Single byte (i.e. fits in ASCII). // Single byte (i.e. fits in ASCII).
*bytes = value & 0x7f; *bytes = value & 0x7f;
return 1; return 1;
} }
else if (value <= 0x7ff) else if (value <= 0x7ff)
{ {
// Two byte sequence: 110xxxxx 10xxxxxx. // Two byte sequence: 110xxxxx 10xxxxxx.
*bytes = 0xc0 | ((value & 0x7c0) >> 6); *bytes = 0xc0 | ((value & 0x7c0) >> 6);
bytes++; bytes++;
*bytes = 0x80 | (value & 0x3f); *bytes = 0x80 | (value & 0x3f);
return 2; return 2;
} }
else if (value <= 0xffff) else if (value <= 0xffff)
{ {
// Three byte sequence: 1110xxxx 10xxxxxx 10xxxxxx. // Three byte sequence: 1110xxxx 10xxxxxx 10xxxxxx.
*bytes = 0xe0 | ((value & 0xf000) >> 12); *bytes = 0xe0 | ((value & 0xf000) >> 12);
bytes++; bytes++;
*bytes = 0x80 | ((value & 0xfc0) >> 6); *bytes = 0x80 | ((value & 0xfc0) >> 6);
bytes++; bytes++;
*bytes = 0x80 | (value & 0x3f); *bytes = 0x80 | (value & 0x3f);
return 3; return 3;
} }
else if (value <= 0x10ffff) else if (value <= 0x10ffff)
{ {
// Four byte sequence: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx. // Four byte sequence: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx.
*bytes = 0xf0 | ((value & 0x1c0000) >> 18); *bytes = 0xf0 | ((value & 0x1c0000) >> 18);
bytes++; bytes++;
*bytes = 0x80 | ((value & 0x3f000) >> 12); *bytes = 0x80 | ((value & 0x3f000) >> 12);
bytes++; bytes++;
*bytes = 0x80 | ((value & 0xfc0) >> 6); *bytes = 0x80 | ((value & 0xfc0) >> 6);
bytes++; bytes++;
*bytes = 0x80 | (value & 0x3f); *bytes = 0x80 | (value & 0x3f);
return 4; return 4;
} }
// Invalid Unicode value. See: http://tools.ietf.org/html/rfc3629 // Invalid Unicode value. See: http://tools.ietf.org/html/rfc3629
UNREACHABLE(); UNREACHABLE();
return 0; return 0;
} }
int wrenUtf8Decode(const uint8_t* bytes, uint32_t length) int wrenUtf8Decode(const uint8_t* bytes, uint32_t length)
{ {
// Single byte (i.e. fits in ASCII). // Single byte (i.e. fits in ASCII).
if (*bytes <= 0x7f) return *bytes; if (*bytes <= 0x7f) return *bytes;
int value; int value;
uint32_t remainingBytes; uint32_t remainingBytes;
if ((*bytes & 0xe0) == 0xc0) if ((*bytes & 0xe0) == 0xc0)
{ {
// Two byte sequence: 110xxxxx 10xxxxxx. // Two byte sequence: 110xxxxx 10xxxxxx.
value = *bytes & 0x1f; value = *bytes & 0x1f;
remainingBytes = 1; remainingBytes = 1;
} }
else if ((*bytes & 0xf0) == 0xe0) else if ((*bytes & 0xf0) == 0xe0)
{ {
// Three byte sequence: 1110xxxx 10xxxxxx 10xxxxxx. // Three byte sequence: 1110xxxx 10xxxxxx 10xxxxxx.
value = *bytes & 0x0f; value = *bytes & 0x0f;
remainingBytes = 2; remainingBytes = 2;
} }
else if ((*bytes & 0xf8) == 0xf0) else if ((*bytes & 0xf8) == 0xf0)
{ {
// Four byte sequence: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx. // Four byte sequence: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx.
value = *bytes & 0x07; value = *bytes & 0x07;
remainingBytes = 3; remainingBytes = 3;
} }
else else
{ {
// Invalid UTF-8 sequence. // Invalid UTF-8 sequence.
return -1; return -1;
} }
// Don't read past the end of the buffer on truncated UTF-8. // Don't read past the end of the buffer on truncated UTF-8.
if (remainingBytes > length - 1) return -1; if (remainingBytes > length - 1) return -1;
while (remainingBytes > 0) while (remainingBytes > 0)
{ {
bytes++; bytes++;
remainingBytes--; remainingBytes--;
// Remaining bytes must be of form 10xxxxxx. // Remaining bytes must be of form 10xxxxxx.
if ((*bytes & 0xc0) != 0x80) return -1; if ((*bytes & 0xc0) != 0x80) return -1;
value = value << 6 | (*bytes & 0x3f); value = value << 6 | (*bytes & 0x3f);
} }
return value; return value;
} }
int wrenUtf8DecodeNumBytes(uint8_t byte) int wrenUtf8DecodeNumBytes(uint8_t byte)
{ {
// If the byte starts with 10xxxxx, it's the middle of a UTF-8 sequence, so // If the byte starts with 10xxxxx, it's the middle of a UTF-8 sequence, so
// don't count it at all. // don't count it at all.
if ((byte & 0xc0) == 0x80) return 0; if ((byte & 0xc0) == 0x80) return 0;
// The first byte's high bits tell us how many bytes are in the UTF-8 // The first byte's high bits tell us how many bytes are in the UTF-8
// sequence. // sequence.
if ((byte & 0xf8) == 0xf0) return 4; if ((byte & 0xf8) == 0xf0) return 4;
if ((byte & 0xf0) == 0xe0) return 3; if ((byte & 0xf0) == 0xe0) return 3;
if ((byte & 0xe0) == 0xc0) return 2; if ((byte & 0xe0) == 0xc0) return 2;
return 1; return 1;
} }
// From: http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2Float // From: http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2Float
int wrenPowerOf2Ceil(int n) int wrenPowerOf2Ceil(int n)
{ {
n--; n--;
n |= n >> 1; n |= n >> 1;
n |= n >> 2; n |= n >> 2;
n |= n >> 4; n |= n >> 4;
n |= n >> 8; n |= n >> 8;
n |= n >> 16; n |= n >> 16;
n++; n++;
return 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;
}
+126 -121
View File
@@ -1,121 +1,126 @@
#ifndef wren_utils_h #ifndef wren_utils_h
#define wren_utils_h #define wren_utils_h
#include "wren.h" #include "wren.h"
#include "wren_common.h" #include "wren_common.h"
// Reusable data structures and other utility functions. // Reusable data structures and other utility functions.
// Forward declare this here to break a cycle between wren_utils.h and // Forward declare this here to break a cycle between wren_utils.h and
// wren_value.h. // wren_value.h.
typedef struct sObjString ObjString; typedef struct sObjString ObjString;
// We need buffers of a few different types. To avoid lots of casting between // 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 // void* and back, we'll use the preprocessor as a poor man's generics and let
// it generate a few type-specific ones. // it generate a few type-specific ones.
#define DECLARE_BUFFER(name, type) \ #define DECLARE_BUFFER(name, type) \
typedef struct \ typedef struct \
{ \ { \
type* data; \ type* data; \
int count; \ int count; \
int capacity; \ int capacity; \
} name##Buffer; \ } name##Buffer; \
void wren##name##BufferInit(name##Buffer* buffer); \ void wren##name##BufferInit(name##Buffer* buffer); \
void wren##name##BufferClear(WrenVM* vm, name##Buffer* buffer); \ void wren##name##BufferClear(WrenVM* vm, name##Buffer* buffer); \
void wren##name##BufferFill(WrenVM* vm, name##Buffer* buffer, type data, \ void wren##name##BufferFill(WrenVM* vm, name##Buffer* buffer, type data, \
int count); \ int count); \
void wren##name##BufferWrite(WrenVM* vm, name##Buffer* buffer, type data) 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. // This should be used once for each type instantiation, somewhere in a .c file.
#define DEFINE_BUFFER(name, type) \ #define DEFINE_BUFFER(name, type) \
void wren##name##BufferInit(name##Buffer* buffer) \ void wren##name##BufferInit(name##Buffer* buffer) \
{ \ { \
buffer->data = NULL; \ buffer->data = NULL; \
buffer->capacity = 0; \ buffer->capacity = 0; \
buffer->count = 0; \ buffer->count = 0; \
} \ } \
\ \
void wren##name##BufferClear(WrenVM* vm, name##Buffer* buffer) \ void wren##name##BufferClear(WrenVM* vm, name##Buffer* buffer) \
{ \ { \
wrenReallocate(vm, buffer->data, 0, 0); \ wrenReallocate(vm, buffer->data, 0, 0); \
wren##name##BufferInit(buffer); \ wren##name##BufferInit(buffer); \
} \ } \
\ \
void wren##name##BufferFill(WrenVM* vm, name##Buffer* buffer, type data, \ void wren##name##BufferFill(WrenVM* vm, name##Buffer* buffer, type data, \
int count) \ int count) \
{ \ { \
if (buffer->capacity < buffer->count + count) \ if (buffer->capacity < buffer->count + count) \
{ \ { \
int capacity = wrenPowerOf2Ceil(buffer->count + count); \ int capacity = wrenPowerOf2Ceil(buffer->count + count); \
buffer->data = (type*)wrenReallocate(vm, buffer->data, \ buffer->data = (type*)wrenReallocate(vm, buffer->data, \
buffer->capacity * sizeof(type), capacity * sizeof(type)); \ buffer->capacity * sizeof(type), capacity * sizeof(type)); \
buffer->capacity = capacity; \ buffer->capacity = capacity; \
} \ } \
\ \
for (int i = 0; i < count; i++) \ for (int i = 0; i < count; i++) \
{ \ { \
buffer->data[buffer->count++] = data; \ buffer->data[buffer->count++] = data; \
} \ } \
} \ } \
\ \
void wren##name##BufferWrite(WrenVM* vm, name##Buffer* buffer, type data) \ void wren##name##BufferWrite(WrenVM* vm, name##Buffer* buffer, type data) \
{ \ { \
wren##name##BufferFill(vm, buffer, data, 1); \ wren##name##BufferFill(vm, buffer, data, 1); \
} }
DECLARE_BUFFER(Byte, uint8_t); DECLARE_BUFFER(Byte, uint8_t);
DECLARE_BUFFER(Int, int); DECLARE_BUFFER(Int, int);
DECLARE_BUFFER(String, ObjString*); DECLARE_BUFFER(String, ObjString*);
// TODO: Change this to use a map. // TODO: Change this to use a map.
typedef StringBuffer SymbolTable; typedef StringBuffer SymbolTable;
// Initializes the symbol table. // Initializes the symbol table.
void wrenSymbolTableInit(SymbolTable* symbols); void wrenSymbolTableInit(SymbolTable* symbols);
// Frees all dynamically allocated memory used by the symbol table, but not the // Frees all dynamically allocated memory used by the symbol table, but not the
// SymbolTable itself. // SymbolTable itself.
void wrenSymbolTableClear(WrenVM* vm, SymbolTable* symbols); void wrenSymbolTableClear(WrenVM* vm, SymbolTable* symbols);
// Adds name to the symbol table. Returns the index of it in the table. // Adds name to the symbol table. Returns the index of it in the table.
int wrenSymbolTableAdd(WrenVM* vm, SymbolTable* symbols, int wrenSymbolTableAdd(WrenVM* vm, SymbolTable* symbols,
const char* name, size_t length); const char* name, size_t length);
// Adds name to the symbol table. Returns the index of it in the table. Will // Adds name to the symbol table. Returns the index of it in the table. Will
// use an existing symbol if already present. // use an existing symbol if already present.
int wrenSymbolTableEnsure(WrenVM* vm, SymbolTable* symbols, int wrenSymbolTableEnsure(WrenVM* vm, SymbolTable* symbols,
const char* name, size_t length); const char* name, size_t length);
// Looks up name in the symbol table. Returns its index if found or -1 if not. // Looks up name in the symbol table. Returns its index if found or -1 if not.
int wrenSymbolTableFind(const SymbolTable* symbols, int wrenSymbolTableFind(const SymbolTable* symbols,
const char* name, size_t length); const char* name, size_t length);
void wrenBlackenSymbolTable(WrenVM* vm, SymbolTable* symbolTable); void wrenBlackenSymbolTable(WrenVM* vm, SymbolTable* symbolTable);
// Returns the number of bytes needed to encode [value] in UTF-8. // Returns the number of bytes needed to encode [value] in UTF-8.
// //
// Returns 0 if [value] is too large to encode. // Returns 0 if [value] is too large to encode.
int wrenUtf8EncodeNumBytes(int value); int wrenUtf8EncodeNumBytes(int value);
// Encodes value as a series of bytes in [bytes], which is assumed to be large // Encodes value as a series of bytes in [bytes], which is assumed to be large
// enough to hold the encoded result. // enough to hold the encoded result.
// //
// Returns the number of written bytes. // Returns the number of written bytes.
int wrenUtf8Encode(int value, uint8_t* bytes); int wrenUtf8Encode(int value, uint8_t* bytes);
// Decodes the UTF-8 sequence starting at [bytes] (which has max [length]), // Decodes the UTF-8 sequence starting at [bytes] (which has max [length]),
// returning the code point. // returning the code point.
// //
// Returns -1 if the bytes are not a valid UTF-8 sequence. // Returns -1 if the bytes are not a valid UTF-8 sequence.
int wrenUtf8Decode(const uint8_t* bytes, uint32_t length); int wrenUtf8Decode(const uint8_t* bytes, uint32_t length);
// Returns the number of bytes in the UTF-8 sequence starting with [byte]. // 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, // If the character at that index is not the beginning of a UTF-8 sequence,
// returns 0. // returns 0.
int wrenUtf8DecodeNumBytes(uint8_t byte); int wrenUtf8DecodeNumBytes(uint8_t byte);
// Returns the smallest power of two that is equal to or greater than [n]. // Returns the smallest power of two that is equal to or greater than [n].
int wrenPowerOf2Ceil(int n); int wrenPowerOf2Ceil(int n);
#endif // 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
+1330 -1315
View File
File diff suppressed because it is too large Load Diff
+890 -876
View File
File diff suppressed because it is too large Load Diff
+1993 -1783
View File
File diff suppressed because it is too large Load Diff
+251 -246
View File
@@ -1,246 +1,251 @@
#ifndef wren_vm_h #ifndef wren_vm_h
#define wren_vm_h #define wren_vm_h
#include "wren_common.h" #include "wren_common.h"
#include "wren_compiler.h" #include "wren_compiler.h"
#include "wren_value.h" #include "wren_value.h"
#include "wren_utils.h" #include "wren_utils.h"
// The maximum number of temporary objects that can be made visible to the GC // The maximum number of temporary objects that can be made visible to the GC
// at one time. // at one time.
#define WREN_MAX_TEMP_ROOTS 5 #define WREN_MAX_TEMP_ROOTS 8
typedef enum typedef enum
{ {
#define OPCODE(name, _) CODE_##name, #define OPCODE(name, _) CODE_##name,
#include "wren_opcodes.h" #include "wren_opcodes.h"
#undef OPCODE #undef OPCODE
} Code; } Code;
// A handle to a value, basically just a linked list of extra GC roots. // 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. // Note that even non-heap-allocated values can be stored here.
struct WrenHandle struct WrenHandle
{ {
Value value; Value value;
WrenHandle* prev; WrenHandle* prev;
WrenHandle* next; WrenHandle* next;
}; };
struct WrenVM struct WrenVM
{ {
ObjClass* boolClass; ObjClass* boolClass;
ObjClass* classClass; ObjClass* classClass;
ObjClass* fiberClass; ObjClass* fiberClass;
ObjClass* fnClass; ObjClass* fnClass;
ObjClass* listClass; ObjClass* listClass;
ObjClass* mapClass; ObjClass* mapClass;
ObjClass* nullClass; ObjClass* nullClass;
ObjClass* numClass; ObjClass* numClass;
ObjClass* objectClass; ObjClass* objectClass;
ObjClass* rangeClass; ObjClass* rangeClass;
ObjClass* stringClass; ObjClass* stringClass;
// The fiber that is currently running. // The fiber that is currently running.
ObjFiber* fiber; ObjFiber* fiber;
// The loaded modules. Each key is an ObjString (except for the main module, // 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 // whose key is null) for the module's name and the value is the ObjModule
// for the module. // for the module.
ObjMap* modules; ObjMap* modules;
// The most recently imported module. More specifically, the module whose // The most recently imported module. More specifically, the module whose
// code has most recently finished executing. // code has most recently finished executing.
// //
// Not treated like a GC root since the module is already in [modules]. // Not treated like a GC root since the module is already in [modules].
ObjModule* lastModule; ObjModule* lastModule;
// Memory management data: // Memory management data:
// The number of bytes that are known to be currently allocated. Includes all // 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 // 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 // that were allocated since then. Does *not* include bytes for objects that
// were freed since the last GC. // were freed since the last GC.
size_t bytesAllocated; size_t bytesAllocated;
// The number of total allocated bytes that will trigger the next GC. // The number of total allocated bytes that will trigger the next GC.
size_t nextGC; size_t nextGC;
// The first object in the linked list of all currently allocated objects. // The first object in the linked list of all currently allocated objects.
Obj* first; Obj* first;
// The "gray" set for the garbage collector. This is the stack of unprocessed // The "gray" set for the garbage collector. This is the stack of unprocessed
// objects while a garbage collection pass is in process. // objects while a garbage collection pass is in process.
Obj** gray; Obj** gray;
int grayCount; int grayCount;
int grayCapacity; int grayCapacity;
// The list of temporary roots. This is for temporary or new objects that are // The list of temporary roots. This is for temporary or new objects that are
// not otherwise reachable but should not be collected. // not otherwise reachable but should not be collected.
// //
// They are organized as a stack of pointers stored in this array. This // 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 // implies that temporary roots need to have stack semantics: only the most
// recently pushed object can be released. // recently pushed object can be released.
Obj* tempRoots[WREN_MAX_TEMP_ROOTS]; Obj* tempRoots[WREN_MAX_TEMP_ROOTS];
int numTempRoots; int numTempRoots;
// Pointer to the first node in the linked list of active handles or NULL if // Pointer to the first node in the linked list of active handles or NULL if
// there are none. // there are none.
WrenHandle* handles; WrenHandle* handles;
// Pointer to the bottom of the range of stack slots available for use from // 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 // the C API. During a foreign method, this will be in the stack of the fiber
// that is executing a method. // that is executing a method.
// //
// If not in a foreign method, this is initially NULL. If the user requests // 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 // slots by calling wrenEnsureSlots(), a stack is created and this is
// initialized. // initialized.
Value* apiStack; Value* apiStack;
WrenConfiguration config; WrenConfiguration config;
// Compiler and debugger data: // Compiler and debugger data:
// The compiler that is currently compiling code. This is used so that heap // 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 // allocated objects used by the compiler can be found if a GC is kicked off
// in the middle of a compile. // in the middle of a compile.
Compiler* compiler; Compiler* compiler;
// There is a single global symbol table for all method names on all classes. // There is a single global symbol table for all method names on all classes.
// Method calls are dispatched directly by index in this table. // Method calls are dispatched directly by index in this table.
SymbolTable methodNames; SymbolTable methodNames;
}; };
// A generic allocation function that handles all explicit memory management. // A generic allocation function that handles all explicit memory management.
// It's used like so: // It's used like so:
// //
// - To allocate new memory, [memory] is NULL and [oldSize] is zero. It should // - To allocate new memory, [memory] is NULL and [oldSize] is zero. It should
// return the allocated memory or NULL on failure. // return the allocated memory or NULL on failure.
// //
// - To attempt to grow an existing allocation, [memory] is the memory, // - To attempt to grow an existing allocation, [memory] is the memory,
// [oldSize] is its previous size, and [newSize] is the desired size. // [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 // It should return [memory] if it was able to grow it in place, or a new
// pointer if it had to move it. // pointer if it had to move it.
// //
// - To shrink memory, [memory], [oldSize], and [newSize] are the same as above // - To shrink memory, [memory], [oldSize], and [newSize] are the same as above
// but it will always return [memory]. // but it will always return [memory].
// //
// - To free memory, [memory] will be the memory to free and [newSize] and // - To free memory, [memory] will be the memory to free and [newSize] and
// [oldSize] will be zero. It should return NULL. // [oldSize] will be zero. It should return NULL.
void* wrenReallocate(WrenVM* vm, void* memory, size_t oldSize, size_t newSize); void* wrenReallocate(WrenVM* vm, void* memory, size_t oldSize, size_t newSize);
// Invoke the finalizer for the foreign object referenced by [foreign]. // Invoke the finalizer for the foreign object referenced by [foreign].
void wrenFinalizeForeign(WrenVM* vm, ObjForeign* foreign); void wrenFinalizeForeign(WrenVM* vm, ObjForeign* foreign);
// Creates a new [WrenHandle] for [value]. // Creates a new [WrenHandle] for [value].
WrenHandle* wrenMakeHandle(WrenVM* vm, Value value); WrenHandle* wrenMakeHandle(WrenVM* vm, Value value);
// Compile [source] in the context of [module] and wrap in a fiber that can // Compile [source] in the context of [module] and wrap in a fiber that can
// execute it. // execute it.
// //
// Returns NULL if a compile error occurred. // Returns NULL if a compile error occurred.
ObjClosure* wrenCompileSource(WrenVM* vm, const char* module, ObjClosure* wrenCompileSource(WrenVM* vm, const char* module,
const char* source, bool isExpression, const char* source, bool isExpression,
bool printErrors); bool printErrors);
// Looks up a variable from a previously-loaded module. // Looks up a variable from a previously-loaded module.
// //
// Aborts the current fiber if the module or variable could not be found. // Aborts the current fiber if the module or variable could not be found.
Value wrenGetModuleVariable(WrenVM* vm, Value moduleName, Value variableName); Value wrenGetModuleVariable(WrenVM* vm, Value moduleName, Value variableName);
// Returns the value of the module-level variable named [name] in the main // Returns the value of the module-level variable named [name] in the main
// module. // module.
Value wrenFindVariable(WrenVM* vm, ObjModule* module, const char* name); Value wrenFindVariable(WrenVM* vm, ObjModule* module, const char* name);
// Adds a new implicitly declared top-level variable named [name] to [module] // Adds a new implicitly declared top-level variable named [name] to [module]
// based on a use site occurring on [line]. // based on a use site occurring on [line].
// //
// Does not check to see if a variable with that name is already declared or // 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 // defined. Returns the symbol for the new variable or -2 if there are too many
// variables defined. // variables defined.
int wrenDeclareVariable(WrenVM* vm, ObjModule* module, const char* name, int wrenDeclareVariable(WrenVM* vm, ObjModule* module, const char* name,
size_t length, int line); size_t length, int line);
// Adds a new top-level variable named [name] to [module], and optionally // 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). // 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 // 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. // 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 // Returns -3 if this is a top-level lowercase variable (localname) that was
// used before being defined. // used before being defined.
int wrenDefineVariable(WrenVM* vm, ObjModule* module, const char* name, int wrenDefineVariable(WrenVM* vm, ObjModule* module, const char* name,
size_t length, Value value, int* line); size_t length, Value value, int* line);
// Pushes [closure] onto [fiber]'s callstack to invoke it. Expects [numArgs] // Pushes [closure] onto [fiber]'s callstack to invoke it. Expects [numArgs]
// arguments (including the receiver) to be on the top of the stack already. // arguments (including the receiver) to be on the top of the stack already.
static inline void wrenCallFunction(WrenVM* vm, ObjFiber* fiber, static inline void wrenCallFunction(WrenVM* vm, ObjFiber* fiber,
ObjClosure* closure, int numArgs) ObjClosure* closure, int numArgs)
{ {
// Grow the call frame array if needed. // Grow the call frame array if needed.
if (fiber->numFrames + 1 > fiber->frameCapacity) if (fiber->numFrames + 1 > fiber->frameCapacity)
{ {
int max = fiber->frameCapacity * 2; int max = fiber->frameCapacity * 2;
fiber->frames = (CallFrame*)wrenReallocate(vm, fiber->frames, fiber->frames = (CallFrame*)wrenReallocate(vm, fiber->frames,
sizeof(CallFrame) * fiber->frameCapacity, sizeof(CallFrame) * max); sizeof(CallFrame) * fiber->frameCapacity, sizeof(CallFrame) * max);
fiber->frameCapacity = max; fiber->frameCapacity = max;
} }
// Grow the stack if needed. // Grow the stack if needed.
int stackSize = (int)(fiber->stackTop - fiber->stack); int stackSize = (int)(fiber->stackTop - fiber->stack);
int needed = stackSize + closure->fn->maxSlots; int needed = stackSize + closure->fn->maxSlots;
wrenEnsureStack(vm, fiber, needed); wrenEnsureStack(vm, fiber, needed);
wrenAppendCallFrame(vm, fiber, closure, fiber->stackTop - numArgs); wrenAppendCallFrame(vm, fiber, closure, fiber->stackTop - numArgs);
} }
// Marks [obj] as a GC root so that it doesn't get collected. // Marks [obj] as a GC root so that it doesn't get collected.
void wrenPushRoot(WrenVM* vm, Obj* obj); void wrenPushRoot(WrenVM* vm, Obj* obj);
// Removes the most recently pushed temporary root. // Removes the most recently pushed temporary root.
void wrenPopRoot(WrenVM* vm); void wrenPopRoot(WrenVM* vm);
// Returns the class of [value]. // Returns the class of [value].
// //
// Defined here instead of in wren_value.h because it's critical that this be // 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 // 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. // header doesn't have a full definitely of WrenVM yet.
static inline ObjClass* wrenGetClassInline(WrenVM* vm, Value value) static inline ObjClass* wrenGetClassInline(WrenVM* vm, Value value)
{ {
if (IS_NUM(value)) return vm->numClass; if (IS_NUM(value)) return vm->numClass;
if (IS_OBJ(value)) return AS_OBJ(value)->classObj; if (IS_OBJ(value)) return AS_OBJ(value)->classObj;
#if WREN_NAN_TAGGING #if WREN_NAN_TAGGING
switch (GET_TAG(value)) switch (GET_TAG(value))
{ {
case TAG_FALSE: return vm->boolClass; break; case TAG_FALSE: return vm->boolClass; break;
case TAG_NAN: return vm->numClass; break; case TAG_NAN: return vm->numClass; break;
case TAG_NULL: return vm->nullClass; break; case TAG_NULL: return vm->nullClass; break;
case TAG_TRUE: return vm->boolClass; break; case TAG_TRUE: return vm->boolClass; break;
case TAG_UNDEFINED: UNREACHABLE(); case TAG_UNDEFINED: UNREACHABLE();
} }
#else #else
switch (value.type) switch (value.type)
{ {
case VAL_FALSE: return vm->boolClass; case VAL_FALSE: return vm->boolClass;
case VAL_NULL: return vm->nullClass; case VAL_NULL: return vm->nullClass;
case VAL_NUM: return vm->numClass; case VAL_NUM: return vm->numClass;
case VAL_TRUE: return vm->boolClass; case VAL_TRUE: return vm->boolClass;
case VAL_OBJ: return AS_OBJ(value)->classObj; case VAL_OBJ: return AS_OBJ(value)->classObj;
case VAL_UNDEFINED: UNREACHABLE(); case VAL_UNDEFINED: UNREACHABLE();
} }
#endif #endif
UNREACHABLE(); UNREACHABLE();
return NULL; return NULL;
} }
// Returns `true` if [name] is a local variable name (starts with a lowercase // Returns `true` if [name] is a local variable name (starts with a lowercase
// letter). // letter).
static inline bool wrenIsLocalName(const char* name) static inline bool wrenIsLocalName(const char* name)
{ {
return name[0] >= 'a' && name[0] <= 'z'; return name[0] >= 'a' && name[0] <= 'z';
} }
#endif static inline bool wrenIsFalsyValue(Value value)
{
return IS_FALSE(value) || IS_NULL(value);
}
#endif