parslet 1.6.2 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 48adca502f4d3fa08eeaa93ee6c2349866ec2028
4
- data.tar.gz: d74a18ffbf0fa05cc3b9c6ba42d112ce7fcb218e
3
+ metadata.gz: d212114155ea2fce120ce17157dcd10a9bb81ebf
4
+ data.tar.gz: 07ad0296a6bea037a87594ffc565429b39e6258b
5
5
  SHA512:
6
- metadata.gz: 0271bfb7536639b396e5203b52fa3d148f09534d08bd67d54c58efb4be4454be35761203b02270719bf32f4ab54313b63b3bb4321e4b993094248f36f7b7f88d
7
- data.tar.gz: 56a0e3845d0e0dd7a553c6d901c7ed37863c89d5140bd460285dea2e81d63aeb917190f80a7f29f7a914ac2cb17f301874db2fbb7d4f2bcfc1e6715645541b64
6
+ metadata.gz: 704d66fde31caf3741fa202d2ac56eaf410ec3ed0cf9050bc834af92a115d847a14e72d61cd97b44c60ba8bc1e50bf792cfadff3257e2b789a7bc9550d0b944a
7
+ data.tar.gz: 6ae7e679bfe78d0de4f6c66f4d096f2985b23ece311d5705deb5fe9098ce516ba4d6a7ecb2df7f34d942c7c2370dac3b075d0a6c168e887ac1bdbbdc020a21b2
@@ -3,9 +3,11 @@
3
3
  - prsnt? and absnt? are now finally banned into oblivion. Wasting vocals for
4
4
  the win.
5
5
 
6
- = 1.7 / ???
6
+ = 1.7 / 12Mar2015
7
7
 
8
8
  ! Small speed gains from improvements on the hot spots.
9
+ + Contextual error reporter, a flavor of error reporting that emphasizes
10
+ context.
9
11
 
10
12
  = 1.6 / 1May2014, 13Okt14
11
13
 
data/README CHANGED
@@ -11,7 +11,7 @@ the atoms of your language first; _parslet_ takes pride in making this
11
11
  possible.
12
12
 
13
13
  Eager to try this out? Please see the associated web site:
14
- http://kschiess.github.com/parslet
14
+ http://kschiess.github.io/parslet
15
15
 
16
16
  SYNOPSIS
17
17
 
@@ -99,7 +99,7 @@ module Parslet
99
99
  # root :twobar
100
100
  # end
101
101
  #
102
- def rule(name, &definition)
102
+ def rule(name, opts={}, &definition)
103
103
  define_method(name) do
104
104
  @rules ||= {} # <name, rule> memoization
105
105
  return @rules[name] if @rules.has_key?(name)
@@ -109,7 +109,7 @@ module Parslet
109
109
  self.instance_eval(&definition)
110
110
  }
111
111
 
112
- @rules[name] = Atoms::Entity.new(name, &definition_closure)
112
+ @rules[name] = Atoms::Entity.new(name, opts[:label], &definition_closure)
113
113
  end
114
114
  end
115
115
  end
@@ -7,7 +7,10 @@ class Parslet::Atoms::Base
7
7
  include Parslet::Atoms::Precedence
8
8
  include Parslet::Atoms::DSL
9
9
  include Parslet::Atoms::CanFlatten
10
-
10
+
11
+ # Parslet label as provided in grammar
12
+ attr_accessor :label
13
+
11
14
  # Given a string or an IO object, this will attempt a parse of its contents
12
15
  # and return a result. If the parse fails, a Parslet::ParseFailed exception
13
16
  # will be thrown.
@@ -83,6 +86,8 @@ class Parslet::Atoms::Base
83
86
  success, value = result = context.try_with_cache(self, source, consume_all)
84
87
 
85
88
  if success
89
+ # Notify context
90
+ context.succ(source)
86
91
  # If a consume_all parse was made and doesn't result in the consumption
87
92
  # of all the input, that is considered an error.
88
93
  if consume_all && source.chars_left>0
@@ -132,10 +137,11 @@ class Parslet::Atoms::Base
132
137
  end
133
138
  precedence BASE
134
139
  def to_s(outer_prec=OUTER)
140
+ str = @label || to_s_inner(precedence)
135
141
  if outer_prec < precedence
136
- "("+to_s_inner(precedence)+")"
142
+ "(#{str})"
137
143
  else
138
- to_s_inner(precedence)
144
+ str
139
145
  end
140
146
  end
141
147
  def inspect
@@ -62,7 +62,15 @@ module Parslet::Atoms
62
62
  return [false, @reporter.err(*args)] if @reporter
