diff options
-rw-r--r-- | internal/vm.h | 2 | ||||
-rw-r--r-- | io.c | 35 | ||||
-rw-r--r-- | io.rb | 13 | ||||
-rw-r--r-- | test/ruby/test_io.rb | 104 | ||||
-rw-r--r-- | vm.c | 11 |
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); @@ -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); @@ -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" @@ -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 * |