gherkin-ruby 0.0.2 → 0.1.0

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 CHANGED
@@ -2,3 +2,4 @@
2
2
  .bundle
3
3
  Gemfile.lock
4
4
  pkg/*
5
+ .rbx
data/Rakefile CHANGED
@@ -8,4 +8,22 @@ Rake::TestTask.new do |t|
8
8
  t.test_files = FileList['./test/**/*_test.rb']
9
9
  end
10
10
 
11
- task :default => [:test]
11
+ desc "Regenerate Gherkin-ruby lexer and parser."
12
+ task :regenerate do
13
+ has_rex = `which rex`
14
+ has_racc = `which racc`
15
+
16
+ if has_rex && has_racc
17
+ `rex lib/gherkin/parser/gherkin.rex -o lib/gherkin/parser/lexer.rb`
18
+ `racc lib/gherkin/parser/gherkin.y -o lib/gherkin/parser/parser.rb`
19
+ else
20
+ puts "You need both Rexical and Racc to do that. Install them by doing:"
21
+ puts
22
+ puts "\t\tgem install rexical"
23
+ puts "\t\tgem install racc"
24
+ puts
25
+ puts "Or just type `bundle install`."
26
+ end
27
+ end
28
+
29
+ task :default => [:regenerate, :test]
data/Readme.md CHANGED
@@ -1,10 +1,9 @@
1
1
  # gherkin-ruby
