rgviz 0.2 → 0.3
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/lib/rgviz/lexer.rb +605 -0
- data/lib/rgviz/nodes.rb +437 -0
- data/lib/rgviz/parser.rb +403 -0
- data/lib/rgviz/token.rb +83 -0
- data/lib/rgviz/visitor.rb +18 -0
- metadata +8 -3
data/lib/rgviz/parser.rb
ADDED
@@ -0,0 +1,403 @@
|
|
1
|
+
module Rgviz
|
2
|
+
class Parser < Lexer
|
3
|
+
def initialize(string)
|
4
|
+
super
|
5
|
+
@query = Query.new
|
6
|
+
next_token
|
7
|
+
end
|
8
|
+
|
9
|
+
def parse
|
10
|
+
parse_select
|
11
|
+
parse_where
|
12
|
+
parse_group_by
|
13
|
+
parse_pivot
|
14
|
+
parse_order_by
|
15
|
+
parse_limit
|
16
|
+
parse_offset
|
17
|
+
parse_label
|
18
|
+
parse_format
|
19
|
+
parse_options
|
20
|
+
|
21
|
+
check Token::EOF
|
22
|
+
@query
|
23
|
+
end
|
24
|
+
|
25
|
+
def parse_select
|
26
|
+
return if not token_is! Token::Select
|
27
|
+
|
28
|
+
@query.select = Select.new
|
29
|
+
|
30
|
+
return if token_is! Token::STAR
|
31
|
+
|
32
|
+
parse_columns @query.select.columns
|
33
|
+
|
34
|
+
raise ParseException.new "Expecting select columns" if @query.select.columns.empty?
|
35
|
+
end
|
36
|
+
|
37
|
+
def parse_where
|
38
|
+
return if not token_is! Token::Where
|
39
|
+
|
40
|
+
@query.where = Where.new parse_expression
|
41
|
+
end
|
42
|
+
|
43
|
+
def parse_group_by
|
44
|
+
return if not token_is! Token::Group
|
45
|
+
check! Token::By
|
46
|
+
|
47
|
+
@query.group_by = GroupBy.new
|
48
|
+
@query.group_by.columns = parse_columns
|
49
|
+
|
50
|
+
raise ParseException.new "Expecting group by columns" if @query.group_by.columns.empty?
|
51
|
+
end
|
52
|
+
|
53
|
+
def parse_pivot
|
54
|
+
return if not token_is! Token::Pivot
|
55
|
+
|
56
|
+
@query.pivot = Pivot.new
|
57
|
+
@query.pivot.columns = parse_columns
|
58
|
+
|
59
|
+
raise ParseException.new "Expecting pivot columns" if @query.pivot.columns.empty?
|
60
|
+
end
|
61
|
+
|
62
|
+
def parse_order_by
|
63
|
+
return if not token_is! Token::Order
|
64
|
+
check! Token::By
|
65
|
+
|
66
|
+
@query.order_by = OrderBy.new
|
67
|
+
@query.order_by.sorts = parse_sorts
|
68
|
+
|
69
|
+
raise ParseException.new "Expecting order by columns" if @query.order_by.sorts.empty?
|
70
|
+
end
|
71
|
+
|
72
|
+
def parse_limit
|
73
|
+
return if not token_is! Token::Limit
|
74
|
+
|
75
|
+
check Token::INTEGER
|
76
|
+
|
77
|
+
@query.limit = @token.number
|
78
|
+
next_token
|
79
|
+
end
|
80
|
+
|
81
|
+
def parse_offset
|
82
|
+
return if not token_is! Token::Offset
|
83
|
+
|
84
|
+
check Token::INTEGER
|
85
|
+
|
86
|
+
@query.offset = @token.number
|
87
|
+
next_token
|
88
|
+
end
|
89
|
+
|
90
|
+
def parse_label
|
91
|
+
return if not token_is! Token::Label
|
92
|
+
|
93
|
+
@query.labels = []
|
94
|
+
|
95
|
+
column = parse_column
|
96
|
+
raise ParseException.new "Expecting label" unless column
|
97
|
+
|
98
|
+
check Token::STRING
|
99
|
+
@query.labels << Label.new(column, @token.string)
|
100
|
+
next_token
|
101
|
+
|
102
|
+
while token_is! Token::COMMA
|
103
|
+
column = parse_column
|
104
|
+
break unless column
|
105
|
+
|
106
|
+
check Token::STRING
|
107
|
+
@query.labels << Label.new(column, @token.string)
|
108
|
+
next_token
|
109
|
+
end
|
110
|
+
|
111
|
+
raise ParseException.new "Expecting label" if @query.labels.empty?
|
112
|
+
end
|
113
|
+
|
114
|
+
def parse_format
|
115
|
+
return if not token_is! Token::Format
|
116
|
+
|
117
|
+
@query.formats = []
|
118
|
+
|
119
|
+
column = parse_column
|
120
|
+
raise ParseException.new "Expecting format" unless column
|
121
|
+
|
122
|
+
check Token::STRING
|
123
|
+
@query.formats << Format.new(column, @token.string)
|
124
|
+
next_token
|
125
|
+
|
126
|
+
while token_is! Token::COMMA
|
127
|
+
column = parse_column
|
128
|
+
break unless column
|
129
|
+
|
130
|
+
check Token::STRING
|
131
|
+
@query.formats << Format.new(column, @token.string)
|
132
|
+
next_token
|
133
|
+
end
|
134
|
+
|
135
|
+
raise ParseException.new "Expecting format" if @query.formats.empty?
|
136
|
+
end
|
137
|
+
|
138
|
+
def parse_options
|
139
|
+
return if not token_is! Token::Options
|
140
|
+
|
141
|
+
@query.options = []
|
142
|
+
|
143
|
+
while @token.value == Token::NoValues || @token.value == Token::NoFormat
|
144
|
+
@query.options << Option.new(@token.value)
|
145
|
+
next_token
|
146
|
+
end
|
147
|
+
|
148
|
+
raise ParseException.new "Expecting option" if @query.options.empty?
|
149
|
+
raise ParseException.new "Unknown option" if @token.value != Token::EOF
|
150
|
+
end
|
151
|
+
|
152
|
+
def parse_sorts
|
153
|
+
sorts = []
|
154
|
+
|
155
|
+
column = parse_column
|
156
|
+
return sorts unless column
|
157
|
+
|
158
|
+
order = parse_sort_order
|
159
|
+
|
160
|
+
sorts << Sort.new(column, order)
|
161
|
+
while token_is! Token::COMMA
|
162
|
+
column = parse_column
|
163
|
+
break unless column
|
164
|
+
|
165
|
+
order = parse_sort_order
|
166
|
+
sorts << Sort.new(column, order)
|
167
|
+
end
|
168
|
+
sorts
|
169
|
+
end
|
170
|
+
|
171
|
+
def parse_sort_order
|
172
|
+
if token_is! Token::Asc
|
173
|
+
Sort::Asc
|
174
|
+
elsif token_is! Token::Desc
|
175
|
+
Sort::Desc
|
176
|
+
else
|
177
|
+
Sort::Asc
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def parse_expression
|
182
|
+
parse_or_expression
|
183
|
+
end
|
184
|
+
|
185
|
+
def parse_or_expression
|
186
|
+
left = parse_and_expression
|
187
|
+
if token_is! Token::Or
|
188
|
+
right = parse_and_expression
|
189
|
+
BinaryExpression.new left, BinaryExpression::Or, right
|
190
|
+
else
|
191
|
+
left
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def parse_and_expression
|
196
|
+
left = parse_not_expression
|
197
|
+
if token_is! Token::And
|
198
|
+
right = parse_not_expression
|
199
|
+
BinaryExpression.new left, BinaryExpression::And, right
|
200
|
+
else
|
201
|
+
left
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def parse_not_expression
|
206
|
+
if token_is! Token::Not
|
207
|
+
operand = parse_primary_expression
|
208
|
+
UnaryExpression.new UnaryExpression::Not, operand
|
209
|
+
else
|
210
|
+
parse_primary_expression
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def parse_primary_expression
|
215
|
+
case @token.value
|
216
|
+
when Token::LPAREN
|
217
|
+
next_token
|
218
|
+
exp = parse_expression
|
219
|
+
check! Token::RPAREN
|
220
|
+
return exp
|
221
|
+
else
|
222
|
+
left = parse_column
|
223
|
+
raise ParseException.new "Expecting left exp" unless left
|
224
|
+
|
225
|
+
case @token.value
|
226
|
+
when Token::Is
|
227
|
+
next_token
|
228
|
+
if token_is! Token::Not
|
229
|
+
check! Token::Null
|
230
|
+
return UnaryExpression.new UnaryExpression::IsNotNull, left
|
231
|
+
elsif token_is! Token::Null
|
232
|
+
return UnaryExpression.new UnaryExpression::IsNull, left
|
233
|
+
end
|
234
|
+
when Token::EQ, Token::NEQ, Token::LT, Token::LTE, Token::GT, Token::GTE,
|
235
|
+
Token::Contains, Token::Matches, Token::Like
|
236
|
+
operator = @token.value
|
237
|
+
next_token
|
238
|
+
right = parse_column
|
239
|
+
raise ParseException.new "Expecting right exp" unless right
|
240
|
+
return BinaryExpression.new left, operator, right
|
241
|
+
when Token::Starts, Token::Ends
|
242
|
+
operator = "#{@token.value}_with".to_sym
|
243
|
+
next_token
|
244
|
+
check! Token::With
|
245
|
+
right = parse_column
|
246
|
+
raise ParseException.new "Expecting right exp" unless right
|
247
|
+
return BinaryExpression.new left, operator, right
|
248
|
+
else
|
249
|
+
raise ParseException.new "Expecting comparison"
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
def parse_columns(columns = [])
|
255
|
+
column = parse_column
|
256
|
+
return columns unless column
|
257
|
+
|
258
|
+
columns << column
|
259
|
+
while token_is! Token::COMMA
|
260
|
+
column = parse_column
|
261
|
+
break unless column
|
262
|
+
columns << column
|
263
|
+
end
|
264
|
+
columns
|
265
|
+
end
|
266
|
+
|
267
|
+
def parse_column
|
268
|
+
parse_arithmetic_expression
|
269
|
+
end
|
270
|
+
|
271
|
+
def parse_arithmetic_expression
|
272
|
+
parse_summation_or_substraction
|
273
|
+
end
|
274
|
+
|
275
|
+
def parse_summation_or_substraction
|
276
|
+
column1 = parse_multiplication_or_divition
|
277
|
+
case @token.value
|
278
|
+
when Token::PLUS
|
279
|
+
next_token
|
280
|
+
column2 = parse_multiplication_or_divition
|
281
|
+
ScalarFunctionColumn.new ScalarFunctionColumn::Sum, column1, column2
|
282
|
+
when Token::MINUS
|
283
|
+
next_token
|
284
|
+
column2 = parse_multiplication_or_divition
|
285
|
+
ScalarFunctionColumn.new ScalarFunctionColumn::Difference, column1, column2
|
286
|
+
else
|
287
|
+
column1
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
def parse_multiplication_or_divition
|
292
|
+
column1 = parse_atomic_column
|
293
|
+
case @token.value
|
294
|
+
when Token::STAR
|
295
|
+
next_token
|
296
|
+
column2 = parse_atomic_column
|
297
|
+
ScalarFunctionColumn.new ScalarFunctionColumn::Product, column1, column2
|
298
|
+
when Token::SLASH
|
299
|
+
next_token
|
300
|
+
column2 = parse_atomic_column
|
301
|
+
ScalarFunctionColumn.new ScalarFunctionColumn::Quotient, column1, column2
|
302
|
+
else
|
303
|
+
column1
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
def parse_atomic_column
|
308
|
+
case @token.value
|
309
|
+
when Token::ID
|
310
|
+
return value_column(IdColumn.new @token.string)
|
311
|
+
when Token::Contains, Token::Starts, Token::Ends, Token::With,
|
312
|
+
Token::Matches, Token::Like, Token::NoValues, Token::NoFormat,
|
313
|
+
Token::Is, Token::Null
|
314
|
+
return value_column(IdColumn.new @token.value.to_s)
|
315
|
+
when Token::PLUS
|
316
|
+
next_token
|
317
|
+
check Token::INTEGER, Token::DECIMAL
|
318
|
+
return value_column(NumberColumn.new @token.number)
|
319
|
+
when Token::MINUS
|
320
|
+
next_token
|
321
|
+
check Token::INTEGER, Token::DECIMAL
|
322
|
+
return value_column(NumberColumn.new -@token.number)
|
323
|
+
when Token::INTEGER, Token::DECIMAL
|
324
|
+
return value_column(NumberColumn.new @token.number)
|
325
|
+
when Token::LPAREN
|
326
|
+
next_token
|
327
|
+
column = parse_column
|
328
|
+
check! Token::RPAREN
|
329
|
+
return column
|
330
|
+
when Token::STRING
|
331
|
+
return value_column(StringColumn.new @token.string)
|
332
|
+
when Token::False
|
333
|
+
return value_column(BooleanColumn.new false)
|
334
|
+
when Token::True
|
335
|
+
return value_column(BooleanColumn.new true)
|
336
|
+
when Token::Date
|
337
|
+
next_token
|
338
|
+
check Token::STRING
|
339
|
+
return value_column(DateColumn.new Date.parse(@token.string))
|
340
|
+
when Token::DateTime
|
341
|
+
next_token
|
342
|
+
check Token::STRING
|
343
|
+
return value_column(DateTimeColumn.new Time.parse(@token.string))
|
344
|
+
when Token::TimeOfDay
|
345
|
+
next_token
|
346
|
+
check Token::STRING
|
347
|
+
return value_column(TimeOfDayColumn.new Time.parse(@token.string))
|
348
|
+
when Token::Avg, Token::Count, Token::Min, Token::Max, Token::Sum
|
349
|
+
function = @token.value
|
350
|
+
next_token
|
351
|
+
if token_is! Token::LPAREN
|
352
|
+
column = parse_column
|
353
|
+
check! Token::RPAREN
|
354
|
+
return AggregateColumn.new function, column
|
355
|
+
else
|
356
|
+
return IdColumn.new function.to_s
|
357
|
+
end
|
358
|
+
when Token::Year, Token::Month, Token::Day,
|
359
|
+
Token::Hour, Token::Minute, Token::Second, Token::Millisecond,
|
360
|
+
Token::Now, Token::DateDiff, Token::Lower, Token::Upper,
|
361
|
+
Token::Quarter, Token::DayOfWeek, Token::ToDate
|
362
|
+
function = @token.value
|
363
|
+
next_token
|
364
|
+
if token_is! Token::LPAREN
|
365
|
+
columns = parse_columns
|
366
|
+
check! Token::RPAREN
|
367
|
+
return ScalarFunctionColumn.new function, *columns
|
368
|
+
else
|
369
|
+
return IdColumn.new function.to_s
|
370
|
+
end
|
371
|
+
end
|
372
|
+
nil
|
373
|
+
end
|
374
|
+
|
375
|
+
def value_column(col)
|
376
|
+
column = col
|
377
|
+
next_token
|
378
|
+
return column
|
379
|
+
end
|
380
|
+
|
381
|
+
def token_is?(token_value)
|
382
|
+
@token.value == token_value
|
383
|
+
end
|
384
|
+
|
385
|
+
def token_is!(token_value)
|
386
|
+
if @token.value == token_value
|
387
|
+
next_token
|
388
|
+
true
|
389
|
+
else
|
390
|
+
false
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
def check(*token_values)
|
395
|
+
raise ParseException.new "Expecting token #{token_values}" unless token_values.any?{|value| @token.value == value}
|
396
|
+
end
|
397
|
+
|
398
|
+
def check!(*token_values)
|
399
|
+
check *token_values
|
400
|
+
next_token
|
401
|
+
end
|
402
|
+
end
|
403
|
+
end
|
data/lib/rgviz/token.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
module Rgviz
|
2
|
+
class Token
|
3
|
+
|
4
|
+
And = :and
|
5
|
+
Asc = :asc
|
6
|
+
Avg = :avg
|
7
|
+
By = :by
|
8
|
+
Contains = :contains
|
9
|
+
Count = :count
|
10
|
+
Desc = :desc
|
11
|
+
Date = :date
|
12
|
+
DateDiff = :datediff
|
13
|
+
DateTime = :datetime
|
14
|
+
Day = :day
|
15
|
+
DayOfWeek = :dayofweek
|
16
|
+
Ends = :ends
|
17
|
+
False = :false
|
18
|
+
Format = :format
|
19
|
+
Group = :group
|
20
|
+
Hour = :hour
|
21
|
+
Is = :is
|
22
|
+
Label = :label
|
23
|
+
Like = :like
|
24
|
+
Limit = :limit
|
25
|
+
Lower = :lower
|
26
|
+
Matches = :matches
|
27
|
+
Millisecond = :millisecond
|
28
|
+
Min = :min
|
29
|
+
Minute = :minute
|
30
|
+
Max = :max
|
31
|
+
Month = :month
|
32
|
+
Not = :not
|
33
|
+
Now = :now
|
34
|
+
NoFormat = :no_format
|
35
|
+
NoValues = :no_values
|
36
|
+
Null = :null
|
37
|
+
Offset = :offset
|
38
|
+
Options = :options
|
39
|
+
Or = :or
|
40
|
+
Order = :order
|
41
|
+
Pivot = :pivot
|
42
|
+
Quarter = :quarter
|
43
|
+
Second = :second
|
44
|
+
Select = :select
|
45
|
+
Starts = :starts
|
46
|
+
Sum = :sum
|
47
|
+
TimeOfDay = :timeofday
|
48
|
+
Timestamp = :timestamp
|
49
|
+
ToDate = :todate
|
50
|
+
True = :true
|
51
|
+
Upper = :upper
|
52
|
+
Where = :where
|
53
|
+
With = :with
|
54
|
+
Year = :year
|
55
|
+
|
56
|
+
ID = :ID
|
57
|
+
INTEGER = :INTEGER
|
58
|
+
DECIMAL = :DECIMAL
|
59
|
+
STRING = :STRING
|
60
|
+
|
61
|
+
PLUS = :PLUS
|
62
|
+
MINUS = :MINUS
|
63
|
+
STAR = :STAR
|
64
|
+
SLASH = :SLASH
|
65
|
+
|
66
|
+
COMMA = :COMMA
|
67
|
+
LPAREN = :LPAREN
|
68
|
+
RPAREN = :RPAREN
|
69
|
+
EQ = :EQ
|
70
|
+
LT = :LT
|
71
|
+
LTE = :LTE
|
72
|
+
GT = :GT
|
73
|
+
GTE = :GTE
|
74
|
+
NEQ = :NEQ
|
75
|
+
|
76
|
+
EOF = :EOF
|
77
|
+
|
78
|
+
attr_accessor :start
|
79
|
+
attr_accessor :value
|
80
|
+
attr_accessor :string
|
81
|
+
attr_accessor :number
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Rgviz
|
2
|
+
class Visitor
|
3
|
+
['Query', 'Select', 'GroupBy', 'Pivot', 'OrderBy',
|
4
|
+
'Sort', 'Where', 'Label', 'Format', 'Option',
|
5
|
+
'BinaryExpression', 'UnaryExpression',
|
6
|
+
'IdColumn', 'NumberColumn', 'StringColumn',
|
7
|
+
'BooleanColumn', 'DateColumn', 'DateTimeColumn',
|
8
|
+
'TimeOfDayColumn', 'ScalarFunctionColumn', 'AggregateColumn'].each do |name|
|
9
|
+
define_method "visit#{name}" do |node|
|
10
|
+
true
|
11
|
+
end
|
12
|
+
|
13
|
+
define_method "endVisit#{name}" do |node|
|
14
|
+
true
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
metadata
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rgviz
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 13
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: "0.
|
8
|
+
- 3
|
9
|
+
version: "0.3"
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Ary Borenszweig
|
@@ -28,6 +28,11 @@ extra_rdoc_files: []
|
|
28
28
|
|
29
29
|
files:
|
30
30
|
- lib/rgviz.rb
|
31
|
+
- lib/rgviz/lexer.rb
|
32
|
+
- lib/rgviz/nodes.rb
|
33
|
+
- lib/rgviz/parser.rb
|
34
|
+
- lib/rgviz/token.rb
|
35
|
+
- lib/rgviz/visitor.rb
|
31
36
|
has_rdoc: true
|
32
37
|
homepage: http://code.google.com/p/rgviz
|
33
38
|
licenses: []
|