parslet 1.2.3 → 1.3.0
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/HISTORY.txt +21 -0
- data/README +1 -1
- data/example/ignore_whitespace.rb +66 -0
- data/example/mathn.rb +44 -0
- data/example/output/ignore_whitespace.out +1 -0
- data/example/output/ip_address.out +2 -2
- data/example/output/mathn.out +4 -0
- data/lib/parslet.rb +8 -1
- data/lib/parslet/atoms.rb +1 -0
- data/lib/parslet/atoms/alternative.rb +1 -1
- data/lib/parslet/atoms/base.rb +26 -157
- data/lib/parslet/atoms/can_flatten.rb +132 -0
- data/lib/parslet/atoms/lookahead.rb +5 -8
- data/lib/parslet/atoms/str.rb +1 -1
- data/lib/parslet/atoms/visitor.rb +23 -9
- data/lib/parslet/bytecode.rb +6 -0
- data/lib/parslet/bytecode/compiler.rb +138 -0
- data/lib/parslet/bytecode/instructions.rb +358 -0
- data/lib/parslet/bytecode/vm.rb +209 -0
- data/lib/parslet/cause.rb +62 -0
- data/lib/parslet/export.rb +2 -2
- data/lib/parslet/rig/rspec.rb +18 -17
- data/lib/parslet/source.rb +66 -48
- data/lib/parslet/source/line_cache.rb +7 -1
- data/lib/parslet/transform/context.rb +15 -7
- metadata +57 -16
- data/Gemfile +0 -16
- data/lib/parslet/atoms/transform.rb +0 -75
data/HISTORY.txt
CHANGED
@@ -3,6 +3,27 @@
|
|
3
3
|
- prsnt? and absnt? are now finally banned into oblivion. Wasting vocals for
|
4
4
|
the win.
|
5
5
|
|
6
|
+
= 1.3.1 / ???
|
7
|
+
|
8
|
+
= 1.3.0 / 5Mar2012
|
9
|
+
|
10
|
+
! Parslet::Transform::Context is now much more well-behaved. It has
|
11
|
+
#respond_to? and #method_missing; it now looks like a plain old Ruby
|
12
|
+
object with instance variables and attribute readers.
|
13
|
+
|
14
|
+
- Grammar transforms turned out to be a dead end and have been removed.
|
15
|
+
|
16
|
+
! A few problems in error message generation have been fixed. This will
|
17
|
+
improve diagnostics further.
|
18
|
+
|
19
|
+
+ A VM driven parser engine: Removes the limitation that parsing needs a
|
20
|
+
lot of stack space, something dearly missing from Ruby 1.9.3 fibers.
|
21
|
+
This engine is experimental and might be removed in the future.
|
22
|
+
|
23
|
+
! Interaction with mathn fixed - Line number generation will terminate.
|
24
|
+
|
25
|
+
. Internal reorganisation, removing cruft and bit rot.
|
26
|
+
|
6
27
|
= 1.2.3 / 22Sep2011
|
7
28
|
|
8
29
|
+ Transform#apply can now be called with a hash as second argument. This
|
data/README
CHANGED
@@ -0,0 +1,66 @@
|
|
1
|
+
# An example on how to ignore whitespace. Use the composition, luke.
|
2
|
+
|
3
|
+
$:.unshift File.dirname(__FILE__) + "/../lib"
|
4
|
+
|
5
|
+
require 'pp'
|
6
|
+
require 'parslet'
|
7
|
+
require 'parslet/convenience'
|
8
|
+
|
9
|
+
class AParser < Parslet::Parser
|
10
|
+
root :as
|
11
|
+
|
12
|
+
rule(:as) { a.repeat }
|
13
|
+
rule(:a) { str('a').as(:a) }
|
14
|
+
end
|
15
|
+
|
16
|
+
class WsIgnoreSource
|
17
|
+
def initialize(string)
|
18
|
+
@io = StringIO.new(string)
|
19
|
+
@early_eof = nil
|
20
|
+
end
|
21
|
+
|
22
|
+
def pos
|
23
|
+
@io.pos
|
24
|
+
end
|
25
|
+
|
26
|
+
def pos=(n)
|
27
|
+
@io.pos = n
|
28
|
+
end
|
29
|
+
|
30
|
+
def gets(buf, n)
|
31
|
+
return nil if eof?
|
32
|
+
|
33
|
+
return read(n).tap {
|
34
|
+
@early_eof = pos unless can_read?
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
def eof?
|
39
|
+
@io.eof? || # the underlying source is EOF
|
40
|
+
@early_eof && pos >= @early_eof # we have no non-ws chars left
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
# Reads n chars from @io.
|
46
|
+
def read(n)
|
47
|
+
b = ''
|
48
|
+
while b.size < n && !@io.eof?
|
49
|
+
c = @io.gets(nil, 1)
|
50
|
+
b << c unless c == ' '
|
51
|
+
end
|
52
|
+
b
|
53
|
+
end
|
54
|
+
|
55
|
+
# True if there are any chars left in @io.
|
56
|
+
def can_read?
|
57
|
+
old_pos = @io.pos
|
58
|
+
read(1).size == 1
|
59
|
+
rescue => ex
|
60
|
+
return false
|
61
|
+
ensure
|
62
|
+
@io.pos = old_pos
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
pp AParser.new.parse_with_debug(WsIgnoreSource.new('a a a a '))
|
data/example/mathn.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# Demonstrates that we have a compatibility fix to mathn's weird idea of
|
2
|
+
# integer mathematics.
|
3
|
+
# This was contributed by Jonathan Hinkle (https://github.com/hynkle). Thanks!
|
4
|
+
|
5
|
+
$:.unshift File.dirname(__FILE__) + "/../lib"
|
6
|
+
|
7
|
+
require 'parslet'
|
8
|
+
require 'parslet/convenience'
|
9
|
+
include Parslet
|
10
|
+
|
11
|
+
def attempt_parse
|
12
|
+
possible_whitespace = match['\s'].repeat
|
13
|
+
|
14
|
+
cephalopod =
|
15
|
+
str('octopus') |
|
16
|
+
str('squid')
|
17
|
+
|
18
|
+
parenthesized_cephalopod =
|
19
|
+
str('(') >>
|
20
|
+
possible_whitespace >>
|
21
|
+
cephalopod >>
|
22
|
+
possible_whitespace >>
|
23
|
+
str(')')
|
24
|
+
|
25
|
+
parser =
|
26
|
+
possible_whitespace >>
|
27
|
+
parenthesized_cephalopod >>
|
28
|
+
possible_whitespace
|
29
|
+
|
30
|
+
# This parse fails, but that is not the point. When mathn is in the current
|
31
|
+
# ruby environment, it modifies integer division in a way that makes
|
32
|
+
# parslet loop indefinitely.
|
33
|
+
parser.parse %{(\nsqeed)\n}
|
34
|
+
rescue Parslet::ParseFailed
|
35
|
+
end
|
36
|
+
|
37
|
+
attempt_parse
|
38
|
+
puts 'it terminates before we require mathn'
|
39
|
+
|
40
|
+
puts "requiring mathn now"
|
41
|
+
require 'mathn'
|
42
|
+
puts "and trying again (will hang without the fix)"
|
43
|
+
attempt_parse # but it doesn't terminate after requiring mathn
|
44
|
+
puts "okay!"
|
@@ -0,0 +1 @@
|
|
1
|
+
[{:a=>"a"@0}, {:a=>"a"@1}, {:a=>"a"@5}, {:a=>"a"@7}]
|
@@ -1,9 +1,9 @@
|
|
1
1
|
0.0.0.0 -> {:ipv4=>"0.0.0.0"@0}
|
2
2
|
255.255.255.255 -> {:ipv4=>"255.255.255.255"@0}
|
3
|
-
255.255.255 -> Failed: Expected one of [IPV4, IPV6]
|
3
|
+
255.255.255 -> Failed: Expected one of [IPV4, IPV6] at line 1 char 1.
|
4
4
|
1:2:3:4:5:6:7:8 -> {:ipv6=>"1:2:3:4:5:6:7:8"@0}
|
5
5
|
12AD:34FC:A453:1922:: -> {:ipv6=>"12AD:34FC:A453:1922::"@0}
|
6
6
|
12AD::34FC -> {:ipv6=>"12AD::34FC"@0}
|
7
7
|
12AD:: -> {:ipv6=>"12AD::"@0}
|
8
8
|
:: -> {:ipv6=>"::"@0}
|
9
|
-
1:2 -> Failed: Expected one of [IPV4, IPV6]
|
9
|
+
1:2 -> Failed: Expected one of [IPV4, IPV6] at line 1 char 1.
|
data/lib/parslet.rb
CHANGED
@@ -73,6 +73,11 @@ module Parslet
|
|
73
73
|
# parslet.parse_with_debug(str)
|
74
74
|
#
|
75
75
|
class ParseFailed < StandardError
|
76
|
+
def initialize(message, cause=nil)
|
77
|
+
super(message)
|
78
|
+
@cause = cause
|
79
|
+
end
|
80
|
+
attr_reader :cause
|
76
81
|
end
|
77
82
|
|
78
83
|
# Raised when the parse operation didn't consume all of its input. In this
|
@@ -224,10 +229,12 @@ module Parslet
|
|
224
229
|
end
|
225
230
|
|
226
231
|
require 'parslet/slice'
|
232
|
+
require 'parslet/cause'
|
227
233
|
require 'parslet/source'
|
228
234
|
require 'parslet/error_tree'
|
229
235
|
require 'parslet/atoms'
|
230
236
|
require 'parslet/pattern'
|
231
237
|
require 'parslet/pattern/binding'
|
232
238
|
require 'parslet/transform'
|
233
|
-
require 'parslet/parser'
|
239
|
+
require 'parslet/parser'
|
240
|
+
require 'parslet/bytecode'
|
data/lib/parslet/atoms.rb
CHANGED
data/lib/parslet/atoms/base.rb
CHANGED
@@ -6,6 +6,7 @@
|
|
6
6
|
class Parslet::Atoms::Base
|
7
7
|
include Parslet::Atoms::Precedence
|
8
8
|
include Parslet::Atoms::DSL
|
9
|
+
include Parslet::Atoms::CanFlatten
|
9
10
|
|
10
11
|
# Internally, all parsing functions return either an instance of Fail
|
11
12
|
# or an instance of Success.
|
@@ -25,7 +26,23 @@ class Parslet::Atoms::Base
|
|
25
26
|
# and return a result. If the parse fails, a Parslet::ParseFailed exception
|
26
27
|
# will be thrown.
|
27
28
|
#
|
28
|
-
def parse(io)
|
29
|
+
def parse(io, traditional=true)
|
30
|
+
if traditional
|
31
|
+
parse_traditional(io)
|
32
|
+
else
|
33
|
+
parse_vm(io)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def parse_vm(io)
|
38
|
+
compiler = Parslet::Bytecode::Compiler.new
|
39
|
+
program = compiler.compile(self)
|
40
|
+
|
41
|
+
vm = Parslet::Bytecode::VM.new
|
42
|
+
vm.run(program, io)
|
43
|
+
end
|
44
|
+
|
45
|
+
def parse_traditional(io)
|
29
46
|
source = Parslet::Source.new(io)
|
30
47
|
context = Parslet::Atoms::Context.new
|
31
48
|
|
@@ -36,7 +53,8 @@ class Parslet::Atoms::Base
|
|
36
53
|
# Stack trace will be off, but the error tree should explain the reason
|
37
54
|
# it failed.
|
38
55
|
if value.error?
|
39
|
-
|
56
|
+
@last_cause = value.message
|
57
|
+
@last_cause.raise
|
40
58
|
end
|
41
59
|
|
42
60
|
# assert: value is a success answer
|
@@ -48,16 +66,15 @@ class Parslet::Atoms::Base
|
|
48
66
|
# error to fail with. Otherwise just report that we cannot consume the
|
49
67
|
# input.
|
50
68
|
if cause
|
51
|
-
# We'
|
52
|
-
# Still: We'll raise this differently, since the real cause is different.
|
69
|
+
# NOTE We don't overwrite last_cause here.
|
53
70
|
raise Parslet::UnconsumedInput,
|
54
71
|
"Unconsumed input, maybe because of this: #{cause}"
|
55
72
|
else
|
56
73
|
old_pos = source.pos
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
74
|
+
@last_cause = source.error(
|
75
|
+
"Don't know what to do with #{source.read(100)}", old_pos)
|
76
|
+
|
77
|
+
@last_cause.raise(Parslet::UnconsumedInput)
|
61
78
|
end
|
62
79
|
end
|
63
80
|
|
@@ -94,110 +111,6 @@ class Parslet::Atoms::Base
|
|
94
111
|
"Atoms::Base doesn't have behaviour, please implement #try(source, context)."
|
95
112
|
end
|
96
113
|
|
97
|
-
# Takes a mixed value coming out of a parslet and converts it to a return
|
98
|
-
# value for the user by dropping things and merging hashes.
|
99
|
-
#
|
100
|
-
# Named is set to true if this result will be embedded in a Hash result from
|
101
|
-
# naming something using <code>.as(...)</code>. It changes the folding
|
102
|
-
# semantics of repetition.
|
103
|
-
#
|
104
|
-
def flatten(value, named=false) # :nodoc:
|
105
|
-
# Passes through everything that isn't an array of things
|
106
|
-
return value unless value.instance_of? Array
|
107
|
-
|
108
|
-
# Extracts the s-expression tag
|
109
|
-
tag, *tail = value
|
110
|
-
|
111
|
-
# Merges arrays:
|
112
|
-
result = tail.
|
113
|
-
map { |e| flatten(e) } # first flatten each element
|
114
|
-
|
115
|
-
case tag
|
116
|
-
when :sequence
|
117
|
-
return flatten_sequence(result)
|
118
|
-
when :maybe
|
119
|
-
return named ? result.first : result.first || ''
|
120
|
-
when :repetition
|
121
|
-
return flatten_repetition(result, named)
|
122
|
-
end
|
123
|
-
|
124
|
-
fail "BUG: Unknown tag #{tag.inspect}."
|
125
|
-
end
|
126
|
-
|
127
|
-
# Lisp style fold left where the first element builds the basis for
|
128
|
-
# an inject.
|
129
|
-
#
|
130
|
-
def foldl(list, &block)
|
131
|
-
return '' if list.empty?
|
132
|
-
list[1..-1].inject(list.first, &block)
|
133
|
-
end
|
134
|
-
|
135
|
-
# Flatten results from a sequence of parslets.
|
136
|
-
#
|
137
|
-
def flatten_sequence(list) # :nodoc:
|
138
|
-
foldl(list.compact) { |r, e| # and then merge flat elements
|
139
|
-
merge_fold(r, e)
|
140
|
-
}
|
141
|
-
end
|
142
|
-
def merge_fold(l, r) # :nodoc:
|
143
|
-
# equal pairs: merge. ----------------------------------------------------
|
144
|
-
if l.class == r.class
|
145
|
-
if l.is_a?(Hash)
|
146
|
-
warn_about_duplicate_keys(l, r)
|
147
|
-
return l.merge(r)
|
148
|
-
else
|
149
|
-
return l + r
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
# unequal pairs: hoist to same level. ------------------------------------
|
154
|
-
|
155
|
-
# Maybe classes are not equal, but both are stringlike?
|
156
|
-
if l.respond_to?(:to_str) && r.respond_to?(:to_str)
|
157
|
-
# if we're merging a String with a Slice, the slice wins.
|
158
|
-
return r if r.respond_to? :to_slice
|
159
|
-
return l if l.respond_to? :to_slice
|
160
|
-
|
161
|
-
fail "NOTREACHED: What other stringlike classes are there?"
|
162
|
-
end
|
163
|
-
|
164
|
-
# special case: If one of them is a string/slice, the other is more important
|
165
|
-
return l if r.respond_to? :to_str
|
166
|
-
return r if l.respond_to? :to_str
|
167
|
-
|
168
|
-
# otherwise just create an array for one of them to live in
|
169
|
-
return l + [r] if r.class == Hash
|
170
|
-
return [l] + r if l.class == Hash
|
171
|
-
|
172
|
-
fail "Unhandled case when foldr'ing sequence."
|
173
|
-
end
|
174
|
-
|
175
|
-
# Flatten results from a repetition of a single parslet. named indicates
|
176
|
-
# whether the user has named the result or not. If the user has named
|
177
|
-
# the results, we want to leave an empty list alone - otherwise it is
|
178
|
-
# turned into an empty string.
|
179
|
-
#
|
180
|
-
def flatten_repetition(list, named) # :nodoc:
|
181
|
-
if list.any? { |e| e.instance_of?(Hash) }
|
182
|
-
# If keyed subtrees are in the array, we'll want to discard all
|
183
|
-
# strings inbetween. To keep them, name them.
|
184
|
-
return list.select { |e| e.instance_of?(Hash) }
|
185
|
-
end
|
186
|
-
|
187
|
-
if list.any? { |e| e.instance_of?(Array) }
|
188
|
-
# If any arrays are nested in this array, flatten all arrays to this
|
189
|
-
# level.
|
190
|
-
return list.
|
191
|
-
select { |e| e.instance_of?(Array) }.
|
192
|
-
flatten(1)
|
193
|
-
end
|
194
|
-
|
195
|
-
# Consistent handling of empty lists, when we act on a named result
|
196
|
-
return [] if named && list.empty?
|
197
|
-
|
198
|
-
# If there are only strings, concatenate them and return that.
|
199
|
-
foldl(list) { |s,e| s+e }
|
200
|
-
end
|
201
114
|
|
202
115
|
# Debug printing - in Treetop syntax.
|
203
116
|
#
|
@@ -245,51 +158,7 @@ private
|
|
245
158
|
# Produces an instance of Fail and returns it.
|
246
159
|
#
|
247
160
|
def error(source, str, pos=nil)
|
248
|
-
@last_cause =
|
161
|
+
@last_cause = source.error(str, pos)
|
249
162
|
Fail.new(@last_cause)
|
250
163
|
end
|
251
|
-
|
252
|
-
# Signals to the outside that the parse has failed. Use this in conjunction
|
253
|
-
# with #format_cause for nice error messages.
|
254
|
-
#
|
255
|
-
def parse_failed(cause, exception_klass=Parslet::ParseFailed)
|
256
|
-
@last_cause = cause
|
257
|
-
raise exception_klass,
|
258
|
-
@last_cause.to_s
|
259
|
-
end
|
260
|
-
|
261
|
-
# An internal class that allows delaying the construction of error messages
|
262
|
-
# (as strings) until we really need to print them.
|
263
|
-
#
|
264
|
-
class Cause < Struct.new(:message, :source, :pos)
|
265
|
-
def to_s
|
266
|
-
line, column = source.line_and_column(pos)
|
267
|
-
# Allow message to be a list of objects. Join them here, since we now
|
268
|
-
# really need it.
|
269
|
-
Array(message).map { |o|
|
270
|
-
o.respond_to?(:to_slice) ?
|
271
|
-
o.str.inspect :
|
272
|
-
o.to_s }.join + " at line #{line} char #{column}."
|
273
|
-
end
|
274
|
-
end
|
275
|
-
|
276
|
-
# Appends 'at line ... char ...' to the string given. Use +pos+ to override
|
277
|
-
# the position of the +source+. This method returns an object that can
|
278
|
-
# be turned into a string using #to_s.
|
279
|
-
#
|
280
|
-
def format_cause(source, str, pos=nil)
|
281
|
-
real_pos = (pos||source.pos)
|
282
|
-
Cause.new(str, source, real_pos)
|
283
|
-
end
|
284
|
-
|
285
|
-
# That annoying warning 'Duplicate subtrees while merging result' comes
|
286
|
-
# from here. You should add more '.as(...)' names to your intermediary tree.
|
287
|
-
#
|
288
|
-
def warn_about_duplicate_keys(h1, h2)
|
289
|
-
d = h1.keys & h2.keys
|
290
|
-
unless d.empty?
|
291
|
-
warn "Duplicate subtrees while merging result of \n #{self.inspect}\nonly the values"+
|
292
|
-
" of the latter will be kept. (keys: #{d.inspect})"
|
293
|
-
end
|
294
|
-
end
|
295
164
|
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
|
2
|
+
module Parslet::Atoms
|
3
|
+
# A series of helper functions that have the common topic of flattening
|
4
|
+
# result values into the intermediary tree that consists of Ruby Hashes and
|
5
|
+
# Arrays.
|
6
|
+
#
|
7
|
+
# This module has one main function, #flatten, that takes an annotated
|
8
|
+
# structure as input and returns the reduced form that users expect from
|
9
|
+
# Atom#parse.
|
10
|
+
#
|
11
|
+
# NOTE: Since all of these functions are just that, functions without
|
12
|
+
# side effects, they are in a module and not in a class. Its hard to draw
|
13
|
+
# the line sometimes, but this is beyond.
|
14
|
+
#
|
15
|
+
module CanFlatten
|
16
|
+
# Takes a mixed value coming out of a parslet and converts it to a return
|
17
|
+
# value for the user by dropping things and merging hashes.
|
18
|
+
#
|
19
|
+
# Named is set to true if this result will be embedded in a Hash result from
|
20
|
+
# naming something using <code>.as(...)</code>. It changes the folding
|
21
|
+
# semantics of repetition.
|
22
|
+
#
|
23
|
+
def flatten(value, named=false) # :nodoc:
|
24
|
+
# Passes through everything that isn't an array of things
|
25
|
+
return value unless value.instance_of? Array
|
26
|
+
|
27
|
+
# Extracts the s-expression tag
|
28
|
+
tag, *tail = value
|
29
|
+
|
30
|
+
# Merges arrays:
|
31
|
+
result = tail.
|
32
|
+
map { |e| flatten(e) } # first flatten each element
|
33
|
+
|
34
|
+
case tag
|
35
|
+
when :sequence
|
36
|
+
return flatten_sequence(result)
|
37
|
+
when :maybe
|
38
|
+
return named ? result.first : result.first || ''
|
39
|
+
when :repetition
|
40
|
+
return flatten_repetition(result, named)
|
41
|
+
end
|
42
|
+
|
43
|
+
fail "BUG: Unknown tag #{tag.inspect}."
|
44
|
+
end
|
45
|
+
|
46
|
+
# Lisp style fold left where the first element builds the basis for
|
47
|
+
# an inject.
|
48
|
+
#
|
49
|
+
def foldl(list, &block)
|
50
|
+
return '' if list.empty?
|
51
|
+
list[1..-1].inject(list.first, &block)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Flatten results from a sequence of parslets.
|
55
|
+
#
|
56
|
+
def flatten_sequence(list) # :nodoc:
|
57
|
+
foldl(list.compact) { |r, e| # and then merge flat elements
|
58
|
+
merge_fold(r, e)
|
59
|
+
}
|
60
|
+
end
|
61
|
+
def merge_fold(l, r) # :nodoc:
|
62
|
+
# equal pairs: merge. ----------------------------------------------------
|
63
|
+
if l.class == r.class
|
64
|
+
if l.is_a?(Hash)
|
65
|
+
warn_about_duplicate_keys(l, r)
|
66
|
+
return l.merge(r)
|
67
|
+
else
|
68
|
+
return l + r
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# unequal pairs: hoist to same level. ------------------------------------
|
73
|
+
|
74
|
+
# Maybe classes are not equal, but both are stringlike?
|
75
|
+
if l.respond_to?(:to_str) && r.respond_to?(:to_str)
|
76
|
+
# if we're merging a String with a Slice, the slice wins.
|
77
|
+
return r if r.respond_to? :to_slice
|
78
|
+
return l if l.respond_to? :to_slice
|
79
|
+
|
80
|
+
fail "NOTREACHED: What other stringlike classes are there?"
|
81
|
+
end
|
82
|
+
|
83
|
+
# special case: If one of them is a string/slice, the other is more important
|
84
|
+
return l if r.respond_to? :to_str
|
85
|
+
return r if l.respond_to? :to_str
|
86
|
+
|
87
|
+
# otherwise just create an array for one of them to live in
|
88
|
+
return l + [r] if r.class == Hash
|
89
|
+
return [l] + r if l.class == Hash
|
90
|
+
|
91
|
+
fail "Unhandled case when foldr'ing sequence."
|
92
|
+
end
|
93
|
+
|
94
|
+
# Flatten results from a repetition of a single parslet. named indicates
|
95
|
+
# whether the user has named the result or not. If the user has named
|
96
|
+
# the results, we want to leave an empty list alone - otherwise it is
|
97
|
+
# turned into an empty string.
|
98
|
+
#
|
99
|
+
def flatten_repetition(list, named) # :nodoc:
|
100
|
+
if list.any? { |e| e.instance_of?(Hash) }
|
101
|
+
# If keyed subtrees are in the array, we'll want to discard all
|
102
|
+
# strings inbetween. To keep them, name them.
|
103
|
+
return list.select { |e| e.instance_of?(Hash) }
|
104
|
+
end
|
105
|
+
|
106
|
+
if list.any? { |e| e.instance_of?(Array) }
|
107
|
+
# If any arrays are nested in this array, flatten all arrays to this
|
108
|
+
# level.
|
109
|
+
return list.
|
110
|
+
select { |e| e.instance_of?(Array) }.
|
111
|
+
flatten(1)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Consistent handling of empty lists, when we act on a named result
|
115
|
+
return [] if named && list.empty?
|
116
|
+
|
117
|
+
# If there are only strings, concatenate them and return that.
|
118
|
+
foldl(list) { |s,e| s+e }
|
119
|
+
end
|
120
|
+
|
121
|
+
# That annoying warning 'Duplicate subtrees while merging result' comes
|
122
|
+
# from here. You should add more '.as(...)' names to your intermediary tree.
|
123
|
+
#
|
124
|
+
def warn_about_duplicate_keys(h1, h2)
|
125
|
+
d = h1.keys & h2.keys
|
126
|
+
unless d.empty?
|
127
|
+
warn "Duplicate subtrees while merging result of \n #{self.inspect}\nonly the values"+
|
128
|
+
" of the latter will be kept. (keys: #{d.inspect})"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|