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.
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