# Request for feedback, suggestions, contributions

**URL:** <https://www.ruby-forum.com/t/request-for-feedback-suggestions-contributions/147129>\
**Category:** Ruby\
**Created:** [August 28, 2008, 5:39pm UTC](https://www.ruby-forum.com/t/request-for-feedback-suggestions-contributions/147129 "2008-08-28T17:39:52Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![Kenneth\_McDonald](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/k/dc4da7/32.png) [@Kenneth\_McDonald](https://www.ruby-forum.com/u/Kenneth_McDonald)\
**Post date:** [August 28, 2008, 5:39pm UTC](https://www.ruby-forum.com/t/request-for-feedback-suggestions-contributions/147129/1 "2008-08-28T17:39:52Z")

</div>

I’m attempting to write a module whose classes will make it easier to  
construct and work with regular expressions. The two, somewhat self  
explanatory files (main and test) are given below. I would be grateful  
for feedback, especially on better ways of doing things. Also note  
that I am developing on JRuby, so currently JRuby re sytax is more  
supported than Ruby syntax.

Check out the second (test) file for simple examples of use.

Thanks,  
Ken

## File ‘rex.rb’

=begin rdoc  
‘rex.rb’ is a file that provide classes intended to make it easier to  
develop  
and use regular expressions. A primary feature is that it allows one  
to easily  
construct larger regular expressions out of smaller regular  
expressions. The  
other main feature is that it provides (or will provide) many  
functions that  
make it easier to apply regular expressions in useful ways. I also  
believe that,  
thought it is more verbose than standard Regexps, it provides much  
more readable  
code when constructing complex regular expressions.

rex is not intended to be comprehensive; I don’t have time for that.  
My hope is  
that it will be useful for the 95% of ‘common case’ re’s.  
=end

CHARACTERS = {  
:dot =\> “\t”,  
:tab =\> “\t”,  
:vtab =\> “\v”,  
:newline =\> “\n”,  
:return =\> “\r”,  
:backspace =\> “\b”,  
:form\_feed =\> “\f”,  
:bell =\> “\a”,  
:esc =\> “\e”,  
:word\_char =\> “\w”,  
:non\_word\_char =\> “\W”,  
:whitespace\_char =\> “\s”,  
:non\_whitespace\_char =\> “\S”,  
:digit\_char =\> “\d”,  
:non\_digit\_char =\> “\D”  
}

class Rex

attr\_writer :is\_group

=begin rdoc  
Create a new Rex pattern with _string_ as the pattern that will be  
passed to  
Regexp. This is used by other Rex functions; you can also use it to  
create  
a ‘raw’ pattern.  
=end  
def initialize(string)  
@pat = string  
@is\_group = false  
@regexp = Regexp.new(@pat)  
end

def index(string, start=0)  
return string.index(@regexp, start)  
end

=begin rdoc  
yields each match in the string in succession  
=end  
def each(string)  
start = 0  
while true:  
i = string.index(@regexp, start)  
print “MATCHED #{@regexp.inspect} AT #{i}!\n”  
if i == nil; break; end  
md = $~  
yield md  
if md.end(0) == start  
start = start + 1  
else  
start = md.end(0)  
end  
end  
end  
=begin rdoc  
Same as =~ on the corresponding Regexp  
=end  
def =~(string)  
return @regexp =~ string  
end

=begin rdoc  
Returns the pattern associated with this Rex instance. This is the  
string is  
passed to Regexp to create a new Regexp.  
=end  
def pat  
return @pat  
end

def group  
if @is\_group  
return self  
else  
result = Rex.new("(?:#{@pat})")  
result.is\_group = true  
return result  
end  
end

=begin rdoc  
Regular expression concatenation; Lit.new(“ab”) + Lit.new(“cd”) will  
produce  
a Rex that has the same meaning as the Regexp /abcd/ (though the  
pattern will  
be different.  
=end  
def +(other)  
return Rex.new(self.group.pat + other.group.pat)  
end

=begin rdoc  
Used to define a named group. If _rex_ is a Rex instance with an  
internal pattern  
_pat_, then _rex_[‘name’] produces a new Rex with pattern (?  
_pat_).  
=end  
def   
result = Rex.new("(?\<#{name}\>#{@pat})")  
result.is\_group = true  
return result  
end

# def +(other)

# r1 = self

# r1 = r1.wrap\_if\_not("+")

# other = other.wrap\_if\_not("+")

# r = Regexp.new(r1 + other)

# r.operator = “+”

# return r

# end

=begin rdoc  
Regular expression alternation. Lit.new(“ab”) | Lit.new(“cd”) will  
produce  
a Rex that has the same meaning as the Regexp /ab|cd/ (though the  
pattern will  
be different.  
=end  
def |(other)  
return Rex.new(self.group.pat + “|” + other.group.pat)  
end

=begin rdoc  
Same as the corresponding _match_ method in Regexp.  
=end  
def match(string)  
return @regexp.match(string)  
end

=begin rdoc  
Returns a new Rex that is an optional version of this one;  
Lit.new(‘a’).optional  
has the same effect as the Regexp /a?/  
=end  
def optional  
return Rex.new(self.group.pat + “?”)  
end

# Invoke up a Rex to indicate it is naturally grouped, i.e. does

not need to

# be surrounded with parens before being put into another Rex.

def natural\_group # :nodoc:  
@is\_group = true  
return self  
end

=begin rdoc  
Defines regular expression repetitions. Lit.new(‘a’).n(3) is the same as  
/a{3,}/, while Lit.new(3…7) is the same as /a{3,7}/. use 0 or 1 to  
achieve  
the same effect as the \* and + Regexp operators. Tri-period ranges of  
the form  
3…8 are allowed, and have the same meaning as one would expect, i.e.  
that  
range give the same result as 3…7.  
=end  
def n(repetitions)  
if repetitions.is\_a?(Integer)  
return Rex.new(self.group.pat +  
“{#{repetitions},}”).natural\_group  
elsif repetitions.is\_a?(Range)  
ending = repetitions.end  
if repetitions.exclude\_end?  
ending -= 1  
end  
return Rex.new(self.group.pat +  
“{#{repetitions.begin},#{ending}}”).natural\_group  
end  
end

=begin rdoc  
Same as method _n_, but nongreedy.  
=end  
def n?(repetitions)  
if repetitions.is\_a?(Integer)  
return Rex.new(self.group.pat +  
“{#{repetitions},}?”).natural\_group  
elsif repetitions.is\_a?(Range)  
ending = repetitions.end  
if repetitions.exclude\_end?  
ending -= 1  
end  
return Rex.new(self.group.pat +  
“{#{repetitions.begin},#{ending}}?”).natural\_group  
end  
end

def to\_s  
return @pat  
end  
end

=begin rdoc  
Create a new literal that will match exactly that string. This handles  
Regexp  
escaping for you, so you do not need to worry about handling  
characters with  
special meanings in Regexp.  
=end  
class Lit \< Rex  
def initialize(string)  
@pat = Regexp.escape(string)  
@regexp = Regexp.new(@pat)  
@is\_group = false  
end  
end

class Chars \< Rex  
=begin rdoc  
Creates a character class that matches those characters given in  
_include_,  
except for those given in _exclude_. Each of _include_ and _exclude_  
should be  
one of:

- A string, in which case it defines the set of characters to be  
included or excluded.
- A double-dot (x…y) range, which will define a range of characters  
to be included or excluded.
- A list of strings and ranges, which have the same meanings as above  
and are combined to produce the set of characters to be included or  
excluded.
- A symbol, which is used to denote one of the special character  
classes.

Note that Chars defines no special characters.  
_include_:: The set of characters to be included in the class. Include  
may be nil or the empty string, if you don’t want to include  
characters in the class.  
_exclude_:: The set of characters to be excluded from the class.  
Defaults to nil.  
=end  
def initialize(include, exclude=nil)

```
 def list_to_chars(list)
   chars = ""
   list.each {|e|
     if e.is_a?(String)
       chars << Regexp.escape(e)
     elsif e.is_a?(Range)
       chars << Regexp.escape(e.begin) << "-" <<

```

Regexp.escape(e.end)  
elsif e.is\_a?(Symbol)  
chars \<\< “[:” \<\< e.to\_s \<\< “:]”  
end  
}  
return chars  
end

```
 if include == nil or include == ""
   include = nil
 elsif include.is_a?(Array)
   include = list_to_chars(include)
 else
   include = list_to_chars([include])
 end

 if exclude.is_a?(Array)
   exclude = list_to_chars(exclude)
 elsif exclude != nil
   exclude = list_to_chars([exclude])
 end

 if exclude == nil
   chars = ("[#{include}]")
 elsif include == nil
   chars = "[^#{exclude}]"
 else
   chars = ("[#{include}&&[^#{exclude}]]")
 end

 @pat = chars
 @regexp = Regexp.new(@pat)
 @is_group = true

```

end  
end

## File ‘rex\_test.rb’

$:.unshift File.join(File.dirname( **FILE** ),’…’,‘lib’)

require ‘test/unit’  
require ‘rex’

class RexTest \< Test::Unit::TestCase  
def test\_simple  
posint = Rex.new(’[0-9]+’)  
posfloat = posint + (Lit.new(’.’) + posint).optional  
float = (Lit.new(’+’)|Lit.new(’-’)).optional + posfloat  
complex = float[‘re’] + (Lit.new(’+’)|Lit.new(’-’)) +  
posfloat[‘im’] + Lit.new(‘i’)  
print complex  
assert\_equal(0, posint =~ “123”)  
assert\_equal(0, posfloat =~ “123.45”)  
assert\_equal(0, posfloat =~ “123”)  
assert\_equal(“3.45”, complex.match(" 3.45-2i")[‘re’])  
end

def test\_repetitions  
assert\_equal("(?:a){3,}", Lit.new(‘a’).n(3).pat)  
assert\_equal("(?:a){3,5}", Lit.new(‘a’).n(3…5).pat)  
assert\_equal("(?:a){3,4}", Lit.new(‘a’).n(3…5).pat)  
assert\_equal("(?:a){3,}?", Lit.new(‘a’).n?(3).pat)  
assert\_equal("(?:a){3,5}?", Lit.new(‘a’).n?(3…5).pat)  
assert\_equal("(?:a){3,4}?", Lit.new(‘a’).n?(3…5).pat)  
end

def test\_char\_class  
assert\_equal("[abc]", Chars.new(“abc”).pat)  
assert\_equal("[^abc]", Chars.new(nil, “abc”).pat)  
assert\_equal("[abc&&[^de]]", Chars.new(“abc”, “de”).pat)  
assert\_equal("[abct-z&&[^n-u]]", Chars.new([“abc”, “t”…“z”],  
“n”…“u”).pat)  
assert\_equal("[[:alnum:]]", Chars.new(:alnum).pat)  
end

def test\_index  
assert\_equal(3, Rex.new(“a”).index(“bcda”))  
assert\_equal(3, Lit.new(“a”).index(“bcda”))  
end

def test\_each  
pat = Lit.new(‘a’).n(1)  
s = “aababbaaababb”  
result = []  
pat.each(s) {|md|  
result \<\< md[0]  
}  
assert\_equal([“aa”, “a”, “aaa”, “a”], result)  
end  
end
