walrat 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 (38) hide show
  1. data/lib/walrat.rb +70 -0
  2. data/lib/walrat/additions/proc.rb +32 -0
  3. data/lib/walrat/additions/regexp.rb +33 -0
  4. data/lib/walrat/additions/string.rb +99 -0
  5. data/lib/walrat/additions/symbol.rb +42 -0
  6. data/lib/walrat/and_predicate.rb +49 -0
  7. data/lib/walrat/array_result.rb +29 -0
  8. data/lib/walrat/continuation_wrapper_exception.rb +35 -0
  9. data/lib/walrat/grammar.rb +259 -0
  10. data/lib/walrat/left_recursion_exception.rb +34 -0
  11. data/lib/walrat/location_tracking.rb +126 -0
  12. data/lib/walrat/match_data_wrapper.rb +84 -0
  13. data/lib/walrat/memoizing.rb +55 -0
  14. data/lib/walrat/memoizing_cache.rb +126 -0
  15. data/lib/walrat/no_parameter_marker.rb +30 -0
  16. data/lib/walrat/node.rb +63 -0
  17. data/lib/walrat/not_predicate.rb +49 -0
  18. data/lib/walrat/parse_error.rb +48 -0
  19. data/lib/walrat/parser_state.rb +205 -0
  20. data/lib/walrat/parslet.rb +38 -0
  21. data/lib/walrat/parslet_choice.rb +155 -0
  22. data/lib/walrat/parslet_combination.rb +34 -0
  23. data/lib/walrat/parslet_combining.rb +190 -0
  24. data/lib/walrat/parslet_merge.rb +96 -0
  25. data/lib/walrat/parslet_omission.rb +74 -0
  26. data/lib/walrat/parslet_repetition.rb +114 -0
  27. data/lib/walrat/parslet_repetition_default.rb +77 -0
  28. data/lib/walrat/parslet_sequence.rb +241 -0
  29. data/lib/walrat/predicate.rb +68 -0
  30. data/lib/walrat/proc_parslet.rb +60 -0
  31. data/lib/walrat/regexp_parslet.rb +84 -0
  32. data/lib/walrat/skipped_substring_exception.rb +46 -0
  33. data/lib/walrat/string_enumerator.rb +47 -0
  34. data/lib/walrat/string_parslet.rb +89 -0
  35. data/lib/walrat/string_result.rb +34 -0
  36. data/lib/walrat/symbol_parslet.rb +82 -0
  37. data/lib/walrat/version.rb +26 -0
  38. metadata +110 -0
