From 0ac793d4f88daa62a8433dff7fbf95e34016334b Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Thu, 24 Dec 2015 10:12:12 -0800 Subject: [PATCH 1/7] Add Directory.list() to io. --- src/cli/modules.c | 6 +- src/module/io.c | 152 +++++++++++++-------- src/module/io.wren | 33 +++++ src/module/io.wren.inc | 33 +++++ test/io/directory/dir/a.txt | 1 + test/io/directory/dir/b.txt | 1 + test/io/directory/dir/c.txt | 1 + test/io/directory/list.wren | 8 ++ test/io/directory/list_file.wren | 3 + test/io/directory/list_nonexistent.wren | 3 + test/io/directory/list_wrong_arg_type.wren | 3 + 11 files changed, 188 insertions(+), 56 deletions(-) create mode 100644 test/io/directory/dir/a.txt create mode 100644 test/io/directory/dir/b.txt create mode 100644 test/io/directory/dir/c.txt create mode 100644 test/io/directory/list.wren create mode 100644 test/io/directory/list_file.wren create mode 100644 test/io/directory/list_nonexistent.wren create mode 100644 test/io/directory/list_wrong_arg_type.wren 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 9f3a836b..ac1cc789 100644 --- a/src/module/io.c +++ b/src/module/io.c @@ -46,6 +46,96 @@ void ioShutdown() shutdownStdin(); } +// If [request] failed with an error, sends the runtime error to the VM and +// frees the request. +// +// Returns true if an error was reported. +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); + return true; +} + +// Allocates a new request that resumes [fiber] when it completes. +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; +} + +// Releases resources used by [request]. +// +// Returns the fiber that should be resumed after [request] completes. +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 directoryListCallback(uv_fs_t* request) +{ + if (handleRequestError(request)) return; + + uv_dirent_t entry; + + // TODO: Right now, there's 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 resultLength = 0; + size_t bufferSize = 1024; + char* result = (char*)malloc(bufferSize); + + while (uv_fs_scandir_next(request, &entry) != UV_EOF) + { + size_t length = strlen(entry.name); + + // Grow the buffer if needed. + while (resultLength + length + 1 > bufferSize) + { + bufferSize *= 2; + result = realloc(result, bufferSize); + } + + // Copy the path, including the null terminator. + memcpy(result + resultLength, entry.name, length + 1); + resultLength += length + 1; + } + + WrenValue* fiber = freeRequest(request); + schedulerResumeBytes(fiber, result, resultLength); + free(result); +} + +void directoryList(WrenVM* vm) +{ + const char* path = wrenGetArgumentString(vm, 1); + + uv_fs_t* request = createRequest(wrenGetArgumentValue(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 @@ -66,59 +156,12 @@ void fileFinalize(WrenVM* vm) uv_fs_req_cleanup(&request); } -// If [request] failed with an error, sends the runtime error to the VM and -// frees the request. -// -// Returns true if an error was reported. -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); - return true; -} - -// Allocates a new request that resumes [fiber] when it completes. -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; -} - -// Releases resources used by [request]. -// -// Returns the fiber that should be resumed after [request] completes. -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 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(wrenGetArgumentValue(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 = wrenGetArgumentString(vm, 1); uv_fs_t* request = createRequest(wrenGetArgumentValue(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(wrenGetArgumentValue(vm, 1)); - uv_fs_close(getLoop(), request, fd, closeCallback); + uv_fs_close(getLoop(), request, fd, fileCloseCallback); wrenReturnBool(vm, false); } @@ -226,7 +268,7 @@ void fileSize(WrenVM* vm) int fd = *(int*)wrenGetArgumentForeign(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/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. From 8203baf7bd2baf62c86a398fd0ecaf9adf1c89ef Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Sat, 26 Dec 2015 20:08:20 -0800 Subject: [PATCH 2/7] Initialize match in the test script. Thanks, Michel! --- util/test.py | 1 + 1 file changed, 1 insertion(+) 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) From 8ab329ff91553f49cbdb3161529efbd3e7627c5e Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Sat, 26 Dec 2015 20:11:48 -0800 Subject: [PATCH 3/7] Make wren.mk log wren_to_c_string. Thanks, Michel! --- util/wren.mk | 3 +++ util/wren_to_c_string.py | 2 -- 2 files changed, 3 insertions(+), 2 deletions(-) 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() From 4ad4953196122e1412d4dff55032df853d388829 Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Sat, 26 Dec 2015 20:12:35 -0800 Subject: [PATCH 4/7] Fix typo. Thanks, Michel! --- src/vm/wren_compiler.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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. From a5dc5e83903f1e4e1337213d9e3db57b661b7b6b Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Sat, 26 Dec 2015 20:13:39 -0800 Subject: [PATCH 5/7] Sort cases alphabetically. Thanks, Michel! --- src/vm/wren_value.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vm/wren_value.c b/src/vm/wren_value.c index c0970762..c40cb423 100644 --- a/src/vm/wren_value.c +++ b/src/vm/wren_value.c @@ -1156,10 +1156,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; } From 5b90896fa847a5e8ab79502d4a1d383ed0c5d58f Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Sat, 26 Dec 2015 20:15:00 -0800 Subject: [PATCH 6/7] Print object type for unknown object error. Thanks, Michel! --- src/vm/wren_debug.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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; } } From fbc76cdc058da54fb1ec9750f8c1b0484f607a29 Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Sat, 26 Dec 2015 20:26:05 -0800 Subject: [PATCH 7/7] Fix C++ compile error in Directory.list(). --- src/module/io.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/module/io.c b/src/module/io.c index ac1cc789..580bf876 100644 --- a/src/module/io.c +++ b/src/module/io.c @@ -98,32 +98,32 @@ static void directoryListCallback(uv_fs_t* request) uv_dirent_t entry; - // TODO: Right now, there's 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 resultLength = 0; - size_t bufferSize = 1024; - char* result = (char*)malloc(bufferSize); + // 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 (resultLength + length + 1 > bufferSize) + while (bufferLength + length + 1 > bufferCapacity) { - bufferSize *= 2; - result = realloc(result, bufferSize); + bufferCapacity *= 2; + buffer = (char*)realloc(buffer, bufferCapacity); } // Copy the path, including the null terminator. - memcpy(result + resultLength, entry.name, length + 1); - resultLength += length + 1; + memcpy(buffer + bufferLength, entry.name, length + 1); + bufferLength += length + 1; } WrenValue* fiber = freeRequest(request); - schedulerResumeBytes(fiber, result, resultLength); - free(result); + schedulerResumeBytes(fiber, buffer, bufferLength); + free(buffer); } void directoryList(WrenVM* vm)