diff --git a/doc/site/method-calls.markdown b/doc/site/method-calls.markdown index f13305b2..d9284b94 100644 --- a/doc/site/method-calls.markdown +++ b/doc/site/method-calls.markdown @@ -61,8 +61,8 @@ any "if I got two arguments do this..." logic. ## Getters -Some methods exist to expose some stored or computed property of an object. -These are *getters* and have no parentheses: +Some methods exist to expose a stored or computed property of an object. These +are *getters* and have no parentheses: :::wren "string".count //> 6 @@ -70,8 +70,9 @@ These are *getters* and have no parentheses: 1.23.sin //> 0.9424888019317 [1, 2, 3].isEmpty //> false -Sometimes you have a method doesn't need any parameters, but modifies the object -or has some other side effect. For those, it's better to use empty parentheses: +Sometimes you have a method that doesn't need any parameters, but modifies the +object or has some other side effect. For those, it's better to use empty +parentheses: :::wren list.clear() @@ -89,7 +90,7 @@ like a getter and a `()` method like a `()` method. These don't work: :::wren "string".count() - list.clear + [1, 2, 3].clear ## Setters @@ -104,7 +105,7 @@ language's perspective, the above line is just a call to the `height=(_)` method, passing in `74`. Since the `=(_)` is in the setter's signature, an object can have both a getter -and setter with the same name without any collision. This way, you can have +and setter with the same name without a collision. This way, you can have read/write properties. ## Operators diff --git a/doc/site/modules/core/system.markdown b/doc/site/modules/core/system.markdown index 1104c34d..ac091193 100644 --- a/doc/site/modules/core/system.markdown +++ b/doc/site/modules/core/system.markdown @@ -21,7 +21,7 @@ Prints a single newline to the console. ### System.**print**(object) -Prints [object] to the console followed by a newline. If not already a string, +Prints `object` to the console followed by a newline. If not already a string, the object is converted to a string by calling `toString` on it. :::wren @@ -29,7 +29,7 @@ the object is converted to a string by calling `toString` on it. ### System.**printAll**(sequence) -Iterates over [sequence] and prints each element, then prints a single newline +Iterates over `sequence` and prints each element, then prints a single newline at the end. Each element is converted to a string by calling `toString` on it. :::wren diff --git a/doc/site/modules/io/directory.markdown b/doc/site/modules/io/directory.markdown new file mode 100644 index 00000000..0e41a33b --- /dev/null +++ b/doc/site/modules/io/directory.markdown @@ -0,0 +1,10 @@ +^title Directory Class + +A directory on the file system. + +## Static Methods + +### Directory.**list**(path) + +Lists the contents of the directory at `path`. Returns a sorted list of path +strings for all of the contents of the directory. diff --git a/doc/site/modules/io/file.markdown b/doc/site/modules/io/file.markdown index b9b690cd..cb32454e 100644 --- a/doc/site/modules/io/file.markdown +++ b/doc/site/modules/io/file.markdown @@ -1,7 +1,88 @@ ^title File Class -**TODO** +Lets you work with files on the file system. An instance of this class +represents an open file with a file descriptor. + +When you are done with a file object, it's a good idea to explicitly close it. +If you don't, the GC will close it when the file is no longer used and gets +finalized, but that may take a while. In the meantime, leaving it open wastes +a file descriptor. + +## Static Methods + +### File.**open**(path, fn) + +Opens the file at `path` and passes it to `fn`. After the function returns, the +file is automatically closed. + + :::wren + File.open("words.txt") {|file| + file.readBytes(5) + } + +### File.**read**(path) + +Reads the entire contents of the file at `path` and returns it as a string. + + :::wren + File.read("words.txt") + +The encoding or decoding is done. If the file is UTF-8, then the resulting +string will be a UTF-8 string. Otherwise, it will be a string of bytes in +whatever encoding the file uses. + +### File.**size**(path) + +Returns the size in bytes of the contents of the file at `path`. + +### File.**stat**(path) + +"Stats" the file or directory at `path`. Returns a [Stat][] object describing +the low-level details of the file system entry. + +[stat]: stat.html + +## Constructors + +### File.**open**(path) + +Opens the file at `path` for reading. ## Methods -**TODO** +### **descriptor** + +The numeric file descriptor used to access the file. + +### **isOpen** + +Whether the file is still open or has been closed. + +### **size** + +The size of the contents of the file in bytes. + +### **close**() + +Closes the file. After calling this, you can read or write from it. + +### **readBytes**(count) + +Reads up to `count` bytes starting from the beginning of the file. + + :::wren + // Assume this file contains "I am a file!". + File.open("example.txt") {|file| + System.print(file.readBytes(6)) //> I am a + } + +### **readBytes**(count, offset) + +Reads up to `count` bytes starting at `offset` bytes from the beginning of +the file. + + :::wren + // Assume this file contains "I am a file!". + File.open("example.txt") {|file| + System.print(file.readBytes(6, 2)) //> am a f + } diff --git a/doc/site/modules/io/index.markdown b/doc/site/modules/io/index.markdown index ba41efbd..2b5a765c 100644 --- a/doc/site/modules/io/index.markdown +++ b/doc/site/modules/io/index.markdown @@ -1,6 +1,8 @@ ^title Module "io" -**TODO** +Provides access to operating system streams and the file system. +* [Directory](directory.html) * [File](file.html) +* [Stat](stat.html) * [Stdin](stdin.html) diff --git a/doc/site/modules/io/stat.markdown b/doc/site/modules/io/stat.markdown new file mode 100644 index 00000000..0b69b219 --- /dev/null +++ b/doc/site/modules/io/stat.markdown @@ -0,0 +1,50 @@ +^title Stat Class + +Contains the data returned by [File.stat()][stat]. + +[stat]: file.html#file.stat(path) + +## Methods + +### **device** + +The ID of the device containing the entry. + +### **inode** + +The [inode][] number of the entry. + +[inode]: https://en.wikipedia.org/wiki/Inode + +### **mode** + +A bit field describing the entry's type and protection flags. + +### **linkCount** + +The number of hard links to the entry. + +### **user** + +Numeric user ID of the file's owner. + +### **group** + +Numeric group ID of the file's owner. + +### **specialDevice** + +The device ID for the entry, if it's a special file. + +### **size** + +The size of the entry in bytes. + +### **blockSize** + +The preferred block size in bytes for interacting with the file. It may vary +from file to file. + +### **blockCount** + +The number of system blocks allocated on disk for the file. diff --git a/doc/site/modules/io/stdin.markdown b/doc/site/modules/io/stdin.markdown index 399a9af5..66377f85 100644 --- a/doc/site/modules/io/stdin.markdown +++ b/doc/site/modules/io/stdin.markdown @@ -1,7 +1,12 @@ ^title Stdin Class -**TODO** +The standard input stream. -## Methods +## Static Methods -**TODO** +### **readLine**() + +Reads one line of input from stdin. Blocks the current fiber until a full line +of input has been received. + +Returns the string of input or `null` if stdin is closed. diff --git a/doc/site/modules/io/template.html b/doc/site/modules/io/template.html index ab36a796..ebce39a1 100644 --- a/doc/site/modules/io/template.html +++ b/doc/site/modules/io/template.html @@ -27,6 +27,7 @@

io classes