@@ -0,0 +1,34 @@
1
+ # Copyright 2007-2010 Wincent Colaiuta. All rights reserved.
2
+ # Redistribution and use in source and binary forms, with or without
3
+ # modification, are permitted provided that the following conditions are met:
4
+ #
5
+ # 1. Redistributions of source code must retain the above copyright notice,
6
+ # this list of conditions and the following disclaimer.
7
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
8
+ # this list of conditions and the following disclaimer in the documentation
9
+ # and/or other materials provided with the distribution.
10
+ #
11
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
12
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
13
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
14
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
15
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
16
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
17
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
18
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
19
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
20
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
21
+ # POSSIBILITY OF SUCH DAMAGE.
22
+
23
+ require 'walrat'
24
+
25
+ module Walrat
26
+ class ParsletCombination
27
+ include Walrat::ParsletCombining
28
+ include Walrat::Memoizing
29
+
30
+ def to_parseable
31
+ self
32
+ end
33
+ end # module ParsletCombination
34
+ end # module Walrat
@@ -0,0 +1,190 @@
1
+ # Copyright 2007-2010 Wincent Colaiuta. All rights reserved.
2
+ # Redistribution and use in source and binary forms, with or without
3
+ # modification, are permitted provided that the following conditions are met:
4
+ #
5
+ # 1. Redistributions of source code must retain the above copyright notice,
6
+ # this list of conditions and the following disclaimer.
7
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
8
+ # this list of conditions and the following disclaimer in the documentation
9
+ # and/or other materials provided with the distribution.
10
+ #
11
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
12
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
13
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
14
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
15
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
16
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
17
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
18
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
19
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
20
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
21
+ # POSSIBILITY OF SUCH DAMAGE.
22
+
23
+ require 'walrat'
24
+
25
+ module Walrat
26
+ # The ParsletCombining module, together with the ParsletCombination class and
27
+ # its subclasses, provides simple container classes for encapsulating
28
+ # relationships among Parslets. By storing this information outside of the
29
+ # Parslet objects themselves their design is kept clean and they can become
30
+ # immutable objects which are much more easily copied and shared among
31
+ # multiple rules in a Grammar.
32
+ module ParsletCombining
33
+ # Convenience method.
34
+ def memoizing_parse string, options = {}
35
+ self.to_parseable.memoizing_parse string, options
36
+ end
37
+
38
+ # Convenience method.
39
+ def parse string, options = {}
40
+ self.to_parseable.parse string, options
41
+ end
42
+
43
+ # Defines a sequence of Parslets (or ParsletCombinations).
44
+ # Returns a ParsletSequence instance.
45
+ def sequence first, second, *others
46
+ Walrat::ParsletSequence.new first.to_parseable,
47
+ second.to_parseable, *others
48
+ end
49
+
50
+ # Shorthand for ParsletCombining.sequence(first, second).
51
+ def &(next_parslet)
52
+ self.sequence self, next_parslet
53
+ end
54
+
55
+ # Defines a sequence of Parslets similar to the sequence method but with
56
+ # the difference that the contents of array results from the component
57
+ # parslets will be merged into a single array rather than being added as
58
+ # arrays. To illustrate:
59
+ #
60
+ # 'foo' & 'bar'.one_or_more # returns results like ['foo', ['bar', 'bar', 'bar']]
61
+ # 'foo' >> 'bar'.one_or_more # returns results like ['foo', 'bar', 'bar', 'bar']
62
+ #
63
+ def merge first, second, *others
64
+ Walrat::ParsletMerge.new first.to_parseable,
65
+ second.to_parseable, *others
66
+ end
67
+
68
+ # Shorthand for ParsletCombining.sequence(first, second)
69
+ def >>(next_parslet)
70
+ self.merge self, next_parslet
71
+ end
72
+
73
+ # Defines a choice of Parslets (or ParsletCombinations).
74
+ # Returns a ParsletChoice instance.
75
+ def choice left, right, *others
76
+ Walrat::ParsletChoice.new left.to_parseable,
77
+ right.to_parseable, *others
78
+ end
79
+
80
+ # Shorthand for ParsletCombining.choice(left, right)
81
+ def |(alternative_parslet)
82
+ self.choice self, alternative_parslet
83
+ end
84
+
85
+ # Defines a repetition of the supplied Parslet (or ParsletCombination).
86
+ # Returns a ParsletRepetition instance.
87
+ def repetition parslet, min, max
88
+ Walrat::ParsletRepetition.new parslet.to_parseable, min, max
89
+ end
90
+
91
+ # Shorthand for ParsletCombining.repetition.
92
+ def repeat min = nil, max = nil
93
+ self.repetition self, min, max
94
+ end
95
+
96
+ def repetition_with_default parslet, min, max, default
97
+ Walrat::ParsletRepetitionDefault.new parslet.to_parseable, min,
98
+ max, default
99
+ end
100
+
101
+ def repeat_with_default min = nil, max = nil, default = nil
102
+ self.repetition_with_default self, min, max, default
103
+ end
104
+
105
+ # Shorthand for ParsletCombining.repetition(0, 1).
106
+ # This method optionally takes a single parameter specifying what object
107
+ # should be returned as a placeholder when there are no matches; this is
108
+ # useful for packing into ASTs where it may be better to parse an empty
109
+ # Array rather than nil. The specified object is cloned and returned in the
110
+ # event that there are no matches. As a convenience, the specified object
111
+ # is automatically extended using the LocationTracking module (this is a
112
+ # convenience so that you can specify empty Arrays, "[]", rather than
113
+ # explicitly passing an "ArrayResult.new")
114
+ def optional default_return_value = NoParameterMarker.instance
115
+ if default_return_value == NoParameterMarker.instance
116
+ self.repeat 0, 1 # default behaviour
117
+ else
118
+ self.repeat_with_default 0, 1, default_return_value
119
+ end
120
+ end
121
+
122
+ # Alternative to optional.
123
+ def zero_or_one
124
+ self.optional
125
+ end
126
+
127
+ # possible synonym "star"
128
+ def zero_or_more default_return_value = NoParameterMarker.instance
129
+ if default_return_value == NoParameterMarker.instance
130
+ self.repeat 0 # default behaviour
131
+ else
132
+ self.repeat_with_default 0, nil, default_return_value
133
+ end
134
+ end
135
+
136
+ # possible synonym "plus"
137
+ def one_or_more
138
+ self.repeat 1
139
+ end
140
+
141
+ # Parsing Expression Grammar support.
142
+ # Succeeds if parslet succeeds but consumes no input (throws an
143
+ # :AndPredicateSuccess symbol).
144
+ def and_predicate parslet
145
+ Walrat::AndPredicate.new parslet.to_parseable
146
+ end
147
+
148
+ # Shorthand for and_predicate
149
+ # Strictly speaking, this shorthand breaks with established Ruby practice
150
+ # that "?" at the end of a method name should indicate a method that
151
+ # returns true or false.
152
+ def and?
153
+ self.and_predicate self
154
+ end
155
+
156
+ # Parsing Expression Grammar support.
157
+ # Succeeds if parslet fails (throws a :NotPredicateSuccess symbol).
158
+ # Fails if parslet succeeds (raise a ParseError).
159
+ # Consumes no output.
160
+ # This method will almost invariably be used in conjuntion with the &
161
+ # operator, like this:
162
+ # rule :foo, :p1 & :p2.not_predicate
163
+ # rule :foo, :p1 & :p2.not!
164
+ def not_predicate parslet
165
+ Walrat::NotPredicate.new parslet.to_parseable
166
+ end
167
+
168
+ # Shorthand for not_predicate.
169
+ # Strictly speaking, this shorthand breaks with established Ruby practice
170
+ # that "!" at the end of a method name should indicate a destructive
171
+ # behaviour on (mutation of) the receiver.
172
+ def not!
173
+ self.not_predicate self
174
+ end
175
+
176
+ # Succeeds if parsing succeeds, consuming the output, but doesn't actually
177
+ # return anything.
178
+ #
179
+ # This is for elements which are required but which shouldn't appear in the
180
+ # final AST.
181
+ def omission parslet
182
+ Walrat::ParsletOmission.new parslet.to_parseable
183
+ end
184
+
185
+ # Shorthand for ParsletCombining.omission
186
+ def skip
187
+ self.omission self
188
+ end
189
+ end # module ParsletCombining
190
+ end # module Walrat
@@ -0,0 +1,96 @@
1
+ # Copyright 2007-2010 Wincent Colaiuta. All rights reserved.
2
+ # Redistribution and use in source and binary forms, with or without
3
+ # modification, are permitted provided that the following conditions are met:
4
+ #
5
+ # 1. Redistributions of source code must retain the above copyright notice,
6
+ # this list of conditions and the following disclaimer.
7
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
8
+ # this list of conditions and the following disclaimer in the documentation
9
+ # and/or other materials provided with the distribution.
10
+ #
11
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
12
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
13
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
14
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
15
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
16
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
17
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
18
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
19
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
20
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
21
+ # POSSIBILITY OF SUCH DAMAGE.
22
+
23
+ require 'walrat'
24
+
25
+ module Walrat
26
+ class ParsletMerge < ParsletSequence
27
+ def parse string, options = {}
28
+ raise ArgumentError if string.nil?
29
+ state = ParserState.new string, options
30
+ last_caught = nil # keep track of the last kind of throw to be caught
31
+ @components.each do |parseable|
32
+ catch :ProcessNextComponent do
33
+ catch :NotPredicateSuccess do
34
+ catch :AndPredicateSuccess do
35
+ catch :ZeroWidthParseSuccess do
36
+ begin
37
+ parsed = parseable.memoizing_parse state.remainder, state.options
38
+ if parsed.respond_to? :each
39
+ parsed.each { |element| state.parsed element }
40
+ else
41
+ state.parsed(parsed)
42
+ end
43
+ rescue SkippedSubstringException => e
44
+ state.skipped(e)
45
+ # rescue ParseError => e # failed, will try to skip; save original error in case skipping fails
46
+ # if options.has_key?(:skipping_override) : skipping_parslet = options[:skipping_override]
47
+ # elsif options.has_key?(:skipping) : skipping_parslet = options[:skipping]
48
+ # else skipping_parslet = nil
49
+ # end
50
+ # raise e if skipping_parslet.nil? # no skipper defined, raise original error
51
+ # begin
52
+ # # guard against self references (possible infinite recursion) here?
53
+ # parsed = skipping_parslet.memoizing_parse(state.remainder, state.options)
54
+ # state.skipped(parsed)
55
+ # redo # skipping succeeded, try to redo
56
+ # rescue ParseError
57
+ # raise e # skipping didn't help either, raise original error
58
+ # end
59
+ end
60
+ last_caught = nil
61
+ throw :ProcessNextComponent # can't use "next" here because it will only break out of innermost "do"
62
+ end
63
+ last_caught = :ZeroWidthParseSuccess
64
+ throw :ProcessNextComponent
65
+ end
66
+ last_caught = :AndPredicateSuccess
67
+ throw :ProcessNextComponent
68
+ end
69
+ last_caught = :NotPredicateSuccess
70
+ end
71
+ end
72
+
73
+ if state.results.respond_to? :empty? and state.results.empty? and
74
+ throw last_caught
75
+ else
76
+ state.results
77
+ end
78
+ end
79
+
80
+ def eql?(other)
81
+ return false if not other.instance_of? ParsletMerge
82
+ other_components = other.components
83
+ return false if @components.length != other_components.length
84
+ for i in 0..(@components.length - 1)
85
+ return false unless @components[i].eql? other_components[i]
86
+ end
87
+ true
88
+ end
89
+
90
+ private
91
+
92
+ def hash_offset
93
+ 53
94
+ end
95
+ end # class ParsletMerge
96
+ end # module Walrat
@@ -0,0 +1,74 @@
1
+ # Copyright 2007-2010 Wincent Colaiuta. All rights reserved.
2
+ # Redistribution and use in source and binary forms, with or without
3
+ # modification, are permitted provided that the following conditions are met:
4
+ #
5
+ # 1. Redistributions of source code must retain the above copyright notice,
6
+ # this list of conditions and the following disclaimer.
7
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
8
+ # this list of conditions and the following disclaimer in the documentation
9
+ # and/or other materials provided with the distribution.
10
+ #
11
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
12
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
13
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
14
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
15
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
16
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
17
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
18
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
19
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
20
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
21
+ # POSSIBILITY OF SUCH DAMAGE.
22
+
23
+ require 'walrat'
24
+
25
+ module Walrat
26
+ class ParsletOmission < ParsletCombination
27
+ attr_reader :hash
28
+
29
+ # Raises an ArgumentError if parseable is nil.
30
+ def initialize parseable
31
+ raise ArgumentError, 'nil parseable' if parseable.nil?
32
+ @parseable = parseable
33
+
34
+ # fixed offset to avoid unwanted collisions with similar classes
35
+ @hash = @parseable.hash + 46
36
+ end
37
+
38
+ def parse string, options = {}
39
+ raise ArgumentError, 'nil string' if string.nil?
40
+ substring = StringResult.new
41
+ substring.start = [options[:line_start], options[:column_start]]
42
+ substring.end = [options[:line_start], options[:column_start]]
43
+
44
+ # possibly should catch these here as well
45
+ #catch :NotPredicateSuccess do
46
+ #catch :AndPredicateSuccess do
47
+ # one of the fundamental problems is that if a parslet throws such a
48
+ # symbol any info about already skipped material is lost (because the
49
+ # symbols contain nothing)
50
+ # this may be one reason to change these to exceptions...
51
+ catch :ZeroWidthParseSuccess do
52
+ substring = @parseable.memoizing_parse(string, options)
53
+ end
54
+
55
+ # not enough to just return a ZeroWidthParseSuccess here; that could
56
+ # cause higher levels to stop parsing and in any case there'd be no
57
+ # clean way to embed the scanned substring in the symbol
58
+ raise SkippedSubstringException.new(substring,
59
+ :line_start => options[:line_start],
60
+ :column_start => options[:column_start],
61
+ :line_end => substring.line_end,
62
+ :column_end => substring.column_end)
63
+ end
64
+
65
+ def eql?(other)
66
+ other.instance_of? ParsletOmission and other.parseable.eql? @parseable
67
+ end
68
+
69
+ protected
70
+
71
+ # For determining equality.
72
+ attr_reader :parseable
73
+ end # class ParsletOmission
74
+ end # module Walrat
@@ -0,0 +1,114 @@
1
+ # Copyright 2007-2010 Wincent Colaiuta. All rights reserved.
2
+ # Redistribution and use in source and binary forms, with or without
3
+ # modification, are permitted provided that the following conditions are met:
4
+ #
5
+ # 1. Redistributions of source code must retain the above copyright notice,
6
+ # this list of conditions and the following disclaimer.
7
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
8
+ # this list of conditions and the following disclaimer in the documentation
9
+ # and/or other materials provided with the distribution.
10
+ #
11
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
12
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
13
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
14
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
15
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
16
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
17
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
18
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
19
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
20
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
21
+ # POSSIBILITY OF SUCH DAMAGE.
22
+
23
+ require 'walrat'
24
+
25
+ module Walrat
26
+ class ParsletRepetition < ParsletCombination
27
+ attr_reader :hash
28
+
29
+ # Raises an ArgumentError if parseable or min is nil.
30
+ def initialize parseable, min, max = nil
31
+ raise ArgumentError, 'nil parseable' if parseable.nil?
32
+ raise ArgumentError, 'nil min' if min.nil?
33
+ @parseable = parseable
34
+ self.min = min
35
+ self.max = max
36
+ end
37
+
38
+ def parse string, options = {}
39
+ raise ArgumentError, 'nil string' if string.nil?
40
+ state = ParserState.new string, options
41
+ catch :ZeroWidthParseSuccess do # a zero-width match is grounds for immediate abort
42
+ while @max.nil? or state.length < @max # try forever if max is nil; otherwise keep trying while match count < max
43
+ begin
44
+ parsed = @parseable.memoizing_parse state.remainder, state.options
45
+ state.parsed parsed
46
+ rescue SkippedSubstringException => e
47
+ state.skipped e
48
+ rescue ParseError => e # failed, will try to skip; save original error in case skipping fails
49
+ if options.has_key?(:skipping_override)
50
+ skipping_parslet = options[:skipping_override]
51
+ elsif options.has_key?(:skipping)
52
+ skipping_parslet = options[:skipping]
53
+ else
54
+ skipping_parslet = nil
55
+ end
56
+ break if skipping_parslet.nil?
57
+ begin
58
+ # guard against self references (possible infinite recursion) here?
59
+ parsed = skipping_parslet.memoizing_parse state.remainder, state.options
60
+ state.skipped parsed
61
+ redo # skipping succeeded, try to redo
62
+ rescue ParseError
63
+ break # skipping didn't help either, give up
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+ # now assess whether our tries met the requirements
70
+ if state.length == 0 and @min == 0 # success (special case)
71
+ throw :ZeroWidthParseSuccess
72
+ elsif state.length < @min # matches < min (failure)
73
+ raise ParseError.new('required %d matches but obtained %d while parsing "%s"' % [@min, state.length, string],
74
+ :line_end => state.options[:line_end],
75
+ :column_end => state.options[:column_end])
76
+ else # success (general case)
77
+ state.results # returns multiple matches as an array, single matches as a single object
78
+ end
79
+ end
80
+
81
+ def eql?(other)
82
+ other.instance_of? ParsletRepetition and
83
+ @min == other.min and
84
+ @max == other.max and
85
+ @parseable.eql? other.parseable
86
+ end
87
+
88
+ protected
89
+
90
+ # For determining equality.
91
+ attr_reader :parseable, :min, :max
92
+
93
+ private
94
+
95
+ def hash_offset
96
+ 87
97
+ end
98
+
99
+ def update_hash
100
+ # fixed offset to minimize risk of collisions
101
+ @hash = @min.hash + @max.hash + @parseable.hash + hash_offset
102
+ end
103
+
104
+ def min=(min)
105
+ @min = (min.clone rescue min)
106
+ update_hash
107
+ end
108
+
109
+ def max=(max)
110
+ @max = (max.clone rescue max)
111
+ update_hash
112
+ end
113
+ end # class ParsletRepetition
114
+ end # module Walrat