walrat 0.1

Sign up to get free protection for your applications and to get access to all the features.
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