parslet 1.3.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/HISTORY.txt +38 -1
  2. data/README +33 -21
  3. data/example/deepest_errors.rb +131 -0
  4. data/example/email_parser.rb +2 -6
  5. data/example/ignore.rb +2 -2
  6. data/example/json.rb +0 -3
  7. data/example/modularity.rb +47 -0
  8. data/example/nested_errors.rb +132 -0
  9. data/example/output/deepest_errors.out +54 -0
  10. data/example/output/modularity.out +0 -0
  11. data/example/output/nested_errors.out +54 -0
  12. data/lib/parslet.rb +65 -51
  13. data/lib/parslet/atoms.rb +1 -1
  14. data/lib/parslet/atoms/alternative.rb +11 -12
  15. data/lib/parslet/atoms/base.rb +57 -99
  16. data/lib/parslet/atoms/can_flatten.rb +9 -4
  17. data/lib/parslet/atoms/context.rb +26 -4
  18. data/lib/parslet/atoms/entity.rb +5 -10
  19. data/lib/parslet/atoms/lookahead.rb +11 -7
  20. data/lib/parslet/atoms/named.rb +8 -12
  21. data/lib/parslet/atoms/re.rb +10 -9
  22. data/lib/parslet/atoms/repetition.rb +23 -24
  23. data/lib/parslet/atoms/sequence.rb +10 -16
  24. data/lib/parslet/atoms/str.rb +11 -13
  25. data/lib/parslet/cause.rb +45 -13
  26. data/lib/parslet/convenience.rb +6 -6
  27. data/lib/parslet/error_reporter.rb +7 -0
  28. data/lib/parslet/error_reporter/deepest.rb +95 -0
  29. data/lib/parslet/error_reporter/tree.rb +57 -0
  30. data/lib/parslet/export.rb +4 -4
  31. data/lib/parslet/expression.rb +0 -2
  32. data/lib/parslet/expression/treetop.rb +2 -2
  33. data/lib/parslet/parser.rb +2 -6
  34. data/lib/parslet/pattern.rb +15 -4
  35. data/lib/parslet/pattern/binding.rb +3 -3
  36. data/lib/parslet/rig/rspec.rb +2 -2
  37. data/lib/parslet/slice.rb +0 -6
  38. data/lib/parslet/source.rb +40 -59
  39. data/lib/parslet/source/line_cache.rb +2 -2
  40. data/lib/parslet/transform.rb +13 -7
  41. data/lib/parslet/transform/context.rb +1 -1
  42. metadata +69 -26
  43. data/example/ignore_whitespace.rb +0 -66
  44. data/lib/parslet/bytecode.rb +0 -6
  45. data/lib/parslet/bytecode/compiler.rb +0 -138
  46. data/lib/parslet/bytecode/instructions.rb +0 -358
  47. data/lib/parslet/bytecode/vm.rb +0 -209
  48. 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. A detailed explanation
45
- # of what went wrong can be obtained from the parslet involved or the root of
46
- # the parser instance.
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
- def self.included(base) # :nodoc:
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 or to consume all its input. It
54
- # contains the message that should be presented to the user. If you want to
55
- # display more error explanation, you can print the #error_tree that is
56
- # stored in the parslet. This is a graphical representation of what went
57
- # wrong.
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 parslet.error_tree
64
+ # puts failure.cause.ascii_tree
65
65
  # end
66
66
  #
67
- # Alternatively, you can just require 'parslet/convenience' and call
68
- # the method #parse_with_debug instead of #parse. This method will never
69
- # raise and print error trees to stdout.
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
- attr_reader :cause
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 :bar { str('bar') }
102
- # rule :twobar do
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
- class DelayedMatchConstructor # :nodoc:
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
- # Example:
191
+ # # the same as str('a') >> str('b').maybe
192
+ # exp(%Q("a" "b"?))
179
193
  #
180
- # exp(%Q("a" "b"?)) # => returns the same as str('a') >> str('b').maybe
194
+ # @param str [String] a treetop expression
195
+ # @return [Parslet::Atoms::Base] the corresponding parslet parser
181
196
  #
182
- def exp(str) # :nodoc:
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 :body => ['a', 'b'], but not :body => 'a'.
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 #sequence doesn't match.
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/bytecode'
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 # :nodoc:
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) # :nodoc:
29
+ def |(parslet)
30
30
  self.class.new(*@alternatives + [parslet])
31
31
  end
32
32
 
33
- def try(source, context) # :nodoc:
34
- alternatives.each { |a|
35
- value = a.apply(source, context)
36
- return value unless value.error?
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
- error(source, @error_msg)
43
+ context.err(self, source, @error_msg, errors)
40
44
  end
41
45
 
42
46
  precedence ALTERNATE
43
- def to_s_inner(prec) # :nodoc:
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
@@ -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
- 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)
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
- if value.error?
56
- @last_cause = value.message
57
- @last_cause.raise
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: value is a success answer
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
- unless source.eof?
65
- # Do we know why we stopped matching input? If yes, that's a good
66
- # error to fail with. Otherwise just report that we cannot consume the
67
- # input.
68
- if cause
69
- # NOTE We don't overwrite last_cause here.
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.result)
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) # :nodoc:
79
+ def apply(source, context)
89
80
  old_pos = source.pos
90
81
 
91
- result = context.cache(self, source) {
92
- try(source, context)
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 # is instance of Fail
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) # :nodoc:
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) # :nodoc:
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 # :nodoc:
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 success(result)
155
- Success.new(result)
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