summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--internal/vm.h2
-rw-r--r--io.c35
-rw-r--r--io.rb13
-rw-r--r--test/ruby/test_io.rb104
-rw-r--r--vm.c11
5 files changed, 150 insertions, 15 deletions
diff --git a/internal/vm.h b/internal/vm.h
index 571cf73c52..d10c14eb4d 100644
--- a/internal/vm.h
+++ b/internal/vm.h
@@ -58,6 +58,8 @@ VALUE rb_yield_refine_block(VALUE refinement, VALUE refinements);
VALUE ruby_vm_special_exception_copy(VALUE);
PUREFUNC(st_table *rb_vm_fstring_table(void));
+void rb_lastline_set_up(VALUE val, unsigned int up);
+
/* vm_eval.c */
VALUE rb_current_realfilepath(void);
VALUE rb_check_block_call(VALUE, ID, int, const VALUE *, rb_block_call_func_t, VALUE);
diff --git a/io.c b/io.c
index 779f671876..dd42423c25 100644
--- a/io.c
+++ b/io.c
@@ -4357,22 +4357,28 @@ rb_io_set_lineno(VALUE io, VALUE lineno)
return lineno;
}
-/*
- * call-seq:
- * readline(sep = $/, chomp: false) -> string
- * readline(limit, chomp: false) -> string
- * readline(sep, limit, chomp: false) -> string
- *
- * Reads a line as with IO#gets, but raises EOFError if already at end-of-stream.
- *
- * Optional keyword argument +chomp+ specifies whether line separators
- * are to be omitted.
- */
-
+/* :nodoc: */
static VALUE
-rb_io_readline(int argc, VALUE *argv, VALUE io)
+io_readline(rb_execution_context_t *ec, VALUE io, VALUE sep, VALUE lim, VALUE chomp)
{
- VALUE line = rb_io_gets_m(argc, argv, io);
+ if (NIL_P(lim)) {
+ // If sep is specified, but it's not a string and not nil, then assume
+ // it's the limit (it should be an integer)
+ if (!NIL_P(sep) && NIL_P(rb_check_string_type(sep))) {
+ // If the user has specified a non-nil / non-string value
+ // for the separator, we assume it's the limit and set the
+ // separator to default: rb_rs.
+ lim = sep;
+ sep = rb_rs;
+ }
+ }
+
+ if (!NIL_P(sep)) {
+ StringValue(sep);
+ }
+
+ VALUE line = rb_io_getline_1(sep, NIL_P(lim) ? -1L : NUM2LONG(lim), RTEST(chomp), io);
+ rb_lastline_set_up(line, 1);
if (NIL_P(line)) {
rb_eof_error();
@@ -15420,7 +15426,6 @@ Init_IO(void)
rb_define_method(rb_cIO, "read", io_read, -1);
rb_define_method(rb_cIO, "write", io_write_m, -1);
rb_define_method(rb_cIO, "gets", rb_io_gets_m, -1);
- rb_define_method(rb_cIO, "readline", rb_io_readline, -1);
rb_define_method(rb_cIO, "getc", rb_io_getc, 0);
rb_define_method(rb_cIO, "getbyte", rb_io_getbyte, 0);
rb_define_method(rb_cIO, "readchar", rb_io_readchar, 0);
diff --git a/io.rb b/io.rb
index 40873ea4fd..549c5e62bd 100644
--- a/io.rb
+++ b/io.rb
@@ -120,4 +120,17 @@ class IO
def write_nonblock(buf, exception: true)
Primitive.io_write_nonblock(buf, exception)
end
+
+ # call-seq:
+ # readline(sep = $/, chomp: false) -> string
+ # readline(limit, chomp: false) -> string
+ # readline(sep, limit, chomp: false) -> string
+ #
+ # Reads a line as with IO#gets, but raises EOFError if already at end-of-stream.
+ #
+ # Optional keyword argument +chomp+ specifies whether line separators
+ # are to be omitted.
+ def readline(sep = $/, limit = nil, chomp: false)
+ Primitive.io_readline(sep, limit, chomp)
+ end
end
diff --git a/test/ruby/test_io.rb b/test/ruby/test_io.rb
index 7689b52e23..476d9f882f 100644
--- a/test/ruby/test_io.rb
+++ b/test/ruby/test_io.rb
@@ -1898,6 +1898,110 @@ class TestIO < Test::Unit::TestCase
end)
end
+ def test_readline_bad_param_raises
+ File.open(__FILE__) do |f|
+ assert_raise(TypeError) do
+ f.readline Object.new
+ end
+ end
+
+ File.open(__FILE__) do |f|
+ assert_raise(TypeError) do
+ f.readline 1, 2
+ end
+ end
+ end
+
+ def test_readline_raises
+ File.open(__FILE__) do |f|
+ assert_equal File.read(__FILE__), f.readline(nil)
+ assert_raise(EOFError) do
+ f.readline
+ end
+ end
+ end
+
+ def test_readline_separators
+ File.open(__FILE__) do |f|
+ line = f.readline("def")
+ assert_equal File.read(__FILE__)[/\A.*?def/m], line
+ end
+
+ File.open(__FILE__) do |f|
+ line = f.readline("def", chomp: true)
+ assert_equal File.read(__FILE__)[/\A.*?(?=def)/m], line
+ end
+ end
+
+ def test_readline_separators_limits
+ t = Tempfile.open("readline_limit")
+ str = "#" * 50
+ sep = "def"
+
+ t.write str
+ t.write sep
+ t.write str
+ t.flush
+
+ # over limit
+ File.open(t.path) do |f|
+ line = f.readline sep, str.bytesize
+ assert_equal(str, line)
+ end
+
+ # under limit
+ File.open(t.path) do |f|
+ line = f.readline(sep, str.bytesize + 5)
+ assert_equal(str + sep, line)
+ end
+
+ # under limit + chomp
+ File.open(t.path) do |f|
+ line = f.readline(sep, str.bytesize + 5, chomp: true)
+ assert_equal(str, line)
+ end
+ ensure
+ t&.close!
+ end
+
+ def test_readline_limit_without_separator
+ t = Tempfile.open("readline_limit")
+ str = "#" * 50
+ sep = "\n"
+
+ t.write str
+ t.write sep
+ t.write str
+ t.flush
+
+ # over limit
+ File.open(t.path) do |f|
+ line = f.readline str.bytesize
+ assert_equal(str, line)
+ end
+
+ # under limit
+ File.open(t.path) do |f|
+ line = f.readline(str.bytesize + 5)
+ assert_equal(str + sep, line)
+ end
+
+ # under limit + chomp
+ File.open(t.path) do |f|
+ line = f.readline(str.bytesize + 5, chomp: true)
+ assert_equal(str, line)
+ end
+ ensure
+ t&.close!
+ end
+
+ def test_readline_chomp_true
+ File.open(__FILE__) do |f|
+ line = f.readline(chomp: true)
+ assert_equal File.readlines(__FILE__).first.chomp, line
+ end
+ end
+
def test_set_lineno_readline
pipe(proc do |w|
w.puts "foo"
diff --git a/vm.c b/vm.c
index ecf8c8ba74..f1529a3ea8 100644
--- a/vm.c
+++ b/vm.c
@@ -1750,6 +1750,17 @@ rb_lastline_set(VALUE val)
vm_svar_set(GET_EC(), VM_SVAR_LASTLINE, val);
}
+void
+rb_lastline_set_up(VALUE val, unsigned int up)
+{
+ rb_control_frame_t * cfp = GET_EC()->cfp;
+
+ for(unsigned int i = 0; i < up; i++) {
+ cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp);
+ }
+ vm_cfp_svar_set(GET_EC(), cfp, VM_SVAR_LASTLINE, val);
+}
+
/* misc */
const char *