rgviz 0.40 → 0.41
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/memory_executor.rb +617 -0
- metadata +4 -3
@@ -0,0 +1,617 @@
|
|
1
|
+
module Rgviz
|
2
|
+
class MemoryExecutor
|
3
|
+
def initialize(query, rows, types)
|
4
|
+
@query = query
|
5
|
+
@rows = rows
|
6
|
+
@types = types
|
7
|
+
@types_to_indices = {}
|
8
|
+
i = 0
|
9
|
+
@types.each do |k, v|
|
10
|
+
@types_to_indices[k.to_s] = i
|
11
|
+
i += 1
|
12
|
+
end
|
13
|
+
@labels = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def execute(options = {})
|
17
|
+
@query = Parser.parse(@query, options) unless @query.kind_of?(Query)
|
18
|
+
@table = Table.new
|
19
|
+
|
20
|
+
process_labels
|
21
|
+
|
22
|
+
check_has_aggregation
|
23
|
+
|
24
|
+
generate_columns
|
25
|
+
|
26
|
+
if @has_aggregation
|
27
|
+
filter_and_group_rows
|
28
|
+
else
|
29
|
+
filter_rows
|
30
|
+
end
|
31
|
+
|
32
|
+
sort_rows
|
33
|
+
limit_rows
|
34
|
+
generate_rows
|
35
|
+
|
36
|
+
@table
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def process_labels
|
42
|
+
return unless @query.labels
|
43
|
+
|
44
|
+
@query.labels.each do |label|
|
45
|
+
@labels[label.column] = label.label
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def check_has_aggregation
|
50
|
+
@has_aggregation = @query.group_by || @query.pivot || (@query.select? && @query.select.columns.any?{|x| x.class == AggregateColumn})
|
51
|
+
end
|
52
|
+
|
53
|
+
def generate_columns
|
54
|
+
if @query.select?
|
55
|
+
# Select the specified columns
|
56
|
+
i = 0
|
57
|
+
@query.select.columns.each do |col|
|
58
|
+
@table.cols << (Column.new :id => column_id(col, i), :type => column_type(col), :label => column_label(col))
|
59
|
+
i += 1
|
60
|
+
end
|
61
|
+
else
|
62
|
+
# Select all columns
|
63
|
+
@table.cols = @types.map{|k, v| Column.new :id => k, :type => v, :label => k}
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def filter_rows
|
68
|
+
return unless @query.where
|
69
|
+
|
70
|
+
@rows = @rows.reject{|row| row_is_filtered? row}
|
71
|
+
end
|
72
|
+
|
73
|
+
def row_is_filtered?(row)
|
74
|
+
visitor = EvalWhereVisitor.new @types_to_indices, row
|
75
|
+
@query.where.accept visitor
|
76
|
+
!visitor.value
|
77
|
+
end
|
78
|
+
|
79
|
+
def filter_and_group_rows
|
80
|
+
if !@query.group_by && !@query.pivot
|
81
|
+
@rows = [[nil, @rows]] if @has_aggregation
|
82
|
+
return
|
83
|
+
end
|
84
|
+
|
85
|
+
group_columns = []
|
86
|
+
group_columns += @query.group_by.columns if @query.group_by
|
87
|
+
group_columns += @query.pivot.columns if @query.pivot
|
88
|
+
|
89
|
+
groups = Hash.new{|h, k| h[k] = []}
|
90
|
+
@rows.each do |row|
|
91
|
+
next if @query.where && row_is_filtered?(row)
|
92
|
+
|
93
|
+
group = group_columns.map{|col| group_row row, col}
|
94
|
+
groups[group] << row
|
95
|
+
end
|
96
|
+
@rows = groups.to_a
|
97
|
+
end
|
98
|
+
|
99
|
+
def group_row(row, col)
|
100
|
+
visitor = EvalGroupVisitor.new @types_to_indices, row
|
101
|
+
col.accept visitor
|
102
|
+
[col, visitor.value]
|
103
|
+
end
|
104
|
+
|
105
|
+
def sort_rows
|
106
|
+
return unless @query.order_by
|
107
|
+
|
108
|
+
if @has_aggregation
|
109
|
+
sort_aggregated_rows
|
110
|
+
return
|
111
|
+
end
|
112
|
+
|
113
|
+
@rows.sort! do |row1, row2|
|
114
|
+
@sort = 0
|
115
|
+
@query.order_by.sorts.each do |sort|
|
116
|
+
val1 = eval_select sort.column, row1
|
117
|
+
val2 = eval_select sort.column, row2
|
118
|
+
@sort = sort.order == Sort::Asc ? val1 <=> val2 : val2 <=> val1
|
119
|
+
break unless @sort == 0
|
120
|
+
end
|
121
|
+
@sort
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def sort_aggregated_rows
|
126
|
+
@rows.sort! do |row1, row2|
|
127
|
+
g1 = row1[0]
|
128
|
+
g2 = row2[0]
|
129
|
+
@sort = 0
|
130
|
+
@query.order_by.sorts.each do |sort|
|
131
|
+
g1sc = g1.select{|x| x[0] == sort.column}.first
|
132
|
+
if g1sc
|
133
|
+
g2sc = g2.select{|x| x[0] == sort.column}.first
|
134
|
+
@sort = sort.order == Sort::Asc ? g1sc[1] <=> g2sc[1] : g2sc[1] <=> g1sc[1]
|
135
|
+
break unless @sort == 0
|
136
|
+
else
|
137
|
+
raise "Order by column not found: #{sort.column}"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
@sort
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def limit_rows
|
145
|
+
return unless @query.limit
|
146
|
+
|
147
|
+
offset = (@query.offset ? (@query.offset - 1) : 0)
|
148
|
+
@rows = @rows[offset ... offset + @query.limit]
|
149
|
+
end
|
150
|
+
|
151
|
+
def generate_rows
|
152
|
+
if !@has_aggregation
|
153
|
+
@table.rows = @rows.map{|row| generate_row row}
|
154
|
+
return
|
155
|
+
end
|
156
|
+
|
157
|
+
rows = generate_aggregated_rows
|
158
|
+
|
159
|
+
if !@query.pivot
|
160
|
+
rows_to_table rows
|
161
|
+
return
|
162
|
+
end
|
163
|
+
|
164
|
+
uniq_pivots = pivot_rows rows
|
165
|
+
pivot_cols uniq_pivots
|
166
|
+
end
|
167
|
+
|
168
|
+
def pivot_rows(rows)
|
169
|
+
# This is grouping => pivot => [selections]
|
170
|
+
fin = if RUBY_VERSION.start_with?("1.8")
|
171
|
+
if defined? ActiveSupport::OrderedHash
|
172
|
+
ActiveSupport::OrderedHash.new
|
173
|
+
else
|
174
|
+
SimpleOrderedHash.new
|
175
|
+
end
|
176
|
+
else
|
177
|
+
Hash.new
|
178
|
+
end
|
179
|
+
|
180
|
+
# The uniq pivot values
|
181
|
+
uniq_pivots = []
|
182
|
+
|
183
|
+
selection_indices = []
|
184
|
+
|
185
|
+
i = 0
|
186
|
+
@query.select.columns.each do |col|
|
187
|
+
if !@query.group_by || !@query.group_by.columns.include?(col)
|
188
|
+
selection_indices << i
|
189
|
+
end
|
190
|
+
i += 1
|
191
|
+
end
|
192
|
+
|
193
|
+
# Fill fin and uniq_pivots
|
194
|
+
rows.each do |group, row|
|
195
|
+
grouped_by = []
|
196
|
+
pivots = []
|
197
|
+
|
198
|
+
# The grouping key of this result
|
199
|
+
grouped_by = group.select{|g| @query.group_by && @query.group_by.columns.include?(g[0])}
|
200
|
+
|
201
|
+
# The pivots of this result
|
202
|
+
pivots = group.select{|g| @query.pivot.columns.include?(g[0])}
|
203
|
+
|
204
|
+
# The selections of this result
|
205
|
+
selections = selection_indices.map{|i| row[i]}
|
206
|
+
|
207
|
+
uniq_pivots << pivots unless uniq_pivots.include? pivots
|
208
|
+
|
209
|
+
# Now put all this info into fin
|
210
|
+
fin[grouped_by] = {} unless fin[grouped_by]
|
211
|
+
fin[grouped_by][pivots] = selections
|
212
|
+
end
|
213
|
+
|
214
|
+
# Sort the uniq pivots so the results will be sorted for a human
|
215
|
+
uniq_pivots = uniq_pivots.sort_by{|a| a.map{|x| x[1]}.to_s}
|
216
|
+
|
217
|
+
# Create the rows
|
218
|
+
fin.each do |key, value|
|
219
|
+
row = Row.new
|
220
|
+
@table.rows << row
|
221
|
+
|
222
|
+
pivot_i = 0
|
223
|
+
|
224
|
+
@query.select.columns.each do |col|
|
225
|
+
if @query.group_by && @query.group_by.columns.include?(col)
|
226
|
+
matching_col = key.select{|x| x[0] == col}.first
|
227
|
+
row.c << Cell.new(:v => format_value(matching_col[1]))
|
228
|
+
else
|
229
|
+
uniq_pivots.each do |uniq_pivot|
|
230
|
+
u = value[uniq_pivot]
|
231
|
+
row.c << Cell.new(:v => u ? format_value(u[pivot_i]) : nil)
|
232
|
+
end
|
233
|
+
pivot_i += 1
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
uniq_pivots
|
239
|
+
end
|
240
|
+
|
241
|
+
def pivot_cols(uniq_pivots)
|
242
|
+
return unless uniq_pivots.length > 0
|
243
|
+
|
244
|
+
new_cols = []
|
245
|
+
i = 0
|
246
|
+
@query.select.columns.each do |col|
|
247
|
+
id = "c#{i}"
|
248
|
+
label = column_label col
|
249
|
+
type = column_type col
|
250
|
+
|
251
|
+
if @query.group_by && @query.group_by.columns.include?(col)
|
252
|
+
new_cols << Column.new(:id => id, :label => label, :type => type)
|
253
|
+
i += 1
|
254
|
+
else
|
255
|
+
uniq_pivots.each do |uniq_pivot|
|
256
|
+
new_cols << Column.new(:id => "c#{i}", :label => "#{uniq_pivot.map{|x| x[1]}.join ', '} #{label}", :type => type)
|
257
|
+
i += 1
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
@table.cols = new_cols
|
262
|
+
end
|
263
|
+
|
264
|
+
def rows_to_table(rows)
|
265
|
+
@table.rows = rows.map do |grouping, cols|
|
266
|
+
r = Row.new
|
267
|
+
r.c = cols.map do |value|
|
268
|
+
Cell.new :v => format_value(value)
|
269
|
+
end
|
270
|
+
r
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
def generate_aggregated_rows
|
275
|
+
@rows.map do |grouping, rows|
|
276
|
+
ag = []
|
277
|
+
rows_length = rows.length
|
278
|
+
row_i = 0
|
279
|
+
rows.each do |row|
|
280
|
+
compute_row_aggregation row, row_i, rows_length, ag
|
281
|
+
row_i += 1
|
282
|
+
end
|
283
|
+
[grouping, ag]
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
def generate_row(row)
|
288
|
+
r = Row.new
|
289
|
+
if @query.select?
|
290
|
+
r.c = @query.select.columns.map{|col| Cell.new :v => format_value(eval_select col, row)}
|
291
|
+
else
|
292
|
+
r.c = row.map{|v| Cell.new :v => format_value(v)}
|
293
|
+
end
|
294
|
+
r
|
295
|
+
end
|
296
|
+
|
297
|
+
def compute_row_aggregation(row, row_i, rows_length, ag)
|
298
|
+
raise "Must specify a select clause if group by or pivot specified" unless @query.select?
|
299
|
+
|
300
|
+
i = 0
|
301
|
+
@query.select.columns.each do |col|
|
302
|
+
ag[i] = eval_aggregated_select col, row, row_i, rows_length, ag[i]
|
303
|
+
i += 1
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
def format_value(v)
|
308
|
+
case v
|
309
|
+
when FalseClass, TrueClass, Integer, Float
|
310
|
+
v
|
311
|
+
when Date
|
312
|
+
v.strftime "%Y-%m-%d"
|
313
|
+
when Time
|
314
|
+
v.strftime "%Y-%m-%d %H:%M:%S"
|
315
|
+
else
|
316
|
+
v.to_s
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
def column_id(col, i)
|
321
|
+
case col
|
322
|
+
when IdColumn
|
323
|
+
col.name
|
324
|
+
else
|
325
|
+
"c#{i}"
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
def column_type(col)
|
330
|
+
case col
|
331
|
+
when IdColumn
|
332
|
+
i = 0
|
333
|
+
type = @types.select{|x| x[0].to_s == col.name}.first
|
334
|
+
raise "Unknown column #{col}" unless type
|
335
|
+
type[1]
|
336
|
+
when NumberColumn
|
337
|
+
:number
|
338
|
+
when StringColumn
|
339
|
+
:string
|
340
|
+
when BooleanColumn
|
341
|
+
:boolean
|
342
|
+
when DateColumn
|
343
|
+
:date
|
344
|
+
when DateTimeColumn
|
345
|
+
:datetime
|
346
|
+
when TimeOfDayColumn
|
347
|
+
:timeofday
|
348
|
+
when ScalarFunctionColumn
|
349
|
+
case col.function
|
350
|
+
when ScalarFunctionColumn::Now
|
351
|
+
:datetime
|
352
|
+
when ScalarFunctionColumn::ToDate
|
353
|
+
:date
|
354
|
+
when ScalarFunctionColumn::Upper, ScalarFunctionColumn::Lower, ScalarFunctionColumn::Concat
|
355
|
+
:string
|
356
|
+
else
|
357
|
+
:number
|
358
|
+
end
|
359
|
+
when AggregateColumn
|
360
|
+
:number
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
def column_label(col)
|
365
|
+
@labels[col] || col.to_s
|
366
|
+
end
|
367
|
+
|
368
|
+
def eval_select(col, row)
|
369
|
+
eval_aggregated_select col, row, nil, nil, nil
|
370
|
+
end
|
371
|
+
|
372
|
+
def eval_aggregated_select(col, row, row_i, rows_length, ag)
|
373
|
+
visitor = EvalSelectVisitor.new @types_to_indices, row, row_i, rows_length, ag
|
374
|
+
col.accept visitor
|
375
|
+
visitor.value
|
376
|
+
end
|
377
|
+
|
378
|
+
class EvalSelectVisitor < Visitor
|
379
|
+
attr_reader :value
|
380
|
+
|
381
|
+
def initialize(types_to_indices, row, row_i, rows_length, ag)
|
382
|
+
@types_to_indices = types_to_indices
|
383
|
+
@row = row
|
384
|
+
@row_i = row_i
|
385
|
+
@rows_length = rows_length
|
386
|
+
@row_i = row_i
|
387
|
+
@rows_length = rows_length
|
388
|
+
@ag = ag
|
389
|
+
end
|
390
|
+
|
391
|
+
def visit_id_column(col)
|
392
|
+
i = @types_to_indices[col.name]
|
393
|
+
raise "Unknown column #{col}" unless i
|
394
|
+
@value = @row[i]
|
395
|
+
end
|
396
|
+
|
397
|
+
def visit_number_column(col)
|
398
|
+
@value = col.value
|
399
|
+
end
|
400
|
+
|
401
|
+
def visit_string_column(col)
|
402
|
+
@value = col.value
|
403
|
+
end
|
404
|
+
|
405
|
+
def visit_boolean_column(col)
|
406
|
+
@value = col.value
|
407
|
+
end
|
408
|
+
|
409
|
+
def visit_date_column(col)
|
410
|
+
@value = col.value
|
411
|
+
end
|
412
|
+
|
413
|
+
def visit_date_time_column(node)
|
414
|
+
@value = node.value
|
415
|
+
end
|
416
|
+
|
417
|
+
def visit_time_of_day_column(node)
|
418
|
+
@value = node.value.strftime("%H:%M:%S")
|
419
|
+
end
|
420
|
+
|
421
|
+
def visit_scalar_function_column(node)
|
422
|
+
if node.arguments.length >= 1
|
423
|
+
node.arguments[0].accept self; val1 = @value
|
424
|
+
end
|
425
|
+
if node.arguments.length == 2
|
426
|
+
node.arguments[1].accept self; val2 = @value
|
427
|
+
end
|
428
|
+
case node.function
|
429
|
+
when ScalarFunctionColumn::Sum
|
430
|
+
@value = val1 + val2
|
431
|
+
when ScalarFunctionColumn::Difference
|
432
|
+
@value = val1 - val2
|
433
|
+
when ScalarFunctionColumn::Product
|
434
|
+
@value = val1 * val2
|
435
|
+
when ScalarFunctionColumn::Quotient
|
436
|
+
@value = val1 / val2
|
437
|
+
when ScalarFunctionColumn::Concat
|
438
|
+
@value = "#{val1}#{val2}"
|
439
|
+
when ScalarFunctionColumn::DateDiff
|
440
|
+
@value = (val1 - val2).to_i
|
441
|
+
when ScalarFunctionColumn::Year
|
442
|
+
@value = val1.year
|
443
|
+
when ScalarFunctionColumn::Month
|
444
|
+
@value = val1.month
|
445
|
+
when ScalarFunctionColumn::Day
|
446
|
+
@value = val1.day
|
447
|
+
when ScalarFunctionColumn::DayOfWeek
|
448
|
+
@value = val1.wday
|
449
|
+
when ScalarFunctionColumn::Hour
|
450
|
+
@value = val1.hour
|
451
|
+
when ScalarFunctionColumn::Minute
|
452
|
+
@value = val1.min
|
453
|
+
when ScalarFunctionColumn::Second
|
454
|
+
@value = val1.sec
|
455
|
+
when ScalarFunctionColumn::Quarter
|
456
|
+
@value = (val1.month / 3.0).ceil
|
457
|
+
when ScalarFunctionColumn::Millisecond
|
458
|
+
raise "Millisecond is not implemented"
|
459
|
+
when ScalarFunctionColumn::Lower
|
460
|
+
@value = val1.downcase
|
461
|
+
when ScalarFunctionColumn::Upper
|
462
|
+
@value = val1.upcase
|
463
|
+
when ScalarFunctionColumn::Now
|
464
|
+
@value = Time.now
|
465
|
+
when ScalarFunctionColumn::ToDate
|
466
|
+
case @value
|
467
|
+
when Date
|
468
|
+
@value = @value
|
469
|
+
when Time
|
470
|
+
@value = Date.civil @value.year, @value.month, @value.day
|
471
|
+
when Integer
|
472
|
+
seconds = @value / 1000
|
473
|
+
millis = @value % 1000
|
474
|
+
@value = Time.at seconds, millis
|
475
|
+
@value = Date.civil @value.year, @value.month, @value.day
|
476
|
+
end
|
477
|
+
end
|
478
|
+
false
|
479
|
+
end
|
480
|
+
|
481
|
+
def visit_aggregate_column(col)
|
482
|
+
case col.function
|
483
|
+
when AggregateColumn::Sum
|
484
|
+
col.argument.accept self
|
485
|
+
@value += @ag || 0
|
486
|
+
when AggregateColumn::Avg
|
487
|
+
col.argument.accept self
|
488
|
+
@value += @ag || 0
|
489
|
+
@value = @value / @rows_length.to_f if @row_i == @rows_length - 1
|
490
|
+
when AggregateColumn::Count
|
491
|
+
@value = (@ag || 0) + 1
|
492
|
+
when AggregateColumn::Max
|
493
|
+
col.argument.accept self
|
494
|
+
@value = @ag if @ag && @ag > @value
|
495
|
+
when AggregateColumn::Min
|
496
|
+
col.argument.accept self
|
497
|
+
@value = @ag if @ag && @ag < @value
|
498
|
+
end
|
499
|
+
false
|
500
|
+
end
|
501
|
+
end
|
502
|
+
|
503
|
+
class EvalWhereVisitor < EvalSelectVisitor
|
504
|
+
def initialize(types_to_indices, row)
|
505
|
+
@row = row
|
506
|
+
@types_to_indices = types_to_indices
|
507
|
+
end
|
508
|
+
|
509
|
+
def visit_aggregate_column(col)
|
510
|
+
raise "Aggregation function #{col.function} cannot be used in where clause"
|
511
|
+
end
|
512
|
+
|
513
|
+
def visit_binary_expression(node)
|
514
|
+
node.left.accept self; left = @value
|
515
|
+
node.right.accept self; right = @value
|
516
|
+
case node.operator
|
517
|
+
when BinaryExpression::Gt
|
518
|
+
@value = left > right
|
519
|
+
when BinaryExpression::Gte
|
520
|
+
@value = left >= right
|
521
|
+
when BinaryExpression::Lt
|
522
|
+
@value = left < right
|
523
|
+
when BinaryExpression::Lte
|
524
|
+
@value = left <= right
|
525
|
+
when BinaryExpression::Eq
|
526
|
+
@value = left == right
|
527
|
+
when BinaryExpression::Neq
|
528
|
+
@value = left != right
|
529
|
+
when BinaryExpression::Contains
|
530
|
+
@value = !!left[right]
|
531
|
+
when BinaryExpression::StartsWith
|
532
|
+
@value = left.start_with? right
|
533
|
+
when BinaryExpression::EndsWith
|
534
|
+
@value = left.end_with? right
|
535
|
+
when BinaryExpression::Like
|
536
|
+
right.gsub!('%', '.*')
|
537
|
+
right.gsub!('_', '.')
|
538
|
+
right = Regexp.new right
|
539
|
+
@value = !!(left =~ right)
|
540
|
+
when BinaryExpression::Matches
|
541
|
+
right = Regexp.new "^#{right}$"
|
542
|
+
@value = !!(left =~ right)
|
543
|
+
end
|
544
|
+
false
|
545
|
+
end
|
546
|
+
|
547
|
+
def visit_logical_expression(node)
|
548
|
+
values = []
|
549
|
+
node.operands.each do |op|
|
550
|
+
op.accept self
|
551
|
+
values << @value
|
552
|
+
end
|
553
|
+
case node.operator
|
554
|
+
when LogicalExpression::And
|
555
|
+
node.operands.each do |op|
|
556
|
+
op.accept self
|
557
|
+
break unless @value
|
558
|
+
end
|
559
|
+
when LogicalExpression::Or
|
560
|
+
node.operands.each do |op|
|
561
|
+
op.accept self
|
562
|
+
break if @value
|
563
|
+
end
|
564
|
+
end
|
565
|
+
false
|
566
|
+
end
|
567
|
+
|
568
|
+
def visit_unary_expression(node)
|
569
|
+
node.operand.accept self
|
570
|
+
case node.operator
|
571
|
+
when UnaryExpression::Not
|
572
|
+
@value = !@value
|
573
|
+
when UnaryExpression::IsNull
|
574
|
+
@value = @value.nil?
|
575
|
+
when UnaryExpression::IsNotNull
|
576
|
+
@value = !@value.nil?
|
577
|
+
end
|
578
|
+
false
|
579
|
+
end
|
580
|
+
end
|
581
|
+
|
582
|
+
class EvalGroupVisitor < EvalSelectVisitor
|
583
|
+
def initialize(types_to_indices, row)
|
584
|
+
@row = row
|
585
|
+
@types_to_indices = types_to_indices
|
586
|
+
end
|
587
|
+
|
588
|
+
def visit_aggregate_column(node)
|
589
|
+
raise "Can't use aggregation functions in group by"
|
590
|
+
end
|
591
|
+
end
|
592
|
+
|
593
|
+
class SimpleOrderedHash
|
594
|
+
def initialize
|
595
|
+
@keys = []
|
596
|
+
@hash = Hash.new
|
597
|
+
end
|
598
|
+
|
599
|
+
def [](key)
|
600
|
+
@hash[key]
|
601
|
+
end
|
602
|
+
|
603
|
+
def []=(key, value)
|
604
|
+
if !@keys.include?(key)
|
605
|
+
@keys << key
|
606
|
+
end
|
607
|
+
@hash[key] = value
|
608
|
+
end
|
609
|
+
|
610
|
+
def each
|
611
|
+
@keys.each do |key|
|
612
|
+
yield key, @hash[key]
|
613
|
+
end
|
614
|
+
end
|
615
|
+
end
|
616
|
+
end
|
617
|
+
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: 89
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: "0.
|
8
|
+
- 41
|
9
|
+
version: "0.41"
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Ary Borenszweig
|
@@ -31,6 +31,7 @@ files:
|
|
31
31
|
- lib/rgviz/csv_renderer.rb
|
32
32
|
- lib/rgviz/html_renderer.rb
|
33
33
|
- lib/rgviz/lexer.rb
|
34
|
+
- lib/rgviz/memory_executor.rb
|
34
35
|
- lib/rgviz/nodes.rb
|
35
36
|
- lib/rgviz/parser.rb
|
36
37
|
- lib/rgviz/token.rb
|