1
0
forked from Mirror/wren

Add Directory.list() to io.

This commit is contained in:
Bob Nystrom
2015-12-24 10:12:12 -08:00
parent 1ebc711c30
commit 0ac793d4f8
11 changed files with 188 additions and 56 deletions

View File

@ -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)

View File

@ -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,

View File

@ -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)

View File

@ -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"

View File

@ -0,0 +1 @@
this is a text file

View File

@ -0,0 +1 @@
this is a text file

View File

@ -0,0 +1 @@
this is a text file

View 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]

View 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

View File

@ -0,0 +1,3 @@
import "io" for Directory
Directory.list("nonexistent") // expect runtime error: no such file or directory

View File

@ -0,0 +1,3 @@
import "io" for Directory
Directory.list(123) // expect runtime error: Path must be a string.