diff --git a/src/cli/modules.c b/src/cli/modules.c index 093844e6..27111e4c 100644 --- a/src/cli/modules.c +++ b/src/cli/modules.c @@ -7,6 +7,7 @@ #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 fileOpen(WrenVM* vm); @@ -35,7 +36,7 @@ extern void timerStartTimer(WrenVM* vm); // 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 @@ -87,6 +88,9 @@ typedef struct static ModuleRegistry modules[] = { MODULE(io) + CLASS(Directory) + STATIC_METHOD("list_(_,_)", directoryList) + END_CLASS CLASS(File) STATIC_METHOD("", fileAllocate) STATIC_METHOD("", fileFinalize) diff --git a/src/module/io.c b/src/module/io.c index d118e6e1..284f5a20 100644 --- a/src/module/io.c +++ b/src/module/io.c @@ -46,26 +46,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)wrenGetSlotDouble(vm, 1); -} - -void fileFinalize(WrenVM* vm) -{ - int fd = *(int*)wrenGetSlotForeign(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. // @@ -76,9 +56,9 @@ static bool handleRequestError(uv_fs_t* request) 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 +69,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,21 +84,84 @@ 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; + + // TODO: Right now, there's no way to create a list using the C API, so + // create a buffer containing all of the result paths terminated by '\0'. + // We'll split that back into a list in Wren. + size_t bufferLength = 0; + size_t bufferCapacity = 1024; + char* buffer = (char*)malloc(bufferCapacity); + + while (uv_fs_scandir_next(request, &entry) != UV_EOF) + { + size_t length = strlen(entry.name); + + // Grow the buffer if needed. + while (bufferLength + length + 1 > bufferCapacity) + { + bufferCapacity *= 2; + buffer = (char*)realloc(buffer, bufferCapacity); + } + + // Copy the path, including the null terminator. + memcpy(buffer + bufferLength, entry.name, length + 1); + bufferLength += length + 1; + } + + WrenValue* fiber = freeRequest(request); + schedulerResumeBytes(fiber, buffer, bufferLength); + free(buffer); +} + +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(WrenVM* vm) +{ + int fd = *(int*)wrenGetSlotForeign(vm, 0); + + // 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); } @@ -128,17 +171,16 @@ void fileOpen(WrenVM* vm) 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); } @@ -146,10 +188,10 @@ void fileSizePath(WrenVM* vm) { const char* path = wrenGetSlotString(vm, 1); uv_fs_t* request = createRequest(wrenGetSlotValue(vm, 2)); - uv_fs_stat(getLoop(), request, path, sizeCallback); + uv_fs_stat(getLoop(), request, path, fileSizeCallback); } -static void closeCallback(uv_fs_t* request) +static void fileCloseCallback(uv_fs_t* request) { if (handleRequestError(request)) return; @@ -173,7 +215,7 @@ void fileClose(WrenVM* vm) *foreign = -1; uv_fs_t* request = createRequest(wrenGetSlotValue(vm, 1)); - uv_fs_close(getLoop(), request, fd, closeCallback); + uv_fs_close(getLoop(), request, fd, fileCloseCallback); wrenSetSlotBool(vm, 0, false); } @@ -226,7 +268,7 @@ void fileSize(WrenVM* vm) 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, diff --git a/src/module/io.wren b/src/module/io.wren index 03ca9ce0..d641743a 100644 --- a/src/module/io.wren +++ b/src/module/io.wren @@ -1,5 +1,37 @@ import "scheduler" for Scheduler +class Directory { + static list(path) { + if (!(path is String)) Fiber.abort("Path must be a string.") + + list_(path, Fiber.current) + + // We get back a byte array containing all of the paths, each terminated by + // a zero byte. + var entryBuffer = Scheduler.runNextScheduled_() + + // TODO: Add split() to String. + // Split it into an array of strings. + var entries = [] + var start = 0 + var i = 0 + var bytes = entryBuffer.bytes + while (i < bytes.count) { + if (bytes[i] == 0) { + var entry = entryBuffer[start...i] + start = i + 1 + entries.add(entry) + } + + i = i + 1 + } + + return entries + } + + foreign static list_(path, fiber) +} + foreign class File { static open(path) { if (!(path is String)) Fiber.abort("Path must be a string.") @@ -90,6 +122,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 b9b5272f..32b62086 100644 --- a/src/module/io.wren.inc +++ b/src/module/io.wren.inc @@ -2,6 +2,38 @@ static const char* ioModuleSource = "import \"scheduler\" for Scheduler\n" "\n" +"class Directory {\n" +" static list(path) {\n" +" if (!(path is String)) Fiber.abort(\"Path must be a string.\")\n" +"\n" +" list_(path, Fiber.current)\n" +"\n" +" // We get back a byte array containing all of the paths, each terminated by\n" +" // a zero byte.\n" +" var entryBuffer = Scheduler.runNextScheduled_()\n" +"\n" +" // TODO: Add split() to String.\n" +" // Split it into an array of strings.\n" +" var entries = []\n" +" var start = 0\n" +" var i = 0\n" +" var bytes = entryBuffer.bytes\n" +" while (i < bytes.count) {\n" +" if (bytes[i] == 0) {\n" +" var entry = entryBuffer[start...i]\n" +" start = i + 1\n" +" entries.add(entry)\n" +" }\n" +"\n" +" i = i + 1\n" +" }\n" +"\n" +" return entries\n" +" }\n" +"\n" +" foreign static list_(path, fiber)\n" +"}\n" +"\n" "foreign class File {\n" " static open(path) {\n" " if (!(path is String)) Fiber.abort(\"Path must be a string.\")\n" @@ -92,6 +124,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/vm/wren_compiler.c b/src/vm/wren_compiler.c index 14ff8781..80af5451 100644 --- a/src/vm/wren_compiler.c +++ b/src/vm/wren_compiler.c @@ -1321,7 +1321,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_debug.c b/src/vm/wren_debug.c index c44522ad..572c826d 100644 --- a/src/vm/wren_debug.c +++ b/src/vm/wren_debug.c @@ -50,7 +50,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 6d1bd4c3..ea576b9a 100644 --- a/src/vm/wren_value.c +++ b/src/vm/wren_value.c @@ -1160,10 +1160,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/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/util/test.py b/util/test.py index 8e5bc0fb..1f9454b5 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:] for stack_line in stack_lines: match = STACK_TRACE_PATTERN.search(stack_line) 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()