2
- Gherkin-ruby is a pure Ruby implementation of a [Gherkin](http://github.com/cucumber/gherkin) parser, using [Parslet](http://github.com/kschiess/parslet).
2
+ Gherkin-ruby is a pure Ruby implementation of a [Gherkin](http://github.com/cucumber/gherkin) parser.
3
3
 
4
4
  ## Why this one over the official, fast, Ragel-based Gherkin parser?
5
5
 
6
6
  * Less than 200 LOC.
7
- * No C-exts
8
7
  * No Java/.NET crap.
9
8
  * Fast enough for our purposes (using it for the [Spinach](http://github.com/codegram/spinach) project)
10
9
 
@@ -74,3 +73,4 @@ visitor.visit(ast)
74
73
  ## Todo
75
74
 
76
75
  * Some optimization
76
+
data/gherkin-ruby.gemspec CHANGED
@@ -13,7 +13,8 @@ Gem::Specification.new do |s|
13
13
 
14
14
  s.rubyforge_project = "gherkin-ruby"
15
15
 
16
- s.add_runtime_dependency 'parslet'
16
+ s.add_runtime_dependency 'rexical'
17
+ s.add_runtime_dependency 'racc'
17
18
  s.add_development_dependency 'minitest'
18
19
 
19
20
  s.files = `git ls-files`.split("\n")
data/lib/gherkin/ast.rb CHANGED
@@ -1,25 +1,28 @@
1
1
  module Gherkin
2
2
  module AST
3
3
  class Node
4
- attr_reader :line, :column
4
+ attr_reader :filename, :line
5
5
 
6
6
  def accept(visitor)
7
7
  name = self.class.name.split('::').last
8
8
  visitor.send("visit_#{name}".to_sym, self)
9
9
  end
10
+
11
+ def pos(filename, line=nil)
12
+ @filename, @line = filename, line
13
+ end
10
14
  end
11
15
 
12
16
  class Feature < Node
13
17
  attr_reader :name, :background, :scenarios
18
+ attr_writer :background, :scenarios
14
19
 
15
20
  include Enumerable
16
21
 
17
22
  def initialize(name, scenarios=[], background=nil)
18
- @line, @column = name.line_and_column
19
-
20
- @name = name.to_s
23
+ @name = name
21
24
  @background = background
22
- @scenarios = scenarios
25
+ @scenarios = scenarios
23
26
  end
24
27
 
25
28
  def each
@@ -29,18 +32,18 @@ module Gherkin
29
32
 
30
33
  class Background < Node
31
34
  attr_reader :steps
35
+ attr_writer :steps
32
36
 
33
37
  include Enumerable
34
38
 
35
39
  def initialize(steps=[])
36
- if steps.any?
37
- @line = steps.first.line - 1
38
- @column = 3
39
- end
40
-
41
40
  @steps = steps
42
41
  end
43
42
 
43
+ def line
44
+ @steps.first.line - 1 if @steps.any?
45
+ end
46
+
44
47
  def each
45
48
  @steps.each
46
49
  end
@@ -52,13 +55,15 @@ module Gherkin
52
55
  include Enumerable
53
56
 
54
57
  def initialize(name, steps=[], tags=[])
55
- @line, @column = name.line_and_column
56
-
57
58
  @name = name.to_s
58
59
  @steps = steps
59
60
  @tags = tags
60
61
  end
61
62
 
63
+ def line
64
+ @steps.first.line - 1 if @steps.any?
65
+ end
66
+
62
67
  def each
63
68
  @steps.each
64
69
  end
@@ -67,8 +72,6 @@ module Gherkin
67
72
  class Step < Node
68
73
  attr_reader :name, :keyword
69
74
  def initialize(name, keyword)
70
- @line, @column = name.line_and_column
71
-
72
75
  @name = name.to_s
73
76
  @keyword = keyword.to_s
74
77
  end
@@ -77,9 +80,7 @@ module Gherkin
77
80
  class Tag < Node
78
81
  attr_reader :name
79
82
  def initialize(name)
80
- @line, @column = name.line_and_column
81
-
82
- @name = name.to_s
83
+ @name = name
83
84
  end
84
85
  end
85
86
  end
@@ -0,0 +1,44 @@
1
+ # Compile with: rex gherkin.rex -o lexer.rb
2
+
3
+ class Gherkin::Parser
4
+
5
+ macro
6
+ BLANK [\ \t]+
7
+
8
+ rule
9
+ # Whitespace
10
+ {BLANK} # no action
11
+ \#.*$
12
+
13
+ # Literals
14
+ \n { [:NEWLINE, text] }
15
+
16
+ # Keywords
17
+ Feature: { [:FEATURE, text[0..-2]] }
18
+ Background: { [:BACKGROUND, text[0..-2]] }
19
+ Scenario: { [:SCENARIO, text[0..-2]] }
20
+
21
+ # Tags
22
+ @\w+ { [:TAG, text[1..-1]] }
23
+
24
+ # Step keywords
25
+ Given { [:GIVEN, text] }
26
+ When { [:WHEN, text] }
27
+ Then { [:THEN, text] }
28
+ And { [:AND, text] }
29
+ But { [:BUT, text] }
30
+
31
+ # Text
32
+ [^#\n]* { [:TEXT, text.strip] }
33
+
34
+ inner
35
+ def tokenize(code)
36
+ scan_setup(code)
37
+ tokens = []
38
+ while token = next_token
39
+ tokens << token
40
+ end
41
+ tokens
42
+ end
43
+
44
+ end
@@ -0,0 +1,100 @@
1
+ # Compile with: racc gherkin.y -o parser.rb
2
+
3
+ class Gherkin::Parser
4
+
5
+ # Declare tokens produced by the lexer
6
+ token NEWLINE
7
+ token FEATURE BACKGROUND SCENARIO
8
+ token TAG
9
+ token GIVEN WHEN THEN AND BUT
10
+ token TEXT
11
+
12
+ rule
13
+
14
+ Root:
15
+ Feature { result = val[0]; }
16
+ |
17
+ Feature
18
+ Scenarios { result = val[0]; result.scenarios = val[1] }
19
+ ;
20
+
21
+ Newline:
22
+ NEWLINE
23
+ | Newline NEWLINE
24
+ ;
25
+
26
+ Feature:
27
+ FeatureHeader { result = val[0] }
28
+ | FeatureHeader
29
+ Background { result = val[0]; result.background = val[1] }
30
+ ;
31
+
32
+ FeatureHeader:
33
+ FeatureName { result = val[0] }
34
+ | FeatureName Newline { result = val[0] }
35
+ | FeatureName Newline
36
+ Description { result = val[0] }
37
+ ;
38
+
39
+ FeatureName:
40
+ FEATURE TEXT { result = AST::Feature.new(val[1]); result.pos(filename, lineno) }
41
+ | Newline FEATURE TEXT { result = AST::Feature.new(val[2]); result.pos(filename, lineno) }
42
+ ;
43
+
44
+ Description:
45
+ TEXT Newline
46
+ | Description TEXT Newline
47
+ ;
48
+
49
+ Background:
50
+ BackgroundHeader
51
+ Steps { result = val[0]; result.steps = val[1] }
52
+ ;
53
+
54
+ BackgroundHeader:
55
+ BACKGROUND Newline { result = AST::Background.new; result.pos(filename, lineno) }
56
+ ;
57
+
58
+ Steps:
59
+ Step Newline { result = [val[0]] }
60
+ | Steps Step Newline { result = val[0] << val[1] }
61
+ ;
62
+
63
+ Step:
64
+ Keyword TEXT { result = AST::Step.new(val[1], val[0]); result.pos(filename, lineno) }
65
+ ;
66
+
67
+ Keyword:
68
+ GIVEN | WHEN | THEN | AND | BUT
69
+ ;
70
+
71
+ Scenarios:
72
+ Scenario { result = [val[0]] }
73
+ | Scenarios Scenario { result = val[0] << val[1] }
74
+ | Scenarios Newline Scenario { result = val[0] << val[2] }
75
+ ;
76
+
77
+ Scenario:
78
+ SCENARIO TEXT Newline
79
+ Steps { result = AST::Scenario.new(val[1], val[3]); result.pos(filename, lineno - 1) }
80
+ | Tags Newline
81
+ SCENARIO TEXT Newline
82
+ Steps { result = AST::Scenario.new(val[3], val[5], val[0]); result.pos(filename, lineno - 2) }
83
+ ;
84
+
85
+ Tags:
86
+ TAG { result = [AST::Tag.new(val[0])] }
87
+ | Tags TAG { result = val[0] << AST::Tag.new(val[1]) }
88
+ ;
89
+
90
+ end
91
+
92
+ ---- header
93
+ require_relative "lexer"
94
+ require_relative "../ast"
95
+
96
+ ---- inner
97
+
98
+ def parse(input)
99
+ scan_str(input)
100
+ end
@@ -0,0 +1,120 @@
1
+ #--
2
+ # DO NOT MODIFY!!!!
3
+ # This file is automatically generated by rex 1.0.5
4
+ # from lexical definition file "lib/gherkin/parser/gherkin.rex".
5
+ #++
6
+
7
+ require 'racc/parser'
8
+ # Compile with: rex gherkin.rex -o lexer.rb
9
+
10
+ class Gherkin::Parser < Racc::Parser
11
+ require 'strscan'
12
+
13
+ class ScanError < StandardError ; end
14
+
15
+ attr_reader :lineno
16
+ attr_reader :filename
17
+ attr_accessor :state
18
+
19
+ def scan_setup(str)
20
+ @ss = StringScanner.new(str)
21
+ @lineno = 1
22
+ @state = nil
23
+ end
24
+
25
+ def action
26
+ yield
27
+ end
28
+
29
+ def scan_str(str)
30
+ scan_setup(str)
31
+ do_parse
32
+ end
33
+ alias :scan :scan_str
34
+
35
+ def load_file( filename )
36
+ @filename = filename
37
+ open(filename, "r") do |f|
38
+ scan_setup(f.read)
39
+ end
40
+ end
41
+
42
+ def scan_file( filename )
43
+ load_file(filename)
44
+ do_parse
45
+ end
46
+
47
+
48
+ def next_token
49
+ return if @ss.eos?
50
+
51
+ # skips empty actions
52
+ until token = _next_token or @ss.eos?; end
53
+ token
54
+ end
55
+
56
+ def _next_token
57
+ text = @ss.peek(1)
58
+ @lineno += 1 if text == "\n"
59
+ token = case @state
60
+ when nil
61
+ case
62
+ when (text = @ss.scan(/[ \t]+/))
63
+ ;
64
+
65
+ when (text = @ss.scan(/\#.*$/))
66
+ ;
67
+
68
+ when (text = @ss.scan(/\n/))
69
+ action { [:NEWLINE, text] }
70
+
71
+ when (text = @ss.scan(/Feature:/))
72
+ action { [:FEATURE, text[0..-2]] }
73
+
74
+ when (text = @ss.scan(/Background:/))
75
+ action { [:BACKGROUND, text[0..-2]] }
76
+
77
+ when (text = @ss.scan(/Scenario:/))
78
+ action { [:SCENARIO, text[0..-2]] }
79
+
80
+ when (text = @ss.scan(/@\w+/))
81
+ action { [:TAG, text[1..-1]] }
82
+
83
+ when (text = @ss.scan(/Given/))
84
+ action { [:GIVEN, text] }
85
+
86
+ when (text = @ss.scan(/When/))
87
+ action { [:WHEN, text] }
88
+
89
+ when (text = @ss.scan(/Then/))
90
+ action { [:THEN, text] }
91
+
92
+ when (text = @ss.scan(/And/))
93
+ action { [:AND, text] }
94
+
95
+ when (text = @ss.scan(/But/))
96
+ action { [:BUT, text] }
97
+
98
+ when (text = @ss.scan(/[^#\n]*/))
99
+ action { [:TEXT, text.strip] }
100
+
101
+ else
102
+ text = @ss.string[@ss.pos .. -1]
103
+ raise ScanError, "can not match: '" + text + "'"
104
+ end # if
105
+
106
+ else
107
+ raise ScanError, "undefined state: '" + state.to_s + "'"
108
+ end # case state
109
+ token
110
+ end # def _next_token
111
+
112
+ def tokenize(code)
113
+ scan_setup(code)
114
+ tokens = []
115
+ while token = next_token
116
+ tokens << token
117
+ end
118
+ tokens
119
+ end
120
+ end # class