From 786f0aa01521d4c070fc1eee8233bbdf52eb06ef Mon Sep 17 00:00:00 2001 From: "Yukihiro \"Matz\" Matsumoto" Date: Mon, 18 Aug 2025 10:26:09 +0900 Subject: [PATCH] mruby-io: migrate File.extname to C Implement C version of File.extname for better performance: - Direct C string processing instead of Ruby basename + rindex - Efficient path parsing with single pass through string - Proper handling of edge cases (dotfiles, trailing slashes, etc.) - Maintains full compatibility with Ruby implementation Performance improvement: - Eliminates Ruby method call overhead for basename/rindex - Direct C string operations vs Ruby string methods - Faster path processing for file extension extraction Co-authored-by: Claude --- mrbgems/mruby-io/mrblib/file.rb | 19 ----------- mrbgems/mruby-io/src/file.c | 59 +++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 19 deletions(-) diff --git a/mrbgems/mruby-io/mrblib/file.rb b/mrbgems/mruby-io/mrblib/file.rb index ea19d5a0a..d9331ae37 100644 --- a/mrbgems/mruby-io/mrblib/file.rb +++ b/mrbgems/mruby-io/mrblib/file.rb @@ -274,25 +274,6 @@ class File < IO FileTest.zero?(file) end - # - # call-seq: - # File.extname(path) -> string - # - # Returns the extension (the portion of file name in path starting from the - # last period). If path is a dotfile, or starts with a period, then the starting - # dot is not dealt with the start of the extension. - # - # File.extname("test.rb") #=> ".rb" - # File.extname("a/b/d/test.rb") #=> ".rb" - # File.extname("test") #=> "" - # File.extname(".profile") #=> "" - # - def self.extname(filename) - fname = self.basename(filename) - epos = fname.rindex('.') - return '' if epos == 0 || epos.nil? - return fname[epos..-1] - end # # call-seq: diff --git a/mrbgems/mruby-io/src/file.c b/mrbgems/mruby-io/src/file.c index 07db35558..9760c52d4 100644 --- a/mrbgems/mruby-io/src/file.c +++ b/mrbgems/mruby-io/src/file.c @@ -961,6 +961,64 @@ mrb_file_s_readlink(mrb_state *mrb, mrb_value klass) #endif } +/* + * call-seq: + * File.extname(path) -> string + * + * Returns the extension (the portion of file name in path starting from the + * last period). If path is a dotfile, or starts with a period, then the starting + * dot is not dealt with the start of the extension. + * + * File.extname("test.rb") #=> ".rb" + * File.extname("a/b/d/test.rb") #=> ".rb" + * File.extname("test") #=> "" + * File.extname(".profile") #=> "" + */ +static mrb_value +mrb_file_extname(mrb_state *mrb, mrb_value klass) +{ + char *path; + mrb_get_args(mrb, "z", &path); + + size_t len = strlen(path); + if (len == 0) { + return mrb_str_new_lit(mrb, ""); + } + + // Remove trailing slashes to find the actual filename + while (len > 1 && path[len - 1] == '/') { + len--; + } + + // Find the last path separator to get basename + ssize_t base_start = len - 1; + while (base_start >= 0 && path[base_start] != '/') { + base_start--; + } + base_start++; // move to first character after '/' + + // If the result is only slashes, no extension + if ((size_t)base_start == len) { + return mrb_str_new_lit(mrb, ""); + } + + // Look for the last '.' in the basename + ssize_t dot_pos = -1; + for (size_t i = base_start; i < len; i++) { + if (path[i] == '.') { + dot_pos = i; + } + } + + // No dot found, or dot is the first character (dotfile) + if (dot_pos == -1 || dot_pos == (ssize_t)base_start) { + return mrb_str_new_lit(mrb, ""); + } + + // Return extension from dot to end + return mrb_str_new(mrb, path + dot_pos, len - dot_pos); +} + void mrb_init_file(mrb_state *mrb) { @@ -977,6 +1035,7 @@ mrb_init_file(mrb_state *mrb) mrb_define_class_method_id(mrb, file, MRB_SYM(dirname), mrb_file_dirname, MRB_ARGS_REQ(1)); mrb_define_class_method_id(mrb, file, MRB_SYM(basename), mrb_file_basename, MRB_ARGS_REQ(1)|MRB_ARGS_OPT(1)); + mrb_define_class_method_id(mrb, file, MRB_SYM(extname), mrb_file_extname, MRB_ARGS_REQ(1)); mrb_define_class_method_id(mrb, file, MRB_SYM(realpath), mrb_file_realpath, MRB_ARGS_REQ(1)|MRB_ARGS_OPT(1)); mrb_define_class_method_id(mrb, file, MRB_SYM(absolute_path), mrb_file_absolute_path, MRB_ARGS_REQ(1)|MRB_ARGS_OPT(1)); mrb_define_class_method_id(mrb, file, MRB_SYM_Q(absolute_path), mrb_file_absolute_path_p, MRB_ARGS_REQ(1));