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,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
|
+
|