From 500dd41ccd56e6e4f273dfe4962c06c7c0d5a09f Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Fri, 20 May 2016 11:50:14 -0700 Subject: [PATCH] Add Stdin.readByte(). --- doc/site/modules/io/stdin.markdown | 7 ++ src/module/io.c | 2 +- src/module/io.wren | 78 ++++++++++++------- src/module/io.wren.inc | 78 ++++++++++++------- test/io/stdin/read_byte.wren | 21 +++++ test/io/stdin/read_byte_eof.wren | 9 +++ ...{aborts_on_eof.wren => read_line_eof.wren} | 0 7 files changed, 140 insertions(+), 55 deletions(-) create mode 100644 test/io/stdin/read_byte.wren create mode 100644 test/io/stdin/read_byte_eof.wren rename test/io/stdin/{aborts_on_eof.wren => read_line_eof.wren} (100%) diff --git a/doc/site/modules/io/stdin.markdown b/doc/site/modules/io/stdin.markdown index 66377f85..12938335 100644 --- a/doc/site/modules/io/stdin.markdown +++ b/doc/site/modules/io/stdin.markdown @@ -4,6 +4,13 @@ The standard input stream. ## Static Methods +### **readByte**() + +Reads one byte of input from stdin. Blocks the current fiber until a byte has +been received. + +Returns the byte value as a number or `null` if stdin is closed. + ### **readLine**() Reads one line of input from stdin. Blocks the current fiber until a full line diff --git a/src/module/io.c b/src/module/io.c index 428d9f62..7c07fdd3 100644 --- a/src/module/io.c +++ b/src/module/io.c @@ -496,7 +496,7 @@ static void allocCallback(uv_handle_t* handle, size_t suggestedSize, } static void stdinReadCallback(uv_stream_t* stream, ssize_t numRead, - const uv_buf_t* buffer) + const uv_buf_t* buffer) { WrenVM* vm = getVM(); diff --git a/src/module/io.wren b/src/module/io.wren index 1a9766c6..3cf14430 100644 --- a/src/module/io.wren +++ b/src/module/io.wren @@ -215,55 +215,79 @@ foreign class Stat { } class Stdin { - static readLine() { - if (__isClosed == true) { - Fiber.abort("Stdin was closed.") + static readByte() { + return read_ { + // Peel off the first byte. + var byte = __buffered.bytes[0] + __buffered = __buffered[1..-1] + return byte } + } + + static readLine() { + return read_ { + // TODO: Handle Windows line separators. + var lineSeparator = __buffered.indexOf("\n") + if (lineSeparator == -1) return null + + // Split the line at the separator. + var line = __buffered[0...lineSeparator] + __buffered = __buffered[lineSeparator + 1..-1] + return line + } + } + + static read_(handleData) { + // See if we're already buffered enough to immediately produce a result. + if (__buffered != null && !__buffered.isEmpty) { + var result = handleData.call() + if (result != null) return result + } + + if (__isClosed == true) Fiber.abort("Stdin was closed.") + + // Otherwise, we need to wait for input to come in. + __handleData = handleData // TODO: Error if other fiber is already waiting. readStart_() __waitingFiber = Fiber.current - var line = Scheduler.runNextScheduled_() + var result = Scheduler.runNextScheduled_() readStop_() - return line + return result } static onData_(data) { + // If data is null, it means stdin just closed. if (data == null) { __isClosed = true readStop_() - if (__line != null) { - // Emit the last line. - var line = __line - __line = null - if (__waitingFiber != null) __waitingFiber.transfer(line) + if (__buffered != null) { + // TODO: Is this correct for readByte()? + // Emit the last remaining bytes. + var result = __buffered + __buffered = null + __waitingFiber.transfer(result) } 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 + // Append to the buffer. + if (__buffered == null) { + __buffered = 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) + // TODO: Instead of concatenating strings each time, it's probably faster + // to keep a list of buffers and flatten lazily. + __buffered = __buffered + data } + + // Ask the data handler if we have a complete result now. + var result = __handleData.call() + if (result != null) __waitingFiber.transfer(result) } foreign static readStart_() diff --git a/src/module/io.wren.inc b/src/module/io.wren.inc index b6cfccb4..2e246a32 100644 --- a/src/module/io.wren.inc +++ b/src/module/io.wren.inc @@ -217,55 +217,79 @@ static const char* ioModuleSource = "}\n" "\n" "class Stdin {\n" -" static readLine() {\n" -" if (__isClosed == true) {\n" -" Fiber.abort(\"Stdin was closed.\")\n" +" static readByte() {\n" +" return read_ {\n" +" // Peel off the first byte.\n" +" var byte = __buffered.bytes[0]\n" +" __buffered = __buffered[1..-1]\n" +" return byte\n" " }\n" +" }\n" +"\n" +" static readLine() {\n" +" return read_ {\n" +" // TODO: Handle Windows line separators.\n" +" var lineSeparator = __buffered.indexOf(\"\n\")\n" +" if (lineSeparator == -1) return null\n" +"\n" +" // Split the line at the separator.\n" +" var line = __buffered[0...lineSeparator]\n" +" __buffered = __buffered[lineSeparator + 1..-1]\n" +" return line\n" +" }\n" +" }\n" +"\n" +" static read_(handleData) {\n" +" // See if we're already buffered enough to immediately produce a result.\n" +" if (__buffered != null && !__buffered.isEmpty) {\n" +" var result = handleData.call()\n" +" if (result != null) return result\n" +" }\n" +"\n" +" if (__isClosed == true) Fiber.abort(\"Stdin was closed.\")\n" +"\n" +" // Otherwise, we need to wait for input to come in.\n" +" __handleData = handleData\n" "\n" " // TODO: Error if other fiber is already waiting.\n" " readStart_()\n" "\n" " __waitingFiber = Fiber.current\n" -" var line = Scheduler.runNextScheduled_()\n" +" var result = Scheduler.runNextScheduled_()\n" "\n" " readStop_()\n" -" return line\n" +" return result\n" " }\n" "\n" " static onData_(data) {\n" +" // If data is null, it means stdin just closed.\n" " if (data == null) {\n" " __isClosed = true\n" " 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" +" if (__buffered != null) {\n" +" // TODO: Is this correct for readByte()?\n" +" // Emit the last remaining bytes.\n" +" var result = __buffered\n" +" __buffered = null\n" +" __waitingFiber.transfer(result)\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" +" // Append to the buffer.\n" +" if (__buffered == null) {\n" +" __buffered = 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" +" // TODO: Instead of concatenating strings each time, it's probably faster\n" +" // to keep a list of buffers and flatten lazily.\n" +" __buffered = __buffered + data\n" " }\n" +"\n" +" // Ask the data handler if we have a complete result now.\n" +" var result = __handleData.call()\n" +" if (result != null) __waitingFiber.transfer(result)\n" " }\n" "\n" " foreign static readStart_()\n" diff --git a/test/io/stdin/read_byte.wren b/test/io/stdin/read_byte.wren new file mode 100644 index 00000000..b6de42fb --- /dev/null +++ b/test/io/stdin/read_byte.wren @@ -0,0 +1,21 @@ +import "io" for Stdin + +for (i in 1...13) { + System.print(Stdin.readByte()) +} + +// stdin: first +// expect: 102 +// expect: 105 +// expect: 114 +// expect: 115 +// expect: 116 +// expect: 10 + +// stdin: second +// expect: 115 +// expect: 101 +// expect: 99 +// expect: 111 +// expect: 110 +// expect: 100 diff --git a/test/io/stdin/read_byte_eof.wren b/test/io/stdin/read_byte_eof.wren new file mode 100644 index 00000000..d1ac3008 --- /dev/null +++ b/test/io/stdin/read_byte_eof.wren @@ -0,0 +1,9 @@ +import "io" for Stdin + +Stdin.readLine() // stdin: one line + +var error = Fiber.new { + Stdin.readByte() +}.try() + +System.print(error) // expect: Stdin was closed. diff --git a/test/io/stdin/aborts_on_eof.wren b/test/io/stdin/read_line_eof.wren similarity index 100% rename from test/io/stdin/aborts_on_eof.wren rename to test/io/stdin/read_line_eof.wren