Start sketching in support for reading from stdin.
This commit is contained in:
+97
-2
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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_()
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user