rattler 0.2.0 → 0.2.1

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 (38) hide show
  1. data/README.rdoc +2 -0
  2. data/bin/rtlr +8 -5
  3. data/bin/rtlr.bat +0 -0
  4. data/features/command_line/dest_option.feature +34 -0
  5. data/features/command_line/output_option.feature +31 -0
  6. data/features/command_line/parser_generator.feature +80 -0
  7. data/features/grammar/word_literal.feature +3 -3
  8. data/features/step_definitions/cli_steps.rb +3 -0
  9. data/features/step_definitions/{rattler_steps.rb → grammar_steps.rb} +0 -0
  10. data/features/support/env.rb +1 -0
  11. data/lib/rattler.rb +2 -2
  12. data/lib/rattler/back_end/parser_generator.rb +1 -0
  13. data/lib/rattler/back_end/parser_generator/one_or_more_generator.rb +17 -28
  14. data/lib/rattler/back_end/parser_generator/repeat_generating.rb +69 -0
  15. data/lib/rattler/back_end/parser_generator/rule_generator.rb +1 -1
  16. data/lib/rattler/back_end/parser_generator/zero_or_more_generator.rb +9 -63
  17. data/lib/rattler/grammar/grammar_parser.rb +1 -1
  18. data/lib/rattler/grammar/metagrammar.rb +63 -73
  19. data/lib/rattler/grammar/rattler.rtlr +5 -13
  20. data/lib/rattler/parsers/combining.rb +7 -0
  21. data/lib/rattler/parsers/match_joining.rb +7 -0
  22. data/lib/rattler/parsers/predicate.rb +7 -0
  23. data/lib/rattler/parsers/sequence.rb +1 -1
  24. data/lib/rattler/parsers/token.rb +1 -1
  25. data/lib/rattler/runner.rb +6 -0
  26. data/lib/rattler/runtime.rb +1 -1
  27. data/lib/rattler/runtime/extended_packrat_parser.rb +62 -0
  28. data/lib/rattler/runtime/packrat_parser.rb +46 -12
  29. data/lib/rattler/runtime/parse_failure.rb +1 -1
  30. data/lib/rattler/util/parser_spec_helper.rb +1 -0
  31. data/spec/rattler/back_end/parser_generator/one_or_more_generator_spec.rb +14 -4
  32. data/spec/rattler/back_end/parser_generator/zero_or_more_generator_spec.rb +12 -2
  33. data/spec/rattler/runtime/extended_packrat_parser_spec.rb +36 -0
  34. data/spec/rattler/runtime/packrat_parser_spec.rb +1 -1
  35. data/spec/rattler/runtime/shared_parser_examples.rb +29 -0
  36. metadata +462 -79
  37. data/lib/rattler/runtime/wdm_parser.rb +0 -83
  38. data/spec/rattler/runtime/wdm_parser_spec.rb +0 -21
@@ -8,7 +8,7 @@ require 'rattler/grammar'
8
8
 
9
9
  module Rattler::Grammar
10
10
  # @private
11
- class GrammarParser < Rattler::Runtime::WDMParser #:nodoc:
11
+ class GrammarParser < Rattler::Runtime::ExtendedPackratParser #:nodoc:
12
12
 
13
13
  include Metagrammar
14
14
  include Rattler::Parsers
@@ -53,7 +53,18 @@ module Rattler
53
53
  # @private
54
54
  def match_requires #:nodoc:
55
55
  a0 = []
