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.
- data/README.markdown +84 -0
- data/Rakefile +19 -0
- data/bin/piggly +245 -0
- data/lib/piggly/compiler/cache.rb +151 -0
- data/lib/piggly/compiler/pretty.rb +67 -0
- data/lib/piggly/compiler/queue.rb +46 -0
- data/lib/piggly/compiler/tags.rb +244 -0
- data/lib/piggly/compiler/trace.rb +91 -0
- data/lib/piggly/compiler.rb +5 -0
- data/lib/piggly/config.rb +43 -0
- data/lib/piggly/filecache.rb +40 -0
- data/lib/piggly/installer.rb +95 -0
- data/lib/piggly/parser/grammar.tt +747 -0
- data/lib/piggly/parser/nodes.rb +319 -0
- data/lib/piggly/parser/parser.rb +11783 -0
- data/lib/piggly/parser/traversal.rb +48 -0
- data/lib/piggly/parser/treetop_ruby19_patch.rb +17 -0
- data/lib/piggly/parser.rb +67 -0
- data/lib/piggly/profile.rb +87 -0
- data/lib/piggly/reporter/html.rb +207 -0
- data/lib/piggly/reporter/piggly.css +187 -0
- data/lib/piggly/reporter/sortable.js +493 -0
- data/lib/piggly/reporter.rb +21 -0
- data/lib/piggly/task.rb +64 -0
- data/lib/piggly/util.rb +28 -0
- data/lib/piggly/version.rb +15 -0
- data/lib/piggly.rb +18 -0
- data/spec/compiler/cache_spec.rb +9 -0
- data/spec/compiler/pretty_spec.rb +9 -0
- data/spec/compiler/queue_spec.rb +3 -0
- data/spec/compiler/rewrite_spec.rb +3 -0
- data/spec/compiler/tags_spec.rb +285 -0
- data/spec/compiler/trace_spec.rb +173 -0
- data/spec/config_spec.rb +58 -0
- data/spec/filecache_spec.rb +70 -0
- data/spec/fixtures/snippets.sql +158 -0
- data/spec/grammar/expression_spec.rb +302 -0
- data/spec/grammar/statements/assignment_spec.rb +70 -0
- data/spec/grammar/statements/exception_spec.rb +52 -0
- data/spec/grammar/statements/if_spec.rb +178 -0
- data/spec/grammar/statements/loop_spec.rb +41 -0
- data/spec/grammar/statements/sql_spec.rb +71 -0
- data/spec/grammar/tokens/comment_spec.rb +58 -0
- data/spec/grammar/tokens/datatype_spec.rb +52 -0
- data/spec/grammar/tokens/identifier_spec.rb +58 -0
- data/spec/grammar/tokens/keyword_spec.rb +44 -0
- data/spec/grammar/tokens/label_spec.rb +40 -0
- data/spec/grammar/tokens/literal_spec.rb +30 -0
- data/spec/grammar/tokens/lval_spec.rb +50 -0
- data/spec/grammar/tokens/number_spec.rb +34 -0
- data/spec/grammar/tokens/sqlkeywords_spec.rb +45 -0
- data/spec/grammar/tokens/string_spec.rb +54 -0
- data/spec/grammar/tokens/whitespace_spec.rb +40 -0
- data/spec/parser_spec.rb +8 -0
- data/spec/profile_spec.rb +5 -0
- data/spec/reporter/html_spec.rb +0 -0
- data/spec/spec_helper.rb +61 -0
- data/spec/spec_suite.rb +5 -0
- 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
|