cucumber-cucumber-expressions 16.1.2 → 17.0.2
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 +4 -4
- data/.rubocop.yml +37 -0
- data/.rubocop_todo.yml +443 -0
- data/Gemfile +2 -1
- data/Rakefile +4 -17
- data/VERSION +1 -1
- data/cucumber-cucumber-expressions.gemspec +22 -17
- data/lib/cucumber/cucumber_expressions/argument.rb +7 -3
- data/lib/cucumber/cucumber_expressions/ast.rb +24 -59
- data/lib/cucumber/cucumber_expressions/combinatorial_generated_expression_factory.rb +6 -13
- data/lib/cucumber/cucumber_expressions/cucumber_expression.rb +17 -19
- data/lib/cucumber/cucumber_expressions/cucumber_expression_generator.rb +11 -15
- data/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb +37 -53
- data/lib/cucumber/cucumber_expressions/cucumber_expression_tokenizer.rb +13 -18
- data/lib/cucumber/cucumber_expressions/errors.rb +121 -103
- data/lib/cucumber/cucumber_expressions/expression_factory.rb +2 -0
- data/lib/cucumber/cucumber_expressions/generated_expression.rb +2 -0
- data/lib/cucumber/cucumber_expressions/group.rb +2 -0
- data/lib/cucumber/cucumber_expressions/group_builder.rb +3 -1
- data/lib/cucumber/cucumber_expressions/parameter_type.rb +14 -26
- data/lib/cucumber/cucumber_expressions/parameter_type_matcher.rb +9 -7
- data/lib/cucumber/cucumber_expressions/parameter_type_registry.rb +27 -22
- data/lib/cucumber/cucumber_expressions/regular_expression.rb +3 -2
- data/lib/cucumber/cucumber_expressions/tree_regexp.rb +8 -7
- data/spec/cucumber/cucumber_expressions/argument_spec.rb +4 -2
- data/spec/cucumber/cucumber_expressions/combinatorial_generated_expression_factory_spec.rb +41 -0
- data/spec/cucumber/cucumber_expressions/cucumber_expression_generator_spec.rb +106 -103
- data/spec/cucumber/cucumber_expressions/cucumber_expression_parser_spec.rb +3 -1
- data/spec/cucumber/cucumber_expressions/cucumber_expression_spec.rb +90 -93
- data/spec/cucumber/cucumber_expressions/cucumber_expression_tokenizer_spec.rb +4 -2
- data/spec/cucumber/cucumber_expressions/cucumber_expression_transformation_spec.rb +4 -2
- data/spec/cucumber/cucumber_expressions/custom_parameter_type_spec.rb +80 -61
- data/spec/cucumber/cucumber_expressions/expression_factory_spec.rb +3 -1
- data/spec/cucumber/cucumber_expressions/parameter_type_registry_spec.rb +44 -49
- data/spec/cucumber/cucumber_expressions/parameter_type_spec.rb +5 -2
- data/spec/cucumber/cucumber_expressions/regular_expression_spec.rb +39 -30
- data/spec/cucumber/cucumber_expressions/tree_regexp_spec.rb +49 -49
- metadata +80 -25
- data/.rspec +0 -1
- data/scripts/update-gemspec +0 -32
- data/spec/capture_warnings.rb +0 -74
- data/spec/cucumber/cucumber_expressions/combinatorial_generated_expression_factory_test.rb +0 -43
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Cucumber
|
2
4
|
module CucumberExpressions
|
3
5
|
ESCAPE_CHARACTER = '\\'
|
@@ -8,55 +10,31 @@ module Cucumber
|
|
8
10
|
END_OPTIONAL_CHARACTER = ')'
|
9
11
|
|
10
12
|
class Node
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
13
|
+
attr_reader :type, :nodes, :token, :start, :end
|
14
|
+
|
15
|
+
def initialize(type, nodes, token, start, ending)
|
16
|
+
raise 'Either nodes or token must be defined' if nodes.nil? && token.nil?
|
17
|
+
|
15
18
|
@type = type
|
16
19
|
@nodes = nodes
|
17
20
|
@token = token
|
18
21
|
@start = start
|
19
|
-
@end =
|
20
|
-
end
|
21
|
-
|
22
|
-
def type
|
23
|
-
@type
|
24
|
-
end
|
25
|
-
|
26
|
-
def nodes
|
27
|
-
@nodes
|
28
|
-
end
|
29
|
-
|
30
|
-
def token
|
31
|
-
@token
|
32
|
-
end
|
33
|
-
|
34
|
-
def start
|
35
|
-
@start
|
36
|
-
end
|
37
|
-
|
38
|
-
def end
|
39
|
-
@end
|
22
|
+
@end = ending
|
40
23
|
end
|
41
24
|
|
42
25
|
def text
|
43
|
-
if @token.nil?
|
44
|
-
|
45
|
-
end
|
26
|
+
return @nodes.map { |value| value.text }.join('') if @token.nil?
|
27
|
+
|
46
28
|
@token
|
47
29
|
end
|
48
30
|
|
49
31
|
def to_hash
|
50
32
|
hash = Hash.new
|
51
|
-
hash[
|
52
|
-
unless @nodes.nil?
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
hash["token"] = @token
|
57
|
-
end
|
58
|
-
hash["start"] = @start
|
59
|
-
hash["end"] = @end
|
33
|
+
hash['type'] = @type
|
34
|
+
hash['nodes'] = @nodes.map { |node| node.to_hash } unless @nodes.nil?
|
35
|
+
hash['token'] = @token unless @token.nil?
|
36
|
+
hash['start'] = @start
|
37
|
+
hash['end'] = @end
|
60
38
|
hash
|
61
39
|
end
|
62
40
|
end
|
@@ -70,26 +48,11 @@ module Cucumber
|
|
70
48
|
EXPRESSION = 'EXPRESSION_NODE'
|
71
49
|
end
|
72
50
|
|
73
|
-
|
74
51
|
class Token
|
75
|
-
|
76
|
-
@type, @text, @start, @end = type, text, start, _end
|
77
|
-
end
|
52
|
+
attr_reader :type, :text, :start, :end
|
78
53
|
|
79
|
-
def type
|
80
|
-
@type
|
81
|
-
end
|
82
|
-
|
83
|
-
def text
|
84
|
-
@text
|
85
|
-
end
|
86
|
-
|
87
|
-
def start
|
88
|
-
@start
|
89
|
-
end
|
90
|
-
|
91
|
-
def end
|
92
|
-
@end
|
54
|
+
def initialize(type, text, start, ending)
|
55
|
+
@type, @text, @start, @end = type, text, start, ending
|
93
56
|
end
|
94
57
|
|
95
58
|
def self.is_escape_character(codepoint)
|
@@ -102,6 +65,7 @@ module Cucumber
|
|
102
65
|
# TODO: Unicode whitespace?
|
103
66
|
return true
|
104
67
|
end
|
68
|
+
|
105
69
|
case c
|
106
70
|
when ESCAPE_CHARACTER
|
107
71
|
true
|
@@ -126,6 +90,7 @@ module Cucumber
|
|
126
90
|
# TODO: Unicode whitespace?
|
127
91
|
return TokenType::WHITE_SPACE
|
128
92
|
end
|
93
|
+
|
129
94
|
case c
|
130
95
|
when ALTERNATION_CHARACTER
|
131
96
|
TokenType::ALTERNATION
|
@@ -178,10 +143,10 @@ module Cucumber
|
|
178
143
|
|
179
144
|
def to_hash
|
180
145
|
{
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
146
|
+
'type' => @type,
|
147
|
+
'text' => @text,
|
148
|
+
'start' => @start,
|
149
|
+
'end' => @end
|
185
150
|
}
|
186
151
|
end
|
187
152
|
end
|
@@ -1,8 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require('cucumber/cucumber_expressions/generated_expression')
|
2
4
|
|
3
5
|
module Cucumber
|
4
6
|
module CucumberExpressions
|
5
|
-
|
6
7
|
class CombinatorialGeneratedExpressionFactory
|
7
8
|
def initialize(expression_template, parameter_type_combinations)
|
8
9
|
@expression_template = expression_template
|
@@ -19,9 +20,7 @@ module Cucumber
|
|
19
20
|
MAX_EXPRESSIONS = 256
|
20
21
|
|
21
22
|
def generate_permutations(generated_expressions, depth, current_parameter_types)
|
22
|
-
if generated_expressions.length >= MAX_EXPRESSIONS
|
23
|
-
return
|
24
|
-
end
|
23
|
+
return if generated_expressions.length >= MAX_EXPRESSIONS
|
25
24
|
|
26
25
|
if depth == @parameter_type_combinations.length
|
27
26
|
generated_expression = GeneratedExpression.new(@expression_template, current_parameter_types)
|
@@ -31,19 +30,13 @@ module Cucumber
|
|
31
30
|
|
32
31
|
(0...@parameter_type_combinations[depth].length).each do |i|
|
33
32
|
# Avoid recursion if no elements can be added.
|
34
|
-
if generated_expressions.length >= MAX_EXPRESSIONS
|
35
|
-
|
36
|
-
end
|
33
|
+
break if generated_expressions.length >= MAX_EXPRESSIONS
|
34
|
+
|
37
35
|
new_current_parameter_types = current_parameter_types.dup # clone
|
38
36
|
new_current_parameter_types.push(@parameter_type_combinations[depth][i])
|
39
|
-
generate_permutations(
|
40
|
-
generated_expressions,
|
41
|
-
depth + 1,
|
42
|
-
new_current_parameter_types
|
43
|
-
)
|
37
|
+
generate_permutations(generated_expressions, depth + 1, new_current_parameter_types)
|
44
38
|
end
|
45
39
|
end
|
46
40
|
end
|
47
|
-
|
48
41
|
end
|
49
42
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'cucumber/cucumber_expressions/argument'
|
2
4
|
require 'cucumber/cucumber_expressions/tree_regexp'
|
3
5
|
require 'cucumber/cucumber_expressions/errors'
|
@@ -6,8 +8,7 @@ require 'cucumber/cucumber_expressions/cucumber_expression_parser'
|
|
6
8
|
module Cucumber
|
7
9
|
module CucumberExpressions
|
8
10
|
class CucumberExpression
|
9
|
-
|
10
|
-
ESCAPE_PATTERN = /([\\^\[({$.|?*+})\]])/
|
11
|
+
ESCAPE_PATTERN = /([\\^\[({$.|?*+})\]])/.freeze
|
11
12
|
|
12
13
|
def initialize(expression, parameter_type_registry)
|
13
14
|
@expression = expression
|
@@ -62,9 +63,9 @@ module Cucumber
|
|
62
63
|
end
|
63
64
|
|
64
65
|
def rewrite_optional(node)
|
65
|
-
assert_no_parameters(node) { |
|
66
|
-
assert_no_optionals(node) { |
|
67
|
-
assert_not_empty(node) { |
|
66
|
+
assert_no_parameters(node) { |ast_node| raise ParameterIsNotAllowedInOptional.new(ast_node, @expression) }
|
67
|
+
assert_no_optionals(node) { |ast_node| raise OptionalIsNotAllowedInOptional.new(ast_node, @expression) }
|
68
|
+
assert_not_empty(node) { |ast_node| raise OptionalMayNotBeEmpty.new(ast_node, @expression) }
|
68
69
|
regex = node.nodes.map { |n| rewrite_to_regex(n) }.join('')
|
69
70
|
"(?:#{regex})?"
|
70
71
|
end
|
@@ -72,30 +73,27 @@ module Cucumber
|
|
72
73
|
def rewrite_alternation(node)
|
73
74
|
# Make sure the alternative parts aren't empty and don't contain parameter types
|
74
75
|
node.nodes.each { |alternative|
|
75
|
-
if alternative.nodes.length == 0
|
76
|
-
|
77
|
-
|
78
|
-
assert_not_empty(alternative) { |astNode| raise AlternativeMayNotExclusivelyContainOptionals.new(astNode, @expression) }
|
76
|
+
raise AlternativeMayNotBeEmpty.new(alternative, @expression) if alternative.nodes.length == 0
|
77
|
+
|
78
|
+
assert_not_empty(alternative) { |ast_node| raise AlternativeMayNotExclusivelyContainOptionals.new(ast_node, @expression) }
|
79
79
|
}
|
80
80
|
regex = node.nodes.map { |n| rewrite_to_regex(n) }.join('|')
|
81
81
|
"(?:#{regex})"
|
82
82
|
end
|
83
83
|
|
84
84
|
def rewrite_alternative(node)
|
85
|
-
node.nodes.map { |
|
85
|
+
node.nodes.map { |last_node| rewrite_to_regex(last_node) }.join('')
|
86
86
|
end
|
87
87
|
|
88
88
|
def rewrite_parameter(node)
|
89
89
|
name = node.text
|
90
90
|
parameter_type = @parameter_type_registry.lookup_by_type_name(name)
|
91
|
-
if parameter_type.nil?
|
92
|
-
|
93
|
-
end
|
91
|
+
raise UndefinedParameterTypeError.new(node, @expression, name) if parameter_type.nil?
|
92
|
+
|
94
93
|
@parameter_types.push(parameter_type)
|
95
94
|
regexps = parameter_type.regexps
|
96
|
-
if regexps.length == 1
|
97
|
-
|
98
|
-
end
|
95
|
+
return "(#{regexps[0]})" if regexps.length == 1
|
96
|
+
|
99
97
|
"((?:#{regexps.join(')|(?:')}))"
|
100
98
|
end
|
101
99
|
|
@@ -105,17 +103,17 @@ module Cucumber
|
|
105
103
|
end
|
106
104
|
|
107
105
|
def assert_not_empty(node, &raise_error)
|
108
|
-
text_nodes = node.nodes.select { |
|
106
|
+
text_nodes = node.nodes.select { |ast_node| NodeType::TEXT == ast_node.type }
|
109
107
|
raise_error.call(node) if text_nodes.length == 0
|
110
108
|
end
|
111
109
|
|
112
110
|
def assert_no_parameters(node, &raise_error)
|
113
|
-
nodes = node.nodes.select { |
|
111
|
+
nodes = node.nodes.select { |ast_node| NodeType::PARAMETER == ast_node.type }
|
114
112
|
raise_error.call(nodes[0]) if nodes.length > 0
|
115
113
|
end
|
116
114
|
|
117
115
|
def assert_no_optionals(node, &raise_error)
|
118
|
-
nodes = node.nodes.select { |
|
116
|
+
nodes = node.nodes.select { |ast_node| NodeType::OPTIONAL == ast_node.type }
|
119
117
|
raise_error.call(nodes[0]) if nodes.length > 0
|
120
118
|
end
|
121
119
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'cucumber/cucumber_expressions/parameter_type_matcher'
|
2
4
|
require 'cucumber/cucumber_expressions/generated_expression'
|
3
5
|
require 'cucumber/cucumber_expressions/combinatorial_generated_expression_factory'
|
@@ -12,16 +14,14 @@ module Cucumber
|
|
12
14
|
def generate_expressions(text)
|
13
15
|
parameter_type_combinations = []
|
14
16
|
parameter_type_matchers = create_parameter_type_matchers(text)
|
15
|
-
expression_template =
|
17
|
+
expression_template = +''
|
16
18
|
pos = 0
|
17
19
|
|
18
20
|
loop do
|
19
21
|
matching_parameter_type_matchers = []
|
20
22
|
parameter_type_matchers.each do |parameter_type_matcher|
|
21
23
|
advanced_parameter_type_matcher = parameter_type_matcher.advance_to(pos)
|
22
|
-
if advanced_parameter_type_matcher.find
|
23
|
-
matching_parameter_type_matchers.push(advanced_parameter_type_matcher)
|
24
|
-
end
|
24
|
+
matching_parameter_type_matchers.push(advanced_parameter_type_matcher) if advanced_parameter_type_matcher.find
|
25
25
|
end
|
26
26
|
|
27
27
|
if matching_parameter_type_matchers.any?
|
@@ -39,16 +39,14 @@ module Cucumber
|
|
39
39
|
# Users are most likely to want these, so they should be listed at the top.
|
40
40
|
parameter_types = []
|
41
41
|
best_parameter_type_matchers.each do |parameter_type_matcher|
|
42
|
-
unless parameter_types.include?(parameter_type_matcher.parameter_type)
|
43
|
-
parameter_types.push(parameter_type_matcher.parameter_type)
|
44
|
-
end
|
42
|
+
parameter_types.push(parameter_type_matcher.parameter_type) unless parameter_types.include?(parameter_type_matcher.parameter_type)
|
45
43
|
end
|
46
44
|
parameter_types.sort!
|
47
45
|
|
48
46
|
parameter_type_combinations.push(parameter_types)
|
49
47
|
|
50
48
|
expression_template += escape(text.slice(pos...best_parameter_type_matcher.start))
|
51
|
-
expression_template +=
|
49
|
+
expression_template += '{%s}'
|
52
50
|
|
53
51
|
pos = best_parameter_type_matcher.start + best_parameter_type_matcher.group.length
|
54
52
|
else
|
@@ -66,14 +64,12 @@ module Cucumber
|
|
66
64
|
).generate_expressions
|
67
65
|
end
|
68
66
|
|
69
|
-
|
67
|
+
private
|
70
68
|
|
71
69
|
def create_parameter_type_matchers(text)
|
72
70
|
parameter_matchers = []
|
73
71
|
@parameter_type_registry.parameter_types.each do |parameter_type|
|
74
|
-
if parameter_type.use_for_snippets
|
75
|
-
parameter_matchers += create_parameter_type_matchers2(parameter_type, text)
|
76
|
-
end
|
72
|
+
parameter_matchers += create_parameter_type_matchers2(parameter_type, text) if parameter_type.use_for_snippets
|
77
73
|
end
|
78
74
|
parameter_matchers
|
79
75
|
end
|
@@ -88,9 +84,9 @@ module Cucumber
|
|
88
84
|
|
89
85
|
def escape(s)
|
90
86
|
s.gsub(/%/, '%%')
|
91
|
-
|
92
|
-
|
93
|
-
|
87
|
+
.gsub(/\(/, '\\(')
|
88
|
+
.gsub(/{/, '\\{')
|
89
|
+
.gsub(/\//, '\\/')
|
94
90
|
end
|
95
91
|
end
|
96
92
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'cucumber/cucumber_expressions/ast'
|
2
4
|
require 'cucumber/cucumber_expressions/errors'
|
3
5
|
require 'cucumber/cucumber_expressions/cucumber_expression_tokenizer'
|
@@ -11,16 +13,16 @@ module Cucumber
|
|
11
13
|
token = tokens[current]
|
12
14
|
case token.type
|
13
15
|
when TokenType::WHITE_SPACE, TokenType::TEXT, TokenType::END_PARAMETER, TokenType::END_OPTIONAL
|
14
|
-
return 1, [Node.new(NodeType::TEXT, nil, token.text, token.start, token.end)]
|
16
|
+
return [1, [Node.new(NodeType::TEXT, nil, token.text, token.start, token.end)]]
|
15
17
|
when TokenType::ALTERNATION
|
16
18
|
raise AlternationNotAllowedInOptional.new(expression, token)
|
17
19
|
when TokenType::BEGIN_PARAMETER, TokenType::START_OF_LINE, TokenType::END_OF_LINE, TokenType::BEGIN_OPTIONAL
|
18
20
|
else
|
19
21
|
# If configured correctly this will never happen
|
20
|
-
return 0, nil
|
22
|
+
return [0, nil]
|
21
23
|
end
|
22
24
|
# If configured correctly this will never happen
|
23
|
-
return 0, nil
|
25
|
+
return [0, nil]
|
24
26
|
end
|
25
27
|
|
26
28
|
# name := whitespace | .
|
@@ -28,51 +30,40 @@ module Cucumber
|
|
28
30
|
token = tokens[current]
|
29
31
|
case token.type
|
30
32
|
when TokenType::WHITE_SPACE, TokenType::TEXT
|
31
|
-
return 1, [Node.new(NodeType::TEXT, nil, token.text, token.start, token.end)]
|
33
|
+
return [1, [Node.new(NodeType::TEXT, nil, token.text, token.start, token.end)]]
|
32
34
|
when TokenType::BEGIN_PARAMETER, TokenType::END_PARAMETER, TokenType::BEGIN_OPTIONAL, TokenType::END_OPTIONAL, TokenType::ALTERNATION
|
33
35
|
raise InvalidParameterTypeNameInNode.new(expression, token)
|
34
36
|
when TokenType::START_OF_LINE, TokenType::END_OF_LINE
|
35
37
|
# If configured correctly this will never happen
|
36
|
-
return 0, nil
|
38
|
+
return [0, nil]
|
37
39
|
else
|
38
40
|
# If configured correctly this will never happen
|
39
|
-
return 0, nil
|
41
|
+
return [0, nil]
|
40
42
|
end
|
41
43
|
end
|
42
44
|
|
43
45
|
# parameter := '{' + name* + '}'
|
44
|
-
parse_parameter = parse_between(
|
45
|
-
NodeType::PARAMETER,
|
46
|
-
TokenType::BEGIN_PARAMETER,
|
47
|
-
TokenType::END_PARAMETER,
|
48
|
-
[parse_name]
|
49
|
-
)
|
46
|
+
parse_parameter = parse_between(NodeType::PARAMETER, TokenType::BEGIN_PARAMETER, TokenType::END_PARAMETER, [parse_name])
|
50
47
|
|
51
48
|
# optional := '(' + option* + ')'
|
52
49
|
# option := optional | parameter | text
|
53
50
|
optional_sub_parsers = []
|
54
|
-
parse_optional = parse_between(
|
55
|
-
NodeType::OPTIONAL,
|
56
|
-
TokenType::BEGIN_OPTIONAL,
|
57
|
-
TokenType::END_OPTIONAL,
|
58
|
-
optional_sub_parsers
|
59
|
-
)
|
51
|
+
parse_optional = parse_between(NodeType::OPTIONAL, TokenType::BEGIN_OPTIONAL, TokenType::END_OPTIONAL, optional_sub_parsers)
|
60
52
|
optional_sub_parsers << parse_optional << parse_parameter << parse_text
|
61
53
|
|
62
54
|
# alternation := alternative* + ( '/' + alternative* )+
|
63
55
|
parse_alternative_separator = lambda do |_, tokens, current|
|
64
|
-
unless looking_at(tokens, current, TokenType::ALTERNATION)
|
65
|
-
|
66
|
-
end
|
56
|
+
return [0, nil] unless looking_at(tokens, current, TokenType::ALTERNATION)
|
57
|
+
|
67
58
|
token = tokens[current]
|
68
|
-
return 1, [Node.new(NodeType::ALTERNATIVE, nil, token.text, token.start, token.end)]
|
59
|
+
return [1, [Node.new(NodeType::ALTERNATIVE, nil, token.text, token.start, token.end)]]
|
69
60
|
end
|
70
61
|
|
71
62
|
alternative_parsers = [
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
63
|
+
parse_alternative_separator,
|
64
|
+
parse_optional,
|
65
|
+
parse_parameter,
|
66
|
+
parse_text,
|
76
67
|
]
|
77
68
|
|
78
69
|
# alternation := (?<=left-boundary) + alternative* + ( '/' + alternative* )+ + (?=right-boundary)
|
@@ -81,30 +72,26 @@ module Cucumber
|
|
81
72
|
# alternative: = optional | parameter | text
|
82
73
|
parse_alternation = lambda do |expr, tokens, current|
|
83
74
|
previous = current - 1
|
84
|
-
unless looking_at_any(tokens, previous, [TokenType::START_OF_LINE, TokenType::WHITE_SPACE, TokenType::END_PARAMETER])
|
85
|
-
return 0, nil
|
86
|
-
end
|
75
|
+
return [0, nil] unless looking_at_any(tokens, previous, [TokenType::START_OF_LINE, TokenType::WHITE_SPACE, TokenType::END_PARAMETER])
|
87
76
|
|
88
77
|
consumed, ast = parse_tokens_until(expr, alternative_parsers, tokens, current, [TokenType::WHITE_SPACE, TokenType::END_OF_LINE, TokenType::BEGIN_PARAMETER])
|
89
78
|
sub_current = current + consumed
|
90
|
-
unless ast.map { |
|
91
|
-
return 0, nil
|
92
|
-
end
|
79
|
+
return [0, nil] unless ast.map { |ast_node| ast_node.type }.include? NodeType::ALTERNATIVE
|
93
80
|
|
94
81
|
start = tokens[current].start
|
95
82
|
_end = tokens[sub_current].start
|
96
83
|
# Does not consume right hand boundary token
|
97
|
-
return consumed, [Node.new(NodeType::ALTERNATION, split_alternatives(start, _end, ast), nil, start, _end)]
|
84
|
+
return [consumed, [Node.new(NodeType::ALTERNATION, split_alternatives(start, _end, ast), nil, start, _end)]]
|
98
85
|
end
|
99
86
|
|
100
87
|
#
|
101
88
|
# cucumber-expression := ( alternation | optional | parameter | text )*
|
102
89
|
#
|
103
90
|
parse_cucumber_expression = parse_between(
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
91
|
+
NodeType::EXPRESSION,
|
92
|
+
TokenType::START_OF_LINE,
|
93
|
+
TokenType::END_OF_LINE,
|
94
|
+
[parse_alternation, parse_optional, parse_parameter, parse_text]
|
108
95
|
)
|
109
96
|
|
110
97
|
tokenizer = CucumberExpressionTokenizer.new
|
@@ -117,30 +104,28 @@ module Cucumber
|
|
117
104
|
|
118
105
|
def parse_between(type, begin_token, end_token, parsers)
|
119
106
|
lambda do |expression, tokens, current|
|
120
|
-
unless looking_at(tokens, current, begin_token)
|
121
|
-
|
122
|
-
end
|
107
|
+
return [0, nil] unless looking_at(tokens, current, begin_token)
|
108
|
+
|
123
109
|
sub_current = current + 1
|
124
110
|
consumed, ast = parse_tokens_until(expression, parsers, tokens, sub_current, [end_token, TokenType::END_OF_LINE])
|
125
111
|
sub_current += consumed
|
126
112
|
|
127
113
|
# endToken not found
|
128
|
-
unless looking_at(tokens, sub_current, end_token)
|
129
|
-
|
130
|
-
end
|
114
|
+
raise MissingEndToken.new(expression, begin_token, end_token, tokens[current]) unless looking_at(tokens, sub_current, end_token)
|
115
|
+
|
131
116
|
# consumes endToken
|
132
117
|
start = tokens[current].start
|
133
118
|
_end = tokens[sub_current].end
|
134
119
|
consumed = sub_current + 1 - current
|
135
120
|
ast = [Node.new(type, ast, nil, start, _end)]
|
136
|
-
return consumed, ast
|
121
|
+
return [consumed, ast]
|
137
122
|
end
|
138
123
|
end
|
139
124
|
|
140
125
|
def parse_token(expression, parsers, tokens, start_at)
|
141
126
|
parsers.each do |parser|
|
142
127
|
consumed, ast = parser.call(expression, tokens, start_at)
|
143
|
-
return consumed, ast unless consumed == 0
|
128
|
+
return [consumed, ast] unless consumed == 0
|
144
129
|
end
|
145
130
|
# If configured correctly this will never happen
|
146
131
|
raise 'No eligible parsers for ' + tokens
|
@@ -151,15 +136,15 @@ module Cucumber
|
|
151
136
|
size = tokens.length
|
152
137
|
ast = []
|
153
138
|
while current < size do
|
154
|
-
if looking_at_any(tokens, current, end_tokens)
|
155
|
-
|
156
|
-
end
|
139
|
+
break if looking_at_any(tokens, current, end_tokens)
|
140
|
+
|
157
141
|
consumed, sub_ast = parse_token(expression, parsers, tokens, current)
|
158
142
|
if consumed == 0
|
159
143
|
# If configured correctly this will never happen
|
160
144
|
# Keep to avoid infinite loops
|
161
145
|
raise 'No eligible parsers for ' + tokens
|
162
146
|
end
|
147
|
+
|
163
148
|
current += consumed
|
164
149
|
ast += sub_ast
|
165
150
|
end
|
@@ -176,9 +161,8 @@ module Cucumber
|
|
176
161
|
# Keep for completeness
|
177
162
|
return token == TokenType::START_OF_LINE
|
178
163
|
end
|
179
|
-
if at >= tokens.length
|
180
|
-
|
181
|
-
end
|
164
|
+
return token == TokenType::END_OF_LINE if at >= tokens.length
|
165
|
+
|
182
166
|
tokens[at].type == token
|
183
167
|
end
|
184
168
|
|
@@ -186,7 +170,7 @@ module Cucumber
|
|
186
170
|
separators = []
|
187
171
|
alternatives = []
|
188
172
|
alternative = []
|
189
|
-
alternation.each
|
173
|
+
alternation.each do |n|
|
190
174
|
if NodeType::ALTERNATIVE == n.type
|
191
175
|
separators.push(n)
|
192
176
|
alternatives.push(alternative)
|
@@ -194,7 +178,7 @@ module Cucumber
|
|
194
178
|
else
|
195
179
|
alternative.push(n)
|
196
180
|
end
|
197
|
-
|
181
|
+
end
|
198
182
|
alternatives.push(alternative)
|
199
183
|
create_alternative_nodes(start, _end, separators, alternatives)
|
200
184
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'cucumber/cucumber_expressions/ast'
|
2
4
|
require 'cucumber/cucumber_expressions/errors'
|
3
5
|
|
@@ -15,9 +17,7 @@ module Cucumber
|
|
15
17
|
|
16
18
|
codepoints = expression.codepoints
|
17
19
|
|
18
|
-
if codepoints.empty?
|
19
|
-
tokens.push(Token.new(TokenType::START_OF_LINE, '', 0, 0))
|
20
|
-
end
|
20
|
+
tokens.push(Token.new(TokenType::START_OF_LINE, '', 0, 0)) if codepoints.empty?
|
21
21
|
|
22
22
|
codepoints.each do |codepoint|
|
23
23
|
if !treat_as_text && Token.is_escape_character(codepoint)
|
@@ -63,10 +63,10 @@ module Cucumber
|
|
63
63
|
|
64
64
|
consumed_index = @buffer_start_index + @buffer.length + escape_tokens
|
65
65
|
t = Token.new(
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
66
|
+
token_type,
|
67
|
+
@buffer.map { |codepoint| codepoint.chr(Encoding::UTF_8) }.join(''),
|
68
|
+
@buffer_start_index,
|
69
|
+
consumed_index
|
70
70
|
)
|
71
71
|
@buffer = []
|
72
72
|
@buffer_start_index = consumed_index
|
@@ -74,21 +74,16 @@ module Cucumber
|
|
74
74
|
end
|
75
75
|
|
76
76
|
def token_type_of(codepoint, treat_as_text)
|
77
|
-
unless treat_as_text
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
end
|
83
|
-
raise CantEscape.new(
|
84
|
-
@expression,
|
85
|
-
@buffer_start_index + @buffer.length + @escaped
|
86
|
-
)
|
77
|
+
return Token.type_of(codepoint) unless treat_as_text
|
78
|
+
|
79
|
+
return TokenType::TEXT if Token.can_escape(codepoint)
|
80
|
+
|
81
|
+
raise CantEscape.new(@expression, @buffer_start_index + @buffer.length + @escaped)
|
87
82
|
end
|
88
83
|
|
89
84
|
def should_create_new_token?(previous_token_type, current_token_type)
|
90
85
|
current_token_type != previous_token_type ||
|
91
|
-
|
86
|
+
(current_token_type != TokenType::WHITE_SPACE && current_token_type != TokenType::TEXT)
|
92
87
|
end
|
93
88
|
end
|
94
89
|
end
|