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.
Files changed (148) hide show
  1. checksums.yaml +7 -0
  2. data/HISTORY.txt +284 -0
  3. data/LICENSE +23 -0
  4. data/README.adoc +454 -0
  5. data/Rakefile +71 -0
  6. data/lib/parslet/accelerator/application.rb +62 -0
  7. data/lib/parslet/accelerator/engine.rb +112 -0
  8. data/lib/parslet/accelerator.rb +162 -0
  9. data/lib/parslet/atoms/alternative.rb +53 -0
  10. data/lib/parslet/atoms/base.rb +157 -0
  11. data/lib/parslet/atoms/can_flatten.rb +137 -0
  12. data/lib/parslet/atoms/capture.rb +38 -0
  13. data/lib/parslet/atoms/context.rb +103 -0
  14. data/lib/parslet/atoms/dsl.rb +112 -0
  15. data/lib/parslet/atoms/dynamic.rb +32 -0
  16. data/lib/parslet/atoms/entity.rb +45 -0
  17. data/lib/parslet/atoms/ignored.rb +26 -0
  18. data/lib/parslet/atoms/infix.rb +115 -0
  19. data/lib/parslet/atoms/lookahead.rb +52 -0
  20. data/lib/parslet/atoms/named.rb +32 -0
  21. data/lib/parslet/atoms/re.rb +41 -0
  22. data/lib/parslet/atoms/repetition.rb +87 -0
  23. data/lib/parslet/atoms/scope.rb +26 -0
  24. data/lib/parslet/atoms/sequence.rb +48 -0
  25. data/lib/parslet/atoms/str.rb +42 -0
  26. data/lib/parslet/atoms/visitor.rb +89 -0
  27. data/lib/parslet/atoms.rb +34 -0
  28. data/lib/parslet/cause.rb +101 -0
  29. data/lib/parslet/context.rb +21 -0
  30. data/lib/parslet/convenience.rb +33 -0
  31. data/lib/parslet/error_reporter/contextual.rb +120 -0
  32. data/lib/parslet/error_reporter/deepest.rb +100 -0
  33. data/lib/parslet/error_reporter/tree.rb +63 -0
  34. data/lib/parslet/error_reporter.rb +8 -0
  35. data/lib/parslet/export.rb +163 -0
  36. data/lib/parslet/expression/treetop.rb +92 -0
  37. data/lib/parslet/expression.rb +51 -0
  38. data/lib/parslet/graphviz.rb +97 -0
  39. data/lib/parslet/parser.rb +68 -0
  40. data/lib/parslet/pattern/binding.rb +49 -0
  41. data/lib/parslet/pattern.rb +113 -0
  42. data/lib/parslet/position.rb +21 -0
  43. data/lib/parslet/rig/rspec.rb +52 -0
  44. data/lib/parslet/scope.rb +42 -0
  45. data/lib/parslet/slice.rb +105 -0
  46. data/lib/parslet/source/line_cache.rb +99 -0
  47. data/lib/parslet/source.rb +96 -0
  48. data/lib/parslet/transform.rb +265 -0
  49. data/lib/parslet/version.rb +5 -0
  50. data/lib/parslet.rb +314 -0
  51. data/plurimath-parslet.gemspec +42 -0
  52. data/spec/acceptance/infix_parser_spec.rb +145 -0
  53. data/spec/acceptance/mixing_parsers_spec.rb +74 -0
  54. data/spec/acceptance/regression_spec.rb +329 -0
  55. data/spec/acceptance/repetition_and_maybe_spec.rb +44 -0
  56. data/spec/acceptance/unconsumed_input_spec.rb +21 -0
  57. data/spec/examples/boolean_algebra_spec.rb +257 -0
  58. data/spec/examples/calc_spec.rb +278 -0
  59. data/spec/examples/capture_spec.rb +137 -0
  60. data/spec/examples/comments_spec.rb +186 -0
  61. data/spec/examples/deepest_errors_spec.rb +420 -0
  62. data/spec/examples/documentation_spec.rb +205 -0
  63. data/spec/examples/email_parser_spec.rb +275 -0
  64. data/spec/examples/empty_spec.rb +37 -0
  65. data/spec/examples/erb_spec.rb +482 -0
  66. data/spec/examples/ip_address_spec.rb +153 -0
  67. data/spec/examples/json_spec.rb +413 -0
  68. data/spec/examples/local_spec.rb +302 -0
  69. data/spec/examples/mathn_spec.rb +151 -0
  70. data/spec/examples/minilisp_spec.rb +492 -0
  71. data/spec/examples/modularity_spec.rb +340 -0
  72. data/spec/examples/nested_errors_spec.rb +322 -0
  73. data/spec/examples/optimized_erb_spec.rb +299 -0
  74. data/spec/examples/parens_spec.rb +239 -0
  75. data/spec/examples/prec_calc_spec.rb +525 -0
  76. data/spec/examples/readme_spec.rb +228 -0
  77. data/spec/examples/scopes_spec.rb +187 -0
  78. data/spec/examples/seasons_spec.rb +196 -0
  79. data/spec/examples/sentence_spec.rb +119 -0
  80. data/spec/examples/simple_xml_spec.rb +250 -0
  81. data/spec/examples/string_parser_spec.rb +407 -0
  82. data/spec/fixtures/examples/boolean_algebra.rb +62 -0
  83. data/spec/fixtures/examples/calc.rb +86 -0
  84. data/spec/fixtures/examples/capture.rb +36 -0
  85. data/spec/fixtures/examples/comments.rb +22 -0
  86. data/spec/fixtures/examples/deepest_errors.rb +99 -0
  87. data/spec/fixtures/examples/documentation.rb +32 -0
  88. data/spec/fixtures/examples/email_parser.rb +42 -0
  89. data/spec/fixtures/examples/empty.rb +10 -0
  90. data/spec/fixtures/examples/erb.rb +39 -0
  91. data/spec/fixtures/examples/ip_address.rb +103 -0
  92. data/spec/fixtures/examples/json.rb +107 -0
  93. data/spec/fixtures/examples/local.rb +60 -0
  94. data/spec/fixtures/examples/mathn.rb +47 -0
  95. data/spec/fixtures/examples/minilisp.rb +75 -0
  96. data/spec/fixtures/examples/modularity.rb +60 -0
  97. data/spec/fixtures/examples/nested_errors.rb +95 -0
  98. data/spec/fixtures/examples/optimized_erb.rb +105 -0
  99. data/spec/fixtures/examples/parens.rb +25 -0
  100. data/spec/fixtures/examples/prec_calc.rb +71 -0
  101. data/spec/fixtures/examples/readme.rb +59 -0
  102. data/spec/fixtures/examples/scopes.rb +43 -0
  103. data/spec/fixtures/examples/seasons.rb +40 -0
  104. data/spec/fixtures/examples/sentence.rb +18 -0
  105. data/spec/fixtures/examples/simple_xml.rb +51 -0
  106. data/spec/fixtures/examples/string_parser.rb +77 -0
  107. data/spec/parslet/atom_results_spec.rb +39 -0
  108. data/spec/parslet/atoms/alternative_spec.rb +26 -0
  109. data/spec/parslet/atoms/base_spec.rb +127 -0
  110. data/spec/parslet/atoms/capture_spec.rb +21 -0
  111. data/spec/parslet/atoms/combinations_spec.rb +5 -0
  112. data/spec/parslet/atoms/dsl_spec.rb +7 -0
  113. data/spec/parslet/atoms/entity_spec.rb +77 -0
  114. data/spec/parslet/atoms/ignored_spec.rb +15 -0
  115. data/spec/parslet/atoms/infix_spec.rb +5 -0
  116. data/spec/parslet/atoms/lookahead_spec.rb +22 -0
  117. data/spec/parslet/atoms/named_spec.rb +4 -0
  118. data/spec/parslet/atoms/re_spec.rb +14 -0
  119. data/spec/parslet/atoms/repetition_spec.rb +24 -0
  120. data/spec/parslet/atoms/scope_spec.rb +26 -0
  121. data/spec/parslet/atoms/sequence_spec.rb +28 -0
  122. data/spec/parslet/atoms/str_spec.rb +15 -0
  123. data/spec/parslet/atoms/visitor_spec.rb +101 -0
  124. data/spec/parslet/atoms_spec.rb +488 -0
  125. data/spec/parslet/convenience_spec.rb +54 -0
  126. data/spec/parslet/error_reporter/contextual_spec.rb +118 -0
  127. data/spec/parslet/error_reporter/deepest_spec.rb +82 -0
  128. data/spec/parslet/error_reporter/tree_spec.rb +7 -0
  129. data/spec/parslet/export_spec.rb +40 -0
  130. data/spec/parslet/expression/treetop_spec.rb +74 -0
  131. data/spec/parslet/minilisp.citrus +29 -0
  132. data/spec/parslet/minilisp.tt +29 -0
  133. data/spec/parslet/parser_spec.rb +36 -0
  134. data/spec/parslet/parslet_spec.rb +38 -0
  135. data/spec/parslet/pattern_spec.rb +272 -0
  136. data/spec/parslet/position_spec.rb +14 -0
  137. data/spec/parslet/rig/rspec_spec.rb +54 -0
  138. data/spec/parslet/scope_spec.rb +45 -0
  139. data/spec/parslet/slice_spec.rb +186 -0
  140. data/spec/parslet/source/line_cache_spec.rb +74 -0
  141. data/spec/parslet/source_spec.rb +210 -0
  142. data/spec/parslet/transform/context_spec.rb +56 -0
  143. data/spec/parslet/transform_spec.rb +183 -0
  144. data/spec/spec_helper.rb +74 -0
  145. data/spec/support/opal.rb +8 -0
  146. data/spec/support/opal.rb.erb +14 -0
  147. data/spec/support/parslet_matchers.rb +96 -0
  148. 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