gherkin 2.2.5-x86-mswin32

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.
Files changed (132) hide show
  1. data/.gitattributes +2 -0
  2. data/.gitignore +11 -0
  3. data/.mailmap +2 -0
  4. data/.rspec +1 -0
  5. data/.rvmrc +1 -0
  6. data/Gemfile +5 -0
  7. data/History.txt +306 -0
  8. data/LICENSE +20 -0
  9. data/README.rdoc +59 -0
  10. data/Rakefile +16 -0
  11. data/VERSION +1 -0
  12. data/bin/gherkin +5 -0
  13. data/build_native_gems.sh +8 -0
  14. data/cucumber.yml +3 -0
  15. data/features/escaped_pipes.feature +8 -0
  16. data/features/feature_parser.feature +237 -0
  17. data/features/json_formatter.feature +278 -0
  18. data/features/json_parser.feature +318 -0
  19. data/features/native_lexer.feature +19 -0
  20. data/features/parser_with_native_lexer.feature +205 -0
  21. data/features/pretty_formatter.feature +15 -0
  22. data/features/step_definitions/eyeball_steps.rb +3 -0
  23. data/features/step_definitions/gherkin_steps.rb +29 -0
  24. data/features/step_definitions/json_formatter_steps.rb +28 -0
  25. data/features/step_definitions/json_parser_steps.rb +20 -0
  26. data/features/step_definitions/pretty_formatter_steps.rb +82 -0
  27. data/features/steps_parser.feature +46 -0
  28. data/features/support/env.rb +38 -0
  29. data/gherkin.gemspec +59 -0
  30. data/ikvm/.gitignore +3 -0
  31. data/java/.gitignore +2 -0
  32. data/java/src/main/java/gherkin/lexer/i18n/.gitignore +1 -0
  33. data/java/src/main/resources/gherkin/.gitignore +1 -0
  34. data/lib/.gitignore +4 -0
  35. data/lib/gherkin.rb +2 -0
  36. data/lib/gherkin/c_lexer.rb +17 -0
  37. data/lib/gherkin/cli/main.rb +33 -0
  38. data/lib/gherkin/formatter/argument.rb +28 -0
  39. data/lib/gherkin/formatter/colors.rb +119 -0
  40. data/lib/gherkin/formatter/escaping.rb +15 -0
  41. data/lib/gherkin/formatter/filter_formatter.rb +136 -0
  42. data/lib/gherkin/formatter/json_formatter.rb +72 -0
  43. data/lib/gherkin/formatter/line_filter.rb +26 -0
  44. data/lib/gherkin/formatter/model.rb +231 -0
  45. data/lib/gherkin/formatter/monochrome_format.rb +9 -0
  46. data/lib/gherkin/formatter/pretty_formatter.rb +174 -0
  47. data/lib/gherkin/formatter/regexp_filter.rb +21 -0
  48. data/lib/gherkin/formatter/tag_count_formatter.rb +47 -0
  49. data/lib/gherkin/formatter/tag_filter.rb +19 -0
  50. data/lib/gherkin/i18n.rb +180 -0
  51. data/lib/gherkin/i18n.yml +601 -0
  52. data/lib/gherkin/json_parser.rb +88 -0
  53. data/lib/gherkin/lexer/i18n_lexer.rb +47 -0
  54. data/lib/gherkin/listener/event.rb +45 -0
  55. data/lib/gherkin/listener/formatter_listener.rb +113 -0
  56. data/lib/gherkin/native.rb +7 -0
  57. data/lib/gherkin/native/ikvm.rb +55 -0
  58. data/lib/gherkin/native/java.rb +55 -0
  59. data/lib/gherkin/native/null.rb +9 -0
  60. data/lib/gherkin/parser/meta.txt +5 -0
  61. data/lib/gherkin/parser/parser.rb +164 -0
  62. data/lib/gherkin/parser/root.txt +11 -0
  63. data/lib/gherkin/parser/steps.txt +4 -0
  64. data/lib/gherkin/rb_lexer.rb +8 -0
  65. data/lib/gherkin/rb_lexer/.gitignore +1 -0
  66. data/lib/gherkin/rb_lexer/README.rdoc +8 -0
  67. data/lib/gherkin/rubify.rb +24 -0
  68. data/lib/gherkin/tag_expression.rb +62 -0
  69. data/lib/gherkin/tools.rb +8 -0
  70. data/lib/gherkin/tools/files.rb +34 -0
  71. data/lib/gherkin/tools/reformat.rb +20 -0
  72. data/lib/gherkin/tools/stats.rb +20 -0
  73. data/lib/gherkin/tools/stats_listener.rb +60 -0
  74. data/lib/gherkin/version.rb +3 -0
  75. data/ragel/i18n/.gitignore +1 -0
  76. data/ragel/lexer.c.rl.erb +459 -0
  77. data/ragel/lexer.java.rl.erb +224 -0
  78. data/ragel/lexer.rb.rl.erb +179 -0
  79. data/ragel/lexer_common.rl.erb +50 -0
  80. data/spec/gherkin/c_lexer_spec.rb +21 -0
  81. data/spec/gherkin/fixtures/1.feature +8 -0
  82. data/spec/gherkin/fixtures/comments_in_table.feature +9 -0
  83. data/spec/gherkin/fixtures/complex.feature +45 -0
  84. data/spec/gherkin/fixtures/complex.json +143 -0
  85. data/spec/gherkin/fixtures/complex_for_filtering.feature +60 -0
  86. data/spec/gherkin/fixtures/complex_with_tags.feature +61 -0
  87. data/spec/gherkin/fixtures/dos_line_endings.feature +45 -0
  88. data/spec/gherkin/fixtures/hantu_pisang.feature +35 -0
  89. data/spec/gherkin/fixtures/i18n_fr.feature +14 -0
  90. data/spec/gherkin/fixtures/i18n_no.feature +7 -0
  91. data/spec/gherkin/fixtures/i18n_zh-CN.feature +9 -0
  92. data/spec/gherkin/fixtures/scenario_outline_with_tags.feature +13 -0
  93. data/spec/gherkin/fixtures/scenario_without_steps.feature +5 -0
  94. data/spec/gherkin/fixtures/simple_with_comments.feature +7 -0
  95. data/spec/gherkin/fixtures/simple_with_tags.feature +11 -0
  96. data/spec/gherkin/fixtures/with_bom.feature +3 -0
  97. data/spec/gherkin/formatter/argument_spec.rb +28 -0
  98. data/spec/gherkin/formatter/colors_spec.rb +18 -0
  99. data/spec/gherkin/formatter/filter_formatter_spec.rb +165 -0
  100. data/spec/gherkin/formatter/model_spec.rb +15 -0
  101. data/spec/gherkin/formatter/pretty_formatter_spec.rb +140 -0
  102. data/spec/gherkin/formatter/spaces.feature +9 -0
  103. data/spec/gherkin/formatter/tabs.feature +9 -0
  104. data/spec/gherkin/formatter/tag_count_formatter_spec.rb +30 -0
  105. data/spec/gherkin/i18n_spec.rb +149 -0
  106. data/spec/gherkin/java_lexer_spec.rb +20 -0
  107. data/spec/gherkin/json.rb +5 -0
  108. data/spec/gherkin/json_parser_spec.rb +67 -0
  109. data/spec/gherkin/lexer/i18n_lexer_spec.rb +43 -0
  110. data/spec/gherkin/output_stream_string_io.rb +24 -0
  111. data/spec/gherkin/parser/parser_spec.rb +16 -0
  112. data/spec/gherkin/rb_lexer_spec.rb +19 -0
  113. data/spec/gherkin/sexp_recorder.rb +56 -0
  114. data/spec/gherkin/shared/lexer_group.rb +592 -0
  115. data/spec/gherkin/shared/py_string_group.rb +153 -0
  116. data/spec/gherkin/shared/row_group.rb +120 -0
  117. data/spec/gherkin/shared/tags_group.rb +54 -0
  118. data/spec/gherkin/tag_expression_spec.rb +137 -0
  119. data/spec/spec_helper.rb +68 -0
  120. data/tasks/bench.rake +184 -0
  121. data/tasks/bench/feature_builder.rb +49 -0
  122. data/tasks/bench/generated/.gitignore +1 -0
  123. data/tasks/bench/null_listener.rb +4 -0
  124. data/tasks/compile.rake +102 -0
  125. data/tasks/cucumber.rake +18 -0
  126. data/tasks/gems.rake +42 -0
  127. data/tasks/ikvm.rake +54 -0
  128. data/tasks/ragel_task.rb +70 -0
  129. data/tasks/rdoc.rake +9 -0
  130. data/tasks/release.rake +30 -0
  131. data/tasks/rspec.rake +8 -0
  132. metadata +447 -0
