srl_ruby 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +4 -0
  3. data/.rubocop.yml +3 -0
  4. data/.yardopts +6 -0
  5. data/Gemfile +6 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +66 -0
  8. data/Rakefile +16 -0
  9. data/bin/srl_ruby +58 -0
  10. data/lib/regex/abstract_method.rb +35 -0
  11. data/lib/regex/alternation.rb +27 -0
  12. data/lib/regex/anchor.rb +45 -0
  13. data/lib/regex/atomic_expression.rb +16 -0
  14. data/lib/regex/capturing_group.rb +51 -0
  15. data/lib/regex/char_class.rb +38 -0
  16. data/lib/regex/char_range.rb +51 -0
  17. data/lib/regex/char_shorthand.rb +50 -0
  18. data/lib/regex/character.rb +204 -0
  19. data/lib/regex/compound_expression.rb +57 -0
  20. data/lib/regex/concatenation.rb +29 -0
  21. data/lib/regex/expression.rb +60 -0
  22. data/lib/regex/lookaround.rb +50 -0
  23. data/lib/regex/match_option.rb +34 -0
  24. data/lib/regex/monadic_expression.rb +28 -0
  25. data/lib/regex/multiplicity.rb +91 -0
  26. data/lib/regex/non_capturing_group.rb +27 -0
  27. data/lib/regex/polyadic_expression.rb +60 -0
  28. data/lib/regex/quantifiable.rb +22 -0
  29. data/lib/regex/repetition.rb +29 -0
  30. data/lib/regex/wildcard.rb +23 -0
  31. data/lib/srl_ruby/ast_builder.rb +384 -0
  32. data/lib/srl_ruby/grammar.rb +106 -0
  33. data/lib/srl_ruby/regex_repr.rb +13 -0
  34. data/lib/srl_ruby/tokenizer.rb +147 -0
  35. data/lib/srl_ruby/version.rb +3 -0
  36. data/lib/srl_ruby.rb +4 -0
  37. data/spec/integration_spec.rb +451 -0
  38. data/spec/regex/character_spec.rb +166 -0
  39. data/spec/regex/multiplicity_spec.rb +79 -0
  40. data/spec/spec_helper.rb +16 -0
  41. data/spec/srl_ruby/srl_ruby_spec.rb +7 -0
  42. data/spec/srl_ruby/tokenizer_spec.rb +147 -0
  43. data/srl_ruby.gemspec +58 -0
  44. metadata +150 -0
