mustermann19 0.3.1
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.
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/.travis.yml +10 -0
- data/.yardopts +1 -0
- data/Gemfile +2 -0
- data/LICENSE +22 -0
- data/README.md +1081 -0
- data/Rakefile +6 -0
- data/bench/capturing.rb +57 -0
- data/bench/regexp.rb +21 -0
- data/bench/simple_vs_sinatra.rb +23 -0
- data/bench/template_vs_addressable.rb +26 -0
- data/internals.md +64 -0
- data/lib/mustermann.rb +61 -0
- data/lib/mustermann/ast/compiler.rb +168 -0
- data/lib/mustermann/ast/expander.rb +134 -0
- data/lib/mustermann/ast/node.rb +160 -0
- data/lib/mustermann/ast/parser.rb +137 -0
- data/lib/mustermann/ast/pattern.rb +84 -0
- data/lib/mustermann/ast/transformer.rb +129 -0
- data/lib/mustermann/ast/translator.rb +108 -0
- data/lib/mustermann/ast/tree_renderer.rb +29 -0
- data/lib/mustermann/ast/validation.rb +43 -0
- data/lib/mustermann/caster.rb +117 -0
- data/lib/mustermann/equality_map.rb +48 -0
- data/lib/mustermann/error.rb +6 -0
- data/lib/mustermann/expander.rb +206 -0
- data/lib/mustermann/extension.rb +52 -0
- data/lib/mustermann/identity.rb +19 -0
- data/lib/mustermann/mapper.rb +98 -0
- data/lib/mustermann/pattern.rb +182 -0
- data/lib/mustermann/rails.rb +17 -0
- data/lib/mustermann/regexp_based.rb +30 -0
- data/lib/mustermann/regular.rb +26 -0
- data/lib/mustermann/router.rb +9 -0
- data/lib/mustermann/router/rack.rb +50 -0
- data/lib/mustermann/router/simple.rb +144 -0
- data/lib/mustermann/shell.rb +29 -0
- data/lib/mustermann/simple.rb +38 -0
- data/lib/mustermann/simple_match.rb +30 -0
- data/lib/mustermann/sinatra.rb +22 -0
- data/lib/mustermann/template.rb +48 -0
- data/lib/mustermann/to_pattern.rb +45 -0
- data/lib/mustermann/version.rb +3 -0
- data/mustermann.gemspec +31 -0
- data/spec/expander_spec.rb +105 -0
- data/spec/extension_spec.rb +296 -0
- data/spec/identity_spec.rb +83 -0
- data/spec/mapper_spec.rb +83 -0
- data/spec/mustermann_spec.rb +65 -0
- data/spec/pattern_spec.rb +49 -0
- data/spec/rails_spec.rb +522 -0
- data/spec/regexp_based_spec.rb +8 -0
- data/spec/regular_spec.rb +36 -0
- data/spec/router/rack_spec.rb +39 -0
- data/spec/router/simple_spec.rb +32 -0
- data/spec/shell_spec.rb +109 -0
- data/spec/simple_match_spec.rb +10 -0
- data/spec/simple_spec.rb +237 -0
- data/spec/sinatra_spec.rb +574 -0
- data/spec/support.rb +5 -0
- data/spec/support/coverage.rb +16 -0
- data/spec/support/env.rb +15 -0
- data/spec/support/expand_matcher.rb +27 -0
- data/spec/support/match_matcher.rb +39 -0
- data/spec/support/pattern.rb +39 -0
- data/spec/template_spec.rb +815 -0
- data/spec/to_pattern_spec.rb +20 -0
- metadata +301 -0
@@ -0,0 +1,160 @@
|
|
1
|
+
module Mustermann
|
2
|
+
# @see Mustermann::AST::Pattern
|
3
|
+
module AST
|
4
|
+
# @!visibility private
|
5
|
+
class Node
|
6
|
+
# @!visibility private
|
7
|
+
attr_accessor :payload
|
8
|
+
|
9
|
+
# @!visibility private
|
10
|
+
# @param [Symbol] name of the node
|
11
|
+
# @return [Class] factory for the node
|
12
|
+
def self.[](name)
|
13
|
+
@names ||= {}
|
14
|
+
#@names.fetch(name) { Object.const_get(constant_name(name)) }
|
15
|
+
@names.fetch(name) do
|
16
|
+
const_name = constant_name(name)
|
17
|
+
const_name.split("::").inject(Object){|current, const| current.const_get(const) }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# @!visibility private
|
22
|
+
# @param [Symbol] name of the node
|
23
|
+
# @return [String] qualified name of factory for the node
|
24
|
+
def self.constant_name(name)
|
25
|
+
return self.name if name.to_sym == :node
|
26
|
+
name = name.to_s.split(?_).map(&:capitalize).join
|
27
|
+
"#{self.name}::#{name}"
|
28
|
+
end
|
29
|
+
|
30
|
+
# Helper for creating a new instance and calling #parse on it.
|
31
|
+
# @return [Mustermann::AST::Node]
|
32
|
+
# @!visibility private
|
33
|
+
def self.parse(*args, &block)
|
34
|
+
new(*args).tap { |n| n.parse(&block) }
|
35
|
+
end
|
36
|
+
|
37
|
+
# @!visibility private
|
38
|
+
def initialize(payload = nil, options = {})
|
39
|
+
options, payload = payload, nil if payload.is_a?(Hash)
|
40
|
+
options.each { |key, value| public_send("#{key}=", value) }
|
41
|
+
self.payload = payload
|
42
|
+
end
|
43
|
+
|
44
|
+
# Double dispatch helper for reading from the buffer into the payload.
|
45
|
+
# @!visibility private
|
46
|
+
def parse
|
47
|
+
self.payload ||= []
|
48
|
+
while element = yield
|
49
|
+
payload << element
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Loop through all nodes that don't have child nodes.
|
54
|
+
# @!visibility private
|
55
|
+
def each_leaf(&block)
|
56
|
+
return enum_for(__method__) unless block_given?
|
57
|
+
called = false
|
58
|
+
Array(payload).each do |entry|
|
59
|
+
next unless entry.respond_to? :each_leaf
|
60
|
+
entry.each_leaf(&block)
|
61
|
+
called = true
|
62
|
+
end
|
63
|
+
yield(self) unless called
|
64
|
+
end
|
65
|
+
|
66
|
+
# @!visibility private
|
67
|
+
class Capture < Node
|
68
|
+
# @see Mustermann::AST::Node#parse
|
69
|
+
# @!visibility private
|
70
|
+
def parse
|
71
|
+
self.payload ||= ""
|
72
|
+
super
|
73
|
+
end
|
74
|
+
|
75
|
+
# @!visibility private
|
76
|
+
alias_method :name, :payload
|
77
|
+
end
|
78
|
+
|
79
|
+
# @!visibility private
|
80
|
+
class Char < Node
|
81
|
+
end
|
82
|
+
|
83
|
+
# AST node for template expressions.
|
84
|
+
# @!visibility private
|
85
|
+
class Expression < Node
|
86
|
+
# @!visibility private
|
87
|
+
attr_accessor :operator
|
88
|
+
end
|
89
|
+
|
90
|
+
# @!visibility private
|
91
|
+
class Group < Node
|
92
|
+
# @!visibility private
|
93
|
+
def initialize(payload = nil, options = {})
|
94
|
+
options, payload = payload, nil if payload.is_a?(Hash)
|
95
|
+
super(Array(payload), options)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# @!visibility private
|
100
|
+
class Optional < Node
|
101
|
+
end
|
102
|
+
|
103
|
+
# @!visibility private
|
104
|
+
class Root < Node
|
105
|
+
# @!visibility private
|
106
|
+
attr_accessor :pattern
|
107
|
+
|
108
|
+
# Will trigger transform.
|
109
|
+
#
|
110
|
+
# @see Mustermann::AST::Node.parse
|
111
|
+
# @!visibility private
|
112
|
+
def self.parse(string, &block)
|
113
|
+
root = new
|
114
|
+
root.pattern = string
|
115
|
+
root.parse(&block)
|
116
|
+
root
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# @!visibility private
|
121
|
+
class Separator < Node
|
122
|
+
end
|
123
|
+
|
124
|
+
# @!visibility private
|
125
|
+
class Splat < Capture
|
126
|
+
# @see Mustermann::AST::Node::Capture#name
|
127
|
+
# @!visibility private
|
128
|
+
def name
|
129
|
+
"splat"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# @!visibility private
|
134
|
+
class NamedSplat < Splat
|
135
|
+
# @see Mustermann::AST::Node::Capture#name
|
136
|
+
# @!visibility private
|
137
|
+
alias_method :name, :payload
|
138
|
+
end
|
139
|
+
|
140
|
+
# AST node for template variables.
|
141
|
+
# @!visibility private
|
142
|
+
class Variable < Capture
|
143
|
+
# @!visibility private
|
144
|
+
attr_accessor :prefix, :explode
|
145
|
+
end
|
146
|
+
|
147
|
+
# @!visibility private
|
148
|
+
class WithLookAhead < Node
|
149
|
+
# @!visibility private
|
150
|
+
attr_accessor :head, :at_end
|
151
|
+
|
152
|
+
# @!visibility private
|
153
|
+
def initialize(payload, at_end)
|
154
|
+
self.head, *self.payload = Array(payload)
|
155
|
+
self.at_end = at_end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'mustermann/ast/node'
|
2
|
+
require 'forwardable'
|
3
|
+
require 'strscan'
|
4
|
+
|
5
|
+
module Mustermann
|
6
|
+
# @see Mustermann::AST::Pattern
|
7
|
+
module AST
|
8
|
+
# Simple, StringScanner based parser.
|
9
|
+
# @!visibility private
|
10
|
+
class Parser
|
11
|
+
# @param [String] string to be parsed
|
12
|
+
# @return [Mustermann::AST::Node] parse tree for string
|
13
|
+
# @!visibility private
|
14
|
+
def self.parse(string)
|
15
|
+
new.parse(string)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Defines another grammar rule for first character.
|
19
|
+
#
|
20
|
+
# @see Mustermann::Rails
|
21
|
+
# @see Mustermann::Sinatra
|
22
|
+
# @see Mustermann::Template
|
23
|
+
# @!visibility private
|
24
|
+
def self.on(*chars, &block)
|
25
|
+
chars.each do |char|
|
26
|
+
define_method("read %p" % char, &block)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Defines another grammar rule for a suffix.
|
31
|
+
#
|
32
|
+
# @see Mustermann::Sinatra
|
33
|
+
# @!visibility private
|
34
|
+
def self.suffix(pattern = /./, &block)
|
35
|
+
@suffix ||= []
|
36
|
+
@suffix << [pattern, block] if block
|
37
|
+
@suffix
|
38
|
+
end
|
39
|
+
|
40
|
+
# @!visibility private
|
41
|
+
attr_reader :buffer, :string
|
42
|
+
|
43
|
+
extend Forwardable
|
44
|
+
def_delegators :buffer, :eos?, :getch
|
45
|
+
|
46
|
+
# @param [String] string to be parsed
|
47
|
+
# @return [Mustermann::AST::Node] parse tree for string
|
48
|
+
# @!visibility private
|
49
|
+
def parse(string)
|
50
|
+
@string = string
|
51
|
+
@buffer = StringScanner.new(string)
|
52
|
+
node(:root, string) { read unless eos? }
|
53
|
+
end
|
54
|
+
|
55
|
+
# @example
|
56
|
+
# node(:char, 'x').compile =~ 'x' # => true
|
57
|
+
#
|
58
|
+
# @param [Symbol] type node type
|
59
|
+
# @return [Mustermann::AST::Node]
|
60
|
+
# @!visibility private
|
61
|
+
def node(type, *args, &block)
|
62
|
+
type = Node[type] unless type.respond_to? :new
|
63
|
+
block ? type.parse(*args, &block) : type.new(*args)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Create a node for a character we don't have an explicit rule for.
|
67
|
+
#
|
68
|
+
# @param [String] char the character
|
69
|
+
# @return [Mustermann::AST::Node] the node
|
70
|
+
# @!visibility private
|
71
|
+
def default_node(char)
|
72
|
+
char == ?/ ? node(:separator, char) : node(:char, char)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Reads the next element from the buffer.
|
76
|
+
# @return [Mustermann::AST::Node] next element
|
77
|
+
# @!visibility private
|
78
|
+
def read
|
79
|
+
char = getch
|
80
|
+
method = "read %p" % char
|
81
|
+
element = respond_to?(method) ? send(method, char) : default_node(char)
|
82
|
+
read_suffix(element)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Checks for a potential suffix on the buffer.
|
86
|
+
# @param [Mustermann::AST::Node] element node without suffix
|
87
|
+
# @return [Mustermann::AST::Node] node with suffix
|
88
|
+
# @!visibility private
|
89
|
+
def read_suffix(element)
|
90
|
+
self.class.suffix.inject(element) do |ele, (regexp, callback)|
|
91
|
+
next ele unless payload = scan(regexp)
|
92
|
+
instance_exec(payload, ele, &callback)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Wrapper around {StringScanner#scan} that turns strings into escaped
|
97
|
+
# regular expressions and returns a MatchData if the regexp has any
|
98
|
+
# named captures.
|
99
|
+
#
|
100
|
+
# @param [Regexp, String] regexp
|
101
|
+
# @see StringScanner#scan
|
102
|
+
# @return [String, MatchData, nil]
|
103
|
+
# @!visibility private
|
104
|
+
def scan(regexp)
|
105
|
+
regexp = Regexp.new(Regexp.escape(regexp)) unless regexp.is_a? Regexp
|
106
|
+
string = buffer.scan(regexp)
|
107
|
+
regexp.names.any? ? regexp.match(string) : string
|
108
|
+
end
|
109
|
+
|
110
|
+
# Asserts a regular expression matches what's next on the buffer.
|
111
|
+
# Will return corresponding MatchData if regexp includes named captures.
|
112
|
+
#
|
113
|
+
# @param [Regexp] regexp expected to match
|
114
|
+
# @return [String, MatchData] the match
|
115
|
+
# @raise [Mustermann::ParseError] if expectation wasn't met
|
116
|
+
# @!visibility private
|
117
|
+
def expect(regexp, options = {})
|
118
|
+
scan(regexp)|| unexpected(options)
|
119
|
+
end
|
120
|
+
|
121
|
+
# Helper for raising an exception for an unexpected character.
|
122
|
+
# Will read character from buffer if buffer is passed in.
|
123
|
+
#
|
124
|
+
# @param [String, nil] char the unexpected character
|
125
|
+
# @raise [Mustermann::ParseError, Exception]
|
126
|
+
# @!visibility private
|
127
|
+
def unexpected(char = getch, options = {})
|
128
|
+
options, char = char, getch if char.is_a?(Hash)
|
129
|
+
exception = options.fetch(:exception, ParseError)
|
130
|
+
char = "space" if char == " "
|
131
|
+
raise exception, "unexpected #{char || "end of string"} while parsing #{string.inspect}"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
#private_constant :Parser
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'mustermann/ast/parser'
|
2
|
+
require 'mustermann/ast/compiler'
|
3
|
+
require 'mustermann/ast/transformer'
|
4
|
+
require 'mustermann/ast/validation'
|
5
|
+
require 'mustermann/regexp_based'
|
6
|
+
require 'mustermann/expander'
|
7
|
+
require 'mustermann/equality_map'
|
8
|
+
|
9
|
+
module Mustermann
|
10
|
+
# @see Mustermann::AST::Pattern
|
11
|
+
module AST
|
12
|
+
# Superclass for pattern styles that parse an AST from the string pattern.
|
13
|
+
# @abstract
|
14
|
+
class Pattern < Mustermann::RegexpBased
|
15
|
+
supported_options :capture, :except, :greedy, :space_matches_plus
|
16
|
+
|
17
|
+
extend Forwardable, SingleForwardable
|
18
|
+
single_delegate on: :parser, suffix: :parser
|
19
|
+
instance_delegate %w[parser compiler transformer validation].map(&:to_sym) => 'self.class'
|
20
|
+
instance_delegate parse: :parser, transform: :transformer, validate: :validation
|
21
|
+
|
22
|
+
# @api private
|
23
|
+
# @return [#parse] parser object for pattern
|
24
|
+
# @!visibility private
|
25
|
+
def self.parser
|
26
|
+
return Parser if self == AST::Pattern
|
27
|
+
const_set :Parser, Class.new(superclass.parser) unless const_defined? :Parser, false
|
28
|
+
const_get :Parser
|
29
|
+
end
|
30
|
+
|
31
|
+
# @api private
|
32
|
+
# @return [#compile] compiler object for pattern
|
33
|
+
# @!visibility private
|
34
|
+
def self.compiler
|
35
|
+
Compiler
|
36
|
+
end
|
37
|
+
|
38
|
+
# @api private
|
39
|
+
# @return [#transform] compiler object for pattern
|
40
|
+
# @!visibility private
|
41
|
+
def self.transformer
|
42
|
+
Transformer
|
43
|
+
end
|
44
|
+
|
45
|
+
# @api private
|
46
|
+
# @return [#validate] validation object for pattern
|
47
|
+
# @!visibility private
|
48
|
+
def self.validation
|
49
|
+
Validation
|
50
|
+
end
|
51
|
+
|
52
|
+
# @!visibility private
|
53
|
+
def compile(options = {})
|
54
|
+
options[:except] &&= parse options[:except]
|
55
|
+
compiler.compile(to_ast, options)
|
56
|
+
rescue CompileError => error
|
57
|
+
error.message << ": %p" % @string
|
58
|
+
raise error
|
59
|
+
end
|
60
|
+
|
61
|
+
# Internal AST representation of pattern.
|
62
|
+
# @!visibility private
|
63
|
+
def to_ast
|
64
|
+
@ast_cache ||= EqualityMap.new
|
65
|
+
@ast_cache.fetch(@string) { validate(transform(parse(@string))) }
|
66
|
+
end
|
67
|
+
|
68
|
+
# All AST-based pattern implementations support expanding.
|
69
|
+
#
|
70
|
+
# @example (see Mustermann::Pattern#expand)
|
71
|
+
# @param (see Mustermann::Pattern#expand)
|
72
|
+
# @return (see Mustermann::Pattern#expand)
|
73
|
+
# @raise (see Mustermann::Pattern#expand)
|
74
|
+
# @see Mustermann::Pattern#expand
|
75
|
+
# @see Mustermann::Expander
|
76
|
+
def expand(values = {})
|
77
|
+
@expander ||= Mustermann::Expander.new(self)
|
78
|
+
@expander.expand(values)
|
79
|
+
end
|
80
|
+
|
81
|
+
private :compile
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'mustermann/ast/translator'
|
2
|
+
|
3
|
+
module Mustermann
|
4
|
+
module AST
|
5
|
+
# Takes a tree, turns it into an even better tree.
|
6
|
+
# @!visibility private
|
7
|
+
class Transformer < Translator
|
8
|
+
|
9
|
+
# Transforms a tree.
|
10
|
+
# @note might mutate handed in tree instead of creating a new one
|
11
|
+
# @param [Mustermann::AST::Node] tree to be transformed
|
12
|
+
# @return [Mustermann::AST::Node] transformed tree
|
13
|
+
# @!visibility private
|
14
|
+
def self.transform(tree)
|
15
|
+
new.translate(tree)
|
16
|
+
end
|
17
|
+
|
18
|
+
translate(:node) { self }
|
19
|
+
translate(:group, :root) do
|
20
|
+
self.payload = t(payload)
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
# URI expression transformations depending on operator
|
25
|
+
# @!visibility private
|
26
|
+
class ExpressionTransform < NodeTranslator
|
27
|
+
register :expression
|
28
|
+
|
29
|
+
# @!visibility private
|
30
|
+
Operator ||= Struct.new(:separator, :allow_reserved, :prefix, :parametric)
|
31
|
+
|
32
|
+
# Operators available for expressions.
|
33
|
+
# @!visibility private
|
34
|
+
OPERATORS ||= {
|
35
|
+
nil => Operator.new(?,, false, false, false), ?+ => Operator.new(?,, true, false, false),
|
36
|
+
?# => Operator.new(?,, true, ?#, false), ?. => Operator.new(?., false, ?., false),
|
37
|
+
?/ => Operator.new(?/, false, ?/, false), ?; => Operator.new(?;, false, ?;, true),
|
38
|
+
?? => Operator.new(?&, false, ??, true), ?& => Operator.new(?&, false, ?&, true)
|
39
|
+
}
|
40
|
+
|
41
|
+
# Sets operator and inserts separators in between variables.
|
42
|
+
# @!visibility private
|
43
|
+
def translate
|
44
|
+
self.operator = OPERATORS.fetch(operator) { raise CompileError, "#{operator} operator not supported" }
|
45
|
+
separator = Node[:separator].new(operator.separator)
|
46
|
+
prefix = Node[:separator].new(operator.prefix)
|
47
|
+
self.payload = Array(payload.inject { |list, element| Array(list) << t(separator) << t(element) })
|
48
|
+
payload.unshift(prefix) if operator.prefix
|
49
|
+
self
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Inserts with_look_ahead nodes wherever appropriate
|
54
|
+
# @!visibility private
|
55
|
+
class ArrayTransform < NodeTranslator
|
56
|
+
register Array
|
57
|
+
|
58
|
+
# the new array
|
59
|
+
# @!visibility private
|
60
|
+
def payload
|
61
|
+
@payload ||= []
|
62
|
+
end
|
63
|
+
|
64
|
+
# buffer for potential look ahead
|
65
|
+
# @!visibility private
|
66
|
+
def lookahead_buffer
|
67
|
+
@lookahead_buffer ||= []
|
68
|
+
end
|
69
|
+
|
70
|
+
# transform the array
|
71
|
+
# @!visibility private
|
72
|
+
def translate
|
73
|
+
each { |e| track t(e) }
|
74
|
+
payload.concat create_lookahead(lookahead_buffer, true)
|
75
|
+
end
|
76
|
+
|
77
|
+
# handle a single element from the array
|
78
|
+
# @!visibility private
|
79
|
+
def track(element)
|
80
|
+
return list_for(element) << element if lookahead_buffer.empty?
|
81
|
+
return lookahead_buffer << element if lookahead? element
|
82
|
+
|
83
|
+
lookahead = lookahead_buffer.dup
|
84
|
+
lookahead = create_lookahead(lookahead, false) if element.is_a? Node[:separator]
|
85
|
+
lookahead_buffer.clear
|
86
|
+
|
87
|
+
payload.concat(lookahead) << element
|
88
|
+
end
|
89
|
+
|
90
|
+
# turn look ahead buffer into look ahead node
|
91
|
+
# @!visibility private
|
92
|
+
def create_lookahead(elements, *args)
|
93
|
+
return elements unless elements.size > 1
|
94
|
+
[Node[:with_look_ahead].new(elements, *args)]
|
95
|
+
end
|
96
|
+
|
97
|
+
# can the given element be used in a look-ahead?
|
98
|
+
# @!visibility private
|
99
|
+
def lookahead?(element, in_lookahead = false)
|
100
|
+
case element
|
101
|
+
when Node[:char] then in_lookahead
|
102
|
+
when Node[:group] then lookahead_payload?(element.payload, in_lookahead)
|
103
|
+
when Node[:optional] then lookahead?(element.payload, true) or expect_lookahead?(element.payload)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# does the list of elements look look-ahead-ish to you?
|
108
|
+
# @!visibility private
|
109
|
+
def lookahead_payload?(payload, in_lookahead)
|
110
|
+
return unless payload[0..-2].all? { |e| lookahead?(e, in_lookahead) }
|
111
|
+
expect_lookahead?(payload.last) or lookahead?(payload.last, in_lookahead)
|
112
|
+
end
|
113
|
+
|
114
|
+
# can the current element deal with a look-ahead?
|
115
|
+
# @!visibility private
|
116
|
+
def expect_lookahead?(element)
|
117
|
+
return element.class == Node[:capture] unless element.is_a? Node[:group]
|
118
|
+
element.payload.all? { |e| expect_lookahead?(e) }
|
119
|
+
end
|
120
|
+
|
121
|
+
# helper method for deciding where to put an element for now
|
122
|
+
# @!visibility private
|
123
|
+
def list_for(element)
|
124
|
+
expect_lookahead?(element) ? lookahead_buffer : payload
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|