mustermann19 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|