@@ -0,0 +1,204 @@
1
+ # File: character.rb
2
+
3
+ require_relative 'atomic_expression' # Access the superclass
4
+
5
+ module Regex # This module is used as a namespace
6
+ # A regular expression that matches a specific character in a given character set
7
+ class Character < AtomicExpression
8
+ # Constant with all special 2-characters escape sequences
9
+ DigramSequences = {
10
+ "\\a" => 0x7, # alarm
11
+ "\\n" => 0xA, # newline
12
+ "\\r" => 0xD, # carriage return
13
+ "\\t" => 0x9, # tab
14
+ "\\e" => 0x1B, # escape
15
+ "\\f" => 0xC, # form feed
16
+ "\\v" => 0xB, # vertical feed
17
+ # Single octal digit literals
18
+ "\\0" => 0,
19
+ "\\1" => 1,
20
+ "\\2" => 2,
21
+ "\\3" => 3,
22
+ "\\4" => 4,
23
+ "\\5" => 5,
24
+ "\\6" => 6,
25
+ "\\7" => 7
26
+ }.freeze
27
+
28
+ MetaChars = '\^$+?.'.freeze
29
+
30
+ # The integer value that uniquely identifies the character.
31
+ attr_reader(:codepoint)
32
+
33
+ # The initial text representation of the character (if any).
34
+ attr_reader(:lexeme)
35
+
36
+ # Constructor.
37
+ # [aValue] Initialize the character with a either a String literal or a
38
+ # codepoint value.
39
+ # Examples:
40
+ # Initializing with codepoint value...
41
+ # RegAn::Character.new(0x3a3) # Represents: Σ
42
+ # (Unicode GREEK CAPITAL LETTER SIGMA)
43
+ # RegAn::Character.new(931) # Also represents: Σ (931 dec == 3a3 hex)
44
+ #
45
+ # Initializing with a single character string
46
+ # RegAn::Character.new(?\u03a3) # Also represents: Σ
47
+ # RegAn::Character.new('Σ') # Obviously, represents a Σ
48
+ #
49
+ # Initializing with an escape sequence string
50
+ # Recognized escaped characters are: \a (alarm, 0x07), \n (newline, 0xA),
51
+ # \r (carriage return, 0xD), \t (tab, 0x9), \e (escape, 0x1B),
52
+ # \f (form feed, 0xC)
53
+ # \uXXXX where XXXX is a 4 hex digits integer value, \u{X...}, \ooo (octal)
54
+ # \xXX (hex)
55
+ # Any other escaped character will be treated as a literal character
56
+ # RegAn::Character.new('\n') # Represents a newline
57
+ # RegAn::Character.new('\u03a3') # Represents a Σ
58
+ def initialize(aValue)
59
+ case aValue
60
+ when String
61
+ if aValue.size == 1
62
+ # Literal single character case...
63
+ @codepoint = self.class.char2codepoint(aValue)
64
+ else
65
+ # Should be an escape sequence...
66
+ @codepoint = self.class.esc2codepoint(aValue)
67
+ end
68
+ @lexeme = aValue
69
+
70
+ when Integer
71
+ @codepoint = aValue
72
+ else
73
+ raise StandardError, "Cannot initialize a Character with a '#{aValue}'."
74
+ end
75
+ end
76
+
77
+ # Convertion method that returns a character given a codepoint (integer) value.
78
+ # Example:
79
+ # RegAn::Character::codepoint2char(0x3a3) # Returns: Σ (
80
+ # The Unicode GREEK CAPITAL LETTER SIGMA)
81
+ def self.codepoint2char(aCodepoint)
82
+ return [aCodepoint].pack('U') # Remark: chr() fails with codepoints > 256
83
+ end
84
+
85
+ # Convertion method that returns the codepoint for the given single character.
86
+ # Example:
87
+ # RegAn::Character::char2codepoint('Σ') # Returns: 0x3a3
88
+ def self.char2codepoint(aChar)
89
+ return aChar.ord
90
+ end
91
+
92
+ # Convertion method that returns the codepoint for the given escape
93
+ # sequence (a String).
94
+ # Recognized escaped characters are: \a (alarm, 0x07), \n (newline, 0xA),
95
+ # \r (carriage return, 0xD), \t (tab, 0x9), \e (escape, 0x1B), \f (form feed,
96
+ # 0xC), \v (vertical feed, 0xB)
97
+ # \uXXXX where XXXX is a 4 hex digits integer value, \u{X...}, \ooo (octal)
98
+ # \xXX (hex)
99
+ # Any other escaped character will be treated as a literal character
100
+ # Example:
101
+ # RegAn::Character::esc2codepoint('\n') # Returns: 0xd
102
+ def self.esc2codepoint(anEscapeSequence)
103
+ msg = "Escape sequence #{anEscapeSequence} does not begin with a backslash (\)."
104
+ raise StandardError, msg unless anEscapeSequence[0] == "\\"
105
+ result = (anEscapeSequence.length == 2)? digram2codepoint(anEscapeSequence) : esc_number2codepoint(anEscapeSequence)
106
+
107
+ return result
108
+ end
109
+
110
+ # Return the character as a String object
111
+ def char()
112
+ self.class.codepoint2char(@codepoint)
113
+ end
114
+
115
+ # Returns true iff this Character and parameter 'another' represent the same character.
116
+ # [another] any Object. The way the equality is tested depends on the another's class
117
+ # Example:
118
+ # newOne = Character.new(?\u03a3)
119
+ # newOne == newOne # true. Identity
120
+ # newOne == Character.new(?\u03a3) # true. Both have same codepoint
121
+ # newOne == ?\u03a3 # true. The single character String match exactly the char attribute.
122
+ # newOne == 0x03a3 # true. The Integer is compared to the codepoint value.
123
+ # Will test equality with any Object that knows the to_s method
124
+ def ==(other)
125
+ result = case other
126
+ when Character
127
+ self.to_str == other.to_str
128
+
129
+ when Integer
130
+ self.codepoint == other
131
+
132
+ when String
133
+ other.size > 1 ? false : to_str == other
134
+
135
+ else
136
+ # Unknown type: try with a convertion
137
+ self == other.to_s # Recursive call
138
+ end
139
+
140
+ return result
141
+ end
142
+
143
+ # Return a plain English description of the character
144
+ def explain()
145
+ return "the character '#{to_str}'"
146
+ end
147
+
148
+ protected
149
+
150
+ # Conversion method re-definition.
151
+ # Purpose: Return the String representation of the expression.
152
+ # If the Character was initially from a text (the lexeme), then the lexeme
153
+ # is returned back.
154
+ # Otherwise the character corresponding to the codepoint is returned.
155
+ def text_repr()
156
+ return char if lexeme.nil?
157
+ return lexeme.dup
158
+ end
159
+
160
+ # Convertion method that returns a codepoint for the given two characters
161
+ # (digram) escape sequence.
162
+ # Recognized escaped characters are: \a (alarm, 0x07), \n (newline, 0xA),
163
+ # \r (carriage return, 0xD), \t (tab, 0x9), \e (escape, 0x1B),
164
+ # \f (form feed, 0xC), \v (vertical feed, 0xB)
165
+ # Any other escape sequence will return the codepoint of the escaped
166
+ # character.
167
+ # [aDigram] A sequence of two characters that starts with a backslash.
168
+ def self.digram2codepoint(aDigram)
169
+ # Check that the digram is a special escape sequence
170
+ result = DigramSequences.fetch(aDigram, nil)
171
+
172
+ # If it not a special sequence, then escaped character is
173
+ # considered literally (the backslash is 'dummy')
174
+ result = char2codepoint(aDigram[-1]) if result.nil?
175
+ return result
176
+ end
177
+
178
+ private_class_method :digram2codepoint
179
+
180
+ # Convertion method that returns a codepoint for the given complex
181
+ # escape sequence.
182
+ # [anEscapeSequence] A String with the format:
183
+ # \uXXXX where XXXX is a 4 hex digits integer value,
184
+ # \u{X...} X 1 or more hex digits
185
+ # \ooo (1..3 octal digits literal)
186
+ # \xXX (1..2 hex digits literal)
187
+ def self.esc_number2codepoint(anEscapeSequence)
188
+ unless /^\\(?:(?:(?<prefix>[uxX])\{?(?<hexa>\h+)\}?)|(?<octal>[0-7]{1,3}))$/ =~ anEscapeSequence
189
+ raise StandardError, "Unsupported escape sequence #{anEscapeSequence}."
190
+ else
191
+ # Octal literal case?
192
+ return octal.oct if octal # shorterSeq =~ /[0-7]{1,3}/
193
+
194
+ # Extract the hexadecimal number
195
+ hexliteral = hexa # shorterSeq.sub(/^[xXu]\{?([0-9a-fA-F]+)}?$/, '\1')
196
+ return hexliteral.hex
197
+ end
198
+ end
199
+
200
+ private_class_method :esc_number2codepoint
201
+ end # class
202
+ end # module
203
+
204
+ # End of file
@@ -0,0 +1,57 @@
1
+ # File: compound_expression.rb
2
+
3
+ require_relative 'expression' # Access the superclass
4
+
5
+ module Regex # This module is used as a namespace
6
+ # Abstract class. An element that is part of a regular expression &
7
+ # that has its own child sub-expressions.
8
+ class CompoundExpression < Expression
9
+ # Redefined method. Return false since it may have one or more children.
10
+ def atomic?
11
+ return false
12
+ end
13
+
14
+ =begin
15
+ # Build a depth-first in-order children visitor.
16
+ # The visitor is implemented as an Enumerator.
17
+ def df_visitor()
18
+ root = children # The visit will start from the children of this object
19
+
20
+ visitor = Enumerator.new do |result| # result is a Yielder
21
+ # Initialization part: will run once
22
+ visit_stack = [ root ] # The LIFO queue of nodes to visit
23
+
24
+ begin # Traversal part (as a loop)
25
+ top = visit_stack.pop()
26
+ if top.kind_of?(Array)
27
+ if top.empty?
28
+ next
29
+ else
30
+ currChild = top.pop()
31
+ visit_stack.push top
32
+ end
33
+ else
34
+ currChild = top
35
+ end
36
+
37
+ result << currChild # Return the visited child
38
+
39
+ unless currChild.atomic?
40
+ children_to_enqueue = currChild.children.reverse() # in-order traversal implies LIFO queue
41
+ visit_stack.push(children_to_enqueue)
42
+ end
43
+ end until visit_stack.empty?
44
+ end
45
+ end
46
+ =end
47
+
48
+ protected
49
+
50
+ # Abstract method. Return the text representation of the child (if any)
51
+ def all_child_text()
52
+ abstract_method
53
+ end
54
+ end # class
55
+ end # module
56
+
57
+ # End of file
@@ -0,0 +1,29 @@
1
+ # File: concatenation.rb
2
+
3
+ require_relative 'polyadic_expression' # Access the superclass
4
+
5
+ module Regex # This module is used as a namespace
6
+ # Abstract class. A n-ary matching operator.
7
+ # It succeeds when each child succeeds to match the subject text in the same
8
+ # serial arrangement than defined by this concatenation.
9
+ class Concatenation < PolyadicExpression
10
+ # Constructor.
11
+ def initialize(*theChildren)
12
+ super(theChildren)
13
+ end
14
+
15
+ protected
16
+
17
+ # Conversion method re-definition.
18
+ # Purpose: Return the String representation of the concatented expressions.
19
+ def text_repr()
20
+ outcome = children.inject('') do |result, aChild|
21
+ result << aChild.to_str
22
+ end
23
+
24
+ return outcome
25
+ end
26
+ end # class
27
+ end # module
28
+
29
+ # End of file
@@ -0,0 +1,60 @@
1
+ # File: expression.rb
2
+
3
+ require_relative 'abstract_method'
4
+
5
+ module Regex # This module is used as a namespace
6
+ # Abstract class. The generalization of any valid regular (sub)expression.
7
+ class Expression
8
+ attr_accessor :begin_anchor
9
+ attr_accessor :end_anchor
10
+
11
+ # Constructor
12
+ def initialize(); end
13
+
14
+ # Abstract method. Return true iff the expression is atomic
15
+ # (= may not have any child).
16
+ def atomic?()
17
+ abstract_method
18
+ end
19
+
20
+ # Abstract method. Return the number of values that match this expression.
21
+ # [_parent_options] an Hash of matching options. They are overridden
22
+ # by options with same name that are bound to this object.
23
+ def cardinality(_parent_options)
24
+ abstract_method
25
+ end
26
+
27
+ # Determine the matching options to apply to this object, given the options
28
+ # coming from the parent
29
+ # and options that are local to this object. Local options take precedence.
30
+ # @param theParentOptions [Hash] matching options. They are overridden
31
+ # by options with same name that are bound to this object.
32
+ def options(theParentOptions)
33
+ resulting_options = theParentOptions.merge(@local_options)
34
+ return resulting_options
35
+ end
36
+
37
+ # Template method.
38
+ # Purpose: Return the String representation of the expression.
39
+ def to_str()
40
+ result = ''
41
+ result << prefix
42
+ result << text_repr
43
+ result << suffix
44
+
45
+ return result
46
+ end
47
+
48
+ protected
49
+
50
+ def prefix()
51
+ begin_anchor ? begin_anchor.to_str : ''
52
+ end
53
+
54
+ def suffix()
55
+ end_anchor ? end_anchor.to_str : ''
56
+ end
57
+ end # class
58
+ end # module
59
+
60
+ # End of file
@@ -0,0 +1,50 @@
1
+ # File: Lookaround.rb
2
+
3
+ ########################
4
+ # TODO: make it a binary expression
5
+ ########################
6
+
7
+
8
+ require_relative 'polyadic_expression' # Access the superclass
9
+
10
+ module Regex # This module is used as a namespace
11
+ # Lookaround is a zero-width assertion just like the start and end of line
12
+ # anchors.
13
+ # The difference is that lookarounds will actually match characters, but only
14
+ # return the result of the match: match or no match.
15
+ # That is why they are called "assertions". They do not consume characters
16
+ # from the subject, but only assert whether a match is possible or not.
17
+ class Lookaround < PolyadicExpression
18
+ # The "direction" of the lookaround. Can be ahead or behind. It specifies
19
+ # the relative position of the expression to match compared to
20
+ # the current 'position' in the subject text.
21
+ attr_reader(:dir)
22
+
23
+ # The kind indicates whether the assertion is positive
24
+ # (succeeds when there is a match) or negative
25
+ # (assertion succeeds when there is NO match).
26
+ attr_reader(:kind)
27
+
28
+ # Constructor.
29
+ # [assertedExpression] A sub-expression to match.
30
+ # [theDir] One of the following values: [ :ahead, :behind ]
31
+ # [theKind] One of the following values: [ :positive, :negative ]
32
+ def initialize(assertedExpression, theDir, theKind)
33
+ super([assertedExpression])
34
+ @dir = theDir
35
+ @kind = theKind
36
+ end
37
+
38
+ # Conversion method re-definition.
39
+ # Purpose: Return the String representation of the captured expression.
40
+ def to_str()
41
+ result = children[0].to_str
42
+ dir_syntax = (dir == :ahead) ? '' : '<'
43
+ kind_syntax = (kind == :positive) ? '=' : '!'
44
+ result << '(?' + dir_syntax + kind_syntax + children[1].to_str + ')'
45
+ return result
46
+ end
47
+ end # class
48
+ end # module
49
+
50
+ # End of file
@@ -0,0 +1,34 @@
1
+ # File: MatchOption.rb
2
+
3
+ module Regex # This module is used as a namespace
4
+ # Represents an option that influences the way a regular (sub)expression
5
+ # can perform its matching.
6
+ class MatchOption
7
+ # The symbolic name of the option
8
+ attr_reader(:name)
9
+
10
+ # An indicator that tells whether the option is turned on or off
11
+ attr_reader(:setting)
12
+
13
+ # Constructor.
14
+ def initialize(theName, theSetting)
15
+ @name = theName
16
+ @setting = theSetting
17
+ end
18
+
19
+ # Equality operator
20
+ def ==(other)
21
+ return true if object_id == other.object_id
22
+
23
+ if other.kind_of?(MatchOption)
24
+ isEqual = ((name == other.name) && (setting == other.setting))
25
+ else
26
+ isEqual = false
27
+ end
28
+
29
+ return isEqual
30
+ end
31
+ end # class
32
+ end # module
33
+
34
+ # End of file
@@ -0,0 +1,28 @@
1
+ # File: monadic_expression.rb
2
+
3
+ require_relative 'compound_expression' # Access the superclass
4
+
5
+ module Regex # This module is used as a namespace
6
+ # Abstract class. An element that is part of a regular expression &
7
+ # that can have up to one child sub-expression.
8
+ class MonadicExpression < CompoundExpression
9
+ # The (optional) child sub-expression
10
+ attr_reader(:child)
11
+
12
+ # Constructor.
13
+ def initialize(theChild)
14
+ super()
15
+ @child = theChild
16
+ end
17
+
18
+ protected
19
+
20
+ # Return the text representation of the child (if any)
21
+ def all_child_text()
22
+ result = child.nil? ? '' : child.to_str
23
+
24
+ return result
25
+ end
26
+ end # class
27
+ end # module
28
+ # End of file
@@ -0,0 +1,91 @@
1
+ # File: Multiplicity.rb
2
+
3
+ module SRL
4
+ module Regex # This module is used as a namespace
5
+ # The multiplicity specifies by how much a given expression can be repeated.
6
+ class Multiplicity
7
+ # The lowest acceptable repetition count
8
+ attr_reader(:lower_bound)
9
+
10
+ # The highest possible repetition count
11
+ attr_reader(:upper_bound)
12
+
13
+ # An indicator that specifies how to repeat (:greedy, :lazy, :possessive)
14
+ attr_reader(:policy)
15
+
16
+ # @param aLowerBound [Integer]
17
+ # @param anUpperBound [Integer, Symbol] integer or :more symbol
18
+ # @param aPolicy [Symbol] One of: (:greedy, :lazy, :possessive)
19
+ def initialize(aLowerBound, anUpperBound, aPolicy)
20
+ @lower_bound = valid_lower_bound(aLowerBound)
21
+ @upper_bound = valid_upper_bound(anUpperBound)
22
+ @policy = valid_policy(aPolicy)
23
+ end
24
+
25
+ # Purpose: Return the String representation of the multiplicity.
26
+ def to_str()
27
+ case upper_bound
28
+ when :more
29
+ case lower_bound
30
+ when 0
31
+ subresult = '*'
32
+ when 1
33
+ subresult = '+'
34
+ else
35
+ subresult = "{#{lower_bound},}"
36
+ end
37
+
38
+ when lower_bound
39
+ subresult = "{#{lower_bound}}"
40
+ else
41
+ if [lower_bound, upper_bound] == [0, 1]
42
+ subresult = '?'
43
+ else
44
+ subresult = "{#{lower_bound},#{upper_bound}}"
45
+ end
46
+ end
47
+
48
+ suffix = case policy
49
+ when :greedy
50
+ ''
51
+ when :lazy
52
+ '?'
53
+ when :possessive
54
+ '+'
55
+ end
56
+
57
+ return subresult + suffix
58
+ end
59
+
60
+ private
61
+
62
+ # Validation method. Return the validated lower bound value
63
+ def valid_lower_bound(aLowerBound)
64
+ err_msg = "Invalid lower bound of repetition count #{aLowerBound}"
65
+ raise StandardError, err_msg unless aLowerBound.kind_of?(Integer)
66
+ return aLowerBound
67
+ end
68
+
69
+ # Validation method. Return the validated lower bound value
70
+ def valid_upper_bound(anUpperBound)
71
+ err_msg = "Invalid upper bound of repetition count #{anUpperBound}"
72
+ unless anUpperBound.kind_of?(Integer) || (anUpperBound == :more)
73
+ raise StandardError, err_msg
74
+ end
75
+
76
+ return anUpperBound
77
+ end
78
+
79
+ # Validation method. Return the validated policy value.
80
+ def valid_policy(aPolicy)
81
+ err_msg = "Invalid repetition policy '#{aPolicy}'."
82
+ valid_policies = %i[greedy lazy possessive]
83
+ raise StandardError, err_msg unless valid_policies.include? aPolicy
84
+
85
+ return aPolicy
86
+ end
87
+ end # class
88
+ end # module
89
+ end # module
90
+
91
+ # End of file
@@ -0,0 +1,27 @@
1
+ # File: non_capturing_group.rb
2
+
3
+ require_relative 'monadic_expression' # Access the superclass
4
+
5
+ module Regex # This module is used as a namespace
6
+ # A non-capturing group, in other word it is a pure grouping
7
+ # of sub-expressions
8
+ class NonCapturingGroup < MonadicExpression
9
+ # Constructor.
10
+ # [aChildExpression] A sub-expression to match. When successful
11
+ # the matching text is assigned to the capture variable.
12
+ def initialize(aChildExpression)
13
+ super(aChildExpression)
14
+ end
15
+
16
+ protected
17
+
18
+ # Conversion method re-definition.
19
+ # Purpose: Return the String representation of the captured expression.
20
+ def text_repr()
21
+ result = '(?:' + all_child_text + ')'
22
+ return result
23
+ end
24
+ end # class
25
+ end # module
26
+
27
+ # End of file
@@ -0,0 +1,60 @@
1
+ # File: polyadic_expression.rb
2
+
3
+ require_relative 'compound_expression' # Access the superclass
4
+
5
+ module Regex # This module is used as a namespace
6
+ # Abstract class. An element that is part of a regular expression &
7
+ # that has its own child sub-expressions.
8
+ class PolyadicExpression < CompoundExpression
9
+ # The aggregation of child elements
10
+ attr_reader(:children)
11
+
12
+ # Constructor.
13
+ def initialize(theChildren)
14
+ super()
15
+ @children = theChildren
16
+ end
17
+
18
+ # Append the given child to the list of children.
19
+ # TODO: assess whether to defer to a subclass NAryExpression
20
+ def <<(aChild)
21
+ @children << aChild
22
+
23
+ return self
24
+ end
25
+
26
+ # Build a depth-first in-order children visitor.
27
+ # The visitor is implemented as an Enumerator.
28
+ def df_visitor()
29
+ root = children # The visit will start from the children of this object
30
+
31
+ visitor = Enumerator.new do |result| # result is a Yielder
32
+ # Initialization part: will run once
33
+ visit_stack = [root] # The LIFO queue of nodes to visit
34
+
35
+ begin # Traversal part (as a loop)
36
+ top = visit_stack.pop
37
+ if top.kind_of?(Array)
38
+ next if top.empty?
39
+ currChild = top.pop
40
+ visit_stack.push top
41
+ else
42
+ currChild = top
43
+ end
44
+
45
+ result << currChild # Return the visited child
46
+
47
+ unless currChild.atomic?
48
+ # in-order traversal implies LIFO queue
49
+ children_to_enqueue = currChild.children.reverse
50
+ visit_stack.push(children_to_enqueue)
51
+ end
52
+ end until visit_stack.empty?
53
+ end
54
+
55
+ return visitor
56
+ end
57
+ end # class
58
+ end # module
59
+
60
+ # End of file
@@ -0,0 +1,22 @@
1
+ # File: quantifiable.rb
2
+
3
+ require_relative 'multiplicity'
4
+
5
+ module Regex # This module is used as a namespace
6
+ module Quantifiable
7
+ # Redefined method. Return true since it may not have any child.
8
+ def quantified?
9
+ return @quantifier.nil? ? false : true
10
+ end
11
+
12
+ def quantifier
13
+ @quantifier
14
+ end
15
+
16
+ def quantifier=(aQuantifier)
17
+ @quantifier = aQuantifier
18
+ end
19
+ end # module
20
+ end # module
21
+
22
+ # End of file
@@ -0,0 +1,29 @@
1
+ # File: repetition.rb
2
+
3
+ require_relative 'monadic_expression' # Access the superclass
4
+
5
+ module Regex # This module is used as a namespace
6
+ # Abstract class. An unary matching operator.
7
+ # It succeeds when the specified repetition of the child expression
8
+ # succeeds to match the subject text in the same serial arrangement
9
+ class Repetition < MonadicExpression
10
+ attr_reader(:multiplicity)
11
+
12
+ # Constructor.
13
+ def initialize(childExpressionToRepeat, aMultiplicity)
14
+ super(childExpressionToRepeat)
15
+ @multiplicity = aMultiplicity
16
+ end
17
+
18
+ protected
19
+
20
+ # Conversion method re-definition.
21
+ # Purpose: Return the String representation of the concatented expressions.
22
+ def text_repr()
23
+ result = all_child_text + multiplicity.to_str
24
+ return result
25
+ end
26
+ end # class
27
+ end # module
28
+
29
+ # End of file