@@ -0,0 +1,88 @@
1
+ require 'json'
2
+ require 'gherkin/formatter/model'
3
+ require 'gherkin/native'
4
+
5
+ module Gherkin
6
+ class JSONParser
7
+ native_impl('gherkin')
8
+
9
+ def initialize(formatter)
10
+ @formatter = formatter
11
+ end
12
+
13
+ # Parse a gherkin object +o+, which can either be a JSON String,
14
+ # or a Hash (from a parsed JSON String).
15
+ def parse(o, feature_uri='unknown.json', line_offset=0)
16
+ o = JSON.parse(o) if String === o
17
+ @formatter.uri(feature_uri)
18
+
19
+ Formatter::Model::Feature.new(comments(o), tags(o), keyword(o), name(o), description(o), line(o)).replay(@formatter)
20
+ (o["elements"] || []).each do |feature_element|
21
+ feature_element(feature_element).replay(@formatter)
22
+ (feature_element["steps"] || []).each do |step|
23
+ step(step).replay(@formatter)
24
+ end
25
+ (feature_element["examples"] || []).each do |eo|
26
+ Formatter::Model::Examples.new(comments(eo), tags(eo), keyword(eo), name(eo), description(eo), line(eo), rows(eo['rows'])).replay(@formatter)
27
+ end
28
+ end
29
+
30
+ @formatter.eof
31
+ end
32
+
33
+ def feature_element(o)
34
+ case o['type']
35
+ when 'background'
36
+ Formatter::Model::Background.new(comments(o), keyword(o), name(o), description(o), line(o))
37
+ when 'scenario'
38
+ Formatter::Model::Scenario.new(comments(o), tags(o), keyword(o), name(o), description(o), line(o))
39
+ when 'scenario_outline'
40
+ Formatter::Model::ScenarioOutline.new(comments(o), tags(o), keyword(o), name(o), description(o), line(o))
41
+ end
42
+ end
43
+
44
+ def step(o)
45
+ multiline_arg = nil
46
+ if(ma = o['multiline_arg'])
47
+ if(ma['type'] == 'table')
48
+ multiline_arg = rows(ma['value'])
49
+ else
50
+ multiline_arg = Formatter::Model::PyString.new(ma['value'], ma['line'])
51
+ end
52
+ end
53
+ Formatter::Model::Step.new(comments(o), keyword(o), name(o), line(o), multiline_arg)
54
+ end
55
+
56
+ def rows(o)
57
+ o.map{|row| Formatter::Model::Row.new(comments(row), row['cells'], row['line'])}
58
+ end
59
+
60
+ def comments(o)
61
+ (o['comments'] || []).map do |comment|
62
+ Formatter::Model::Comment.new(comment['value'], comment['line'])
63
+ end
64
+ end
65
+
66
+ def tags(o)
67
+ (o['tags'] || []).map do |tag|
68
+ Formatter::Model::Tag.new(tag['name'], tag['line'])
69
+ end
70
+ end
71
+
72
+ def keyword(o)
73
+ o['keyword']
74
+ end
75
+
76
+ def name(o)
77
+ o['name']
78
+ end
79
+
80
+ def description(o)
81
+ o['description']
82
+ end
83
+
84
+ def line(o)
85
+ o['line']
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,47 @@
1
+ require 'gherkin/i18n'
2
+ require 'gherkin/native'
3
+
4
+ module Gherkin
5
+ module Lexer
6
+ I18nLexerNotFound = Class.new(LoadError)
7
+ LexingError = Class.new(StandardError)
8
+
9
+ # The main entry point to lexing Gherkin source.
10
+ class I18nLexer
11
+ native_impl('gherkin')
12
+
13
+ COMMENT_OR_EMPTY_LINE_PATTERN = /^\s*#|^\s*$/
14
+ LANGUAGE_PATTERN = /^\s*#\s*language\s*:\s*([a-zA-Z\-]+)/ #:nodoc:
15
+ attr_reader :i18n_language
16
+
17
+ def initialize(listener, force_ruby=false)
18
+ @listener = listener
19
+ @force_ruby = force_ruby
20
+ end
21
+
22
+ def scan(source)
23
+ create_delegate(source).scan(source)
24
+ end
25
+
26
+ private
27
+
28
+ def create_delegate(source)
29
+ @i18n_language = lang(source)
30
+ @i18n_language.lexer(@listener, @force_ruby)
31
+ end
32
+
33
+ def lang(source)
34
+ key = 'en'
35
+ source.split(/\n/).each do |line|
36
+ break unless COMMENT_OR_EMPTY_LINE_PATTERN =~ line
37
+ if LANGUAGE_PATTERN =~ line
38
+ key = $1
39
+ break
40
+ end
41
+ end
42
+ I18n.get(key)
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,45 @@
1
+ module Gherkin
2
+ module Listener
3
+ class Event < Array
4
+ def event
5
+ self[0]
6
+ end
7
+
8
+ def keyword
9
+ self[1]
10
+ end
11
+
12
+ def line_match?(lines)
13
+ lines.include?(line)
14
+ end
15
+
16
+ def name_match?(name_regexen)
17
+ return false unless [:feature, :background, :scenario, :scenario_outline, :examples].include?(event)
18
+ name_regexen.detect{|name_regex| name =~ name_regex}
19
+ end
20
+
21
+ def replay(listener)
22
+ begin
23
+ listener.__send__(event, *args)
24
+ rescue ArgumentError => e
25
+ e.message << "\nListener: #{listener.class}, args: #{args.inspect}"
26
+ raise e
27
+ end
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,113 @@
1
+ require 'gherkin/native'
2
+ require 'gherkin/formatter/model'
3
+
4
+ module Gherkin
5
+ module Listener
6
+ # Adapter from the "raw" Gherkin <tt>Listener</tt> API
7
+ # to the slightly more high-level <tt>Formatter</tt> API,
8
+ # which is easier to implement (less state to keep track of).
9
+ class FormatterListener
10
+ native_impl('gherkin')
11
+
12
+ def initialize(formatter)
13
+ @formatter = formatter
14
+ @comments = []
15
+ @tags = []
16
+ @table = nil
17
+ end
18
+
19
+ def comment(value, line)
20
+ @comments << Formatter::Model::Comment.new(value, line)
21
+ end
22
+
23
+ def tag(name, line)
24
+ @tags << Formatter::Model::Tag.new(name, line)
25
+ end
26
+
27
+ def feature(keyword, name, description, line)
28
+ @formatter.feature(Formatter::Model::Feature.new(grab_comments!, grab_tags!, keyword, name, description, line))
29
+ end
30
+
31
+ def background(keyword, name, description, line)
32
+ @formatter.background(Formatter::Model::Background.new(grab_comments!, keyword, name, description, line))
33
+ end
34
+
35
+ def scenario(keyword, name, description, line)
36
+ replay_step_or_examples
37
+ @formatter.scenario(Formatter::Model::Scenario.new(grab_comments!, grab_tags!, keyword, name, description, line))
38
+ end
39
+
40
+ def scenario_outline(keyword, name, description, line)
41
+ replay_step_or_examples
42
+ @formatter.scenario_outline(Formatter::Model::ScenarioOutline.new(grab_comments!, grab_tags!, keyword, name, description, line))
43
+ end
44
+
45
+ def examples(keyword, name, description, line)
46
+ replay_step_or_examples
47
+ @examples_statement = Formatter::Model::Examples.new(grab_comments!, grab_tags!, keyword, name, description, line)
48
+ end
49
+
50
+ def step(keyword, name, line)
51
+ replay_step_or_examples
52
+ @step_statement = Formatter::Model::Step.new(grab_comments!, keyword, name, line)
53
+ end
54
+
55
+ def row(cells, line)
56
+ @table ||= []
57
+ @table << Formatter::Model::Row.new(grab_comments!, cells, line)
58
+ end
59
+
60
+ def py_string(string, line)
61
+ @py_string = Formatter::Model::PyString.new(string, line)
62
+ end
63
+
64
+ def eof
65
+ replay_step_or_examples
66
+ @formatter.eof
67
+ end
68
+
69
+ def syntax_error(state, ev, legal_events, uri, line)
70
+ @formatter.syntax_error(state, ev, legal_events, uri, line)
71
+ end
72
+
73
+ private
74
+
75
+ def grab_comments!
76
+ comments = @comments
77
+ @comments = []
78
+ comments
79
+ end
80
+
81
+ def grab_tags!
82
+ tags = @tags
83
+ @tags = []
84
+ tags
85
+ end
86
+
87
+ def grab_rows!
88
+ table = @table
89
+ @table = nil
90
+ table
91
+ end
92
+
93
+ def grab_py_string!
94
+ py_string = @py_string
95
+ @py_string = nil
96
+ py_string
97
+ end
98
+
99
+ def replay_step_or_examples
100
+ if(@step_statement)
101
+ @step_statement.multiline_arg = grab_py_string! || grab_rows!
102
+ @formatter.step(@step_statement)
103
+ @step_statement = nil
104
+ end
105
+ if(@examples_statement)
106
+ @examples_statement.rows = grab_rows!
107
+ @formatter.examples(@examples_statement)
108
+ @examples_statement = nil
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,7 @@
1
+ if defined?(RUBY_ENGINE) && RUBY_ENGINE == "ironruby"
2
+ require 'gherkin/native/ikvm'
3
+ elsif defined?(JRUBY_VERSION)
4
+ require 'gherkin/native/java'
5
+ else
6
+ require 'gherkin/native/null'
7
+ 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,55 @@
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
+ begin
30
+ java_class.new(*javaify(args))
31
+ rescue ArgumentError => e
32
+ e.message << "\n#{java_class.name}"
33
+ raise e
34
+ rescue NameError => e
35
+ e.message << "\n args: #{args.inspect}"
36
+ raise e
37
+ end
38
+ end
39
+
40
+ def ===(object)
41
+ super || object.java_kind_of?(java_class)
42
+ end
43
+
44
+ def java_class
45
+ names = self.name.split('::')
46
+ package = Java
47
+ names[0..-2].each do |module_name|
48
+ package = package.__send__(module_name.downcase)
49
+ end
50
+
51
+ package.__send__(names[-1])
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,9 @@
1
+ class Class
2
+ def implements(java_class_name)
3
+ # no-op
4
+ end
5
+
6
+ def native_impl(lib)
7
+ # no-op
8
+ end
9
+ 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,164 @@
1
+ require 'gherkin/i18n'
2
+ require 'gherkin/lexer/i18n_lexer'
3
+ require 'gherkin/native'
4
+ require 'gherkin/listener/formatter_listener'
5
+
6
+ module Gherkin
7
+ module Parser
8
+ class ParseError < StandardError
9
+ def initialize(state, new_state, expected_states, uri, line)
10
+ super("Parse error at #{uri}:#{line}. Found #{new_state} when expecting one of: #{expected_states.join(', ')}. (Current state: #{state}).")
11
+ end
12
+ end
13
+
14
+ class Parser
15
+ native_impl('gherkin')
16
+
17
+ # Initialize the parser. +machine_name+ refers to a state machine table.
18
+ def initialize(formatter, raise_on_error=true, machine_name='root', force_ruby=false)
19
+ @formatter = formatter
20
+ @listener = Listener::FormatterListener.new(@formatter)
21
+ @raise_on_error = raise_on_error
22
+ @machine_name = machine_name
23
+ @machines = []
24
+ push_machine(@machine_name)
25
+ @lexer = Gherkin::Lexer::I18nLexer.new(self, force_ruby)
26
+ end
27
+
28
+ def parse(gherkin, feature_uri, line_offset)
29
+ @formatter.uri(feature_uri)
30
+ @line_offset = line_offset
31
+ @lexer.scan(gherkin)
32
+ end
33
+
34
+ def i18n_language
35
+ @lexer.i18n_language
36
+ end
37
+
38
+ def errors
39
+ @lexer.errors
40
+ end
41
+
42
+ # Doesn't yet fall back to super
43
+ def method_missing(method, *args)
44
+ # TODO: Catch exception and call super
45
+ event(method.to_s, args[-1])
46
+ @listener.__send__(method, *args)
47
+ if method == :eof
48
+ pop_machine
49
+ push_machine(@machine_name)
50
+ end
51
+ end
52
+
53
+ def event(ev, line)
54
+ l = line ? @line_offset+line : nil
55
+ machine.event(ev, l) do |state, legal_events|
56
+ if @raise_on_error
57
+ raise ParseError.new(state, ev, legal_events, @feature_uri, l)
58
+ else
59
+ # Only used for testing
60
+ @listener.syntax_error(state, ev, legal_events, @feature_uri, l)
61
+ end
62
+ end
63
+ end
64
+
65
+ def push_machine(name)
66
+ @machines.push(Machine.new(self, name))
67
+ end
68
+
69
+ def pop_machine
70
+ @machines.pop
71
+ end
72
+
73
+ def machine
74
+ @machines[-1]
75
+ end
76
+
77
+ def expected
78
+ machine.expected
79
+ end
80
+
81
+ def force_state(state)
82
+ machine.instance_variable_set('@state', state)
83
+ end
84
+
85
+ class Machine
86
+ def initialize(parser, name)
87
+ @parser = parser
88
+ @name = name
89
+ @transition_map = transition_map(name)
90
+ @state = name
91
+ end
92
+
93
+ def event(ev, line)
94
+ states = @transition_map[@state]
95
+ raise "Unknown state: #{@state.inspect} for machine #{@name}" if states.nil?
96
+ new_state = states[ev]
97
+ case new_state
98
+ when "E"
99
+ yield @state, expected
100
+ when /push\((.+)\)/
101
+ @parser.push_machine($1)
102
+ @parser.event(ev, line)
103
+ when "pop()"
104
+ @parser.pop_machine()
105
+ @parser.event(ev, line)
106
+ else
107
+ raise "Unknown transition: #{ev.inspect} among #{states.inspect} for machine #{@name}" if new_state.nil?
108
+ @state = new_state
109
+ end
110
+ end
111
+
112
+ def expected
113
+ allowed = @transition_map[@state].find_all { |_, action| action != "E" }
114
+ allowed.collect { |state| state[0] }.sort - ['eof']
115
+ end
116
+
117
+ private
118
+
119
+ @@transition_maps = {}
120
+
121
+ def transition_map(name)
122
+ @@transition_maps[name] ||= build_transition_map(name)
123
+ end
124
+
125
+ def build_transition_map(name)
126
+ table = transition_table(name)
127
+ events = table.shift[1..-1]
128
+ table.inject({}) do |machine, actions|
129
+ state = actions.shift
130
+ machine[state] = Hash[*events.zip(actions).flatten]
131
+ machine
132
+ end
133
+ end
134
+
135
+ def transition_table(name)
136
+ state_machine_reader = StateMachineReader.new
137
+ lexer = Gherkin::I18n.new('en').lexer(state_machine_reader)
138
+ machine = File.dirname(__FILE__) + "/#{name}.txt"
139
+ lexer.scan(File.read(machine))
140
+ state_machine_reader.rows
141
+ end
142
+
143
+ class StateMachineReader
144
+ attr_reader :rows
145
+
146
+ def initialize
147
+ @rows = []
148
+ end
149
+
150
+ def uri(uri)
151
+ end
152
+
153
+ def row(row, line_number)
154
+ @rows << row
155
+ end
156
+
157
+ def eof
158
+ end
159
+ end
160
+
161
+ end
162
+ end
163
+ end
164
+ end