gherkin-ruby 0.0.2 → 0.1.0

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