diff --git a/src/cli/modules.c b/src/cli/modules.c index 093844e6..024156e3 100644 --- a/src/cli/modules.c +++ b/src/cli/modules.c @@ -7,10 +7,12 @@ #include "scheduler.wren.inc" #include "timer.wren.inc" +extern void directoryList(WrenVM* vm); extern void fileAllocate(WrenVM* vm); -extern void fileFinalize(WrenVM* vm); +extern void fileFinalize(void* data); extern void fileOpen(WrenVM* vm); extern void fileSizePath(WrenVM* vm); +extern void fileStatPath(WrenVM* vm); extern void fileClose(WrenVM* vm); extern void fileDescriptor(WrenVM* vm); extern void fileReadBytes(WrenVM* vm); @@ -28,14 +30,14 @@ extern void timerStartTimer(WrenVM* vm); // If you add a new method to the longest class below, make sure to bump this. // Note that it also includes an extra slot for the sentinel value indicating // the end of the list. -#define MAX_METHODS_PER_CLASS 9 +#define MAX_METHODS_PER_CLASS 10 // The maximum number of foreign classes a single built-in module defines. // // If you add a new class to the largest module below, make sure to bump this. // Note that it also includes an extra slot for the sentinel value indicating // the end of the list. -#define MAX_CLASSES_PER_MODULE 3 +#define MAX_CLASSES_PER_MODULE 4 // Describes one foreign method in a class. typedef struct @@ -82,19 +84,24 @@ typedef struct #define METHOD(signature, fn) { false, signature, fn }, #define STATIC_METHOD(signature, fn) { true, signature, fn }, +#define FINALIZER(fn) { true, "", (WrenForeignMethodFn)fn }, // The array of built-in modules. static ModuleRegistry modules[] = { MODULE(io) + CLASS(Directory) + STATIC_METHOD("list_(_,_)", directoryList) + END_CLASS CLASS(File) STATIC_METHOD("", fileAllocate) - STATIC_METHOD("", fileFinalize) + FINALIZER(fileFinalize) STATIC_METHOD("open_(_,_)", fileOpen) STATIC_METHOD("sizePath_(_,_)", fileSizePath) + STATIC_METHOD("statPath_(_,_)", fileStatPath) METHOD("close_(_)", fileClose) METHOD("descriptor", fileDescriptor) - METHOD("readBytes_(_,_)", fileReadBytes) + METHOD("readBytes_(_,_,_)", fileReadBytes) METHOD("size_(_)", fileSize) END_CLASS CLASS(Stdin) @@ -204,7 +211,7 @@ WrenForeignClassMethods bindBuiltInForeignClass( if (clas == NULL) return methods; methods.allocate = findMethod(clas, true, ""); - methods.finalize = findMethod(clas, true, ""); + methods.finalize = (WrenFinalizerFn)findMethod(clas, true, ""); return methods; } diff --git a/src/include/wren.h b/src/include/wren.h index 29645f83..de2b7874 100644 --- a/src/include/wren.h +++ b/src/include/wren.h @@ -38,6 +38,12 @@ typedef void* (*WrenReallocateFn)(void* memory, size_t newSize); // A function callable from Wren code, but implemented in C. typedef void (*WrenForeignMethodFn)(WrenVM* vm); +// A finalizer function for freeing resources owned by an instance of a foreign +// class. Unlike most foreign methods, finalizers do not have access to the VM +// and should not interact with it since it's in the middle of a garbage +// collection. +typedef void (*WrenFinalizerFn)(void* data); + // Loads and returns the source code for the module [name]. typedef char* (*WrenLoadModuleFn)(WrenVM* vm, const char* name); @@ -64,7 +70,7 @@ typedef struct // foreign object's memory. // // This may be `NULL` if the foreign class does not need to finalize. - WrenForeignMethodFn finalize; + WrenFinalizerFn finalize; } WrenForeignClassMethods; // Returns a pair of pointers to the foreign methods used to allocate and @@ -120,7 +126,7 @@ typedef struct // // If this is `NULL`, Wren discards any printed text. WrenWriteFn writeFn; - + // The number of bytes Wren will allocate before triggering the first garbage // collection. // @@ -183,53 +189,71 @@ void wrenCollectGarbage(WrenVM* vm); // Runs [source], a string of Wren source code in a new fiber in [vm]. WrenInterpretResult wrenInterpret(WrenVM* vm, const char* source); -// Creates a handle that can be used to invoke a method with [signature] on the -// object in [module] currently stored in top-level [variable]. +// Creates a handle that can be used to invoke a method with [signature] on +// using a receiver and arguments that are set up on the stack. // // This handle can be used repeatedly to directly invoke that method from C // code using [wrenCall]. // -// When done with this handle, it must be released using [wrenReleaseValue]. -WrenValue* wrenGetMethod(WrenVM* vm, const char* module, const char* variable, - const char* signature); +// When you are done with this handle, it must be released using +// [wrenReleaseValue]. +WrenValue* wrenMakeCallHandle(WrenVM* vm, const char* signature); -// Calls [method], passing in a series of arguments whose types must match the -// specifed [argTypes]. This is a string where each character identifies the -// type of a single argument, in order. The allowed types are: +// Calls [method], using the receiver and arguments previously set up on the +// stack. // -// - "b" - A C `int` converted to a Wren Bool. -// - "d" - A C `double` converted to a Wren Num. -// - "i" - A C `int` converted to a Wren Num. -// - "s" - A C null-terminated `const char*` converted to a Wren String. Wren -// will allocate its own string and copy the characters from this, so -// you don't have to worry about the lifetime of the string you pass to -// Wren. -// - "a" - An array of bytes converted to a Wren String. This requires two -// consecutive arguments in the argument list: `const char*` pointing -// to the array of bytes, followed by an `int` defining the length of -// the array. This is used when the passed string may contain null -// bytes, or just to avoid the implicit `strlen()` call of "s" if you -// happen to already know the length. -// - "v" - A previously acquired WrenValue*. Passing this in does not implicitly -// release the value. If the passed argument is NULL, this becomes a -// Wren NULL. +// [method] must have been created by a call to [wrenMakeCallHandle]. The +// arguments to the method must be already on the stack. The receiver should be +// in slot 0 with the remaining arguments following it, in order. It is an +// error if the number of arguments provided does not match the method's +// signature. // -// [method] must have been created by a call to [wrenGetMethod]. If -// [returnValue] is not `NULL`, the return value of the method will be stored -// in a new [WrenValue] that [returnValue] will point to. Don't forget to -// release it, when done with it. -WrenInterpretResult wrenCall(WrenVM* vm, WrenValue* method, - WrenValue** returnValue, - const char* argTypes, ...); - -WrenInterpretResult wrenCallVarArgs(WrenVM* vm, WrenValue* method, - WrenValue** returnValue, - const char* argTypes, va_list args); +// After this returns, you can access the return value from slot 0 on the stack. +WrenInterpretResult wrenCall(WrenVM* vm, WrenValue* method); // Releases the reference stored in [value]. After calling this, [value] can no // longer be used. void wrenReleaseValue(WrenVM* vm, WrenValue* value); +// The following functions are intended to be called from foreign methods or +// finalizers. The interface Wren provides to a foreign method is like a +// register machine: you are given a numbered array of slots that values can be +// read from and written to. Values always live in a slot (unless explicitly +// captured using wrenGetSlotValue(), which ensures the garbage collector can +// find them. +// +// When your foreign function is called, you are given one slot for the receiver +// and each argument to the method. The receiver is in slot 0 and the arguments +// are in increasingly numbered slots after that. You are free to read and +// write to those slots as you want. If you want more slots to use as scratch +// space, you can call wrenEnsureSlots() to add more. +// +// When your function returns, every slot except slot zero is discarded and the +// value in slot zero is used as the return value of the method. If you don't +// store a return value in that slot yourself, it will retain its previous +// value, the receiver. +// +// While Wren is dynamically typed, C is not. This means the C interface has to +// support the various types of primitive values a Wren variable can hold: bool, +// double, string, etc. If we supported this for every operation in the C API, +// there would be a combinatorial explosion of functions, like "get a +// double-valued element from a list", "insert a string key and double value +// into a map", etc. +// +// To avoid that, the only way to convert to and from a raw C value is by going +// into and out of a slot. All other functions work with values already in a +// slot. So, to add an element to a list, you put the list in one slot, and the +// element in another. Then there is a single API function wrenInsertInList() +// that takes the element out of that slot and puts it into the list. +// +// The goal of this API is to be easy to use while not compromising performance. +// The latter means it does not do type or bounds checking at runtime except +// using assertions which are generally removed from release builds. C is an +// unsafe language, so it's up to you to be careful to use it correctly. In +// return, you get a very fast FFI. + +// TODO: Generalize this to look up a foreign class in any slot and place the +// object in a desired slot. // This must be called once inside a foreign class's allocator function. // // It tells Wren how many bytes of raw data need to be stored in the foreign @@ -237,55 +261,60 @@ void wrenReleaseValue(WrenVM* vm, WrenValue* value); // the foreign object's data. void* wrenAllocateForeign(WrenVM* vm, size_t size); -// Returns the number of arguments available to the current foreign method. -int wrenGetArgumentCount(WrenVM* vm); +// Returns the number of slots available to the current foreign method. +int wrenGetSlotCount(WrenVM* vm); -// The following functions read one of the arguments passed to a foreign call. -// They may only be called while within a function provided to -// [wrenDefineMethod] or [wrenDefineStaticMethod] that Wren has invoked. +// Ensures that the foreign method stack has at least [numSlots] available for +// use, growing the stack if needed. // -// They retreive the argument at a given index which ranges from 0 to the number -// of parameters the method expects. The zeroth parameter is used for the -// receiver of the method. For example, given a foreign method "foo" on String -// invoked like: +// Does not shrink the stack if it has more than enough slots. // -// "receiver".foo("one", "two", "three") -// -// The foreign function will be able to access the arguments like so: -// -// 0: "receiver" -// 1: "one" -// 2: "two" -// 3: "three" -// -// It is an error to pass an invalid argument index. +// It is an error to call this from a finalizer. +void wrenEnsureSlots(WrenVM* vm, int numSlots); -// Reads a boolean argument for a foreign call. Returns false if the argument -// is not a boolean. -bool wrenGetArgumentBool(WrenVM* vm, int index); +// Reads a boolean value from [slot]. +// +// It is an error to call this if the slot does not contain a boolean value. +bool wrenGetSlotBool(WrenVM* vm, int slot); -// Reads a numeric argument for a foreign call. Returns 0 if the argument is not -// a number. -double wrenGetArgumentDouble(WrenVM* vm, int index); - -// Reads a foreign object argument for a foreign call and returns a pointer to -// the foreign data stored with it. Returns NULL if the argument is not a -// foreign object. -void* wrenGetArgumentForeign(WrenVM* vm, int index); - -// Reads an string argument for a foreign call. Returns NULL if the argument is -// not a string. +// Reads a byte array from [slot]. // // The memory for the returned string is owned by Wren. You can inspect it -// while in your foreign function, but cannot keep a pointer to it after the +// while in your foreign method, but cannot keep a pointer to it after the // function returns, since the garbage collector may reclaim it. -const char* wrenGetArgumentString(WrenVM* vm, int index); +// +// Returns a pointer to the first byte of the array and fill [length] with the +// number of bytes in the array. +// +// It is an error to call this if the slot does not contain a string. +const char* wrenGetSlotBytes(WrenVM* vm, int slot, int* length); -// Creates a handle for the value passed as an argument to a foreign call. +// Reads a number from [slot]. +// +// It is an error to call this if the slot does not contain a number. +double wrenGetSlotDouble(WrenVM* vm, int slot); + +// Reads a foreign object from [slot] and returns a pointer to the foreign data +// stored with it. +// +// It is an error to call this if the slot does not contain an instance of a +// foreign class. +void* wrenGetSlotForeign(WrenVM* vm, int slot); + +// Reads a string from [slot]. +// +// The memory for the returned string is owned by Wren. You can inspect it +// while in your foreign method, but cannot keep a pointer to it after the +// function returns, since the garbage collector may reclaim it. +// +// It is an error to call this if the slot does not contain a string. +const char* wrenGetSlotString(WrenVM* vm, int slot); + +// Creates a handle for the value stored in [slot]. // // This will prevent the object that is referred to from being garbage collected // until the handle is released by calling [wrenReleaseValue()]. -WrenValue* wrenGetArgumentValue(WrenVM* vm, int index); +WrenValue* wrenGetSlotValue(WrenVM* vm, int slot); // The following functions provide the return value for a foreign method back // to Wren. Like above, they may only be called during a foreign call invoked @@ -296,27 +325,47 @@ WrenValue* wrenGetArgumentValue(WrenVM* vm, int index); // call one of these once. It is an error to access any of the foreign calls // arguments after one of these has been called. -// Provides a boolean return value for a foreign call. -void wrenReturnBool(WrenVM* vm, bool value); +// Stores the boolean [value] in [slot]. +void wrenSetSlotBool(WrenVM* vm, int slot, bool value); -// Provides a numeric return value for a foreign call. -void wrenReturnDouble(WrenVM* vm, double value); +// Stores the array [length] of [bytes] in [slot]. +// +// The bytes are copied to a new string within Wren's heap, so you can free +// memory used by them after this is called. +void wrenSetSlotBytes(WrenVM* vm, int slot, const char* bytes, size_t length); -// Provides a string return value for a foreign call. -// -// The [text] will be copied to a new string within Wren's heap, so you can -// free memory used by it after this is called. -// -// If [length] is non-zero, Wren copies that many bytes from [text], including -// any null bytes. If it is -1, then the length of [text] is calculated using -// `strlen()`. If the string may contain any null bytes in the middle, then you -// must pass an explicit length. -void wrenReturnString(WrenVM* vm, const char* text, int length); +// Stores the numeric [value] in [slot]. +void wrenSetSlotDouble(WrenVM* vm, int slot, double value); -// Provides the return value for a foreign call. +// Stores a new empty list in [slot]. +void wrenSetSlotNewList(WrenVM* vm, int slot); + +// Stores null in [slot]. +void wrenSetSlotNull(WrenVM* vm, int slot); + +// Stores the string [text] in [slot]. // -// This uses the value referred to by the handle as the return value, but it -// does not release the handle. -void wrenReturnValue(WrenVM* vm, WrenValue* value); +// The [text] is copied to a new string within Wren's heap, so you can free +// memory used by it after this is called. The length is calculated using +// [strlen()]. If the string may contain any null bytes in the middle, then you +// should use [wrenSetSlotBytes()] instead. +void wrenSetSlotString(WrenVM* vm, int slot, const char* text); + +// Stores the value captured in [value] in [slot]. +// +// This does not release the handle for the value. +void wrenSetSlotValue(WrenVM* vm, int slot, WrenValue* value); + +// Takes the value stored at [elementSlot] and inserts it into the list stored +// at [listSlot] at [index]. +// +// As in Wren, negative indexes can be used to insert from the end. To append +// an element, use `-1` for the index. +void wrenInsertInList(WrenVM* vm, int listSlot, int index, int elementSlot); + +// Looks up the top level variable with [name] in [module] and stores it in +// [slot]. +void wrenGetVariable(WrenVM* vm, const char* module, const char* name, + int slot); #endif diff --git a/src/module/io.c b/src/module/io.c index 9f3a836b..06371264 100644 --- a/src/module/io.c +++ b/src/module/io.c @@ -18,7 +18,10 @@ typedef struct sFileRequestData static const int stdinDescriptor = 0; -// Handle to Stdin.onData_(). Called when libuv provides data on stdin. +// Handle to the Stdin class object. +static WrenValue* stdinClass = NULL; + +// Handle to an onData_() method call. Called when libuv provides data on stdin. static WrenValue* stdinOnData = NULL; // The stream used to read from stdin. Initialized on the first read. @@ -34,6 +37,12 @@ static void shutdownStdin() stdinStream = NULL; } + if (stdinClass != NULL) + { + wrenReleaseValue(getVM(), stdinClass); + stdinClass = NULL; + } + if (stdinOnData != NULL) { wrenReleaseValue(getVM(), stdinOnData); @@ -46,26 +55,6 @@ void ioShutdown() shutdownStdin(); } -void fileAllocate(WrenVM* vm) -{ - // Store the file descriptor in the foreign data, so that we can get to it - // in the finalizer. - int* fd = (int*)wrenAllocateForeign(vm, sizeof(int)); - *fd = (int)wrenGetArgumentDouble(vm, 1); -} - -void fileFinalize(WrenVM* vm) -{ - int fd = *(int*)wrenGetArgumentForeign(vm, 0); - - // Already closed. - if (fd == -1) return; - - uv_fs_t request; - uv_fs_close(getLoop(), &request, fd, NULL); - uv_fs_req_cleanup(&request); -} - // If [request] failed with an error, sends the runtime error to the VM and // frees the request. // @@ -73,12 +62,12 @@ void fileFinalize(WrenVM* vm) static bool handleRequestError(uv_fs_t* request) { if (request->result >= 0) return false; - + FileRequestData* data = (FileRequestData*)request->data; WrenValue* fiber = (WrenValue*)data->fiber; - + schedulerResumeError(fiber, uv_strerror((int)request->result)); - + free(data); uv_fs_req_cleanup(request); free(request); @@ -89,10 +78,10 @@ static bool handleRequestError(uv_fs_t* request) uv_fs_t* createRequest(WrenValue* fiber) { uv_fs_t* request = (uv_fs_t*)malloc(sizeof(uv_fs_t)); - + FileRequestData* data = (FileRequestData*)malloc(sizeof(FileRequestData)); data->fiber = fiber; - + request->data = data; return request; } @@ -104,84 +93,189 @@ WrenValue* freeRequest(uv_fs_t* request) { FileRequestData* data = (FileRequestData*)request->data; WrenValue* fiber = data->fiber; - + free(data); uv_fs_req_cleanup(request); free(request); - + return fiber; } -static void openCallback(uv_fs_t* request) +static void directoryListCallback(uv_fs_t* request) +{ + if (handleRequestError(request)) return; + + uv_dirent_t entry; + + WrenVM* vm = getVM(); + wrenEnsureSlots(vm, 3); + wrenSetSlotNewList(vm, 2); + + while (uv_fs_scandir_next(request, &entry) != UV_EOF) + { + wrenSetSlotString(vm, 1, entry.name); + wrenInsertInList(vm, 2, -1, 1); + } + + schedulerResume(freeRequest(request), true); + schedulerFinishResume(); +} + +void directoryList(WrenVM* vm) +{ + const char* path = wrenGetSlotString(vm, 1); + + uv_fs_t* request = createRequest(wrenGetSlotValue(vm, 2)); + + // TODO: Check return. + uv_fs_scandir(getLoop(), request, path, 0, directoryListCallback); +} + +void fileAllocate(WrenVM* vm) +{ + // Store the file descriptor in the foreign data, so that we can get to it + // in the finalizer. + int* fd = (int*)wrenAllocateForeign(vm, sizeof(int)); + *fd = (int)wrenGetSlotDouble(vm, 1); +} + +void fileFinalize(void* data) +{ + int fd = *(int*)data; + + // Already closed. + if (fd == -1) return; + + uv_fs_t request; + uv_fs_close(getLoop(), &request, fd, NULL); + uv_fs_req_cleanup(&request); +} + +static void fileOpenCallback(uv_fs_t* request) { if (handleRequestError(request)) return; double fd = (double)request->result; - WrenValue* fiber = freeRequest(request); - - schedulerResumeDouble(fiber, fd); + schedulerResume(freeRequest(request), true); + wrenSetSlotDouble(getVM(), 2, fd); + schedulerFinishResume(); } void fileOpen(WrenVM* vm) { - const char* path = wrenGetArgumentString(vm, 1); - uv_fs_t* request = createRequest(wrenGetArgumentValue(vm, 2)); - + const char* path = wrenGetSlotString(vm, 1); + uv_fs_t* request = createRequest(wrenGetSlotValue(vm, 2)); + // TODO: Allow controlling flags and modes. - uv_fs_open(getLoop(), request, path, O_RDONLY, 0, openCallback); + uv_fs_open(getLoop(), request, path, O_RDONLY, 0, fileOpenCallback); } // Called by libuv when the stat call for size completes. -static void sizeCallback(uv_fs_t* request) +static void fileSizeCallback(uv_fs_t* request) { if (handleRequestError(request)) return; - + double size = (double)request->statbuf.st_size; - WrenValue* fiber = freeRequest(request); - - schedulerResumeDouble(fiber, size); + schedulerResume(freeRequest(request), true); + wrenSetSlotDouble(getVM(), 2, size); + schedulerFinishResume(); } void fileSizePath(WrenVM* vm) { - const char* path = wrenGetArgumentString(vm, 1); - uv_fs_t* request = createRequest(wrenGetArgumentValue(vm, 2)); - uv_fs_stat(getLoop(), request, path, sizeCallback); + const char* path = wrenGetSlotString(vm, 1); + uv_fs_t* request = createRequest(wrenGetSlotValue(vm, 2)); + uv_fs_stat(getLoop(), request, path, fileSizeCallback); } -static void closeCallback(uv_fs_t* request) +// Called by libuv when the stat call completes. +static void fileStatPathCallback(uv_fs_t* request) { if (handleRequestError(request)) return; - WrenValue* fiber = freeRequest(request); - schedulerResume(fiber); + WrenVM* vm = getVM(); + wrenEnsureSlots(vm, 4); + wrenSetSlotNewList(vm, 2); + + wrenSetSlotDouble(vm, 3, (double)request->statbuf.st_dev); + wrenInsertInList(vm, 2, -1, 3); + + wrenSetSlotDouble(vm, 3, (double)request->statbuf.st_ino); + wrenInsertInList(vm, 2, -1, 3); + + wrenSetSlotDouble(vm, 3, (double)request->statbuf.st_mode); + wrenInsertInList(vm, 2, -1, 3); + + wrenSetSlotDouble(vm, 3, (double)request->statbuf.st_nlink); + wrenInsertInList(vm, 2, -1, 3); + + wrenSetSlotDouble(vm, 3, (double)request->statbuf.st_uid); + wrenInsertInList(vm, 2, -1, 3); + + wrenSetSlotDouble(vm, 3, (double)request->statbuf.st_gid); + wrenInsertInList(vm, 2, -1, 3); + + wrenSetSlotDouble(vm, 3, (double)request->statbuf.st_rdev); + wrenInsertInList(vm, 2, -1, 3); + + wrenSetSlotDouble(vm, 3, (double)request->statbuf.st_size); + wrenInsertInList(vm, 2, -1, 3); + + wrenSetSlotDouble(vm, 3, (double)request->statbuf.st_blksize); + wrenInsertInList(vm, 2, -1, 3); + + wrenSetSlotDouble(vm, 3, (double)request->statbuf.st_blocks); + wrenInsertInList(vm, 2, -1, 3); + + // TODO: Include access, modification, and change times once we figure out + // how we want to represent it. + // time_t st_atime; /* time of last access */ + // time_t st_mtime; /* time of last modification */ + // time_t st_ctime; /* time of last status change */ + + schedulerResume(freeRequest(request), true); + schedulerFinishResume(); +} + +void fileStatPath(WrenVM* vm) +{ + const char* path = wrenGetSlotString(vm, 1); + uv_fs_t* request = createRequest(wrenGetSlotValue(vm, 2)); + uv_fs_stat(getLoop(), request, path, fileStatPathCallback); +} + +static void fileCloseCallback(uv_fs_t* request) +{ + if (handleRequestError(request)) return; + + schedulerResume(freeRequest(request), false); } void fileClose(WrenVM* vm) { - int* foreign = (int*)wrenGetArgumentForeign(vm, 0); + int* foreign = (int*)wrenGetSlotForeign(vm, 0); int fd = *foreign; - + // If it's already closed, we're done. if (fd == -1) { - wrenReturnBool(vm, true); + wrenSetSlotBool(vm, 0, true); return; } - + // Mark it closed immediately. *foreign = -1; - - uv_fs_t* request = createRequest(wrenGetArgumentValue(vm, 1)); - uv_fs_close(getLoop(), request, fd, closeCallback); - wrenReturnBool(vm, false); + + uv_fs_t* request = createRequest(wrenGetSlotValue(vm, 1)); + uv_fs_close(getLoop(), request, fd, fileCloseCallback); + wrenSetSlotBool(vm, 0, false); } void fileDescriptor(WrenVM* vm) { - int* foreign = (int*)wrenGetArgumentForeign(vm, 0); + int* foreign = (int*)wrenGetSlotForeign(vm, 0); int fd = *foreign; - wrenReturnDouble(vm, fd); + wrenSetSlotDouble(vm, 0, fd); } static void fileReadBytesCallback(uv_fs_t* request) @@ -190,43 +284,45 @@ static void fileReadBytesCallback(uv_fs_t* request) FileRequestData* data = (FileRequestData*)request->data; uv_buf_t buffer = data->buffer; - - WrenValue* fiber = freeRequest(request); + size_t count = request->result; // TODO: Having to copy the bytes here is a drag. It would be good if Wren's // embedding API supported a way to *give* it bytes that were previously // allocated using Wren's own allocator. - schedulerResumeBytes(fiber, buffer.base, buffer.len); - + schedulerResume(freeRequest(request), true); + wrenSetSlotBytes(getVM(), 2, buffer.base, count); + schedulerFinishResume(); + // TODO: Likewise, freeing this after we resume is lame. free(buffer.base); } void fileReadBytes(WrenVM* vm) { - uv_fs_t* request = createRequest(wrenGetArgumentValue(vm, 2)); - - int fd = *(int*)wrenGetArgumentForeign(vm, 0); + uv_fs_t* request = createRequest(wrenGetSlotValue(vm, 3)); + + int fd = *(int*)wrenGetSlotForeign(vm, 0); // TODO: Assert fd != -1. FileRequestData* data = (FileRequestData*)request->data; - size_t length = (size_t)wrenGetArgumentDouble(vm, 1); + size_t length = (size_t)wrenGetSlotDouble(vm, 1); + size_t offset = (size_t)wrenGetSlotDouble(vm, 2); data->buffer.len = length; data->buffer.base = (char*)malloc(length); - - // TODO: Allow passing in offset. - uv_fs_read(getLoop(), request, fd, &data->buffer, 1, 0, fileReadBytesCallback); + + uv_fs_read(getLoop(), request, fd, &data->buffer, 1, offset, + fileReadBytesCallback); } void fileSize(WrenVM* vm) { - uv_fs_t* request = createRequest(wrenGetArgumentValue(vm, 1)); + uv_fs_t* request = createRequest(wrenGetSlotValue(vm, 1)); - int fd = *(int*)wrenGetArgumentForeign(vm, 0); + int fd = *(int*)wrenGetSlotForeign(vm, 0); // TODO: Assert fd != -1. - - uv_fs_fstat(getLoop(), request, fd, sizeCallback); + + uv_fs_fstat(getLoop(), request, fd, fileSizeCallback); } static void allocCallback(uv_handle_t* handle, size_t suggestedSize, @@ -240,26 +336,42 @@ static void allocCallback(uv_handle_t* handle, size_t suggestedSize, static void stdinReadCallback(uv_stream_t* stream, ssize_t numRead, const uv_buf_t* buffer) { - // If stdin was closed, send null to let io.wren know. - if (numRead == UV_EOF) - { - wrenCall(getVM(), stdinOnData, NULL, "v", NULL); - shutdownStdin(); - return; - } + WrenVM* vm = getVM(); - // TODO: Handle other errors. + if (stdinClass == NULL) + { + wrenEnsureSlots(vm, 1); + wrenGetVariable(vm, "io", "Stdin", 0); + stdinClass = wrenGetSlotValue(vm, 0); + } if (stdinOnData == NULL) { - stdinOnData = wrenGetMethod(getVM(), "io", "Stdin", "onData_(_)"); + stdinOnData = wrenMakeCallHandle(vm, "onData_(_)"); } + + // If stdin was closed, send null to let io.wren know. + if (numRead == UV_EOF) + { + wrenEnsureSlots(vm, 2); + wrenSetSlotValue(vm, 0, stdinClass); + wrenSetSlotNull(vm, 1); + wrenCall(vm, stdinOnData); + + shutdownStdin(); + return; + } + + // TODO: Handle other errors. // TODO: Having to copy the bytes here is a drag. It would be good if Wren's // embedding API supported a way to *give* it bytes that were previously // allocated using Wren's own allocator. - wrenCall(getVM(), stdinOnData, NULL, "a", buffer->base, numRead); - + wrenEnsureSlots(vm, 2); + wrenSetSlotValue(vm, 0, stdinClass); + wrenSetSlotBytes(vm, 1, buffer->base, numRead); + wrenCall(vm, stdinOnData); + // TODO: Likewise, freeing this after we resume is lame. free(buffer->base); } @@ -284,7 +396,7 @@ void stdinReadStart(WrenVM* vm) stdinStream = (uv_stream_t*)handle; } } - + uv_read_start(stdinStream, allocCallback, stdinReadCallback); // TODO: Check return. } diff --git a/src/module/io.wren b/src/module/io.wren index f2c5d570..2bc967bd 100644 --- a/src/module/io.wren +++ b/src/module/io.wren @@ -1,5 +1,16 @@ import "scheduler" for Scheduler +class Directory { + static def list(path) { + if (!(path is String)) Fiber.abort("Path must be a string.") + + list_(path, Fiber.current) + return Scheduler.runNextScheduled_() + } + + foreign static def list_(path, fiber) +} + foreign class File { static def open(path) { if (!(path is String)) Fiber.abort("Path must be a string.") @@ -33,6 +44,13 @@ foreign class File { return Scheduler.runNextScheduled_() } + static def stat(path) { + if (!(path is String)) Fiber.abort("Path must be a string.") + + statPath_(path, Fiber.current) + return Stat.new_(Scheduler.runNextScheduled_()) + } + construct new_(fd) {} def close() { @@ -40,6 +58,8 @@ foreign class File { Scheduler.runNextScheduled_() } + foreign def descriptor + def isOpen { descriptor != -1 } def size { @@ -49,25 +69,48 @@ foreign class File { return Scheduler.runNextScheduled_() } - def readBytes(count) { + def readBytes(count) { readBytes(count, 0) } + + def readBytes(count, offset) { if (!isOpen) Fiber.abort("File is not open.") if (!(count is Num)) Fiber.abort("Count must be an integer.") if (!count.isInteger) Fiber.abort("Count must be an integer.") if (count < 0) Fiber.abort("Count cannot be negative.") - readBytes_(count, Fiber.current) + if (!(offset is Num)) Fiber.abort("Offset must be an integer.") + if (!offset.isInteger) Fiber.abort("Offset must be an integer.") + if (offset < 0) Fiber.abort("Offset cannot be negative.") + + readBytes_(count, offset, Fiber.current) return Scheduler.runNextScheduled_() } foreign static def open_(path, fiber) foreign static def sizePath_(path, fiber) + foreign static def statPath_(path, fiber) foreign def close_(fiber) - foreign def descriptor - foreign def readBytes_(count, fiber) + foreign def readBytes_(count, start, fiber) foreign def size_(fiber) } +class Stat { + construct new_(fields) { + _fields = fields + } + + def device { _fields[0] } + def inode { _fields[1] } + def mode { _fields[2] } + def linkCount { _fields[3] } + def user { _fields[4] } + def group { _fields[5] } + def specialDevice { _fields[6] } + def size { _fields[7] } + def blockSize { _fields[8] } + def blockCount { _fields[9] } +} + class Stdin { static def readLine() { if (__isClosed == true) { @@ -90,6 +133,7 @@ class Stdin { readStop_() if (__line != null) { + // Emit the last line. var line = __line __line = null if (__waitingFiber != null) __waitingFiber.transfer(line) diff --git a/src/module/io.wren.inc b/src/module/io.wren.inc index b5e6981c..8353c5ce 100644 --- a/src/module/io.wren.inc +++ b/src/module/io.wren.inc @@ -2,6 +2,17 @@ static const char* ioModuleSource = "import \"scheduler\" for Scheduler\n" "\n" +"class Directory {\n" +" static def list(path) {\n" +" if (!(path is String)) Fiber.abort(\"Path must be a string.\")\n" +"\n" +" list_(path, Fiber.current)\n" +" return Scheduler.runNextScheduled_()\n" +" }\n" +"\n" +" foreign static def list_(path, fiber)\n" +"}\n" +"\n" "foreign class File {\n" " static def open(path) {\n" " if (!(path is String)) Fiber.abort(\"Path must be a string.\")\n" @@ -35,6 +46,13 @@ static const char* ioModuleSource = " return Scheduler.runNextScheduled_()\n" " }\n" "\n" +" static def stat(path) {\n" +" if (!(path is String)) Fiber.abort(\"Path must be a string.\")\n" +"\n" +" statPath_(path, Fiber.current)\n" +" return Stat.new_(Scheduler.runNextScheduled_())\n" +" }\n" +"\n" " construct new_(fd) {}\n" "\n" " def close() {\n" @@ -42,6 +60,8 @@ static const char* ioModuleSource = " Scheduler.runNextScheduled_()\n" " }\n" "\n" +" foreign def descriptor\n" +"\n" " def isOpen { descriptor != -1 }\n" "\n" " def size {\n" @@ -51,25 +71,48 @@ static const char* ioModuleSource = " return Scheduler.runNextScheduled_()\n" " }\n" "\n" -" def readBytes(count) {\n" +" def readBytes(count) { readBytes(count, 0) }\n" +"\n" +" def readBytes(count, offset) {\n" " if (!isOpen) Fiber.abort(\"File is not open.\")\n" " if (!(count is Num)) Fiber.abort(\"Count must be an integer.\")\n" " if (!count.isInteger) Fiber.abort(\"Count must be an integer.\")\n" " if (count < 0) Fiber.abort(\"Count cannot be negative.\")\n" "\n" -" readBytes_(count, Fiber.current)\n" +" if (!(offset is Num)) Fiber.abort(\"Offset must be an integer.\")\n" +" if (!offset.isInteger) Fiber.abort(\"Offset must be an integer.\")\n" +" if (offset < 0) Fiber.abort(\"Offset cannot be negative.\")\n" +"\n" +" readBytes_(count, offset, Fiber.current)\n" " return Scheduler.runNextScheduled_()\n" " }\n" "\n" " foreign static def open_(path, fiber)\n" " foreign static def sizePath_(path, fiber)\n" +" foreign static def statPath_(path, fiber)\n" "\n" " foreign def close_(fiber)\n" -" foreign def descriptor\n" -" foreign def readBytes_(count, fiber)\n" +" foreign def readBytes_(count, start, fiber)\n" " foreign def size_(fiber)\n" "}\n" "\n" +"class Stat {\n" +" construct new_(fields) {\n" +" _fields = fields\n" +" }\n" +"\n" +" def device { _fields[0] }\n" +" def inode { _fields[1] }\n" +" def mode { _fields[2] }\n" +" def linkCount { _fields[3] }\n" +" def user { _fields[4] }\n" +" def group { _fields[5] }\n" +" def specialDevice { _fields[6] }\n" +" def size { _fields[7] }\n" +" def blockSize { _fields[8] }\n" +" def blockCount { _fields[9] }\n" +"}\n" +"\n" "class Stdin {\n" " static def readLine() {\n" " if (__isClosed == true) {\n" @@ -92,6 +135,7 @@ static const char* ioModuleSource = " readStop_()\n" "\n" " if (__line != null) {\n" +" // Emit the last line.\n" " var line = __line\n" " __line = null\n" " if (__waitingFiber != null) __waitingFiber.transfer(line)\n" diff --git a/src/module/scheduler.c b/src/module/scheduler.c index 20347eb8..28f4989f 100644 --- a/src/module/scheduler.c +++ b/src/module/scheduler.c @@ -7,30 +7,19 @@ #include "wren.h" #include "vm.h" +// A handle to the "Scheduler" class object. Used to call static methods on it. +static WrenValue* schedulerClass; + // This method resumes a fiber that is suspended waiting on an asynchronous // operation. The first resumes it with zero arguments, and the second passes // one. -static WrenValue* resume; -static WrenValue* resumeWithArg; +static WrenValue* resume1; +static WrenValue* resume2; static WrenValue* resumeError; -void schedulerCaptureMethods(WrenVM* vm) +static void resume(WrenValue* method) { - resume = wrenGetMethod(vm, "scheduler", "Scheduler", "resume_(_)"); - resumeWithArg = wrenGetMethod(vm, "scheduler", "Scheduler", "resume_(_,_)"); - resumeError = wrenGetMethod(vm, "scheduler", "Scheduler", "resumeError_(_,_)"); -} - -static void callResume(WrenValue* resumeMethod, WrenValue* fiber, - const char* argTypes, ...) -{ - va_list args; - va_start(args, argTypes); - WrenInterpretResult result = wrenCallVarArgs(getVM(), resumeMethod, NULL, - argTypes, args); - va_end(args); - - wrenReleaseValue(getVM(), fiber); + WrenInterpretResult result = wrenCall(getVM(), method); // If a runtime error occurs in response to an async operation and nothing // catches the error in the fiber, then exit the CLI. @@ -41,34 +30,50 @@ static void callResume(WrenValue* resumeMethod, WrenValue* fiber, } } -void schedulerResume(WrenValue* fiber) +void schedulerCaptureMethods(WrenVM* vm) { - callResume(resume, fiber, "v", fiber); + wrenEnsureSlots(vm, 1); + wrenGetVariable(vm, "scheduler", "Scheduler", 0); + schedulerClass = wrenGetSlotValue(vm, 0); + + resume1 = wrenMakeCallHandle(vm, "resume_(_)"); + resume2 = wrenMakeCallHandle(vm, "resume_(_,_)"); + resumeError = wrenMakeCallHandle(vm, "resumeError_(_,_)"); } -void schedulerResumeBytes(WrenValue* fiber, const char* bytes, size_t length) +void schedulerResume(WrenValue* fiber, bool hasArgument) { - callResume(resumeWithArg, fiber, "va", fiber, bytes, length); + WrenVM* vm = getVM(); + wrenEnsureSlots(vm, 2 + (hasArgument ? 1 : 0)); + wrenSetSlotValue(vm, 0, schedulerClass); + wrenSetSlotValue(vm, 1, fiber); + wrenReleaseValue(vm, fiber); + + // If we don't need to wait for an argument to be stored on the stack, resume + // it now. + if (!hasArgument) resume(resume1); } -void schedulerResumeDouble(WrenValue* fiber, double value) +void schedulerFinishResume() { - callResume(resumeWithArg, fiber, "vd", fiber, value); -} - -void schedulerResumeString(WrenValue* fiber, const char* text) -{ - callResume(resumeWithArg, fiber, "vs", fiber, text); + resume(resume2); } void schedulerResumeError(WrenValue* fiber, const char* error) { - callResume(resumeError, fiber, "vs", fiber, error); + schedulerResume(fiber, true); + wrenSetSlotString(getVM(), 2, error); + resume(resumeError); } void schedulerShutdown() { - if (resume != NULL) wrenReleaseValue(getVM(), resume); - if (resumeWithArg != NULL) wrenReleaseValue(getVM(), resumeWithArg); - if (resumeError != NULL) wrenReleaseValue(getVM(), resumeError); + // If the module was never loaded, we don't have anything to release. + if (schedulerClass == NULL) return; + + WrenVM* vm = getVM(); + wrenReleaseValue(vm, schedulerClass); + wrenReleaseValue(vm, resume1); + wrenReleaseValue(vm, resume2); + wrenReleaseValue(vm, resumeError); } diff --git a/src/module/scheduler.h b/src/module/scheduler.h index e90ce1e4..943d5780 100644 --- a/src/module/scheduler.h +++ b/src/module/scheduler.h @@ -3,10 +3,16 @@ #include "wren.h" -void schedulerResume(WrenValue* fiber); -void schedulerResumeBytes(WrenValue* fiber, const char* bytes, size_t length); -void schedulerResumeDouble(WrenValue* fiber, double value); -void schedulerResumeString(WrenValue* fiber, const char* text); +// Sets up the API stack to call one of the resume methods on Scheduler. +// +// If [hasArgument] is false, this just sets up the stack to have another +// argument stored in slot 2 and returns. The module must store the argument +// on the stack and then call [schedulerFinishResume] to complete the call. +// +// Otherwise, the call resumes immediately. Releases [fiber] when called. +void schedulerResume(WrenValue* fiber, bool hasArgument); + +void schedulerFinishResume(); void schedulerResumeError(WrenValue* fiber, const char* error); void schedulerShutdown(); diff --git a/src/module/timer.c b/src/module/timer.c index 43b8d662..406635f0 100644 --- a/src/module/timer.c +++ b/src/module/timer.c @@ -22,13 +22,13 @@ static void timerCallback(uv_timer_t* handle) uv_close((uv_handle_t*)handle, timerCloseCallback); // Run the fiber that was sleeping. - schedulerResume(fiber); + schedulerResume(fiber, false); } void timerStartTimer(WrenVM* vm) { - int milliseconds = (int)wrenGetArgumentDouble(vm, 1); - WrenValue* fiber = wrenGetArgumentValue(vm, 2); + int milliseconds = (int)wrenGetSlotDouble(vm, 1); + WrenValue* fiber = wrenGetSlotValue(vm, 2); // Store the fiber to resume when the timer completes. uv_timer_t* handle = (uv_timer_t*)malloc(sizeof(uv_timer_t)); diff --git a/src/optional/wren_opt_meta.c b/src/optional/wren_opt_meta.c index 435ab678..a4f922df 100644 --- a/src/optional/wren_opt_meta.c +++ b/src/optional/wren_opt_meta.c @@ -18,13 +18,11 @@ void metaCompile(WrenVM* vm) : AS_CLOSURE(callingFn)->fn->module; // Compile it. - ObjFn* fn = wrenCompile(vm, module, wrenGetArgumentString(vm, 1), false); - if (fn == NULL) return; + ObjFn* fn = wrenCompile(vm, module, wrenGetSlotString(vm, 1), false); // Return the result. We can't use the public API for this since we have a // bare ObjFn. - *vm->foreignStackStart = OBJ_VAL(fn); - vm->foreignStackStart = NULL; + vm->apiStack[0] = fn != NULL ? OBJ_VAL(fn) : NULL_VAL; } static WrenForeignMethodFn bindMetaForeignMethods(WrenVM* vm, diff --git a/src/optional/wren_opt_random.c b/src/optional/wren_opt_random.c index b02c7ca7..120f5850 100644 --- a/src/optional/wren_opt_random.c +++ b/src/optional/wren_opt_random.c @@ -45,7 +45,7 @@ static void randomAllocate(WrenVM* vm) static void randomSeed0(WrenVM* vm) { - Well512* well = (Well512*)wrenGetArgumentForeign(vm, 0); + Well512* well = (Well512*)wrenGetSlotForeign(vm, 0); srand((uint32_t)time(NULL)); for (int i = 0; i < 16; i++) @@ -56,9 +56,9 @@ static void randomSeed0(WrenVM* vm) static void randomSeed1(WrenVM* vm) { - Well512* well = (Well512*)wrenGetArgumentForeign(vm, 0); + Well512* well = (Well512*)wrenGetSlotForeign(vm, 0); - srand((uint32_t)wrenGetArgumentDouble(vm, 1)); + srand((uint32_t)wrenGetSlotDouble(vm, 1)); for (int i = 0; i < 16; i++) { well->state[i] = rand(); @@ -67,17 +67,17 @@ static void randomSeed1(WrenVM* vm) static void randomSeed16(WrenVM* vm) { - Well512* well = (Well512*)wrenGetArgumentForeign(vm, 0); + Well512* well = (Well512*)wrenGetSlotForeign(vm, 0); for (int i = 0; i < 16; i++) { - well->state[i] = (uint32_t)wrenGetArgumentDouble(vm, i + 1); + well->state[i] = (uint32_t)wrenGetSlotDouble(vm, i + 1); } } static void randomFloat(WrenVM* vm) { - Well512* well = (Well512*)wrenGetArgumentForeign(vm, 0); + Well512* well = (Well512*)wrenGetSlotForeign(vm, 0); // A double has 53 bits of precision in its mantissa, and we'd like to take // full advantage of that, so we need 53 bits of random source data. @@ -92,14 +92,14 @@ static void randomFloat(WrenVM* vm) // from 0 to 1.0 (half-inclusive). result /= 9007199254740992.0; - wrenReturnDouble(vm, result); + wrenSetSlotDouble(vm, 0, result); } static void randomInt0(WrenVM* vm) { - Well512* well = (Well512*)wrenGetArgumentForeign(vm, 0); + Well512* well = (Well512*)wrenGetSlotForeign(vm, 0); - wrenReturnDouble(vm, (double)advanceState(well)); + wrenSetSlotDouble(vm, 0, (double)advanceState(well)); } // TODO: The way these are wired up is pretty verbose and tedious. Also, the diff --git a/src/vm/wren_compiler.c b/src/vm/wren_compiler.c index f905c916..8c8cc7d8 100644 --- a/src/vm/wren_compiler.c +++ b/src/vm/wren_compiler.c @@ -1331,7 +1331,7 @@ static int resolveLocal(Compiler* compiler, const char* name, int length) // Adds an upvalue to [compiler]'s function with the given properties. Does not // add one if an upvalue for that variable is already in the list. Returns the -// index of the uvpalue. +// index of the upvalue. static int addUpvalue(Compiler* compiler, bool isLocal, int index) { // Look for an existing one. diff --git a/src/vm/wren_core.c b/src/vm/wren_core.c index 79ef463a..a5b38dd3 100644 --- a/src/vm/wren_core.c +++ b/src/vm/wren_core.c @@ -150,6 +150,8 @@ DEF_PRIMITIVE(fiber_suspend) { // Switching to a null fiber tells the interpreter to stop and exit. vm->fiber = NULL; + vm->apiStack = NULL; + return false; } diff --git a/src/vm/wren_debug.c b/src/vm/wren_debug.c index c44522ad..2d5d7063 100644 --- a/src/vm/wren_debug.c +++ b/src/vm/wren_debug.c @@ -20,6 +20,9 @@ void wrenDebugPrintStackTrace(ObjFiber* fiber) CallFrame* frame = &fiber->frames[i]; ObjFn* fn = wrenUpwrapClosure(frame->fn); + // Skip over stub functions for calling methods from the C API. + if (fn->module == NULL) continue; + // The built-in core module has no name. We explicitly omit it from stack // traces since we don't want to highlight to a user the implementation // detail of what part of the core module is written in C and what is Wren. @@ -50,7 +53,7 @@ static void dumpObject(Obj* obj) case OBJ_RANGE: printf("[range %p]", obj); break; case OBJ_STRING: printf("%s", ((ObjString*)obj)->value); break; case OBJ_UPVALUE: printf("[upvalue %p]", obj); break; - default: printf("[unknown object]"); break; + default: printf("[unknown object %d]", obj->type); break; } } diff --git a/src/vm/wren_value.c b/src/vm/wren_value.c index 1dfbd4f8..8daae9c1 100644 --- a/src/vm/wren_value.c +++ b/src/vm/wren_value.c @@ -157,7 +157,11 @@ ObjFiber* wrenNewFiber(WrenVM* vm, Obj* fn) // Allocate the arrays before the fiber in case it triggers a GC. CallFrame* frames = ALLOCATE_ARRAY(vm, CallFrame, INITIAL_CALL_FRAMES); - int stackCapacity = wrenPowerOf2Ceil(wrenUpwrapClosure(fn)->maxSlots); + // Add one slot for the unused implicit receiver slot that the compiler + // assumes all functions have. + int stackCapacity = fn == NULL + ? 1 + : wrenPowerOf2Ceil(wrenUpwrapClosure(fn)->maxSlots + 1); Value* stack = ALLOCATE_ARRAY(vm, Value, stackCapacity); ObjFiber* fiber = ALLOCATE(vm, ObjFiber); @@ -179,10 +183,52 @@ void wrenResetFiber(WrenVM* vm, ObjFiber* fiber, Obj* fn) fiber->caller = NULL; fiber->error = NULL_VAL; fiber->callerIsTrying = false; + fiber->numFrames = 0; // Initialize the first call frame. - fiber->numFrames = 0; - wrenAppendCallFrame(vm, fiber, fn, fiber->stack); + if (fn != NULL) wrenAppendCallFrame(vm, fiber, fn, fiber->stack); +} + +void wrenEnsureStack(WrenVM* vm, ObjFiber* fiber, int needed) +{ + if (fiber->stackCapacity >= needed) return; + + int capacity = wrenPowerOf2Ceil(needed); + + Value* oldStack = fiber->stack; + fiber->stack = (Value*)wrenReallocate(vm, fiber->stack, + sizeof(Value) * fiber->stackCapacity, + sizeof(Value) * capacity); + fiber->stackCapacity = capacity; + + // If the reallocation moves the stack, then we need to shift every pointer + // into the stack to point to its new location. + if (fiber->stack != oldStack) + { + // Top of the stack. + long offset = fiber->stack - oldStack; + + if (vm->apiStack >= oldStack && vm->apiStack <= fiber->stackTop) + { + vm->apiStack += offset; + } + + // Stack pointer for each call frame. + for (int i = 0; i < fiber->numFrames; i++) + { + fiber->frames[i].stackStart += offset; + } + + // Open upvalues. + for (ObjUpvalue* upvalue = fiber->openUpvalues; + upvalue != NULL; + upvalue = upvalue->next) + { + upvalue->value += offset; + } + + fiber->stackTop += offset; + } } ObjForeign* wrenNewForeign(WrenVM* vm, ObjClass* classObj, size_t size) @@ -389,6 +435,8 @@ static uint32_t hashValue(Value value) case VAL_OBJ: return hashObject(AS_OBJ(value)); default: UNREACHABLE(); } + + return 0; #endif } @@ -1163,10 +1211,10 @@ void wrenFreeObj(WrenVM* vm, Obj* obj) wrenValueBufferClear(vm, &((ObjModule*)obj)->variables); break; - case OBJ_STRING: case OBJ_CLOSURE: case OBJ_INSTANCE: case OBJ_RANGE: + case OBJ_STRING: case OBJ_UPVALUE: break; } diff --git a/src/vm/wren_value.h b/src/vm/wren_value.h index d901747c..322ba011 100644 --- a/src/vm/wren_value.h +++ b/src/vm/wren_value.h @@ -74,6 +74,7 @@ #define IS_FN(value) (wrenIsObjType(value, OBJ_FN)) // ObjFn #define IS_FOREIGN(value) (wrenIsObjType(value, OBJ_FOREIGN)) // ObjForeign #define IS_INSTANCE(value) (wrenIsObjType(value, OBJ_INSTANCE)) // ObjInstance +#define IS_LIST(value) (wrenIsObjType(value, OBJ_LIST)) // ObjList #define IS_RANGE(value) (wrenIsObjType(value, OBJ_RANGE)) // ObjRange #define IS_STRING(value) (wrenIsObjType(value, OBJ_STRING)) // ObjString @@ -662,6 +663,9 @@ static inline void wrenAppendCallFrame(WrenVM* vm, ObjFiber* fiber, frame->ip = wrenUpwrapClosure(frame->fn)->bytecode; } +// Ensures [fiber]'s stack has at least [needed] slots. +void wrenEnsureStack(WrenVM* vm, ObjFiber* fiber, int needed); + ObjForeign* wrenNewForeign(WrenVM* vm, ObjClass* classObj, size_t size); // TODO: The argument list here is getting a bit gratuitous. diff --git a/src/vm/wren_vm.c b/src/vm/wren_vm.c index 67fcc82f..ea93f4b3 100644 --- a/src/vm/wren_vm.c +++ b/src/vm/wren_vm.c @@ -65,7 +65,7 @@ WrenVM* wrenNewVM(WrenConfiguration* config) vm->modules = wrenNewMap(vm); wrenInitializeCore(vm); - + // TODO: Lazy load these. #if WREN_OPT_META wrenLoadMetaModule(vm); @@ -137,7 +137,7 @@ void wrenCollectGarbage(WrenVM* vm) wrenGrayObj(vm, vm->tempRoots[i]); } - // The current fiber. + // The rooted fiber. wrenGrayObj(vm, (Obj*)vm->fiber); // The value handles. @@ -348,19 +348,14 @@ static void bindMethod(WrenVM* vm, int methodType, int symbol, static void callForeign(WrenVM* vm, ObjFiber* fiber, WrenForeignMethodFn foreign, int numArgs) { - vm->foreignStackStart = fiber->stackTop - numArgs; + vm->apiStack = fiber->stackTop - numArgs; + foreign(vm); - // Discard the stack slots for the arguments (but leave one for - // the result). - fiber->stackTop -= numArgs - 1; - - // If nothing was returned, implicitly return null. - if (vm->foreignStackStart != NULL) - { - *vm->foreignStackStart = NULL_VAL; - vm->foreignStackStart = NULL; - } + // Discard the stack slots for the arguments and temporaries but leave one + // for the result. + fiber->stackTop = vm->apiStack + 1; + vm->apiStack = NULL; } // Handles the current fiber having aborted because of an error. Switches to @@ -469,41 +464,8 @@ static inline void callFunction( // Grow the stack if needed. int stackSize = (int)(fiber->stackTop - fiber->stack); int needed = stackSize + wrenUpwrapClosure(function)->maxSlots; + wrenEnsureStack(vm, fiber, needed); - if (fiber->stackCapacity < needed) - { - int capacity = wrenPowerOf2Ceil(needed); - - Value* oldStack = fiber->stack; - fiber->stack = (Value*)wrenReallocate(vm, fiber->stack, - sizeof(Value) * fiber->stackCapacity, - sizeof(Value) * capacity); - fiber->stackCapacity = capacity; - - // If the reallocation moves the stack, then we need to shift every pointer - // into the stack to point to its new location. - if (fiber->stack != oldStack) - { - // Top of the stack. - long offset = fiber->stack - oldStack; - fiber->stackTop += offset; - - // Stack pointer for each call frame. - for (int i = 0; i < fiber->numFrames; i++) - { - fiber->frames[i].stackStart += offset; - } - - // Open upvalues. - for (ObjUpvalue* upvalue = fiber->openUpvalues; - upvalue != NULL; - upvalue = upvalue->next) - { - upvalue->value += offset; - } - } - } - wrenAppendCallFrame(vm, fiber, function, fiber->stackTop - numArgs); } @@ -689,7 +651,7 @@ static void bindForeignClass(WrenVM* vm, ObjClass* classObj, ObjModule* module) if (methods.finalize != NULL) { - method.fn.foreign = methods.finalize; + method.fn.foreign = (WrenForeignMethodFn)methods.finalize; wrenBindMethod(vm, classObj, symbol, method); } } @@ -742,7 +704,7 @@ static void createForeign(WrenVM* vm, ObjFiber* fiber, Value* stack) ASSERT(method->type == METHOD_FOREIGN, "Allocator should be foreign."); // Pass the constructor arguments to the allocator as well. - vm->foreignStackStart = stack; + vm->apiStack = stack; method->fn.foreign(vm); @@ -767,11 +729,8 @@ void wrenFinalizeForeign(WrenVM* vm, ObjForeign* foreign) ASSERT(method->type == METHOD_FOREIGN, "Finalizer should be foreign."); - // Pass the constructor arguments to the allocator as well. - Value slot = OBJ_VAL(foreign); - vm->foreignStackStart = &slot; - - method->fn.foreign(vm); + WrenFinalizerFn finalizer = (WrenFinalizerFn)method->fn.foreign; + finalizer(foreign->data); } // The main bytecode interpreter loop. This is where the magic happens. It is @@ -1179,18 +1138,23 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber) // If the fiber is complete, end it. if (fiber->numFrames == 0) { - // See if there's another fiber to return to. - ObjFiber* callingFiber = fiber->caller; + // See if there's another fiber to return to. If not, we're done. + if (fiber->caller == NULL) + { + // Store the final result value at the beginning of the stack so the + // C API can get it. + fiber->stack[0] = result; + fiber->stackTop = fiber->stack + 1; + return WREN_RESULT_SUCCESS; + } + + ObjFiber* resumingFiber = fiber->caller; fiber->caller = NULL; + fiber = resumingFiber; + vm->fiber = resumingFiber; - fiber = callingFiber; - vm->fiber = fiber; - - // If not, we're done. - if (fiber == NULL) return WREN_RESULT_SUCCESS; - // Store the result in the resuming fiber. - *(fiber->stackTop - 1) = result; + fiber->stackTop[-1] = result; } else { @@ -1202,7 +1166,7 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber) // result). fiber->stackTop = frame->stackStart + 1; } - + LOAD_FRAME(); DISPATCH(); } @@ -1330,11 +1294,10 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber) #undef READ_SHORT } -// Creates an [ObjFn] that invokes a method with [signature] when called. -static ObjFn* makeCallStub(WrenVM* vm, ObjModule* module, const char* signature) +WrenValue* wrenMakeCallHandle(WrenVM* vm, const char* signature) { int signatureLength = (int)strlen(signature); - + // Count the number parameters the method expects. int numParams = 0; if (signature[signatureLength - 1] == ')') @@ -1345,167 +1308,73 @@ static ObjFn* makeCallStub(WrenVM* vm, ObjModule* module, const char* signature) if (*s == '_') numParams++; } } - + + // Add the signatue to the method table. int method = wrenSymbolTableEnsure(vm, &vm->methodNames, signature, signatureLength); - + + // Create a little stub function that assumes the arguments are on the stack + // and calls the method. uint8_t* bytecode = ALLOCATE_ARRAY(vm, uint8_t, 5); bytecode[0] = (uint8_t)(CODE_CALL_0 + numParams); bytecode[1] = (method >> 8) & 0xff; bytecode[2] = method & 0xff; bytecode[3] = CODE_RETURN; bytecode[4] = CODE_END; - + int* debugLines = ALLOCATE_ARRAY(vm, int, 5); memset(debugLines, 1, 5); + + ObjFn* fn = wrenNewFunction(vm, NULL, NULL, 0, 0, numParams + 1, 0, bytecode, + 5, signature, signatureLength, debugLines); - return wrenNewFunction(vm, module, NULL, 0, 0, numParams + 1, 0, bytecode, 5, - signature, signatureLength, debugLines); + // Wrap the function in a handle. + return wrenCaptureValue(vm, OBJ_VAL(fn)); } -WrenValue* wrenGetMethod(WrenVM* vm, const char* module, const char* variable, - const char* signature) +WrenInterpretResult wrenCall(WrenVM* vm, WrenValue* method) { - Value moduleName = wrenStringFormat(vm, "$", module); - wrenPushRoot(vm, AS_OBJ(moduleName)); - - ObjModule* moduleObj = getModule(vm, moduleName); - // TODO: Handle module not being found. - - int variableSlot = wrenSymbolTableFind(&moduleObj->variableNames, - variable, strlen(variable)); - // TODO: Handle the variable not being found. - - ObjFn* fn = makeCallStub(vm, moduleObj, signature); - wrenPushRoot(vm, (Obj*)fn); - - // Create a single fiber that we can reuse each time the method is invoked. - ObjFiber* fiber = wrenNewFiber(vm, (Obj*)fn); - wrenPushRoot(vm, (Obj*)fiber); - - // Create a handle that keeps track of the function that calls the method. - WrenValue* method = wrenCaptureValue(vm, OBJ_VAL(fiber)); - - // Store the receiver in the fiber's stack so we can use it later in the call. - *fiber->stackTop++ = moduleObj->variables.data[variableSlot]; - - wrenPopRoot(vm); // fiber. - wrenPopRoot(vm); // fn. - wrenPopRoot(vm); // moduleName. - - return method; -} - -WrenInterpretResult wrenCall(WrenVM* vm, WrenValue* method, - WrenValue** returnValue, - const char* argTypes, ...) -{ - va_list args; - va_start(args, argTypes); - WrenInterpretResult result = wrenCallVarArgs(vm, method, returnValue, - argTypes, args); - va_end(args); - - return result; -} - -WrenInterpretResult wrenCallVarArgs(WrenVM* vm, WrenValue* method, - WrenValue** returnValue, - const char* argTypes, va_list args) -{ - // TODO: Validate that the number of arguments matches what the method - // expects. - - ASSERT(IS_FIBER(method->value), "Value must come from wrenGetMethod()."); - ObjFiber* fiber = AS_FIBER(method->value); - - // Push the arguments. - for (const char* argType = argTypes; *argType != '\0'; argType++) - { - Value value = NULL_VAL; - switch (*argType) - { - case 'a': - { - const char* bytes = va_arg(args, const char*); - int length = va_arg(args, int); - value = wrenNewString(vm, bytes, (size_t)length); - break; - } - - case 'b': value = BOOL_VAL(va_arg(args, int)); break; - case 'd': value = NUM_VAL(va_arg(args, double)); break; - case 'i': value = NUM_VAL((double)va_arg(args, int)); break; - case 'n': value = NULL_VAL; va_arg(args, void*); break; - case 's': - value = wrenStringFormat(vm, "$", va_arg(args, const char*)); - break; - - case 'v': - { - // Allow a NULL value pointer for Wren null. - WrenValue* wrenValue = va_arg(args, WrenValue*); - if (wrenValue != NULL) value = wrenValue->value; - break; - } - - default: - ASSERT(false, "Unknown argument type."); - break; - } - - *fiber->stackTop++ = value; - } - - Value receiver = fiber->stack[0]; - Obj* fn = fiber->frames[0].fn; - wrenPushRoot(vm, (Obj*)fn); - - WrenInterpretResult result = runInterpreter(vm, fiber); - - if (result == WREN_RESULT_SUCCESS) - { - if (returnValue != NULL) - { - // Make sure the return value doesn't get collected while capturing it. - fiber->stackTop++; - *returnValue = wrenCaptureValue(vm, fiber->stack[0]); - } - - // Reset the fiber to get ready for the next call. - wrenResetFiber(vm, fiber, fn); - - // Push the receiver back on the stack. - *fiber->stackTop++ = receiver; - } - else - { - if (returnValue != NULL) *returnValue = NULL; - } - - wrenPopRoot(vm); - - return result; + ASSERT(method != NULL, "Method cannot be NULL."); + ASSERT(IS_FN(method->value), "Method must be a method handle."); + ASSERT(vm->fiber != NULL, "Must set up arguments for call first."); + ASSERT(vm->apiStack != NULL, "Must set up arguments for call first."); + ASSERT(vm->fiber->numFrames == 0, "Can not call from a foreign method."); + + ObjFn* fn = AS_FN(method->value); + + ASSERT(vm->fiber->stackTop - vm->fiber->stack >= fn->arity, + "Stack must have enough arguments for method."); + + // Discard any extra temporary slots. We take for granted that the stub + // function has exactly one slot for each arguments. + vm->fiber->stackTop = &vm->fiber->stack[fn->maxSlots]; + + callFunction(vm, vm->fiber, (Obj*)fn, 0); + return runInterpreter(vm, vm->fiber); } WrenValue* wrenCaptureValue(WrenVM* vm, Value value) { + if (IS_OBJ(value)) wrenPushRoot(vm, AS_OBJ(value)); + // Make a handle for it. WrenValue* wrappedValue = ALLOCATE(vm, WrenValue); wrappedValue->value = value; + if (IS_OBJ(value)) wrenPopRoot(vm); + // Add it to the front of the linked list of handles. if (vm->valueHandles != NULL) vm->valueHandles->prev = wrappedValue; wrappedValue->prev = NULL; wrappedValue->next = vm->valueHandles; vm->valueHandles = wrappedValue; - + return wrappedValue; } void wrenReleaseValue(WrenVM* vm, WrenValue* value) { - ASSERT(value != NULL, "NULL value."); + ASSERT(value != NULL, "Value cannot be NULL."); // Update the VM's head pointer if we're releasing the first handle. if (vm->valueHandles == value) vm->valueHandles = value->next; @@ -1524,14 +1393,14 @@ void wrenReleaseValue(WrenVM* vm, WrenValue* value) void* wrenAllocateForeign(WrenVM* vm, size_t size) { - ASSERT(vm->foreignStackStart != NULL, "Must be in foreign call."); + ASSERT(vm->apiStack != NULL, "Must be in foreign call."); // TODO: Validate this. It can fail if the user calls this inside another // foreign method, or calls one of the return functions. - ObjClass* classObj = AS_CLASS(vm->foreignStackStart[0]); + ObjClass* classObj = AS_CLASS(vm->apiStack[0]); ObjForeign* foreign = wrenNewForeign(vm, classObj, size); - vm->foreignStackStart[0] = OBJ_VAL(foreign); + vm->apiStack[0] = OBJ_VAL(foreign); return (void*)foreign->data; } @@ -1658,100 +1527,170 @@ void wrenPopRoot(WrenVM* vm) vm->numTempRoots--; } -int wrenGetArgumentCount(WrenVM* vm) +int wrenGetSlotCount(WrenVM* vm) { - ASSERT(vm->foreignStackStart != NULL, "Must be in foreign call."); + if (vm->apiStack == NULL) return 0; - // If no fiber is executing, we must be in a finalizer, in which case the - // "stack" just has one object, the object being finalized. - if (vm->fiber == NULL) return 1; - - return (int)(vm->fiber->stackTop - vm->foreignStackStart); + return (int)(vm->fiber->stackTop - vm->apiStack); } -static void validateForeignArgument(WrenVM* vm, int index) +void wrenEnsureSlots(WrenVM* vm, int numSlots) { - ASSERT(vm->foreignStackStart != NULL, "Must be in foreign call."); - ASSERT(index >= 0, "index cannot be negative."); - ASSERT(index < wrenGetArgumentCount(vm), "Not that many arguments."); + // If we don't have a fiber accessible, create one for the API to use. + if (vm->apiStack == NULL) + { + vm->fiber = wrenNewFiber(vm, NULL); + vm->apiStack = vm->fiber->stack; + } + + int currentSize = (int)(vm->fiber->stackTop - vm->apiStack); + if (currentSize >= numSlots) return; + + // Grow the stack if needed. + int needed = (int)(vm->apiStack - vm->fiber->stack) + numSlots; + wrenEnsureStack(vm, vm->fiber, needed); + + vm->fiber->stackTop = vm->apiStack + numSlots; } -bool wrenGetArgumentBool(WrenVM* vm, int index) +// Ensures that [slot] is a valid index into the API's stack of slots. +static void validateApiSlot(WrenVM* vm, int slot) { - validateForeignArgument(vm, index); - - if (!IS_BOOL(vm->foreignStackStart[index])) return false; - - return AS_BOOL(vm->foreignStackStart[index]); + ASSERT(slot >= 0, "Slot cannot be negative."); + ASSERT(slot < wrenGetSlotCount(vm), "Not that many slots."); } -double wrenGetArgumentDouble(WrenVM* vm, int index) +bool wrenGetSlotBool(WrenVM* vm, int slot) { - validateForeignArgument(vm, index); + validateApiSlot(vm, slot); + ASSERT(IS_BOOL(vm->apiStack[slot]), "Slot must hold a bool."); - if (!IS_NUM(vm->foreignStackStart[index])) return 0.0; - - return AS_NUM(vm->foreignStackStart[index]); + return AS_BOOL(vm->apiStack[slot]); } -void* wrenGetArgumentForeign(WrenVM* vm, int index) +const char* wrenGetSlotBytes(WrenVM* vm, int slot, int* length) { - validateForeignArgument(vm, index); - - if (!IS_FOREIGN(vm->foreignStackStart[index])) return NULL; - - return AS_FOREIGN(vm->foreignStackStart[index])->data; + validateApiSlot(vm, slot); + ASSERT(IS_STRING(vm->apiStack[slot]), "Slot must hold a string."); + + ObjString* string = AS_STRING(vm->apiStack[slot]); + *length = string->length; + return string->value; } -const char* wrenGetArgumentString(WrenVM* vm, int index) +double wrenGetSlotDouble(WrenVM* vm, int slot) { - validateForeignArgument(vm, index); + validateApiSlot(vm, slot); + ASSERT(IS_NUM(vm->apiStack[slot]), "Slot must hold a number."); - if (!IS_STRING(vm->foreignStackStart[index])) return NULL; - - return AS_CSTRING(vm->foreignStackStart[index]); + return AS_NUM(vm->apiStack[slot]); } -WrenValue* wrenGetArgumentValue(WrenVM* vm, int index) +void* wrenGetSlotForeign(WrenVM* vm, int slot) { - validateForeignArgument(vm, index); + validateApiSlot(vm, slot); + ASSERT(IS_FOREIGN(vm->apiStack[slot]), + "Slot must hold a foreign instance."); - return wrenCaptureValue(vm, vm->foreignStackStart[index]); + return AS_FOREIGN(vm->apiStack[slot])->data; } -void wrenReturnBool(WrenVM* vm, bool value) +const char* wrenGetSlotString(WrenVM* vm, int slot) { - ASSERT(vm->foreignStackStart != NULL, "Must be in foreign call."); + validateApiSlot(vm, slot); + ASSERT(IS_STRING(vm->apiStack[slot]), "Slot must hold a string."); - *vm->foreignStackStart = BOOL_VAL(value); - vm->foreignStackStart = NULL; + return AS_CSTRING(vm->apiStack[slot]); } -void wrenReturnDouble(WrenVM* vm, double value) +WrenValue* wrenGetSlotValue(WrenVM* vm, int slot) { - ASSERT(vm->foreignStackStart != NULL, "Must be in foreign call."); - - *vm->foreignStackStart = NUM_VAL(value); - vm->foreignStackStart = NULL; + validateApiSlot(vm, slot); + return wrenCaptureValue(vm, vm->apiStack[slot]); } -void wrenReturnString(WrenVM* vm, const char* text, int length) +// Stores [value] in [slot] in the foreign call stack. +static void setSlot(WrenVM* vm, int slot, Value value) +{ + validateApiSlot(vm, slot); + vm->apiStack[slot] = value; +} + +void wrenSetSlotBool(WrenVM* vm, int slot, bool value) +{ + setSlot(vm, slot, BOOL_VAL(value)); +} + +void wrenSetSlotBytes(WrenVM* vm, int slot, const char* bytes, size_t length) +{ + ASSERT(bytes != NULL, "Byte array cannot be NULL."); + setSlot(vm, slot, wrenNewString(vm, bytes, length)); +} + +void wrenSetSlotDouble(WrenVM* vm, int slot, double value) +{ + setSlot(vm, slot, NUM_VAL(value)); +} + +void wrenSetSlotNewList(WrenVM* vm, int slot) +{ + setSlot(vm, slot, OBJ_VAL(wrenNewList(vm, 0))); +} + +void wrenSetSlotNull(WrenVM* vm, int slot) +{ + setSlot(vm, slot, NULL_VAL); +} + +void wrenSetSlotString(WrenVM* vm, int slot, const char* text) { - ASSERT(vm->foreignStackStart != NULL, "Must be in foreign call."); ASSERT(text != NULL, "String cannot be NULL."); - - size_t size = length; - if (length == -1) size = strlen(text); - - *vm->foreignStackStart = wrenNewString(vm, text, size); - vm->foreignStackStart = NULL; + setSlot(vm, slot, wrenNewString(vm, text, strlen(text))); } -void wrenReturnValue(WrenVM* vm, WrenValue* value) +void wrenSetSlotValue(WrenVM* vm, int slot, WrenValue* value) { - ASSERT(vm->foreignStackStart != NULL, "Must be in foreign call."); ASSERT(value != NULL, "Value cannot be NULL."); - *vm->foreignStackStart = value->value; - vm->foreignStackStart = NULL; + setSlot(vm, slot, value->value); +} + +void wrenInsertInList(WrenVM* vm, int listSlot, int index, int elementSlot) +{ + validateApiSlot(vm, listSlot); + validateApiSlot(vm, elementSlot); + ASSERT(IS_LIST(vm->apiStack[listSlot]), "Must insert into a list."); + + ObjList* list = AS_LIST(vm->apiStack[listSlot]); + + // Negative indices count from the end. + if (index < 0) index = list->elements.count + 1 + index; + + ASSERT(index <= list->elements.count, "Index out of bounds."); + + wrenListInsert(vm, list, vm->apiStack[elementSlot], index); +} + +// TODO: Maybe just have this always return a WrenValue* instead of having to +// deal with slots? +void wrenGetVariable(WrenVM* vm, const char* module, const char* name, + int slot) +{ + ASSERT(module != NULL, "Module cannot be NULL."); + ASSERT(module != NULL, "Variable name cannot be NULL."); + validateApiSlot(vm, slot); + + Value moduleName = wrenStringFormat(vm, "$", module); + wrenPushRoot(vm, AS_OBJ(moduleName)); + + ObjModule* moduleObj = getModule(vm, moduleName); + ASSERT(moduleObj != NULL, "Could not find module."); + + wrenPopRoot(vm); // moduleName. + + int variableSlot = wrenSymbolTableFind(&moduleObj->variableNames, + name, strlen(name)); + ASSERT(variableSlot != -1, "Could not find variable."); + + setSlot(vm, slot, moduleObj->variables.data[variableSlot]); } diff --git a/src/vm/wren_vm.h b/src/vm/wren_vm.h index 1d0b0776..2b9d8d01 100644 --- a/src/vm/wren_vm.h +++ b/src/vm/wren_vm.h @@ -84,11 +84,14 @@ struct WrenVM // NULL if there are no handles. WrenValue* valueHandles; - // Foreign function data: - - // During a foreign function call, this will point to the first argument (the - // receiver) of the call on the fiber's stack. - Value* foreignStackStart; + // Pointer to the bottom of the range of stack slots available for use from + // the C API. During a foreign method, this will be in the stack of the fiber + // that is executing a method. + // + // If not in a foreign method, this is initially NULL. If the user requests + // slots by calling wrenEnsureSlots(), a stack is created and this is + // initialized. + Value* apiStack; WrenConfiguration config; diff --git a/test/api/benchmark.c b/test/api/benchmark.c index 5c7f4ccb..672e97ba 100644 --- a/test/api/benchmark.c +++ b/test/api/benchmark.c @@ -1,21 +1,81 @@ #include +#include #include "benchmark.h" static void arguments(WrenVM* vm) { double result = 0; - result += wrenGetArgumentDouble(vm, 1); - result += wrenGetArgumentDouble(vm, 2); - result += wrenGetArgumentDouble(vm, 3); - result += wrenGetArgumentDouble(vm, 4); + + result += wrenGetSlotDouble(vm, 1); + result += wrenGetSlotDouble(vm, 2); + result += wrenGetSlotDouble(vm, 3); + result += wrenGetSlotDouble(vm, 4); + + wrenSetSlotDouble(vm, 0, result); +} + +const char* testScript = +"class Test {\n" +" static method(a, b, c, d) { a + b + c + d }\n" +"}\n"; + +static void call(WrenVM* vm) +{ + int iterations = (int)wrenGetSlotDouble(vm, 1); - wrenReturnDouble(vm, result); + // Since the VM is not re-entrant, we can't call from within this foreign + // method. Instead, make a new VM to run the call test in. + WrenConfiguration config; + wrenInitConfiguration(&config); + WrenVM* otherVM = wrenNewVM(&config); + + wrenInterpret(otherVM, testScript); + + WrenValue* method = wrenMakeCallHandle(otherVM, "method(_,_,_,_)"); + + wrenEnsureSlots(otherVM, 1); + wrenGetVariable(otherVM, "main", "Test", 0); + WrenValue* testClass = wrenGetSlotValue(otherVM, 0); + + double startTime = (double)clock() / CLOCKS_PER_SEC; + + double result = 0; + for (int i = 0; i < iterations; i++) + { + wrenEnsureSlots(otherVM, 5); + wrenSetSlotValue(otherVM, 0, testClass); + wrenSetSlotDouble(otherVM, 1, 1.0); + wrenSetSlotDouble(otherVM, 2, 2.0); + wrenSetSlotDouble(otherVM, 3, 3.0); + wrenSetSlotDouble(otherVM, 4, 4.0); + + wrenCall(otherVM, method); + + result += wrenGetSlotDouble(otherVM, 0); + } + + double elapsed = (double)clock() / CLOCKS_PER_SEC - startTime; + + wrenReleaseValue(otherVM, testClass); + wrenReleaseValue(otherVM, method); + wrenFreeVM(otherVM); + + if (result == (1.0 + 2.0 + 3.0 + 4.0) * iterations) + { + wrenSetSlotDouble(vm, 0, elapsed); + } + else + { + // Got the wrong result. + wrenSetSlotBool(vm, 0, false); + } } WrenForeignMethodFn benchmarkBindMethod(const char* signature) { if (strcmp(signature, "static Benchmark.arguments(_,_,_,_)") == 0) return arguments; - + if (strcmp(signature, "static Benchmark.call(_)") == 0) return call; + return NULL; } diff --git a/test/api/call.c b/test/api/call.c index 5207a456..d119dde9 100644 --- a/test/api/call.c +++ b/test/api/call.c @@ -5,33 +5,84 @@ void callRunTests(WrenVM* vm) { - WrenValue* noParams = wrenGetMethod(vm, "main", "Call", "noParams"); - WrenValue* zero = wrenGetMethod(vm, "main", "Call", "zero()"); - WrenValue* one = wrenGetMethod(vm, "main", "Call", "one(_)"); - WrenValue* two = wrenGetMethod(vm, "main", "Call", "two(_,_)"); + wrenEnsureSlots(vm, 1); + wrenGetVariable(vm, "main", "Call", 0); + WrenValue* callClass = wrenGetSlotValue(vm, 0); + + WrenValue* noParams = wrenMakeCallHandle(vm, "noParams"); + WrenValue* zero = wrenMakeCallHandle(vm, "zero()"); + WrenValue* one = wrenMakeCallHandle(vm, "one(_)"); + WrenValue* two = wrenMakeCallHandle(vm, "two(_,_)"); // Different arity. - wrenCall(vm, noParams, NULL, ""); - wrenCall(vm, zero, NULL, ""); - wrenCall(vm, one, NULL, "i", 1); - wrenCall(vm, two, NULL, "ii", 1, 2); - - WrenValue* getValue = wrenGetMethod(vm, "main", "Call", "getValue(_)"); + wrenEnsureSlots(vm, 1); + wrenSetSlotValue(vm, 0, callClass); + wrenCall(vm, noParams); + + wrenEnsureSlots(vm, 1); + wrenSetSlotValue(vm, 0, callClass); + wrenCall(vm, zero); + + wrenEnsureSlots(vm, 2); + wrenSetSlotValue(vm, 0, callClass); + wrenSetSlotDouble(vm, 1, 1.0); + wrenCall(vm, one); + + wrenEnsureSlots(vm, 3); + wrenSetSlotValue(vm, 0, callClass); + wrenSetSlotDouble(vm, 1, 1.0); + wrenSetSlotDouble(vm, 2, 2.0); + wrenCall(vm, two); // Returning a value. - WrenValue* value = NULL; - wrenCall(vm, getValue, &value, "v", NULL); + WrenValue* getValue = wrenMakeCallHandle(vm, "getValue()"); + wrenEnsureSlots(vm, 1); + wrenSetSlotValue(vm, 0, callClass); + wrenCall(vm, getValue); + WrenValue* value = wrenGetSlotValue(vm, 0); // Different argument types. - wrenCall(vm, two, NULL, "bb", true, false); - wrenCall(vm, two, NULL, "dd", 1.2, 3.4); - wrenCall(vm, two, NULL, "ii", 3, 4); - wrenCall(vm, two, NULL, "ss", "string", "another"); - wrenCall(vm, two, NULL, "vv", NULL, value); + wrenEnsureSlots(vm, 3); + wrenSetSlotValue(vm, 0, callClass); + wrenSetSlotBool(vm, 1, true); + wrenSetSlotBool(vm, 2, false); + wrenCall(vm, two); + + wrenEnsureSlots(vm, 3); + wrenSetSlotValue(vm, 0, callClass); + wrenSetSlotDouble(vm, 1, 1.2); + wrenSetSlotDouble(vm, 2, 3.4); + wrenCall(vm, two); + + wrenEnsureSlots(vm, 3); + wrenSetSlotValue(vm, 0, callClass); + wrenSetSlotString(vm, 1, "string"); + wrenSetSlotString(vm, 2, "another"); + wrenCall(vm, two); + + wrenEnsureSlots(vm, 3); + wrenSetSlotValue(vm, 0, callClass); + wrenSetSlotNull(vm, 1); + wrenSetSlotValue(vm, 2, value); + wrenCall(vm, two); // Truncate a string, or allow null bytes. - wrenCall(vm, two, NULL, "aa", "string", 3, "b\0y\0t\0e", 7); + wrenEnsureSlots(vm, 3); + wrenSetSlotValue(vm, 0, callClass); + wrenSetSlotBytes(vm, 1, "string", 3); + wrenSetSlotBytes(vm, 2, "b\0y\0t\0e", 7); + wrenCall(vm, two); + // Call ignores with extra temporary slots on stack. + wrenEnsureSlots(vm, 10); + wrenSetSlotValue(vm, 0, callClass); + for (int i = 1; i < 10; i++) + { + wrenSetSlotDouble(vm, i, i * 0.1); + } + wrenCall(vm, one); + + wrenReleaseValue(vm, callClass); wrenReleaseValue(vm, noParams); wrenReleaseValue(vm, zero); wrenReleaseValue(vm, one); diff --git a/test/api/call.wren b/test/api/call.wren index a7ee97ff..89f603a4 100644 --- a/test/api/call.wren +++ b/test/api/call.wren @@ -20,13 +20,7 @@ class Call { System.print("two %(one) %(two)") } - static def getValue(value) { - // Return a new value if we aren't given one. - if (value == null) return ["a", "b"] - - // Otherwise print it. - System.print(value) - } + static def getValue() { ["a", "b"] } } // expect: noParams @@ -36,7 +30,7 @@ class Call { // expect: two true false // expect: two 1.2 3.4 -// expect: two 3 4 // expect: two string another // expect: two null [a, b] // expect: two str [98, 0, 121, 0, 116, 0, 101] +// expect: one 0.1 diff --git a/test/api/foreign_class.c b/test/api/foreign_class.c index cf9fbfeb..f77bf17d 100644 --- a/test/api/foreign_class.c +++ b/test/api/foreign_class.c @@ -7,7 +7,7 @@ static int finalized = 0; static void apiFinalized(WrenVM* vm) { - wrenReturnDouble(vm, finalized); + wrenSetSlotDouble(vm, 0, finalized); } static void counterAllocate(WrenVM* vm) @@ -18,25 +18,25 @@ static void counterAllocate(WrenVM* vm) static void counterIncrement(WrenVM* vm) { - double* value = (double*)wrenGetArgumentForeign(vm, 0); - double increment = wrenGetArgumentDouble(vm, 1); + double* value = (double*)wrenGetSlotForeign(vm, 0); + double increment = wrenGetSlotDouble(vm, 1); *value += increment; } static void counterValue(WrenVM* vm) { - double value = *(double*)wrenGetArgumentForeign(vm, 0); - wrenReturnDouble(vm, value); + double value = *(double*)wrenGetSlotForeign(vm, 0); + wrenSetSlotDouble(vm, 0, value); } static void pointAllocate(WrenVM* vm) { double* coordinates = (double*)wrenAllocateForeign(vm, sizeof(double[3])); - // This gets called by both constructors, so sniff the argument count to see + // This gets called by both constructors, so sniff the slot count to see // which one was invoked. - if (wrenGetArgumentCount(vm) == 1) + if (wrenGetSlotCount(vm) == 1) { coordinates[0] = 0.0; coordinates[1] = 0.0; @@ -44,36 +44,41 @@ static void pointAllocate(WrenVM* vm) } else { - coordinates[0] = wrenGetArgumentDouble(vm, 1); - coordinates[1] = wrenGetArgumentDouble(vm, 2); - coordinates[2] = wrenGetArgumentDouble(vm, 3); + coordinates[0] = wrenGetSlotDouble(vm, 1); + coordinates[1] = wrenGetSlotDouble(vm, 2); + coordinates[2] = wrenGetSlotDouble(vm, 3); } } static void pointTranslate(WrenVM* vm) { - double* coordinates = (double*)wrenGetArgumentForeign(vm, 0); - coordinates[0] += wrenGetArgumentDouble(vm, 1); - coordinates[1] += wrenGetArgumentDouble(vm, 2); - coordinates[2] += wrenGetArgumentDouble(vm, 3); + double* coordinates = (double*)wrenGetSlotForeign(vm, 0); + coordinates[0] += wrenGetSlotDouble(vm, 1); + coordinates[1] += wrenGetSlotDouble(vm, 2); + coordinates[2] += wrenGetSlotDouble(vm, 3); } static void pointToString(WrenVM* vm) { - double* coordinates = (double*)wrenGetArgumentForeign(vm, 0); + double* coordinates = (double*)wrenGetSlotForeign(vm, 0); char result[100]; sprintf(result, "(%g, %g, %g)", coordinates[0], coordinates[1], coordinates[2]); - wrenReturnString(vm, result, (int)strlen(result)); + wrenSetSlotString(vm, 0, result); } static void resourceAllocate(WrenVM* vm) { - wrenAllocateForeign(vm, 0); + int* value = (int*)wrenAllocateForeign(vm, sizeof(int)); + *value = 123; } -static void resourceFinalize(WrenVM* vm) +static void resourceFinalize(void* data) { + // Make sure we get the right data back. + int* value = (int*)data; + if (*value != 123) exit(1); + finalized++; } diff --git a/test/api/get_variable.c b/test/api/get_variable.c new file mode 100644 index 00000000..c5237214 --- /dev/null +++ b/test/api/get_variable.c @@ -0,0 +1,44 @@ +#include + +#include "get_variable.h" + +static void beforeDefined(WrenVM* vm) +{ + wrenGetVariable(vm, "main", "A", 0); +} + +static void afterDefined(WrenVM* vm) +{ + wrenGetVariable(vm, "main", "A", 0); +} + +static void afterAssigned(WrenVM* vm) +{ + wrenGetVariable(vm, "main", "A", 0); +} + +static void otherSlot(WrenVM* vm) +{ + wrenEnsureSlots(vm, 3); + wrenGetVariable(vm, "main", "B", 2); + + // Move it into return position. + const char* string = wrenGetSlotString(vm, 2); + wrenSetSlotString(vm, 0, string); +} + +static void otherModule(WrenVM* vm) +{ + wrenGetVariable(vm, "get_variable_module", "Variable", 0); +} + +WrenForeignMethodFn getVariableBindMethod(const char* signature) +{ + if (strcmp(signature, "static GetVariable.beforeDefined()") == 0) return beforeDefined; + if (strcmp(signature, "static GetVariable.afterDefined()") == 0) return afterDefined; + if (strcmp(signature, "static GetVariable.afterAssigned()") == 0) return afterAssigned; + if (strcmp(signature, "static GetVariable.otherSlot()") == 0) return otherSlot; + if (strcmp(signature, "static GetVariable.otherModule()") == 0) return otherModule; + + return NULL; +} diff --git a/test/api/get_variable.h b/test/api/get_variable.h new file mode 100644 index 00000000..8150ee23 --- /dev/null +++ b/test/api/get_variable.h @@ -0,0 +1,3 @@ +#include "wren.h" + +WrenForeignMethodFn getVariableBindMethod(const char* signature); diff --git a/test/api/get_variable.wren b/test/api/get_variable.wren new file mode 100644 index 00000000..7d2c5c2f --- /dev/null +++ b/test/api/get_variable.wren @@ -0,0 +1,24 @@ +import "get_variable_module" + +class GetVariable { + foreign static def beforeDefined() + foreign static def afterDefined() + foreign static def afterAssigned() + foreign static def otherSlot() + foreign static def otherModule() +} + +System.print(GetVariable.beforeDefined()) // expect: null + +var A = "a" + +System.print(GetVariable.afterDefined()) // expect: a + +A = "changed" + +System.print(GetVariable.afterAssigned()) // expect: changed + +var B = "b" +System.print(GetVariable.otherSlot()) // expect: b + +System.print(GetVariable.otherModule()) // expect: value diff --git a/test/api/get_variable_module.wren b/test/api/get_variable_module.wren new file mode 100644 index 00000000..17c96844 --- /dev/null +++ b/test/api/get_variable_module.wren @@ -0,0 +1,3 @@ +// nontest + +var Variable = "value" diff --git a/test/api/lists.c b/test/api/lists.c new file mode 100644 index 00000000..8a2f7a48 --- /dev/null +++ b/test/api/lists.c @@ -0,0 +1,46 @@ +#include + +#include "lists.h" + +static void newList(WrenVM* vm) +{ + wrenSetSlotNewList(vm, 0); +} + +// Helper function to store a double in a slot then insert it into the list at +// slot zero. +static void insertNumber(WrenVM* vm, int index, double value) +{ + wrenSetSlotDouble(vm, 1, value); + wrenInsertInList(vm, 0, index, 1); +} + +static void insert(WrenVM* vm) +{ + wrenSetSlotNewList(vm, 0); + + wrenEnsureSlots(vm, 2); + + // Appending. + insertNumber(vm, 0, 1.0); + insertNumber(vm, 1, 2.0); + insertNumber(vm, 2, 3.0); + + // Inserting. + insertNumber(vm, 0, 4.0); + insertNumber(vm, 1, 5.0); + insertNumber(vm, 2, 6.0); + + // Negative indexes. + insertNumber(vm, -1, 7.0); + insertNumber(vm, -2, 8.0); + insertNumber(vm, -3, 9.0); +} + +WrenForeignMethodFn listsBindMethod(const char* signature) +{ + if (strcmp(signature, "static Lists.newList()") == 0) return newList; + if (strcmp(signature, "static Lists.insert()") == 0) return insert; + + return NULL; +} diff --git a/test/api/lists.h b/test/api/lists.h new file mode 100644 index 00000000..9c74380b --- /dev/null +++ b/test/api/lists.h @@ -0,0 +1,3 @@ +#include "wren.h" + +WrenForeignMethodFn listsBindMethod(const char* signature); diff --git a/test/api/lists.wren b/test/api/lists.wren new file mode 100644 index 00000000..d0e1d456 --- /dev/null +++ b/test/api/lists.wren @@ -0,0 +1,10 @@ +class Lists { + foreign static def newList() + foreign static def insert() +} + +var list = Lists.newList() +System.print(list is List) // expect: true +System.print(list.count) // expect: 0 + +System.print(Lists.insert()) // expect: [4, 5, 6, 1, 2, 3, 9, 8, 7] diff --git a/test/api/main.c b/test/api/main.c index 7674db05..7ec7f797 100644 --- a/test/api/main.c +++ b/test/api/main.c @@ -6,8 +6,10 @@ #include "benchmark.h" #include "call.h" +#include "get_variable.h" #include "foreign_class.h" -#include "returns.h" +#include "lists.h" +#include "slots.h" #include "value.h" // The name of the currently executing API test. @@ -32,11 +34,17 @@ static WrenForeignMethodFn bindForeignMethod( method = benchmarkBindMethod(fullName); if (method != NULL) return method; - + + method = getVariableBindMethod(fullName); + if (method != NULL) return method; + method = foreignClassBindMethod(fullName); if (method != NULL) return method; - method = returnsBindMethod(fullName); + method = listsBindMethod(fullName); + if (method != NULL) return method; + + method = slotsBindMethod(fullName); if (method != NULL) return method; method = valueBindMethod(fullName); @@ -60,7 +68,7 @@ static WrenForeignClassMethods bindForeignClass( } static void afterLoad(WrenVM* vm) { - if (strstr(testName, "call.wren") != NULL) callRunTests(vm); + if (strstr(testName, "/call.wren") != NULL) callRunTests(vm); } int main(int argc, const char* argv[]) diff --git a/test/api/returns.c b/test/api/returns.c deleted file mode 100644 index f622ded9..00000000 --- a/test/api/returns.c +++ /dev/null @@ -1,51 +0,0 @@ -#include - -#include "returns.h" - -static void implicitNull(WrenVM* vm) -{ - // Do nothing. -} - -static void returnInt(WrenVM* vm) -{ - wrenReturnDouble(vm, 123456); -} - -static void returnFloat(WrenVM* vm) -{ - wrenReturnDouble(vm, 123.456); -} - -static void returnTrue(WrenVM* vm) -{ - wrenReturnBool(vm, true); -} - -static void returnFalse(WrenVM* vm) -{ - wrenReturnBool(vm, false); -} - -static void returnString(WrenVM* vm) -{ - wrenReturnString(vm, "a string", -1); -} - -static void returnBytes(WrenVM* vm) -{ - wrenReturnString(vm, "a\0b\0c", 5); -} - -WrenForeignMethodFn returnsBindMethod(const char* signature) -{ - if (strcmp(signature, "static Returns.implicitNull") == 0) return implicitNull; - if (strcmp(signature, "static Returns.returnInt") == 0) return returnInt; - if (strcmp(signature, "static Returns.returnFloat") == 0) return returnFloat; - if (strcmp(signature, "static Returns.returnTrue") == 0) return returnTrue; - if (strcmp(signature, "static Returns.returnFalse") == 0) return returnFalse; - if (strcmp(signature, "static Returns.returnString") == 0) return returnString; - if (strcmp(signature, "static Returns.returnBytes") == 0) return returnBytes; - - return NULL; -} diff --git a/test/api/returns.h b/test/api/returns.h deleted file mode 100644 index 21fa57cd..00000000 --- a/test/api/returns.h +++ /dev/null @@ -1,3 +0,0 @@ -#include "wren.h" - -WrenForeignMethodFn returnsBindMethod(const char* signature); diff --git a/test/api/returns.wren b/test/api/returns.wren deleted file mode 100644 index 69f09027..00000000 --- a/test/api/returns.wren +++ /dev/null @@ -1,23 +0,0 @@ -class Returns { - foreign static def implicitNull - - foreign static def returnInt - foreign static def returnFloat - - foreign static def returnTrue - foreign static def returnFalse - - foreign static def returnString - foreign static def returnBytes -} - -System.print(Returns.implicitNull == null) // expect: true - -System.print(Returns.returnInt) // expect: 123456 -System.print(Returns.returnFloat) // expect: 123.456 - -System.print(Returns.returnTrue) // expect: true -System.print(Returns.returnFalse) // expect: false - -System.print(Returns.returnString) // expect: a string -System.print(Returns.returnBytes.bytes.toList) // expect: [97, 0, 98, 0, 99] diff --git a/test/api/slots.c b/test/api/slots.c new file mode 100644 index 00000000..9e995b9b --- /dev/null +++ b/test/api/slots.c @@ -0,0 +1,146 @@ +#include +#include + +#include "slots.h" + +static void noSet(WrenVM* vm) +{ + // Do nothing. +} + +static void getSlots(WrenVM* vm) +{ + bool result = true; + if (wrenGetSlotBool(vm, 1) != true) result = false; + // TODO: Test wrenGetSlotForeign(). + + int length; + const char* bytes = wrenGetSlotBytes(vm, 2, &length); + if (length != 5) result = false; + if (memcmp(bytes, "by\0te", length) != 0) result = false; + + if (wrenGetSlotDouble(vm, 3) != 12.34) result = false; + if (strcmp(wrenGetSlotString(vm, 4), "str") != 0) result = false; + + WrenValue* value = wrenGetSlotValue(vm, 5); + + if (result) + { + // Otherwise, return the value so we can tell if we captured it correctly. + wrenSetSlotValue(vm, 0, value); + wrenReleaseValue(vm, value); + } + else + { + // If anything failed, return false. + wrenSetSlotBool(vm, 0, false); + } +} + +static void setSlots(WrenVM* vm) +{ + WrenValue* value = wrenGetSlotValue(vm, 1); + + wrenSetSlotBool(vm, 1, true); + wrenSetSlotBytes(vm, 2, "by\0te", 5); + wrenSetSlotDouble(vm, 3, 12.34); + wrenSetSlotString(vm, 4, "str"); + + // TODO: wrenSetSlotNull(). + + // Read the slots back to make sure they were set correctly. + + bool result = true; + if (wrenGetSlotBool(vm, 1) != true) result = false; + + int length; + const char* bytes = wrenGetSlotBytes(vm, 2, &length); + if (length != 5) result = false; + if (memcmp(bytes, "by\0te", length) != 0) result = false; + + if (wrenGetSlotDouble(vm, 3) != 12.34) result = false; + if (strcmp(wrenGetSlotString(vm, 4), "str") != 0) result = false; + + if (result) + { + // Move the value into the return position. + wrenSetSlotValue(vm, 0, value); + wrenReleaseValue(vm, value); + } + else + { + // If anything failed, return false. + wrenSetSlotBool(vm, 0, false); + } +} + +static void ensure(WrenVM* vm) +{ + int before = wrenGetSlotCount(vm); + + wrenEnsureSlots(vm, 20); + + int after = wrenGetSlotCount(vm); + + // Use the slots to make sure they're available. + for (int i = 0; i < 20; i++) + { + wrenSetSlotDouble(vm, i, i); + } + + int sum = 0; + + for (int i = 0; i < 20; i++) + { + sum += (int)wrenGetSlotDouble(vm, i); + } + + char result[100]; + sprintf(result, "%d -> %d (%d)", before, after, sum); + wrenSetSlotString(vm, 0, result); +} + +static void ensureOutsideForeign(WrenVM* vm) +{ + // To test the behavior outside of a foreign method (which we're currently + // in), create a new separate VM. + WrenConfiguration config; + wrenInitConfiguration(&config); + WrenVM* otherVM = wrenNewVM(&config); + + int before = wrenGetSlotCount(otherVM); + + wrenEnsureSlots(otherVM, 20); + + int after = wrenGetSlotCount(otherVM); + + // Use the slots to make sure they're available. + for (int i = 0; i < 20; i++) + { + wrenSetSlotDouble(otherVM, i, i); + } + + int sum = 0; + + for (int i = 0; i < 20; i++) + { + sum += (int)wrenGetSlotDouble(otherVM, i); + } + + wrenFreeVM(otherVM); + + char result[100]; + sprintf(result, "%d -> %d (%d)", before, after, sum); + wrenSetSlotString(vm, 0, result); +} + +WrenForeignMethodFn slotsBindMethod(const char* signature) +{ + if (strcmp(signature, "static Slots.noSet") == 0) return noSet; + if (strcmp(signature, "static Slots.getSlots(_,_,_,_,_)") == 0) return getSlots; + if (strcmp(signature, "static Slots.setSlots(_,_,_,_)") == 0) return setSlots; + if (strcmp(signature, "static Slots.ensure()") == 0) return ensure; + if (strcmp(signature, "static Slots.ensureOutsideForeign()") == 0) return ensureOutsideForeign; + + return NULL; +} diff --git a/test/api/slots.h b/test/api/slots.h new file mode 100644 index 00000000..cf4392fa --- /dev/null +++ b/test/api/slots.h @@ -0,0 +1,3 @@ +#include "wren.h" + +WrenForeignMethodFn slotsBindMethod(const char* signature); diff --git a/test/api/slots.wren b/test/api/slots.wren new file mode 100644 index 00000000..764307ae --- /dev/null +++ b/test/api/slots.wren @@ -0,0 +1,24 @@ +class Slots { + foreign static def noSet + foreign static def getSlots(bool, num, string, bytes, value) + foreign static def setSlots(a, b, c, d) + foreign static def ensure() + foreign static def ensureOutsideForeign() +} + +// If nothing is set in the return slot, it retains its previous value, the +// receiver. +System.print(Slots.noSet == Slots) // expect: true + +var value = ["value"] +System.print(Slots.getSlots(true, "by\0te", 12.34, "str", value) == value) +// expect: true + +System.print(Slots.setSlots(value, 0, 0, 0) == value) +// expect: true + +System.print(Slots.ensure()) +// expect: 1 -> 20 (190) + +System.print(Slots.ensureOutsideForeign()) +// expect: 0 -> 20 (190) diff --git a/test/api/value.c b/test/api/value.c index 5b7b6225..837e8ce2 100644 --- a/test/api/value.c +++ b/test/api/value.c @@ -6,12 +6,12 @@ static WrenValue* value; static void setValue(WrenVM* vm) { - value = wrenGetArgumentValue(vm, 1); + value = wrenGetSlotValue(vm, 1); } static void getValue(WrenVM* vm) { - wrenReturnValue(vm, value); + wrenSetSlotValue(vm, 0, value); wrenReleaseValue(vm, value); } diff --git a/test/benchmark/api_call.wren b/test/benchmark/api_call.wren new file mode 100644 index 00000000..568f2549 --- /dev/null +++ b/test/benchmark/api_call.wren @@ -0,0 +1,9 @@ +class Benchmark { + foreign static call(iterations) +} + +var result = Benchmark.call(1000000) +// Returns false if it didn't calculate the right value. Otherwise returns the +// elapsed time. +System.print(result is Num) +System.print("elapsed: %(result)") diff --git a/test/io/directory/dir/a.txt b/test/io/directory/dir/a.txt new file mode 100644 index 00000000..e068544d --- /dev/null +++ b/test/io/directory/dir/a.txt @@ -0,0 +1 @@ +this is a text file \ No newline at end of file diff --git a/test/io/directory/dir/b.txt b/test/io/directory/dir/b.txt new file mode 100644 index 00000000..e068544d --- /dev/null +++ b/test/io/directory/dir/b.txt @@ -0,0 +1 @@ +this is a text file \ No newline at end of file diff --git a/test/io/directory/dir/c.txt b/test/io/directory/dir/c.txt new file mode 100644 index 00000000..e068544d --- /dev/null +++ b/test/io/directory/dir/c.txt @@ -0,0 +1 @@ +this is a text file \ No newline at end of file diff --git a/test/io/directory/list.wren b/test/io/directory/list.wren new file mode 100644 index 00000000..bf61d759 --- /dev/null +++ b/test/io/directory/list.wren @@ -0,0 +1,8 @@ +import "io" for Directory + +var entries = Directory.list("test/io/directory/dir") + +// Ignore OS-specific dot files like ".DS_Store". +entries = entries.where {|entry| !entry.startsWith(".") }.toList + +System.print(entries) // expect: [a.txt, b.txt, c.txt] diff --git a/test/io/directory/list_file.wren b/test/io/directory/list_file.wren new file mode 100644 index 00000000..a411eeb0 --- /dev/null +++ b/test/io/directory/list_file.wren @@ -0,0 +1,3 @@ +import "io" for Directory + +var entries = Directory.list("test/io/directory/dir/a.txt") // expect runtime error: not a directory diff --git a/test/io/directory/list_nonexistent.wren b/test/io/directory/list_nonexistent.wren new file mode 100644 index 00000000..024e7de3 --- /dev/null +++ b/test/io/directory/list_nonexistent.wren @@ -0,0 +1,3 @@ +import "io" for Directory + +Directory.list("nonexistent") // expect runtime error: no such file or directory diff --git a/test/io/directory/list_wrong_arg_type.wren b/test/io/directory/list_wrong_arg_type.wren new file mode 100644 index 00000000..03bedcd5 --- /dev/null +++ b/test/io/directory/list_wrong_arg_type.wren @@ -0,0 +1,3 @@ +import "io" for Directory + +Directory.list(123) // expect runtime error: Path must be a string. diff --git a/test/io/file/finalize.wren b/test/io/file/finalize.wren new file mode 100644 index 00000000..5502fbab --- /dev/null +++ b/test/io/file/finalize.wren @@ -0,0 +1,10 @@ +import "io" for File + +// Don't store in a variable. +File.open("test/io/file/finalize.wren") + +System.gc() +// We can't really test what the finalizer *does* from Wren, since the object +// is unreachable, but this at least ensures it doesn't crash. + +System.print("ok") // expect: ok \ No newline at end of file diff --git a/test/io/file/read_bytes.wren b/test/io/file/read_bytes.wren index 44297cb4..a9d56a87 100644 --- a/test/io/file/read_bytes.wren +++ b/test/io/file/read_bytes.wren @@ -10,4 +10,10 @@ System.print(file.readBytes(7)) // expect: this is // Allows zero. System.print(file.readBytes(0).bytes.count) // expect: 0 +// A longer number reads the whole file. +System.print(file.readBytes(100)) // expect: this is a text file + +// Reading past the end truncates the buffer. +System.print(file.readBytes(100).bytes.count) // expect: 19 + file.close() diff --git a/test/io/file/read_bytes_negative.wren b/test/io/file/read_bytes_count_negative.wren similarity index 100% rename from test/io/file/read_bytes_negative.wren rename to test/io/file/read_bytes_count_negative.wren diff --git a/test/io/file/read_bytes_not_integer.wren b/test/io/file/read_bytes_count_not_integer.wren similarity index 100% rename from test/io/file/read_bytes_not_integer.wren rename to test/io/file/read_bytes_count_not_integer.wren diff --git a/test/io/file/read_bytes_from.wren b/test/io/file/read_bytes_from.wren new file mode 100644 index 00000000..a05cbc97 --- /dev/null +++ b/test/io/file/read_bytes_from.wren @@ -0,0 +1,20 @@ +import "io" for File + +var file = File.open("test/io/file/file.txt") + +// Zero starts at the beginning. +System.print(file.readBytes(3, 0)) // expect: thi + +// Starts at the offset. +System.print(file.readBytes(8, 3)) // expect: s is a t + +// Allows zero. +System.print(file.readBytes(0, 4).bytes.count) // expect: 0 + +// A longer number length reads until the end. +System.print(file.readBytes(100, 2)) // expect: is is a text file + +// An offset past the end returns an empty string. +System.print(file.readBytes(100, 30).bytes.count) // expect: 0 + +file.close() diff --git a/test/io/file/read_bytes_from_after_close.wren b/test/io/file/read_bytes_from_after_close.wren new file mode 100644 index 00000000..4cc8a753 --- /dev/null +++ b/test/io/file/read_bytes_from_after_close.wren @@ -0,0 +1,6 @@ +import "io" for File + +var file = File.open("test/io/file/file.txt") +file.close() + +file.readBytes(3, 0) // expect runtime error: File is not open. diff --git a/test/io/file/read_bytes_from_count_negative.wren b/test/io/file/read_bytes_from_count_negative.wren new file mode 100644 index 00000000..9d251303 --- /dev/null +++ b/test/io/file/read_bytes_from_count_negative.wren @@ -0,0 +1,4 @@ +import "io" for File + +var file = File.open("test/io/file/file.txt") +file.readBytes(-1, 0) // expect runtime error: Count cannot be negative. diff --git a/test/io/file/read_bytes_from_count_not_integer.wren b/test/io/file/read_bytes_from_count_not_integer.wren new file mode 100644 index 00000000..8a2ba576 --- /dev/null +++ b/test/io/file/read_bytes_from_count_not_integer.wren @@ -0,0 +1,4 @@ +import "io" for File + +var file = File.open("test/io/file/file.txt") +file.readBytes(1.2, 0) // expect runtime error: Count must be an integer. diff --git a/test/io/file/read_bytes_from_count_not_num.wren b/test/io/file/read_bytes_from_count_not_num.wren new file mode 100644 index 00000000..7783b522 --- /dev/null +++ b/test/io/file/read_bytes_from_count_not_num.wren @@ -0,0 +1,4 @@ +import "io" for File + +var file = File.open("test/io/file/file.txt") +file.readBytes("not num", 0) // expect runtime error: Count must be an integer. diff --git a/test/io/file/read_bytes_from_offset_negative.wren b/test/io/file/read_bytes_from_offset_negative.wren new file mode 100644 index 00000000..5a303082 --- /dev/null +++ b/test/io/file/read_bytes_from_offset_negative.wren @@ -0,0 +1,4 @@ +import "io" for File + +var file = File.open("test/io/file/file.txt") +file.readBytes(1, -1) // expect runtime error: Offset cannot be negative. diff --git a/test/io/file/read_bytes_from_offset_not_integer.wren b/test/io/file/read_bytes_from_offset_not_integer.wren new file mode 100644 index 00000000..dc573cea --- /dev/null +++ b/test/io/file/read_bytes_from_offset_not_integer.wren @@ -0,0 +1,4 @@ +import "io" for File + +var file = File.open("test/io/file/file.txt") +file.readBytes(1, 1.2) // expect runtime error: Offset must be an integer. diff --git a/test/io/file/read_bytes_from_offset_not_num.wren b/test/io/file/read_bytes_from_offset_not_num.wren new file mode 100644 index 00000000..528d0097 --- /dev/null +++ b/test/io/file/read_bytes_from_offset_not_num.wren @@ -0,0 +1,4 @@ +import "io" for File + +var file = File.open("test/io/file/file.txt") +file.readBytes(1, "not num") // expect runtime error: Offset must be an integer. diff --git a/test/io/file/size.wren b/test/io/file/size.wren index 84e72eab..b1cc17fe 100644 --- a/test/io/file/size.wren +++ b/test/io/file/size.wren @@ -1,13 +1,6 @@ import "io" for File import "scheduler" for Scheduler -System.print(File.size("test/io/file/size.wren")) // expect: 270 - -// Runs asynchronously. -Scheduler.add { - System.print("async") -} - -System.print(File.size("test/io/file/size.wren")) -// expect: async -// expect: 270 +var file = File.open("test/io/file/file.txt") +System.print(file.size) // expect: 19 +file.close() diff --git a/test/io/file/size_static.wren b/test/io/file/size_static.wren new file mode 100644 index 00000000..c9b720f0 --- /dev/null +++ b/test/io/file/size_static.wren @@ -0,0 +1,13 @@ +import "io" for File +import "scheduler" for Scheduler + +System.print(File.size("test/io/file/file.txt")) // expect: 19 + +// Runs asynchronously. +Scheduler.add { + System.print("async") +} + +System.print(File.size("test/io/file/file.txt")) +// expect: async +// expect: 19 diff --git a/test/io/file/size_nonexistent.wren b/test/io/file/size_static_nonexistent.wren similarity index 100% rename from test/io/file/size_nonexistent.wren rename to test/io/file/size_static_nonexistent.wren diff --git a/test/io/file/stat_static.wren b/test/io/file/stat_static.wren new file mode 100644 index 00000000..0ca59d3f --- /dev/null +++ b/test/io/file/stat_static.wren @@ -0,0 +1,16 @@ +import "io" for File, Stat +import "scheduler" for Scheduler + +var stat = File.stat("test/io/file/file.txt") + +System.print(stat is Stat) // expect: true +System.print(stat.device is Num) // expect: true +System.print(stat.inode is Num) // expect: true +System.print(stat.mode is Num) // expect: true +System.print(stat.linkCount) // expect: 1 +System.print(stat.user is Num) // expect: true +System.print(stat.group is Num) // expect: true +System.print(stat.specialDevice) // expect: 0 +System.print(stat.size) // expect: 19 +System.print(stat.blockSize is Num) // expect: true +System.print(stat.blockCount is Num) // expect: true diff --git a/test/io/file/stat_static_directory.wren b/test/io/file/stat_static_directory.wren new file mode 100644 index 00000000..b5aa8ad3 --- /dev/null +++ b/test/io/file/stat_static_directory.wren @@ -0,0 +1,16 @@ +import "io" for File, Stat +import "scheduler" for Scheduler + +var stat = File.stat("test/io/directory/dir") + +System.print(stat is Stat) // expect: true +System.print(stat.device is Num) // expect: true +System.print(stat.inode is Num) // expect: true +System.print(stat.mode is Num) // expect: true +System.print(stat.linkCount) // expect: 5 +System.print(stat.user is Num) // expect: true +System.print(stat.group is Num) // expect: true +System.print(stat.specialDevice) // expect: 0 +System.print(stat.size) // expect: 170 +System.print(stat.blockSize is Num) // expect: true +System.print(stat.blockCount is Num) // expect: true diff --git a/test/io/file/stat_static_nonexistent.wren b/test/io/file/stat_static_nonexistent.wren new file mode 100644 index 00000000..8bc17802 --- /dev/null +++ b/test/io/file/stat_static_nonexistent.wren @@ -0,0 +1,3 @@ +import "io" for File + +File.stat("nonexistent") // expect runtime error: no such file or directory diff --git a/util/benchmark.py b/util/benchmark.py index 36e7952d..cb00a649 100755 --- a/util/benchmark.py +++ b/util/benchmark.py @@ -51,6 +51,8 @@ def BENCHMARK(name, pattern): regex = re.compile(pattern + "\n" + r"elapsed: (\d+\.\d+)", re.MULTILINE) BENCHMARKS.append([name, regex, None]) +BENCHMARK("api_call", "true") + BENCHMARK("api_foreign_method", "100000000") BENCHMARK("binary_trees", """stretch tree of depth 13 check: -1 diff --git a/util/test.py b/util/test.py index 080d2e60..b338cbd6 100755 --- a/util/test.py +++ b/util/test.py @@ -182,6 +182,7 @@ class Test: # Make sure the stack trace has the right line. Skip over any lines that # come from builtin libraries. + match = False stack_lines = error_lines[line + 1:] match = None for stack_line in stack_lines: diff --git a/util/wren.mk b/util/wren.mk index b48f5ca1..88e8b457 100644 --- a/util/wren.mk +++ b/util/wren.mk @@ -206,12 +206,15 @@ $(LIBUV): $(LIBUV_DIR)/build/gyp/gyp util/libuv.py # Wren modules that get compiled into the binary as C strings. src/optional/wren_opt_%.wren.inc: src/optional/wren_opt_%.wren util/wren_to_c_string.py + @ printf "%10s %-30s %s\n" str $< @ ./util/wren_to_c_string.py $@ $< src/vm/wren_%.wren.inc: src/vm/wren_%.wren util/wren_to_c_string.py + @ printf "%10s %-30s %s\n" str $< @ ./util/wren_to_c_string.py $@ $< src/module/%.wren.inc: src/module/%.wren util/wren_to_c_string.py + @ printf "%10s %-30s %s\n" str $< @ ./util/wren_to_c_string.py $@ $< .PHONY: all cli test vm diff --git a/util/wren_to_c_string.py b/util/wren_to_c_string.py index 6715fb40..8129882f 100755 --- a/util/wren_to_c_string.py +++ b/util/wren_to_c_string.py @@ -50,7 +50,5 @@ def main(): with open(args.output, "w") as f: f.write(c_source) - print(" str " + args.input) - main() diff --git a/util/xcode/wren.xcodeproj/project.pbxproj b/util/xcode/wren.xcodeproj/project.pbxproj index fe7b67a4..c250b022 100644 --- a/util/xcode/wren.xcodeproj/project.pbxproj +++ b/util/xcode/wren.xcodeproj/project.pbxproj @@ -19,6 +19,7 @@ 29205C9E1AB4E6430073018D /* wren_value.c in Sources */ = {isa = PBXBuildFile; fileRef = 29205C971AB4E6430073018D /* wren_value.c */; }; 29205C9F1AB4E6430073018D /* wren_vm.c in Sources */ = {isa = PBXBuildFile; fileRef = 29205C981AB4E6430073018D /* wren_vm.c */; }; 293D46961BB43F9900200083 /* call.c in Sources */ = {isa = PBXBuildFile; fileRef = 293D46941BB43F9900200083 /* call.c */; }; + 2949AA8D1C2F14F000B106BA /* get_variable.c in Sources */ = {isa = PBXBuildFile; fileRef = 2949AA8B1C2F14F000B106BA /* get_variable.c */; }; 29512C811B91F8EB008C10E6 /* libuv.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 29512C801B91F8EB008C10E6 /* libuv.a */; }; 29512C821B91F901008C10E6 /* libuv.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 29512C801B91F8EB008C10E6 /* libuv.a */; }; 29729F311BA70A620099CA20 /* io.c in Sources */ = {isa = PBXBuildFile; fileRef = 29729F2E1BA70A620099CA20 /* io.c */; }; @@ -26,6 +27,7 @@ 29729F331BA70A620099CA20 /* io.wren.inc in Sources */ = {isa = PBXBuildFile; fileRef = 29729F301BA70A620099CA20 /* io.wren.inc */; }; 2986F6D71ACF93BA00BCE26C /* wren_primitive.c in Sources */ = {isa = PBXBuildFile; fileRef = 2986F6D51ACF93BA00BCE26C /* wren_primitive.c */; }; 29932D511C20D8C900099DEE /* benchmark.c in Sources */ = {isa = PBXBuildFile; fileRef = 29932D4F1C20D8C900099DEE /* benchmark.c */; }; + 29932D541C210F8D00099DEE /* lists.c in Sources */ = {isa = PBXBuildFile; fileRef = 29932D521C210F8D00099DEE /* lists.c */; }; 29A427341BDBE435001E6E22 /* wren_opt_meta.c in Sources */ = {isa = PBXBuildFile; fileRef = 29A4272E1BDBE435001E6E22 /* wren_opt_meta.c */; }; 29A427351BDBE435001E6E22 /* wren_opt_meta.c in Sources */ = {isa = PBXBuildFile; fileRef = 29A4272E1BDBE435001E6E22 /* wren_opt_meta.c */; }; 29A427361BDBE435001E6E22 /* wren_opt_meta.wren.inc in Sources */ = {isa = PBXBuildFile; fileRef = 29A427301BDBE435001E6E22 /* wren_opt_meta.wren.inc */; }; @@ -47,7 +49,7 @@ 29DC14A91BBA302F008A8274 /* wren_vm.c in Sources */ = {isa = PBXBuildFile; fileRef = 29205C981AB4E6430073018D /* wren_vm.c */; }; 29DC14AA1BBA3032008A8274 /* main.c in Sources */ = {isa = PBXBuildFile; fileRef = 29D009A61B7E3993000CE58C /* main.c */; }; 29DC14AB1BBA3038008A8274 /* foreign_class.c in Sources */ = {isa = PBXBuildFile; fileRef = 29D009A81B7E39A8000CE58C /* foreign_class.c */; }; - 29DC14AC1BBA303D008A8274 /* returns.c in Sources */ = {isa = PBXBuildFile; fileRef = 29D009AA1B7E39A8000CE58C /* returns.c */; }; + 29DC14AC1BBA303D008A8274 /* slots.c in Sources */ = {isa = PBXBuildFile; fileRef = 29D009AA1B7E39A8000CE58C /* slots.c */; }; 29DC14AD1BBA3040008A8274 /* value.c in Sources */ = {isa = PBXBuildFile; fileRef = 29D009AC1B7E39A8000CE58C /* value.c */; }; /* End PBXBuildFile section */ @@ -97,6 +99,8 @@ 29205CA81AB4E65E0073018D /* wren_vm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = wren_vm.h; path = ../../src/vm/wren_vm.h; sourceTree = ""; }; 293D46941BB43F9900200083 /* call.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = call.c; path = ../../test/api/call.c; sourceTree = ""; }; 293D46951BB43F9900200083 /* call.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = call.h; path = ../../test/api/call.h; sourceTree = ""; }; + 2949AA8B1C2F14F000B106BA /* get_variable.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = get_variable.c; path = ../../test/api/get_variable.c; sourceTree = ""; }; + 2949AA8C1C2F14F000B106BA /* get_variable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = get_variable.h; path = ../../test/api/get_variable.h; sourceTree = ""; }; 29512C7F1B91F86E008C10E6 /* api_test */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = api_test; sourceTree = BUILT_PRODUCTS_DIR; }; 29512C801B91F8EB008C10E6 /* libuv.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libuv.a; path = ../../build/libuv.a; sourceTree = ""; }; 296371B31AC713D000079FDA /* wren_opcodes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = wren_opcodes.h; path = ../../src/vm/wren_opcodes.h; sourceTree = ""; }; @@ -106,6 +110,8 @@ 2986F6D61ACF93BA00BCE26C /* wren_primitive.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = wren_primitive.h; path = ../../src/vm/wren_primitive.h; sourceTree = ""; }; 29932D4F1C20D8C900099DEE /* benchmark.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = benchmark.c; path = ../../test/api/benchmark.c; sourceTree = ""; }; 29932D501C20D8C900099DEE /* benchmark.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = benchmark.h; path = ../../test/api/benchmark.h; sourceTree = ""; }; + 29932D521C210F8D00099DEE /* lists.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = lists.c; path = ../../test/api/lists.c; sourceTree = ""; }; + 29932D531C210F8D00099DEE /* lists.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = lists.h; path = ../../test/api/lists.h; sourceTree = ""; }; 29A4272E1BDBE435001E6E22 /* wren_opt_meta.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = wren_opt_meta.c; path = ../../src/optional/wren_opt_meta.c; sourceTree = ""; }; 29A4272F1BDBE435001E6E22 /* wren_opt_meta.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = wren_opt_meta.h; path = ../../src/optional/wren_opt_meta.h; sourceTree = ""; }; 29A427301BDBE435001E6E22 /* wren_opt_meta.wren.inc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.pascal; name = wren_opt_meta.wren.inc; path = ../../src/optional/wren_opt_meta.wren.inc; sourceTree = ""; }; @@ -118,8 +124,8 @@ 29D009A61B7E3993000CE58C /* main.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = main.c; path = ../../test/api/main.c; sourceTree = ""; }; 29D009A81B7E39A8000CE58C /* foreign_class.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = foreign_class.c; path = ../../test/api/foreign_class.c; sourceTree = ""; }; 29D009A91B7E39A8000CE58C /* foreign_class.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = foreign_class.h; path = ../../test/api/foreign_class.h; sourceTree = ""; }; - 29D009AA1B7E39A8000CE58C /* returns.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = returns.c; path = ../../test/api/returns.c; sourceTree = ""; }; - 29D009AB1B7E39A8000CE58C /* returns.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = returns.h; path = ../../test/api/returns.h; sourceTree = ""; }; + 29D009AA1B7E39A8000CE58C /* slots.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = slots.c; path = ../../test/api/slots.c; sourceTree = ""; }; + 29D009AB1B7E39A8000CE58C /* slots.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = slots.h; path = ../../test/api/slots.h; sourceTree = ""; }; 29D009AC1B7E39A8000CE58C /* value.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = value.c; path = ../../test/api/value.c; sourceTree = ""; }; 29D009AD1B7E39A8000CE58C /* value.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = value.h; path = ../../test/api/value.h; sourceTree = ""; }; 29F384111BD19706002F84E0 /* io.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = io.h; path = ../../src/module/io.h; sourceTree = ""; }; @@ -249,8 +255,12 @@ 293D46951BB43F9900200083 /* call.h */, 29D009A81B7E39A8000CE58C /* foreign_class.c */, 29D009A91B7E39A8000CE58C /* foreign_class.h */, - 29D009AA1B7E39A8000CE58C /* returns.c */, - 29D009AB1B7E39A8000CE58C /* returns.h */, + 2949AA8B1C2F14F000B106BA /* get_variable.c */, + 2949AA8C1C2F14F000B106BA /* get_variable.h */, + 29932D521C210F8D00099DEE /* lists.c */, + 29932D531C210F8D00099DEE /* lists.h */, + 29D009AA1B7E39A8000CE58C /* slots.c */, + 29D009AB1B7E39A8000CE58C /* slots.h */, 29D009AC1B7E39A8000CE58C /* value.c */, 29D009AD1B7E39A8000CE58C /* value.h */, ); @@ -358,7 +368,9 @@ files = ( 29A427371BDBE435001E6E22 /* wren_opt_meta.wren.inc in Sources */, 29729F321BA70A620099CA20 /* io.c in Sources */, + 29932D541C210F8D00099DEE /* lists.c in Sources */, 291647C81BA5EC5E006142EE /* modules.c in Sources */, + 2949AA8D1C2F14F000B106BA /* get_variable.c in Sources */, 29DC14A11BBA2FEC008A8274 /* scheduler.c in Sources */, 29A427391BDBE435001E6E22 /* wren_opt_random.c in Sources */, 29932D511C20D8C900099DEE /* benchmark.c in Sources */, @@ -376,7 +388,7 @@ 293D46961BB43F9900200083 /* call.c in Sources */, 29A427351BDBE435001E6E22 /* wren_opt_meta.c in Sources */, 29DC14AB1BBA3038008A8274 /* foreign_class.c in Sources */, - 29DC14AC1BBA303D008A8274 /* returns.c in Sources */, + 29DC14AC1BBA303D008A8274 /* slots.c in Sources */, 29DC14AD1BBA3040008A8274 /* value.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0;