56
- while r = match(:require_decl)
56
+ while r = begin
57
+ p0 = @scanner.pos
58
+ begin
59
+ @scanner.skip(/(?>(?>(?>[[:space:]])+|(?>\#)(?>(?>[^\n])*))*)(?>(?>require)(?>(?![[:alnum:]_])))/) &&
60
+ (r0_0 = match(:literal)) &&
61
+ match(:eol) &&
62
+ r0_0
63
+ end || begin
64
+ @scanner.pos = p0
65
+ false
66
+ end
67
+ end
57
68
  a0 << r
58
69
  end
59
70
  ({:requires => select_captures(a0)})
@@ -61,91 +72,70 @@ module Rattler
61
72
 
62
73
  # @private
63
74
  def match_module_decl #:nodoc:
64
- match(:parser_decl) ||
65
- match(:grammar_decl)
66
- end
67
-
68
- # @private
69
- def match_includes #:nodoc:
70
- a0 = []
71
- while r = match(:include_decl)
72
- a0 << r
73
- end
74
- ({:includes => select_captures(a0)})
75
- end
76
-
77
- # @private
78
- def match_require_decl #:nodoc:
79
- p0 = @scanner.pos
80
75
  begin
81
- @scanner.skip(/(?>(?>(?>[[:space:]])+|(?>\#)(?>(?>[^\n])*))*)(?>(?>require)(?>(?![[:alnum:]_])))/) &&
82
- (r0_0 = match(:literal)) &&
83
- match(:eol) &&
84
- r0_0
85
- end || begin
86
- @scanner.pos = p0
87
- false
88
- end
89
- end
90
-
91
- # @private
92
- def match_parser_decl #:nodoc:
93
- p0 = @scanner.pos
76
+ p0 = @scanner.pos
77
+ begin
78
+ @scanner.skip(/(?>(?>(?>[[:space:]])+|(?>\#)(?>(?>[^\n])*))*)(?>(?>parser)(?>(?![[:alnum:]_])))/) &&
79
+ (r0_0 = match(:constant)) &&
80
+ (r0_1 = ((r = begin
81
+ p1 = @scanner.pos
82
+ begin
83
+ @scanner.skip(/(?>(?>(?>[[:space:]])+|(?>\#)(?>(?>[^\n])*))*)(?><)/) &&
84
+ match(:constant)
85
+ end || begin
86
+ @scanner.pos = p1
87
+ false
88
+ end
89
+ end) ? [r] : [])) &&
90
+ match(:eol) &&
91
+ (parser_decl r0_0, r0_1)
92
+ end || begin
93
+ @scanner.pos = p0
94
+ false
95
+ end
96
+ end ||
94
97
  begin
95
- @scanner.skip(/(?>(?>(?>[[:space:]])+|(?>\#)(?>(?>[^\n])*))*)(?>(?>parser)(?>(?![[:alnum:]_])))/) &&
96
- (r0_0 = match(:constant)) &&
97
- (r0_1 = ((r = begin
98
- p1 = @scanner.pos
99
- begin
100
- @scanner.skip(/(?>(?>(?>[[:space:]])+|(?>\#)(?>(?>[^\n])*))*)(?><)/) &&
101
- match(:constant)
102
- end || begin
103
- @scanner.pos = p1
104
- false
105
- end
106
- end) ? [r] : [])) &&
107
- match(:eol) &&
108
- (parser_decl r0_0, r0_1)
109
- end || begin
110
- @scanner.pos = p0
111
- false
98
+ p0 = @scanner.pos
99
+ begin
100
+ @scanner.skip(/(?>(?>(?>[[:space:]])+|(?>\#)(?>(?>[^\n])*))*)(?>(?>grammar)(?>(?![[:alnum:]_])))/) &&
101
+ (r0_0 = match(:constant)) &&
102
+ match(:eol) &&
103
+ ({:grammar_name => r0_0})
104
+ end || begin
105
+ @scanner.pos = p0
106
+ false
107
+ end
112
108
  end
113
109
  end
114
110
 
115
111
  # @private
116
- def match_grammar_decl #:nodoc:
117
- p0 = @scanner.pos
118
- begin
119
- @scanner.skip(/(?>(?>(?>[[:space:]])+|(?>\#)(?>(?>[^\n])*))*)(?>(?>grammar)(?>(?![[:alnum:]_])))/) &&
120
- (r0_0 = match(:constant)) &&
121
- match(:eol) &&
122
- ({:grammar_name => r0_0})
123
- end || begin
124
- @scanner.pos = p0
125
- false
112
+ def match_includes #:nodoc:
113
+ a0 = []
114
+ while r = begin
115
+ p0 = @scanner.pos
116
+ begin
117
+ @scanner.skip(/(?>(?>(?>[[:space:]])+|(?>\#)(?>(?>[^\n])*))*)(?>(?>include)(?>(?![[:alnum:]_])))/) &&
118
+ (r0_0 = match(:constant)) &&
119
+ match(:eol) &&
120
+ r0_0
121
+ end || begin
122
+ @scanner.pos = p0
123
+ false
124
+ end
126
125
  end
127
- end
128
-
129
- # @private
130
- def match_include_decl #:nodoc:
131
- p0 = @scanner.pos
132
- begin
133
- @scanner.skip(/(?>(?>(?>[[:space:]])+|(?>\#)(?>(?>[^\n])*))*)(?>(?>include)(?>(?![[:alnum:]_])))/) &&
134
- (r0_0 = match(:constant)) &&
135
- match(:eol) &&
136
- r0_0
137
- end || begin
138
- @scanner.pos = p0
139
- false
126
+ a0 << r
140
127
  end
128
+ ({:includes => select_captures(a0)})
141
129
  end
142
130
 
143
131
  # @private
144
132
  def match_rules #:nodoc:
145
133
  a0 = []
146
- while r = match(:directive) ||
147
- match(:rule) ||
148
- match(:block_close)
134
+ while r = begin
135
+ match(:directive) ||
136
+ match(:rule) ||
137
+ match(:block_close)
138
+ end
149
139
  a0 << r
150
140
  end
151
141
  Rules.parsed(select_captures(a0)) unless a0.empty?
@@ -10,24 +10,16 @@ include Rattler::Parsers
10
10
 
11
11
  heading <- requires module_decl? includes {|r,m,i| heading r, m, i }
12
12
 
13
- requires <- require_decl* {|r| {:requires => r} }
13
+ requires <- (~`require` literal ~eol)* {|r| {:requires => r} }
14
14
 
15
- module_decl <- parser_decl | grammar_decl
15
+ module_decl <- ~`parser` constant (~'<' constant)? ~eol {|p,b| parser_decl p, b }
16
+ | ~`grammar` constant ~eol {|g| {:grammar_name => g} }
16
17
 
17
- includes <- include_decl* {|i| {:includes => i} }
18
-
19
- require_decl <- ~`require` literal ~eol
20
-
21
- parser_decl <- ~`parser` constant (~'<' constant)? ~eol {|p,b| parser_decl p, b }
22
-
23
- grammar_decl <- ~`grammar` constant ~eol {|g| {:grammar_name => g} }
24
-
25
- include_decl <- ~`include` constant ~eol
18
+ includes <- (~`include` constant ~eol)* {|i| {:includes => i} }
26
19
 
27
20
  rules <- (directive | rule | block_close)+ <Rules>
28
21
 
29
- directive <- ws_directive
30
- | wc_directive
22
+ directive <- ws_directive | wc_directive
31
23
 
32
24
  ws_directive <- ws_decl ~'{' {|e| start_ws e }
33
25
  | ws_decl {|e| set_ws e }
@@ -1,3 +1,10 @@
1
+ #
2
+ # = rattler/parsers/combining.rb
3
+ #
4
+ # Author:: Jason Arhart
5
+ # Documentation:: Author
6
+ #
7
+
1
8
  module Rattler::Parsers
2
9
  # @private
3
10
  module Combining #:nodoc:
@@ -1,3 +1,10 @@
1
+ #
2
+ # = rattler/parsers/match_joining.rb
3
+ #
4
+ # Author:: Jason Arhart
5
+ # Documentation:: Author
6
+ #
7
+
1
8
  require 'rattler/parsers'
2
9
 
3
10
  module Rattler
@@ -1,3 +1,10 @@
1
+ #
2
+ # = rattler/parsers/predicate.rb
3
+ #
4
+ # Author:: Jason Arhart
5
+ # Documentation:: Author
6
+ #
7
+
1
8
  require 'rattler/parsers'
2
9
 
3
10
  module Rattler::Parsers
@@ -1,5 +1,5 @@
1
1
  #
2
- # = rattler/parsers/choice.rb
2
+ # = rattler/parsers/sequence.rb
3
3
  #
4
4
  # Author:: Jason Arhart
5
5
  # Documentation:: Author
@@ -9,7 +9,7 @@ require 'rattler/parsers'
9
9
 
10
10
  module Rattler::Parsers
11
11
  #
12
- # Token decorates a parser to return the entire matched string
12
+ # +Token+ decorates a parser to return the entire matched string
13
13
  #
14
14
  # @author Jason Arhart
15
15
  #
@@ -1,3 +1,9 @@
1
+ #
2
+ # = rattler/runner.rb
3
+ #
4
+ # Author:: Jason Arhart
5
+ # Documentation:: Author
6
+ #
1
7
  require 'rattler'
2
8
 
3
9
  require 'optparse'
@@ -16,7 +16,7 @@ module Rattler
16
16
  autoload :Parser, 'rattler/runtime/parser'
17
17
  autoload :RecursiveDescentParser, 'rattler/runtime/recursive_descent_parser'
18
18
  autoload :PackratParser, 'rattler/runtime/packrat_parser'
19
- autoload :WDMParser, 'rattler/runtime/wdm_parser'
19
+ autoload :ExtendedPackratParser, 'rattler/runtime/extended_packrat_parser'
20
20
  autoload :ParseNode, 'rattler/runtime/parse_node'
21
21
  autoload :ParserHelper, 'rattler/runtime/parser_helper'
22
22
  autoload :ParseFailure, 'rattler/runtime/parse_failure'
@@ -0,0 +1,62 @@
1
+ #
2
+ # = rattler/runtime/extended_packrat_parser.rb
3
+ #
4
+ # Author:: Jason Arhart
5
+ # Documentation:: Author
6
+ #
7
+
8
+ require 'rattler/runtime'
9
+
10
+ module Rattler::Runtime
11
+ #
12
+ # +ExtendedPackratParser+ implements the algorithm described by Alessandro
13
+ # Warth, James R. Douglass, and Todd Millstein for extending packrat parsing
14
+ # to support left-recursive grammars. It currently only implements the first
15
+ # part to support direct left recursion.
16
+ #
17
+ # @author Jason Arhart
18
+ #
19
+ class ExtendedPackratParser < PackratParser
20
+
21
+ private
22
+
23
+ # @private
24
+ def apply!(rule_name, key, start_pos) #:nodoc:
25
+ lr = LR.new
26
+ m = @memo[key] = MemoEntry.new(lr, start_pos, nil, nil)
27
+ result = eval_rule rule_name
28
+ memorize m, result
29
+ result = grow_lr(rule_name, start_pos, m) if result and lr.detected
30
+ result
31
+ end
32
+
33
+ def recall(m)
34
+ if (result = m.result).is_a? LR
35
+ result.detected = true
36
+ false
37
+ else
38
+ super
39
+ end
40
+ end
41
+
42
+ # @private
43
+ def grow_lr(rule_name, start_pos, m) #:nodoc:
44
+ loop do
45
+ @scanner.pos = start_pos
46
+ result = eval_rule(rule_name)
47
+ return recall(m) if !result or @scanner.pos <= m.end_pos
48
+ memorize m, result
49
+ end
50
+ end
51
+
52
+ # @private
53
+ class LR
54
+ def initialize(detected = false)
55
+ @detected = detected
56
+ end
57
+ attr_accessor :detected
58
+ end
59
+
60
+ end
61
+
62
+ end
@@ -1,5 +1,5 @@
1
1
  #
2
- # = rattler/runtime/recursive_descent_parser.rb
2
+ # = rattler/runtime/packrat_parser.rb
3
3
  #
4
4
  # Author:: Jason Arhart
5
5
  # Documentation:: Author
@@ -24,10 +24,13 @@ module Rattler::Runtime
24
24
  #
25
25
  def initialize(source, options={})
26
26
  super
27
- @result_memo = {}
28
- @pos_memo = {}
27
+ @memo = {}
29
28
  end
30
29
 
30
+ # @private
31
+ alias_method :eval_rule, :apply
32
+ private :eval_rule
33
+
31
34
  protected
32
35
 
33
36
  # Apply a rule by dispatching to the method associated with the given rule
@@ -40,18 +43,49 @@ module Rattler::Runtime
40
43
  # @return (see RecursiveDescentParser#apply)
41
44
  #
42
45
  def apply(rule_name)
43
- key = [rule_name, @scanner.pos]
44
- if @result_memo.has_key? key
45
- result = @result_memo[key]
46
- @scanner.pos = @pos_memo[key]
46
+ start_pos = @scanner.pos
47
+ key = [rule_name, start_pos]
48
+ if @memo.has_key? key
49
+ recall @memo[key]
47
50
  else
48
- @result_memo[key] = false
49
- @pos_memo[key] = @scanner.pos
50
- @result_memo[key] = result = super
51
- @pos_memo[key] = @scanner.pos
51
+ apply! rule_name, key, start_pos
52
52
  end
53
- result
54
53
  end
55
54
 
55
+ private
56
+
57
+ # @private
58
+ def apply!(rule_name, key, start_pos) #:nodoc:
59
+ m = @memo[key] = MemoEntry.new(false, start_pos, start_pos, 'left-recursion detected')
60
+ memorize m, eval_rule(rule_name)
61
+ end
62
+
63
+ # @private
64
+ def memorize(m, result) #:nodoc:
65
+ m.end_pos = @scanner.pos
66
+ m.failure_pos = @failure_pos
67
+ m.failure_msg = @failure_msg
68
+ m.result = result
69
+ end
70
+
71
+ # @private
72
+ def recall(m) #:nodoc:
73
+ @scanner.pos = m.end_pos
74
+ @failure_pos = m.failure_pos
75
+ @failure_msg = m.failure_msg
76
+ m.result
77
+ end
78
+
79
+ # @private
80
+ class MemoEntry
81
+ def initialize(result, end_pos, failure_pos, failure_msg)
82
+ @result = result
83
+ @end_pos = end_pos
84
+ @failure_pos = failure_pos
85
+ @failure_msg = failure_msg
86
+ end
87
+ attr_accessor :result, :end_pos, :failure_pos, :failure_msg
88
+ end
89
+
56
90
  end
57
91
  end
@@ -1,5 +1,5 @@
1
1
  #
2
- # = rattler/runtime/parse_error.rb
2
+ # = rattler/runtime/parse_failure.rb
3
3
  #
4
4
  # Author:: Jason Arhart
5
5
  # Documentation:: Author
@@ -131,6 +131,7 @@ MESSAGE
131
131
  def initialize(parser)
132
132
  @parser = parser
133
133
  end
134
+ attr_reader :parser
134
135
  def from(pos)
135
136
  @parser.pos = pos
136
137
  self
@@ -9,6 +9,10 @@ describe Rattler::BackEnd::ParserGenerator::OneOrMoreGenerator do
9
9
  let(:one_or_more) { OneOrMore[Match[/w+/]] }
10
10
 
11
11
  describe '#gen_basic' do
12
+
13
+ let :one_or_more do
14
+ OneOrMore[Choice[Match[/[[:alpha:]]/], Match[/[[:digit:]]/]]]
15
+ end
12
16
 
13
17
  context 'when nested' do
14
18
  it 'generates nested one-or-more matching code' do
@@ -16,7 +20,10 @@ describe Rattler::BackEnd::ParserGenerator::OneOrMoreGenerator do
16
20
  should == (<<-CODE).strip
17
21
  begin
18
22
  a = []
19
- while r = @scanner.scan(/w+/)
23
+ while r = begin
24
+ @scanner.scan(/[[:alpha:]]/) ||
25
+ @scanner.scan(/[[:digit:]]/)
26
+ end
20
27
  a << r
21
28
  end
22
29
  a unless a.empty?
@@ -30,7 +37,10 @@ end
30
37
  top_level_code {|g| g.gen_basic one_or_more }.
31
38
  should == (<<-CODE).strip
32
39
  a = []
33
- while r = @scanner.scan(/w+/)
40
+ while r = begin
41
+ @scanner.scan(/[[:alpha:]]/) ||
42
+ @scanner.scan(/[[:digit:]]/)
43
+ end
34
44
  a << r
35
45
  end
36
46
  a unless a.empty?
@@ -49,7 +59,7 @@ a unless a.empty?
49
59
  end
50
60
 
51
61
  context 'when top-level' do
52
- it 'generates top level one-or-more positive lookahead code' do
62
+ it 'generates nested one-or-more positive lookahead code' do
53
63
  top_level_code {|g| g.gen_assert one_or_more }.
54
64
  should == '@scanner.skip(/(?=w+)/) && true'
55
65
  end
@@ -66,7 +76,7 @@ a unless a.empty?
66
76
  end
67
77
 
68
78
  context 'when top-level' do
69
- it 'generates top level one-or-more negative lookahead code' do
79
+ it 'generates nested one-or-more negative lookahead code' do
70
80
  top_level_code {|g| g.gen_disallow one_or_more }.
71
81
  should == '@scanner.skip(/(?!w+)/) && true'
72
82
  end