piggly 1.2.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.
Files changed (59) hide show
  1. data/README.markdown +84 -0
  2. data/Rakefile +19 -0
  3. data/bin/piggly +245 -0
  4. data/lib/piggly/compiler/cache.rb +151 -0
  5. data/lib/piggly/compiler/pretty.rb +67 -0
  6. data/lib/piggly/compiler/queue.rb +46 -0
  7. data/lib/piggly/compiler/tags.rb +244 -0
  8. data/lib/piggly/compiler/trace.rb +91 -0
  9. data/lib/piggly/compiler.rb +5 -0
  10. data/lib/piggly/config.rb +43 -0
  11. data/lib/piggly/filecache.rb +40 -0
  12. data/lib/piggly/installer.rb +95 -0
  13. data/lib/piggly/parser/grammar.tt +747 -0
  14. data/lib/piggly/parser/nodes.rb +319 -0
  15. data/lib/piggly/parser/parser.rb +11783 -0
  16. data/lib/piggly/parser/traversal.rb +48 -0
  17. data/lib/piggly/parser/treetop_ruby19_patch.rb +17 -0
  18. data/lib/piggly/parser.rb +67 -0
  19. data/lib/piggly/profile.rb +87 -0
  20. data/lib/piggly/reporter/html.rb +207 -0
  21. data/lib/piggly/reporter/piggly.css +187 -0
  22. data/lib/piggly/reporter/sortable.js +493 -0
  23. data/lib/piggly/reporter.rb +21 -0
  24. data/lib/piggly/task.rb +64 -0
  25. data/lib/piggly/util.rb +28 -0
  26. data/lib/piggly/version.rb +15 -0
  27. data/lib/piggly.rb +18 -0
  28. data/spec/compiler/cache_spec.rb +9 -0
  29. data/spec/compiler/pretty_spec.rb +9 -0
  30. data/spec/compiler/queue_spec.rb +3 -0
  31. data/spec/compiler/rewrite_spec.rb +3 -0
  32. data/spec/compiler/tags_spec.rb +285 -0
  33. data/spec/compiler/trace_spec.rb +173 -0
  34. data/spec/config_spec.rb +58 -0
  35. data/spec/filecache_spec.rb +70 -0
  36. data/spec/fixtures/snippets.sql +158 -0
  37. data/spec/grammar/expression_spec.rb +302 -0
  38. data/spec/grammar/statements/assignment_spec.rb +70 -0
  39. data/spec/grammar/statements/exception_spec.rb +52 -0
  40. data/spec/grammar/statements/if_spec.rb +178 -0
  41. data/spec/grammar/statements/loop_spec.rb +41 -0
  42. data/spec/grammar/statements/sql_spec.rb +71 -0
  43. data/spec/grammar/tokens/comment_spec.rb +58 -0
  44. data/spec/grammar/tokens/datatype_spec.rb +52 -0
  45. data/spec/grammar/tokens/identifier_spec.rb +58 -0
  46. data/spec/grammar/tokens/keyword_spec.rb +44 -0
  47. data/spec/grammar/tokens/label_spec.rb +40 -0
  48. data/spec/grammar/tokens/literal_spec.rb +30 -0
  49. data/spec/grammar/tokens/lval_spec.rb +50 -0
  50. data/spec/grammar/tokens/number_spec.rb +34 -0
  51. data/spec/grammar/tokens/sqlkeywords_spec.rb +45 -0
  52. data/spec/grammar/tokens/string_spec.rb +54 -0
  53. data/spec/grammar/tokens/whitespace_spec.rb +40 -0
  54. data/spec/parser_spec.rb +8 -0
  55. data/spec/profile_spec.rb +5 -0
  56. data/spec/reporter/html_spec.rb +0 -0
  57. data/spec/spec_helper.rb +61 -0
  58. data/spec/spec_suite.rb +5 -0
  59. metadata +121 -0
@@ -0,0 +1,158 @@
1
+ CREATE OR REPLACE FUNCTION piggly_coverage_prog(a integer, b boolean) RETURNS void AS $$
2
+ DECLARE
3
+ x varchar(20);
4
+ y integer[] := '{}';
5
+ BEGIN
6
+
7
+ -- IF-THEN
8
+ IF v_user_id <> 0
9
+ THEN
10
+ UPDATE users SET email = v_email WHERE user_id = v_user_id;
11
+ END IF;
12
+
13
+ -- IF-THEN-ELSE
14
+ IF parentid IS NULL OR parentid = ''
15
+ THEN
16
+ RETURN fullname;
17
+ ELSE
18
+ RETURN hp_true_filename(parentid) || '/' || fullname;
19
+ END IF;
20
+
21
+ -- IF-THEN-ELSE
22
+ IF v_count > 0 THEN
23
+ INSERT INTO users_count(count) VALUES (v_count);
24
+ RETURN 't';
25
+ ELSE
26
+ RETURN 'f';
27
+ END IF;
28
+
29
+ -- IF-THEN-ELSIF
30
+ IF number = 0 THEN
31
+ result := 'zero';
32
+ ELSIF number > 0 THEN
33
+ result := 'positive';
34
+ ELSIF number < 0 THEN
35
+ result := 'negative';
36
+ ELSE
37
+ -- hmm, the only other possibility is that the number is null
38
+ result := 'NULL';
39
+ END IF;
40
+
41
+ -- nest IF-THEN-ELSE statements
42
+ IF demo_row.sex = 'm' THEN
43
+ pretty_sex := 'man';
44
+ ELSE
45
+ IF demo_row.sex = 'f' THEN
46
+ pretty_sex := 'woman';
47
+ END IF;
48
+ END IF;
49
+
50
+ -- simple CASE with search-expression
51
+ CASE x
52
+ WHEN 1, 2 THEN
53
+ msg := 'one or two';
54
+ ELSE
55
+ msg := 'other value than one or two';
56
+ END CASE;
57
+
58
+ -- searched CASE with boolean-expression
59
+ CASE
60
+ WHEN x BETWEEN 0 and 10 THEN
61
+ msg := 'value is between zero and ten';
62
+ WHEN x BETWEEN 11 and 20 THEN
63
+ msg := 'value is between eleven and twenty';
64
+ END CASE;
65
+
66
+ << labelA >>
67
+ LOOP
68
+ a := a + 1;
69
+ EXIT labelA WHEN a > 10;
70
+ END LOOP labelA;
71
+
72
+ LOOP
73
+ b := b + 1
74
+ EXIT WHEN b > 10;
75
+ END LOOP;
76
+
77
+ LOOP
78
+ c := c + 1;
79
+ IF c > 10 THEN
80
+ EXIT;
81
+ END IF;
82
+ END LOOP;
83
+
84
+ << labelB >>
85
+ BEGIN
86
+ -- some computations
87
+ IF stocks > 100000 THEN
88
+ EXIT labelB; -- causes exit from BEGIN block
89
+ END IF;
90
+ -- computations here will be skipped when stocks > 100000
91
+ END;
92
+
93
+ LOOP
94
+ -- some computations
95
+ EXIT WHEN count > 100;
96
+ CONTINUE WHEN count < 50;
97
+ -- some computations for count IN [50 .. 100]
98
+ END LOOP;
99
+
100
+ WHILE amount_owed > 0 AND gift_certificate_balance > 0
101
+ LOOP
102
+ -- some computations here
103
+ a := 10;
104
+ END LOOP;
105
+
106
+ << labelC >>
107
+ WHILE NOT done LOOP
108
+ -- some computations here
109
+ a := 10;
110
+ END LOOP labelC;
111
+
112
+ << labelD >>
113
+ WHILE x NOT IN (1,2,3) LOOP
114
+ CONTINUE labelD WHEN x < 10;
115
+ END LOOP;
116
+
117
+ FOR i IN 1..10 LOOP
118
+ --
119
+ END LOOP;
120
+
121
+ << labelD >>
122
+ FOR i IN REVERSE 10..1 LOOP
123
+ --
124
+ EXIT labelD WHEN i = 5;
125
+ END LOOP labelD;
126
+
127
+ << labelE >>
128
+ FOR i IN REVERSE 10..1 BY 2 LOOP
129
+ --
130
+ CONTINUE labelE;
131
+ END LOOP;
132
+
133
+ FOR f IN SELECT *
134
+ FROM foo
135
+ WHERE id > 100
136
+ LOOP
137
+ -- can do some processing home
138
+ RETURN NEXT f; -- return current row of SELECT
139
+ END LOOP;
140
+
141
+ FOR t IN EXECUTE 'SELECT * FROM foo' LOOP
142
+ CONTINUE;
143
+ END LOOP;
144
+
145
+ BEGIN
146
+ UPDATE tab SET fname = 'J' WHERE lname = 'J';
147
+ x := x + 1;
148
+ y := x / 0;
149
+ EXCEPTION
150
+ WHEN SQLSTATE '22012' THEN
151
+ -- relax, don't do it
152
+ WHEN division_by_zero OR unique_violation THEN
153
+ RAISE NOTICE 'caught a fool';
154
+ RETURN x;
155
+ END;
156
+
157
+ END $$
158
+ LANGUAGE 'plpgsql' IMMUTABLE;
@@ -0,0 +1,302 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
2
+
3
+ module Piggly
4
+ describe Parser, "expressions" do
5
+ include GrammarHelper
6
+
7
+ describe "expressionUntilSemiColon" do
8
+ it "does not consume semicolon" do
9
+ # parser stops in front of THEN and dies
10
+ lambda{ parse(:expressionUntilSemiColon, 'abc;') }.should raise_error
11
+
12
+ node, rest = parse_some(:expressionUntilSemiColon, 'abc; xyz')
13
+ rest.should == '; xyz'
14
+ end
15
+
16
+ it "can be a blank expression" do
17
+ node, rest = parse_some(:expressionUntilSemiColon, ';')
18
+ node.should be_a(Expression)
19
+ node.source_text.should == ''
20
+ rest.should == ';'
21
+ end
22
+
23
+ it "can be a comment" do
24
+ node, rest = parse_some(:expressionUntilSemiColon, "/* comment */;")
25
+ node.should be_a(Expression)
26
+ node.count{|e| e.is_a?(TComment) }.should == 1
27
+
28
+ node, rest = parse_some(:expressionUntilSemiColon, "-- comment\n;")
29
+ node.should be_a(Expression)
30
+ node.count{|e| e.is_a?(TComment) }.should == 1
31
+ end
32
+
33
+ it "can be a string" do
34
+ node, rest = parse_some(:expressionUntilSemiColon, "'string';")
35
+ node.should be_a(Expression)
36
+ node.count{|e| e.is_a?(TString) }.should == 1
37
+
38
+ node, rest = parse_some(:expressionUntilSemiColon, "$$ string $$;")
39
+ node.should be_a(Expression)
40
+ node.count{|e| e.is_a?(TString) }.should == 1
41
+ end
42
+
43
+ it "can be an arithmetic expression" do
44
+ node, rest = parse_some(:expressionUntilSemiColon, "10 * (3 + x);")
45
+ node.should be_a(Expression)
46
+ end
47
+
48
+ it "can be an SQL statement" do
49
+ node, rest = parse_some(:expressionUntilSemiColon, "SELECT id FROM dataset;")
50
+ node.should be_a(Expression)
51
+ end
52
+
53
+ it "can be an expression with comments embedded" do
54
+ node, rest = parse_some(:expressionUntilSemiColon, <<-SQL)
55
+ SELECT id -- primary key ;
56
+ FROM "dataset" /* ; */ -- previous comments shouldn't terminate expression
57
+ WHERE value IS /*NOT*/ NULL;
58
+ SQL
59
+ node.should be_a(Expression)
60
+ node.count{|e| e.is_a?(TComment) }.should == 4
61
+ end
62
+
63
+ it "can be an expression with strings and comments embedded" do
64
+ node, rest = parse_some(:expressionUntilSemiColon, <<-SQL)
65
+ SELECT id -- 1. upcoming single quote doesn't matter
66
+ FROM dataset /* 2. this one's no problem either */
67
+ WHERE value LIKE '/* comment within a string! shouldn''t parse a comment */'
68
+ AND length(value) > 10 -- 3. this comment in tail doesn't contain any 'string's
69
+ /* 4. farewell comment in tail */;
70
+ SQL
71
+ node.should be_a(Expression)
72
+ node.count{|e| e.is_a?(TComment) }.should == 4
73
+ node.count{|e| e.is_a?(TString) }.should == 1
74
+ end
75
+
76
+ it "can be an expression with strings embedded" do
77
+ node, rest = parse_some(:expressionUntilSemiColon, <<-SQL)
78
+ SELECT id, created_at
79
+ FROM "dataset"
80
+ WHERE value IS NOT NULL
81
+ AND value <> '; this should not terminate expression'
82
+ AND created_at = '2001-01-01';
83
+ SQL
84
+ node.should be_a(Expression)
85
+ node.count{|e| e.is_a?(TString) }.should == 2
86
+ end
87
+
88
+ it "should combine trailing whitespace into 'tail' node" do
89
+ node, rest = parse_some(:expressionUntilSemiColon, "a := x + y \t;")
90
+ node.should be_a(Expression)
91
+ node.tail.source_text.should == " \t"
92
+ end
93
+
94
+ it "should combine trailing comments into 'tail' node" do
95
+ node, rest = parse_some(:expressionUntilSemiColon, "a := x + y /* note -- comment */;")
96
+ node.should be_a(Expression)
97
+ node.tail.source_text.should == ' /* note -- comment */'
98
+
99
+ node, rest = parse_some(:expressionUntilSemiColon, <<-SQL)
100
+ SELECT id -- 1. upcoming single quote doesn't matter
101
+ FROM dataset /* 2. this one's no problem either */
102
+ WHERE value LIKE '/* comment within a string! shouldn''t parse a comment */'
103
+ AND length(value) > 10 -- 3. this comment in tail doesn't contain any 'string's
104
+ /* 4. farewell comment in tail */;
105
+ SQL
106
+ node.tail.count{|e| e.is_a?(TComment) }.should == 2
107
+ end
108
+ end
109
+
110
+ describe "expressionUntilThen" do
111
+ it "does not consume THEN token" do
112
+ # parser stops in front of THEN and dies
113
+ lambda{ parse(:expressionUntilThen, 'abc THEN') }.should raise_error
114
+
115
+ node, rest = parse_some(:expressionUntilThen, 'abc THEN xyz')
116
+ rest.should == 'THEN xyz'
117
+ end
118
+
119
+ it "cannot be a blank expression" do
120
+ lambda{ parse_some(:expressionUntilThen, ' THEN') }.should raise_error
121
+ end
122
+
123
+ it "cannot be a comment" do
124
+ lambda{ parse_some(:expressionUntilThen, "/* comment */ THEN") }.should raise_error
125
+ lambda{ parse_some(:expressionUntilThen, "-- comment\n THEN") }.should raise_error
126
+ end
127
+
128
+ it "can be a string" do
129
+ node, rest = parse_some(:expressionUntilThen, "'string' THEN")
130
+ node.should be_a(Expression)
131
+ node.count{|e| e.is_a?(TString) }.should == 1
132
+
133
+ node, rest = parse_some(:expressionUntilThen, "$$ string $$ THEN")
134
+ node.should be_a(Expression)
135
+ node.count{|e| e.is_a?(TString) }.should == 1
136
+ end
137
+
138
+ it "can be an arithmetic expression" do
139
+ node, rest = parse_some(:expressionUntilThen, "10 * (3 + x) THEN")
140
+ node.should be_a(Expression)
141
+ end
142
+
143
+ it "can be an SQL statement" do
144
+ node, rest = parse_some(:expressionUntilThen, "SELECT id FROM dataset THEN")
145
+ node.should be_a(Expression)
146
+ end
147
+
148
+ it "can be an expression with comments embedded" do
149
+ node, rest = parse_some(:expressionUntilThen, <<-SQL)
150
+ SELECT id -- primary key THEN
151
+ FROM "dataset" /* THEN */ -- previous comments shouldn't terminate expression
152
+ WHERE value IS /*NOT*/ NULL THEN
153
+ SQL
154
+ node.should be_a(Expression)
155
+ node.count{|e| e.is_a?(TComment) }.should == 4
156
+ end
157
+
158
+ it "can be an expression with strings and comments embedded" do
159
+ node, rest = parse_some(:expressionUntilThen, <<-SQL)
160
+ SELECT id -- 1. upcoming single quote doesn't matter
161
+ FROM dataset /* 2. this one's no problem either */
162
+ WHERE value LIKE '/* comment within a string! shouldn''t parse a comment */'
163
+ AND length(value) > 10 -- 3. this comment in tail doesn't contain any 'string's
164
+ /* 4. farewell comment in tail */ THEN
165
+ SQL
166
+ node.should be_a(Expression)
167
+ node.count{|e| e.is_a?(TComment) }.should == 4
168
+ node.count{|e| e.is_a?(TString) }.should == 1
169
+ end
170
+
171
+ it "can be an expression with strings embedded" do
172
+ node, rest = parse_some(:expressionUntilThen, <<-SQL)
173
+ SELECT id, created_at
174
+ FROM "dataset"
175
+ WHERE value IS NOT NULL
176
+ AND value <> ' THEN this should not terminate expression'
177
+ AND created_at = '2001-01-01' THEN
178
+ SQL
179
+ node.should be_a(Expression)
180
+ node.count{|e| e.is_a?(TString) }.should == 2
181
+ end
182
+
183
+ it "should combine trailing whitespace into 'tail' node" do
184
+ node, rest = parse_some(:expressionUntilThen, "a := x + y \tTHEN")
185
+ node.should be_a(Expression)
186
+ node.tail.source_text.should == " \t"
187
+ end
188
+
189
+ it "should combine trailing comments into 'tail' node" do
190
+ node, rest = parse_some(:expressionUntilThen, "a := x + y /* note -- comment */THEN")
191
+ node.should be_a(Expression)
192
+ node.tail.source_text.should == ' /* note -- comment */'
193
+
194
+ node, rest = parse_some(:expressionUntilThen, <<-SQL)
195
+ SELECT id -- 1. upcoming single quote doesn't matter
196
+ FROM dataset /* 2. this one's no problem either */
197
+ WHERE value LIKE '/* comment within a string! shouldn''t parse a comment */'
198
+ AND length(value) > 10 -- 3. this comment in tail doesn't contain any 'string's
199
+ /* 4. farewell comment in tail */THEN
200
+ SQL
201
+ node.tail.count{|e| e.is_a?(TComment) }.should == 2
202
+ end
203
+ end
204
+
205
+ describe "expressionUntilLoop" do
206
+ it "does not consume LOOP token" do
207
+ # parser stops in front of LOOP and dies
208
+ lambda{ parse(:expressionUntilLoop, 'abc LOOP') }.should raise_error
209
+
210
+ node, rest = parse_some(:expressionUntilLoop, 'abc LOOP xyz')
211
+ rest.should == 'LOOP xyz'
212
+ end
213
+
214
+ it "cannot be a blank expression" do
215
+ lambda{ parse_some(:expressionUntilLoop, ' LOOP') }.should raise_error
216
+ end
217
+
218
+ it "cannot be a comment" do
219
+ lambda{ parse_some(:expressionUntilLoop, "/* comment */ LOOP") }.should raise_error
220
+ lambda{ parse_some(:expressionUntilLoop, "-- comment\n LOOP") }.should raise_error
221
+ end
222
+
223
+ it "can be a string" do
224
+ node, rest = parse_some(:expressionUntilLoop, "'string' LOOP")
225
+ node.should be_a(Expression)
226
+ node.count{|e| e.is_a?(TString) }.should == 1
227
+
228
+ node, rest = parse_some(:expressionUntilLoop, "$$ string $$ LOOP")
229
+ node.should be_a(Expression)
230
+ node.count{|e| e.is_a?(TString) }.should == 1
231
+ end
232
+
233
+ it "can be an arithmetic expression" do
234
+ node, rest = parse_some(:expressionUntilLoop, "10 * (3 + x) LOOP")
235
+ node.should be_a(Expression)
236
+ end
237
+
238
+ it "can be an SQL statement" do
239
+ node, rest = parse_some(:expressionUntilLoop, "SELECT id FROM dataset LOOP")
240
+ node.should be_a(Expression)
241
+ end
242
+
243
+ it "can be an expression with comments embedded" do
244
+ node, rest = parse_some(:expressionUntilLoop, <<-SQL)
245
+ SELECT id -- primary key LOOP
246
+ FROM "dataset" /* LOOP */ -- previous comments shouldn't terminate expression
247
+ WHERE value IS /*NOT*/ NULL LOOP
248
+ SQL
249
+ node.should be_a(Expression)
250
+ node.count{|e| e.is_a?(TComment) }.should == 4
251
+ end
252
+
253
+ it "can be an expression with strings and comments embedded" do
254
+ node, rest = parse_some(:expressionUntilLoop, <<-SQL)
255
+ SELECT id -- 1. upcoming single quote doesn't matter
256
+ FROM dataset /* 2. this one's no problem either */
257
+ WHERE value LIKE '/* comment within a string! shouldn''t parse a comment */'
258
+ AND length(value) > 10 -- 3. this comment in tail doesn't contain any 'string's
259
+ /* 4. farewell comment in tail */ LOOP
260
+ SQL
261
+ node.should be_a(Expression)
262
+ node.count{|e| e.is_a?(TComment) }.should == 4
263
+ node.count{|e| e.is_a?(TString) }.should == 1
264
+ end
265
+
266
+ it "can be an expression with strings embedded" do
267
+ node, rest = parse_some(:expressionUntilLoop, <<-SQL)
268
+ SELECT id, created_at
269
+ FROM "dataset"
270
+ WHERE value IS NOT NULL
271
+ AND value <> ' LOOP this should not terminate expression'
272
+ AND created_at = '2001-01-01' LOOP
273
+ SQL
274
+ node.should be_a(Expression)
275
+ node.count{|e| e.is_a?(TString) }.should == 2
276
+ end
277
+
278
+ it "should combine trailing whitespace into 'tail' node" do
279
+ node, rest = parse_some(:expressionUntilLoop, "a := x + y \tLOOP")
280
+ node.should be_a(Expression)
281
+ node.tail.source_text.should == " \t"
282
+ end
283
+
284
+ it "should combine trailing comments into 'tail' node" do
285
+ node, rest = parse_some(:expressionUntilLoop, "a := x + y /* note -- comment */LOOP")
286
+ node.should be_a(Expression)
287
+ node.tail.source_text.should == ' /* note -- comment */'
288
+
289
+ node, rest = parse_some(:expressionUntilLoop, <<-SQL)
290
+ SELECT id -- 1. upcoming single quote doesn't matter
291
+ FROM dataset /* 2. this one's no problem either */
292
+ WHERE value LIKE '/* comment within a string! shouldn''t parse a comment */'
293
+ AND length(value) > 10 -- 3. this comment in tail doesn't contain any 'string's
294
+ /* 4. farewell comment in tail */LOOP
295
+ SQL
296
+ node.tail.count{|e| e.is_a?(TComment) }.should == 2
297
+ end
298
+ end
299
+
300
+ end
301
+
302
+ end
@@ -0,0 +1,70 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'spec_helper'))
2
+
3
+ module Piggly
4
+
5
+ describe Parser, "statements" do
6
+ include GrammarHelper
7
+
8
+ describe "assignment statements" do
9
+ it "parses successfully" do
10
+ node = parse(:statement, "a := 10;")
11
+ node.should be_a(Statement)
12
+ node.count{|e| e.is_a? Assignment }.should == 1
13
+ node.count{|e| e.is_a? Assignable }.should == 1
14
+ node.find{|e| e.named? :lval }.should be_a(Assignable)
15
+ node.find{|e| e.named? :rval }.should be_a(Expression)
16
+ end
17
+
18
+ it "must end with a semicolon" do
19
+ lambda { parse_some(:statement, 'a := 10') }.should raise_error
20
+ lambda { parse(:statement, 'a := 10') }.should raise_error
21
+ end
22
+
23
+ it "can use := or =" do
24
+ a = parse(:statement, "a := 10;")
25
+ a.should be_a(Statement)
26
+ a.count{|e| e.is_a? Assignment }.should == 1
27
+
28
+ b = parse(:statement, "a = 10;")
29
+ b.should be_a(Statement)
30
+ b.count{|e| e.is_a? Assignment }.should == 1
31
+ end
32
+
33
+ it "can assign strings" do
34
+ node = parse(:statement, "a := 'string';")
35
+ rval = node.find{|e| e.named? :rval }
36
+ rval.count{|e| e.is_a? TString }.should == 1
37
+ end
38
+
39
+ it "can assign value expressions containing comments" do
40
+ ['a := /* comment */ 100;',
41
+ 'a := 100 /* comment */;',
42
+ 'a := 10 /* comment */ + 10;'].test_each do |s|
43
+ node = parse(:statement, s)
44
+ rval = node.find{|e| e.named? :rval }
45
+ rval.count{|e| e.is_a? TComment }.should == 1
46
+ end
47
+ end
48
+
49
+ it "can assign value expressions containing strings" do
50
+ node = parse(:statement, "a := 'Hello,' || space || 'world';")
51
+ rval = node.find{|e| e.named? :rval }
52
+ rval.count{|e| e.is_a? TString }.should == 2
53
+ end
54
+
55
+ it "can assign value expressions containing comments and strings" do
56
+ node = parse(:statement, <<-SQL.strip)
57
+ a := (SELECT fk, count(*), 'not a statement;'
58
+ FROM dataset
59
+ WHERE id > 100 /* filter out 'reserved' range */
60
+ AND id < 900 -- shouldn't be a string
61
+ OR value <> 'alpha /* no comment */;'
62
+ GROUP BY /* pk ; 'abc' */ fk);
63
+ SQL
64
+ rval = node.find{|e| e.named? :rval }
65
+ rval.count{|e| e.is_a? TString }.should == 2
66
+ end
67
+ end
68
+
69
+ end
70
+ end
@@ -0,0 +1,52 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'spec_helper'))
2
+
3
+ module Piggly
4
+ describe Parser, "control structures" do
5
+ include GrammarHelper
6
+
7
+ describe "exceptions" do
8
+ describe "raise" do
9
+ it "parses successfully" do
10
+ node, rest = parse_some(:statement, "RAISE EXCEPTION 'message';")
11
+ node.should be_a(Statement)
12
+ rest.should == ''
13
+ end
14
+
15
+ it "handles exception" do
16
+ node = parse(:statement, "RAISE EXCEPTION 'message';")
17
+ node.count{|e| e.is_a?(Throw) }.should == 1
18
+ node.count{|e| e.is_a?(Raise) }.should == 0
19
+ end
20
+
21
+ it "handles events" do
22
+ %w(WARNING LOG INFO NOTICE DEBUG).each do |event|
23
+ node = parse(:statement, "RAISE #{event} 'message';")
24
+ node.count{|e| e.is_a?(Throw) }.should == 0
25
+ node.count{|e| e.is_a?(Raise) }.should == 1
26
+ end
27
+ end
28
+ end
29
+
30
+ describe "catch" do
31
+ before do
32
+ @text = 'BEGIN a := 10; EXCEPTION WHEN cond THEN b := 10; WHEN cond THEN b := 20; END;'
33
+ end
34
+
35
+ it "parses successfully" do
36
+ node, rest = parse_some(:statement, @text)
37
+ node.should be_a(Statement)
38
+ rest.should == ''
39
+ end
40
+
41
+ it "has Catch node" do
42
+ node = parse(:statement, @text)
43
+ catches = node.select{|e| e.is_a?(Catch) }
44
+ catches.size.should == 2
45
+
46
+ catches[0].count{|e| e.named?(:cond) and e.is_a?(Expression) }.should == 1
47
+ catches[1].count{|e| e.named?(:cond) and e.is_a?(Expression) }.should == 1
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end