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.
- data/lib/walrat.rb +70 -0
- data/lib/walrat/additions/proc.rb +32 -0
- data/lib/walrat/additions/regexp.rb +33 -0
- data/lib/walrat/additions/string.rb +99 -0
- data/lib/walrat/additions/symbol.rb +42 -0
- data/lib/walrat/and_predicate.rb +49 -0
- data/lib/walrat/array_result.rb +29 -0
- data/lib/walrat/continuation_wrapper_exception.rb +35 -0
- data/lib/walrat/grammar.rb +259 -0
- data/lib/walrat/left_recursion_exception.rb +34 -0
- data/lib/walrat/location_tracking.rb +126 -0
- data/lib/walrat/match_data_wrapper.rb +84 -0
- data/lib/walrat/memoizing.rb +55 -0
- data/lib/walrat/memoizing_cache.rb +126 -0
- data/lib/walrat/no_parameter_marker.rb +30 -0
- data/lib/walrat/node.rb +63 -0
- data/lib/walrat/not_predicate.rb +49 -0
- data/lib/walrat/parse_error.rb +48 -0
- data/lib/walrat/parser_state.rb +205 -0
- data/lib/walrat/parslet.rb +38 -0
- data/lib/walrat/parslet_choice.rb +155 -0
- data/lib/walrat/parslet_combination.rb +34 -0
- data/lib/walrat/parslet_combining.rb +190 -0
- data/lib/walrat/parslet_merge.rb +96 -0
- data/lib/walrat/parslet_omission.rb +74 -0
- data/lib/walrat/parslet_repetition.rb +114 -0
- data/lib/walrat/parslet_repetition_default.rb +77 -0
- data/lib/walrat/parslet_sequence.rb +241 -0
- data/lib/walrat/predicate.rb +68 -0
- data/lib/walrat/proc_parslet.rb +60 -0
- data/lib/walrat/regexp_parslet.rb +84 -0
- data/lib/walrat/skipped_substring_exception.rb +46 -0
- data/lib/walrat/string_enumerator.rb +47 -0
- data/lib/walrat/string_parslet.rb +89 -0
- data/lib/walrat/string_result.rb +34 -0
- data/lib/walrat/symbol_parslet.rb +82 -0
- data/lib/walrat/version.rb +26 -0
- 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
|