Start sketching in support for reading from stdin.

This commit is contained in:
Bob Nystrom
2015-10-16 21:05:24 -07:00
parent c714c53c83
commit d431c2eaa8
12 changed files with 263 additions and 16 deletions
+97 -2
View File
@@ -9,6 +9,36 @@
#include <stdio.h>
static const int stdinDescriptor = 0;
// Handle to Stdin.onData_(). Called when libuv provides data on stdin.
static WrenValue* stdinOnData = NULL;
// The stream used to read from stdin. Initialized on the first read.
static uv_stream_t* stdinStream = NULL;
// Frees all resources related to stdin.
static void shutdownStdin()
{
if (stdinStream != NULL)
{
uv_close((uv_handle_t*)stdinStream, NULL);
free(stdinStream);
stdinStream = NULL;
}
if (stdinOnData != NULL)
{
wrenReleaseValue(getVM(), stdinOnData);
stdinOnData = NULL;
}
}
void ioShutdown()
{
shutdownStdin();
}
void fileAllocate(WrenVM* vm)
{
// Store the file descriptor in the foreign data, so that we can get to it
@@ -137,7 +167,7 @@ void fileDescriptor(WrenVM* vm)
wrenReturnDouble(vm, fd);
}
static void readBytesCallback(uv_fs_t* request)
static void fileReadBytesCallback(uv_fs_t* request)
{
if (handleRequestError(request)) return;
@@ -165,7 +195,7 @@ void fileReadBytes(WrenVM* vm)
buffer.base = (char*)malloc(buffer.len);
// TODO: Allow passing in offset.
uv_fs_read(getLoop(), request, fd, &buffer, 1, 0, readBytesCallback);
uv_fs_read(getLoop(), request, fd, &buffer, 1, 0, fileReadBytesCallback);
}
void fileSize(WrenVM* vm)
@@ -177,3 +207,68 @@ void fileSize(WrenVM* vm)
uv_fs_fstat(getLoop(), request, fd, sizeCallback);
}
static void allocCallback(uv_handle_t* handle, size_t suggestedSize,
uv_buf_t* buf)
{
// TODO: Handle allocation failure.
buf->base = malloc(suggestedSize);
buf->len = 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;
}
// TODO: Handle other errors.
if (stdinOnData == NULL)
{
stdinOnData = wrenGetMethod(getVM(), "io", "Stdin", "onData_(_)");
}
// 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);
// TODO: Likewise, freeing this after we resume is lame.
free(buffer->base);
}
void stdinReadStart(WrenVM* vm)
{
if (stdinStream == NULL)
{
if (uv_guess_handle(stdinDescriptor) == UV_TTY)
{
// stdin is connected to a terminal.
uv_tty_t* handle = (uv_tty_t*)malloc(sizeof(uv_tty_t));
uv_tty_init(getLoop(), handle, stdinDescriptor, true);
stdinStream = (uv_stream_t*)handle;
}
else
{
// stdin is a pipe or a file.
uv_pipe_t* handle = (uv_pipe_t*)malloc(sizeof(uv_pipe_t));
uv_pipe_init(getLoop(), handle, false);
uv_pipe_open(handle, stdinDescriptor);
stdinStream = (uv_stream_t*)handle;
}
}
uv_read_start(stdinStream, allocCallback, stdinReadCallback);
// TODO: Check return.
}
void stdinReadStop(WrenVM* vm)
{
uv_read_stop(stdinStream);
}
+11
View File
@@ -0,0 +1,11 @@
#ifndef io_h
#define io_h
#include "wren.h"
// Frees up any pending resources in use by the IO module.
//
// In particular, this closes down the stdin stream.
void ioShutdown();
#endif
+55
View File
@@ -67,3 +67,58 @@ foreign class File {
foreign readBytes_(count, fiber)
foreign size_(fiber)
}
class Stdin {
static readLine() {
if (__isClosed == true) {
Fiber.abort("Stdin was closed.")
}
// TODO: Error if other fiber is already waiting.
readStart_()
__waitingFiber = Fiber.current
var line = Scheduler.runNextScheduled_()
readStop_()
return line
}
static onData_(data) {
if (data == null) {
__isClosed = true
readStop_()
if (__line != null) {
var line = __line
__line = null
if (__waitingFiber != null) __waitingFiber.transfer(line)
} else {
__waitingFiber.transferError("Stdin was closed.")
}
}
// TODO: Handle Windows line separators.
var lineSeparator = data.indexOf("\n")
if (__line == null) __line = ""
if (lineSeparator == -1) {
// No end of line yet, so just accumulate it.
__line = __line + data
} else {
// Split the line at the separator.
var line = __line + data[0...lineSeparator]
if (lineSeparator > 0 && lineSeparator < data.count - 1) {
// Buffer up the characters after the separator for the next line.
__line = data[lineSeparator + 1..-1]
} else {
__line = ""
}
if (__waitingFiber != null) __waitingFiber.transfer(line)
}
}
foreign static readStart_()
foreign static readStop_()
}
+55
View File
@@ -68,4 +68,59 @@ static const char* ioModuleSource =
" foreign descriptor\n"
" foreign readBytes_(count, fiber)\n"
" foreign size_(fiber)\n"
"}\n"
"\n"
"class Stdin {\n"
" static readLine() {\n"
" if (__isClosed == true) {\n"
" Fiber.abort(\"Stdin was closed.\")\n"
" }\n"
"\n"
" // TODO: Error if other fiber is already waiting.\n"
" readStart_()\n"
"\n"
" __waitingFiber = Fiber.current\n"
" var line = Scheduler.runNextScheduled_()\n"
"\n"
" readStop_()\n"
" return line\n"
" }\n"
"\n"
" static onData_(data) {\n"
" if (data == null) {\n"
" __isClosed = true\n"
" readStop_()\n"
"\n"
" if (__line != null) {\n"
" var line = __line\n"
" __line = null\n"
" if (__waitingFiber != null) __waitingFiber.transfer(line)\n"
" } else {\n"
" __waitingFiber.transferError(\"Stdin was closed.\")\n"
" }\n"
" }\n"
"\n"
" // TODO: Handle Windows line separators.\n"
" var lineSeparator = data.indexOf(\"\n\")\n"
"\n"
" if (__line == null) __line = \"\"\n"
" if (lineSeparator == -1) {\n"
" // No end of line yet, so just accumulate it.\n"
" __line = __line + data\n"
" } else {\n"
" // Split the line at the separator.\n"
" var line = __line + data[0...lineSeparator]\n"
" if (lineSeparator > 0 && lineSeparator < data.count - 1) {\n"
" // Buffer up the characters after the separator for the next line.\n"
" __line = data[lineSeparator + 1..-1]\n"
" } else {\n"
" __line = \"\"\n"
" }\n"
"\n"
" if (__waitingFiber != null) __waitingFiber.transfer(line)\n"
" }\n"
" }\n"
"\n"
" foreign static readStart_()\n"
" foreign static readStop_()\n"
"}\n";
+1 -1
View File
@@ -66,7 +66,7 @@ void schedulerResumeError(WrenValue* fiber, const char* error)
callResume(resumeError, fiber, "vs", fiber, error);
}
void schedulerReleaseMethods()
void schedulerShutdown()
{
if (resume != NULL) wrenReleaseValue(getVM(), resume);
if (resumeWithArg != NULL) wrenReleaseValue(getVM(), resumeWithArg);
+1 -1
View File
@@ -9,6 +9,6 @@ void schedulerResumeDouble(WrenValue* fiber, double value);
void schedulerResumeString(WrenValue* fiber, const char* text);
void schedulerResumeError(WrenValue* fiber, const char* error);
void schedulerReleaseMethods();
void schedulerShutdown();
#endif