forked from Mirror/wren
Add Directory.list() to io.
This commit is contained in:
@ -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("<allocate>", fileAllocate)
|
||||
STATIC_METHOD("<finalize>", fileFinalize)
|
||||
|
||||
152
src/module/io.c
152
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,
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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"
|
||||
|
||||
1
test/io/directory/dir/a.txt
Normal file
1
test/io/directory/dir/a.txt
Normal file
@ -0,0 +1 @@
|
||||
this is a text file
|
||||
1
test/io/directory/dir/b.txt
Normal file
1
test/io/directory/dir/b.txt
Normal file
@ -0,0 +1 @@
|
||||
this is a text file
|
||||
1
test/io/directory/dir/c.txt
Normal file
1
test/io/directory/dir/c.txt
Normal file
@ -0,0 +1 @@
|
||||
this is a text file
|
||||
8
test/io/directory/list.wren
Normal file
8
test/io/directory/list.wren
Normal file
@ -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]
|
||||
3
test/io/directory/list_file.wren
Normal file
3
test/io/directory/list_file.wren
Normal file
@ -0,0 +1,3 @@
|
||||
import "io" for Directory
|
||||
|
||||
var entries = Directory.list("test/io/directory/dir/a.txt") // expect runtime error: not a directory
|
||||
3
test/io/directory/list_nonexistent.wren
Normal file
3
test/io/directory/list_nonexistent.wren
Normal file
@ -0,0 +1,3 @@
|
||||
import "io" for Directory
|
||||
|
||||
Directory.list("nonexistent") // expect runtime error: no such file or directory
|
||||
3
test/io/directory/list_wrong_arg_type.wren
Normal file
3
test/io/directory/list_wrong_arg_type.wren
Normal file
@ -0,0 +1,3 @@
|
||||
import "io" for Directory
|
||||
|
||||
Directory.list(123) // expect runtime error: Path must be a string.
|
||||
Reference in New Issue
Block a user