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,112 @@
1
+
2
+ # A mixin module that defines operations that can be called on any subclass
3
+ # of Parslet::Atoms::Base. These operations make parslets atoms chainable and
4
+ # allow combination of parslet atoms to form bigger parsers.
5
+ #
6
+ # Example:
7
+ #
8
+ # str('foo') >> str('bar')
9
+ # str('f').repeat
10
+ # any.absent? # also called The Epsilon
11
+ #
12
+ module Parslet::Atoms::DSL
13
+ # Construct a new atom that repeats the current atom min times at least and
14
+ # at most max times. max can be nil to indicate that no maximum is present.
15
+ #
16
+ # Example:
17
+ # # match any number of 'a's
18
+ # str('a').repeat
19
+ #
20
+ # # match between 1 and 3 'a's
21
+ # str('a').repeat(1,3)
22
+ #
23
+ def repeat(min=0, max=nil)
24
+ Parslet::Atoms::Repetition.new(self, min, max)
25
+ end
26
+
27
+ # Returns a new parslet atom that is only maybe present in the input. This
28
+ # is synonymous to calling #repeat(0,1). Generated tree value will be
29
+ # either nil (if atom is not present in the input) or the matched subtree.
30
+ #
31
+ # Example:
32
+ # str('foo').maybe
33
+ #
34
+ def maybe
35
+ Parslet::Atoms::Repetition.new(self, 0, 1, :maybe)
36
+ end
37
+
38
+ # Returns a new parslet atom that will not show up in the output. This
39
+ # is synonymous to calling #repeat(0,1). Generated tree value will always be
40
+ # nil.
41
+ #
42
+ # Example:
43
+ # str('foo').ignore
44
+ #
45
+ def ignore
46
+ Parslet::Atoms::Ignored.new(self)
47
+ end
48
+
49
+ # Chains two parslet atoms together as a sequence.
50
+ #
51
+ # Example:
52
+ # str('a') >> str('b')
53
+ #
54
+ def >>(parslet)
55
+ Parslet::Atoms::Sequence.new(self, parslet)
56
+ end
57
+
58
+ # Chains two parslet atoms together to express alternation. A match will
59
+ # always be attempted with the parslet on the left side first. If it doesn't
60
+ # match, the right side will be tried.
61
+ #
62
+ # Example:
63
+ # # matches either 'a' OR 'b'
64
+ # str('a') | str('b')
65
+ #
66
+ def |(parslet)
67
+ Parslet::Atoms::Alternative.new(self, parslet)
68
+ end
69
+
70
+ # Tests for absence of a parslet atom in the input stream without consuming
71
+ # it.
72
+ #
73
+ # Example:
74
+ # # Only proceed the parse if 'a' is absent.
75
+ # str('a').absent?
76
+ #
77
+ def absent?
78
+ Parslet::Atoms::Lookahead.new(self, false)
79
+ end
80
+
81
+ # Tests for presence of a parslet atom in the input stream without consuming
82
+ # it.
83
+ #
84
+ # Example:
85
+ # # Only proceed the parse if 'a' is present.
86
+ # str('a').present?
87
+ #
88
+ def present?
89
+ Parslet::Atoms::Lookahead.new(self, true)
90
+ end
91
+
92
+ # Marks a parslet atom as important for the tree output. This must be used
93
+ # to achieve meaningful output from the #parse method.
94
+ #
95
+ # Example:
96
+ # str('a').as(:b) # will produce {:b => 'a'}
97
+ #
98
+ def as(name)
99
+ Parslet::Atoms::Named.new(self, name)
100
+ end
101
+
102
+ # Captures a part of the input and stores it under the name given. This
103
+ # is very useful to create self-referential parses. A capture stores
104
+ # the result of its parse (may be complex) on a successful parse action.
105
+ #
106
+ # Example:
107
+ # str('a').capture(:b) # will store captures[:b] == 'a'
108
+ #
109
+ def capture(name)
110
+ Parslet::Atoms::Capture.new(self, name)
111
+ end
112
+ end
@@ -0,0 +1,32 @@
1
+ # Evaluates a block at parse time. The result from the block must be a parser
2
+ # (something which implements #apply). In the first case, the parser will then
3
+ # be applied to the input, creating the result.
4
+ #
5
+ # Dynamic parses are never cached.
6
+ #
7
+ # Example:
8
+ # dynamic { rand < 0.5 ? str('a') : str('b') }
9
+ #
10
+ class Parslet::Atoms::Dynamic < Parslet::Atoms::Base
11
+ attr_reader :block
12
+
13
+ def initialize(block)
14
+ @block = block
15
+ end
16
+
17
+ def cached?
18
+ false
19
+ end
20
+
21
+ def try(source, context, consume_all)
22
+ result = block.call(source, context)
23
+
24
+ # Result is a parslet atom.
25
+ return result.apply(source, context, consume_all)
26
+ end
27
+
28
+ def to_s_inner(prec)
29
+ "dynamic { ... }"
30
+ end
31
+ end
32
+
@@ -0,0 +1,45 @@
1
+ # This wraps pieces of parslet definition and gives them a name. The wrapped
2
+ # piece is lazily evaluated and cached. This has two purposes:
3
+ #
4
+ # * Avoid infinite recursion during evaluation of the definition
5
+ # * Be able to print things by their name, not by their sometimes
6
+ # complicated content.
7
+ #
8
+ # You don't normally use this directly, instead you should generate it by
9
+ # using the structuring method Parslet.rule.
10
+ #
11
+ class Parslet::Atoms::Entity < Parslet::Atoms::Base
12
+ attr_reader :name, :block
13
+ def initialize(name, label=nil, &block)
14
+ super()
15
+
16
+ @name = name
17
+ @label = label
18
+ @block = block
19
+ @parslet = nil
20
+ end
21
+
22
+ def try(source, context, consume_all)
23
+ parslet.apply(source, context, consume_all)
24
+ end
25
+
26
+ def parslet
27
+ return @parslet unless @parslet.nil?
28
+ @parslet = @block.call
29
+ raise_not_implemented if @parslet.nil?
30
+ @parslet.label = @label
31
+ @parslet
32
+ end
33
+
34
+ def to_s_inner(prec)
35
+ name.to_s.upcase
36
+ end
37
+ private
38
+ def raise_not_implemented
39
+ trace = caller.reject {|l| l =~ %r{#{Regexp.escape(__FILE__)}}} # blatantly stolen from dependencies.rb in activesupport
40
+ exception = NotImplementedError.new("rule(#{name.inspect}) { ... } returns nil. Still not implemented, but already used?")
41
+ exception.set_backtrace(trace)
42
+
43
+ raise exception
44
+ end
45
+ end
@@ -0,0 +1,26 @@
1
+ # Ignores the result of a match.
2
+ #
3
+ # Example:
4
+ #
5
+ # str('foo') # will return 'foo',
6
+ # str('foo').ignore # will return nil
7
+ #
8
+ class Parslet::Atoms::Ignored < Parslet::Atoms::Base
9
+ attr_reader :parslet
10
+ def initialize(parslet)
11
+ super()
12
+
13
+ @parslet = parslet
14
+ end
15
+
16
+ def apply(source, context, consume_all)
17
+ success, _ = result = parslet.apply(source, context, consume_all)
18
+
19
+ return result unless success
20
+ succ(nil)
21
+ end
22
+
23
+ def to_s_inner(prec)
24
+ "ignored(#{parslet.to_s(prec)})"
25
+ end
26
+ end
@@ -0,0 +1,115 @@
1
+ class Parslet::Atoms::Infix < Parslet::Atoms::Base
2
+ attr_reader :element, :operations, :reducer
3
+
4
+ def initialize(element, operations, &reducer)
5
+ super()
6
+
7
+ @element = element
8
+ @operations = operations
9
+ @reducer = reducer || lambda { |left, op, right| {l: left, o: op, r: right} }
10
+ end
11
+
12
+ def try(source, context, consume_all)
13
+ return catch(:error) {
14
+ return succ(
15
+ produce_tree(
16
+ precedence_climb(source, context, consume_all)))
17
+ }
18
+ end
19
+
20
+ # Turns an array of the form ['1', '+', ['2', '*', '3']] into a hash that
21
+ # reflects the same structure.
22
+ #
23
+ def produce_tree(ary)
24
+ return ary unless ary.kind_of? Array
25
+
26
+ left = ary.shift
27
+
28
+ until ary.empty?
29
+ op, right = ary.shift(2)
30
+
31
+ # p [left, op, right]
32
+
33
+ if right.kind_of? Array
34
+ # Subexpression -> Subhash
35
+ left = reducer.call(left, op, produce_tree(right))
36
+ else
37
+ left = reducer.call(left, op, right)
38
+ end
39
+ end
40
+
41
+ left
42
+ end
43
+
44
+ # A precedence climbing algorithm married to parslet, as described here
45
+ # http://eli.thegreenplace.net/2012/08/02/parsing-expressions-by-precedence-climbing/
46
+ #
47
+ # @note Error handling in this routine is done by throwing :error and
48
+ # as a value the error to return to parslet. This avoids cluttering
49
+ # the recursion logic here with parslet error handling.
50
+ #
51
+ def precedence_climb(source, context, consume_all, current_prec=1, needs_element=false)
52
+ result = []
53
+
54
+ # To even begin parsing an arithmetic expression, there needs to be
55
+ # at least one @element.
56
+ success, value = @element.apply(source, context, false)
57
+
58
+ unless success
59
+ throw :error, context.err(self, source, "#{@element.inspect} was expected", [value])
60
+ end
61
+
62
+ result << flatten(value, true)
63
+
64
+ # Loop until we fail on operator matching or until input runs out.
65
+ loop do
66
+ op_pos = source.bytepos
67
+ op_match, prec, assoc = match_operation(source, context, false)
68
+
69
+ # If no operator could be matched here, one of several cases
70
+ # applies:
71
+ #
72
+ # - end of file
73
+ # - end of expression
74
+ # - syntax error
75
+ #
76
+ # We abort matching the expression here.
77
+ break unless op_match
78
+
79
+ if prec >= current_prec
80
+ next_prec = (assoc == :left) ? prec+1 : prec
81
+
82
+ result << op_match
83
+ result << precedence_climb(
84
+ source, context, consume_all, next_prec, true)
85
+ else
86
+ source.bytepos = op_pos
87
+ return unwrap(result)
88
+ end
89
+ end
90
+
91
+ return unwrap(result)
92
+ end
93
+
94
+ def unwrap expr
95
+ expr.size == 1 ? expr.first : expr
96
+ end
97
+
98
+ def match_operation(source, context, consume_all)
99
+ errors = []
100
+ @operations.each do |op_atom, prec, assoc|
101
+ success, value = op_atom.apply(source, context, consume_all)
102
+ return flatten(value, true), prec, assoc if success
103
+
104
+ # assert: this was in fact an error, accumulate
105
+ errors << value
106
+ end
107
+
108
+ return nil
109
+ end
110
+
111
+ def to_s_inner(prec)
112
+ ops = @operations.map { |o, _, _| o.inspect }.join(', ')
113
+ "infix_expression(#{@element.inspect}, [#{ops}])"
114
+ end
115
+ end
@@ -0,0 +1,52 @@
1
+ # Either positive or negative lookahead, doesn't consume its input.
2
+ #
3
+ # Example:
4
+ #
5
+ # str('foo').present? # matches when the input contains 'foo', but leaves it
6
+ #
7
+ class Parslet::Atoms::Lookahead < Parslet::Atoms::Base
8
+ attr_reader :positive
9
+ attr_reader :bound_parslet
10
+
11
+ def initialize(bound_parslet, positive=true)
12
+ super()
13
+
14
+ # Model positive and negative lookahead by testing this flag.
15
+ @positive = positive
16
+ @bound_parslet = bound_parslet
17
+ end
18
+
19
+ def error_msgs
20
+ @error_msgs ||= {
21
+ :positive => ["Input should start with ", bound_parslet],
22
+ :negative => ["Input should not start with ", bound_parslet]
23
+ }
24
+ end
25
+
26
+ def try(source, context, consume_all)
27
+ rewind_pos = source.bytepos
28
+ error_pos = source.pos
29
+
30
+ success, _ = bound_parslet.apply(source, context, consume_all)
31
+
32
+ if positive
33
+ return succ(nil) if success
34
+ return context.err_at(self, source, error_msgs[:positive], error_pos)
35
+ else
36
+ return succ(nil) unless success
37
+ return context.err_at(self, source, error_msgs[:negative], error_pos)
38
+ end
39
+
40
+ # This is probably the only parslet that rewinds its input in #try.
41
+ # Lookaheads NEVER consume their input, even on success, that's why.
42
+ ensure
43
+ source.bytepos = rewind_pos
44
+ end
45
+
46
+ precedence LOOKAHEAD
47
+ def to_s_inner(prec)
48
+ @char = positive ? '&' : '!'
49
+
50
+ "#{@char}#{bound_parslet.to_s(prec)}"
51
+ end
52
+ end
@@ -0,0 +1,32 @@
1
+ # Names a match to influence tree construction.
2
+ #
3
+ # Example:
4
+ #
5
+ # str('foo') # will return 'foo',
6
+ # str('foo').as(:foo) # will return :foo => 'foo'
7
+ #
8
+ class Parslet::Atoms::Named < Parslet::Atoms::Base
9
+ attr_reader :parslet, :name
10
+ def initialize(parslet, name)
11
+ super()
12
+
13
+ @parslet, @name = parslet, name
14
+ end
15
+
16
+ def apply(source, context, consume_all)
17
+ success, value = result = parslet.apply(source, context, consume_all)
18
+
19
+ return result unless success
20
+ succ(
21
+ produce_return_value(
22
+ value))
23
+ end
24
+
25
+ def to_s_inner(prec)
26
+ "#{name}:#{parslet.to_s(prec)}"
27
+ end
28
+ private
29
+ def produce_return_value(val)
30
+ { name => flatten(val, true) }
31
+ end
32
+ end
@@ -0,0 +1,41 @@
1
+ # Matches a special kind of regular expression that only ever matches one
2
+ # character at a time. Useful members of this family are: <code>character
3
+ # ranges, \\w, \\d, \\r, \\n, ...</code>
4
+ #
5
+ # Example:
6
+ #
7
+ # match('[a-z]') # matches a-z
8
+ # match('\s') # like regexps: matches space characters
9
+ #
10
+ class Parslet::Atoms::Re < Parslet::Atoms::Base
11
+ attr_reader :match, :re
12
+ def initialize(match)
13
+ super()
14
+
15
+ @match = match.to_s
16
+ @re = Regexp.new(self.match, Regexp::MULTILINE)
17
+ end
18
+
19
+ def error_msgs
20
+ @error_msgs ||= {
21
+ premature: 'Premature end of input',
22
+ failed: "Failed to match #{match.inspect[1..-2]}"
23
+ }
24
+ end
25
+
26
+ def try(source, context, consume_all)
27
+ return succ(source.consume(1)) if source.matches?(@re)
28
+
29
+ # No string could be read
30
+ return context.err(self, source, error_msgs[:premature]) \
31
+ if source.chars_left < 1
32
+
33
+ # No match
34
+ return context.err(self, source, error_msgs[:failed])
35
+ end
36
+
37
+ def to_s_inner(prec)
38
+ match.inspect[1..-2]
39
+ end
40
+ end
41
+
@@ -0,0 +1,87 @@
1
+
2
+ # Matches a parslet repeatedly.
3
+ #
4
+ # Example:
5
+ #
6
+ # str('a').repeat(1,3) # matches 'a' at least once, but at most three times
7
+ # str('a').maybe # matches 'a' if it is present in the input (repeat(0,1))
8
+ #
9
+ class Parslet::Atoms::Repetition < Parslet::Atoms::Base
10
+ attr_reader :min, :max, :parslet
11
+ def initialize(parslet, min, max, tag=:repetition)
12
+ super()
13
+
14
+ raise ArgumentError,
15
+ "Asking for zero repetitions of a parslet. (#{parslet.inspect} repeating #{min},#{max})" \
16
+ if max == 0
17
+
18
+
19
+ @parslet = parslet
20
+ @min = min
21
+ @max = max
22
+ @tag = tag
23
+ end
24
+
25
+ def error_msgs
26
+ @error_msgs ||= {
27
+ minrep: "Expected at least #{min} of #{parslet.inspect}",
28
+ unconsumed: 'Extra input after last repetition'
29
+ }
30
+ end
31
+
32
+ def try(source, context, consume_all)
33
+ occ = 0
34
+ accum = [@tag] # initialize the result array with the tag (for flattening)
35
+ start_pos = source.pos
36
+
37
+ break_on = nil
38
+ loop do
39
+ success, value = parslet.apply(source, context, false)
40
+
41
+ break_on = value
42
+ break unless success
43
+
44
+ occ += 1
45
+ accum << value
46
+
47
+ # If we're not greedy (max is defined), check if that has been reached.
48
+ return succ(accum) if max && occ>=max
49
+ end
50
+
51
+ # Last attempt to match parslet was a failure, failure reason in break_on.
52
+
53
+ # Greedy matcher has produced a failure. Check if occ (which will
54
+ # contain the number of successes) is >= min.
55
+ return context.err_at(
56
+ self,
57
+ source,
58
+ error_msgs[:minrep],
59
+ start_pos,
60
+ [break_on]) if occ < min
61
+
62
+ # consume_all is true, that means that we're inside the part of the parser
63
+ # that should consume the input completely. Repetition failing here means
64
+ # probably that we didn't.
65
+ #
66
+ # We have a special clause to create an error here because otherwise
67
+ # break_on would get thrown away. It turns out, that contains very
68
+ # interesting information in a lot of cases.
69
+ #
70
+ return context.err(
71
+ self,
72
+ source,
73
+ error_msgs[:unconsumed],
74
+ [break_on]) if consume_all && source.chars_left>0
75
+
76
+ return succ(accum)
77
+ end
78
+
79
+ precedence REPETITION
80
+ def to_s_inner(prec)
81
+ minmax = "{#{min}, #{max}}"
82
+ minmax = '?' if min == 0 && max == 1
83
+
84
+ parslet.to_s(prec) + minmax
85
+ end
86
+ end
87
+
@@ -0,0 +1,26 @@
1
+ # Starts a new scope in the parsing process. Please also see the #captures
2
+ # method.
3
+ #
4
+ class Parslet::Atoms::Scope < Parslet::Atoms::Base
5
+ attr_reader :block
6
+ def initialize(block)
7
+ super()
8
+
9
+ @block = block
10
+ end
11
+
12
+ def cached?
13
+ false
14
+ end
15
+
16
+ def apply(source, context, consume_all)
17
+ context.scope do
18
+ parslet = block.call
19
+ return parslet.apply(source, context, consume_all)
20
+ end
21
+ end
22
+
23
+ def to_s_inner(prec)
24
+ "scope { #{block.call.to_s(prec)} }"
25
+ end
26
+ end
@@ -0,0 +1,48 @@
1
+ # A sequence of parslets, matched from left to right. Denoted by '>>'
2
+ #
3
+ # Example:
4
+ #
5
+ # str('a') >> str('b') # matches 'a', then 'b'
6
+ #
7
+ class Parslet::Atoms::Sequence < Parslet::Atoms::Base
8
+ attr_reader :parslets
9
+ def initialize(*parslets)
10
+ super()
11
+
12
+ @parslets = parslets
13
+ end
14
+
15
+ def error_msgs
16
+ @error_msgs ||= {
17
+ failed: "Failed to match sequence (#{inspect})"
18
+ }
19
+ end
20
+
21
+ def >>(parslet)
22
+ self.class.new(* @parslets+[parslet])
23
+ end
24
+
25
+ def try(source, context, consume_all)
26
+ # Presize an array
27
+ result = Array.new(parslets.size + 1)
28
+ result[0] = :sequence
29
+
30
+ parslets.each_with_index do |p, idx|
31
+ child_consume_all = consume_all && (idx == parslets.size-1)
32
+ success, value = p.apply(source, context, child_consume_all)
33
+
34
+ unless success
35
+ return context.err(self, source, error_msgs[:failed], [value])
36
+ end
37
+
38
+ result[idx+1] = value
39
+ end
40
+
41
+ return succ(result)
42
+ end
43
+
44
+ precedence SEQUENCE
45
+ def to_s_inner(prec)
46
+ parslets.map { |p| p.to_s(prec) }.join(' ')
47
+ end
48
+ end
@@ -0,0 +1,42 @@
1
+ # Matches a string of characters.
2
+ #
3
+ # Example:
4
+ #
5
+ # str('foo') # matches 'foo'
6
+ #
7
+ class Parslet::Atoms::Str < Parslet::Atoms::Base
8
+ attr_reader :str
9
+ def initialize(str)
10
+ super()
11
+
12
+ @str = str.to_s
13
+ @pat = Regexp.new(Regexp.escape(str))
14
+ @len = str.size
15
+ end
16
+
17
+ def error_msgs
18
+ @error_msgs ||= {
19
+ premature: 'Premature end of input',
20
+ failed: "Expected #{str.inspect}, but got "
21
+ }
22
+ end
23
+
24
+ def try(source, context, consume_all)
25
+ return succ(source.consume(@len)) if source.matches?(@pat)
26
+
27
+ # Input ending early:
28
+ return context.err(self, source, error_msgs[:premature]) \
29
+ if source.chars_left<@len
30
+
31
+ # Expected something, but got something else instead:
32
+ error_pos = source.pos
33
+ return context.err_at(
34
+ self, source,
35
+ [error_msgs[:failed], source.consume(@len)], error_pos)
36
+ end
37
+
38
+ def to_s_inner(prec)
39
+ "'#{str}'"
40
+ end
41
+ end
42
+