mruby-regexp: implement Regexp#==, Regexp#eql?, and Regexp#hash

Two regexps are equal when they have the same source and flags.
Hash is computed from source string hash mixed with flags.

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Yukihiro "Matz" Matsumoto
2026-03-21 14:16:28 +09:00
parent 4307461e58
commit 0ca3192c9f
2 changed files with 59 additions and 0 deletions
+39
View File
@@ -12,6 +12,7 @@
#include <mruby/variable.h>
#include <mruby/hash.h>
#include <mruby/error.h>
#include <mruby/internal.h>
#include "re_internal.h"
#include <string.h>
@@ -285,6 +286,41 @@ regexp_inspect(mrb_state *mrb, mrb_value self)
return result;
}
/*
* Regexp#== (and eql?)
*/
static mrb_value
regexp_eql(mrb_state *mrb, mrb_value self)
{
mrb_value other;
mrb_get_args(mrb, "o", &other);
if (!mrb_obj_is_kind_of(mrb, other, mrb_class_get(mrb, "Regexp"))) {
return mrb_false_value();
}
mrb_value src1 = mrb_iv_get(mrb, self, mrb_intern_lit(mrb, "@source"));
mrb_value src2 = mrb_iv_get(mrb, other, mrb_intern_lit(mrb, "@source"));
if (!mrb_str_equal(mrb, src1, src2)) return mrb_false_value();
mrb_value f1 = mrb_iv_get(mrb, self, mrb_intern_lit(mrb, "@flags"));
mrb_value f2 = mrb_iv_get(mrb, other, mrb_intern_lit(mrb, "@flags"));
mrb_int flags1 = mrb_nil_p(f1) ? 0 : mrb_integer(f1);
mrb_int flags2 = mrb_nil_p(f2) ? 0 : mrb_integer(f2);
return mrb_bool_value(flags1 == flags2);
}
/*
* Regexp#hash
*/
static mrb_value
regexp_hash(mrb_state *mrb, mrb_value self)
{
mrb_value src = mrb_iv_get(mrb, self, mrb_intern_lit(mrb, "@source"));
mrb_value flags_val = mrb_iv_get(mrb, self, mrb_intern_lit(mrb, "@flags"));
uint32_t flags = mrb_nil_p(flags_val) ? 0 : (uint32_t)mrb_integer(flags_val);
uint32_t h = mrb_str_hash(mrb, src);
h ^= flags * 0x9e3779b9; /* mix flags into hash */
return mrb_int_value(mrb, (mrb_int)h);
}
/*
* Regexp.escape(str)
*/
@@ -532,6 +568,9 @@ mrb_mruby_regexp_gem_init(mrb_state *mrb)
mrb_define_method(mrb, re, "source", regexp_source, MRB_ARGS_NONE());
mrb_define_method(mrb, re, "inspect", regexp_inspect, MRB_ARGS_NONE());
mrb_define_method(mrb, re, "to_s", regexp_to_s, MRB_ARGS_NONE());
mrb_define_method(mrb, re, "==", regexp_eql, MRB_ARGS_REQ(1));
mrb_define_method(mrb, re, "eql?", regexp_eql, MRB_ARGS_REQ(1));
mrb_define_method(mrb, re, "hash", regexp_hash, MRB_ARGS_NONE());
/* MatchData class */
struct RClass *md = mrb_define_class(mrb, "MatchData", mrb->object_class);
+20
View File
@@ -129,6 +129,26 @@ assert("Regexp#to_s") do
assert_equal "(?im:abc)", Regexp.new("abc", Regexp::IGNORECASE | Regexp::MULTILINE).to_s
end
assert("Regexp#== and Regexp#eql?") do
r1 = Regexp.new("abc", Regexp::IGNORECASE)
r2 = Regexp.new("abc", Regexp::IGNORECASE)
r3 = Regexp.new("abc")
r4 = Regexp.new("def", Regexp::IGNORECASE)
assert_true r1 == r2
assert_true r1.eql?(r2)
assert_false r1 == r3 # different flags
assert_false r1 == r4 # different source
assert_false r1 == "abc" # not a Regexp
end
assert("Regexp#hash") do
r1 = Regexp.new("abc", Regexp::IGNORECASE)
r2 = Regexp.new("abc", Regexp::IGNORECASE)
r3 = Regexp.new("abc")
assert_equal r1.hash, r2.hash
assert_not_equal r1.hash, r3.hash
end
assert("String#match") do
md = "hello world".match(Regexp.new("(\\w+)\\s(\\w+)"))
assert_equal "hello", md[1]