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