forthic 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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