mruby-io: implement ungetbyte in c for improved performance

Moved IO#ungetbyte from Ruby to C implementation to eliminate
boundary crossing overhead and avoid temporary string allocations.
Added io_unget_data helper function to handle raw data operations
efficiently.

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Yukihiro "Matz" Matsumoto
2025-08-16 14:39:14 +09:00
parent 89e07d90e2
commit 0a8a7bb329
2 changed files with 49 additions and 33 deletions
-26
View File
@@ -194,32 +194,6 @@ class IO
seek(0, SEEK_SET)
end
#
# call-seq:
# ios.ungetbyte(string) -> nil
# ios.ungetbyte(integer) -> nil
#
# Pushes back bytes (passed as a parameter) onto ios, such that a subsequent
# buffered character read will return it. Only one byte may be pushed back
# before a subsequent read operation (that is, you will be able to read only
# the last of several bytes that have been pushed back). Has no effect with
# unbuffered reads (such as IO#sysread).
#
# f = File.new("testfile") #=> #<File:testfile>
# b = f.getbyte #=> 0x38
# f.ungetbyte(b) #=> nil
# f.getbyte #=> 0x38
#
def ungetbyte(c)
if c.is_a? String
c = c.getbyte(0)
else
c &= 0xff
end
s = " "
s.setbyte(0,c)
ungetc s
end
#
# call-seq:
+49 -7
View File
@@ -1828,15 +1828,12 @@ io_pwrite(mrb_state *mrb, mrb_value io)
* f.ungetc(c) #=> nil
* f.getc #=> "H"
*/
static mrb_value
io_ungetc(mrb_state *mrb, mrb_value io)
/* Helper function for ungetc operations with raw data */
static void
io_unget_data(mrb_state *mrb, struct mrb_io *fptr, const char *ptr, mrb_int len)
{
struct mrb_io *fptr = io_get_read_fptr(mrb, io);
struct mrb_io_buf *buf = fptr->buf;
mrb_value str;
mrb_get_args(mrb, "S", &str);
mrb_int len = RSTRING_LEN(str);
if (len > SHRT_MAX) {
mrb_raise(mrb, E_ARGUMENT_ERROR, "string too long to ungetc");
}
@@ -1845,9 +1842,53 @@ io_ungetc(mrb_state *mrb, mrb_value io)
buf = fptr->buf;
}
memmove(buf->mem+len, buf->mem+buf->start, buf->len);
memcpy(buf->mem, RSTRING_PTR(str), len);
memcpy(buf->mem, ptr, len);
buf->start = 0;
buf->len += (short)len;
}
static mrb_value
io_ungetc(mrb_state *mrb, mrb_value io)
{
struct mrb_io *fptr = io_get_read_fptr(mrb, io);
mrb_value str;
mrb_get_args(mrb, "S", &str);
io_unget_data(mrb, fptr, RSTRING_PTR(str), RSTRING_LEN(str));
return mrb_nil_value();
}
/*
* call-seq:
* ios.ungetbyte(string) -> nil
* ios.ungetbyte(integer) -> nil
*
* Pushes back bytes (passed as a parameter) onto ios, such that a subsequent
* buffered character read will return it. Only one byte may be pushed back
* before a subsequent read operation (that is, you will be able to read only
* the last of several bytes that have been pushed back). Has no effect with
* unbuffered reads (such as IO#sysread).
*/
static mrb_value
io_ungetbyte(mrb_state *mrb, mrb_value io)
{
struct mrb_io *fptr = io_get_read_fptr(mrb, io);
mrb_value c = mrb_get_arg1(mrb);
unsigned char byte_val;
if (mrb_string_p(c)) {
if (RSTRING_LEN(c) == 0) {
return mrb_nil_value(); /* Empty string, do nothing */
}
byte_val = (unsigned char)RSTRING_PTR(c)[0];
}
else {
mrb_int val = mrb_integer(c);
byte_val = (unsigned char)(val & 0xff);
}
/* Use helper function with single byte */
io_unget_data(mrb, fptr, (const char*)&byte_val, 1);
return mrb_nil_value();
}
@@ -2274,6 +2315,7 @@ mrb_init_io(mrb_state *mrb)
mrb_define_method_id(mrb, io, MRB_SYM_Q(closed), io_closed, MRB_ARGS_NONE()); /* 15.2.20.5.2 */
mrb_define_method_id(mrb, io, MRB_SYM(flush), io_flush, MRB_ARGS_NONE()); /* 15.2.20.5.7 */
mrb_define_method_id(mrb, io, MRB_SYM(ungetc), io_ungetc, MRB_ARGS_REQ(1));
mrb_define_method_id(mrb, io, MRB_SYM(ungetbyte), io_ungetbyte, MRB_ARGS_REQ(1));
mrb_define_method_id(mrb, io, MRB_SYM(pos), io_pos, MRB_ARGS_NONE());
mrb_define_method_id(mrb, io, MRB_SYM(pid), io_pid, MRB_ARGS_NONE());
mrb_define_method_id(mrb, io, MRB_SYM(fileno), io_fileno, MRB_ARGS_NONE());