63
63
  return [false, nil]
64
64
  end
65
-
65
+
66
+ # Report a successful parse.
67
+ # @see ErrorReporter::Contextual
68
+ #
69
+ def succ(*args)
70
+ return [true, @reporter.succ(*args)] if @reporter
71
+ return [true, nil]
72
+ end
73
+
66
74
  # Returns the current captures made on the input (see
67
75
  # Parslet::Atoms::Base#capture). Use as follows:
68
76
  #
@@ -5,15 +5,16 @@
5
5
  # * Be able to print things by their name, not by their sometimes
6
6
  # complicated content.
7
7
  #
8
- # You don't normally use this directly, instead you should generated it by
8
+ # You don't normally use this directly, instead you should generate it by
9
9
  # using the structuring method Parslet.rule.
10
10
  #
11
11
  class Parslet::Atoms::Entity < Parslet::Atoms::Base
12
12
  attr_reader :name, :block
13
- def initialize(name, &block)
13
+ def initialize(name, label=nil, &block)
14
14
  super()
15
15
 
16
16
  @name = name
17
+ @label = label
17
18
  @block = block
18
19
  end
19
20
 
@@ -22,9 +23,11 @@ class Parslet::Atoms::Entity < Parslet::Atoms::Base
22
23
  end
23
24
 
24
25
  def parslet
25
- @parslet ||= @block.call.tap { |p|
26
- raise_not_implemented unless p
27
- }
26
+ return @parslet unless @parslet.nil?
27
+ @parslet = @block.call
28
+ raise_not_implemented if @parslet.nil?
29
+ @parslet.label = @label
30
+ @parslet
28
31
  end
29
32
 
30
33
  def to_s_inner(prec)
@@ -31,7 +31,7 @@ class Parslet::Atoms::Sequence < Parslet::Atoms::Base
31
31
  unless success
32
32
  return context.err(self, source, @error_msgs[:failed], [value])
33
33
  end
34
-
34
+
35
35
  result[idx+1] = value
36
36
  end
37
37
 
@@ -44,15 +44,22 @@ module Parslet
44
44
  def self.format(source, pos, str, children=[])
45
45
  self.new(str, source, pos, children)
46
46
  end
47
-
47
+
48
+ # Update error message to include context provided by label
49
+ # Update all child causes too (the same context applies to all causes)
50
+ def set_label(l)
51
+ @context = " when parsing #{l}"
52
+ children.each { |c| c.set_label(l) }
53
+ end
54
+
48
55
  def to_s
49
56
  line, column = source.line_and_column(pos)
50
57
  # Allow message to be a list of objects. Join them here, since we now
51
- # really need it.
52
- Array(message).map { |o|
53
- o.respond_to?(:to_slice) ?
54
- o.str.inspect :
55
- o.to_s }.join + " at line #{line} char #{column}."
58
+ # really need it.
59
+ Array(message).map { |o|
60
+ o.respond_to?(:to_slice) ?
61
+ o.str.inspect :
62
+ o.to_s }.join + " at line #{line} char #{column}#{@context}."
56
63
  end
57
64
 
58
65
  # Signals to the outside that the parse has failed. Use this in
@@ -4,4 +4,5 @@ module Parslet::ErrorReporter
4
4
  end
5
5
 
6
6
  require 'parslet/error_reporter/tree'
