mruby-regexp: support \& \` \' \+ \\ in sub/gsub replacements

Replacement strings now support:
  \& = full match, \` = pre_match, \' = post_match,
  \+ = last successful capture, \\ = literal backslash.

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Yukihiro "Matz" Matsumoto
2026-03-21 14:37:10 +09:00
parent 26dc5f76ea
commit 5a158d32a8
2 changed files with 56 additions and 6 deletions
+37 -6
View File
@@ -13,6 +13,41 @@ class String
re =~ self
end
def __sub_replace(rep, md)
return rep unless rep.include?("\\")
result = ""
i = 0
while i < rep.length
if rep[i] == "\\" && i + 1 < rep.length
c = rep[i + 1]
case c
when '0'..'9'
result += (md[c.to_i] || "")
when '&'
result += (md[0] || "")
when '`'
result += md.pre_match
when "'"
result += md.post_match
when '+'
# last successful capture
last = nil
md.captures.each { |c| last = c if c }
result += (last || "")
when "\\"
result += "\\"
else
result += "\\" + c
end
i += 2
else
result += rep[i]
i += 1
end
end
result
end
def sub(pattern, replacement = nil, &block)
pattern = Regexp.new(Regexp.escape(pattern)) if pattern.is_a?(String)
md = pattern.match(self)
@@ -23,9 +58,7 @@ class String
if block
rep = block.call(md[0]).to_s
else
rep = replacement.to_s
# handle \0, \1, etc. in replacement string
rep = rep.gsub(/\\(\d)/) { md[$1.to_i] || "" } if rep.include?("\\")
rep = __sub_replace(replacement.to_s, md)
end
pre + rep + post
end
@@ -41,9 +74,7 @@ class String
if block
result += block.call(md[0]).to_s
else
rep = replacement.to_s
rep = rep.gsub(/\\(\d)/) { md[$1.to_i] || "" } if rep.include?("\\")
result += rep
result += __sub_replace(replacement.to_s, md)
end
matched_len = md[0].length
if matched_len == 0
+19
View File
@@ -228,6 +228,25 @@ assert("String#gsub") do
assert_equal "h-ll-", "hello".gsub(Regexp.new("[eo]"), "-")
end
assert("String#sub with \\& \\` \\' specials") do
# \& = full match
assert_equal "a[bc]d", "abcd".sub(/bc/, '[\\&]')
# \` = pre_match
assert_equal "a[a]d", "abcd".sub(/bc/, '[\\`]')
# \' = post_match
assert_equal "a[d]d", "abcd".sub(/bc/, "[\\']")
# \+ = last capture
assert_equal "a[c]d", "abcd".sub(/(b)(c)/, '[\\+]')
# \\ = literal backslash
assert_equal "a\\d", "abcd".sub(/bc/, "\\\\")
# \1 still works
assert_equal "abbd", "abcd".sub(/(b)c/, '\\1\\1')
end
assert("String#gsub with \\& special") do
assert_equal "[a][b][c]", "abc".gsub(/./, '[\\&]')
end
assert("String#scan") do
assert_equal ["1", "2", "3"], "a1b2c3".scan(Regexp.new("\\d"))
end