mirror of
https://github.com/ruby/ruby.git
synced 2025-08-15 13:39:04 +02:00
When reading from stdin, put a wrapper around the IO object
The purpose of this commit is to fix Bug #21188. We need to detect when stdin has run in to an EOF case. Unfortunately we can't _call_ the eof function on IO because it will block. Here is a short script to demonstrate the issue: ```ruby x = STDIN.gets puts x puts x.eof? ``` If you run the script, then type some characters (but _NOT_ a newline), then hit Ctrl-D twice, it will print the input string. Unfortunately, calling `eof?` will try to read from STDIN again causing us to need a 3rd Ctrl-D to exit the program. Before introducing the EOF callback to Prism, the input loop looked kind of like this: ```ruby loop do str = STDIN.gets process(str) if str.nil? p :DONE end end ``` Which required 3 Ctrl-D to exit. If we naively changed it to something like this: ```ruby loop do str = STDIN.gets process(str) if STDIN.eof? p :DONE end end ``` It would still require 3 Ctrl-D because `eof?` would block. In this patch, we're wrapping the IO object, checking the buffer for a newline and length, and then using that to simulate a non-blocking eof? method. This commit wraps STDIN and emulates a non-blocking `eof` function. [Bug #21188]
This commit is contained in:
parent
1c6b36af18
commit
89d89fa49d
5 changed files with 66 additions and 13 deletions
|
@ -11492,6 +11492,18 @@ pm_parse_string(pm_parse_result_t *result, VALUE source, VALUE filepath, VALUE *
|
|||
return pm_parse_process(result, node, script_lines);
|
||||
}
|
||||
|
||||
struct rb_stdin_wrapper {
|
||||
VALUE rb_stdin;
|
||||
int eof_seen;
|
||||
};
|
||||
|
||||
static int
|
||||
pm_parse_stdin_eof(void *stream)
|
||||
{
|
||||
struct rb_stdin_wrapper * wrapped_stdin = (struct rb_stdin_wrapper *)stream;
|
||||
return wrapped_stdin->eof_seen;
|
||||
}
|
||||
|
||||
/**
|
||||
* An implementation of fgets that is suitable for use with Ruby IO objects.
|
||||
*/
|
||||
|
@ -11500,7 +11512,9 @@ pm_parse_stdin_fgets(char *string, int size, void *stream)
|
|||
{
|
||||
RUBY_ASSERT(size > 0);
|
||||
|
||||
VALUE line = rb_funcall((VALUE) stream, rb_intern("gets"), 1, INT2FIX(size - 1));
|
||||
struct rb_stdin_wrapper * wrapped_stdin = (struct rb_stdin_wrapper *)stream;
|
||||
|
||||
VALUE line = rb_funcall(wrapped_stdin->rb_stdin, rb_intern("gets"), 1, INT2FIX(size - 1));
|
||||
if (NIL_P(line)) {
|
||||
return NULL;
|
||||
}
|
||||
|
@ -11511,6 +11525,13 @@ pm_parse_stdin_fgets(char *string, int size, void *stream)
|
|||
memcpy(string, cstr, length);
|
||||
string[length] = '\0';
|
||||
|
||||
// We're reading strings from stdin via gets. We'll assume that if the
|
||||
// string is smaller than the requested length, and doesn't end with a
|
||||
// newline, that we hit EOF.
|
||||
if (length < (size - 1) && string[length - 1] != '\n') {
|
||||
wrapped_stdin->eof_seen = 1;
|
||||
}
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
|
@ -11527,8 +11548,13 @@ pm_parse_stdin(pm_parse_result_t *result)
|
|||
{
|
||||
pm_options_frozen_string_literal_init(&result->options);
|
||||
|
||||
struct rb_stdin_wrapper wrapped_stdin = {
|
||||
rb_stdin,
|
||||
0
|
||||
};
|
||||
|
||||
pm_buffer_t buffer;
|
||||
pm_node_t *node = pm_parse_stream(&result->parser, &buffer, (void *) rb_stdin, pm_parse_stdin_fgets, &result->options);
|
||||
pm_node_t *node = pm_parse_stream(&result->parser, &buffer, (void *) &wrapped_stdin, pm_parse_stdin_fgets, pm_parse_stdin_eof, &result->options);
|
||||
|
||||
// Copy the allocated buffer contents into the input string so that it gets
|
||||
// freed. At this point we've handed over ownership, so we don't need to
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue