treetop 1.4.5 → 1.4.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/README.md +44 -20
  2. data/lib/treetop/compiler/metagrammar.rb +126 -33
  3. data/lib/treetop/compiler/metagrammar.treetop +46 -42
  4. data/lib/treetop/compiler/node_classes/repetition.rb +39 -5
  5. data/lib/treetop/version.rb +1 -1
  6. data/spec/compiler/and_predicate_spec.rb +36 -0
  7. data/spec/compiler/anything_symbol_spec.rb +44 -0
  8. data/spec/compiler/character_class_spec.rb +276 -0
  9. data/spec/compiler/choice_spec.rb +80 -0
  10. data/spec/compiler/circular_compilation_spec.rb +30 -0
  11. data/spec/compiler/failure_propagation_functional_spec.rb +21 -0
  12. data/spec/compiler/grammar_compiler_spec.rb +91 -0
  13. data/spec/compiler/grammar_spec.rb +41 -0
  14. data/spec/compiler/multibyte_chars_spec.rb +38 -0
  15. data/spec/compiler/nonterminal_symbol_spec.rb +40 -0
  16. data/spec/compiler/not_predicate_spec.rb +38 -0
  17. data/spec/compiler/occurrence_range_spec.rb +191 -0
  18. data/spec/compiler/one_or_more_spec.rb +35 -0
  19. data/spec/compiler/optional_spec.rb +37 -0
  20. data/spec/compiler/parenthesized_expression_spec.rb +19 -0
  21. data/spec/compiler/parsing_rule_spec.rb +61 -0
  22. data/spec/compiler/repeated_subrule_spec.rb +29 -0
  23. data/spec/compiler/semantic_predicate_spec.rb +175 -0
  24. data/spec/compiler/sequence_spec.rb +115 -0
  25. data/spec/compiler/terminal_spec.rb +81 -0
  26. data/spec/compiler/terminal_symbol_spec.rb +37 -0
  27. data/spec/compiler/test_grammar.treetop +7 -0
  28. data/spec/compiler/test_grammar.tt +7 -0
  29. data/spec/compiler/test_grammar_do.treetop +7 -0
  30. data/spec/compiler/tt_compiler_spec.rb +215 -0
  31. data/spec/compiler/zero_or_more_spec.rb +56 -0
  32. data/spec/composition/a.treetop +11 -0
  33. data/spec/composition/b.treetop +11 -0
  34. data/spec/composition/c.treetop +10 -0
  35. data/spec/composition/d.treetop +10 -0
  36. data/spec/composition/f.treetop +17 -0
  37. data/spec/composition/grammar_composition_spec.rb +40 -0
  38. data/spec/composition/subfolder/e_includes_c.treetop +15 -0
  39. data/spec/ruby_extensions/string_spec.rb +32 -0
  40. data/spec/runtime/compiled_parser_spec.rb +101 -0
  41. data/spec/runtime/interval_skip_list/delete_spec.rb +147 -0
  42. data/spec/runtime/interval_skip_list/expire_range_spec.rb +349 -0
  43. data/spec/runtime/interval_skip_list/insert_and_delete_node.rb +385 -0
  44. data/spec/runtime/interval_skip_list/insert_spec.rb +660 -0
  45. data/spec/runtime/interval_skip_list/interval_skip_list_spec.graffle +6175 -0
  46. data/spec/runtime/interval_skip_list/interval_skip_list_spec.rb +58 -0
  47. data/spec/runtime/interval_skip_list/palindromic_fixture.rb +23 -0
  48. data/spec/runtime/interval_skip_list/palindromic_fixture_spec.rb +163 -0
  49. data/spec/runtime/interval_skip_list/spec_helper.rb +84 -0
  50. data/spec/runtime/syntax_node_spec.rb +77 -0
  51. data/spec/spec_helper.rb +110 -0
  52. data/treetop.gemspec +18 -0
  53. metadata +70 -9
