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,178 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'spec_helper'))
2
+
3
+ module Piggly
4
+
5
+ describe Parser, "control structures" do
6
+ include GrammarHelper
7
+
8
+ describe "if statements" do
9
+ describe "if .. then .. end if" do
10
+ it "must end with a semicolon" do
11
+ lambda{ parse(:statement, 'IF cond THEN a := 10; END IF') }.should raise_error
12
+ lambda{ parse_some(:stmtIf, 'IF cond THEN a := 10; END IF') }.should raise_error
13
+ end
14
+
15
+ it "parses successfully" do
16
+ node, rest = parse_some(:statement, 'IF cond THEN a := 10; END IF;')
17
+ node.should be_a(Statement)
18
+ rest.should == ''
19
+ end
20
+
21
+ it "does not have an Else node" do
22
+ node = parse(:statement, 'IF cond THEN a := 10; END IF;')
23
+ node.count{|e| e.is_a?(Else) }.should == 0
24
+ node.count{|e| e.named?(:else) and not e.empty? }.should == 0
25
+ end
26
+
27
+ it "has a 'cond' Expression" do
28
+ node = parse(:statement, 'IF cond THEN a := 10; END IF;')
29
+ node.count{|e| e.named?(:cond) }.should == 1
30
+ node.find{|e| e.named?(:cond) }.should be_a(Expression)
31
+ end
32
+
33
+ it "can have missing body" do
34
+ node = parse(:statement, 'IF cond THEN END IF;')
35
+ node.count{|e| e.instance_of?(Statement) }.should == 1
36
+ end
37
+
38
+ it "can have comment body" do
39
+ node = parse(:statement, 'IF cond THEN /* removed */ END IF;')
40
+ node.count{|e| e.instance_of?(Statement) }.should == 1
41
+ node.count{|e| e.instance_of?(TComment) }.should == 1
42
+ node.find{|e| e.instance_of?(TComment) }.source_text.should == '/* removed */'
43
+ end
44
+
45
+ it "can have single statement body" do
46
+ node = parse(:statement, 'IF cond THEN a := 10; END IF;')
47
+ node.count{|e| e.instance_of?(Statement) }.should == 2
48
+ end
49
+
50
+ it "can have multiple statement body" do
51
+ node = parse(:statement, 'IF cond THEN a := 10; b := 10; END IF;')
52
+ node.count{|e| e.instance_of?(Statement) }.should == 3
53
+ end
54
+
55
+ it "can contain comments" do
56
+ node = parse(:statement, "IF cond /* comment */ THEN -- foo\n NULL; /* foo */ END IF;")
57
+ node.should be_a(Statement)
58
+ node.count{|e| e.is_a?(TComment) }.should == 3
59
+ end
60
+ end
61
+
62
+ describe "if .. then .. else .. end if" do
63
+ it "parses successfully" do
64
+ node, rest = parse_some(:statement, 'IF cond THEN a := 10; ELSE a := 20; END IF;')
65
+ node.should be_a(Statement)
66
+ rest.should == ''
67
+ end
68
+
69
+ it "has an Else node named 'else'" do
70
+ node = parse(:statement, 'IF cond THEN a := 10; ELSE a := 20; END IF;')
71
+ node.count{|e| e.named?(:else) and e.is_a?(Else) }.should == 1
72
+ node.find{|e| e.named?(:else) }.source_text.should == 'ELSE a := 20; '
73
+ end
74
+
75
+ it "can have missing else body" do
76
+ node = parse(:statement, 'IF cond THEN a := 10; ELSE END IF;')
77
+ node.count{|e| e.instance_of?(Statement) }.should == 2
78
+ end
79
+
80
+ it "can have comment body" do
81
+ node = parse(:statement, 'IF cond THEN a := 10; ELSE /* removed */ END IF;')
82
+ node.count{|e| e.instance_of?(Statement) }.should == 2
83
+ node.count{|e| e.is_a?(TComment) }.should == 1
84
+ end
85
+
86
+ it "can have single statement body" do
87
+ node = parse(:statement, 'IF cond THEN a := 10; ELSE a := 20; END IF;')
88
+ node.count{|e| e.instance_of?(Statement) }.should == 3
89
+ end
90
+
91
+ it "can have multiple statement body" do
92
+ node = parse(:statement, 'IF cond THEN a := 10; ELSE a := 20; b := 30; END IF;')
93
+ node.count{|e| e.instance_of?(Statement) }.should == 4
94
+ end
95
+ end
96
+
97
+ describe "if .. then .. elsif .. then .. end if" do
98
+ it "parses successfully" do
99
+ node, rest = parse_some(:statement, 'IF cond THEN a := 10; ELSIF cond THEN a := 20; END IF;')
100
+ node.should be_a(Statement)
101
+ rest.should == ''
102
+ end
103
+
104
+ it "can have comment body" do
105
+ node = parse(:statement, 'IF cond THEN a := 10; ELSIF cond THEN /* removed */ END IF;')
106
+ node.count{|e| e.is_a?(TComment) }.should == 1
107
+ node.count{|e| e.instance_of?(Statement) }.should == 2
108
+ end
109
+
110
+ it "can having missing body" do
111
+ node = parse(:statement, 'IF cond THEN a := 10; ELSIF cond THEN END IF;')
112
+ node.count{|e| e.instance_of?(Statement) }.should == 2
113
+ end
114
+
115
+ it "can have single statement body" do
116
+ node = parse(:statement, 'IF cond THEN a := 10; ELSIF cond THEN a := 20; END IF;')
117
+ node.count{|e| e.instance_of?(Statement) }.should == 3
118
+ end
119
+
120
+ it "can have multiple statement body" do
121
+ node = parse(:statement, 'IF cond THEN a := 10; ELSIF cond THEN a := 20; b := 30; END IF;')
122
+ node.count{|e| e.instance_of?(Statement) }.should == 4
123
+ end
124
+
125
+ it "can have many elsif branches" do
126
+ node = parse(:statement, <<-SQL.strip)
127
+ IF cond THEN a := 10;
128
+ ELSIF cond THEN a := 20;
129
+ ELSIF cond THEN a := 30;
130
+ ELSIF cond THEN a := 40;
131
+ ELSIF cond THEN a := 50;
132
+ ELSIF cond THEN a := 60;
133
+ END IF;
134
+ SQL
135
+
136
+ node.count{|e| e.named?(:cond) }.should == 6
137
+ node.count{|e| e.is_a?(If) }.should == 6
138
+ node.count{|e| e.is_a?(If) and e.named?(:else) }.should == 5
139
+ end
140
+
141
+ it "has no Else nodes" do
142
+ node = parse(:statement, 'IF cond THEN a := 10; ELSIF cond THEN a := 20; END IF;')
143
+ node.count{|e| e.is_a?(Else) }.should == 0
144
+ end
145
+ end
146
+
147
+ describe "if .. then .. elsif .. then .. else .. endif" do
148
+ before do
149
+ @text = 'IF cond THEN a := 10; ELSIF cond THEN a := 20; ELSE a := 30; END IF;'
150
+ end
151
+
152
+ it "parses successfully" do
153
+ node, rest = parse_some(:statement, @text)
154
+ node.should be_a(Statement)
155
+ rest.should == ''
156
+ end
157
+
158
+ it "has an If node named 'else'" do
159
+ node = parse(:statement, @text)
160
+ node.count{|e| e.named?(:else) and e.is_a?(If) }.should == 1
161
+ node.find{|e| e.named?(:else) and e.is_a?(If) }.source_text.should == 'ELSIF cond THEN a := 20; ELSE a := 30; '
162
+ end
163
+
164
+ it "has an Else node named 'else'" do
165
+ node = parse(:statement, @text)
166
+ node.count{|e| e.named?(:else) and e.is_a?(Else) }.should == 1
167
+ node.find{|e| e.named?(:else) and e.is_a?(Else) }.source_text.should == 'ELSE a := 30; '
168
+ end
169
+
170
+ it "has two If nodes" do
171
+ node = parse(:statement, @text)
172
+ node.count{|e| e.is_a?(If) }.should == 2
173
+ end
174
+ end
175
+ end
176
+
177
+ end
178
+ end
@@ -0,0 +1,41 @@
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 "loops" do
8
+ describe "for loops" do
9
+ it "can loop over integers" do
10
+ node = parse(:stmtForLoop, 'FOR x IN 0 .. 100 LOOP a := x; END LOOP;')
11
+ node.should be_a(Statement)
12
+
13
+ cond = node.find{|e| e.named?(:cond) }
14
+ cond.source_text.should == '0 .. 100 '
15
+ cond.should be_a(Expression)
16
+ end
17
+
18
+ it "can loop over query results" do
19
+ node = parse(:stmtForLoop, 'FOR x IN SELECT * FROM table LOOP a := x; END LOOP;')
20
+ node.should be_a(Statement)
21
+
22
+ cond = node.find{|e| e.named?(:cond) }
23
+ cond.source_text.should == 'SELECT * FROM table '
24
+ cond.should be_a(Sql)
25
+ end
26
+ end
27
+
28
+ describe "while loops" do
29
+ end
30
+
31
+ describe "unconditional loops" do
32
+ end
33
+
34
+ describe "continue" do
35
+ end
36
+
37
+ describe "break" do
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,71 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'spec_helper'))
2
+
3
+ module Piggly
4
+ describe Parser, "statements" do
5
+ include GrammarHelper
6
+
7
+ describe "SQL statements" do
8
+ it "parses successfully" do
9
+ node, rest = parse_some(:statement, 'SELECT id FROM users;')
10
+ node.should be_a(Statement)
11
+ node.count{|e| e.is_a?(Sql) }.should == 1
12
+ node.find{|e| e.is_a?(Sql) }.source_text.should == 'SELECT id FROM users;'
13
+ rest.should == ''
14
+ end
15
+
16
+ it "must end with a semicolon" do
17
+ lambda{ parse(:statement, 'SELECT id FROM users') }.should raise_error
18
+ lambda{ parse_some(:statement, 'SELECT id FROM users') }.should raise_error
19
+ end
20
+
21
+ it "can contain comments" do
22
+ node = parse(:statement, <<-SQL.strip)
23
+ SELECT INTO user u.id, /* u.name */, p.fist_name, p.last_name
24
+ FROM users u
25
+ INNER JOIN people p ON p.id = u.person_id
26
+ WHERE u.disabled -- can't login
27
+ AND u.id = 100;
28
+ SQL
29
+ sql = node.find{|e| e.is_a?(Sql) }
30
+ sql.count{|e| e.is_a?(TComment) }.should == 2
31
+ end
32
+
33
+ it "can be followed by comments" do
34
+ node, rest = parse_some(:statement, 'SELECT id FROM users; -- comment')
35
+ node.find{|e| e.is_a?(Sql) }.source_text == 'SELECT id FROM users;'
36
+ node.tail.source_text.should == ' -- comment'
37
+ rest.should == ''
38
+ end
39
+
40
+ it "can be followed by whitespace" do
41
+ node, rest = parse_some(:statement, "SELECT id FROM users; \n")
42
+ node.find{|e| e.is_a?(Sql) }.source_text == 'SELECT id FROM users;'
43
+ node.tail.source_text.should == " \n"
44
+ rest.should == ''
45
+ end
46
+
47
+ it "can contain strings" do
48
+ node, rest = parse_some(:statement, <<-SQL.strip)
49
+ SELECT INTO user u.id, u.first_name, u.last_name
50
+ FROM users u
51
+ WHERE first_name ILIKE '%a%'
52
+ OR last_name ILIKE '%b%';
53
+ SQL
54
+ sql = node.find{|e| e.is_a?(Sql) }
55
+ sql.count{|e| e.is_a?(TString) }.should == 2
56
+ end
57
+
58
+ it "can contain strings and comments" do
59
+ node = parse(:statement, <<-SQL.strip)
60
+ a := (SELECT fk, count(*), 'not a statement;'
61
+ FROM dataset
62
+ WHERE id > 100 /* filter out 'reserved' range */
63
+ AND id < 900 -- shouldn't be a string
64
+ OR source_text <> 'alpha /* no comment */;'
65
+ GROUP BY /* pk ; 'abc' */ fk);
66
+ SQL
67
+ end
68
+
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,58 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'spec_helper'))
2
+
3
+ module Piggly
4
+ describe Parser, "tokens" do
5
+ include GrammarHelper
6
+
7
+ describe "comments" do
8
+ it "can begin with -- and terminate at EOF" do
9
+ GrammarHelper::COMMENTS.map{|s| "-- #{s}" }.test_each do |s|
10
+ parse(:tComment, s).should be_a(TComment)
11
+ end
12
+ end
13
+
14
+ it "can begin with -- and terminate at line ending" do
15
+ GrammarHelper::COMMENTS.map{|s| "-- #{s}\n" }.test_each do |s|
16
+ parse(:tComment, s).should be_a(TComment)
17
+ end
18
+
19
+ GrammarHelper::COMMENTS.map{|s| "-- #{s}\n\n" }.test_each do |s|
20
+ node, rest = parse_some(:tComment, s)
21
+ node.should be_a(TComment)
22
+ rest.should == "\n"
23
+ end
24
+
25
+ GrammarHelper::COMMENTS.map{|s| "-- #{s}\nremaining cruft\n" }.test_each do |s|
26
+ node, rest = parse_some(:tComment, s)
27
+ node.should be_a(TComment)
28
+ rest.should == "remaining cruft\n"
29
+ end
30
+ end
31
+
32
+ it "can be /* c-style */" do
33
+ GrammarHelper::COMMENTS.map{|s| "/* #{s} */" }.test_each do |s|
34
+ parse(:tComment, s).should be_a(TComment)
35
+ end
36
+ end
37
+
38
+ it "terminate after */ marker" do
39
+ GrammarHelper::COMMENTS.map{|s| "/* #{s} */remaining cruft\n" }.test_each do |s|
40
+ node, rest = parse_some(:tComment, s)
41
+ node.should be_a(TComment)
42
+ rest.should == "remaining cruft\n"
43
+ end
44
+ end
45
+
46
+ it "cannot be nested" do
47
+ node, rest = parse_some(:tComment, "/* nested /*INLINE*/ comments */")
48
+ node.should be_a(TComment)
49
+ rest.should == " comments */"
50
+
51
+ node, rest = parse_some(:tComment, "-- nested -- line comments")
52
+ node.count{|e| e.is_a?(TComment) }.should == 1
53
+ rest.should == ''
54
+ end
55
+ end
56
+
57
+ end
58
+ end
@@ -0,0 +1,52 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'spec_helper'))
2
+
3
+ module Piggly
4
+ describe Parser, "tokens" do
5
+ include GrammarHelper
6
+
7
+ describe "data types" do
8
+ it "can consist of a single word" do
9
+ %w[int boolean char varchar text date timestamp record].test_each do |s|
10
+ parse(:tType, s).should be_a(TDatatype)
11
+ end
12
+ end
13
+
14
+ it "can have parameterized types" do
15
+ ["numeric(10,2)", "decimal(12,4)", "char(1)", "varchar(100)"].test_each do |s|
16
+ parse(:tType, s).should be_a(TDatatype)
17
+ end
18
+ end
19
+
20
+ it "can end in %ROWTYPE" do
21
+ %w[users%rowtype].test_each do |s|
22
+ parse(:tType, s).should be_a(TDatatype)
23
+ end
24
+ end
25
+
26
+ it "can consist of several words" do
27
+ ["timestamp with time zone", "character varying"].test_each do |s|
28
+ parse(:tType, s).should be_a(TDatatype)
29
+ end
30
+ end
31
+
32
+ it "can specify arrays" do
33
+ ["integer[]", "varchar(10)[]", "numeric(10,2)[]", "timestamp without time zone[]"].test_each do |s|
34
+ parse(:tType, s).should be_a(TDatatype)
35
+ end
36
+ end
37
+
38
+ it "can specify multi-dimensional" do
39
+ ["integer[][]", "char(1)[][]", "character varying[][]"].test_each do |s|
40
+ parse(:tType, s).should be_a(TDatatype)
41
+ end
42
+ end
43
+
44
+ it "are terminated by symbol outside of parentheses" do
45
+ node, rest = parse_some(:tType, "character varying, ")
46
+ node.should be_a(TDatatype)
47
+ rest.should == ', '
48
+ end
49
+ end
50
+
51
+ end
52
+ end
@@ -0,0 +1,58 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'spec_helper'))
2
+
3
+ module Piggly
4
+ describe Parser, "tokens" do
5
+ include GrammarHelper
6
+
7
+ describe "identifiers" do
8
+ it "cannot be a keyword" do
9
+ GrammarHelper::KEYWORDS.test_each do |s|
10
+ lambda{ parse(:tIdentifier, s) }.should raise_error
11
+ end
12
+ end
13
+
14
+ it "can be quoted keyword" do
15
+ GrammarHelper::KEYWORDS.map{|s| '"' + s + '"' }.test_each do |s|
16
+ parse(:tIdentifier, s).should be_a(TIdentifier)
17
+ end
18
+ end
19
+
20
+ it "can begin with a keyword" do
21
+ GrammarHelper::KEYWORDS.select{|s| s =~ /^[a-z]/i }.map{|s| "#{s}xyz" }.test_each do |s|
22
+ parse(:tIdentifier, s).should be_a(TIdentifier)
23
+ end
24
+
25
+ GrammarHelper::KEYWORDS.select{|s| s =~ /^[a-z]/i }.map{|s| "#{s}_xyz" }.test_each do |s|
26
+ parse(:tIdentifier, s).should be_a(TIdentifier)
27
+ end
28
+ end
29
+
30
+ it "can end with a keyword" do
31
+ GrammarHelper::KEYWORDS.select{|s| s =~ /^[a-z]/i }.map{|s| "xyz#{s}" }.test_each do |s|
32
+ parse(:tIdentifier, s).should be_a(TIdentifier)
33
+ end
34
+
35
+ GrammarHelper::KEYWORDS.select{|s| s =~ /^[a-z]/i }.map{|s| "xyz_#{s}" }.test_each do |s|
36
+ parse(:tIdentifier, s).should be_a(TIdentifier)
37
+ end
38
+ end
39
+
40
+ it "can be one single character" do
41
+ %w[_ a b c d e f g h i j k l m n o p q r s t u v w x y z].test_each do |s|
42
+ parse(:tIdentifier, s).should be_a(TIdentifier)
43
+ end
44
+ end
45
+
46
+ it "can contain underscores" do
47
+ %w[_abc abc_ ab_c a_bc ab_cd ab_c_d a_bc_d ab_c_d a_b_c_d a__b__c__d].test_each do |s|
48
+ parse(:tIdentifier, s).should be_a(TIdentifier)
49
+ end
50
+ end
51
+
52
+ it "can contain numbers" do
53
+ parse(:tIdentifier, 'foo9000').should be_a(TIdentifier)
54
+ end
55
+ end
56
+
57
+ end
58
+ end
@@ -0,0 +1,44 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'spec_helper'))
2
+
3
+ module Piggly
4
+ describe Parser, "tokens" do
5
+ include GrammarHelper
6
+
7
+ describe "keywords" do
8
+ it "parse successfully" do
9
+ GrammarHelper::KEYWORDS.test_each do |k|
10
+ parse(:keyword, k).should be_a(TKeyword)
11
+ end
12
+ end
13
+
14
+ it "cannot have trailing characters" do
15
+ GrammarHelper::KEYWORDS.each do |k|
16
+ lambda{ parse(:keyword, "#{k}abc") }.should raise_error
17
+ end
18
+ end
19
+
20
+ it "cannot have preceeding characters" do
21
+ GrammarHelper::KEYWORDS.each do |k|
22
+ lambda{ parse(:keyword, "abc#{k}") }.should raise_error
23
+ end
24
+ end
25
+
26
+ it "are terminated by symbols" do
27
+ GrammarHelper::KEYWORDS.test_each do |k|
28
+ node, rest = parse_some(:keyword, "#{k}+")
29
+ node.should be_a(TKeyword)
30
+ rest.should == '+'
31
+ end
32
+ end
33
+
34
+ it "are terminated by spaces" do
35
+ GrammarHelper::KEYWORDS.test_each do |k|
36
+ node, rest = parse_some(:keyword, "#{k} ")
37
+ node.should be_a(TKeyword)
38
+ rest.should == ' '
39
+ end
40
+ end
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,40 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'spec_helper'))
2
+
3
+ module Piggly
4
+ describe Parser, "tokens" do
5
+ include GrammarHelper
6
+
7
+ describe "labels" do
8
+ it "must be enclosed in << and >>" do
9
+ ['', 'a', 'abc', '<< a', 'a >>'].test_each do |s|
10
+ lambda{ parse(:tLabel, s) }.should raise_error
11
+ end
12
+ end
13
+
14
+ it "can have space padding" do
15
+ %w[a abc _].map{|s| "<< #{s} >>" }.test_each do |s|
16
+ parse(:tLabel, s).should be_a(TLabel)
17
+ end
18
+ end
19
+
20
+ it "can have no space padding" do
21
+ %w[a abc _].map{|s| "<<#{s}>>" }.test_each do |s|
22
+ parse(:tLabel, s).should be_a(TLabel)
23
+ end
24
+ end
25
+
26
+ it "cannot be multiple unquoted words" do
27
+ ["<< a b >>", "<< ab cd >>"].test_each do |s|
28
+ lambda{ parse(:tLabel, s) }.should raise_error
29
+ end
30
+ end
31
+
32
+ it "can be enclosed in double quotes" do
33
+ ['<< "a" >>', '<< "a b" >>', '<< "ab cd" >>'].test_each do |s|
34
+ parse(:tLabel, s).should be_a(TLabel)
35
+ end
36
+ end
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,30 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'spec_helper'))
2
+
3
+ module Piggly
4
+ describe Parser, "tokens" do
5
+ include GrammarHelper
6
+
7
+ describe "literals" do
8
+ it "can be a cast call on a string" do
9
+ node = parse(:tLiteral, "cast('100.00' as numeric(10, 2))")
10
+ node = parse(:tLiteral, "cast( '100.00' as numeric (10, 2) )")
11
+ end
12
+
13
+ it "can be a cast call on a number" do
14
+ node = parse(:tLiteral, "cast(100.00 as character varying(8))")
15
+ node = parse(:tLiteral, "cast( 100.00 as character varying (8) )")
16
+ end
17
+
18
+ it "can be a ::cast on a string" do
19
+ node = parse(:tLiteral, "'100'::int")
20
+ node = parse(:tLiteral, "'100' :: int")
21
+ end
22
+
23
+ it "can be a ::cast on a number" do
24
+ node = parse(:tLiteral, '100 :: varchar')
25
+ node = parse(:tLiteral, '100::varchar')
26
+ end
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,50 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'spec_helper'))
2
+
3
+ module Piggly
4
+ describe Parser, "tokens" do
5
+ include GrammarHelper
6
+
7
+ describe "l-values" do
8
+ it "can be a simple identifier" do
9
+ parse(:lValue, 'id').should be_a(Assignable)
10
+ end
11
+
12
+ it "can be an attribute accessor" do
13
+ parse(:lValue, 'record.id').should be_a(Assignable)
14
+ parse(:lValue, 'public.dataset.id').should be_a(Assignable)
15
+ end
16
+
17
+ it "can use quoted attributes" do
18
+ parse(:lValue, 'record."ID"').should be_a(Assignable)
19
+ parse(:lValue, '"schema name"."table name"."column name"').should be_a(Assignable)
20
+ end
21
+
22
+ it "can be an array accessor" do
23
+ parse(:lValue, 'names[0]').should be_a(Assignable)
24
+ parse(:lValue, 'names[1000]').should be_a(Assignable)
25
+ end
26
+
27
+ it "can contain comments in array accessors" do
28
+ node = parse(:lValue, 'names[3 /* comment */]')
29
+ node.should be_a(Assignable)
30
+ node.count{|e| e.is_a?(TComment) }
31
+
32
+ node = parse(:lValue, "names[9 -- comment \n]")
33
+ node.should be_a(Assignable)
34
+ node.count{|e| e.is_a?(TComment) }
35
+ end
36
+
37
+ it "can be an array accessed by another l-value" do
38
+ parse(:lValue, 'names[face.id]').should be_a(Assignable)
39
+ end
40
+
41
+ it "can be a nested array access"
42
+ # names[faces[0].id].id doesn't work because it requires context-sensitivity [faces[0]
43
+
44
+ it "can be a multi-dimensional array access" do
45
+ parse(:lValue, 'data[10][2][0]').should be_a(Assignable)
46
+ end
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,34 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'spec_helper'))
2
+
3
+ module Piggly
4
+ describe Parser, "tokens" do
5
+ include GrammarHelper
6
+
7
+ describe "numbers" do
8
+ it "can be an integer in binary notation" do
9
+ node = parse(:tNumber, "B'0'")
10
+ end
11
+
12
+ it "can be an integer in hexadecimal notation" do
13
+ node = parse(:tNumber, "X'F'")
14
+ end
15
+
16
+ it "can be an integer in decimal notation" do
17
+ node = parse(:tNumber, '11223344556677889900')
18
+ end
19
+
20
+ it "can be a real number in decimial notation" do
21
+ node = parse(:tNumber, '3.2267')
22
+ end
23
+
24
+ it "can be a real number in scientific notation" do
25
+ node = parse(:tNumber, '1.3e3')
26
+ end
27
+
28
+ it "can be an integer in scientific notation" do
29
+ node = parse(:tNumber, '5e4')
30
+ end
31
+ end
32
+
33
+ end
34
+ end