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,163 @@
|
|
1
|
+
# Allows exporting parslet grammars to other lingos.
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
require 'parslet/atoms/visitor'
|
5
|
+
|
6
|
+
class Parslet::Parser
|
7
|
+
module Visitors
|
8
|
+
class Citrus
|
9
|
+
attr_reader :context, :output
|
10
|
+
def initialize(context)
|
11
|
+
@context = context
|
12
|
+
end
|
13
|
+
|
14
|
+
def visit_str(str)
|
15
|
+
"\"#{str.inspect[1..-2]}\""
|
16
|
+
end
|
17
|
+
def visit_re(match)
|
18
|
+
match.to_s
|
19
|
+
end
|
20
|
+
|
21
|
+
def visit_entity(name, block)
|
22
|
+
context.deferred(name, block)
|
23
|
+
|
24
|
+
"(#{context.mangle_name(name)})"
|
25
|
+
end
|
26
|
+
def visit_named(name, parslet)
|
27
|
+
parslet.accept(self)
|
28
|
+
end
|
29
|
+
|
30
|
+
def visit_sequence(parslets)
|
31
|
+
'(' +
|
32
|
+
parslets.
|
33
|
+
map { |el| el.accept(self) }.
|
34
|
+
join(' ') +
|
35
|
+
')'
|
36
|
+
end
|
37
|
+
def visit_repetition(tag, min, max, parslet)
|
38
|
+
parslet.accept(self) + "#{min}*#{max}"
|
39
|
+
end
|
40
|
+
def visit_alternative(alternatives)
|
41
|
+
'(' +
|
42
|
+
alternatives.
|
43
|
+
map { |el| el.accept(self) }.
|
44
|
+
join(' | ') +
|
45
|
+
')'
|
46
|
+
end
|
47
|
+
|
48
|
+
def visit_lookahead(positive, bound_parslet)
|
49
|
+
(positive ? '&' : '!') +
|
50
|
+
bound_parslet.accept(self)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class Treetop < Citrus
|
55
|
+
def visit_repetition(tag, min, max, parslet)
|
56
|
+
parslet.accept(self) + "#{min}..#{max}"
|
57
|
+
end
|
58
|
+
|
59
|
+
def visit_alternative(alternatives)
|
60
|
+
'(' +
|
61
|
+
alternatives.
|
62
|
+
map { |el| el.accept(self) }.
|
63
|
+
join(' / ') +
|
64
|
+
')'
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# A helper class that formats Citrus and Treetop grammars as a string.
|
70
|
+
#
|
71
|
+
class PrettyPrinter
|
72
|
+
attr_reader :visitor
|
73
|
+
def initialize(visitor_klass)
|
74
|
+
@visitor = visitor_klass.new(self)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Pretty prints the given parslet using the visitor that has been
|
78
|
+
# configured in initialize. Returns the string representation of the
|
79
|
+
# Citrus or Treetop grammar.
|
80
|
+
#
|
81
|
+
def pretty_print(name, parslet)
|
82
|
+
output = ["grammar #{name}\n"]
|
83
|
+
|
84
|
+
output << rule('root', parslet)
|
85
|
+
|
86
|
+
seen = Set.new
|
87
|
+
loop do
|
88
|
+
# @todo is constantly filled by the visitor (see #deferred). We
|
89
|
+
# keep going until it is empty.
|
90
|
+
break if @todo.empty?
|
91
|
+
name, block = @todo.shift
|
92
|
+
|
93
|
+
# Track what rules we've already seen. This breaks loops.
|
94
|
+
next if seen.include?(name)
|
95
|
+
seen << name
|
96
|
+
|
97
|
+
output << rule(name, block.call)
|
98
|
+
end
|
99
|
+
|
100
|
+
output << "end\n"
|
101
|
+
output.join
|
102
|
+
end
|
103
|
+
|
104
|
+
# Formats a rule in either dialect.
|
105
|
+
#
|
106
|
+
def rule(name, parslet)
|
107
|
+
" rule #{mangle_name name}\n" +
|
108
|
+
" " + parslet.accept(visitor) + "\n" +
|
109
|
+
" end\n"
|
110
|
+
end
|
111
|
+
|
112
|
+
# Whenever the visitor encounters an rule in a parslet, it defers the
|
113
|
+
# pretty printing of the rule by calling this method.
|
114
|
+
#
|
115
|
+
def deferred(name, content)
|
116
|
+
@todo ||= []
|
117
|
+
@todo << [name, content]
|
118
|
+
end
|
119
|
+
|
120
|
+
# Mangles names so that Citrus and Treetop can live with it. This mostly
|
121
|
+
# transforms some of the things that Ruby allows into other patterns. If
|
122
|
+
# there is collision, we will not detect it for now.
|
123
|
+
#
|
124
|
+
def mangle_name(str)
|
125
|
+
str.to_s.sub(/\?$/, '_p')
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# Exports the current parser instance as a string in the Citrus dialect.
|
130
|
+
#
|
131
|
+
# Example:
|
132
|
+
#
|
133
|
+
# require 'parslet/export'
|
134
|
+
# class MyParser < Parslet::Parser
|
135
|
+
# root(:expression)
|
136
|
+
# rule(:expression) { str('foo') }
|
137
|
+
# end
|
138
|
+
#
|
139
|
+
# MyParser.new.to_citrus # => a citrus grammar as a string
|
140
|
+
#
|
141
|
+
def to_citrus
|
142
|
+
PrettyPrinter.new(Visitors::Citrus).
|
143
|
+
pretty_print(self.class.name, root)
|
144
|
+
end
|
145
|
+
|
146
|
+
# Exports the current parser instance as a string in the Treetop dialect.
|
147
|
+
#
|
148
|
+
# Example:
|
149
|
+
#
|
150
|
+
# require 'parslet/export'
|
151
|
+
# class MyParser < Parslet::Parser
|
152
|
+
# root(:expression)
|
153
|
+
# rule(:expression) { str('foo') }
|
154
|
+
# end
|
155
|
+
#
|
156
|
+
# MyParser.new.to_treetop # => a treetop grammar as a string
|
157
|
+
#
|
158
|
+
def to_treetop
|
159
|
+
PrettyPrinter.new(Visitors::Treetop).
|
160
|
+
pretty_print(self.class.name, root)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
@@ -0,0 +1,92 @@
|
|
1
|
+
class Parslet::Expression::Treetop
|
2
|
+
class Parser < Parslet::Parser
|
3
|
+
root(:expression)
|
4
|
+
|
5
|
+
rule(:expression) { alternatives }
|
6
|
+
|
7
|
+
# alternative 'a' / 'b'
|
8
|
+
rule(:alternatives) {
|
9
|
+
(simple >> (spaced('/') >> simple).repeat).as(:alt)
|
10
|
+
}
|
11
|
+
|
12
|
+
# sequence by simple concatenation 'a' 'b'
|
13
|
+
rule(:simple) { occurrence.repeat(1).as(:seq) }
|
14
|
+
|
15
|
+
# occurrence modifiers
|
16
|
+
rule(:occurrence) {
|
17
|
+
atom.as(:repetition) >> spaced('*').as(:sign) |
|
18
|
+
atom.as(:repetition) >> spaced('+').as(:sign) |
|
19
|
+
atom.as(:repetition) >> repetition_spec |
|
20
|
+
|
21
|
+
atom.as(:maybe) >> spaced('?') |
|
22
|
+
atom
|
23
|
+
}
|
24
|
+
|
25
|
+
rule(:atom) {
|
26
|
+
spaced('(') >> expression.as(:unwrap) >> spaced(')') |
|
27
|
+
dot |
|
28
|
+
string |
|
29
|
+
char_class
|
30
|
+
}
|
31
|
+
|
32
|
+
# a character class
|
33
|
+
rule(:char_class) {
|
34
|
+
(str('[') >>
|
35
|
+
(str('\\') >> any |
|
36
|
+
str(']').absent? >> any).repeat(1) >>
|
37
|
+
str(']')).as(:match) >> space?
|
38
|
+
}
|
39
|
+
|
40
|
+
# anything at all
|
41
|
+
rule(:dot) { spaced('.').as(:any) }
|
42
|
+
|
43
|
+
# recognizing strings
|
44
|
+
rule(:string) {
|
45
|
+
str('\'') >>
|
46
|
+
(
|
47
|
+
(str('\\') >> any) |
|
48
|
+
(str("'").absent? >> any)
|
49
|
+
).repeat.as(:string) >>
|
50
|
+
str('\'') >> space?
|
51
|
+
}
|
52
|
+
|
53
|
+
# repetition specification like {1, 2}
|
54
|
+
rule(:repetition_spec) {
|
55
|
+
spaced('{') >>
|
56
|
+
integer.maybe.as(:min) >> spaced(',') >>
|
57
|
+
integer.maybe.as(:max) >> spaced('}')
|
58
|
+
}
|
59
|
+
rule(:integer) {
|
60
|
+
match['0-9'].repeat(1)
|
61
|
+
}
|
62
|
+
|
63
|
+
# whitespace handling
|
64
|
+
rule(:space) { match("\s").repeat(1) }
|
65
|
+
rule(:space?) { space.maybe }
|
66
|
+
|
67
|
+
def spaced(str)
|
68
|
+
str(str) >> space?
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class Transform < Parslet::Transform
|
73
|
+
|
74
|
+
rule(:repetition => simple(:rep), :sign => simple(:sign)) {
|
75
|
+
min = sign=='+' ? 1 : 0
|
76
|
+
Parslet::Atoms::Repetition.new(rep, min, nil) }
|
77
|
+
rule(:repetition => simple(:rep), :min => simple(:min), :max => simple(:max)) {
|
78
|
+
Parslet::Atoms::Repetition.new(rep,
|
79
|
+
Integer(min || 0),
|
80
|
+
max && Integer(max) || nil) }
|
81
|
+
|
82
|
+
rule(:alt => subtree(:alt)) { Parslet::Atoms::Alternative.new(*alt) }
|
83
|
+
rule(:seq => sequence(:s)) { Parslet::Atoms::Sequence.new(*s) }
|
84
|
+
rule(:unwrap => simple(:u)) { u }
|
85
|
+
rule(:maybe => simple(:m)) { |d| d[:m].maybe }
|
86
|
+
rule(:string => simple(:s)) { Parslet::Atoms::Str.new(s) }
|
87
|
+
rule(:match => simple(:m)) { Parslet::Atoms::Re.new(m) }
|
88
|
+
rule(:any => simple(:a)) { Parslet::Atoms::Re.new('.') }
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
@@ -0,0 +1,51 @@
|
|
1
|
+
|
2
|
+
# Allows specifying rules as strings using the exact same grammar that treetop
|
3
|
+
# does, minus the actions. This is on one hand a good example of a fully
|
4
|
+
# fledged parser and on the other hand might even turn out really useful.
|
5
|
+
#
|
6
|
+
# This can be viewed as an extension to parslet and might even be hosted in
|
7
|
+
# its own gem one fine day.
|
8
|
+
#
|
9
|
+
class Parslet::Expression
|
10
|
+
include Parslet
|
11
|
+
|
12
|
+
autoload :Treetop, 'parslet/expression/treetop'
|
13
|
+
|
14
|
+
# Creates a parslet from a foreign language expression.
|
15
|
+
#
|
16
|
+
# Example:
|
17
|
+
#
|
18
|
+
# Parslet::Expression.new("'a' 'b'")
|
19
|
+
#
|
20
|
+
def initialize(str, opts={}, context=self)
|
21
|
+
@type = opts[:type] || :treetop
|
22
|
+
@exp = str
|
23
|
+
@parslet = transform(
|
24
|
+
parse(str))
|
25
|
+
end
|
26
|
+
|
27
|
+
# Transforms the parse tree into a parslet expression.
|
28
|
+
#
|
29
|
+
def transform(tree)
|
30
|
+
transform = Treetop::Transform.new
|
31
|
+
|
32
|
+
# pp tree
|
33
|
+
transform.apply(tree)
|
34
|
+
rescue
|
35
|
+
warn "Could not transform: " + tree.inspect
|
36
|
+
raise
|
37
|
+
end
|
38
|
+
|
39
|
+
# Parses the string and returns a parse tree.
|
40
|
+
#
|
41
|
+
def parse(str)
|
42
|
+
parser = Treetop::Parser.new
|
43
|
+
parser.parse(str)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Turns this expression into a parslet.
|
47
|
+
#
|
48
|
+
def to_parslet
|
49
|
+
@parslet
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
|
2
|
+
# Paints a graphviz graph of your parser.
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'ruby-graphviz'
|
6
|
+
rescue LoadError
|
7
|
+
puts "Please install the 'ruby-graphviz' gem first."
|
8
|
+
fail
|
9
|
+
end
|
10
|
+
|
11
|
+
require 'set'
|
12
|
+
require 'parslet/atoms/visitor'
|
13
|
+
|
14
|
+
module Parslet
|
15
|
+
class GraphvizVisitor
|
16
|
+
def initialize g
|
17
|
+
@graph = g
|
18
|
+
@known_links = Set.new
|
19
|
+
@visited = Set.new
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :parent
|
23
|
+
|
24
|
+
def visit_parser(root)
|
25
|
+
recurse root, node('parser')
|
26
|
+
end
|
27
|
+
def visit_entity(name, block)
|
28
|
+
s = node(name)
|
29
|
+
|
30
|
+
downwards s
|
31
|
+
|
32
|
+
return if @visited.include?(name)
|
33
|
+
@visited << name
|
34
|
+
|
35
|
+
recurse block.call, s
|
36
|
+
end
|
37
|
+
def visit_named(name, atom)
|
38
|
+
recurse atom, parent
|
39
|
+
end
|
40
|
+
def visit_repetition(tag, min, max, atom)
|
41
|
+
recurse atom, parent
|
42
|
+
end
|
43
|
+
def visit_alternative(alternatives)
|
44
|
+
p = parent
|
45
|
+
alternatives.each do |atom|
|
46
|
+
recurse atom, p
|
47
|
+
end
|
48
|
+
end
|
49
|
+
def visit_sequence(sequence)
|
50
|
+
p = parent
|
51
|
+
sequence.each do |atom|
|
52
|
+
recurse atom, p
|
53
|
+
end
|
54
|
+
end
|
55
|
+
def visit_lookahead(positive, atom)
|
56
|
+
recurse atom, parent
|
57
|
+
end
|
58
|
+
def visit_re(regexp)
|
59
|
+
# downwards node(regexp.object_id, label: escape("re(#{regexp.inspect})"))
|
60
|
+
end
|
61
|
+
def visit_str(str)
|
62
|
+
# downwards node(str.object_id, label: escape("#{str.inspect}"))
|
63
|
+
end
|
64
|
+
|
65
|
+
def escape str
|
66
|
+
str.gsub('"', "'")
|
67
|
+
end
|
68
|
+
def node name, opts={}
|
69
|
+
@graph.add_nodes name.to_s, opts
|
70
|
+
end
|
71
|
+
def downwards child
|
72
|
+
if @parent && !@known_links.include?([@parent, child])
|
73
|
+
@graph.add_edges(@parent, child)
|
74
|
+
@known_links << [@parent, child]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
def recurse node, current
|
78
|
+
@parent = current
|
79
|
+
node.accept(self)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
module Graphable
|
84
|
+
def graph opts
|
85
|
+
g = GraphViz.new(:G, type: :digraph)
|
86
|
+
visitor = GraphvizVisitor.new(g)
|
87
|
+
|
88
|
+
new.accept(visitor)
|
89
|
+
|
90
|
+
g.output opts
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
class Parser # reopen for introducing the .graph method
|
95
|
+
extend Graphable
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
|
2
|
+
# The base class for all your parsers. Use as follows:
|
3
|
+
#
|
4
|
+
# require 'parslet'
|
5
|
+
#
|
6
|
+
# class MyParser < Parslet::Parser
|
7
|
+
# rule(:a) { str('a').repeat }
|
8
|
+
# root(:a)
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
# pp MyParser.new.parse('aaaa') # => 'aaaa'
|
12
|
+
# pp MyParser.new.parse('bbbb') # => Parslet::Atoms::ParseFailed:
|
13
|
+
# # Don't know what to do with bbbb at line 1 char 1.
|
14
|
+
#
|
15
|
+
# Parslet::Parser is also a grammar atom. This means that you can mix full
|
16
|
+
# fledged parsers freely with small parts of a different parser.
|
17
|
+
#
|
18
|
+
# Example:
|
19
|
+
# class ParserA < Parslet::Parser
|
20
|
+
# root :aaa
|
21
|
+
# rule(:aaa) { str('a').repeat(3,3) }
|
22
|
+
# end
|
23
|
+
# class ParserB < Parslet::Parser
|
24
|
+
# root :expression
|
25
|
+
# rule(:expression) { str('b') >> ParserA.new >> str('b') }
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# In the above example, ParserB would parse something like 'baaab'.
|
29
|
+
#
|
30
|
+
class Parslet::Parser < Parslet::Atoms::Base
|
31
|
+
include Parslet
|
32
|
+
|
33
|
+
class << self # class methods
|
34
|
+
# Define the parsers #root function. This is the place where you start
|
35
|
+
# parsing; if you have a rule for 'file' that describes what should be
|
36
|
+
# in a file, this would be your root declaration:
|
37
|
+
#
|
38
|
+
# class Parser
|
39
|
+
# root :file
|
40
|
+
# rule(:file) { ... }
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# #root declares a 'parse' function that works just like the parse
|
44
|
+
# function that you can call on a simple parslet, taking a string as input
|
45
|
+
# and producing parse output.
|
46
|
+
#
|
47
|
+
# In a way, #root is a shorthand for:
|
48
|
+
#
|
49
|
+
# def parse(str)
|
50
|
+
# your_parser_root.parse(str)
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
def root(name)
|
54
|
+
undef_method :root if method_defined? :root
|
55
|
+
define_method(:root) do
|
56
|
+
self.send(name)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def try(source, context, consume_all)
|
62
|
+
root.try(source, context, consume_all)
|
63
|
+
end
|
64
|
+
|
65
|
+
def to_s_inner(prec)
|
66
|
+
root.to_s(prec)
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
|
2
|
+
# Used internally for representing a bind placeholder in a Parslet::Transform
|
3
|
+
# pattern. This is the superclass for all bindings.
|
4
|
+
#
|
5
|
+
# It defines the most permissive kind of bind, the one that matches any subtree
|
6
|
+
# whatever it looks like.
|
7
|
+
#
|
8
|
+
class Parslet::Pattern::SubtreeBind < Struct.new(:symbol)
|
9
|
+
def variable_name
|
10
|
+
symbol
|
11
|
+
end
|
12
|
+
|
13
|
+
def inspect
|
14
|
+
"#{bind_type_name}(#{symbol.inspect})"
|
15
|
+
end
|
16
|
+
|
17
|
+
def can_bind?(subtree)
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
def bind_type_name
|
23
|
+
if md=self.class.name.match(/(\w+)Bind/)
|
24
|
+
md.captures.first.downcase
|
25
|
+
else
|
26
|
+
# This path should never be used, but since this is for inspection only,
|
27
|
+
# let's not raise.
|
28
|
+
'unknown_bind'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Binds a symbol to a simple subtree, one that is not either a sequence of
|
34
|
+
# elements or a collection of attributes.
|
35
|
+
#
|
36
|
+
class Parslet::Pattern::SimpleBind < Parslet::Pattern::SubtreeBind
|
37
|
+
def can_bind?(subtree)
|
38
|
+
not [Hash, Array].include?(subtree.class)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Binds a symbol to a sequence of simple leafs ([element1, element2, ...])
|
43
|
+
#
|
44
|
+
class Parslet::Pattern::SequenceBind < Parslet::Pattern::SubtreeBind
|
45
|
+
def can_bind?(subtree)
|
46
|
+
subtree.kind_of?(Array) &&
|
47
|
+
(not subtree.any? { |el| [Hash, Array].include?(el.class) })
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# Matches trees against expressions. Trees are formed by arrays and hashes
|
2
|
+
# for expressing membership and sequence. The leafs of the tree are other
|
3
|
+
# classes.
|
4
|
+
#
|
5
|
+
# A tree issued by the parslet library might look like this:
|
6
|
+
#
|
7
|
+
# {
|
8
|
+
# :function_call => {
|
9
|
+
# :name => 'foobar',
|
10
|
+
# :args => [1, 2, 3]
|
11
|
+
# }
|
12
|
+
# }
|
13
|
+
#
|
14
|
+
# A pattern that would match against this tree would be:
|
15
|
+
#
|
16
|
+
# { :function_call => { :name => simple(:name), :args => sequence(:args) }}
|
17
|
+
#
|
18
|
+
# Note that Parslet::Pattern only matches at a given subtree; it wont try
|
19
|
+
# to match recursively. To do that, please use Parslet::Transform.
|
20
|
+
#
|
21
|
+
class Parslet::Pattern
|
22
|
+
def initialize(pattern)
|
23
|
+
@pattern = pattern
|
24
|
+
end
|
25
|
+
|
26
|
+
# Decides if the given subtree matches this pattern. Returns the bindings
|
27
|
+
# made on a successful match or nil if the match fails. If you specify
|
28
|
+
# bindings to be a hash, the mappings in it will be treated like bindings
|
29
|
+
# made during an attempted match.
|
30
|
+
#
|
31
|
+
# Pattern.new('a').match('a', :foo => 'bar') # => { :foo => 'bar' }
|
32
|
+
#
|
33
|
+
# @param subtree [String, Hash, Array] poro subtree returned by a parse
|
34
|
+
# @param bindings [Hash] variable bindings to be verified
|
35
|
+
# @return [Hash, nil] On success: variable bindings that allow a match. On
|
36
|
+
# failure: nil
|
37
|
+
#
|
38
|
+
def match(subtree, bindings=nil)
|
39
|
+
bindings = bindings && bindings.dup || Hash.new
|
40
|
+
return bindings if element_match(subtree, @pattern, bindings)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns true if the tree element given by +tree+ matches the expression
|
44
|
+
# given by +exp+. This match must respect bindings already made in
|
45
|
+
# +bindings+. Note that bindings is carried along and modified.
|
46
|
+
#
|
47
|
+
# @api private
|
48
|
+
#
|
49
|
+
def element_match(tree, exp, bindings)
|
50
|
+
# p [:elm, tree, exp]
|
51
|
+
if tree.is_a?(Hash) && exp.is_a?(Hash)
|
52
|
+
return element_match_hash(tree, exp, bindings)
|
53
|
+
elsif tree.is_a?(Array) && exp.is_a?(Array)
|
54
|
+
return element_match_ary_single(tree, exp, bindings)
|
55
|
+
else
|
56
|
+
# If elements match exactly, then that is good enough in all cases
|
57
|
+
return true if exp === tree
|
58
|
+
|
59
|
+
# If exp is a bind variable: Check if the binding matches
|
60
|
+
if exp.respond_to?(:can_bind?) && exp.can_bind?(tree)
|
61
|
+
return element_match_binding(tree, exp, bindings)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Otherwise: No match (we don't know anything about the element
|
65
|
+
# combination)
|
66
|
+
return false
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# @api private
|
71
|
+
#
|
72
|
+
def element_match_binding(tree, exp, bindings)
|
73
|
+
var_name = exp.variable_name
|
74
|
+
|
75
|
+
# TODO test for the hidden :_ feature.
|
76
|
+
if var_name && bound_value = bindings[var_name]
|
77
|
+
return bound_value == tree
|
78
|
+
end
|
79
|
+
|
80
|
+
# New binding:
|
81
|
+
bindings.store var_name, tree
|
82
|
+
|
83
|
+
return true
|
84
|
+
end
|
85
|
+
|
86
|
+
# @api private
|
87
|
+
#
|
88
|
+
def element_match_ary_single(sequence, exp, bindings)
|
89
|
+
return false if sequence.size != exp.size
|
90
|
+
|
91
|
+
return sequence.zip(exp).all? { |elt, subexp|
|
92
|
+
element_match(elt, subexp, bindings) }
|
93
|
+
end
|
94
|
+
|
95
|
+
# @api private
|
96
|
+
#
|
97
|
+
def element_match_hash(tree, exp, bindings)
|
98
|
+
# Early failure when one hash is bigger than the other
|
99
|
+
return false unless exp.size == tree.size
|
100
|
+
|
101
|
+
# We iterate over expected pattern, since we demand that the keys that
|
102
|
+
# are there should be in tree as well.
|
103
|
+
exp.each do |expected_key, expected_value|
|
104
|
+
return false unless tree.has_key? expected_key
|
105
|
+
|
106
|
+
# Recurse into the value and stop early on failure
|
107
|
+
value = tree[expected_key]
|
108
|
+
return false unless element_match(value, expected_value, bindings)
|
109
|
+
end
|
110
|
+
|
111
|
+
return true
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
|
2
|
+
# Encapsules the concept of a position inside a string.
|
3
|
+
#
|
4
|
+
class Parslet::Position
|
5
|
+
attr_reader :bytepos
|
6
|
+
|
7
|
+
include Comparable
|
8
|
+
|
9
|
+
def initialize string, bytepos
|
10
|
+
@string = string
|
11
|
+
@bytepos = bytepos
|
12
|
+
end
|
13
|
+
|
14
|
+
def charpos
|
15
|
+
@string.byteslice(0, @bytepos).size
|
16
|
+
end
|
17
|
+
|
18
|
+
def <=> b
|
19
|
+
self.bytepos <=> b.bytepos
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
RSpec::Matchers.define(:parse) do |input, opts|
|
2
|
+
as = block = nil
|
3
|
+
result = trace = nil
|
4
|
+
|
5
|
+
match do |parser|
|
6
|
+
begin
|
7
|
+
result = parser.parse(input)
|
8
|
+
block ?
|
9
|
+
block.call(result) :
|
10
|
+
(as == result || as.nil?)
|
11
|
+
rescue Parslet::ParseFailed => ex
|
12
|
+
trace = ex.parse_failure_cause.ascii_tree if opts && opts[:trace]
|
13
|
+
false
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
public_send(respond_to?(:failure_message) ? :failure_message : :failure_message_for_should) do |is|
|
18
|
+
if block
|
19
|
+
"expected output of parsing #{input.inspect}" <<
|
20
|
+
" with #{is.inspect} to meet block conditions, but it didn't"
|
21
|
+
else
|
22
|
+
"expected " <<
|
23
|
+
(as ?
|
24
|
+
"output of parsing #{input.inspect}"<<
|
25
|
+
" with #{is.inspect} to equal #{as.inspect}, but was #{result.inspect}" :
|
26
|
+
"#{is.inspect} to be able to parse #{input.inspect}") <<
|
27
|
+
(trace ?
|
28
|
+
"\n"+trace :
|
29
|
+
'')
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
public_send(respond_to?(:failure_message_when_negated) ? :failure_message_when_negated : :failure_message_for_should_not) do |is|
|
34
|
+
if block
|
35
|
+
"expected output of parsing #{input.inspect} with #{is.inspect} not to meet block conditions, but it did"
|
36
|
+
else
|
37
|
+
"expected " <<
|
38
|
+
(as ?
|
39
|
+
"output of parsing #{input.inspect}"<<
|
40
|
+
" with #{is.inspect} not to equal #{as.inspect}" :
|
41
|
+
|
42
|
+
"#{is.inspect} to not parse #{input.inspect}, but it did")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# NOTE: This has a nodoc tag since the rdoc parser puts this into
|
47
|
+
# Object, a thing I would never allow.
|
48
|
+
chain :as do |expected_output=nil, &my_block|
|
49
|
+
as = expected_output
|
50
|
+
block = my_block
|
51
|
+
end
|
52
|
+
end
|