parslet 1.3.0 → 1.4.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 +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
|