7
- require 'parslet/error_reporter/deepest'
7
+ require 'parslet/error_reporter/deepest'
8
+ require 'parslet/error_reporter/contextual'
@@ -0,0 +1,120 @@
1
+ module Parslet
2
+ module ErrorReporter
3
+
4
+ # A reporter that tries to improve on the deepest error reporter by
5
+ # using heuristics to find the most relevant error and provide more
6
+ # context.
7
+ # The heuristic chooses the deepest error when parsing a sequence for which
8
+ # no alternative parsed successfully.
9
+ #
10
+ # Given the following parser:
11
+ #
12
+ # root(:call)
13
+ #
14
+ # rule(:call, label: 'call') {
15
+ # identifier >> str('.') >> method
16
+ # }
17
+ #
18
+ # rule(:method, label: 'method call') {
19
+ # identifier >> str('(') >> arguments.maybe >> str(')')
20
+ # }
21
+ #
22
+ # rule(:identifier, label: 'identifier') {
23
+ # match['[:alnum:]'].repeat(1)
24
+ # }
25
+ #
26
+ # rule(:arguments, label: 'method call arguments') {
27
+ # argument >> str(',') >> arguments | argument
28
+ # }
29
+ #
30
+ # rule(:argument) {
31
+ # call | identifier
32
+ # }
33
+ #
34
+ # and the following source:
35
+ #
36
+ # foo.bar(a,goo.baz(),c,)
37
+ #
38
+ # The contextual reporter returns the following causes:
39
+ #
40
+ # 0: Failed to match sequence (identifier '.' method call) at line 1 char 5
41
+ # when parsing method call arguments.
42
+ # 1: Failed to match sequence (identifier '(' method call arguments? ')') at
43
+ # line 1 char 22 when parsing method call arguments.
44
+ # 2: Failed to match [[:alnum:]] at line 1 char 23 when parsing method call
45
+ # arguments.
46
+ #
47
+ # (where 2 is a child cause of 1 and 1 a child cause of 0)
48
+ #
49
+ # The last piece used by the reporter is the (newly introduced) ability
50
+ # to attach a label to rules that describe a sequence in the grammar. The
51
+ # labels are used in two places:
52
+ # - In the "to_s" of Atom::Base so that any error message uses labels to
53
+ # refer to atoms
54
+ # - In the cause error messages to give information about which expression
55
+ # failed to parse
56
+ #
57
+ class Contextual < Deepest
58
+
59
+ def initialize
60
+ @last_reset_pos = 0
61
+ reset
62
+ end
63
+
64
+ # A sequence expression successfully parsed, reset all errors reported
65
+ # for previous expressions in the sequence (an alternative matched)
66
+ # Only reset errors if the position of the source that matched is higher
67
+ # than the position of the source that was last successful (so we keep
68
+ # errors that are the "deepest" but for which no alternative succeeded)
69
+ #
70
+ def succ(source)
71
+ source_pos = source.pos.bytepos
72
+ return if source_pos < @last_reset_pos
73
+ @last_reset_pos = source_pos
74
+ reset
75
+ end
76
+
77
+ # Reset deepest error and its position and sequence index
78
+ #
79
+ def reset
80
+ @deepest_cause = nil
81
+ @label_pos = -1
82
+ end
83
+
84
+ # Produces an error cause that combines the message at the current level
85
+ # with the errors that happened at a level below (children).
86
+ # Compute and set label used by Cause to produce error message.
87
+ #
88
+ # @param atom [Parslet::Atoms::Base] parslet that failed
89
+ # @param source [Source] Source that we're using for this parse. (line
90
+ # number information...)
91
+ # @param message [String, Array] Error message at this level.
92
+ # @param children [Array] A list of errors from a deeper level (or nil).
93
+ # @return [Cause] An error tree combining children with message.
94
+ #
95
+ def err(atom, source, message, children=nil)
96
+ cause = super(atom, source, message, children)
97
+ if (label = atom.respond_to?(:label) && atom.label)
98
+ update_label(label, source.pos.bytepos)
99
+ cause.set_label(@label)
100
+ end
101
+ cause
102
+ end
103
+
104
+ # Update error message label if given label is more relevant.
105
+ # A label is more relevant if the position of the matched source is
106
+ # bigger.
107
+ #
108
+ # @param label [String] label to apply if more relevant
109
+ # @param bytepos [Integer] position in source code of matched source
110
+ #
111
+ def update_label(label, bytepos)
112
+ if bytepos >= @label_pos
113
+ @label_pos = bytepos
114
+ @label = label
115
+ end
116
+ end
117
+
118
+ end
119
+ end
120
+ end
@@ -50,6 +50,11 @@ module Parslet
50
50
  end
51
51
 
52
52
  # Returns the cause that is currently deepest. Mainly for specs.
53
+
54
+ # Notification that an expression successfully parsed
55
+ # not used, see ErrorReporter::Contextual
56
+ def succ(source)
57
+ end
53
58
  #
54
59
  attr_reader :deepest_cause
55
60
 
@@ -52,6 +52,12 @@ module Parslet
52
52
  position = pos
53
53
  Cause.format(source, position, message, children)
54
54
  end
55
+
56
+ # Notification that an expression successfully parsed
57
+ # not used, see ErrorReporter::Contextual
58
+ def succ(source)
59
+ end
60
+
55
61
  end
56
62
  end
57
- end
63
+ end
@@ -57,6 +57,8 @@ class Parslet::Slice
57
57
  def size
58
58
  str.size
59
59
  end
60
+
61
+ alias length size
60
62
 
61
63
  # Concatenate two slices; it is assumed that the second slice begins
62
64
  # where the first one ends. The offset of the resulting slice is the same
