forked from Mirror/wren
Start sketching in support for reading from stdin.
This commit is contained in:
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
11
src/module/io.h
Normal 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
|
||||
@ -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
|
||||
|
||||
9
test/io/stdin/aborts_on_eof.wren
Normal file
9
test/io/stdin/aborts_on_eof.wren
Normal 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.
|
||||
11
test/io/stdin/read_line.wren
Normal file
11
test/io/stdin/read_line.wren
Normal 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
|
||||
@ -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
|
||||
|
||||
@ -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 */,
|
||||
|
||||
Reference in New Issue
Block a user