@@ -0,0 +1,81 @@
1
+ require 'spec_helper'
2
+
3
+ module TerminalSymbolSpec
4
+ class Foo < Treetop::Runtime::SyntaxNode
5
+ end
6
+
7
+ describe "a terminal symbol followed by a node class declaration and a block" do
8
+ testing_expression "'foo' <TerminalSymbolSpec::Foo> { def a_method; end }"
9
+
10
+ it "correctly parses matching input prefixes at various indices, returning an instance of the declared class that can respond to methods defined in the inline module" do
11
+ parse "foo", :index => 0 do |result|
12
+ result.should be_an_instance_of(Foo)
13
+ result.should respond_to(:a_method)
14
+ result.interval.should == (0...3)
15
+ result.text_value.should == 'foo'
16
+ end
17
+
18
+ parse "xfoo", :index => 1 do |result|
19
+ result.should be_an_instance_of(Foo)
20
+ result.should respond_to(:a_method)
21
+ result.interval.should == (1...4)
22
+ result.text_value.should == 'foo'
23
+ end
24
+
25
+ parse "---foo", :index => 3 do |result|
26
+ result.should be_an_instance_of(Foo)
27
+ result.should respond_to(:a_method)
28
+ result.interval.should == (3...6)
29
+ result.text_value.should == 'foo'
30
+ end
31
+ end
32
+
33
+ it "fails to parse nonmatching input at the index even if a match occurs later" do
34
+ parse(" foo", :index => 0).should be_nil
35
+ end
36
+ end
37
+
38
+ module ModFoo
39
+ end
40
+
41
+ describe "a terminal symbol followed by a node class declaration and a block" do
42
+ testing_expression "'foo' <TerminalSymbolSpec::ModFoo> { def a_method; end }"
43
+
44
+ it "correctly parses matching input prefixes at various indices, returning an instance of SyntaxNode extended with the declared module that can respond to methods defined in the inline module" do
45
+ parse "foo", :index => 0 do |result|
46
+ result.should be_an_instance_of(Treetop::Runtime::SyntaxNode)
47
+ result.should be_a_kind_of(ModFoo)
48
+ result.should respond_to(:a_method)
49
+ result.interval.should == (0...3)
50
+ result.text_value.should == 'foo'
51
+ end
52
+
53
+ parse "xfoo", :index => 1 do |result|
54
+ result.should be_an_instance_of(Treetop::Runtime::SyntaxNode)
55
+ result.should be_a_kind_of(ModFoo)
56
+ result.should respond_to(:a_method)
57
+ result.interval.should == (1...4)
58
+ result.text_value.should == 'foo'
59
+ end
60
+
61
+ parse "---foo", :index => 3 do |result|
62
+ result.should be_an_instance_of(Treetop::Runtime::SyntaxNode)
63
+ result.should be_a_kind_of(ModFoo)
64
+ result.should respond_to(:a_method)
65
+ result.interval.should == (3...6)
66
+ result.text_value.should == 'foo'
67
+ end
68
+ end
69
+ end
70
+
71
+ describe "a transient terminal symbol" do
72
+ testing_expression "~'foo'"
73
+
74
+ it "returns true upon parsing matching input prefixes at various indices" do
75
+ pending "transient terminal expressions"
76
+ parse("foo", :index => 0).should be_true
77
+ parse("-foo", :index => 1).should be_true
78
+ parse("---foo", :index => 3).should be_true
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ module TerminalSymbolSpec
4
+ class Foo < Treetop::Runtime::SyntaxNode
5
+ end
6
+
7
+ describe "a terminal symbol followed by a node class declaration and a block" do
8
+ testing_expression "'foo' <TerminalSymbolSpec::Foo> { def a_method; end }"
9
+
10
+ it "correctly parses matching input prefixes at various indices, returning an instance of the declared class that can respond to methods defined in the inline module" do
11
+ parse "foo", :index => 0 do |result|
12
+ result.should be_an_instance_of(Foo)
13
+ result.should respond_to(:a_method)
14
+ result.interval.should == (0...3)
15
+ result.text_value.should == 'foo'
16
+ end
17
+
18
+ parse "xfoo", :index => 1 do |result|
19
+ result.should be_an_instance_of(Foo)
20
+ result.should respond_to(:a_method)
21
+ result.interval.should == (1...4)
22
+ result.text_value.should == 'foo'
23
+ end
24
+
25
+ parse "---foo", :index => 3 do |result|
26
+ result.should be_an_instance_of(Foo)
27
+ result.should respond_to(:a_method)
28
+ result.interval.should == (3...6)
29
+ result.text_value.should == 'foo'
30
+ end
31
+ end
32
+
33
+ it "fails to parse nonmatching input at the index even if a match occurs later" do
34
+ parse(" foo", :index => 0).should be_nil
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,7 @@
1
+ module Test
2
+ grammar Grammar
3
+ rule foo
4
+ 'foo'
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Test
2
+ grammar Grammar
3
+ rule foo
4
+ 'foo'
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Test
2
+ grammar Grammar do
3
+ rule foo do
4
+ 'foo'
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,215 @@
1
+ require 'digest/sha1'
2
+ require 'tmpdir'
3
+
4
+ # ensure the Kernel.system and Kernel.open's always use the correct tt and
5
+ # Treetop library versions, not a previously installed gem
6
+ ENV['PATH'] = File.expand_path(File.dirname(__FILE__) + '../../../bin' +
7
+ File::PATH_SEPARATOR + ENV['PATH'])
8
+ $LOAD_PATH.unshift(File.expand_path('../../../../lib', __FILE__))
9
+
10
+ describe "The 'tt' comand line compiler" do
11
+
12
+ context 'when processing a single grammar file' do
13
+ before(:each) do
14
+ # create a fresh but dumb grammar file for each example
15
+ @tmpdir = Dir.tmpdir
16
+ @test_base = "dumb-#{rand(1000)}"
17
+ @test_path = "#{@tmpdir}/#{@test_base}"
18
+ @test_grammar = "#{@test_path}.tt"
19
+ @test_ruby = "#{@test_path}.rb"
20
+ File.open(@test_grammar, 'w+') do |f|
21
+ f.print("grammar Dumb\n")
22
+ f.print("end\n")
23
+ end unless File.exists?(@test_grammar)
24
+ end
25
+
26
+ after(:each) do
27
+ # cleanup test grammar and parser output files
28
+ File.delete(@test_grammar) if File.exists?(@test_grammar)
29
+ File.delete(@test_ruby) if File.exists?(@test_ruby)
30
+ end
31
+
32
+ it 'can compile a grammar file' do
33
+ # puts %q{emulate 'tt dumb.tt'}
34
+ system("ruby -S tt #{@test_grammar}").should be_true
35
+
36
+ File.exists?(@test_ruby).should be_true
37
+ File.zero?(@test_ruby).should_not be_true
38
+ end
39
+
40
+ it 'can compile a relative pathed grammar file' do
41
+ dir = File.basename(File.expand_path(File.dirname(@test_grammar)))
42
+
43
+ # puts %q{emulate 'tt "../<current_dir>/dumb.tt"'}
44
+ system("cd #{@tmpdir}/..; ruby -S tt \"./#{dir}/#{@test_base}.tt\"").should be_true
45
+
46
+ File.exists?(@test_ruby).should be_true
47
+ File.zero?(@test_ruby).should_not be_true
48
+ end
49
+
50
+ it 'can compile an absolute pathed grammar file' do
51
+ # puts %q{emulate 'tt "/path/to/dumb.tt"'}
52
+ system("ruby -S tt \"#{File.expand_path(@test_grammar)}\"").should be_true
53
+
54
+ File.exists?(@test_ruby).should be_true
55
+ File.zero?(@test_ruby).should_not be_true
56
+ end
57
+
58
+ it 'can compile without explicit file extensions' do
59
+ # puts %q{emulate 'tt dumb'}
60
+ system("ruby -S tt #{@test_path}").should be_true
61
+
62
+ File.exists?(@test_ruby).should be_true
63
+ File.zero?(@test_ruby).should_not be_true
64
+ end
65
+
66
+ it 'skips nonexistent grammar file without failing or creating bogus output' do
67
+ # puts %q{emulate 'tt dumb.bad'}
68
+ Kernel.open("|ruby -S tt #{@test_base}.bad") do |io|
69
+ (io.read =~ /ERROR.*?not exist.*?continuing/).should_not be_nil
70
+ end
71
+
72
+ File.exists?("#{@test_base}.rb").should be_false
73
+ end
74
+
75
+ it 'can compile to a specified parser source file' do
76
+ # puts %q{emulate 'tt -o my_dumb_test_parser.rb dumb'}
77
+ pf = "#{@tmpdir}/my_dumb_test_parser.rb"
78
+ begin
79
+ system("ruby -S tt -o #{pf} #{@test_path}").should be_true
80
+
81
+ File.exists?(pf).should be_true
82
+ File.zero?(pf).should_not be_true
83
+ ensure
84
+ File.delete(pf) if File.exists?(pf)
85
+ end
86
+ end
87
+
88
+ it 'by default, does not overwrite an existing file without an autogenerated header' do
89
+ # puts %q{emulate 'tt -o must_save_parser.rb dumb'}
90
+ pf = "#{@tmpdir}/must_save_parser.rb"
91
+ begin
92
+ system("ruby -S tt -o #{pf} #{@test_path}").should be_true
93
+
94
+ File.exists?(pf).should be_true
95
+ File.zero?(pf).should_not be_true
96
+
97
+ # Modify the file and make sure it remains unchanged:
98
+ File.open(pf, "r+") { |f| f.write("# Changed...") }
99
+ orig_file_hash = Digest::SHA1.hexdigest(File.read(pf))
100
+
101
+ Kernel.open("|ruby -S tt -o #{pf} #{@test_path}") do |io|
102
+ (io.read =~ /ERROR.*?already exists.*?skipping/).should_not be_nil
103
+ end
104
+
105
+ Digest::SHA1.hexdigest(File.read(pf)).should == orig_file_hash
106
+ ensure
107
+ File.delete(pf) if File.exists?(pf)
108
+ end
109
+ end
110
+
111
+ it 'by default, overwrites a changed file with an intact autogenerated header' do
112
+ # puts %q{emulate 'tt -o must_save_parser.rb dumb'}
113
+ pf = "#{@tmpdir}/must_save_parser.rb"
114
+ begin
115
+ system("ruby -S tt -o #{pf} #{@test_path}").should be_true
116
+
117
+ File.exists?(pf).should be_true
118
+ File.zero?(pf).should_not be_true
119
+ orig_file_hash = Digest::SHA1.hexdigest(File.read(pf))
120
+
121
+ # Modify the file and make sure it gets reverted:
122
+ File.open(pf, "r+") { |f| f.gets; f.write("#") }
123
+
124
+ system("ruby -S tt -o #{pf} #{@test_path}").should be_true
125
+ Digest::SHA1.hexdigest(File.read(pf)).should == orig_file_hash
126
+ ensure
127
+ File.delete(pf) if File.exists?(pf)
128
+ end
129
+ end
130
+
131
+ it 'can be forced to overwrite existing file #{@test_path}' do
132
+ pf = "#{@test_path}.rb"
133
+ system("echo some junk >#{pf}").should be_true
134
+
135
+ File.exists?(pf).should be_true
136
+ File.zero?(pf).should_not be_true
137
+ orig_file_hash = Digest::SHA1.hexdigest(File.read(pf))
138
+
139
+ system("ruby -S tt -f #{@test_path}").should be_true
140
+ Digest::SHA1.hexdigest(File.read(pf)).should_not == orig_file_hash
141
+ end
142
+
143
+ end
144
+
145
+ context 'when processing multiple grammar files' do
146
+
147
+ before(:each) do
148
+ # provide fresh but dumb grammar files for each test
149
+ @test_bases = []
150
+ @test_grammars = []
151
+
152
+ %w[dumb1 dumb2].each do |e|
153
+ base = "#{@tmpdir}/#{e}-#{rand(1000)}"
154
+ grammar_file = "#{base}.tt"
155
+ @test_bases << base
156
+ @test_grammars << grammar_file
157
+
158
+ File.open(grammar_file, 'w+') do |f|
159
+ f.print("grammar #{e.capitalize}\n")
160
+ f.print("end\n")
161
+ end unless File.exists?(grammar_file)
162
+ end
163
+ end
164
+
165
+ after(:each) do
166
+ # cleanup test grammar and output parser files
167
+ @test_grammars.each { |f| File.delete(f) if File.exists?(f) }
168
+ @test_bases.each { |f| File.delete("#{f}.rb") if File.exists?("#{f}.rb") }
169
+ end
170
+
171
+ it 'can compile them in one invocation' do
172
+ # puts %q{emulate 'tt dumb1.tt dumb2.tt'}
173
+ system("ruby -S tt #{@test_grammars.join(' ')}").should be_true
174
+
175
+ @test_bases.each do |f|
176
+ pf = "#{f}.rb"
177
+ File.exists?(pf).should be_true
178
+ File.zero?(pf).should_not be_true
179
+ end
180
+ end
181
+
182
+ it 'can compile them without explicit file extenstions' do
183
+ # puts %q{emulate 'tt dumb1 dumb2'}
184
+ system("ruby -S tt #{@test_bases.join(' ')}").should be_true
185
+
186
+ @test_bases.each do |f|
187
+ pf = "#{f}.rb"
188
+ File.exists?(pf).should be_true
189
+ File.zero?(pf).should_not be_true
190
+ end
191
+ end
192
+
193
+ it 'can skip nonexistent and invalid extension named grammar files' do
194
+ # puts %q{emulate 'tt not_here bad_ext.ttg dumb1 dumb2'}
195
+ system("ruby -S tt not_here bad_ext.ttg #{@test_bases.join(' ')} >/dev/null 2>&1").should be_true
196
+
197
+ File.exists?('not_here.rb').should_not be_true
198
+ File.exists?('bad_ext.rb').should_not be_true
199
+
200
+ @test_bases.each do |f|
201
+ pf = "#{f}.rb"
202
+ File.exists?(pf).should be_true
203
+ File.zero?(pf).should_not be_true
204
+ end
205
+ end
206
+
207
+ it 'can not specify an output file' do
208
+ # puts %q{emulate 'tt -o my_bogus_test_parser.rb dumb1 dumb2'}
209
+ pf = 'my_bogus_test_parser.rb'
210
+ system("ruby -S tt -o #{pf} #{@test_bases.join(' ')} >/dev/null 2>&1").should be_false
211
+ File.exists?(pf).should be_false
212
+ end
213
+ end
214
+
215
+ end
@@ -0,0 +1,56 @@
1
+ require 'spec_helper'
2
+
3
+ module ZeroOrMoreSpec
4
+ class Foo < Treetop::Runtime::SyntaxNode
5
+ end
6
+
7
+ describe "zero or more of a terminal symbol followed by a node class declaration and a block" do
8
+ testing_expression '"foo"* <ZeroOrMoreSpec::Foo> { def a_method; end }'
9
+
10
+ it "successfully parses epsilon, returning an instance declared node class and recording a terminal failure" do
11
+ parse('') do |result|
12
+ result.should_not be_nil
13
+ result.should be_an_instance_of(Foo)
14
+ result.should respond_to(:a_method)
15
+
16
+ terminal_failures = parser.terminal_failures
17
+ terminal_failures.size.should == 1
18
+ failure = terminal_failures.first
19
+ failure.index.should == 0
20
+ failure.expected_string.should == 'foo'
21
+ end
22
+ end
23
+
24
+ it "successfully parses two of that terminal in a row, returning an instance of the declared node class and recording a failure representing the third attempt " do
25
+ parse("foofoo") do |result|
26
+ result.should_not be_nil
27
+ result.should be_an_instance_of(Foo)
28
+
29
+ terminal_failures = parser.terminal_failures
30
+ terminal_failures.size.should == 1
31
+ failure = terminal_failures.first
32
+ failure.index.should == 6
33
+ failure.expected_string.should == 'foo'
34
+ end
35
+ end
36
+ end
37
+
38
+ describe "Zero or more of a sequence" do
39
+ testing_expression '("foo" "bar")*'
40
+
41
+ it "resets the index appropriately following partially matcing input" do
42
+ parse('foobarfoo', :consume_all_input => false) do |result|
43
+ result.should_not be_nil
44
+ result.interval.should == (0...6)
45
+ end
46
+ end
47
+ end
48
+
49
+ describe "Zero or more of a choice" do
50
+ testing_expression '("a" / "b")*'
51
+
52
+ it "successfully parses matching input" do
53
+ parse('abba').should_not be_nil
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,11 @@
1
+ module Test
2
+ grammar A
3
+ rule a
4
+ 'a'
5
+ end
6
+
7
+ rule inherit
8
+ 'super'
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Test
2
+ grammar B
3
+ rule b
4
+ 'b'
5
+ end
6
+
7
+ rule inherit
8
+ super 'keyword'
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,10 @@
1
+ module Test
2
+ grammar C
3
+ include A
4
+ include B
5
+
6
+ rule c
7
+ a b 'c'
8
+ end
9
+ end
10
+ end