@@ -105,4 +107,4 @@ class Parslet::Slice
105
107
 
106
108
  str.inspect << "@#{offset}"
107
109
  end
108
- end
110
+ end
@@ -124,7 +124,8 @@ class Parslet::Transform
124
124
  #
125
125
  def rule(expression, &block)
126
126
  @__transform_rules ||= []
127
- @__transform_rules << [Parslet::Pattern.new(expression), block]
127
+ # Prepend new rules so they have higher precedence than older rules
128
+ @__transform_rules.unshift([Parslet::Pattern.new(expression), block])
128
129
  end
129
130
 
130
131
  # Allows accessing the class' rules
@@ -132,6 +133,11 @@ class Parslet::Transform
132
133
  def rules
133
134
  @__transform_rules || []
134
135
  end
136
+
137
+ def inherited(subclass)
138
+ super
139
+ subclass.instance_variable_set(:@__transform_rules, rules.dup)
140
+ end
135
141
  end
136
142
 
137
143
  def initialize(&block)
@@ -149,16 +155,31 @@ class Parslet::Transform
149
155
  # * a *transformation block*
150
156
  #
151
157
  def rule(expression, &block)
152
- @rules << [
153
- Parslet::Pattern.new(expression),
154
- block
155
- ]
158
+ # Prepend new rules so they have higher precedence than older rules
159
+ @rules.unshift([Parslet::Pattern.new(expression), block])
156
160
  end
157
161
 
158
162
  # Applies the transformation to a tree that is generated by Parslet::Parser
159
163
  # or a simple parslet. Transformation will proceed down the tree, replacing
160
164
  # parts/all of it with new objects. The resulting object will be returned.
161
165
  #
166
+ # Using the context parameter, you can inject bindings for the transformation.
167
+ # This can be used to allow access to the outside world from transform blocks,
168
+ # like so:
169
+ #
170
+ # document = # some class that you act on
171
+ # transform.apply(tree, document: document)
172
+ #
173
+ # The above will make document available to all your action blocks:
174
+ #
175
+ # # Variant A
176
+ # rule(...) { document.foo(bar) }
177
+ # # Variant B
178
+ # rule(...) { |d| d[:document].foo(d[:bar]) }
179
+ #
180
+ # @param obj PORO ast to transform
181
+ # @param context start context to inject into the bindings.
182
+ #
162
183
  def apply(obj, context=nil)
