mruby-io: migrate File.join to C

Implements File.join in C for better performance, replacing the Ruby
implementation with direct C string manipulation and array processing.
Uses mruby's built-in recursion detection (MRB_RECURSIVE_UNARY_P) for
cleaner and more reliable recursive array handling.

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Yukihiro "Matz" Matsumoto
2025-08-18 12:05:31 +09:00
parent 6ee3f00849
commit 9e8fe00114
2 changed files with 133 additions and 53 deletions
-53
View File
@@ -79,59 +79,6 @@ class File < IO
"<#{self.class}:#{@path}>"
end
#
# call-seq:
# File.join(string, ...) -> string
#
# Returns a new string formed by joining the strings using the operating
# system's path separator (File::SEPARATOR).
#
# File.join("usr", "mail", "gumby") #=> "usr/mail/gumby"
# File.join("usr", "mail", "gumby") #=> "usr\\mail\\gumby" (on Windows)
#
def self.join(*names)
return "" if names.empty?
names.map! do |name|
case name
when String
name
when Array
if names == name
raise ArgumentError, "recursive array"
end
join(*name)
else
raise TypeError, "no implicit conversion of #{name.class} into String"
end
end
return names[0] if names.size == 1
if names[0][-1] == File::SEPARATOR
s = names[0][0..-2]
else
s = names[0].dup
end
(1..names.size-2).each { |i|
t = names[i]
if t[0] == File::SEPARATOR and t[-1] == File::SEPARATOR
t = t[1..-2]
elsif t[0] == File::SEPARATOR
t = t[1..-1]
elsif t[-1] == File::SEPARATOR
t = t[0..-2]
end
s += File::SEPARATOR + t if t != ""
}
if names[-1][0] == File::SEPARATOR
s += File::SEPARATOR + names[-1][1..-1]
else
s += File::SEPARATOR + names[-1]
end
s
end
#
# call-seq:
+133
View File
@@ -1036,6 +1036,138 @@ mrb_file_path(mrb_state *mrb, mrb_value klass)
return filename;
}
// Forward declaration for recursive join processing
static mrb_value mrb_file_join_process_args(mrb_state *mrb, const mrb_value *argv, mrb_int argc);
static mrb_value
mrb_file_join_process_args(mrb_state *mrb, const mrb_value *argv, mrb_int argc)
{
mrb_value result = mrb_ary_new_capa(mrb, argc);
for (mrb_int i = 0; i < argc; i++) {
mrb_value arg = argv[i];
if (mrb_array_p(arg)) {
// Check for recursive arrays using mruby's built-in detection
if (MRB_RECURSIVE_UNARY_P(mrb, MRB_SYM(join), arg)) {
mrb_raise(mrb, E_ARGUMENT_ERROR, "recursive array");
}
// Recursively process the array
mrb_value nested = mrb_file_join_process_args(mrb, RARRAY_PTR(arg), RARRAY_LEN(arg));
// Append nested results to our result
mrb_int nested_len = RARRAY_LEN(nested);
for (mrb_int k = 0; k < nested_len; k++) {
mrb_ary_push(mrb, result, RARRAY_PTR(nested)[k]);
}
}
else {
// Convert to string (raises TypeError if not convertible)
mrb_ensure_string_type(mrb, arg);
mrb_ary_push(mrb, result, arg);
}
}
return result;
}
/*
* call-seq:
* File.join(string, ...) -> string
*
* Returns a new string formed by joining the strings using the operating
* system's path separator (File::SEPARATOR).
*
* File.join("usr", "mail", "gumby") #=> "usr/mail/gumby"
* File.join("usr", "mail", "gumby") #=> "usr\\mail\\gumby" (on Windows)
*/
static mrb_value
mrb_file_join(mrb_state *mrb, mrb_value klass)
{
const mrb_value *argv;
mrb_int argc;
mrb_get_args(mrb, "*", &argv, &argc);
// Handle empty case
if (argc == 0) {
return mrb_str_new_lit(mrb, "");
}
// Process arguments and flatten arrays
mrb_value names = mrb_file_join_process_args(mrb, argv, argc);
mrb_int names_len = RARRAY_LEN(names);
if (names_len == 0) {
return mrb_str_new_lit(mrb, "");
}
// Handle single element case
if (names_len == 1) {
return RARRAY_PTR(names)[0];
}
// Start building the result
mrb_value first = RARRAY_PTR(names)[0];
mrb_value result = mrb_str_dup(mrb, first);
// Remove trailing separator from first component
const char *sep = FILE_SEPARATOR;
mrb_int sep_len = strlen(sep);
if (RSTRING_LEN(result) > 0 &&
RSTRING_LEN(result) >= sep_len &&
memcmp(RSTRING_PTR(result) + RSTRING_LEN(result) - sep_len, sep, sep_len) == 0) {
mrb_str_resize(mrb, result, RSTRING_LEN(result) - sep_len);
}
// Process middle components
for (mrb_int i = 1; i < names_len - 1; i++) {
mrb_value component = RARRAY_PTR(names)[i];
const char *comp_str = RSTRING_PTR(component);
mrb_int comp_len = RSTRING_LEN(component);
// Skip empty components
if (comp_len == 0) continue;
// Remove leading separator
if (comp_len >= sep_len && memcmp(comp_str, sep, sep_len) == 0) {
comp_str += sep_len;
comp_len -= sep_len;
}
// Remove trailing separator
if (comp_len >= sep_len && memcmp(comp_str + comp_len - sep_len, sep, sep_len) == 0) {
comp_len -= sep_len;
}
// Add separator and component if not empty
if (comp_len > 0) {
mrb_str_cat_cstr(mrb, result, sep);
mrb_str_cat(mrb, result, comp_str, comp_len);
}
}
// Process last component
if (names_len > 1) {
mrb_value last = RARRAY_PTR(names)[names_len - 1];
const char *last_str = RSTRING_PTR(last);
mrb_int last_len = RSTRING_LEN(last);
// Remove leading separator from last component
if (last_len >= sep_len && memcmp(last_str, sep, sep_len) == 0) {
last_str += sep_len;
last_len -= sep_len;
}
// Add separator and last component
mrb_str_cat_cstr(mrb, result, sep);
mrb_str_cat(mrb, result, last_str, last_len);
}
return result;
}
void
mrb_init_file(mrb_state *mrb)
{
@@ -1053,6 +1185,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(join), mrb_file_join, MRB_ARGS_ANY());
mrb_define_class_method_id(mrb, file, MRB_SYM(path), mrb_file_path, 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));