Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions mrbgems/mruby-regexp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ re.match("string") { |md| ... } # => block result, or nil if no match
re.match?("string") # => true/false
re =~ "string" # => index or nil
re === "string" # => true/false (for case/when)
re.match(:symbol) # a Symbol is matched against its name
re.source # => "pattern"
re.options # => flags integer
Regexp.escape("a.b") # => "a\\.b"
Expand Down Expand Up @@ -121,12 +122,6 @@ pattern analysis.
only.
- **Step limit on backtracking**: Patterns that require the
backtracking engine are subject to a step limit.
- **Symbols only on the left of a match**: `sym.match(re)`,
`sym.match?(re)` and `sym =~ re` work, but the Regexp side still
takes strings only. `re =~ sym`, `re.match(sym)` and
`re.match?(sym)` raise TypeError, `re === sym` returns false, and
therefore `syms.grep(re)` (which goes through `Regexp#===`) returns
`[]`.
- **No regexp form of `String#[]`**: `str[re]` and `str.slice(re)`
are not supported, and neither is `sym[re]`, which delegates to
them.
Expand Down
9 changes: 4 additions & 5 deletions mrbgems/mruby-regexp/mrblib/symbol_regexp.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@
# to the symbol's name, so delegate instead of repeating the pattern handling;
# `$~` is set by the engine either way.
#
# This covers the symbol-on-the-left direction only. The Regexp side still
# rejects symbols -- `Regexp#=~`, `#match` and `#match?` raise TypeError and
# `#===` returns false -- so `/^to_/ =~ :to_s` and `syms.grep(/^to_/)` (which
# goes through `Regexp#===`) do not work yet. That is a separate fix in
# regexp.c, as is `sym[/re/]`, which needs the regexp form of `String#slice`.
# This covers the symbol-on-the-left direction only. The Regexp side converts
# a symbol on its own, in `match_operand()` in regexp.c, so it needs nothing
# from here. `sym[/re/]` is the direction still missing; it waits on the
# regexp form of `String#slice`.
#
# Two differences from CRuby are inherited from `String#=~` rather than
# introduced here: a String argument raises TypeError (CRuby does too), but so
Expand Down
18 changes: 14 additions & 4 deletions mrbgems/mruby-regexp/src/regexp.c
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,15 @@ create_matchdata(mrb_state *mrb, mrb_value regexp, mrb_value str, int *captures,
return obj;
}

/* Internal: the string a match operates on. A Symbol is matched against its
name; anything else has to be a String. */
static mrb_value
match_operand(mrb_state *mrb, mrb_value obj)
{
if (mrb_symbol_p(obj)) return mrb_sym_str(mrb, mrb_symbol(obj));
return mrb_ensure_string_type(mrb, obj);
}

/* Internal: execute match and create MatchData.
Returns MatchData on match, nil on no match.
Sets $~ and $1-$9 globals. */
Expand Down Expand Up @@ -337,7 +346,7 @@ regexp_match(mrb_state *mrb, mrb_value self)
clear_match_globals(mrb);
return mrb_nil_value();
}
str = mrb_ensure_string_type(mrb, str);
str = match_operand(mrb, str);
pos = re_char_to_byte(mrb, str, pos);
if (pos < 0) {
clear_match_globals(mrb);
Expand Down Expand Up @@ -370,7 +379,7 @@ regexp_match_p(mrb_state *mrb, mrb_value self)
mrb_int pos = 0;
mrb_get_args(mrb, "o|i", &str, &pos);
if (mrb_nil_p(str)) return mrb_false_value();
str = mrb_ensure_string_type(mrb, str);
str = match_operand(mrb, str);
pos = re_char_to_byte(mrb, str, pos);
if (pos < 0) return mrb_false_value();

Expand All @@ -394,7 +403,7 @@ regexp_match_op(mrb_state *mrb, mrb_value self)
clear_match_globals(mrb);
return mrb_nil_value();
}
str = mrb_ensure_string_type(mrb, str);
str = match_operand(mrb, str);