163
184
  transform_elt(
164
185
  case obj
metadata CHANGED
@@ -1,33 +1,33 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: parslet
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.2
4
+ version: 1.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kaspar Schiess
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-10-13 00:00:00.000000000 Z
11
+ date: 2015-03-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: blankslate
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - '>='
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '2.0'
20
- - - <=
20
+ - - "<="
21
21
  - !ruby/object:Gem::Version
22
22
  version: '4.0'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
- - - '>='
27
+ - - ">="
28
28
  - !ruby/object:Gem::Version
29
29
  version: '2.0'
30
- - - <=
30
+ - - "<="
31
31
  - !ruby/object:Gem::Version
32
32
  version: '4.0'
33
33
  description:
@@ -39,50 +39,8 @@ extra_rdoc_files:
39
39
  files:
40
40
  - HISTORY.txt
41
41
  - LICENSE
42
- - Rakefile
43
42
  - README
44
- - lib/parslet/accelerator/application.rb
45
- - lib/parslet/accelerator/engine.rb
46
- - lib/parslet/accelerator.rb
47
- - lib/parslet/atoms/alternative.rb
48
- - lib/parslet/atoms/base.rb
49
- - lib/parslet/atoms/can_flatten.rb
50
- - lib/parslet/atoms/capture.rb
51
- - lib/parslet/atoms/context.rb
52
- - lib/parslet/atoms/dsl.rb
53
- - lib/parslet/atoms/dynamic.rb
54
- - lib/parslet/atoms/entity.rb
55
- - lib/parslet/atoms/infix.rb
56
- - lib/parslet/atoms/lookahead.rb
57
- - lib/parslet/atoms/named.rb
58
- - lib/parslet/atoms/re.rb
59
- - lib/parslet/atoms/repetition.rb
60
- - lib/parslet/atoms/scope.rb
61
- - lib/parslet/atoms/sequence.rb
62
- - lib/parslet/atoms/str.rb
63
- - lib/parslet/atoms/visitor.rb
64
- - lib/parslet/atoms.rb
65
- - lib/parslet/cause.rb
66
- - lib/parslet/context.rb
67
- - lib/parslet/convenience.rb
68
- - lib/parslet/error_reporter/deepest.rb
69
- - lib/parslet/error_reporter/tree.rb
70
- - lib/parslet/error_reporter.rb
71
- - lib/parslet/export.rb
72
- - lib/parslet/expression/treetop.rb
73
- - lib/parslet/expression.rb
74
- - lib/parslet/graphviz.rb
75
- - lib/parslet/parser.rb
76
- - lib/parslet/pattern/binding.rb
77
- - lib/parslet/pattern.rb
78
- - lib/parslet/position.rb
79
- - lib/parslet/rig/rspec.rb
80
- - lib/parslet/scope.rb
81
- - lib/parslet/slice.rb
82
- - lib/parslet/source/line_cache.rb
83
- - lib/parslet/source.rb
84
- - lib/parslet/transform.rb
85
- - lib/parslet.rb
43
+ - Rakefile
86
44
  - example/big.erb
87
45
  - example/boolean_algebra.rb
88
46
  - example/calc.rb
@@ -140,30 +98,74 @@ files:
140
98
  - example/simple_xml.rb
141
99
  - example/string_parser.rb
142
100
  - example/test.lit
143
- homepage: http://kschiess.github.com/parslet
101
+ - lib/parslet.rb
102
+ - lib/parslet/accelerator.rb
103
+ - lib/parslet/accelerator/application.rb
104
+ - lib/parslet/accelerator/engine.rb
105
+ - lib/parslet/atoms.rb
106
+ - lib/parslet/atoms/alternative.rb
107
+ - lib/parslet/atoms/base.rb
108
+ - lib/parslet/atoms/can_flatten.rb
109
+ - lib/parslet/atoms/capture.rb
110
+ - lib/parslet/atoms/context.rb
111
+ - lib/parslet/atoms/dsl.rb
112
+ - lib/parslet/atoms/dynamic.rb
113
+ - lib/parslet/atoms/entity.rb
114
+ - lib/parslet/atoms/infix.rb
115
+ - lib/parslet/atoms/lookahead.rb
116
+ - lib/parslet/atoms/named.rb
117
+ - lib/parslet/atoms/re.rb
118
+ - lib/parslet/atoms/repetition.rb
119
+ - lib/parslet/atoms/scope.rb
120
+ - lib/parslet/atoms/sequence.rb
121
+ - lib/parslet/atoms/str.rb
122
+ - lib/parslet/atoms/visitor.rb
123
+ - lib/parslet/cause.rb
124
+ - lib/parslet/context.rb
125
+ - lib/parslet/convenience.rb
126
+ - lib/parslet/error_reporter.rb
127
+ - lib/parslet/error_reporter/contextual.rb
128
+ - lib/parslet/error_reporter/deepest.rb
129
+ - lib/parslet/error_reporter/tree.rb
130
+ - lib/parslet/export.rb
131
+ - lib/parslet/expression.rb
132
+ - lib/parslet/expression/treetop.rb
133
+ - lib/parslet/graphviz.rb
134
+ - lib/parslet/parser.rb
135
+ - lib/parslet/pattern.rb
136
+ - lib/parslet/pattern/binding.rb
137
+ - lib/parslet/position.rb
138
+ - lib/parslet/rig/rspec.rb
139
+ - lib/parslet/scope.rb
140
+ - lib/parslet/slice.rb
141
+ - lib/parslet/source.rb
142
+ - lib/parslet/source/line_cache.rb
143
+ - lib/parslet/transform.rb
144
+ homepage: http://kschiess.github.io/parslet
144
145
  licenses:
145
146
  - MIT
146
147
  metadata: {}
147
148
  post_install_message:
148
149
  rdoc_options:
149
- - --main
150
+ - "--main"
150
151
  - README
151
152
  require_paths:
152
153
  - lib
153
154
  required_ruby_version: !ruby/object:Gem::Requirement
154
155
  requirements:
155
- - - '>='
156
+ - - ">="
156
157
  - !ruby/object:Gem::Version
157
158
  version: '0'
158
159
  required_rubygems_version: !ruby/object:Gem::Requirement
159
160
  requirements:
160
- - - '>='
161
+ - - ">="
161
162
  - !ruby/object:Gem::Version
162
163
  version: '0'
163
164
  requirements: []
164
165
  rubyforge_project:
165
- rubygems_version: 2.0.14
166
+ rubygems_version: 2.2.2
166
167
  signing_key:
167
168
  specification_version: 4
168
169
  summary: Parser construction library with great error reporting in Ruby.
169
170
  test_files: []
171
+ has_rdoc: