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/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ str.gsub(re) { |m| ... } # replace all with block
str.scan(re) # => array of matches
str.split(re) # => array of parts

# Symbol methods (the String methods applied to the symbol's name)
sym.match(re) # => MatchData or nil
sym.match(re) { |md| ... } # => block result, or nil if no match
sym.match?(re) # => true/false
sym =~ re # => index or nil

# Global variables
$~ # last MatchData
```
Expand Down Expand Up @@ -115,6 +121,15 @@ 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.

## Configuration

Expand Down
28 changes: 28 additions & 0 deletions mrbgems/mruby-regexp/mrblib/symbol_regexp.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# CRuby defines these on Symbol so that a symbol can be matched against a
# regexp without spelling out the `to_s`. They are the String methods applied
# 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`.
#
# Two differences from CRuby are inherited from `String#=~` rather than
# introduced here: a String argument raises TypeError (CRuby does too), but so
# does any other object that has no `=~` -- `:a =~ nil` raises NoMethodError
# where CRuby returns nil.
class Symbol
def match(re, pos = 0, &block)
self.to_s.match(re, pos, &block)
end

def match?(re, pos = 0)
self.to_s.match?(re, pos)
end

def =~(re)
self.to_s =~ re
end
end
99 changes: 99 additions & 0 deletions mrbgems/mruby-regexp/test/symbol_regexp.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
assert("Symbol#match") do
md = :"hello world".match(Regexp.new("(\\w+)\\s(\\w+)"))
assert_equal "hello", md[1]
assert_equal "world", md[2]
assert_equal "hello world", md.string

# a String pattern is compiled, as it is for String#match
assert_equal "ll", :hello.match("l+")[0]

# pos is honoured, and counted in the symbol's name
assert_equal 3, :hello.match(Regexp.new("l"), 3).begin(0)
assert_nil :hello.match(Regexp.new("l"), 4)

# a negative pos counts back from the end of the name
assert_equal 3, :hello.match(Regexp.new("l"), -2).begin(0)
assert_nil :hello.match(Regexp.new("l"), -100)

assert_nil :hello.match(Regexp.new("z"))
end

assert("Symbol#match - empty symbol") do
assert_equal 0, :"".match(Regexp.new("")).begin(0)
assert_nil :"".match(Regexp.new("a"))
assert_true :"".match?(Regexp.new(""))
assert_false :"".match?(Regexp.new("a"))
end

assert("Symbol#match - block") do
assert_equal "LL", :hello.match("l+") { |md| md[0].upcase }
assert_equal :broke, :hello.match("l+") { break :broke }

called = false
assert_nil(:hello.match(Regexp.new("z")) { called = true })
assert_false called
end

assert("Symbol#match sets the match globals") do
assert_equal "ll", :hello.match("l+") { $~[0] }
:hello.match("l+")
assert_equal "ll", $~[0]
assert_equal "ll", Regexp.last_match(0)
end

assert("Symbol#match?") do
assert_true :hello.match?(Regexp.new("l+"))
assert_true :hello.match?("l+")
assert_false :hello.match?(Regexp.new("z"))

# pos is honoured, and counted in the symbol's name
assert_true :hello.match?(Regexp.new("l"), 3)
assert_false :hello.match?(Regexp.new("l"), 4)
assert_true :hello.match?(Regexp.new("l"), -2)
assert_false :hello.match?(Regexp.new("l"), -100)
end

assert("Symbol#match? - does not update last match") do
$~ = :matched.match("matched")
assert_true :hello.match?("l+")
assert_equal "matched", $~[0]
assert_false :hello.match?(Regexp.new("z"))
assert_equal "matched", $~[0]
end

assert("Symbol#=~") do
assert_equal 2, :hello =~ Regexp.new("l")
assert_nil :hello =~ Regexp.new("z")

# inherited from String#=~: a String argument is a type mismatch
assert_raise(TypeError) { :hello =~ "l" }
end

assert("Symbol#=~ sets the match globals") do
:hello =~ Regexp.new("l(l)")
assert_equal "ll", $~[0]
assert_equal "l", $1
assert_equal "l", Regexp.last_match(1)

assert_nil :hello =~ Regexp.new("z")
assert_nil $~
end

assert("Symbol#!~") do
assert_false :hello !~ Regexp.new("l")
assert_true :hello !~ Regexp.new("z")
assert_raise(TypeError) { :hello !~ "l" }
end

assert("Symbol - multibyte (UTF-8) name") do
# pos counts characters of the symbol's name, and begin/end report
# character offsets, just as they do for the equivalent String.
skip unless __ENCODING__ == "UTF-8"
assert_equal "い", :"あいう".match(Regexp.new("い"))[0]
assert_equal 2, :"あいあ".match(Regexp.new("あ"), 1).begin(0)
assert_equal 2, :"あいあ".match(Regexp.new("あ"), -1).begin(0)
assert_nil :"あいあ".match(Regexp.new("い"), 2)
assert_true :"あいあ".match?(Regexp.new("あ"), 2)
assert_false :"あいあ".match?(Regexp.new("い"), 2)
assert_equal 1, :"あい" =~ Regexp.new("い")
end
Loading