mrb_value md = exec_match(mrb, self, str, 0);
if (mrb_nil_p(md)) return mrb_nil_value();
Expand All @@ -413,7 +422,8 @@ regexp_case_match(mrb_state *mrb, mrb_value self)
mrb_regexp_pattern *pat;

mrb_get_args(mrb, "o", &str);
if (!mrb_string_p(str)) return mrb_false_value();
if (!mrb_string_p(str) && !mrb_symbol_p(str)) return mrb_false_value();
str = match_operand(mrb, str);

pat = DATA_GET_PTR(mrb, self, &regexp_type, mrb_regexp_pattern);
if (!pat) return mrb_false_value();
Expand Down
75 changes: 75 additions & 0 deletions mrbgems/mruby-regexp/test/regexp.rb
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,81 @@
assert_equal "theo", $1
end

assert("Regexp#match - Symbol argument") do
md = /a(b)/.match(:xaby)
assert_kind_of MatchData, md
assert_equal "ab", md[0]
assert_equal "b", md[1]
assert_equal "xaby", md.string
assert_equal "x", md.pre_match
assert_equal "ab", $~[0]
assert_equal "b", /(?<x>b)/.match(:ab)[:x]
assert_equal "A", (/a/.match(:ab) { |m| m[0].upcase })
assert_nil /z/.match(:ab)
end

assert("Regexp#match - Symbol argument with pos") do
assert_equal 3, /a/.match(:abxay, 1).begin(0)
assert_nil /a/.match(:ab, 2)
end

assert("Regexp#match - multibyte Symbol argument") do
# a multibyte name never fits the inline symbol representation, so this is
# the shared-buffer path, with a subject the offset conversion has to walk
assert_equal "い", /(い)/.match(:あいう)[1]
assert_equal __ENCODING__ == "UTF-8" ? 1 : 3, /い/ =~ :あい
assert_true /う/.match?(:あいう, 2)
assert_false /あ/.match?(:あいう, 1)
assert_true(/^あ/ === :あい)
end

assert("Regexp#match - Symbol argument does not alias the symbol table") do
# A symbol long enough to miss the inline representation shares the symbol
# table's buffer, and a dup keeps sharing it, so a destructive update has to
# copy first.
s = /a/.match(:abcdefghijklmnop).string.dup
s << "Z"
assert_equal "abcdefghijklmnopZ", s
assert_equal "abcdefghijklmnop", :abcdefghijklmnop.to_s
end

assert("Regexp#match? - Symbol argument") do
assert_true /a/.match?(:ab)
assert_false /z/.match?(:ab)
assert_false /a/.match?(:ab, 1)
assert_true /b/.match?(:ab, 1)
end

assert("Regexp#=~ - Symbol argument") do
assert_equal 1, (/b/ =~ :ab)
assert_equal "b", $~[0]
assert_nil(/z/ =~ :ab)
assert_nil $~
end

assert("Regexp#=== - Symbol argument") do
assert_true(/^to_/ === :to_s)
assert_false(/^to_/ === :size)
# Enumerable#grep is the motivating case: it dispatches through #===, so it
# used to answer [] rather than raise
assert_equal %i[to_s to_i], %i[to_s to_i size].grep(/^to_/)
result = case :hello123
when /\d+/ then "has digits"
else "no digits"
end
assert_equal "has digits", result
end

assert("Regexp - match operand rejects other types") do
assert_raise(TypeError) { /a/.match(1) }
assert_raise(TypeError) { /a/.match?(1) }
assert_raise(TypeError) { /a/ =~ 1 }
# #=== answers false rather than raising, for symbols and everything else
assert_false(/a/ === 1)
assert_false(/a/ === Object.new)
assert_false(/a/ === nil)
end

assert("Regexp - character class") do
re = Regexp.new("[a-z]+")
md = re.match("123abc456")
Expand Down
Loading