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
15 changes: 15 additions & 0 deletions mrbgems/mruby-regexp/mrbgem.rake
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,19 @@ MRuby::Gem::Specification.new('mruby-regexp') do |spec|
spec.summary = 'Regexp class (built-in NFA engine)'

spec.add_dependency 'mruby-string-ext', :core => 'mruby-string-ext'

# Enumerator is optional: only String#gsub without a block reaches `to_enum`,
# and without mruby-enumerator that is core Kernel#to_enum, which raises
# NotImplementedError -- the same deal as Kernel#loop and String#each_char
# (mruby-string-ext), neither of which depends on mruby-enumerator either.
# Declaring it unconditionally would drag Enumerator (and thus Fiber) into
# builds that never take that path. Depend on it only when the build has it
# anyway, so that mrbtest -- which runs each gem's tests in a state holding
# just its declared dependencies -- can exercise the enumerator path; the
# test skips itself when Enumerator is missing. A gem that only arrives
# through another gem's dependency is not visible here yet, which just means
# the test skips.
if build.gems.any? {|g| g.name == 'mruby-enumerator'}
spec.add_dependency 'mruby-enumerator', :core => 'mruby-enumerator'
end
end
28 changes: 24 additions & 4 deletions mrbgems/mruby-regexp/mrblib/string_regexp.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,39 @@ def =~(re)
re =~ self
end

def sub(pattern, replacement = nil, &block)
def sub(*args, &block)
# CRuby accepts 1..2 arguments with a block, but demands exactly 2
# without one, and reports the expected count accordingly.
if block
unless (1..2).include?(args.length)
raise ArgumentError, "wrong number of arguments (given #{args.length}, expected 1..2)"
end
elsif args.length != 2
raise ArgumentError, "wrong number of arguments (given #{args.length}, expected 2)"
end
pattern, replacement = *args
pattern = Regexp.new(Regexp.escape(pattern)) if pattern.is_a?(String)
unless block
# A replacement argument wins over the block, as in CRuby.
if args.length == 2
return pattern.__sub_str(self, replacement.to_s)
end
md = pattern.match(self)
return self.dup unless md
md.pre_match + block.call(md[0]).to_s + md.post_match
end

def gsub(pattern, replacement = nil, &block)
def gsub(*args, &block)
unless (1..2).include?(args.length)
raise ArgumentError, "wrong number of arguments (given #{args.length}, expected 1..2)"
end
# Without mruby-enumerator this is core Kernel#to_enum, which raises
# NotImplementedError; every other path here stays usable, so the gem does
# not depend on Enumerator.
return to_enum(:gsub, *args) if args.length == 1 && !block
pattern, replacement = *args
pattern = Regexp.new(Regexp.escape(pattern)) if pattern.is_a?(String)
unless block
# A replacement argument wins over the block, as in CRuby.
if args.length == 2
return pattern.__gsub_str(self, replacement.to_s)
end
# block case: keep in Ruby to avoid VM callback from C
Expand Down
44 changes: 44 additions & 0 deletions mrbgems/mruby-regexp/test/regexp.rb
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,50 @@
assert_equal "h-ll-", "hello".gsub(Regexp.new("[eo]"), "-")
end

assert("String#sub/#gsub - replacement string takes precedence over the block") do
assert_equal "aXc", "abc".sub(/b/, "X") { "Y" }
assert_equal "aXcX", "abcb".gsub(/b/, "X") { "Y" }
# The block is only used when no replacement argument is given.
assert_equal "aYc", "abc".sub(/b/) { "Y" }
assert_equal "aYcY", "abcb".gsub(/b/) { "Y" }
end

assert("String#sub - wrong number of arguments") do
# Without a block CRuby demands exactly 2 arguments, and says so.
assert_raise_with_message(ArgumentError, "wrong number of arguments (given 0, expected 2)") do
"abc".sub
end
assert_raise_with_message(ArgumentError, "wrong number of arguments (given 1, expected 2)") do
"abc".sub(/b/)
end
assert_raise_with_message(ArgumentError, "wrong number of arguments (given 3, expected 2)") do
"abc".sub(/b/, "X", "Y")
end
assert_raise_with_message(ArgumentError, "wrong number of arguments (given 3, expected 1..2)") do
"abc".sub(/b/, "X", "Y") { "Z" }
end
end

assert("String#gsub - wrong number of arguments") do
assert_raise_with_message(ArgumentError, "wrong number of arguments (given 0, expected 1..2)") do
"abc".gsub
end
assert_raise_with_message(ArgumentError, "wrong number of arguments (given 3, expected 1..2)") do
"abc".gsub(/b/, "X", "Y")
end
assert_raise_with_message(ArgumentError, "wrong number of arguments (given 3, expected 1..2)") do
"abc".gsub(/b/, "X", "Y") { "Z" }
end
end

assert("String#gsub without a block returns an enumerator") do
skip "Enumerator is not available" unless Object.const_defined?(:Enumerator)
assert_equal ["b", "b"], "abcb".gsub(/b/).to_a
assert_equal ["b", "b"], "abcb".gsub("b").to_a
# Iterating the enumerator with a block performs the substitution.
assert_equal "aBcB", "abcb".gsub(/b/).each { |m| m.upcase }
end

assert("String#sub with \\& \\` \\' specials") do
# \& = full match
assert_equal "a[bc]d", "abcd".sub(/bc/, '[\\&]')
Expand Down
Loading