parslet 1.3.0 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/HISTORY.txt +38 -1
- data/README +33 -21
- data/example/deepest_errors.rb +131 -0
- data/example/email_parser.rb +2 -6
- data/example/ignore.rb +2 -2
- data/example/json.rb +0 -3
- data/example/modularity.rb +47 -0
- data/example/nested_errors.rb +132 -0
- data/example/output/deepest_errors.out +54 -0
- data/example/output/modularity.out +0 -0
- data/example/output/nested_errors.out +54 -0
- data/lib/parslet.rb +65 -51
- data/lib/parslet/atoms.rb +1 -1
- data/lib/parslet/atoms/alternative.rb +11 -12
- data/lib/parslet/atoms/base.rb +57 -99
- data/lib/parslet/atoms/can_flatten.rb +9 -4
- data/lib/parslet/atoms/context.rb +26 -4
- data/lib/parslet/atoms/entity.rb +5 -10
- data/lib/parslet/atoms/lookahead.rb +11 -7
- data/lib/parslet/atoms/named.rb +8 -12
- data/lib/parslet/atoms/re.rb +10 -9
- data/lib/parslet/atoms/repetition.rb +23 -24
- data/lib/parslet/atoms/sequence.rb +10 -16
- data/lib/parslet/atoms/str.rb +11 -13
- data/lib/parslet/cause.rb +45 -13
- data/lib/parslet/convenience.rb +6 -6
- data/lib/parslet/error_reporter.rb +7 -0
- data/lib/parslet/error_reporter/deepest.rb +95 -0
- data/lib/parslet/error_reporter/tree.rb +57 -0
- data/lib/parslet/export.rb +4 -4
- data/lib/parslet/expression.rb +0 -2
- data/lib/parslet/expression/treetop.rb +2 -2
- data/lib/parslet/parser.rb +2 -6
- data/lib/parslet/pattern.rb +15 -4
- data/lib/parslet/pattern/binding.rb +3 -3
- data/lib/parslet/rig/rspec.rb +2 -2
- data/lib/parslet/slice.rb +0 -6
- data/lib/parslet/source.rb +40 -59
- data/lib/parslet/source/line_cache.rb +2 -2
- data/lib/parslet/transform.rb +13 -7
- data/lib/parslet/transform/context.rb +1 -1
- metadata +69 -26
- data/example/ignore_whitespace.rb +0 -66
- data/lib/parslet/bytecode.rb +0 -6
- data/lib/parslet/bytecode/compiler.rb +0 -138
- data/lib/parslet/bytecode/instructions.rb +0 -358
- data/lib/parslet/bytecode/vm.rb +0 -209
- data/lib/parslet/error_tree.rb +0 -50
@@ -0,0 +1,54 @@
|
|
1
|
+
--------------------------------------------------------------------------------
|
2
|
+
. 10 . 20
|
3
|
+
01
|
4
|
+
02 define f()
|
5
|
+
03 @res.name
|
6
|
+
04 end
|
7
|
+
05
|
8
|
+
Failed to match sequence (LINE_SEPARATOR? BLOCK LINE_SEPARATOR?) at line 2 char 5.
|
9
|
+
`- Expected one of [DEFINE_BLOCK, BEGIN_BLOCK] at line 2 char 5.
|
10
|
+
|- Failed to match sequence (define:'define' SPACE name:IDENTIFIER '()' BODY 'end') at line 2 char 15.
|
11
|
+
| `- Failed to match sequence (body:((LINE_SEPARATOR (BLOCK / EXPRESSION)){1, }) LINE_SEPARATOR) at line 3 char 11.
|
12
|
+
| `- Expected at least 1 of SPACE? (COMMENT? NEWLINE / ';') SPACE? at line 3 char 11.
|
13
|
+
| `- Failed to match sequence (SPACE? (COMMENT? NEWLINE / ';') SPACE?) at line 3 char 11.
|
14
|
+
| `- Expected one of [COMMENT? NEWLINE, ';'] at line 3 char 11.
|
15
|
+
| |- Failed to match sequence (COMMENT? NEWLINE) at line 3 char 11.
|
16
|
+
| | `- Expected "()", but got "\n " at line 3 char 16.
|
17
|
+
| `- Expected "()", but got "\n " at line 3 char 16.
|
18
|
+
`- Failed to match sequence (pre:((type:'concurrent' SPACE)?) begin:'begin' BODY 'end') at line 2 char 5.
|
19
|
+
`- Expected "()", but got "\n " at line 3 char 16.
|
20
|
+
--------------------------------------------------------------------------------
|
21
|
+
. 10 . 20
|
22
|
+
01
|
23
|
+
02 define f()
|
24
|
+
03 begin
|
25
|
+
04 @res.name
|
26
|
+
05 end
|
27
|
+
06 end
|
28
|
+
07
|
29
|
+
Failed to match sequence (LINE_SEPARATOR? BLOCK LINE_SEPARATOR?) at line 2 char 5.
|
30
|
+
`- Expected one of [DEFINE_BLOCK, BEGIN_BLOCK] at line 2 char 5.
|
31
|
+
|- Failed to match sequence (define:'define' SPACE name:IDENTIFIER '()' BODY 'end') at line 2 char 15.
|
32
|
+
| `- Failed to match sequence (body:((LINE_SEPARATOR (BLOCK / EXPRESSION)){1, }) LINE_SEPARATOR) at line 2 char 15.
|
33
|
+
| `- Expected at least 1 of LINE_SEPARATOR (BLOCK / EXPRESSION) at line 2 char 15.
|
34
|
+
| `- Failed to match sequence (LINE_SEPARATOR (BLOCK / EXPRESSION)) at line 3 char 7.
|
35
|
+
| `- Expected one of [BLOCK, EXPRESSION] at line 3 char 7.
|
36
|
+
| |- Expected one of [DEFINE_BLOCK, BEGIN_BLOCK] at line 3 char 7.
|
37
|
+
| | |- Failed to match sequence (define:'define' SPACE name:IDENTIFIER '()' BODY 'end') at line 3 char 7.
|
38
|
+
| | | `- Expected "define", but got "begin\n" at line 3 char 7.
|
39
|
+
| | `- Failed to match sequence (pre:((type:'concurrent' SPACE)?) begin:'begin' BODY 'end') at line 3 char 12.
|
40
|
+
| | `- Failed to match sequence (body:((LINE_SEPARATOR (BLOCK / EXPRESSION)){1, }) LINE_SEPARATOR) at line 4 char 13.
|
41
|
+
| | `- Expected at least 1 of SPACE? (COMMENT? NEWLINE / ';') SPACE? at line 4 char 13.
|
42
|
+
| | `- Failed to match sequence (SPACE? (COMMENT? NEWLINE / ';') SPACE?) at line 4 char 13.
|
43
|
+
| | `- Expected one of [COMMENT? NEWLINE, ';'] at line 4 char 13.
|
44
|
+
| | |- Failed to match sequence (COMMENT? NEWLINE) at line 4 char 13.
|
45
|
+
| | | `- Expected "()", but got "\n " at line 4 char 18.
|
46
|
+
| | `- Expected "()", but got "\n " at line 4 char 18.
|
47
|
+
| `- Failed to match sequence (RES_ACTIONS res_field:((':' name:IDENTIFIER)?)) at line 3 char 7.
|
48
|
+
| `- Failed to match sequence (resources:REFERENCE res_actions:(res_action:RES_ACTION_OR_LINK{0, })) at line 3 char 7.
|
49
|
+
| `- Failed to match sequence ('@'{1, 2} IDENTIFIER) at line 3 char 7.
|
50
|
+
| `- Expected at least 1 of '@' at line 3 char 7.
|
51
|
+
| `- Expected "()", but got "\n " at line 4 char 18.
|
52
|
+
`- Failed to match sequence (pre:((type:'concurrent' SPACE)?) begin:'begin' BODY 'end') at line 2 char 5.
|
53
|
+
`- Expected "()", but got "\n " at line 4 char 18.
|
54
|
+
--------------------------------------------------------------------------------
|
File without changes
|
@@ -0,0 +1,54 @@
|
|
1
|
+
--------------------------------------------------------------------------------
|
2
|
+
. 10 . 20
|
3
|
+
01
|
4
|
+
02 define f()
|
5
|
+
03 @res.name
|
6
|
+
04 end
|
7
|
+
05
|
8
|
+
Failed to match sequence (LINE_SEPARATOR? BLOCK LINE_SEPARATOR?) at line 2 char 5.
|
9
|
+
`- Expected one of [DEFINE_BLOCK, BEGIN_BLOCK] at line 2 char 5.
|
10
|
+
|- Failed to match sequence (define:'define' SPACE name:IDENTIFIER '()' BODY 'end') at line 2 char 15.
|
11
|
+
| `- Failed to match sequence (body:((LINE_SEPARATOR (BLOCK / EXPRESSION)){1, }) LINE_SEPARATOR) at line 3 char 11.
|
12
|
+
| `- Expected at least 1 of SPACE? (COMMENT? NEWLINE / ';') SPACE? at line 3 char 11.
|
13
|
+
| `- Failed to match sequence (SPACE? (COMMENT? NEWLINE / ';') SPACE?) at line 3 char 11.
|
14
|
+
| `- Expected one of [COMMENT? NEWLINE, ';'] at line 3 char 11.
|
15
|
+
| |- Failed to match sequence (COMMENT? NEWLINE) at line 3 char 11.
|
16
|
+
| | `- Failed to match [\\r\\n] at line 3 char 11.
|
17
|
+
| `- Expected ";", but got "." at line 3 char 11.
|
18
|
+
`- Failed to match sequence (pre:((type:'concurrent' SPACE)?) begin:'begin' BODY 'end') at line 2 char 5.
|
19
|
+
`- Expected "begin", but got "defin" at line 2 char 5.
|
20
|
+
--------------------------------------------------------------------------------
|
21
|
+
. 10 . 20
|
22
|
+
01
|
23
|
+
02 define f()
|
24
|
+
03 begin
|
25
|
+
04 @res.name
|
26
|
+
05 end
|
27
|
+
06 end
|
28
|
+
07
|
29
|
+
Failed to match sequence (LINE_SEPARATOR? BLOCK LINE_SEPARATOR?) at line 2 char 5.
|
30
|
+
`- Expected one of [DEFINE_BLOCK, BEGIN_BLOCK] at line 2 char 5.
|
31
|
+
|- Failed to match sequence (define:'define' SPACE name:IDENTIFIER '()' BODY 'end') at line 2 char 15.
|
32
|
+
| `- Failed to match sequence (body:((LINE_SEPARATOR (BLOCK / EXPRESSION)){1, }) LINE_SEPARATOR) at line 2 char 15.
|
33
|
+
| `- Expected at least 1 of LINE_SEPARATOR (BLOCK / EXPRESSION) at line 2 char 15.
|
34
|
+
| `- Failed to match sequence (LINE_SEPARATOR (BLOCK / EXPRESSION)) at line 3 char 7.
|
35
|
+
| `- Expected one of [BLOCK, EXPRESSION] at line 3 char 7.
|
36
|
+
| |- Expected one of [DEFINE_BLOCK, BEGIN_BLOCK] at line 3 char 7.
|
37
|
+
| | |- Failed to match sequence (define:'define' SPACE name:IDENTIFIER '()' BODY 'end') at line 3 char 7.
|
38
|
+
| | | `- Expected "define", but got "begin\n" at line 3 char 7.
|
39
|
+
| | `- Failed to match sequence (pre:((type:'concurrent' SPACE)?) begin:'begin' BODY 'end') at line 3 char 12.
|
40
|
+
| | `- Failed to match sequence (body:((LINE_SEPARATOR (BLOCK / EXPRESSION)){1, }) LINE_SEPARATOR) at line 4 char 13.
|
41
|
+
| | `- Expected at least 1 of SPACE? (COMMENT? NEWLINE / ';') SPACE? at line 4 char 13.
|
42
|
+
| | `- Failed to match sequence (SPACE? (COMMENT? NEWLINE / ';') SPACE?) at line 4 char 13.
|
43
|
+
| | `- Expected one of [COMMENT? NEWLINE, ';'] at line 4 char 13.
|
44
|
+
| | |- Failed to match sequence (COMMENT? NEWLINE) at line 4 char 13.
|
45
|
+
| | | `- Failed to match [\\r\\n] at line 4 char 13.
|
46
|
+
| | `- Expected ";", but got "." at line 4 char 13.
|
47
|
+
| `- Failed to match sequence (RES_ACTIONS res_field:((':' name:IDENTIFIER)?)) at line 3 char 7.
|
48
|
+
| `- Failed to match sequence (resources:REFERENCE res_actions:(res_action:RES_ACTION_OR_LINK{0, })) at line 3 char 7.
|
49
|
+
| `- Failed to match sequence ('@'{1, 2} IDENTIFIER) at line 3 char 7.
|
50
|
+
| `- Expected at least 1 of '@' at line 3 char 7.
|
51
|
+
| `- Expected "@", but got "b" at line 3 char 7.
|
52
|
+
`- Failed to match sequence (pre:((type:'concurrent' SPACE)?) begin:'begin' BODY 'end') at line 2 char 5.
|
53
|
+
`- Expected "begin", but got "defin" at line 2 char 5.
|
54
|
+
--------------------------------------------------------------------------------
|
data/lib/parslet.rb
CHANGED
@@ -34,41 +34,40 @@
|
|
34
34
|
#
|
35
35
|
# == Further reading
|
36
36
|
#
|
37
|
-
# All parslet atoms are subclasses of Parslet::Atoms::Base. You might want to
|
38
|
-
# look at all of those: Parslet::Atoms::Re, Parslet::Atoms::Str,
|
39
|
-
# Parslet::Atoms::Repetition, Parslet::Atoms::Sequence,
|
40
|
-
# Parslet::Atoms::Alternative.
|
37
|
+
# All parslet atoms are subclasses of {Parslet::Atoms::Base}. You might want to
|
38
|
+
# look at all of those: {Parslet::Atoms::Re}, {Parslet::Atoms::Str},
|
39
|
+
# {Parslet::Atoms::Repetition}, {Parslet::Atoms::Sequence},
|
40
|
+
# {Parslet::Atoms::Alternative}.
|
41
41
|
#
|
42
42
|
# == When things go wrong
|
43
43
|
#
|
44
|
-
# A parse that fails will raise Parslet::ParseFailed.
|
45
|
-
# of what went wrong
|
46
|
-
#
|
44
|
+
# A parse that fails will raise {Parslet::ParseFailed}. This exception contains
|
45
|
+
# all the details of what went wrong, including a detailed error trace that
|
46
|
+
# can be printed out as an ascii tree. ({Parslet::Cause})
|
47
47
|
#
|
48
48
|
module Parslet
|
49
|
-
|
49
|
+
# Extends classes that include Parslet with the module
|
50
|
+
# {Parslet::ClassMethods}.
|
51
|
+
#
|
52
|
+
def self.included(base)
|
50
53
|
base.extend(ClassMethods)
|
51
54
|
end
|
52
55
|
|
53
|
-
# Raised when the parse failed to match
|
54
|
-
#
|
55
|
-
#
|
56
|
-
#
|
57
|
-
#
|
58
|
-
#
|
59
|
-
# Example:
|
60
|
-
#
|
56
|
+
# Raised when the parse failed to match. It contains the message that should
|
57
|
+
# be presented to the user. More details can be extracted from the
|
58
|
+
# exceptions #cause member: It contains an instance of {Parslet::Cause} that
|
59
|
+
# stores all the details of your failed parse in a tree structure.
|
60
|
+
#
|
61
61
|
# begin
|
62
62
|
# parslet.parse(str)
|
63
63
|
# rescue Parslet::ParseFailed => failure
|
64
|
-
# puts
|
64
|
+
# puts failure.cause.ascii_tree
|
65
65
|
# end
|
66
66
|
#
|
67
|
-
# Alternatively, you can just require 'parslet/convenience' and call
|
68
|
-
#
|
69
|
-
#
|
67
|
+
# Alternatively, you can just require 'parslet/convenience' and call the
|
68
|
+
# method #parse_with_debug instead of #parse. This method will never raise
|
69
|
+
# and print error trees to stdout.
|
70
70
|
#
|
71
|
-
# Example:
|
72
71
|
# require 'parslet/convenience'
|
73
72
|
# parslet.parse_with_debug(str)
|
74
73
|
#
|
@@ -77,7 +76,11 @@ module Parslet
|
|
77
76
|
super(message)
|
78
77
|
@cause = cause
|
79
78
|
end
|
80
|
-
|
79
|
+
|
80
|
+
# Why the parse failed.
|
81
|
+
#
|
82
|
+
# @return [Parslet::Cause]
|
83
|
+
attr_reader :cause
|
81
84
|
end
|
82
85
|
|
83
86
|
# Raised when the parse operation didn't consume all of its input. In this
|
@@ -85,6 +88,14 @@ module Parslet
|
|
85
88
|
# parser worked just fine, but didn't account for the characters at the tail
|
86
89
|
# of the input?
|
87
90
|
#
|
91
|
+
# str('foo').parse('foobar')
|
92
|
+
# # raises Parslet::UnconsumedInput:
|
93
|
+
# # Don't know what to do with "bar" at line 1 char 4.
|
94
|
+
#
|
95
|
+
# Note that you can have parslet ignore this error:
|
96
|
+
#
|
97
|
+
# str('foo').parse('foobar', prefix: true) # => "foo"@0
|
98
|
+
#
|
88
99
|
class UnconsumedInput < ParseFailed
|
89
100
|
end
|
90
101
|
|
@@ -92,14 +103,12 @@ module Parslet
|
|
92
103
|
# Define an entity for the parser. This generates a method of the same
|
93
104
|
# name that can be used as part of other patterns. Those methods can be
|
94
105
|
# freely mixed in your parser class with real ruby methods.
|
95
|
-
#
|
96
|
-
# Example:
|
97
|
-
#
|
106
|
+
#
|
98
107
|
# class MyParser
|
99
108
|
# include Parslet
|
100
109
|
#
|
101
|
-
# rule
|
102
|
-
# rule
|
110
|
+
# rule(:bar) { str('bar') }
|
111
|
+
# rule(:twobar) do
|
103
112
|
# bar >> bar
|
104
113
|
# end
|
105
114
|
#
|
@@ -123,7 +132,8 @@ module Parslet
|
|
123
132
|
|
124
133
|
# Allows for delayed construction of #match. See also Parslet.match.
|
125
134
|
#
|
126
|
-
|
135
|
+
# @api private
|
136
|
+
class DelayedMatchConstructor
|
127
137
|
def [](str)
|
128
138
|
Atoms::Re.new("[" + str + "]")
|
129
139
|
end
|
@@ -132,8 +142,6 @@ module Parslet
|
|
132
142
|
# Returns an atom matching a character class. All regular expressions can be
|
133
143
|
# used, as long as they match only a single character at a time.
|
134
144
|
#
|
135
|
-
# Example:
|
136
|
-
#
|
137
145
|
# match('[ab]') # will match either 'a' or 'b'
|
138
146
|
# match('[\n\s]') # will match newlines and spaces
|
139
147
|
#
|
@@ -142,6 +150,10 @@ module Parslet
|
|
142
150
|
# match['a-z'] # synonymous to match('[a-z]')
|
143
151
|
# match['\n'] # synonymous to match('[\n]')
|
144
152
|
#
|
153
|
+
# @overload match(str)
|
154
|
+
# @param str [String] character class to match (regexp syntax)
|
155
|
+
# @return [Parslet::Atoms::Re] a parslet atom
|
156
|
+
#
|
145
157
|
def match(str=nil)
|
146
158
|
return DelayedMatchConstructor.new unless str
|
147
159
|
|
@@ -149,12 +161,13 @@ module Parslet
|
|
149
161
|
end
|
150
162
|
module_function :match
|
151
163
|
|
152
|
-
# Returns an atom matching the +str+ given
|
153
|
-
#
|
154
|
-
# Example:
|
164
|
+
# Returns an atom matching the +str+ given:
|
155
165
|
#
|
156
166
|
# str('class') # will match 'class'
|
157
167
|
#
|
168
|
+
# @param str [String] string to match verbatim
|
169
|
+
# @return [Parslet::Atoms::Str] a parslet atom
|
170
|
+
#
|
158
171
|
def str(str)
|
159
172
|
Atoms::Str.new(str)
|
160
173
|
end
|
@@ -163,10 +176,10 @@ module Parslet
|
|
163
176
|
# Returns an atom matching any character. It acts like the '.' (dot)
|
164
177
|
# character in regular expressions.
|
165
178
|
#
|
166
|
-
# Example:
|
167
|
-
#
|
168
179
|
# any.parse('a') # => 'a'
|
169
180
|
#
|
181
|
+
# @return [Parslet::Atoms::Re] a parslet atom
|
182
|
+
#
|
170
183
|
def any
|
171
184
|
Atoms::Re.new('.')
|
172
185
|
end
|
@@ -175,39 +188,43 @@ module Parslet
|
|
175
188
|
# A special kind of atom that allows embedding whole treetop expressions
|
176
189
|
# into parslet construction.
|
177
190
|
#
|
178
|
-
#
|
191
|
+
# # the same as str('a') >> str('b').maybe
|
192
|
+
# exp(%Q("a" "b"?))
|
179
193
|
#
|
180
|
-
#
|
194
|
+
# @param str [String] a treetop expression
|
195
|
+
# @return [Parslet::Atoms::Base] the corresponding parslet parser
|
181
196
|
#
|
182
|
-
def exp(str)
|
197
|
+
def exp(str)
|
183
198
|
Parslet::Expression.new(str).to_parslet
|
184
199
|
end
|
185
200
|
module_function :exp
|
186
201
|
|
187
|
-
# Returns a placeholder for a tree transformation that will only match a
|
188
|
-
# sequence of elements. The +symbol+ you specify will be the key for the
|
202
|
+
# Returns a placeholder for a tree transformation that will only match a
|
203
|
+
# sequence of elements. The +symbol+ you specify will be the key for the
|
189
204
|
# matched sequence in the returned dictionary.
|
190
205
|
#
|
191
|
-
# Example:
|
192
|
-
#
|
193
206
|
# # This would match a body element that contains several declarations.
|
194
207
|
# { :body => sequence(:declarations) }
|
195
208
|
#
|
196
|
-
# The above example would match
|
209
|
+
# The above example would match <code>:body => ['a', 'b']</code>, but not
|
210
|
+
# <code>:body => 'a'</code>.
|
211
|
+
#
|
212
|
+
# see {Parslet::Transform}
|
197
213
|
#
|
198
214
|
def sequence(symbol)
|
199
215
|
Pattern::SequenceBind.new(symbol)
|
200
216
|
end
|
201
217
|
module_function :sequence
|
202
218
|
|
203
|
-
# Returns a placeholder for a tree transformation that will only match
|
204
|
-
# simple elements. This matches everything that
|
205
|
-
#
|
206
|
-
# Example:
|
219
|
+
# Returns a placeholder for a tree transformation that will only match
|
220
|
+
# simple elements. This matches everything that <code>#sequence</code>
|
221
|
+
# doesn't match.
|
207
222
|
#
|
208
223
|
# # Matches a single header.
|
209
224
|
# { :header => simple(:header) }
|
210
225
|
#
|
226
|
+
# see {Parslet::Transform}
|
227
|
+
#
|
211
228
|
def simple(symbol)
|
212
229
|
Pattern::SimpleBind.new(symbol)
|
213
230
|
end
|
@@ -216,8 +233,6 @@ module Parslet
|
|
216
233
|
# Returns a placeholder for tree transformation patterns that will match
|
217
234
|
# any kind of subtree.
|
218
235
|
#
|
219
|
-
# Example:
|
220
|
-
#
|
221
236
|
# { :expression => subtree(:exp) }
|
222
237
|
#
|
223
238
|
def subtree(symbol)
|
@@ -231,10 +246,9 @@ end
|
|
231
246
|
require 'parslet/slice'
|
232
247
|
require 'parslet/cause'
|
233
248
|
require 'parslet/source'
|
234
|
-
require 'parslet/error_tree'
|
235
249
|
require 'parslet/atoms'
|
236
250
|
require 'parslet/pattern'
|
237
251
|
require 'parslet/pattern/binding'
|
238
252
|
require 'parslet/transform'
|
239
253
|
require 'parslet/parser'
|
240
|
-
require 'parslet/
|
254
|
+
require 'parslet/error_reporter'
|
data/lib/parslet/atoms.rb
CHANGED
@@ -5,7 +5,7 @@ module Parslet::Atoms
|
|
5
5
|
# The precedence module controls parenthesis during the #inspect printing
|
6
6
|
# of parslets. It is not relevant to other aspects of the parsing.
|
7
7
|
#
|
8
|
-
module Precedence
|
8
|
+
module Precedence
|
9
9
|
prec = 0
|
10
10
|
BASE = (prec+=1) # everything else
|
11
11
|
LOOKAHEAD = (prec+=1) # &SOMETHING
|
@@ -26,26 +26,25 @@ class Parslet::Atoms::Alternative < Parslet::Atoms::Base
|
|
26
26
|
# Don't construct a hanging tree of Alternative parslets, instead store them
|
27
27
|
# all here. This reduces the number of objects created.
|
28
28
|
#+++
|
29
|
-
def |(parslet)
|
29
|
+
def |(parslet)
|
30
30
|
self.class.new(*@alternatives + [parslet])
|
31
31
|
end
|
32
32
|
|
33
|
-
def try(source, context)
|
34
|
-
alternatives.
|
35
|
-
value = a.apply(source, context)
|
36
|
-
return
|
33
|
+
def try(source, context)
|
34
|
+
errors = alternatives.map { |a|
|
35
|
+
success, value = result = a.apply(source, context)
|
36
|
+
return result if success
|
37
|
+
|
38
|
+
# Aggregate all errors
|
39
|
+
value
|
37
40
|
}
|
41
|
+
|
38
42
|
# If we reach this point, all alternatives have failed.
|
39
|
-
|
43
|
+
context.err(self, source, @error_msg, errors)
|
40
44
|
end
|
41
45
|
|
42
46
|
precedence ALTERNATE
|
43
|
-
def to_s_inner(prec)
|
47
|
+
def to_s_inner(prec)
|
44
48
|
alternatives.map { |a| a.to_s(prec) }.join(' / ')
|
45
49
|
end
|
46
|
-
|
47
|
-
def error_tree # :nodoc:
|
48
|
-
Parslet::ErrorTree.new(self, *alternatives.
|
49
|
-
map { |child| child.error_tree })
|
50
|
-
end
|
51
50
|
end
|
data/lib/parslet/atoms/base.rb
CHANGED
@@ -8,99 +8,84 @@ class Parslet::Atoms::Base
|
|
8
8
|
include Parslet::Atoms::DSL
|
9
9
|
include Parslet::Atoms::CanFlatten
|
10
10
|
|
11
|
-
# Internally, all parsing functions return either an instance of Fail
|
12
|
-
# or an instance of Success.
|
13
|
-
#
|
14
|
-
class Fail < Struct.new(:message)
|
15
|
-
def error?; true end
|
16
|
-
end
|
17
|
-
|
18
|
-
# Internally, all parsing functions return either an instance of Fail
|
19
|
-
# or an instance of Success.
|
20
|
-
#
|
21
|
-
class Success < Struct.new(:result)
|
22
|
-
def error?; false end
|
23
|
-
end
|
24
|
-
|
25
11
|
# Given a string or an IO object, this will attempt a parse of its contents
|
26
12
|
# and return a result. If the parse fails, a Parslet::ParseFailed exception
|
27
13
|
# will be thrown.
|
28
14
|
#
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
def
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
def parse_traditional(io)
|
46
|
-
source = Parslet::Source.new(io)
|
47
|
-
context = Parslet::Atoms::Context.new
|
48
|
-
|
49
|
-
result = nil
|
50
|
-
value = apply(source, context)
|
15
|
+
# @param io [String, Source] input for the parse process
|
16
|
+
# @option options [Parslet::ErrorReporter] :reporter error reporter to use,
|
17
|
+
# defaults to Parslet::ErrorReporter::Tree
|
18
|
+
# @option options [Boolean] :prefix Should a prefix match be accepted?
|
19
|
+
# (default: false)
|
20
|
+
# @return [Hash, Array, Parslet::Slice] PORO (Plain old Ruby object) result
|
21
|
+
# tree
|
22
|
+
#
|
23
|
+
def parse(io, options={})
|
24
|
+
source = io.respond_to?(:line_and_column) ?
|
25
|
+
io :
|
26
|
+
Parslet::Source.new(io)
|
27
|
+
|
28
|
+
# Try to cheat. Assuming that we'll be able to parse the input, don't
|
29
|
+
# run error reporting code.
|
30
|
+
success, value = setup_and_apply(source, nil)
|
51
31
|
|
52
32
|
# If we didn't succeed the parse, raise an exception for the user.
|
53
33
|
# Stack trace will be off, but the error tree should explain the reason
|
54
34
|
# it failed.
|
55
|
-
|
56
|
-
|
57
|
-
|
35
|
+
unless success
|
36
|
+
# Cheating has not paid off. Now pay the cost: Rerun the parse,
|
37
|
+
# gathering error information in the process.
|
38
|
+
reporter = options[:reporter] || Parslet::ErrorReporter::Tree.new
|
39
|
+
success, value = setup_and_apply(source, reporter)
|
40
|
+
|
41
|
+
fail "Assertion failed: success was true when parsing with reporter" \
|
42
|
+
if success
|
43
|
+
|
44
|
+
# Value is a Parslet::Cause, which can be turned into an exception:
|
45
|
+
value.raise
|
46
|
+
|
47
|
+
fail "NEVER REACHED"
|
58
48
|
end
|
59
49
|
|
60
|
-
# assert:
|
50
|
+
# assert: success is true
|
61
51
|
|
62
52
|
# If we haven't consumed the input, then the pattern doesn't match. Try
|
63
53
|
# to provide a good error message (even asking down below)
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
raise Parslet::UnconsumedInput,
|
71
|
-
"Unconsumed input, maybe because of this: #{cause}"
|
72
|
-
else
|
73
|
-
old_pos = source.pos
|
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)
|
78
|
-
end
|
54
|
+
if !options[:prefix] && !source.eof?
|
55
|
+
old_pos = source.pos
|
56
|
+
Parslet::Cause.format(
|
57
|
+
source, old_pos,
|
58
|
+
"Don't know what to do with #{source.consume(10).to_s.inspect}").
|
59
|
+
raise(Parslet::UnconsumedInput)
|
79
60
|
end
|
80
61
|
|
81
|
-
return flatten(value
|
62
|
+
return flatten(value)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Creates a context for parsing and applies the current atom to the input.
|
66
|
+
# Returns the parse result.
|
67
|
+
#
|
68
|
+
# @return [<Boolean, Object>] Result of the parse. If the first member is
|
69
|
+
# true, the parse has succeeded.
|
70
|
+
def setup_and_apply(source, error_reporter)
|
71
|
+
context = Parslet::Atoms::Context.new(error_reporter)
|
72
|
+
apply(source, context)
|
82
73
|
end
|
83
74
|
|
84
75
|
#---
|
85
76
|
# Calls the #try method of this parslet. In case of a parse error, apply
|
86
77
|
# leaves the source in the state it was before the attempt.
|
87
78
|
#+++
|
88
|
-
def apply(source, context)
|
79
|
+
def apply(source, context)
|
89
80
|
old_pos = source.pos
|
90
81
|
|
91
|
-
result = context.
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
# This has just succeeded, so last_cause must be empty
|
96
|
-
unless result.error?
|
97
|
-
@last_cause = nil
|
98
|
-
return result
|
99
|
-
end
|
82
|
+
success, value = result = context.try_with_cache(self, source)
|
83
|
+
|
84
|
+
return result if success
|
100
85
|
|
101
86
|
# We only reach this point if the parse has failed. Rewind the input.
|
102
87
|
source.pos = old_pos
|
103
|
-
return result
|
88
|
+
return result
|
104
89
|
end
|
105
90
|
|
106
91
|
# Override this in your Atoms::Base subclasses to implement parsing
|
@@ -111,54 +96,27 @@ class Parslet::Atoms::Base
|
|
111
96
|
"Atoms::Base doesn't have behaviour, please implement #try(source, context)."
|
112
97
|
end
|
113
98
|
|
114
|
-
|
115
99
|
# Debug printing - in Treetop syntax.
|
116
100
|
#
|
117
|
-
def self.precedence(prec)
|
101
|
+
def self.precedence(prec)
|
118
102
|
define_method(:precedence) { prec }
|
119
103
|
end
|
120
104
|
precedence BASE
|
121
|
-
def to_s(outer_prec=OUTER)
|
105
|
+
def to_s(outer_prec=OUTER)
|
122
106
|
if outer_prec < precedence
|
123
107
|
"("+to_s_inner(precedence)+")"
|
124
108
|
else
|
125
109
|
to_s_inner(precedence)
|
126
110
|
end
|
127
111
|
end
|
128
|
-
def inspect
|
112
|
+
def inspect
|
129
113
|
to_s(OUTER)
|
130
114
|
end
|
131
|
-
|
132
|
-
# Cause should return the current best approximation of this parslet
|
133
|
-
# of what went wrong with the parse. Not relevant if the parse succeeds,
|
134
|
-
# but needed for clever error reports.
|
135
|
-
#
|
136
|
-
def cause # :nodoc:
|
137
|
-
@last_cause && @last_cause.to_s || nil
|
138
|
-
end
|
139
|
-
def cause? # :nodoc:
|
140
|
-
!!@last_cause
|
141
|
-
end
|
142
|
-
|
143
|
-
# Error tree returns what went wrong here plus what went wrong inside
|
144
|
-
# subexpressions as a tree. The error stored for this node will be equal
|
145
|
-
# to #cause.
|
146
|
-
#
|
147
|
-
def error_tree
|
148
|
-
Parslet::ErrorTree.new(self)
|
149
|
-
end
|
150
115
|
private
|
151
116
|
|
152
117
|
# Produces an instance of Success and returns it.
|
153
118
|
#
|
154
|
-
def
|
155
|
-
|
156
|
-
end
|
157
|
-
|
158
|
-
# Produces an instance of Fail and returns it.
|
159
|
-
#
|
160
|
-
def error(source, str, pos=nil)
|
161
|
-
@last_cause = source.error(str, pos)
|
162
|
-
Fail.new(@last_cause)
|
119
|
+
def succ(result)
|
120
|
+
[true, result]
|
163
121
|
end
|
164
122
|
end
|