piggly 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|