gherkin 1.0.30-universal-dotnet
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/.gitattributes +2 -0
- data/.gitignore +9 -0
- data/.mailmap +2 -0
- data/History.txt +187 -0
- data/LICENSE +20 -0
- data/README.rdoc +59 -0
- data/Rakefile +58 -0
- data/VERSION.yml +5 -0
- data/bin/gherkin +5 -0
- data/cucumber.yml +3 -0
- data/features/escaped_pipes.feature +8 -0
- data/features/feature_parser.feature +226 -0
- data/features/native_lexer.feature +19 -0
- data/features/parser_with_native_lexer.feature +205 -0
- data/features/pretty_printer.feature +14 -0
- data/features/step_definitions/eyeball_steps.rb +3 -0
- data/features/step_definitions/gherkin_steps.rb +30 -0
- data/features/step_definitions/pretty_formatter_steps.rb +55 -0
- data/features/steps_parser.feature +46 -0
- data/features/support/env.rb +33 -0
- data/ikvm/.gitignore +3 -0
- data/java/.gitignore +2 -0
- data/java/src/main/java/gherkin/lexer/.gitignore +1 -0
- data/java/src/main/resources/gherkin/.gitignore +1 -0
- data/lib/.gitignore +4 -0
- data/lib/gherkin.rb +2 -0
- data/lib/gherkin/c_lexer.rb +17 -0
- data/lib/gherkin/cli/main.rb +33 -0
- data/lib/gherkin/formatter/argument.rb +27 -0
- data/lib/gherkin/formatter/colors.rb +119 -0
- data/lib/gherkin/formatter/escaping.rb +15 -0
- data/lib/gherkin/formatter/monochrome_format.rb +9 -0
- data/lib/gherkin/formatter/pretty_formatter.rb +168 -0
- data/lib/gherkin/i18n.rb +176 -0
- data/lib/gherkin/i18n.yml +588 -0
- data/lib/gherkin/i18n_lexer.rb +38 -0
- data/lib/gherkin/native.rb +7 -0
- data/lib/gherkin/native/ikvm.rb +55 -0
- data/lib/gherkin/native/java.rb +47 -0
- data/lib/gherkin/native/null.rb +9 -0
- data/lib/gherkin/parser/event.rb +45 -0
- data/lib/gherkin/parser/filter_listener.rb +199 -0
- data/lib/gherkin/parser/meta.txt +5 -0
- data/lib/gherkin/parser/parser.rb +142 -0
- data/lib/gherkin/parser/root.txt +11 -0
- data/lib/gherkin/parser/steps.txt +4 -0
- data/lib/gherkin/parser/tag_expression.rb +50 -0
- data/lib/gherkin/rb_lexer.rb +8 -0
- data/lib/gherkin/rb_lexer/.gitignore +1 -0
- data/lib/gherkin/rb_lexer/README.rdoc +8 -0
- data/lib/gherkin/rubify.rb +18 -0
- data/lib/gherkin/tools.rb +8 -0
- data/lib/gherkin/tools/files.rb +35 -0
- data/lib/gherkin/tools/reformat.rb +19 -0
- data/lib/gherkin/tools/stats.rb +21 -0
- data/lib/gherkin/tools/stats_listener.rb +57 -0
- data/ragel/i18n/.gitignore +1 -0
- data/ragel/lexer.c.rl.erb +425 -0
- data/ragel/lexer.java.rl.erb +216 -0
- data/ragel/lexer.rb.rl.erb +173 -0
- data/ragel/lexer_common.rl.erb +50 -0
- data/spec/gherkin/c_lexer_spec.rb +21 -0
- data/spec/gherkin/csharp_lexer_spec.rb +20 -0
- data/spec/gherkin/fixtures/1.feature +8 -0
- data/spec/gherkin/fixtures/comments_in_table.feature +9 -0
- data/spec/gherkin/fixtures/complex.feature +45 -0
- data/spec/gherkin/fixtures/dos_line_endings.feature +45 -0
- data/spec/gherkin/fixtures/i18n_fr.feature +14 -0
- data/spec/gherkin/fixtures/i18n_no.feature +7 -0
- data/spec/gherkin/fixtures/i18n_zh-CN.feature +9 -0
- data/spec/gherkin/fixtures/simple_with_comments.feature +7 -0
- data/spec/gherkin/fixtures/simple_with_tags.feature +11 -0
- data/spec/gherkin/fixtures/with_bom.feature +3 -0
- data/spec/gherkin/formatter/argument_spec.rb +28 -0
- data/spec/gherkin/formatter/colors_spec.rb +19 -0
- data/spec/gherkin/formatter/pretty_formatter_spec.rb +162 -0
- data/spec/gherkin/formatter/spaces.feature +9 -0
- data/spec/gherkin/formatter/tabs.feature +9 -0
- data/spec/gherkin/i18n_lexer_spec.rb +26 -0
- data/spec/gherkin/i18n_spec.rb +144 -0
- data/spec/gherkin/java_lexer_spec.rb +21 -0
- data/spec/gherkin/parser/filter_listener_spec.rb +390 -0
- data/spec/gherkin/parser/parser_spec.rb +50 -0
- data/spec/gherkin/parser/tag_expression_spec.rb +116 -0
- data/spec/gherkin/rb_lexer_spec.rb +19 -0
- data/spec/gherkin/sexp_recorder.rb +32 -0
- data/spec/gherkin/shared/lexer_spec.rb +550 -0
- data/spec/gherkin/shared/py_string_spec.rb +150 -0
- data/spec/gherkin/shared/row_spec.rb +104 -0
- data/spec/gherkin/shared/tags_spec.rb +50 -0
- data/spec/spec_helper.rb +87 -0
- data/tasks/bench.rake +188 -0
- data/tasks/bench/feature_builder.rb +49 -0
- data/tasks/bench/generated/.gitignore +1 -0
- data/tasks/bench/null_listener.rb +4 -0
- data/tasks/compile.rake +89 -0
- data/tasks/cucumber.rake +26 -0
- data/tasks/gems.rake +45 -0
- data/tasks/ikvm.rake +47 -0
- data/tasks/ragel_task.rb +70 -0
- data/tasks/rdoc.rake +12 -0
- data/tasks/release.rake +26 -0
- data/tasks/rspec.rake +15 -0
- metadata +257 -0
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'gherkin/i18n'
|
2
|
+
require 'gherkin/native'
|
3
|
+
|
4
|
+
module Gherkin
|
5
|
+
I18nLexerNotFound = Class.new(LoadError)
|
6
|
+
LexingError = Class.new(StandardError)
|
7
|
+
|
8
|
+
# The main entry point to lexing Gherkin source.
|
9
|
+
class I18nLexer
|
10
|
+
native_impl('gherkin')
|
11
|
+
|
12
|
+
LANGUAGE_PATTERN = /language\s*:\s*(.*)/ #:nodoc:
|
13
|
+
attr_reader :i18n_language
|
14
|
+
|
15
|
+
def initialize(listener, force_ruby=false)
|
16
|
+
@listener = listener
|
17
|
+
@force_ruby = force_ruby
|
18
|
+
end
|
19
|
+
|
20
|
+
def scan(source)
|
21
|
+
create_delegate(source).scan(source)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def create_delegate(source)
|
27
|
+
@i18n_language = lang(source)
|
28
|
+
@i18n_language.lexer(@listener, @force_ruby)
|
29
|
+
end
|
30
|
+
|
31
|
+
def lang(source)
|
32
|
+
line_one = source.split(/\n/)[0]
|
33
|
+
match = LANGUAGE_PATTERN.match(line_one)
|
34
|
+
I18n.get(match ? match[1] : 'en')
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
class Class
|
2
|
+
|
3
|
+
def implements(java_class_name)
|
4
|
+
m = java_class_name.split('.').inject(Object) do |mod, name|
|
5
|
+
mod = mod.const_get(name)
|
6
|
+
end
|
7
|
+
include m
|
8
|
+
end
|
9
|
+
|
10
|
+
# Causes a .NET class to be instantiated instead of the Ruby class when
|
11
|
+
# running on IronRuby. This is used to test both pure .NET and pure Ruby classes
|
12
|
+
# from the same Ruby based test suite. The .NET Class must have a package name
|
13
|
+
# that corresponds with the Ruby class.
|
14
|
+
def native_impl(lib)
|
15
|
+
begin
|
16
|
+
load_assembly(lib)
|
17
|
+
rescue LoadError => e
|
18
|
+
e.message << "\nTry this: SET MONO_PATH=#{File.expand_path(File.dirname(__FILE__) + '/../..')} (or export MONO_PATH=...)"
|
19
|
+
raise e
|
20
|
+
end
|
21
|
+
|
22
|
+
class << self
|
23
|
+
def ikvmify(arg)
|
24
|
+
if Array === arg
|
25
|
+
arg.map{|a| ikvmify(a)}
|
26
|
+
else
|
27
|
+
case(arg)
|
28
|
+
when Regexp
|
29
|
+
Object.const_get('java').const_get('util').const_get('regex').const_get('Pattern').compile(arg.source)
|
30
|
+
else
|
31
|
+
arg
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def new(*args)
|
37
|
+
ikvm_class.new(*ikvmify(args))
|
38
|
+
end
|
39
|
+
|
40
|
+
def ===(object)
|
41
|
+
super || object.java_kind_of?(java_class)
|
42
|
+
end
|
43
|
+
|
44
|
+
def ikvm_class
|
45
|
+
names = self.name.split('::')
|
46
|
+
namespace = Object
|
47
|
+
names[0..-2].each do |module_name|
|
48
|
+
namespace = namespace.const_get(module_name.downcase)
|
49
|
+
end
|
50
|
+
|
51
|
+
namespace.const_get(names[-1])
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class Class
|
2
|
+
|
3
|
+
def implements(java_class_name)
|
4
|
+
# no-op
|
5
|
+
end
|
6
|
+
|
7
|
+
# Causes a Java class to be instantiated instead of the Ruby class when
|
8
|
+
# running on JRuby. This is used to test both pure Java and pure Ruby classes
|
9
|
+
# from the same Ruby based test suite. The Java Class must have a package name
|
10
|
+
# that corresponds with the Ruby class.
|
11
|
+
def native_impl(lib)
|
12
|
+
require "#{lib}.jar"
|
13
|
+
|
14
|
+
class << self
|
15
|
+
def javaify(arg)
|
16
|
+
if Array === arg
|
17
|
+
arg.map{|a| javaify(a)}
|
18
|
+
else
|
19
|
+
case(arg)
|
20
|
+
when Regexp
|
21
|
+
java.util.regex.Pattern.compile(arg.source)
|
22
|
+
else
|
23
|
+
arg
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def new(*args)
|
29
|
+
java_class.new(*javaify(args))
|
30
|
+
end
|
31
|
+
|
32
|
+
def ===(object)
|
33
|
+
super || object.java_kind_of?(java_class)
|
34
|
+
end
|
35
|
+
|
36
|
+
def java_class
|
37
|
+
names = self.name.split('::')
|
38
|
+
package = Java
|
39
|
+
names[0..-2].each do |module_name|
|
40
|
+
package = package.__send__(module_name.downcase)
|
41
|
+
end
|
42
|
+
|
43
|
+
package.__send__(names[-1])
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Gherkin
|
2
|
+
module Parser
|
3
|
+
class Event < Array
|
4
|
+
def initialize(*args)
|
5
|
+
super
|
6
|
+
self[1] = self[1].to_a if event == :row # Special JRuby handling
|
7
|
+
end
|
8
|
+
|
9
|
+
def event
|
10
|
+
self[0]
|
11
|
+
end
|
12
|
+
|
13
|
+
def keyword
|
14
|
+
self[1]
|
15
|
+
end
|
16
|
+
|
17
|
+
def line_match?(lines)
|
18
|
+
lines.include?(line)
|
19
|
+
end
|
20
|
+
|
21
|
+
def name_match?(name_regexen)
|
22
|
+
return false unless [:feature, :background, :scenario, :scenario_outline, :examples].include?(event)
|
23
|
+
name_regexen.detect{|name_regex| name =~ name_regex}
|
24
|
+
end
|
25
|
+
|
26
|
+
def replay(listener)
|
27
|
+
listener.__send__(event, *args)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def name
|
33
|
+
self[2]
|
34
|
+
end
|
35
|
+
|
36
|
+
def line
|
37
|
+
self[-1]
|
38
|
+
end
|
39
|
+
|
40
|
+
def args
|
41
|
+
self[1..-1]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,199 @@
|
|
1
|
+
require 'gherkin/parser/event'
|
2
|
+
require 'gherkin/parser/tag_expression'
|
3
|
+
require 'gherkin/native'
|
4
|
+
|
5
|
+
module Gherkin
|
6
|
+
module Parser
|
7
|
+
# This class filters events based on filter criteria.
|
8
|
+
class FilterListener
|
9
|
+
native_impl('gherkin')
|
10
|
+
|
11
|
+
# Creates a new instance that replays events to +listener+, filtered by +filters+,
|
12
|
+
# an Array that can contain one of the following:
|
13
|
+
#
|
14
|
+
# * Line numbers (Fixnum) to filter on.
|
15
|
+
# * Name regexen (Regexp) to filter on. Matches against :feature, :background, :scenario, :scenario_outline and :examples
|
16
|
+
# * Tag expressions (String) to filter on.
|
17
|
+
#
|
18
|
+
def initialize(listener, filters)
|
19
|
+
@listener = listener
|
20
|
+
@filter_method = detect_filter(filters)
|
21
|
+
|
22
|
+
@meta_buffer = []
|
23
|
+
@feature_buffer = []
|
24
|
+
@scenario_buffer = []
|
25
|
+
@examples_buffer = []
|
26
|
+
@examples_rows_buffer = []
|
27
|
+
|
28
|
+
@feature_tags = []
|
29
|
+
@scenario_tags = []
|
30
|
+
@example_tags = []
|
31
|
+
|
32
|
+
@table_state = :step
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def method_missing(*event_args)
|
38
|
+
event = Event.new(event_args)
|
39
|
+
|
40
|
+
return event.replay(@listener) if no_filters?
|
41
|
+
|
42
|
+
case(event.event)
|
43
|
+
when :tag
|
44
|
+
@meta_buffer << event
|
45
|
+
when :comment
|
46
|
+
@meta_buffer << event
|
47
|
+
when :feature
|
48
|
+
@feature_buffer = @meta_buffer
|
49
|
+
@feature_buffer << event
|
50
|
+
@feature_tags = extract_tags
|
51
|
+
@meta_buffer = []
|
52
|
+
@feature_ok = true if filter_match?(event)
|
53
|
+
when :background
|
54
|
+
@feature_buffer += @meta_buffer
|
55
|
+
@feature_buffer << event
|
56
|
+
@meta_buffer = []
|
57
|
+
@table_state = :background
|
58
|
+
@background_ok = true if filter_match?(event)
|
59
|
+
when :scenario
|
60
|
+
replay_examples_rows_buffer
|
61
|
+
@scenario_buffer = @meta_buffer
|
62
|
+
@scenario_buffer << event
|
63
|
+
@scenario_tags = extract_tags
|
64
|
+
@example_tags = []
|
65
|
+
@meta_buffer = []
|
66
|
+
@scenario_ok = filter_match?(*@scenario_buffer) || tag_match?
|
67
|
+
@examples_ok = false
|
68
|
+
@background_ok = false
|
69
|
+
@table_state = :step
|
70
|
+
when :scenario_outline
|
71
|
+
replay_examples_rows_buffer
|
72
|
+
@scenario_buffer = @meta_buffer
|
73
|
+
@scenario_buffer << event
|
74
|
+
@scenario_tags = extract_tags
|
75
|
+
@example_tags = []
|
76
|
+
@meta_buffer = []
|
77
|
+
@scenario_ok = filter_match?(*@scenario_buffer)
|
78
|
+
@examples_ok = false
|
79
|
+
@background_ok = false
|
80
|
+
@table_state = :step
|
81
|
+
when :examples
|
82
|
+
replay_examples_rows_buffer
|
83
|
+
@examples_buffer = @meta_buffer
|
84
|
+
@examples_buffer << event
|
85
|
+
@example_tags = extract_tags
|
86
|
+
@meta_buffer = []
|
87
|
+
@examples_rows_buffer = []
|
88
|
+
@examples_ok = filter_match?(*@examples_buffer) || tag_match?
|
89
|
+
@table_state = :examples
|
90
|
+
when :step
|
91
|
+
case(@table_state)
|
92
|
+
when :background
|
93
|
+
@feature_buffer += @meta_buffer
|
94
|
+
@feature_buffer << event
|
95
|
+
@meta_buffer = []
|
96
|
+
@background_ok = true if filter_match?(event)
|
97
|
+
else
|
98
|
+
@scenario_buffer << event
|
99
|
+
@scenario_ok ||= filter_match?(*@scenario_buffer)
|
100
|
+
@table_state = :step
|
101
|
+
end
|
102
|
+
when :row
|
103
|
+
case(@table_state)
|
104
|
+
when :examples
|
105
|
+
unless header_row_already_buffered?
|
106
|
+
@examples_buffer << event
|
107
|
+
@examples_ok = true if filter_match?(*@examples_buffer)
|
108
|
+
else
|
109
|
+
@examples_rows_buffer << event if @scenario_ok || @examples_ok || @feature_ok || filter_match?(event)
|
110
|
+
end
|
111
|
+
when :step
|
112
|
+
@scenario_buffer << event
|
113
|
+
@scenario_ok ||= filter_match?(*@scenario_buffer)
|
114
|
+
when :background
|
115
|
+
@feature_buffer += @meta_buffer
|
116
|
+
@feature_buffer << event
|
117
|
+
@meta_buffer = []
|
118
|
+
else
|
119
|
+
raise "Bad table_state:#{@table_state.inspect}"
|
120
|
+
end
|
121
|
+
when :py_string
|
122
|
+
if @table_state == :background
|
123
|
+
@feature_buffer << event
|
124
|
+
@feature_ok ||= filter_match?(*@feature_buffer)
|
125
|
+
else
|
126
|
+
@scenario_buffer << event
|
127
|
+
@scenario_ok ||= filter_match?(*@scenario_buffer)
|
128
|
+
end
|
129
|
+
when :eof
|
130
|
+
replay_examples_rows_buffer
|
131
|
+
event.replay(@listener)
|
132
|
+
return
|
133
|
+
else
|
134
|
+
super
|
135
|
+
end
|
136
|
+
|
137
|
+
if @scenario_ok || @examples_ok || @feature_ok || @background_ok
|
138
|
+
replay_buffers
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def detect_filter(filters)
|
143
|
+
@filters = filters
|
144
|
+
raise "Bad filter: #{filters.inspect}" if filters.map{|filter| filter.class}.uniq.length > 1
|
145
|
+
@filter_method = case(filters[0])
|
146
|
+
when Fixnum
|
147
|
+
:line_match?
|
148
|
+
when Regexp
|
149
|
+
:name_match?
|
150
|
+
when String
|
151
|
+
TagExpression.new(filters)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def no_filters?
|
156
|
+
@filters.empty?
|
157
|
+
end
|
158
|
+
|
159
|
+
def header_row_already_buffered?
|
160
|
+
return @examples_buffer.any? && @examples_buffer[-1].event == :row
|
161
|
+
end
|
162
|
+
|
163
|
+
def filter_match?(*events)
|
164
|
+
return false unless[:name_match?, :line_match?].include?(@filter_method)
|
165
|
+
events.detect{|event| event.__send__(@filter_method, @filters)}
|
166
|
+
end
|
167
|
+
|
168
|
+
def tag_match?
|
169
|
+
return TagExpression === @filter_method && @filter_method.eval(current_tags)
|
170
|
+
end
|
171
|
+
|
172
|
+
def replay_buffers
|
173
|
+
(@feature_buffer + @scenario_buffer).each do |event|
|
174
|
+
event.replay(@listener)
|
175
|
+
end
|
176
|
+
@feature_buffer = []
|
177
|
+
@scenario_buffer = []
|
178
|
+
end
|
179
|
+
|
180
|
+
def replay_examples_rows_buffer
|
181
|
+
if @examples_rows_buffer.any?
|
182
|
+
replay_buffers
|
183
|
+
(@examples_buffer + @examples_rows_buffer).each do |event|
|
184
|
+
event.replay(@listener)
|
185
|
+
end
|
186
|
+
@examples_rows_buffer = []
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def current_tags
|
191
|
+
@feature_tags + @scenario_tags + @example_tags
|
192
|
+
end
|
193
|
+
|
194
|
+
def extract_tags
|
195
|
+
@meta_buffer.select { |event| event.event == :tag }.map { |event| event.keyword }
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
@@ -0,0 +1,5 @@
|
|
1
|
+
| | feature | background | scenario | scenario_outline | examples | step | row | py_string | eof | comment | tag |
|
2
|
+
| meta | E | E | E | E | E | E | E | E | eof | comment | tag |
|
3
|
+
| comment | pop() | pop() | pop() | pop() | pop() | pop() | pop() | pop() | eof | pop() | tag |
|
4
|
+
| tag | pop() | E | pop() | pop() | pop() | E | E | E | E | E | tag |
|
5
|
+
| eof | E | E | E | E | E | E | E | E | E | E | E |
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require 'gherkin/native'
|
2
|
+
|
3
|
+
module Gherkin
|
4
|
+
module Parser
|
5
|
+
class ParseError < StandardError
|
6
|
+
def initialize(state, new_state, expected_states, line)
|
7
|
+
super("Parse error on line #{line}. Found #{new_state} when expecting one of: #{expected_states.join(', ')}. (Current state: #{state}).")
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class Parser
|
12
|
+
native_impl('gherkin')
|
13
|
+
|
14
|
+
# Initialize the parser. +machine_name+ refers to a state machine table.
|
15
|
+
def initialize(listener, raise_on_error=true, machine_name='root')
|
16
|
+
@listener = listener
|
17
|
+
@raise_on_error = raise_on_error
|
18
|
+
@machines = []
|
19
|
+
@machine_name = machine_name
|
20
|
+
push_machine(@machine_name)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Doesn't yet fall back to super
|
24
|
+
def method_missing(method, *args)
|
25
|
+
# TODO: Catch exception and call super
|
26
|
+
if(event(method.to_s, args[-1]))
|
27
|
+
@listener.send(method, *args)
|
28
|
+
end
|
29
|
+
if method == :eof
|
30
|
+
pop_machine
|
31
|
+
push_machine(@machine_name)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def event(ev, line)
|
36
|
+
machine.event(ev, line) do |state, expected|
|
37
|
+
if @raise_on_error
|
38
|
+
raise ParseError.new(state, ev, expected, line)
|
39
|
+
else
|
40
|
+
@listener.syntax_error(state, ev, expected, line)
|
41
|
+
return false
|
42
|
+
end
|
43
|
+
end
|
44
|
+
true
|
45
|
+
end
|
46
|
+
|
47
|
+
def push_machine(name)
|
48
|
+
@machines.push(Machine.new(self, name))
|
49
|
+
end
|
50
|
+
|
51
|
+
def pop_machine
|
52
|
+
@machines.pop
|
53
|
+
end
|
54
|
+
|
55
|
+
def machine
|
56
|
+
@machines[-1]
|
57
|
+
end
|
58
|
+
|
59
|
+
def expected
|
60
|
+
machine.expected
|
61
|
+
end
|
62
|
+
|
63
|
+
def force_state(state)
|
64
|
+
machine.instance_variable_set('@state', state)
|
65
|
+
end
|
66
|
+
|
67
|
+
class Machine
|
68
|
+
def initialize(parser, name)
|
69
|
+
@parser = parser
|
70
|
+
@name = name
|
71
|
+
@transition_map = transition_map(name)
|
72
|
+
@state = name
|
73
|
+
end
|
74
|
+
|
75
|
+
def event(ev, line)
|
76
|
+
states = @transition_map[@state]
|
77
|
+
raise "Unknown state: #{@state.inspect} for machine #{@name}" if states.nil?
|
78
|
+
new_state = states[ev]
|
79
|
+
case new_state
|
80
|
+
when "E"
|
81
|
+
yield @state, expected
|
82
|
+
when /push\((.+)\)/
|
83
|
+
@parser.push_machine($1)
|
84
|
+
@parser.event(ev, line)
|
85
|
+
when "pop()"
|
86
|
+
@parser.pop_machine()
|
87
|
+
@parser.event(ev, line)
|
88
|
+
else
|
89
|
+
raise "Unknown transition: #{ev.inspect} among #{states.inspect} for machine #{@name}" if new_state.nil?
|
90
|
+
@state = new_state
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def expected
|
95
|
+
allowed = @transition_map[@state].find_all { |_, action| action != "E" }
|
96
|
+
allowed.collect { |state| state[0] }.sort - ['eof']
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
@@transition_maps = {}
|
102
|
+
|
103
|
+
def transition_map(name)
|
104
|
+
@@transition_maps[name] ||= build_transition_map(name)
|
105
|
+
end
|
106
|
+
|
107
|
+
def build_transition_map(name)
|
108
|
+
table = transition_table(name)
|
109
|
+
events = table.shift[1..-1]
|
110
|
+
table.inject({}) do |machine, actions|
|
111
|
+
state = actions.shift
|
112
|
+
machine[state] = Hash[*events.zip(actions).flatten]
|
113
|
+
machine
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def transition_table(name)
|
118
|
+
state_machine_reader = StateMachineReader.new
|
119
|
+
lexer = Gherkin::I18n.new('en').lexer(state_machine_reader)
|
120
|
+
lexer.scan(File.read(File.dirname(__FILE__) + "/#{name}.txt"))
|
121
|
+
state_machine_reader.rows
|
122
|
+
end
|
123
|
+
|
124
|
+
class StateMachineReader
|
125
|
+
attr_reader :rows
|
126
|
+
|
127
|
+
def initialize
|
128
|
+
@rows = []
|
129
|
+
end
|
130
|
+
|
131
|
+
def row(row, line_number)
|
132
|
+
@rows << row
|
133
|
+
end
|
134
|
+
|
135
|
+
def eof
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|