1
0
forked from Mirror/wren

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

View File

@ -15,6 +15,8 @@ extern void fileClose(WrenVM* vm);
extern void fileDescriptor(WrenVM* vm);
extern void fileReadBytes(WrenVM* vm);
extern void fileSize(WrenVM* vm);
extern void stdinReadStart(WrenVM* vm);
extern void stdinReadStop(WrenVM* vm);
extern void schedulerCaptureMethods(WrenVM* vm);
extern void timerStartTimer(WrenVM* vm);
@ -33,7 +35,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 2
#define MAX_CLASSES_PER_MODULE 3
// Describes one foreign method in a class.
typedef struct
@ -68,11 +70,11 @@ typedef struct
// To locate foreign classes and modules, we build a big directory for them in
// static data. The nested collection initializer syntax gets pretty noisy, so
// define a couple of macros to make it easier.
#define MODULE(name) { #name, &name##ModuleSource, { {
#define END_MODULE }, {NULL, {}} } },
#define MODULE(name) { #name, &name##ModuleSource, {
#define END_MODULE {NULL, {}} }, },
#define CLASS(name) #name, {
#define END_CLASS {false, NULL, NULL} }
#define CLASS(name) { #name, {
#define END_CLASS {false, NULL, NULL} }, },
#define METHOD(signature, fn) {false, signature, fn},
#define STATIC_METHOD(signature, fn) {true, signature, fn},
@ -91,14 +93,16 @@ static ModuleRegistry modules[] =
METHOD("readBytes_(_,_)", fileReadBytes)
METHOD("size_(_)", fileSize)
END_CLASS
CLASS(Stdin)
STATIC_METHOD("readStart_()", stdinReadStart)
STATIC_METHOD("readStop_()", stdinReadStop)
END_CLASS
END_MODULE
MODULE(scheduler)
CLASS(Scheduler)
STATIC_METHOD("captureMethods_()", schedulerCaptureMethods)
END_CLASS
END_MODULE
MODULE(timer)
CLASS(Timer)
STATIC_METHOD("startTimer_(_,_)", timerStartTimer)

View File

@ -1,6 +1,7 @@
#include <stdio.h>
#include <string.h>
#include "io.h"
#include "modules.h"
#include "scheduler.h"
#include "vm.h"
@ -152,6 +153,7 @@ static WrenForeignClassMethods bindForeignClass(
static void write(WrenVM* vm, const char* text)
{
printf("%s", text);
fflush(stdout);
}
static void initVM()
@ -175,12 +177,15 @@ static void initVM()
static void freeVM()
{
schedulerReleaseMethods();
ioShutdown();
schedulerShutdown();
uv_loop_close(loop);
free(loop);
wrenFreeVM(vm);
uv_tty_reset_mode();
}
void runFile(const char* path)

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
src/module/io.h Normal file
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

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_()
}

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

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

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

View File

@ -0,0 +1,9 @@
import "io" for Stdin
Stdin.readLine() // stdin: one line
var error = Fiber.new {
Stdin.readLine()
}.try()
System.print(error) // expect: Stdin was closed.

View File

@ -0,0 +1,11 @@
import "io" for Stdin
System.write("> ")
System.print("1 " + Stdin.readLine())
System.write("> ")
System.print("2 " + Stdin.readLine())
// stdin: first
// stdin: second
// expect: > 1 first
// expect: > 2 second

View File

@ -83,7 +83,7 @@ class Test:
match = STDIN_PATTERN.search(line)
if match:
input_lines.append(match.group(1) + '\n')
input_lines.append(match.group(1))
match = SKIP_PATTERN.search(line)
if match:
@ -99,9 +99,9 @@ class Test:
line_num += 1
# If any input is fed to the test in stdin, concatetate it into one string.
# If any input is fed to the test in stdin, concatenate it into one string.
if input_lines:
self.input_bytes = "".join(input_lines).encode("utf-8")
self.input_bytes = "\n".join(input_lines).encode("utf-8")
# If we got here, it's a valid test.
return True

View File

@ -109,6 +109,7 @@
29D009AD1B7E39A8000CE58C /* value.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = value.h; path = ../../test/api/value.h; sourceTree = "<group>"; };
29DE39511AC3A50A00987D41 /* wren_meta.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = wren_meta.c; path = ../../src/vm/wren_meta.c; sourceTree = "<group>"; };
29DE39521AC3A50A00987D41 /* wren_meta.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = wren_meta.h; path = ../../src/vm/wren_meta.h; sourceTree = "<group>"; };
29F384111BD19706002F84E0 /* io.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = io.h; path = ../../src/module/io.h; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -134,6 +135,7 @@
2901D7611B74F3E20083A2C8 /* module */ = {
isa = PBXGroup;
children = (
29F384111BD19706002F84E0 /* io.h */,
29729F2E1BA70A620099CA20 /* io.c */,
29729F301BA70A620099CA20 /* io.wren.inc */,
291647C31BA5EA45006142EE /* scheduler.h */,