plurimath-parslet 3.0.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.
- checksums.yaml +7 -0
- data/HISTORY.txt +284 -0
- data/LICENSE +23 -0
- data/README.adoc +454 -0
- data/Rakefile +71 -0
- data/lib/parslet/accelerator/application.rb +62 -0
- data/lib/parslet/accelerator/engine.rb +112 -0
- data/lib/parslet/accelerator.rb +162 -0
- data/lib/parslet/atoms/alternative.rb +53 -0
- data/lib/parslet/atoms/base.rb +157 -0
- data/lib/parslet/atoms/can_flatten.rb +137 -0
- data/lib/parslet/atoms/capture.rb +38 -0
- data/lib/parslet/atoms/context.rb +103 -0
- data/lib/parslet/atoms/dsl.rb +112 -0
- data/lib/parslet/atoms/dynamic.rb +32 -0
- data/lib/parslet/atoms/entity.rb +45 -0
- data/lib/parslet/atoms/ignored.rb +26 -0
- data/lib/parslet/atoms/infix.rb +115 -0
- data/lib/parslet/atoms/lookahead.rb +52 -0
- data/lib/parslet/atoms/named.rb +32 -0
- data/lib/parslet/atoms/re.rb +41 -0
- data/lib/parslet/atoms/repetition.rb +87 -0
- data/lib/parslet/atoms/scope.rb +26 -0
- data/lib/parslet/atoms/sequence.rb +48 -0
- data/lib/parslet/atoms/str.rb +42 -0
- data/lib/parslet/atoms/visitor.rb +89 -0
- data/lib/parslet/atoms.rb +34 -0
- data/lib/parslet/cause.rb +101 -0
- data/lib/parslet/context.rb +21 -0
- data/lib/parslet/convenience.rb +33 -0
- data/lib/parslet/error_reporter/contextual.rb +120 -0
- data/lib/parslet/error_reporter/deepest.rb +100 -0
- data/lib/parslet/error_reporter/tree.rb +63 -0
- data/lib/parslet/error_reporter.rb +8 -0
- data/lib/parslet/export.rb +163 -0
- data/lib/parslet/expression/treetop.rb +92 -0
- data/lib/parslet/expression.rb +51 -0
- data/lib/parslet/graphviz.rb +97 -0
- data/lib/parslet/parser.rb +68 -0
- data/lib/parslet/pattern/binding.rb +49 -0
- data/lib/parslet/pattern.rb +113 -0
- data/lib/parslet/position.rb +21 -0
- data/lib/parslet/rig/rspec.rb +52 -0
- data/lib/parslet/scope.rb +42 -0
- data/lib/parslet/slice.rb +105 -0
- data/lib/parslet/source/line_cache.rb +99 -0
- data/lib/parslet/source.rb +96 -0
- data/lib/parslet/transform.rb +265 -0
- data/lib/parslet/version.rb +5 -0
- data/lib/parslet.rb +314 -0
- data/plurimath-parslet.gemspec +42 -0
- data/spec/acceptance/infix_parser_spec.rb +145 -0
- data/spec/acceptance/mixing_parsers_spec.rb +74 -0
- data/spec/acceptance/regression_spec.rb +329 -0
- data/spec/acceptance/repetition_and_maybe_spec.rb +44 -0
- data/spec/acceptance/unconsumed_input_spec.rb +21 -0
- data/spec/examples/boolean_algebra_spec.rb +257 -0
- data/spec/examples/calc_spec.rb +278 -0
- data/spec/examples/capture_spec.rb +137 -0
- data/spec/examples/comments_spec.rb +186 -0
- data/spec/examples/deepest_errors_spec.rb +420 -0
- data/spec/examples/documentation_spec.rb +205 -0
- data/spec/examples/email_parser_spec.rb +275 -0
- data/spec/examples/empty_spec.rb +37 -0
- data/spec/examples/erb_spec.rb +482 -0
- data/spec/examples/ip_address_spec.rb +153 -0
- data/spec/examples/json_spec.rb +413 -0
- data/spec/examples/local_spec.rb +302 -0
- data/spec/examples/mathn_spec.rb +151 -0
- data/spec/examples/minilisp_spec.rb +492 -0
- data/spec/examples/modularity_spec.rb +340 -0
- data/spec/examples/nested_errors_spec.rb +322 -0
- data/spec/examples/optimized_erb_spec.rb +299 -0
- data/spec/examples/parens_spec.rb +239 -0
- data/spec/examples/prec_calc_spec.rb +525 -0
- data/spec/examples/readme_spec.rb +228 -0
- data/spec/examples/scopes_spec.rb +187 -0
- data/spec/examples/seasons_spec.rb +196 -0
- data/spec/examples/sentence_spec.rb +119 -0
- data/spec/examples/simple_xml_spec.rb +250 -0
- data/spec/examples/string_parser_spec.rb +407 -0
- data/spec/fixtures/examples/boolean_algebra.rb +62 -0
- data/spec/fixtures/examples/calc.rb +86 -0
- data/spec/fixtures/examples/capture.rb +36 -0
- data/spec/fixtures/examples/comments.rb +22 -0
- data/spec/fixtures/examples/deepest_errors.rb +99 -0
- data/spec/fixtures/examples/documentation.rb +32 -0
- data/spec/fixtures/examples/email_parser.rb +42 -0
- data/spec/fixtures/examples/empty.rb +10 -0
- data/spec/fixtures/examples/erb.rb +39 -0
- data/spec/fixtures/examples/ip_address.rb +103 -0
- data/spec/fixtures/examples/json.rb +107 -0
- data/spec/fixtures/examples/local.rb +60 -0
- data/spec/fixtures/examples/mathn.rb +47 -0
- data/spec/fixtures/examples/minilisp.rb +75 -0
- data/spec/fixtures/examples/modularity.rb +60 -0
- data/spec/fixtures/examples/nested_errors.rb +95 -0
- data/spec/fixtures/examples/optimized_erb.rb +105 -0
- data/spec/fixtures/examples/parens.rb +25 -0
- data/spec/fixtures/examples/prec_calc.rb +71 -0
- data/spec/fixtures/examples/readme.rb +59 -0
- data/spec/fixtures/examples/scopes.rb +43 -0
- data/spec/fixtures/examples/seasons.rb +40 -0
- data/spec/fixtures/examples/sentence.rb +18 -0
- data/spec/fixtures/examples/simple_xml.rb +51 -0
- data/spec/fixtures/examples/string_parser.rb +77 -0
- data/spec/parslet/atom_results_spec.rb +39 -0
- data/spec/parslet/atoms/alternative_spec.rb +26 -0
- data/spec/parslet/atoms/base_spec.rb +127 -0
- data/spec/parslet/atoms/capture_spec.rb +21 -0
- data/spec/parslet/atoms/combinations_spec.rb +5 -0
- data/spec/parslet/atoms/dsl_spec.rb +7 -0
- data/spec/parslet/atoms/entity_spec.rb +77 -0
- data/spec/parslet/atoms/ignored_spec.rb +15 -0
- data/spec/parslet/atoms/infix_spec.rb +5 -0
- data/spec/parslet/atoms/lookahead_spec.rb +22 -0
- data/spec/parslet/atoms/named_spec.rb +4 -0
- data/spec/parslet/atoms/re_spec.rb +14 -0
- data/spec/parslet/atoms/repetition_spec.rb +24 -0
- data/spec/parslet/atoms/scope_spec.rb +26 -0
- data/spec/parslet/atoms/sequence_spec.rb +28 -0
- data/spec/parslet/atoms/str_spec.rb +15 -0
- data/spec/parslet/atoms/visitor_spec.rb +101 -0
- data/spec/parslet/atoms_spec.rb +488 -0
- data/spec/parslet/convenience_spec.rb +54 -0
- data/spec/parslet/error_reporter/contextual_spec.rb +118 -0
- data/spec/parslet/error_reporter/deepest_spec.rb +82 -0
- data/spec/parslet/error_reporter/tree_spec.rb +7 -0
- data/spec/parslet/export_spec.rb +40 -0
- data/spec/parslet/expression/treetop_spec.rb +74 -0
- data/spec/parslet/minilisp.citrus +29 -0
- data/spec/parslet/minilisp.tt +29 -0
- data/spec/parslet/parser_spec.rb +36 -0
- data/spec/parslet/parslet_spec.rb +38 -0
- data/spec/parslet/pattern_spec.rb +272 -0
- data/spec/parslet/position_spec.rb +14 -0
- data/spec/parslet/rig/rspec_spec.rb +54 -0
- data/spec/parslet/scope_spec.rb +45 -0
- data/spec/parslet/slice_spec.rb +186 -0
- data/spec/parslet/source/line_cache_spec.rb +74 -0
- data/spec/parslet/source_spec.rb +210 -0
- data/spec/parslet/transform/context_spec.rb +56 -0
- data/spec/parslet/transform_spec.rb +183 -0
- data/spec/spec_helper.rb +74 -0
- data/spec/support/opal.rb +8 -0
- data/spec/support/opal.rb.erb +14 -0
- data/spec/support/parslet_matchers.rb +96 -0
- metadata +240 -0
@@ -0,0 +1,89 @@
|
|
1
|
+
# Augments all parslet atoms with an accept method that will call back
|
2
|
+
# to the visitor given.
|
3
|
+
|
4
|
+
#
|
5
|
+
module Parslet::Atoms
|
6
|
+
class Base
|
7
|
+
def accept(visitor)
|
8
|
+
raise NotImplementedError, "No #accept method on #{self.class.name}."
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class Str
|
13
|
+
# Call back visitors #visit_str method. See parslet/export for an example.
|
14
|
+
#
|
15
|
+
def accept(visitor)
|
16
|
+
visitor.visit_str(str)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Entity
|
21
|
+
# Call back visitors #visit_entity method. See parslet/export for an
|
22
|
+
# example.
|
23
|
+
#
|
24
|
+
def accept(visitor)
|
25
|
+
visitor.visit_entity(name, block)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class Named
|
30
|
+
# Call back visitors #visit_named method. See parslet/export for an
|
31
|
+
# example.
|
32
|
+
#
|
33
|
+
def accept(visitor)
|
34
|
+
visitor.visit_named(name, parslet)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class Sequence
|
39
|
+
# Call back visitors #visit_sequence method. See parslet/export for an
|
40
|
+
# example.
|
41
|
+
#
|
42
|
+
def accept(visitor)
|
43
|
+
visitor.visit_sequence(parslets)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class Repetition
|
48
|
+
# Call back visitors #visit_repetition method. See parslet/export for an
|
49
|
+
# example.
|
50
|
+
#
|
51
|
+
def accept(visitor)
|
52
|
+
visitor.visit_repetition(@tag, min, max, parslet)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class Alternative
|
57
|
+
# Call back visitors #visit_alternative method. See parslet/export for an
|
58
|
+
# example.
|
59
|
+
#
|
60
|
+
def accept(visitor)
|
61
|
+
visitor.visit_alternative(alternatives)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class Lookahead
|
66
|
+
# Call back visitors #visit_lookahead method. See parslet/export for an
|
67
|
+
# example.
|
68
|
+
#
|
69
|
+
def accept(visitor)
|
70
|
+
visitor.visit_lookahead(positive, bound_parslet)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
class Re
|
75
|
+
# Call back visitors #visit_re method. See parslet/export for an example.
|
76
|
+
#
|
77
|
+
def accept(visitor)
|
78
|
+
visitor.visit_re(match)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
class Parslet::Parser
|
84
|
+
# Call back visitors #visit_parser method.
|
85
|
+
#
|
86
|
+
def accept(visitor)
|
87
|
+
visitor.visit_parser(root)
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
|
2
|
+
# This is where parslets name comes from: Small parser atoms.
|
3
|
+
#
|
4
|
+
module Parslet::Atoms
|
5
|
+
# The precedence module controls parenthesis during the #inspect printing
|
6
|
+
# of parslets. It is not relevant to other aspects of the parsing.
|
7
|
+
#
|
8
|
+
module Precedence
|
9
|
+
BASE = 1 # everything else
|
10
|
+
LOOKAHEAD = 2 # &SOMETHING
|
11
|
+
REPETITION = 3 # 'a'+, 'a'?
|
12
|
+
SEQUENCE = 4 # 'a' 'b'
|
13
|
+
ALTERNATE = 5 # 'a' | 'b'
|
14
|
+
OUTER = 6 # printing is done here.
|
15
|
+
end
|
16
|
+
|
17
|
+
require 'parslet/atoms/can_flatten'
|
18
|
+
require 'parslet/atoms/context'
|
19
|
+
require 'parslet/atoms/dsl'
|
20
|
+
require 'parslet/atoms/base'
|
21
|
+
require 'parslet/atoms/ignored'
|
22
|
+
require 'parslet/atoms/named'
|
23
|
+
require 'parslet/atoms/lookahead'
|
24
|
+
require 'parslet/atoms/alternative'
|
25
|
+
require 'parslet/atoms/sequence'
|
26
|
+
require 'parslet/atoms/repetition'
|
27
|
+
require 'parslet/atoms/re'
|
28
|
+
require 'parslet/atoms/str'
|
29
|
+
require 'parslet/atoms/entity'
|
30
|
+
require 'parslet/atoms/capture'
|
31
|
+
require 'parslet/atoms/dynamic'
|
32
|
+
require 'parslet/atoms/scope'
|
33
|
+
require 'parslet/atoms/infix'
|
34
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module Parslet
|
2
|
+
# Represents a cause why a parse did fail. A lot of these objects are
|
3
|
+
# constructed - not all of the causes turn out to be failures for the whole
|
4
|
+
# parse.
|
5
|
+
#
|
6
|
+
class Cause
|
7
|
+
def initialize(message, source, pos, children)
|
8
|
+
@message, @source, @pos, @children, @context =
|
9
|
+
message, source, pos, children, nil
|
10
|
+
end
|
11
|
+
|
12
|
+
# @return [String, Array] A string or an array of message pieces that
|
13
|
+
# provide failure information. Use #to_s to get a formatted string.
|
14
|
+
attr_reader :message
|
15
|
+
|
16
|
+
# @return [Parslet::Source] Source that was parsed when this error
|
17
|
+
# happend. Mainly used for line number information.
|
18
|
+
attr_reader :source
|
19
|
+
|
20
|
+
# Location of the error.
|
21
|
+
#
|
22
|
+
# @return [Fixnum] Position where the error happened. (character offset)
|
23
|
+
attr_reader :pos
|
24
|
+
|
25
|
+
# When this cause is part of a tree of error causes: child nodes for this
|
26
|
+
# node. Very often carries the reasons for this cause.
|
27
|
+
#
|
28
|
+
# @return [Array<Parslet::Cause>] A list of reasons for this cause.
|
29
|
+
def children
|
30
|
+
@children ||= []
|
31
|
+
end
|
32
|
+
|
33
|
+
# Appends 'at line LINE char CHAR' to the string given. Use +pos+ to
|
34
|
+
# override the position of the +source+. This method returns an object
|
35
|
+
# that can be turned into a string using #to_s.
|
36
|
+
#
|
37
|
+
# @param source [Parslet::Source] source that was parsed when this error
|
38
|
+
# happened
|
39
|
+
# @param pos [Fixnum] position of error
|
40
|
+
# @param str [String, Array<String>] message parts
|
41
|
+
# @param children [Array<Parslet::Cause>] child nodes for this error tree
|
42
|
+
# @return [Parslet::Cause] a new instance of {Parslet::Cause}
|
43
|
+
#
|
44
|
+
def self.format(source, pos, str, children=[])
|
45
|
+
self.new(str, source, pos, children)
|
46
|
+
end
|
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
|
+
|
55
|
+
def to_s
|
56
|
+
line, column = source.line_and_column(pos)
|
57
|
+
# Allow message to be a list of objects. Join them here, since we now
|
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}."
|
63
|
+
end
|
64
|
+
|
65
|
+
# Signals to the outside that the parse has failed. Use this in
|
66
|
+
# conjunction with .format for nice error messages.
|
67
|
+
#
|
68
|
+
def raise(exception_klass=Parslet::ParseFailed)
|
69
|
+
exception = exception_klass.new(self.to_s, self)
|
70
|
+
Kernel.raise exception
|
71
|
+
end
|
72
|
+
|
73
|
+
# Returns an ascii tree representation of the causes of this node and its
|
74
|
+
# children.
|
75
|
+
#
|
76
|
+
def ascii_tree
|
77
|
+
StringIO.new.tap { |io|
|
78
|
+
recursive_ascii_tree(self, io, [true]) }.
|
79
|
+
string
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
def recursive_ascii_tree(node, stream, curved)
|
84
|
+
append_prefix(stream, curved)
|
85
|
+
stream.puts node.to_s
|
86
|
+
|
87
|
+
node.children.each do |child|
|
88
|
+
last_child = (node.children.last == child)
|
89
|
+
|
90
|
+
recursive_ascii_tree(child, stream, curved + [last_child])
|
91
|
+
end
|
92
|
+
end
|
93
|
+
def append_prefix(stream, curved)
|
94
|
+
return if curved.size < 2
|
95
|
+
curved[1..-2].each do |c|
|
96
|
+
stream.print c ? " " : "| "
|
97
|
+
end
|
98
|
+
stream.print curved.last ? "`- " : "|- "
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# Provides a context for tree transformations to run in. The context allows
|
2
|
+
# accessing each of the bindings in the bindings hash as local method.
|
3
|
+
#
|
4
|
+
# Example:
|
5
|
+
#
|
6
|
+
# ctx = Context.new(:a => :b)
|
7
|
+
# ctx.instance_eval do
|
8
|
+
# a # => :b
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
# @api private
|
12
|
+
class Parslet::Context
|
13
|
+
include Parslet
|
14
|
+
|
15
|
+
def initialize(bindings)
|
16
|
+
bindings.each do |key, value|
|
17
|
+
singleton_class.send(:define_method, key) { value }
|
18
|
+
instance_variable_set("@#{key}", value)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class Parslet::Atoms::Base
|
2
|
+
|
3
|
+
# Packages the common idiom
|
4
|
+
#
|
5
|
+
# begin
|
6
|
+
# tree = parser.parse('something')
|
7
|
+
# rescue Parslet::ParseFailed => error
|
8
|
+
# puts parser.parse_failure_cause.ascii_tree
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
# into a convenient method.
|
12
|
+
#
|
13
|
+
# Usage:
|
14
|
+
#
|
15
|
+
# require 'parslet'
|
16
|
+
# require 'parslet/convenience'
|
17
|
+
#
|
18
|
+
# class FooParser < Parslet::Parser
|
19
|
+
# rule(:foo) { str('foo') }
|
20
|
+
# root(:foo)
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# FooParser.new.parse_with_debug('bar')
|
24
|
+
#
|
25
|
+
# @see Parslet::Atoms::Base#parse
|
26
|
+
#
|
27
|
+
def parse_with_debug str, opts={}
|
28
|
+
parse str, opts
|
29
|
+
rescue Parslet::ParseFailed => error
|
30
|
+
puts error.parse_failure_cause.ascii_tree
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -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
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module Parslet
|
2
|
+
module ErrorReporter
|
3
|
+
# Instead of reporting the latest error that happens like {Tree} does,
|
4
|
+
# this class reports the deepest error. Depth is defined here as how
|
5
|
+
# advanced into the input an error happens. The errors close to the
|
6
|
+
# greatest depth tend to be more relevant to the end user, since they
|
7
|
+
# specify what could be done to make them go away.
|
8
|
+
#
|
9
|
+
# More specifically, errors produced by this reporter won't be related to
|
10
|
+
# the structure of the grammar at all. The positions of the errors will
|
11
|
+
# be advanced and convey at every grammar level what the deepest rule
|
12
|
+
# was to fail.
|
13
|
+
#
|
14
|
+
class Deepest
|
15
|
+
def initialize
|
16
|
+
@deepest_cause = nil
|
17
|
+
end
|
18
|
+
|
19
|
+
# Produces an error cause that combines the message at the current level
|
20
|
+
# with the errors that happened at a level below (children).
|
21
|
+
#
|
22
|
+
# @param atom [Parslet::Atoms::Base] parslet that failed
|
23
|
+
# @param source [Source] Source that we're using for this parse. (line
|
24
|
+
# number information...)
|
25
|
+
# @param message [String, Array] Error message at this level.
|
26
|
+
# @param children [Array] A list of errors from a deeper level (or nil).
|
27
|
+
# @return [Cause] An error tree combining children with message.
|
28
|
+
#
|
29
|
+
def err(atom, source, message, children=nil)
|
30
|
+
position = source.pos
|
31
|
+
cause = Cause.format(source, position, message, children)
|
32
|
+
return deepest(cause)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Produces an error cause that combines the message at the current level
|
36
|
+
# with the errors that happened at a level below (children).
|
37
|
+
#
|
38
|
+
# @param atom [Parslet::Atoms::Base] parslet that failed
|
39
|
+
# @param source [Source] Source that we're using for this parse. (line
|
40
|
+
# number information...)
|
41
|
+
# @param message [String, Array] Error message at this level.
|
42
|
+
# @param pos [Fixnum] The real position of the error.
|
43
|
+
# @param children [Array] A list of errors from a deeper level (or nil).
|
44
|
+
# @return [Cause] An error tree combining children with message.
|
45
|
+
#
|
46
|
+
def err_at(atom, source, message, pos, children=nil)
|
47
|
+
position = pos
|
48
|
+
cause = Cause.format(source, position, message, children)
|
49
|
+
return deepest(cause)
|
50
|
+
end
|
51
|
+
|
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
|
58
|
+
#
|
59
|
+
attr_reader :deepest_cause
|
60
|
+
|
61
|
+
# Checks to see if the lineage of the cause given includes a cause with
|
62
|
+
# an error position deeper than the current deepest cause stored. If
|
63
|
+
# yes, it passes the cause through to the caller. If no, it returns the
|
64
|
+
# current deepest error that was saved as a reference.
|
65
|
+
#
|
66
|
+
def deepest(cause)
|
67
|
+
_, leaf = deepest_child(cause)
|
68
|
+
|
69
|
+
if !deepest_cause || leaf.pos >= deepest_cause.pos
|
70
|
+
# This error reaches deeper into the input, save it as reference.
|
71
|
+
@deepest_cause = leaf
|
72
|
+
return cause
|
73
|
+
end
|
74
|
+
|
75
|
+
return deepest_cause
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
# Returns the leaf from a given error tree with the biggest rank.
|
80
|
+
#
|
81
|
+
def deepest_child(cause, rank=0)
|
82
|
+
max_child = cause
|
83
|
+
max_rank = rank
|
84
|
+
|
85
|
+
if cause.children && !cause.children.empty?
|
86
|
+
cause.children.each do |child|
|
87
|
+
c_rank, c_cause = deepest_child(child, rank+1)
|
88
|
+
|
89
|
+
if c_rank > max_rank
|
90
|
+
max_rank = c_rank
|
91
|
+
max_child = c_cause
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
return max_rank, max_child
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Parslet
|
2
|
+
module ErrorReporter
|
3
|
+
# An error reporter has two central methods, one for reporting errors at
|
4
|
+
# the current parse position (#err) and one for reporting errors at a
|
5
|
+
# given parse position (#err_at). The reporter can return an object (a
|
6
|
+
# 'cause') that will be returned to the caller along with the information
|
7
|
+
# that the parse failed.
|
8
|
+
#
|
9
|
+
# When reporting errors on the outer levels of your parser, these methods
|
10
|
+
# get passed a list of error objects ('causes') from the inner levels. In
|
11
|
+
# this default implementation, the inner levels are considered error
|
12
|
+
# subtrees and are appended to the generated tree node at each level,
|
13
|
+
# thereby constructing an error tree.
|
14
|
+
#
|
15
|
+
# This error tree will report in parallel with the grammar structure that
|
16
|
+
# failed. A one-to-one correspondence exists between each error in the
|
17
|
+
# tree and the parslet atom that produced that error.
|
18
|
+
#
|
19
|
+
# The implementor is really free to use these return values as he sees
|
20
|
+
# fit. One example would be to return an error state object from these
|
21
|
+
# methods that is then updated as errors cascade up the parse derivation
|
22
|
+
# tree.
|
23
|
+
#
|
24
|
+
class Tree
|
25
|
+
# Produces an error cause that combines the message at the current level
|
26
|
+
# with the errors that happened at a level below (children).
|
27
|
+
#
|
28
|
+
# @param atom [Parslet::Atoms::Base] parslet that failed
|
29
|
+
# @param source [Source] Source that we're using for this parse. (line
|
30
|
+
# number information...)
|
31
|
+
# @param message [String, Array] Error message at this level.
|
32
|
+
# @param children [Array] A list of errors from a deeper level (or nil).
|
33
|
+
# @return [Cause] An error tree combining children with message.
|
34
|
+
#
|
35
|
+
def err(atom, source, message, children=nil)
|
36
|
+
position = source.pos
|
37
|
+
Cause.format(source, position, message, children)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Produces an error cause that combines the message at the current level
|
41
|
+
# with the errors that happened at a level below (children).
|
42
|
+
#
|
43
|
+
# @param atom [Parslet::Atoms::Base] parslet that failed
|
44
|
+
# @param source [Source] Source that we're using for this parse. (line
|
45
|
+
# number information...)
|
46
|
+
# @param message [String, Array] Error message at this level.
|
47
|
+
# @param pos [Fixnum] The real position of the error.
|
48
|
+
# @param children [Array] A list of errors from a deeper level (or nil).
|
49
|
+
# @return [Cause] An error tree combining children with message.
|
50
|
+
#
|
51
|
+
def err_at(atom, source, message, pos, children=nil)
|
52
|
+
position = pos
|
53
|
+
Cause.format(source, position, message, children)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Notification that an expression successfully parsed
|
57
|
+
# not used, see ErrorReporter::Contextual
|
58
|
+
def succ(source)
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|