forthic 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2341 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
4
+ require 'json'
5
+
6
+ require_relative 'forthic_module'
7
+ require_relative 'forthic_error'
8
+ require_relative 'words/word'
9
+ require_relative 'words/push_value_word'
10
+ require_relative 'words/map_word'
11
+ require_relative 'tokenizer'
12
+ require_relative 'code_location'
13
+
14
+ module Forthic
15
+ class GlobalModule < ForthicModule
16
+ attr_accessor :module_id, :literal_handlers
17
+
18
+ def initialize(interp)
19
+ super("<GLOBAL>", interp)
20
+ @module_id = "<GLOBAL>-#{rand(1_000_000)}"
21
+
22
+ # Set default flags
23
+ interp.set_flags(@module_id, {
24
+ with_key: nil,
25
+ push_error: nil,
26
+ comparator: nil,
27
+ push_rest: nil,
28
+ depth: nil,
29
+ interps: 1,
30
+ })
31
+
32
+ @literal_handlers = [
33
+ method(:to_bool),
34
+ method(:to_float),
35
+ method(:to_literal_date),
36
+ method(:to_time),
37
+ method(:to_int),
38
+ ]
39
+
40
+ # --------------------------------------------------
41
+ # Base words
42
+ add_module_word("VARIABLES", method(:word_VARIABLES))
43
+ add_module_word("!", method(:word_bang))
44
+ add_module_word("@", method(:word_at))
45
+ add_module_word("!@", method(:word_bang_at))
46
+ add_module_word("INTERPRET", method(:word_INTERPRET))
47
+ add_module_word("EXPORT", method(:word_EXPORT))
48
+ add_module_word("USE-MODULES", method(:word_USE_MODULES))
49
+ add_module_word("REC", method(:word_REC))
50
+ add_module_word("REC@", method(:word_REC_at))
51
+ add_module_word("<REC!", method(:word_l_REC_bang))
52
+
53
+ # --------------------------------------------------
54
+ # Array/Record words
55
+ add_module_word("APPEND", method(:word_APPEND))
56
+ add_module_word("REVERSE", method(:word_REVERSE))
57
+ add_module_word("UNIQUE", method(:word_UNIQUE))
58
+ add_module_word("<DEL", method(:word_L_DEL))
59
+ add_module_word("RELABEL", method(:word_RELABEL))
60
+ add_module_word("BY-FIELD", method(:word_BY_FIELD))
61
+ add_module_word("GROUP-BY-FIELD", method(:word_GROUP_BY_FIELD))
62
+ add_module_word("GROUP-BY", method(:word_GROUP_BY))
63
+ add_module_word("GROUPS-OF", method(:word_GROUPS_OF))
64
+ add_module_word("INDEX", method(:word_INDEX))
65
+ add_module_word("MAP", method(:word_MAP))
66
+ add_module_word("FOREACH", method(:word_FOREACH))
67
+ add_module_word("INVERT-KEYS", method(:word_INVERT_KEYS))
68
+ add_module_word("ZIP", method(:word_ZIP))
69
+ add_module_word("ZIP-WITH", method(:word_ZIP_WITH))
70
+ add_module_word("KEYS", method(:word_KEYS))
71
+ add_module_word("VALUES", method(:word_VALUES))
72
+ add_module_word("LENGTH", method(:word_LENGTH))
73
+ add_module_word("RANGE", method(:word_RANGE))
74
+ add_module_word("SLICE", method(:word_SLICE))
75
+ add_module_word("DIFFERENCE", method(:word_DIFFERENCE))
76
+ add_module_word("INTERSECTION", method(:word_INTERSECTION))
77
+ add_module_word("UNION", method(:word_UNION))
78
+ add_module_word("SELECT", method(:word_SELECT))
79
+ add_module_word("TAKE", method(:word_TAKE))
80
+ add_module_word("DROP", method(:word_DROP))
81
+ add_module_word("ROTATE", method(:word_ROTATE))
82
+ add_module_word("ARRAY?", method(:word_ARRAY_q))
83
+ add_module_word("SHUFFLE", method(:word_SHUFFLE))
84
+ add_module_word("SORT", method(:word_SORT))
85
+ add_module_word("FIELD-KEY-FUNC", method(:word_FIELD_KEY_FUNC))
86
+ add_module_word("NTH", method(:word_NTH))
87
+ add_module_word("LAST", method(:word_LAST))
88
+ add_module_word("UNPACK", method(:word_UNPACK))
89
+ add_module_word("FLATTEN", method(:word_FLATTEN))
90
+ add_module_word("KEY-OF", method(:word_KEY_OF))
91
+ add_module_word("REDUCE", method(:word_REDUCE))
92
+
93
+ # --------------------------------------------------
94
+ # Stack words
95
+ add_module_word("POP", method(:word_POP))
96
+ add_module_word("DUP", method(:word_DUP))
97
+ add_module_word("SWAP", method(:word_SWAP))
98
+
99
+ # --------------------------------------------------
100
+ # String words
101
+ add_module_word(">STR", method(:word_to_STR))
102
+ add_module_word("CONCAT", method(:word_CONCAT))
103
+ add_module_word("SPLIT", method(:word_SPLIT))
104
+ add_module_word("JOIN", method(:word_JOIN))
105
+ add_module_word("/N", method(:word_slash_N))
106
+ add_module_word("/R", method(:word_slash_R))
107
+ add_module_word("/T", method(:word_slash_T))
108
+ add_module_word("LOWERCASE", method(:word_LOWERCASE))
109
+ add_module_word("UPPERCASE", method(:word_UPPERCASE))
110
+ add_module_word("ASCII", method(:word_ASCII))
111
+ add_module_word("STRIP", method(:word_STRIP))
112
+ add_module_word("REPLACE", method(:word_REPLACE))
113
+ add_module_word("RE-MATCH", method(:word_RE_MATCH))
114
+ add_module_word("RE-MATCH-GROUP", method(:word_RE_MATCH_GROUP))
115
+ add_module_word("RE-MATCH-ALL", method(:word_RE_MATCH_ALL))
116
+
117
+ # --------------------------------------------------
118
+ # Misc words
119
+ add_module_word("NULL", method(:word_NULL))
120
+ add_module_word("DEFAULT", method(:word_DEFAULT))
121
+ add_module_word("*DEFAULT", method(:word_star_DEFAULT))
122
+ add_module_word("<REPEAT", method(:word_l_REPEAT))
123
+ add_module_word(">FIXED", method(:word_to_FIXED))
124
+ add_module_word(">JSON", method(:word_to_JSON))
125
+ add_module_word("JSON>", method(:word_JSON_to))
126
+ add_module_word(".s", method(:word_dot_s))
127
+ add_module_word(".S", method(:word_dot_S))
128
+
129
+ # --------------------------------------------------
130
+ # Date/time words
131
+ add_module_word("AM", method(:word_AM))
132
+ add_module_word("PM", method(:word_PM))
133
+ add_module_word("NOW", method(:word_NOW))
134
+ add_module_word(">TIME", method(:word_to_TIME))
135
+ add_module_word(">DATE", method(:word_to_DATE))
136
+ add_module_word("TODAY", method(:word_TODAY))
137
+ add_module_word("MONDAY", method(:word_MONDAY))
138
+ add_module_word("TUESDAY", method(:word_TUESDAY))
139
+ add_module_word("WEDNESDAY", method(:word_WEDNESDAY))
140
+ add_module_word("THURSDAY", method(:word_THURSDAY))
141
+ add_module_word("FRIDAY", method(:word_FRIDAY))
142
+ add_module_word("SATURDAY", method(:word_SATURDAY))
143
+ add_module_word("SUNDAY", method(:word_SUNDAY))
144
+ add_module_word("ADD-DAYS", method(:word_ADD_DAYS))
145
+ add_module_word("SUBTRACT-DATES", method(:word_SUBTRACT_DATES))
146
+ add_module_word("DATE>STR", method(:word_DATE_to_STR))
147
+ add_module_word("TIME>STR", method(:word_TIME_to_STR))
148
+ add_module_word("DATE-TIME>DATETIME", method(:word_DATE_TIME_to_DATETIME))
149
+ add_module_word("DATETIME>TIMESTAMP", method(:word_DATETIME_to_TIMESTAMP))
150
+ add_module_word("TIMESTAMP>DATETIME", method(:word_TIMESTAMP_to_DATETIME))
151
+ add_module_word("STR>DATETIME", method(:word_STR_to_DATETIME))
152
+ add_module_word("STR>TIMESTAMP", method(:word_STR_to_TIMESTAMP))
153
+
154
+ # --------------------------------------------------
155
+ # Math words
156
+ add_module_word("+", method(:word_plus))
157
+ add_module_word("-", method(:word_minus))
158
+ add_module_word("*", method(:word_times))
159
+ add_module_word("/", method(:word_divide_by))
160
+ add_module_word("ADD", method(:word_plus))
161
+ add_module_word("SUBTRACT", method(:word_minus))
162
+ add_module_word("MULTIPLY", method(:word_times))
163
+ add_module_word("DIVIDE", method(:word_divide_by))
164
+ add_module_word("MOD", method(:word_MOD))
165
+ add_module_word("MEAN", method(:word_MEAN))
166
+ add_module_word("MAX", method(:word_MAX))
167
+ add_module_word("MIN", method(:word_MIN))
168
+ add_module_word("ROUND", method(:word_ROUND))
169
+ add_module_word("==", method(:word_equal_equal))
170
+ add_module_word("!=", method(:word_not_equal))
171
+ add_module_word(">", method(:word_greater_than))
172
+ add_module_word(">=", method(:word_greater_than_or_equal))
173
+ add_module_word("<", method(:word_less_than))
174
+ add_module_word("<=", method(:word_less_than_or_equal))
175
+ add_module_word("OR", method(:word_OR))
176
+ add_module_word("AND", method(:word_AND))
177
+ add_module_word("NOT", method(:word_NOT))
178
+ add_module_word("IN", method(:word_IN))
179
+ add_module_word("ANY", method(:word_ANY))
180
+ add_module_word("ALL", method(:word_ALL))
181
+ add_module_word(">BOOL", method(:word_to_BOOL))
182
+ add_module_word(">INT", method(:word_to_INT))
183
+ add_module_word(">FLOAT", method(:word_to_FLOAT))
184
+ add_module_word("RANGE-INDEX", method(:word_RANGE_INDEX))
185
+ add_module_word("INFINITY", method(:word_INFINITY))
186
+
187
+ # --------------------------------------------------
188
+ # Flag words
189
+ add_module_word("!PUSH-ERROR", method(:word_bang_PUSH_ERROR))
190
+ add_module_word("!WITH-KEY", method(:word_bang_WITH_KEY))
191
+ add_module_word("!COMPARATOR", method(:word_bang_COMPARATOR))
192
+ add_module_word("!PUSH-REST", method(:word_bang_PUSH_REST))
193
+ add_module_word("!DEPTH", method(:word_bang_DEPTH))
194
+ add_module_word("!INTERPS", method(:word_bang_INTERPS))
195
+
196
+ # --------------------------------------------------
197
+ # Profiling words
198
+ add_module_word("PROFILE-START", method(:word_PROFILE_START))
199
+ add_module_word("PROFILE-TIMESTAMP", method(:word_PROFILE_TIMESTAMP))
200
+ add_module_word("PROFILE-END", method(:word_PROFILE_END))
201
+ add_module_word("PROFILE-DATA", method(:word_PROFILE_DATA))
202
+
203
+ # --------------------------------------------------
204
+ # Ruby-specific words
205
+ add_module_word(">SYM", method(:word_to_SYM))
206
+
207
+ end
208
+
209
+ def find_word(name)
210
+ result = super(name)
211
+ result ||= find_literal_word(name)
212
+ result
213
+ end
214
+
215
+ def find_literal_word(string)
216
+ value = nil
217
+ @literal_handlers.each do |handler|
218
+ value = handler.call(string)
219
+ return PushValueWord.new(string, value) unless value.nil?
220
+ end
221
+ nil
222
+ end
223
+
224
+ # Literal handlers
225
+ def to_bool(str_val)
226
+ return true if str_val == "TRUE"
227
+ return false if str_val == "FALSE"
228
+ nil
229
+ end
230
+
231
+ def to_int(str_val)
232
+ result = Integer(str_val) rescue nil
233
+ result
234
+ end
235
+
236
+ def to_float(str_val)
237
+ return nil unless str_val.include?(".")
238
+ result = Float(str_val) rescue nil
239
+ result
240
+ end
241
+
242
+ def to_literal_date(str_val)
243
+ match = str_val.match(/(\d{4})-(\d{2})-(\d{2})/)
244
+ return nil unless match
245
+
246
+ year = match[1].to_i
247
+ month = match[2].to_i
248
+ day = match[3].to_i
249
+ result = Date.new(year, month, day)
250
+ result
251
+ end
252
+
253
+ def to_time(str_val)
254
+ match = str_val.match(/(\d{1,2}):(\d{2})/)
255
+ return nil unless match
256
+
257
+ hours = match[1].to_i
258
+ minutes = match[2].to_i
259
+ return nil if hours > 23 || minutes >= 60
260
+
261
+ result = Time.now
262
+ result = Time.new(result.year, result.month, result.day, hours, minutes, 0)
263
+ result
264
+ end
265
+
266
+ # Convenience function to create element word handlers
267
+ def make_element_word(element_name)
268
+ proc do |interp|
269
+ interp.stack_push(element_name)
270
+ interp.run("Element")
271
+ end
272
+ end
273
+
274
+ def add_element_word(element_name)
275
+ add_module_word(element_name, make_element_word(element_name))
276
+ end
277
+
278
+ # Words
279
+
280
+ # ( varnames -- )
281
+ def word_VARIABLES(interp)
282
+ varnames = interp.stack_pop
283
+ module_ = interp.cur_module
284
+ varnames.each do |v|
285
+ if v.match(/__.*/)
286
+ raise ForthicError.new(
287
+ "global_module-696",
288
+ "word_VARIABLES: variable names cannot begin with '__': '#{v}'",
289
+ "This is a reserved variable naming convention"
290
+ )
291
+ end
292
+ module_.add_variable(v)
293
+ end
294
+ end
295
+
296
+ # ( value variable -- )
297
+ def word_bang(interp)
298
+ variable = interp.stack_pop
299
+ value = interp.stack_pop
300
+ variable.value = value
301
+ end
302
+
303
+ # ( variable -- value )
304
+ def word_at(interp)
305
+ variable = interp.stack_pop
306
+ interp.stack_push(variable.value)
307
+ end
308
+
309
+ # ( value variable -- value )
310
+ def word_bang_at(interp)
311
+ variable = interp.stack_pop
312
+ value = interp.stack_pop
313
+ variable.value = value
314
+ interp.stack_push(variable.value)
315
+ end
316
+
317
+ # ( string -- )
318
+ def word_INTERPRET(interp)
319
+ string = interp.stack_pop
320
+ string_location = interp.get_string_location
321
+ interp.run(string, string_location) if string
322
+ end
323
+
324
+ # ( names -- )
325
+ def word_EXPORT(interp)
326
+ names = interp.stack_pop
327
+ interp.cur_module.add_exportable(names)
328
+ end
329
+
330
+ # ( names -- )
331
+ def word_USE_MODULES(interp)
332
+ names = interp.stack_pop
333
+
334
+ names.each do |name|
335
+ module_name, prefix = name.is_a?(Array) ? name : [name, name]
336
+ mod = interp.find_module(module_name)
337
+ interp.get_app_module.import_module(prefix, mod, interp)
338
+ end
339
+ end
340
+
341
+ # ( key_vals -- rec )
342
+ def word_REC(interp)
343
+ key_vals = interp.stack_pop || []
344
+ result = {}
345
+ key_vals.each do |pair|
346
+ key, val = pair
347
+ result[key] = val
348
+ end
349
+ interp.stack_push(result)
350
+ end
351
+
352
+ # ( rec field -- value )
353
+ # ( rec fields -- value )
354
+ def word_REC_at(interp)
355
+ field = interp.stack_pop
356
+ rec = interp.stack_pop
357
+
358
+ if rec.nil?
359
+ interp.stack_push(nil)
360
+ return
361
+ end
362
+
363
+ fields = field.is_a?(Array) ? field : [field]
364
+ result = drill_for_value(rec, fields)
365
+ interp.stack_push(result)
366
+ end
367
+
368
+
369
+ # ( rec value field -- rec )
370
+ def word_l_REC_bang(interp)
371
+ field = interp.stack_pop
372
+ value = interp.stack_pop
373
+ rec = interp.stack_pop || {}
374
+
375
+ fields = field.is_a?(Array) ? field : [field]
376
+
377
+ cur_rec = rec
378
+ fields[0...-1].each do |f|
379
+ cur_rec[f] ||= {}
380
+ cur_rec = cur_rec[f]
381
+ end
382
+
383
+ cur_rec[fields[-1]] = value
384
+ interp.stack_push(rec)
385
+ end
386
+
387
+ # ( array item -- array )
388
+ # ( record key/val -- record )
389
+ def word_APPEND(interp)
390
+ item = interp.stack_pop
391
+ result = interp.stack_pop
392
+
393
+ result ||= []
394
+
395
+ if result.is_a?(Array)
396
+ result.push(item)
397
+ else
398
+ result[item[0]] = item[1]
399
+ end
400
+
401
+ interp.stack_push(result)
402
+ end
403
+
404
+ # ( array -- array )
405
+ # ( record -- record )
406
+ def word_REVERSE(interp)
407
+ result = interp.stack_pop
408
+
409
+ if result.nil?
410
+ interp.stack_push(result)
411
+ return
412
+ end
413
+
414
+ result = result.reverse if result.is_a?(Array)
415
+
416
+ interp.stack_push(result)
417
+ end
418
+
419
+ # ( array -- array )
420
+ def word_UNIQUE(interp)
421
+ container = interp.stack_pop
422
+
423
+ if container.nil?
424
+ interp.stack_push(container)
425
+ return
426
+ end
427
+
428
+ result = container
429
+ result = container.uniq if container.is_a?(Array)
430
+
431
+ interp.stack_push(result)
432
+ end
433
+
434
+ # ( array index -- array )
435
+ # ( record key -- record )
436
+ def word_L_DEL(interp)
437
+ key = interp.stack_pop
438
+ container = interp.stack_pop
439
+
440
+ if container.nil?
441
+ interp.stack_push(container)
442
+ return
443
+ end
444
+
445
+ if container.is_a?(Array)
446
+ container.delete_at(key)
447
+ else
448
+ container.delete(key)
449
+ end
450
+
451
+ interp.stack_push(container)
452
+ end
453
+
454
+ # ( array old_keys new_keys -- array )
455
+ # ( record old_keys new_keys -- record )
456
+ def word_RELABEL(interp)
457
+ new_keys = interp.stack_pop
458
+ old_keys = interp.stack_pop
459
+ container = interp.stack_pop
460
+
461
+ if container.nil?
462
+ interp.stack_push(container)
463
+ return
464
+ end
465
+
466
+ raise "RELABEL: old_keys and new_keys must be same length" if old_keys.length != new_keys.length
467
+
468
+ new_to_old = {}
469
+ old_keys.each_with_index do |old_key, i|
470
+ new_to_old[new_keys[i]] = old_key
471
+ end
472
+
473
+ result = if container.is_a?(Array)
474
+ new_to_old.keys.sort.map { |k| container[new_to_old[k]] }
475
+ else
476
+ new_to_old.each_with_object({}) { |(new_key, old_key), res| res[new_key] = container[old_key] }
477
+ end
478
+
479
+ interp.stack_push(result)
480
+ end
481
+
482
+ # ( array field -- field_to_item )
483
+ # ( record field -- field_to_item )
484
+ def word_BY_FIELD(interp)
485
+ field = interp.stack_pop
486
+ container = interp.stack_pop
487
+
488
+ container ||= []
489
+
490
+ values = if container.is_a?(Array)
491
+ container
492
+ else
493
+ container.values
494
+ end
495
+
496
+ result = {}
497
+ values.each do |v|
498
+ result[v[field]] = v if v
499
+ end
500
+
501
+ interp.stack_push(result)
502
+ end
503
+
504
+ # ( array field -- field_to_items )
505
+ # ( record field -- field_to_items )
506
+ def word_GROUP_BY_FIELD(interp)
507
+ field = interp.stack_pop
508
+ container = interp.stack_pop
509
+
510
+ container ||= []
511
+
512
+ values = if container.is_a?(Array)
513
+ container
514
+ else
515
+ container.values
516
+ end
517
+
518
+ result = {}
519
+ values.each do |v|
520
+ field_val = v[field]
521
+ if field_val.is_a?(Array)
522
+ field_val.each do |fv|
523
+ result[fv] ||= []
524
+ result[fv].push(v)
525
+ end
526
+ else
527
+ result[field_val] ||= []
528
+ result[field_val].push(v)
529
+ end
530
+ end
531
+
532
+ interp.stack_push(result)
533
+ end
534
+
535
+ # ( array forthic -- group_to_items )
536
+ # ( record forthic -- group_to_items )
537
+ def word_GROUP_BY(interp)
538
+ forthic = interp.stack_pop
539
+ string_location = interp.get_string_location
540
+
541
+ container = interp.stack_pop
542
+
543
+ flags = interp.get_flags(self.module_id)
544
+
545
+ container ||= []
546
+
547
+ keys, values = if container.is_a?(Array)
548
+ [Array.new(container.size) { |i| i }, container]
549
+ else
550
+ [container.keys, container.values]
551
+ end
552
+
553
+ result = {}
554
+ values.each_with_index do |value, i|
555
+ key = keys[i]
556
+ interp.stack_push(key) if flags[:with_key]
557
+ interp.stack_push(value)
558
+ interp.run(forthic, string_location)
559
+ group = interp.stack_pop
560
+ result[group] ||= []
561
+ result[group].push(value)
562
+ end
563
+
564
+ interp.stack_push(result)
565
+ end
566
+
567
+ # ( array n -- arrays )
568
+ # ( record n -- records )
569
+ def word_GROUPS_OF(interp)
570
+ size = interp.stack_pop
571
+ container = interp.stack_pop
572
+ raise "GROUPS-OF requires group size > 0" if size <= 0
573
+
574
+ container ||= []
575
+ result = if container.is_a?(Array)
576
+ group_items(container, size)
577
+ else
578
+ keys = container.keys
579
+ key_groups = group_items(keys, size)
580
+ key_groups.map { |ks| extract_rec(container, ks) }
581
+ end
582
+
583
+ interp.stack_push(result)
584
+ end
585
+
586
+ # ( array forthic -- record )
587
+ def word_INDEX(interp)
588
+ forthic = interp.stack_pop
589
+ string_location = interp.get_string_location
590
+ items = interp.stack_pop
591
+
592
+ if !items
593
+ interp.stack_push(items)
594
+ return
595
+ end
596
+
597
+ result = {}
598
+ items.each do |item|
599
+ interp.stack_push(item)
600
+ interp.run(forthic, string_location)
601
+ keys = interp.stack_pop
602
+ keys.each do |k|
603
+ lowercased_key = k.downcase
604
+ if result[lowercased_key]
605
+ result[lowercased_key].push(item)
606
+ else
607
+ result[lowercased_key] = [item]
608
+ end
609
+ end
610
+ end
611
+ interp.stack_push(result)
612
+ end
613
+
614
+ # ( items forthic -- [ ? ] )
615
+ def word_MAP(interp)
616
+ forthic = interp.stack_pop
617
+ string_location = interp.get_string_location
618
+ items = interp.stack_pop
619
+ flags = interp.get_flags(self.module_id)
620
+
621
+ map_word = MapWord.new(items, forthic, string_location, flags)
622
+ map_word.execute(interp)
623
+ end
624
+
625
+ # ( items forthic -- )
626
+ def word_FOREACH(interp)
627
+ forthic = interp.stack_pop
628
+ string_location = interp.get_string_location
629
+
630
+ items = interp.stack_pop
631
+ flags = interp.get_flags(self.module_id)
632
+
633
+ if !items
634
+ interp.stack_push(items)
635
+ return
636
+ end
637
+
638
+ errors = []
639
+ if items.is_a?(Array)
640
+ items.each_with_index do |item, i|
641
+ if flags[:with_key]
642
+ interp.stack_push(i)
643
+ end
644
+ interp.stack_push(item)
645
+ if flags[:push_error]
646
+ errors.push(execute_returning_error(interp, forthic, string_location))
647
+ else
648
+ interp.run(forthic, string_location)
649
+ end
650
+ end
651
+ else
652
+ items.each do |k, item|
653
+ if flags[:with_key]
654
+ interp.stack_push(k)
655
+ end
656
+ interp.stack_push(item)
657
+ if flags[:push_error]
658
+ errors.push(execute_returning_error(interp, forthic, string_location))
659
+ else
660
+ interp.run(forthic, string_location)
661
+ end
662
+ end
663
+ end
664
+
665
+ if flags[:push_error]
666
+ interp.stack_push(errors)
667
+ end
668
+ end
669
+
670
+ # ( record -- record )
671
+ def word_INVERT_KEYS(interp)
672
+ record = interp.stack_pop
673
+ result = {}
674
+ record.each do |first_key, sub_record|
675
+ sub_record.each do |second_key, value|
676
+ result[second_key] ||= {}
677
+ result[second_key][first_key] = value
678
+ end
679
+ end
680
+ interp.stack_push(result)
681
+ end
682
+
683
+ # ( array1 array2 -- array )
684
+ # ( record1 record2 -- record )
685
+ def word_ZIP(interp)
686
+ container2 = interp.stack_pop
687
+ container1 = interp.stack_pop
688
+
689
+ container1 ||= []
690
+ container2 ||= []
691
+
692
+ result = if container2.is_a?(Array)
693
+ container1.map.with_index { |v, i| [v, container2[i]] }
694
+ else
695
+ container1.each_with_object({}) { |(k, v), res| res[k] = [v, container2[k]] }
696
+ end
697
+
698
+ interp.stack_push(result)
699
+ end
700
+
701
+ # ( array1 array2 forthic -- array )
702
+ # ( record1 record2 forthic -- record )
703
+ def word_ZIP_WITH(interp)
704
+ forthic = interp.stack_pop
705
+ string_location = interp.get_string_location
706
+
707
+ container2 = interp.stack_pop
708
+ container1 = interp.stack_pop
709
+
710
+ container1 ||= []
711
+ container2 ||= []
712
+
713
+ result = if container2.is_a?(Array)
714
+ container1.map.with_index do |v, i|
715
+ interp.stack_push(v)
716
+ interp.stack_push(container2[i])
717
+ interp.run(forthic, string_location)
718
+ interp.stack_pop
719
+ end
720
+ else
721
+ container1.each_with_object({}) do |(k, v), res|
722
+ interp.stack_push(v)
723
+ interp.stack_push(container2[k])
724
+ interp.run(forthic, string_location)
725
+ res[k] = interp.stack_pop
726
+ end
727
+ end
728
+
729
+ interp.stack_push(result)
730
+ end
731
+
732
+ # ( array -- array )
733
+ # ( record -- array )
734
+ def word_KEYS(interp)
735
+ container = interp.stack_pop
736
+
737
+ container ||= []
738
+
739
+ result = if container.is_a?(Array)
740
+ container.each_index.to_a
741
+ else
742
+ container.keys
743
+ end
744
+
745
+ interp.stack_push(result)
746
+ end
747
+
748
+ # ( array -- array )
749
+ # ( record -- array )
750
+ def word_VALUES(interp)
751
+ container = interp.stack_pop
752
+
753
+ container ||= []
754
+
755
+ result = if container.is_a?(Array)
756
+ container
757
+ else
758
+ container.values
759
+ end
760
+
761
+ interp.stack_push(result)
762
+ end
763
+
764
+ # ( array -- length )
765
+ # ( record -- length )
766
+ def word_LENGTH(interp)
767
+ container = interp.stack_pop
768
+
769
+ if container.nil?
770
+ interp.stack_push(container)
771
+ return
772
+ end
773
+
774
+ result = container.length
775
+ interp.stack_push(result)
776
+ end
777
+
778
+ # ( array start end -- array )
779
+ # ( record start end -- record )
780
+ def word_RANGE(interp)
781
+ fend = interp.stack_pop
782
+ fend_string_location = interp.get_string_location
783
+
784
+ fstart = interp.stack_pop
785
+ fstart_string_location = interp.get_string_location
786
+
787
+ array = interp.stack_pop
788
+
789
+ array ||= []
790
+
791
+ start_found = false
792
+ end_found = false
793
+
794
+ start_index = nil
795
+ end_index = nil
796
+
797
+ array.each_with_index do |item, index|
798
+ unless start_found
799
+ interp.stack_push(item)
800
+ interp.run(fstart, fstart_string_location)
801
+ start_found = interp.stack_pop
802
+ start_index = index if start_found
803
+ end
804
+
805
+ if start_found && !end_found
806
+ interp.stack_push(item)
807
+ interp.run(fend, fend_string_location)
808
+ end_found = interp.stack_pop
809
+ end_index = index if end_found
810
+ break if end_found
811
+ end
812
+ end
813
+
814
+ interp.stack_push([start_index, end_index])
815
+ end
816
+
817
+ # ( array start end -- array )
818
+ # ( record start end -- record )
819
+ def word_SLICE(interp)
820
+ fend = interp.stack_pop.to_i
821
+ fstart = interp.stack_pop.to_i
822
+ container = interp.stack_pop
823
+
824
+ container ||= []
825
+
826
+ length = container.length
827
+
828
+ start = normalize_index(fstart, length)
829
+ fend = normalize_index(fend, length)
830
+
831
+ step = 1
832
+ step = -1 if start > fend
833
+
834
+ indexes = [start]
835
+ indexes = [] if start < 0 || start >= length
836
+
837
+ while start != fend
838
+ start += step
839
+ if start < 0 || start >= length
840
+ indexes.push(nil)
841
+ else
842
+ indexes.push(start)
843
+ end
844
+ end
845
+
846
+ result = if container.is_a?(Array)
847
+ indexes.map { |i| i.nil? ? nil : container[i] }
848
+ else
849
+ keys = container.keys.sort
850
+ indexes.each_with_object({}) do |i, res|
851
+ next if i.nil?
852
+
853
+ k = keys[i]
854
+ res[k] = container[k]
855
+ end
856
+ end
857
+
858
+ interp.stack_push(result)
859
+ end
860
+
861
+ # ( larray rarray -- array )
862
+ # ( lrecord rrecord -- record )
863
+ def word_DIFFERENCE(interp)
864
+ rcontainer = interp.stack_pop
865
+ lcontainer = interp.stack_pop
866
+
867
+ lcontainer ||= []
868
+ rcontainer ||= []
869
+
870
+ result = if rcontainer.is_a?(Array)
871
+ lcontainer - rcontainer
872
+ else
873
+ diff = lcontainer.keys - rcontainer.keys
874
+ diff.each_with_object({}) { |k, res| res[k] = lcontainer[k] }
875
+ end
876
+
877
+ interp.stack_push(result)
878
+ end
879
+
880
+ # ( larray rarray -- array )
881
+ # ( lrecord rrecord -- record )
882
+ def word_INTERSECTION(interp)
883
+ rcontainer = interp.stack_pop
884
+ lcontainer = interp.stack_pop
885
+
886
+ lcontainer ||= []
887
+ rcontainer ||= []
888
+
889
+ result = if rcontainer.is_a?(Array)
890
+ lcontainer & rcontainer
891
+ else
892
+ lkeys = lcontainer.keys
893
+ rkeys = rcontainer.keys
894
+
895
+ intersect = lkeys & rkeys
896
+ intersect.each_with_object({}) { |k, res| res[k] = lcontainer[k] }
897
+ end
898
+
899
+ interp.stack_push(result)
900
+ end
901
+
902
+ # ( larray rarray -- array )
903
+ # ( lrecord rrecord -- record )
904
+ def word_UNION(interp)
905
+ rcontainer = interp.stack_pop
906
+ lcontainer = interp.stack_pop
907
+
908
+ lcontainer ||= []
909
+ rcontainer ||= []
910
+
911
+ result = if rcontainer.is_a?(Array)
912
+ lcontainer | rcontainer
913
+ else
914
+ lkeys = lcontainer.keys
915
+ rkeys = rcontainer.keys
916
+
917
+ keys = lkeys | rkeys
918
+ keys.each_with_object({}) do |k, res|
919
+ val = lcontainer[k]
920
+ val = rcontainer[k] if val.nil?
921
+ res[k] = val
922
+ end
923
+ end
924
+
925
+ interp.stack_push(result)
926
+ end
927
+
928
+ # ( larray forthic -- array )
929
+ # ( lrecord forthic -- record )
930
+ def word_SELECT(interp)
931
+ forthic = interp.stack_pop
932
+ string_location = interp.get_string_location
933
+
934
+ container = interp.stack_pop
935
+ flags = interp.get_flags(self.module_id)
936
+
937
+ if !container
938
+ interp.stack_push(container)
939
+ return
940
+ end
941
+
942
+ result = nil
943
+ if container.is_a?(Array)
944
+ result = []
945
+ container.each_with_index do |item, i|
946
+ interp.stack_push(i) if flags[:with_key]
947
+ interp.stack_push(item)
948
+ interp.run(forthic, string_location)
949
+ should_select = interp.stack_pop
950
+ result.push(item) if should_select
951
+ end
952
+ else
953
+ result = {}
954
+ container.each do |k, v|
955
+ interp.stack_push(k) if flags[:with_key]
956
+ interp.stack_push(v)
957
+ interp.run(forthic, string_location)
958
+ should_select = interp.stack_pop
959
+ result[k] = v if should_select
960
+ end
961
+ end
962
+ interp.stack_push(result)
963
+ end
964
+
965
+ # ( array n -- taken rest )
966
+ # ( record n -- taken rest )
967
+ def word_TAKE(interp)
968
+ n = interp.stack_pop
969
+ container = interp.stack_pop
970
+ flags = interp.get_flags(self.module_id)
971
+
972
+ container ||= []
973
+
974
+ taken = nil
975
+ rest = nil
976
+
977
+ if container.is_a?(Array)
978
+ taken = container[0...n]
979
+ rest = container[n..-1]
980
+ else
981
+ keys = container.keys.sort
982
+ taken_keys = keys[0...n]
983
+ rest_keys = keys[n..-1]
984
+ taken = taken_keys.each_with_object({}) { |k, res| res[k] = container[k] }
985
+ rest = rest_keys.each_with_object({}) { |k, res| res[k] = container[k] }
986
+ end
987
+
988
+ interp.stack_push(taken)
989
+ interp.stack_push(rest) if flags[:push_rest]
990
+ end
991
+
992
+ # ( array n -- array )
993
+ # ( record n -- record )
994
+ def word_DROP(interp)
995
+ n = interp.stack_pop
996
+ container = interp.stack_pop
997
+
998
+ if !container
999
+ interp.stack_push(container)
1000
+ return
1001
+ end
1002
+
1003
+ result = nil
1004
+ if container.is_a?(Array)
1005
+ result = container[n..-1]
1006
+ else
1007
+ keys = container.keys.sort
1008
+ rest_keys = keys[n..-1]
1009
+ result = rest_keys.each_with_object({}) { |k, res| res[k] = container[k] }
1010
+ end
1011
+
1012
+ interp.stack_push(result)
1013
+ end
1014
+
1015
+ # ( array -- array )
1016
+ # ( record -- record )
1017
+ def word_ROTATE(interp)
1018
+ container = interp.stack_pop
1019
+
1020
+ result = container
1021
+ if !container
1022
+ result = container
1023
+ elsif container.is_a?(Array)
1024
+ result = container
1025
+ if container.length > 0
1026
+ val = result.pop
1027
+ result.unshift(val)
1028
+ end
1029
+ else
1030
+ result = container
1031
+ end
1032
+
1033
+ interp.stack_push(result)
1034
+ end
1035
+
1036
+ # ( val -- bool )
1037
+ def word_ARRAY_q(interp)
1038
+ val = interp.stack_pop
1039
+ result = val.is_a?(Array)
1040
+ interp.stack_push(result)
1041
+ end
1042
+
1043
+ # ( array -- array )
1044
+ # ( record -- record )
1045
+ def word_SHUFFLE(interp)
1046
+ container = interp.stack_pop
1047
+
1048
+ if !container
1049
+ interp.stack_push(container)
1050
+ return
1051
+ end
1052
+
1053
+ result = container
1054
+ if container.is_a?(Array)
1055
+ result = container.shuffle
1056
+ else
1057
+ result = container
1058
+ end
1059
+
1060
+ interp.stack_push(result)
1061
+ end
1062
+
1063
+ # ( array -- array )
1064
+ # ( record -- record )
1065
+ def word_SORT(interp)
1066
+ flag_string_position = interp.get_string_location
1067
+ container = interp.stack_pop
1068
+
1069
+ flags = interp.get_flags(self.module_id)
1070
+ comparator = flags[:comparator]
1071
+
1072
+ container ||= []
1073
+ unless container.is_a?(Array)
1074
+ interp.stack_push(container)
1075
+ return
1076
+ end
1077
+
1078
+
1079
+ # Figure out what to do
1080
+ result = if comparator.is_a?(String)
1081
+ sort_with_forthic(interp, comparator, flag_string_position, container)
1082
+ elsif comparator.nil?
1083
+ sort_without_comparator(container)
1084
+ else
1085
+ sort_with_key_func(container, comparator)
1086
+ end
1087
+
1088
+ interp.stack_push(result)
1089
+ end
1090
+
1091
+ # ( field -- key_func )
1092
+ def word_FIELD_KEY_FUNC(interp)
1093
+ field = interp.stack_pop
1094
+
1095
+ result = lambda do |record|
1096
+ record[field]
1097
+ end
1098
+
1099
+ interp.stack_push(result)
1100
+ end
1101
+
1102
+ # ( array n -- item )
1103
+ # ( record n -- value )
1104
+ def word_NTH(interp)
1105
+ n = interp.stack_pop
1106
+ container = interp.stack_pop
1107
+
1108
+ if n.nil? || !container
1109
+ interp.stack_push(nil)
1110
+ return
1111
+ end
1112
+
1113
+ result = nil
1114
+ if container.is_a?(Array)
1115
+ if n < 0 || n >= container.length
1116
+ interp.stack_push(nil)
1117
+ return
1118
+ end
1119
+ result = container[n]
1120
+ else
1121
+ if n < 0 || n >= container.keys.length
1122
+ interp.stack_push(nil)
1123
+ return
1124
+ end
1125
+ keys = container.keys.sort
1126
+ key = keys[n]
1127
+ result = container[key]
1128
+ end
1129
+
1130
+ interp.stack_push(result)
1131
+ end
1132
+
1133
+ # ( array -- item )
1134
+ # ( record -- value )
1135
+ def word_LAST(interp)
1136
+ container = interp.stack_pop
1137
+
1138
+ if !container
1139
+ interp.stack_push(nil)
1140
+ return
1141
+ end
1142
+
1143
+ result = nil
1144
+ if container.is_a?(Array)
1145
+ if container.length == 0
1146
+ result = nil
1147
+ else
1148
+ result = container[container.length - 1]
1149
+ end
1150
+ else
1151
+ keys = container.keys.sort
1152
+ if keys.length == 0
1153
+ result = nil
1154
+ else
1155
+ result = container[keys[keys.length - 1]]
1156
+ end
1157
+ end
1158
+
1159
+ interp.stack_push(result)
1160
+ end
1161
+
1162
+ # ( array -- a1 a2 .. an )
1163
+ # ( record -- v1 v2 .. vn )
1164
+ def word_UNPACK(interp)
1165
+ container = interp.stack_pop
1166
+
1167
+ container = [] unless container
1168
+
1169
+ if container.is_a?(Array)
1170
+ container.each do |item|
1171
+ interp.stack_push(item)
1172
+ end
1173
+ else
1174
+ keys = container.keys.sort
1175
+ keys.each do |k|
1176
+ interp.stack_push(container[k])
1177
+ end
1178
+ end
1179
+ end
1180
+
1181
+ # ( array -- array )
1182
+ # ( record -- record )
1183
+ def word_FLATTEN(interp)
1184
+ nested = interp.stack_pop
1185
+ flags = interp.get_flags(self.module_id)
1186
+
1187
+ nested ||= []
1188
+ depth = flags[:depth]
1189
+
1190
+ result = if nested.is_a?(Array)
1191
+ flatten_array(nested, depth)
1192
+ else
1193
+ flatten_record(nested, depth, {}, [])
1194
+ end
1195
+
1196
+ interp.stack_push(result)
1197
+ end
1198
+
1199
+ # ( array item -- index )
1200
+ # ( record item -- key )
1201
+ def word_KEY_OF(interp)
1202
+ item = interp.stack_pop
1203
+ container = interp.stack_pop
1204
+
1205
+ container ||= []
1206
+
1207
+ result = nil
1208
+ if container.is_a?(Array)
1209
+ index = container.index(item)
1210
+ # If index is nil or < 0, return nil
1211
+ result = index.nil? || index < 0 ? nil : index
1212
+ else
1213
+ keys = container.keys
1214
+ keys.each do |k|
1215
+ v = container[k]
1216
+ if v == item
1217
+ result = k
1218
+ break
1219
+ end
1220
+ end
1221
+ end
1222
+
1223
+ interp.stack_push(result)
1224
+ end
1225
+
1226
+ # ( array init forthic -- value )
1227
+ # ( record init forthic -- value )
1228
+ def word_REDUCE(interp)
1229
+ forthic = interp.stack_pop
1230
+ string_location = interp.get_string_location
1231
+
1232
+ initial = interp.stack_pop
1233
+ container = interp.stack_pop
1234
+
1235
+ container ||= []
1236
+
1237
+ result = nil
1238
+ if container.is_a?(Array)
1239
+ interp.stack_push(initial)
1240
+ container.each do |item|
1241
+ interp.stack_push(item)
1242
+ interp.run(forthic, string_location)
1243
+ end
1244
+ result = interp.stack_pop
1245
+ else
1246
+ interp.stack_push(initial)
1247
+ container.each do |k, v|
1248
+ interp.stack_push(v)
1249
+ interp.run(forthic, string_location)
1250
+ end
1251
+ result = interp.stack_pop
1252
+ end
1253
+
1254
+ interp.stack_push(result)
1255
+ end
1256
+
1257
+
1258
+ # ( a -- )
1259
+ def word_POP(interp)
1260
+ interp.stack_pop
1261
+ end
1262
+
1263
+ # ( a -- a a )
1264
+ def word_DUP(interp)
1265
+ a = interp.stack_pop
1266
+ interp.stack_push(a)
1267
+ interp.stack_push(a)
1268
+ end
1269
+
1270
+ # ( a b -- b a )
1271
+ def word_SWAP(interp)
1272
+ b = interp.stack_pop
1273
+ a = interp.stack_pop
1274
+ interp.stack_push(b)
1275
+ interp.stack_push(a)
1276
+ end
1277
+
1278
+ # ( item -- str )
1279
+ def word_to_STR(interp)
1280
+ item = interp.stack_pop
1281
+ interp.stack_push(item.to_s)
1282
+ end
1283
+
1284
+ # ( str1 str2 -- str )
1285
+ # ( array_of_str -- str )
1286
+ def word_CONCAT(interp)
1287
+ str2 = interp.stack_pop
1288
+ array = str2.is_a?(Array) ? str2 : [interp.stack_pop, str2]
1289
+ result = array.join("")
1290
+ interp.stack_push(result)
1291
+ end
1292
+
1293
+ # ( string sep -- items )
1294
+ def word_SPLIT(interp)
1295
+ sep = interp.stack_pop
1296
+ string = interp.stack_pop
1297
+
1298
+ if !string
1299
+ string = ""
1300
+ end
1301
+
1302
+ result = string.split(sep)
1303
+ interp.stack_push(result)
1304
+ end
1305
+
1306
+ # ( strings sep -- string )
1307
+ def word_JOIN(interp)
1308
+ sep = interp.stack_pop
1309
+ strings = interp.stack_pop
1310
+
1311
+ if !strings
1312
+ strings = []
1313
+ end
1314
+
1315
+ result = strings.join(sep)
1316
+ interp.stack_push(result)
1317
+ end
1318
+
1319
+ # ( -- char )
1320
+ def word_slash_N(interp)
1321
+ interp.stack_push("\n")
1322
+ end
1323
+
1324
+ # ( -- char )
1325
+ def word_slash_R(interp)
1326
+ interp.stack_push("\r")
1327
+ end
1328
+
1329
+ # ( -- char )
1330
+ def word_slash_T(interp)
1331
+ interp.stack_push("\t")
1332
+ end
1333
+
1334
+ # ( A -- a )
1335
+ def word_LOWERCASE(interp)
1336
+ string = interp.stack_pop
1337
+ result = string ? string.downcase : ""
1338
+ interp.stack_push(result)
1339
+ end
1340
+
1341
+ # ( a -- A )
1342
+ def word_UPPERCASE(interp)
1343
+ string = interp.stack_pop
1344
+ result = string ? string.upcase : ""
1345
+ interp.stack_push(result)
1346
+ end
1347
+
1348
+ # ( string -- string )
1349
+ def word_ASCII(interp)
1350
+ string = interp.stack_pop
1351
+
1352
+ if !string
1353
+ string = ""
1354
+ end
1355
+
1356
+ result = ""
1357
+ string.each_char do |ch|
1358
+ result += ch if ch.ord < 256
1359
+ end
1360
+ interp.stack_push(result)
1361
+ end
1362
+
1363
+ # ( string -- string )
1364
+ def word_STRIP(interp)
1365
+ string = interp.stack_pop
1366
+ result = string ? string.strip : ""
1367
+ interp.stack_push(result)
1368
+ end
1369
+
1370
+ # ( string text replace -- string )
1371
+ def word_REPLACE(interp)
1372
+ replace = interp.stack_pop
1373
+ text = interp.stack_pop
1374
+ string = interp.stack_pop
1375
+
1376
+ result = string
1377
+ if string
1378
+ pattern = Regexp.new(text)
1379
+ result = string.gsub(pattern, replace)
1380
+ end
1381
+ interp.stack_push(result)
1382
+ end
1383
+
1384
+ # ( string pattern -- match )
1385
+ def word_RE_MATCH(interp)
1386
+ pattern = interp.stack_pop
1387
+ string = interp.stack_pop
1388
+
1389
+ re_pattern = Regexp.new(pattern)
1390
+ result = false
1391
+ if string
1392
+ result = string.match(re_pattern)
1393
+ end
1394
+ interp.stack_push(result)
1395
+ end
1396
+
1397
+ # ( match num -- string )
1398
+ def word_RE_MATCH_GROUP(interp)
1399
+ num = interp.stack_pop
1400
+ match = interp.stack_pop
1401
+ result = nil
1402
+ if match
1403
+ result = match[num]
1404
+ end
1405
+ interp.stack_push(result)
1406
+ end
1407
+
1408
+ # ( string pattern -- matches )
1409
+ def word_RE_MATCH_ALL(interp)
1410
+ pattern = interp.stack_pop
1411
+ string = interp.stack_pop
1412
+
1413
+ re_pattern = Regexp.new(pattern)
1414
+ matches = []
1415
+ if string
1416
+ matches = string.scan(re_pattern)
1417
+ end
1418
+ result = matches.map { |m| m[0] }
1419
+ interp.stack_push(result)
1420
+ end
1421
+
1422
+ # ( -- nil )
1423
+ def word_NULL(interp)
1424
+ interp.stack_push(nil)
1425
+ end
1426
+
1427
+ # ( value default -- value )
1428
+ def word_DEFAULT(interp)
1429
+ default_value = interp.stack_pop
1430
+ value = interp.stack_pop
1431
+ result = value.nil? || value == "" ? default_value : value
1432
+ interp.stack_push(result)
1433
+ end
1434
+
1435
+ # ( value default_forthic -- value )
1436
+ def word_star_DEFAULT(interp)
1437
+ default_forthic = interp.stack_pop
1438
+ value = interp.stack_pop
1439
+
1440
+ if value.nil? || value == ""
1441
+ interp.run(default_forthic)
1442
+ value = interp.stack_pop
1443
+ end
1444
+ interp.stack_push(value)
1445
+ end
1446
+
1447
+ # ( item forthic num-times -- ? )
1448
+ def word_l_REPEAT(interp)
1449
+ num_times = interp.stack_pop
1450
+ forthic = interp.stack_pop
1451
+ string_location = interp.get_string_location
1452
+
1453
+ num_times.times do
1454
+ item = interp.stack_pop
1455
+ interp.stack_push(item)
1456
+
1457
+ interp.run(forthic, string_location)
1458
+ res = interp.stack_pop
1459
+
1460
+ interp.stack_push(item)
1461
+ interp.stack_push(res)
1462
+ end
1463
+ end
1464
+
1465
+ # ( value num_places -- str )
1466
+ def word_to_FIXED(interp)
1467
+ num_places = interp.stack_pop
1468
+ value = interp.stack_pop
1469
+ result = value
1470
+ if value.nil?
1471
+ result = ""
1472
+ elsif !value.is_a?(Numeric)
1473
+ result = value
1474
+ else
1475
+ # Round value to num_places
1476
+ result = value.round(num_places)
1477
+ end
1478
+ interp.stack_push(result.to_s)
1479
+ end
1480
+
1481
+ # ( object -- json )
1482
+ def word_to_JSON(interp)
1483
+ object = interp.stack_pop
1484
+ result = object.to_json
1485
+ interp.stack_push(result)
1486
+ end
1487
+
1488
+ # ( json -- object )
1489
+ def word_JSON_to(interp)
1490
+ json = interp.stack_pop
1491
+ result = JSON.parse(json)
1492
+ interp.stack_push(result)
1493
+ end
1494
+
1495
+ # ( -- )
1496
+ def word_dot_s(interp)
1497
+ stack = interp.stack
1498
+ puts "===> Stack <==="
1499
+ if stack.length > 0
1500
+ p stack[stack.length - 1]
1501
+ else
1502
+ puts "<STACK EMPTY>"
1503
+ end
1504
+ interp.halt
1505
+ end
1506
+
1507
+ # ( -- )
1508
+ def word_dot_S(interp)
1509
+ # Print full stack in reverse with index, pretty printing stack value
1510
+ puts "===> Stack <==="
1511
+ # Get reversed stack
1512
+ reversed_stack = interp.stack.reverse
1513
+ reversed_stack.each_with_index do |item, i|
1514
+ puts "#{i}: #{item.inspect}"
1515
+ end
1516
+ end
1517
+
1518
+
1519
+ # ( time -- time )
1520
+ def word_AM(interp)
1521
+ time = interp.stack_pop
1522
+ raise "AM expecting a time" unless time.is_a?(Time)
1523
+
1524
+ result = time
1525
+ if time.hour >= 12
1526
+ result = Time.new(time.year, time.month, time.day, time.hour - 12, time.min, time.sec)
1527
+ end
1528
+ interp.stack_push(result)
1529
+ end
1530
+
1531
+ # ( time -- time )
1532
+ def word_PM(interp)
1533
+ time = interp.stack_pop
1534
+ raise "PM expecting a time" unless time.is_a?(Time)
1535
+
1536
+ result = time
1537
+ if time.hour < 12
1538
+ result = Time.new(time.year, time.month, time.day, time.hour + 12, time.min, time.sec)
1539
+ end
1540
+ interp.stack_push(result)
1541
+ end
1542
+
1543
+ # ( -- time )
1544
+ def word_NOW(interp)
1545
+ result = Time.now
1546
+ interp.stack_push(result)
1547
+ end
1548
+
1549
+ # ( obj -- time )
1550
+ def word_to_TIME(interp)
1551
+ obj = interp.stack_pop
1552
+ result = nil
1553
+ if obj.is_a?(Time)
1554
+ result = obj
1555
+ else
1556
+ result = Time.parse(obj.to_s)
1557
+ end
1558
+ interp.stack_push(result)
1559
+ end
1560
+
1561
+
1562
+ # ( obj -- date )
1563
+ def word_to_DATE(interp)
1564
+ obj = interp.stack_pop
1565
+ result = nil
1566
+ if obj.is_a?(Date)
1567
+ result = obj
1568
+ else
1569
+ result = Date.parse(obj.to_s)
1570
+ end
1571
+ interp.stack_push(result)
1572
+ end
1573
+
1574
+ # ( -- date )
1575
+ def word_TODAY(interp)
1576
+ result = Date.today
1577
+ interp.stack_push(result)
1578
+ end
1579
+
1580
+ # ( -- date )
1581
+ def word_MONDAY(interp)
1582
+ result = get_day_this_week(0)
1583
+ interp.stack_push(result)
1584
+ end
1585
+
1586
+ # ( -- date )
1587
+ def word_TUESDAY(interp)
1588
+ result = get_day_this_week(1)
1589
+ interp.stack_push(result)
1590
+ end
1591
+
1592
+ # ( -- date )
1593
+ def word_WEDNESDAY(interp)
1594
+ result = get_day_this_week(2)
1595
+ interp.stack_push(result)
1596
+ end
1597
+
1598
+ # ( -- date )
1599
+ def word_THURSDAY(interp)
1600
+ result = get_day_this_week(3)
1601
+ interp.stack_push(result)
1602
+ end
1603
+
1604
+ # ( -- date )
1605
+ def word_FRIDAY(interp)
1606
+ result = get_day_this_week(4)
1607
+ interp.stack_push(result)
1608
+ end
1609
+
1610
+ # ( -- date )
1611
+ def word_SATURDAY(interp)
1612
+ result = get_day_this_week(5)
1613
+ interp.stack_push(result)
1614
+ end
1615
+
1616
+ # ( -- date )
1617
+ def word_SUNDAY(interp)
1618
+ result = get_day_this_week(6)
1619
+ interp.stack_push(result)
1620
+ end
1621
+
1622
+ # ( date num-days -- date )
1623
+ def word_ADD_DAYS(interp)
1624
+ num_days = interp.stack_pop
1625
+ date = interp.stack_pop
1626
+
1627
+ result = date + num_days
1628
+ interp.stack_push(result)
1629
+ end
1630
+
1631
+ # ( l_date r_date -- num_days )
1632
+ def word_SUBTRACT_DATES(interp)
1633
+ r_date = interp.stack_pop
1634
+ l_date = interp.stack_pop
1635
+ result = (l_date - r_date).to_i
1636
+ interp.stack_push(result)
1637
+ end
1638
+
1639
+ # ( a b -- a+b )
1640
+ # ( items -- sum )
1641
+ def word_plus(interp)
1642
+ items = interp.stack_pop
1643
+
1644
+ if items.is_a?(Array)
1645
+ sum = 0
1646
+ items.each do |item|
1647
+ sum += item
1648
+ end
1649
+ interp.stack_push(sum)
1650
+ else
1651
+ b = items
1652
+ a = interp.stack_pop
1653
+ interp.stack_push(a + b)
1654
+ end
1655
+ end
1656
+
1657
+ # ( a b -- a - b )
1658
+ def word_minus(interp)
1659
+ b = interp.stack_pop
1660
+ a = interp.stack_pop
1661
+ interp.stack_push(a - b)
1662
+ end
1663
+
1664
+ # ( a b -- a*b )
1665
+ def word_times(interp)
1666
+ b = interp.stack_pop
1667
+ result = 1
1668
+ numbers = []
1669
+
1670
+ if b.is_a?(Array)
1671
+ numbers = b
1672
+ else
1673
+ a = interp.stack_pop
1674
+ numbers = [a, b]
1675
+ end
1676
+
1677
+ numbers.each do |num|
1678
+ if num.nil?
1679
+ interp.stack_push(nil)
1680
+ return
1681
+ end
1682
+ result *= num
1683
+ end
1684
+
1685
+ interp.stack_push(result)
1686
+ end
1687
+
1688
+ # ( a b -- a/b )
1689
+ def word_divide_by(interp)
1690
+ b = interp.stack_pop
1691
+ a = interp.stack_pop
1692
+ result = a / b
1693
+ interp.stack_push(result)
1694
+ end
1695
+
1696
+ # ( value mod -- remainder )
1697
+ def word_MOD(interp)
1698
+ mod = interp.stack_pop
1699
+ value = interp.stack_pop
1700
+ result = value % mod
1701
+ interp.stack_push(result)
1702
+ end
1703
+
1704
+ # ( numbers -- mean )
1705
+ # ( records -- mean_record )
1706
+ def word_MEAN(interp)
1707
+ values = interp.stack_pop
1708
+
1709
+ if !values || values.empty?
1710
+ interp.stack_push(0)
1711
+ return
1712
+ end
1713
+
1714
+ result = compute_mean(values)
1715
+ interp.stack_push(result)
1716
+ end
1717
+
1718
+ # ( num1 num2 -- num )
1719
+ # ( numbers -- num )
1720
+ def word_MAX(interp)
1721
+ num2 = interp.stack_pop
1722
+
1723
+ numbers = []
1724
+ if num2.is_a?(Array)
1725
+ numbers = num2
1726
+ else
1727
+ num1 = interp.stack_pop
1728
+ numbers = [num1, num2]
1729
+ end
1730
+
1731
+ interp.stack_push(numbers.max)
1732
+ end
1733
+
1734
+ # ( num1 num2 -- num )
1735
+ # ( numbers -- num )
1736
+ def word_MIN(interp)
1737
+ num2 = interp.stack_pop
1738
+
1739
+ numbers = []
1740
+ if num2.is_a?(Array)
1741
+ numbers = num2
1742
+ else
1743
+ num1 = interp.stack_pop
1744
+ numbers = [num1, num2]
1745
+ end
1746
+
1747
+ interp.stack_push(numbers.min)
1748
+ end
1749
+
1750
+ # ( a -- a )
1751
+ def word_ROUND(interp)
1752
+ a = interp.stack_pop
1753
+ result = a.round
1754
+ interp.stack_push(result)
1755
+ end
1756
+
1757
+ # ( a b -- a == b )
1758
+ # ( items -- all_equal )
1759
+ def word_equal_equal(interp)
1760
+ items = interp.stack_pop
1761
+
1762
+ if items.is_a?(Array)
1763
+ all_equal = items.all? { |item| item == items[0] }
1764
+ interp.stack_push(all_equal)
1765
+ else
1766
+ b = items
1767
+ a = interp.stack_pop
1768
+ interp.stack_push(a == b)
1769
+ end
1770
+ end
1771
+
1772
+ # ( a b -- a != b )
1773
+ # ( items -- all_not_equal )
1774
+ def word_not_equal(interp)
1775
+ items = interp.stack_pop
1776
+
1777
+ if items.is_a?(Array)
1778
+ all_not_equal = items.all? { |item| item != items[0] }
1779
+ interp.stack_push(all_not_equal)
1780
+ else
1781
+ b = items
1782
+ a = interp.stack_pop
1783
+ interp.stack_push(a != b)
1784
+ end
1785
+ end
1786
+
1787
+ # ( l r -- bool )
1788
+ def word_greater_than(interp)
1789
+ r = interp.stack_pop
1790
+ l = interp.stack_pop
1791
+
1792
+ if l.nil? || r.nil?
1793
+ interp.stack_push(nil)
1794
+ return
1795
+ end
1796
+
1797
+ result = l > r
1798
+ interp.stack_push(result)
1799
+ end
1800
+
1801
+ # ( l r -- bool )
1802
+ def word_greater_than_or_equal(interp)
1803
+ r = interp.stack_pop
1804
+ l = interp.stack_pop
1805
+
1806
+ if l.nil? || r.nil?
1807
+ interp.stack_push(nil)
1808
+ return
1809
+ end
1810
+
1811
+ result = l >= r
1812
+ interp.stack_push(result)
1813
+ end
1814
+
1815
+ # ( l r -- bool )
1816
+ def word_less_than(interp)
1817
+ r = interp.stack_pop
1818
+ l = interp.stack_pop
1819
+
1820
+ if l.nil? || r.nil?
1821
+ interp.stack_push(nil)
1822
+ return
1823
+ end
1824
+
1825
+ result = l < r
1826
+ interp.stack_push(result)
1827
+ end
1828
+
1829
+ # ( l r -- bool )
1830
+ # ( items -- bool )
1831
+ def word_OR(interp)
1832
+ r = interp.stack_pop
1833
+
1834
+ items = r.is_a?(Array) ? r : [interp.stack_pop, r]
1835
+ result = items.any? { |item| item }
1836
+ interp.stack_push(result)
1837
+ end
1838
+
1839
+ # ( l r -- bool )
1840
+ # ( items -- bool )
1841
+ def word_AND(interp)
1842
+ r = interp.stack_pop
1843
+
1844
+ items = r.is_a?(Array) ? r : [interp.stack_pop, r]
1845
+ result = items.all? { |item| item }
1846
+ interp.stack_push(result)
1847
+ end
1848
+
1849
+ # ( bool -- bool )
1850
+ def word_NOT(interp)
1851
+ value = interp.stack_pop
1852
+ interp.stack_push(!value)
1853
+ end
1854
+
1855
+ # ( item items -- bool )
1856
+ def word_IN(interp)
1857
+ items = interp.stack_pop
1858
+ item = interp.stack_pop
1859
+
1860
+ if !items
1861
+ items = []
1862
+ end
1863
+ result = items.include?(item)
1864
+ interp.stack_push(result)
1865
+ end
1866
+
1867
+ # ( vals required_vals -- bool )
1868
+ def word_ANY(interp)
1869
+ required_vals = interp.stack_pop
1870
+ vals = interp.stack_pop
1871
+
1872
+ result = false
1873
+ required_vals.each do |rv|
1874
+ if vals.include?(rv)
1875
+ result = true
1876
+ break
1877
+ end
1878
+ end
1879
+
1880
+ # If nothing is required, then all values are true
1881
+ result = true if required_vals.empty?
1882
+
1883
+ interp.stack_push(result)
1884
+ end
1885
+
1886
+
1887
+ # ( vals required_vals -- bool )
1888
+ def word_ALL(interp)
1889
+ required_vals = interp.stack_pop
1890
+ vals = interp.stack_pop
1891
+
1892
+ vals ||= []
1893
+ required_vals ||= []
1894
+
1895
+ result = required_vals.all? { |val| vals.include?(val) }
1896
+ interp.stack_push(result)
1897
+ end
1898
+
1899
+ # ( item -- bool )
1900
+ def word_to_BOOL(interp)
1901
+ item = interp.stack_pop
1902
+ result = false
1903
+ if item.nil? || item == "" || item == 0
1904
+ result = false
1905
+ else
1906
+ result = !!item
1907
+ end
1908
+ interp.stack_push(result)
1909
+ end
1910
+
1911
+ # ( item -- int )
1912
+ def word_to_INT(interp)
1913
+ str = interp.stack_pop
1914
+ result = str.to_i
1915
+ interp.stack_push(result)
1916
+ end
1917
+
1918
+ # ( item -- float )
1919
+ def word_to_FLOAT(interp)
1920
+ str = interp.stack_pop
1921
+ result = str.to_f
1922
+ interp.stack_push(result)
1923
+ end
1924
+
1925
+
1926
+ # ( val start_ranges -- index )
1927
+ def word_RANGE_INDEX(interp)
1928
+ start_ranges = interp.stack_pop
1929
+ val = interp.stack_pop
1930
+
1931
+ # Cap off the value ranges with infinity
1932
+ start_ranges.push(Float::INFINITY)
1933
+
1934
+ if val.nil? || start_ranges.nil?
1935
+ interp.stack_push(nil)
1936
+ return
1937
+ end
1938
+
1939
+ if val < start_ranges[0]
1940
+ interp.stack_push(nil)
1941
+ return
1942
+ end
1943
+
1944
+ result = nil
1945
+ (0...start_ranges.length - 1).each do |i|
1946
+ if val >= start_ranges[i] && val < start_ranges[i + 1]
1947
+ result = i
1948
+ break
1949
+ end
1950
+ end
1951
+
1952
+ interp.stack_push(result)
1953
+ end
1954
+
1955
+
1956
+ # ( -- Infinity )
1957
+ def word_INFINITY(interp)
1958
+ interp.stack_push(Float::INFINITY)
1959
+ end
1960
+
1961
+ # ( l r -- bool )
1962
+ def word_less_than_or_equal(interp)
1963
+ r = interp.stack_pop
1964
+ l = interp.stack_pop
1965
+
1966
+ if l.nil? || r.nil?
1967
+ interp.stack_push(nil)
1968
+ return
1969
+ end
1970
+
1971
+ result = l <= r
1972
+ interp.stack_push(result)
1973
+ end
1974
+
1975
+ # ( date -- str )
1976
+ def word_DATE_to_STR(interp)
1977
+ date = interp.stack_pop
1978
+ result = date_to_string(date)
1979
+ interp.stack_push(result)
1980
+ end
1981
+
1982
+ # ( time -- str )
1983
+ def word_TIME_to_STR(interp)
1984
+ time = interp.stack_pop
1985
+ result = time.strftime("%H:%M")
1986
+ interp.stack_push(result)
1987
+ end
1988
+
1989
+ # ( date time -- datetime )
1990
+ def word_DATE_TIME_to_DATETIME(interp)
1991
+ time = interp.stack_pop
1992
+ date = interp.stack_pop
1993
+ dt_string = "#{date.year}-#{date.month}-#{date.day} #{time.hour}:#{time.min}"
1994
+ result = Time.parse(dt_string)
1995
+ interp.stack_push(result)
1996
+ end
1997
+
1998
+ # ( datetime -- timestamp )
1999
+ def word_DATETIME_to_TIMESTAMP(interp)
2000
+ datetime = interp.stack_pop
2001
+ result = datetime.to_i
2002
+ interp.stack_push(result)
2003
+ end
2004
+
2005
+ # ( timestamp -- datetime )
2006
+ def word_TIMESTAMP_to_DATETIME(interp)
2007
+ timestamp = interp.stack_pop
2008
+ result = Time.at(timestamp)
2009
+ interp.stack_push(result)
2010
+ end
2011
+
2012
+ # ( str -- datetime )
2013
+ def word_STR_to_DATETIME(interp)
2014
+ s = interp.stack_pop
2015
+ result = Time.parse(s)
2016
+ interp.stack_push(result)
2017
+ end
2018
+
2019
+ # ( str -- timestamp )
2020
+ def word_STR_to_TIMESTAMP(interp)
2021
+ s = interp.stack_pop
2022
+ datetime = Time.parse(s)
2023
+ result = datetime.to_i
2024
+ interp.stack_push(result)
2025
+ end
2026
+
2027
+ # ( -- )
2028
+ def word_bang_PUSH_ERROR(interp)
2029
+ interp.modify_flags(self.module_id, {push_error: true})
2030
+ end
2031
+
2032
+ # ( -- )
2033
+ def word_bang_WITH_KEY(interp)
2034
+ interp.modify_flags(self.module_id, {with_key: true})
2035
+ end
2036
+
2037
+ # ( comparator -- )
2038
+ def word_bang_COMPARATOR(interp)
2039
+ comparator = interp.stack_pop
2040
+ interp.modify_flags(self.module_id, {comparator: comparator})
2041
+ end
2042
+
2043
+ # ( -- )
2044
+ def word_bang_PUSH_REST(interp)
2045
+ interp.modify_flags(self.module_id, {push_rest: true})
2046
+ end
2047
+
2048
+ # ( depth -- )
2049
+ #
2050
+ # NOTE: `depth` of 0 is the same not having set depth
2051
+ def word_bang_DEPTH(interp)
2052
+ depth = interp.stack_pop
2053
+ interp.modify_flags(self.module_id, {depth: depth})
2054
+ end
2055
+
2056
+ # ( num_inteprs -- )
2057
+ def word_bang_INTERPS(interp)
2058
+ num_interps = interp.stack_pop
2059
+ interp.modify_flags(self.module_id, { interps: num_interps })
2060
+ end
2061
+
2062
+ # ( -- )
2063
+ def word_PROFILE_START(interp)
2064
+ interp.start_profiling
2065
+ end
2066
+
2067
+ # ( -- )
2068
+ def word_PROFILE_END(interp)
2069
+ interp.stop_profiling
2070
+ end
2071
+
2072
+ # ( label -- )
2073
+ def word_PROFILE_TIMESTAMP(interp)
2074
+ label = interp.stack_pop
2075
+ interp.add_timestamp(label)
2076
+ end
2077
+
2078
+ # ( -- )
2079
+ def word_PROFILE_DATA(interp)
2080
+ histogram = interp.word_histogram
2081
+ timestamps = interp.profile_timestamps
2082
+
2083
+ result = {
2084
+ word_counts: [],
2085
+ timestamps: [],
2086
+ }
2087
+
2088
+ histogram.each do |val|
2089
+ rec = { word: val[:word], count: val[:count] }
2090
+ result[:word_counts].push(rec)
2091
+ end
2092
+
2093
+ prev_time = 0.0
2094
+ timestamps.each do |t|
2095
+ rec = {
2096
+ label: t[:label],
2097
+ time_ms: t[:time_ms],
2098
+ delta: t[:time_ms] - prev_time,
2099
+ }
2100
+ prev_time = t[:time_ms]
2101
+ result[:timestamps].push(rec)
2102
+ end
2103
+
2104
+ interp.stack_push(result)
2105
+ end
2106
+
2107
+
2108
+ # ( string -- symbol )
2109
+ def word_to_SYM(interp)
2110
+ string = interp.stack_pop
2111
+ result = string.to_sym
2112
+ interp.stack_push(result)
2113
+ end
2114
+
2115
+ # --------------------------------------------------
2116
+ # Helpers
2117
+ private
2118
+ def drill_for_value(rec, fields)
2119
+ cur_rec = rec
2120
+ fields.each do |f|
2121
+ if cur_rec.is_a?(Array) or cur_rec.is_a?(Hash)
2122
+ cur_rec = cur_rec[f]
2123
+ else
2124
+ return nil
2125
+ end
2126
+ end
2127
+ cur_rec
2128
+ end
2129
+
2130
+ def date_to_string(date)
2131
+ date.strftime("%Y-%m-%d")
2132
+ end
2133
+
2134
+ def group_items(items, group_size)
2135
+ num_groups = (items.length / group_size.to_f).ceil
2136
+ res = []
2137
+ remaining = items.dup
2138
+ num_groups.times do
2139
+ res.push(remaining.slice!(0, group_size))
2140
+ end
2141
+ res
2142
+ end
2143
+
2144
+ def extract_rec(record, keys)
2145
+ res = {}
2146
+ keys.each { |k| res[k] = record[k] }
2147
+ res
2148
+ end
2149
+
2150
+ def execute_returning_error(interp, forthic, string_location)
2151
+ result = nil
2152
+ begin
2153
+ interp.run(forthic, string_location)
2154
+ rescue StandardError => e
2155
+ result = e
2156
+ end
2157
+ result
2158
+ end
2159
+
2160
+ def normalize_index(index, length)
2161
+ res = index
2162
+ res += length if index < 0
2163
+ res
2164
+ end
2165
+
2166
+ # Default sort
2167
+ def sort_without_comparator(container)
2168
+ # Separate nil values from non-nil values
2169
+ nils, non_nils = container.partition(&:nil?)
2170
+
2171
+ # Sort non_nils and append nils
2172
+ non_nils.sort + nils
2173
+ end
2174
+
2175
+ # Sort using a forthic string
2176
+ def sort_with_forthic(interp, forthic, flag_string_position, container)
2177
+ aug_array = make_aug_array(interp, forthic, flag_string_position, container)
2178
+ aug_array.sort! { |l, r| cmp_items(l, r) }
2179
+ de_aug_array(aug_array)
2180
+ end
2181
+
2182
+ def make_aug_array(interp, forthic, flag_string_position, vals)
2183
+ res = []
2184
+ vals.each do |val|
2185
+ interp.stack_push(val)
2186
+ interp.run(forthic, flag_string_position)
2187
+ aug_val = interp.stack_pop
2188
+ res.push([val, aug_val])
2189
+ end
2190
+ res
2191
+ end
2192
+
2193
+ def cmp_items(l, r)
2194
+ l_val = l[1]
2195
+ r_val = r[1]
2196
+
2197
+ if l_val < r_val
2198
+ -1
2199
+ elsif l_val > r_val
2200
+ 1
2201
+ else
2202
+ 0
2203
+ end
2204
+ end
2205
+
2206
+ def de_aug_array(aug_vals)
2207
+ aug_vals.map { |aug_val| aug_val[0] }
2208
+ end
2209
+
2210
+ # Sort with key func
2211
+ def sort_with_key_func(container, key_func)
2212
+ container.sort do |l, r|
2213
+ l_val = key_func.call(l)
2214
+ r_val = key_func.call(r)
2215
+ if l_val < r_val
2216
+ -1
2217
+ elsif l_val > r_val
2218
+ 1
2219
+ else
2220
+ 0
2221
+ end
2222
+ end
2223
+ end
2224
+
2225
+ def add_to_record_result(item, key, keys, result)
2226
+ new_key = (keys + [key]).join("\t")
2227
+ result[new_key] = item
2228
+ end
2229
+
2230
+ def fully_flatten_record(record, res, keys)
2231
+ record.each do |k, item|
2232
+ if is_record(item)
2233
+ fully_flatten_record(item, res, keys + [k])
2234
+ else
2235
+ add_to_record_result(item, k, keys, res)
2236
+ end
2237
+ end
2238
+ res
2239
+ end
2240
+
2241
+ def flatten_record(record, depth, res, keys)
2242
+ return fully_flatten_record(record, res, keys) if depth.nil?
2243
+
2244
+ record.each do |k, item|
2245
+ if depth > 0 && is_record(item)
2246
+ flatten_record(item, depth - 1, res, keys + [k])
2247
+ else
2248
+ add_to_record_result(item, k, keys, res)
2249
+ end
2250
+ end
2251
+ res
2252
+ end
2253
+
2254
+ def flatten_array(array, depth)
2255
+ return array.flatten(depth) if depth
2256
+ array.flatten
2257
+ end
2258
+
2259
+ def is_record(obj)
2260
+ obj.is_a?(Hash) && !obj.empty?
2261
+ end
2262
+
2263
+ def get_day_this_week(day_of_week)
2264
+ # Assume the start of the week is Monday and a day_of_week of 0 means a Monday
2265
+ # Get the current day of the week
2266
+ today = Date.today
2267
+ current_day_of_week = today.wday
2268
+
2269
+ # Return the date of the day_of_week
2270
+ today - (current_day_of_week - day_of_week)
2271
+ end
2272
+
2273
+ # NOTE: Monday is the start of the week
2274
+ def normalize_day(day)
2275
+ day
2276
+ end
2277
+
2278
+ def compute_number_mean(numbers)
2279
+ sum = numbers.reduce(0) { |acc, num| acc + num }
2280
+ sum.to_f / numbers.length
2281
+ end
2282
+
2283
+ def compute_non_number_mean(objects)
2284
+ non_null_objects = objects.reject { |obj| obj.nil? }
2285
+ res = Hash.new(0)
2286
+
2287
+ non_null_objects.each do |obj|
2288
+ obj_str = obj.is_a?(String) ? obj : obj.to_json
2289
+ res[obj_str] += 1
2290
+ end
2291
+
2292
+ res.each do |key, value|
2293
+ res[key] = value.to_f / non_null_objects.length
2294
+ end
2295
+ res
2296
+ end
2297
+
2298
+ def compute_object_mean(records)
2299
+ res = {}
2300
+ records.each do |record|
2301
+ record.each do |key, value|
2302
+ res[key] ||= []
2303
+ res[key] << value
2304
+ end
2305
+ end
2306
+
2307
+ res.each do |key, values|
2308
+ res[key] = compute_mean(values)
2309
+ end
2310
+ res
2311
+ end
2312
+
2313
+ def is_all_numbers(values)
2314
+ values.all? { |val| val.is_a?(Numeric) }
2315
+ end
2316
+
2317
+ def is_all_records(values)
2318
+ values.all? { |val| val.is_a?(Hash) }
2319
+ end
2320
+
2321
+ def select_non_null_values(values)
2322
+ values.reject { |val| val.nil? }
2323
+ end
2324
+
2325
+ def compute_mean(values)
2326
+ result = nil
2327
+ if values.is_a?(Array)
2328
+ non_null_values = select_non_null_values(values)
2329
+ if is_all_numbers(non_null_values)
2330
+ result = compute_number_mean(non_null_values)
2331
+ elsif is_all_records(non_null_values)
2332
+ result = compute_object_mean(non_null_values)
2333
+ else
2334
+ result = compute_non_number_mean(non_null_values)
2335
+ end
2336
+ end
2337
+ result
2338
+ end
2339
+
2340
+ end
2341
+ end