forthic 0.2.0 → 0.3.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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +314 -14
  3. data/Rakefile +36 -7
  4. data/lib/forthic/decorators/docs.rb +69 -0
  5. data/lib/forthic/decorators/word.rb +331 -0
  6. data/lib/forthic/errors.rb +270 -0
  7. data/lib/forthic/grpc/client.rb +223 -0
  8. data/lib/forthic/grpc/errors.rb +149 -0
  9. data/lib/forthic/grpc/forthic_runtime_pb.rb +32 -0
  10. data/lib/forthic/grpc/forthic_runtime_services_pb.rb +31 -0
  11. data/lib/forthic/grpc/remote_module.rb +120 -0
  12. data/lib/forthic/grpc/remote_runtime_module.rb +148 -0
  13. data/lib/forthic/grpc/remote_word.rb +91 -0
  14. data/lib/forthic/grpc/runtime_manager.rb +60 -0
  15. data/lib/forthic/grpc/serializer.rb +184 -0
  16. data/lib/forthic/grpc/server.rb +361 -0
  17. data/lib/forthic/interpreter.rb +694 -245
  18. data/lib/forthic/literals.rb +170 -0
  19. data/lib/forthic/module.rb +383 -0
  20. data/lib/forthic/modules/standard/array_module.rb +940 -0
  21. data/lib/forthic/modules/standard/boolean_module.rb +176 -0
  22. data/lib/forthic/modules/standard/core_module.rb +362 -0
  23. data/lib/forthic/modules/standard/datetime_module.rb +349 -0
  24. data/lib/forthic/modules/standard/json_module.rb +55 -0
  25. data/lib/forthic/modules/standard/math_module.rb +365 -0
  26. data/lib/forthic/modules/standard/record_module.rb +203 -0
  27. data/lib/forthic/modules/standard/string_module.rb +170 -0
  28. data/lib/forthic/tokenizer.rb +224 -77
  29. data/lib/forthic/utils.rb +35 -0
  30. data/lib/forthic/websocket/handler.rb +548 -0
  31. data/lib/forthic/websocket/serializer.rb +160 -0
  32. data/lib/forthic/word_options.rb +141 -0
  33. data/lib/forthic.rb +30 -20
  34. data/protos/README.md +43 -0
  35. data/protos/v1/forthic_runtime.proto +200 -0
  36. metadata +72 -39
  37. data/.standard.yml +0 -3
  38. data/CHANGELOG.md +0 -11
  39. data/CLAUDE.md +0 -74
  40. data/Guardfile +0 -42
  41. data/lib/forthic/code_location.rb +0 -20
  42. data/lib/forthic/forthic_error.rb +0 -50
  43. data/lib/forthic/forthic_module.rb +0 -146
  44. data/lib/forthic/global_module.rb +0 -2328
  45. data/lib/forthic/positioned_string.rb +0 -19
  46. data/lib/forthic/token.rb +0 -37
  47. data/lib/forthic/variable.rb +0 -34
  48. data/lib/forthic/version.rb +0 -5
  49. data/lib/forthic/words/definition_word.rb +0 -38
  50. data/lib/forthic/words/end_array_word.rb +0 -28
  51. data/lib/forthic/words/end_module_word.rb +0 -16
  52. data/lib/forthic/words/imported_word.rb +0 -27
  53. data/lib/forthic/words/map_word.rb +0 -169
  54. data/lib/forthic/words/module_memo_bang_at_word.rb +0 -22
  55. data/lib/forthic/words/module_memo_bang_word.rb +0 -21
  56. data/lib/forthic/words/module_memo_word.rb +0 -35
  57. data/lib/forthic/words/module_word.rb +0 -21
  58. data/lib/forthic/words/push_value_word.rb +0 -21
  59. data/lib/forthic/words/start_module_word.rb +0 -31
  60. data/lib/forthic/words/word.rb +0 -30
  61. data/sig/forthic.rbs +0 -4
@@ -1,2328 +0,0 @@
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
- end
207
-
208
- def find_word(name)
209
- result = super
210
- result ||= find_literal_word(name)
211
- result
212
- end
213
-
214
- def find_literal_word(string)
215
- value = nil
216
- @literal_handlers.each do |handler|
217
- value = handler.call(string)
218
- return PushValueWord.new(string, value) unless value.nil?
219
- end
220
- nil
221
- end
222
-
223
- # Literal handlers
224
- def to_bool(str_val)
225
- return true if str_val == "TRUE"
226
- return false if str_val == "FALSE"
227
- nil
228
- end
229
-
230
- def to_int(str_val)
231
- Integer(str_val)
232
- rescue
233
- nil
234
- end
235
-
236
- def to_float(str_val)
237
- return nil unless str_val.include?(".")
238
- begin
239
- Float(str_val)
240
- rescue
241
- nil
242
- end
243
- end
244
-
245
- def to_literal_date(str_val)
246
- match = str_val.match(/(\d{4})-(\d{2})-(\d{2})/)
247
- return nil unless match
248
-
249
- year = match[1].to_i
250
- month = match[2].to_i
251
- day = match[3].to_i
252
- Date.new(year, month, day)
253
- end
254
-
255
- def to_time(str_val)
256
- match = str_val.match(/(\d{1,2}):(\d{2})/)
257
- return nil unless match
258
-
259
- hours = match[1].to_i
260
- minutes = match[2].to_i
261
- return nil if hours > 23 || minutes >= 60
262
-
263
- result = Time.now
264
- Time.new(result.year, result.month, result.day, hours, minutes, 0)
265
- end
266
-
267
- # Convenience function to create element word handlers
268
- def make_element_word(element_name)
269
- proc do |interp|
270
- interp.stack_push(element_name)
271
- interp.run("Element")
272
- end
273
- end
274
-
275
- def add_element_word(element_name)
276
- add_module_word(element_name, make_element_word(element_name))
277
- end
278
-
279
- # Words
280
-
281
- # ( varnames -- )
282
- def word_VARIABLES(interp)
283
- varnames = interp.stack_pop
284
- module_ = interp.cur_module
285
- varnames.each do |v|
286
- if /__.*/.match?(v)
287
- raise ForthicError.new(
288
- "global_module-696",
289
- "word_VARIABLES: variable names cannot begin with '__': '#{v}'",
290
- "This is a reserved variable naming convention"
291
- )
292
- end
293
- module_.add_variable(v)
294
- end
295
- end
296
-
297
- # ( value variable -- )
298
- def word_bang(interp)
299
- variable = interp.stack_pop
300
- value = interp.stack_pop
301
- variable.value = value
302
- end
303
-
304
- # ( variable -- value )
305
- def word_at(interp)
306
- variable = interp.stack_pop
307
- interp.stack_push(variable.value)
308
- end
309
-
310
- # ( value variable -- value )
311
- def word_bang_at(interp)
312
- variable = interp.stack_pop
313
- value = interp.stack_pop
314
- variable.value = value
315
- interp.stack_push(variable.value)
316
- end
317
-
318
- # ( string -- )
319
- def word_INTERPRET(interp)
320
- string = interp.stack_pop
321
- string_location = interp.get_string_location
322
- interp.run(string, string_location) if string
323
- end
324
-
325
- # ( names -- )
326
- def word_EXPORT(interp)
327
- names = interp.stack_pop
328
- interp.cur_module.add_exportable(names)
329
- end
330
-
331
- # ( names -- )
332
- def word_USE_MODULES(interp)
333
- names = interp.stack_pop
334
-
335
- names.each do |name|
336
- module_name, prefix = name.is_a?(Array) ? name : [name, name]
337
- mod = interp.find_module(module_name)
338
- interp.get_app_module.import_module(prefix, mod, interp)
339
- end
340
- end
341
-
342
- # ( key_vals -- rec )
343
- def word_REC(interp)
344
- key_vals = interp.stack_pop || []
345
- result = {}
346
- key_vals.each do |pair|
347
- key, val = pair
348
- result[key] = val
349
- end
350
- interp.stack_push(result)
351
- end
352
-
353
- # ( rec field -- value )
354
- # ( rec fields -- value )
355
- def word_REC_at(interp)
356
- field = interp.stack_pop
357
- rec = interp.stack_pop
358
-
359
- if rec.nil?
360
- interp.stack_push(nil)
361
- return
362
- end
363
-
364
- fields = field.is_a?(Array) ? field : [field]
365
- result = drill_for_value(rec, fields)
366
- interp.stack_push(result)
367
- end
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(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(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(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(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(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..]
980
- else
981
- keys = container.keys.sort
982
- taken_keys = keys[0...n]
983
- rest_keys = keys[n..]
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..]
1006
- else
1007
- keys = container.keys.sort
1008
- rest_keys = keys[n..]
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
- result = if container.is_a?(Array)
1053
- container.shuffle
1054
- else
1055
- container
1056
- end
1057
-
1058
- interp.stack_push(result)
1059
- end
1060
-
1061
- # ( array -- array )
1062
- # ( record -- record )
1063
- def word_SORT(interp)
1064
- flag_string_position = interp.get_string_location
1065
- container = interp.stack_pop
1066
-
1067
- flags = interp.get_flags(module_id)
1068
- comparator = flags[:comparator]
1069
-
1070
- container ||= []
1071
- unless container.is_a?(Array)
1072
- interp.stack_push(container)
1073
- return
1074
- end
1075
-
1076
- # Figure out what to do
1077
- result = if comparator.is_a?(String)
1078
- sort_with_forthic(interp, comparator, flag_string_position, container)
1079
- elsif comparator.nil?
1080
- sort_without_comparator(container)
1081
- else
1082
- sort_with_key_func(container, comparator)
1083
- end
1084
-
1085
- interp.stack_push(result)
1086
- end
1087
-
1088
- # ( field -- key_func )
1089
- def word_FIELD_KEY_FUNC(interp)
1090
- field = interp.stack_pop
1091
-
1092
- result = lambda do |record|
1093
- record[field]
1094
- end
1095
-
1096
- interp.stack_push(result)
1097
- end
1098
-
1099
- # ( array n -- item )
1100
- # ( record n -- value )
1101
- def word_NTH(interp)
1102
- n = interp.stack_pop
1103
- container = interp.stack_pop
1104
-
1105
- if n.nil? || !container
1106
- interp.stack_push(nil)
1107
- return
1108
- end
1109
-
1110
- result = nil
1111
- if container.is_a?(Array)
1112
- if n < 0 || n >= container.length
1113
- interp.stack_push(nil)
1114
- return
1115
- end
1116
- result = container[n]
1117
- else
1118
- if n < 0 || n >= container.keys.length
1119
- interp.stack_push(nil)
1120
- return
1121
- end
1122
- keys = container.keys.sort
1123
- key = keys[n]
1124
- result = container[key]
1125
- end
1126
-
1127
- interp.stack_push(result)
1128
- end
1129
-
1130
- # ( array -- item )
1131
- # ( record -- value )
1132
- def word_LAST(interp)
1133
- container = interp.stack_pop
1134
-
1135
- if !container
1136
- interp.stack_push(nil)
1137
- return
1138
- end
1139
-
1140
- result = nil
1141
- if container.is_a?(Array)
1142
- result = if container.length == 0
1143
- nil
1144
- else
1145
- container[container.length - 1]
1146
- end
1147
- else
1148
- keys = container.keys.sort
1149
- result = if keys.length == 0
1150
- nil
1151
- else
1152
- container[keys[keys.length - 1]]
1153
- end
1154
- end
1155
-
1156
- interp.stack_push(result)
1157
- end
1158
-
1159
- # ( array -- a1 a2 .. an )
1160
- # ( record -- v1 v2 .. vn )
1161
- def word_UNPACK(interp)
1162
- container = interp.stack_pop
1163
-
1164
- container ||= []
1165
-
1166
- if container.is_a?(Array)
1167
- container.each do |item|
1168
- interp.stack_push(item)
1169
- end
1170
- else
1171
- keys = container.keys.sort
1172
- keys.each do |k|
1173
- interp.stack_push(container[k])
1174
- end
1175
- end
1176
- end
1177
-
1178
- # ( array -- array )
1179
- # ( record -- record )
1180
- def word_FLATTEN(interp)
1181
- nested = interp.stack_pop
1182
- flags = interp.get_flags(module_id)
1183
-
1184
- nested ||= []
1185
- depth = flags[:depth]
1186
-
1187
- result = if nested.is_a?(Array)
1188
- flatten_array(nested, depth)
1189
- else
1190
- flatten_record(nested, depth, {}, [])
1191
- end
1192
-
1193
- interp.stack_push(result)
1194
- end
1195
-
1196
- # ( array item -- index )
1197
- # ( record item -- key )
1198
- def word_KEY_OF(interp)
1199
- item = interp.stack_pop
1200
- container = interp.stack_pop
1201
-
1202
- container ||= []
1203
-
1204
- result = nil
1205
- if container.is_a?(Array)
1206
- index = container.index(item)
1207
- # If index is nil or < 0, return nil
1208
- result = (index.nil? || index < 0) ? nil : index
1209
- else
1210
- keys = container.keys
1211
- keys.each do |k|
1212
- v = container[k]
1213
- if v == item
1214
- result = k
1215
- break
1216
- end
1217
- end
1218
- end
1219
-
1220
- interp.stack_push(result)
1221
- end
1222
-
1223
- # ( array init forthic -- value )
1224
- # ( record init forthic -- value )
1225
- def word_REDUCE(interp)
1226
- forthic = interp.stack_pop
1227
- string_location = interp.get_string_location
1228
-
1229
- initial = interp.stack_pop
1230
- container = interp.stack_pop
1231
-
1232
- container ||= []
1233
-
1234
- interp.stack_push(initial)
1235
-
1236
- if container.is_a?(Array)
1237
- container.each do |item|
1238
- interp.stack_push(item)
1239
- interp.run(forthic, string_location)
1240
- end
1241
- else
1242
- container.each do |k, v|
1243
- interp.stack_push(v)
1244
- interp.run(forthic, string_location)
1245
- end
1246
- end
1247
-
1248
- result = interp.stack_pop
1249
-
1250
- interp.stack_push(result)
1251
- end
1252
-
1253
- # ( a -- )
1254
- def word_POP(interp)
1255
- interp.stack_pop
1256
- end
1257
-
1258
- # ( a -- a a )
1259
- def word_DUP(interp)
1260
- a = interp.stack_pop
1261
- interp.stack_push(a)
1262
- interp.stack_push(a)
1263
- end
1264
-
1265
- # ( a b -- b a )
1266
- def word_SWAP(interp)
1267
- b = interp.stack_pop
1268
- a = interp.stack_pop
1269
- interp.stack_push(b)
1270
- interp.stack_push(a)
1271
- end
1272
-
1273
- # ( item -- str )
1274
- def word_to_STR(interp)
1275
- item = interp.stack_pop
1276
- interp.stack_push(item.to_s)
1277
- end
1278
-
1279
- # ( str1 str2 -- str )
1280
- # ( array_of_str -- str )
1281
- def word_CONCAT(interp)
1282
- str2 = interp.stack_pop
1283
- array = str2.is_a?(Array) ? str2 : [interp.stack_pop, str2]
1284
- result = array.join("")
1285
- interp.stack_push(result)
1286
- end
1287
-
1288
- # ( string sep -- items )
1289
- def word_SPLIT(interp)
1290
- sep = interp.stack_pop
1291
- string = interp.stack_pop
1292
-
1293
- if !string
1294
- string = ""
1295
- end
1296
-
1297
- result = string.split(sep)
1298
- interp.stack_push(result)
1299
- end
1300
-
1301
- # ( strings sep -- string )
1302
- def word_JOIN(interp)
1303
- sep = interp.stack_pop
1304
- strings = interp.stack_pop
1305
-
1306
- if !strings
1307
- strings = []
1308
- end
1309
-
1310
- result = strings.join(sep)
1311
- interp.stack_push(result)
1312
- end
1313
-
1314
- # ( -- char )
1315
- def word_slash_N(interp)
1316
- interp.stack_push("\n")
1317
- end
1318
-
1319
- # ( -- char )
1320
- def word_slash_R(interp)
1321
- interp.stack_push("\r")
1322
- end
1323
-
1324
- # ( -- char )
1325
- def word_slash_T(interp)
1326
- interp.stack_push("\t")
1327
- end
1328
-
1329
- # ( A -- a )
1330
- def word_LOWERCASE(interp)
1331
- string = interp.stack_pop
1332
- result = string ? string.downcase : ""
1333
- interp.stack_push(result)
1334
- end
1335
-
1336
- # ( a -- A )
1337
- def word_UPPERCASE(interp)
1338
- string = interp.stack_pop
1339
- result = string ? string.upcase : ""
1340
- interp.stack_push(result)
1341
- end
1342
-
1343
- # ( string -- string )
1344
- def word_ASCII(interp)
1345
- string = interp.stack_pop
1346
-
1347
- if !string
1348
- string = ""
1349
- end
1350
-
1351
- result = ""
1352
- string.each_char do |ch|
1353
- result += ch if ch.ord < 256
1354
- end
1355
- interp.stack_push(result)
1356
- end
1357
-
1358
- # ( string -- string )
1359
- def word_STRIP(interp)
1360
- string = interp.stack_pop
1361
- result = string ? string.strip : ""
1362
- interp.stack_push(result)
1363
- end
1364
-
1365
- # ( string text replace -- string )
1366
- def word_REPLACE(interp)
1367
- replace = interp.stack_pop
1368
- text = interp.stack_pop
1369
- string = interp.stack_pop
1370
-
1371
- result = string
1372
- if string
1373
- pattern = Regexp.new(text)
1374
- result = string.gsub(pattern, replace)
1375
- end
1376
- interp.stack_push(result)
1377
- end
1378
-
1379
- # ( string pattern -- match )
1380
- def word_RE_MATCH(interp)
1381
- pattern = interp.stack_pop
1382
- string = interp.stack_pop
1383
-
1384
- re_pattern = Regexp.new(pattern)
1385
- result = false
1386
- if string
1387
- result = string.match(re_pattern)
1388
- end
1389
- interp.stack_push(result)
1390
- end
1391
-
1392
- # ( match num -- string )
1393
- def word_RE_MATCH_GROUP(interp)
1394
- num = interp.stack_pop
1395
- match = interp.stack_pop
1396
- result = nil
1397
- if match
1398
- result = match[num]
1399
- end
1400
- interp.stack_push(result)
1401
- end
1402
-
1403
- # ( string pattern -- matches )
1404
- def word_RE_MATCH_ALL(interp)
1405
- pattern = interp.stack_pop
1406
- string = interp.stack_pop
1407
-
1408
- re_pattern = Regexp.new(pattern)
1409
- matches = []
1410
- if string
1411
- matches = string.scan(re_pattern)
1412
- end
1413
- result = matches.map { |m| m[0] }
1414
- interp.stack_push(result)
1415
- end
1416
-
1417
- # ( -- nil )
1418
- def word_NULL(interp)
1419
- interp.stack_push(nil)
1420
- end
1421
-
1422
- # ( value default -- value )
1423
- def word_DEFAULT(interp)
1424
- default_value = interp.stack_pop
1425
- value = interp.stack_pop
1426
- result = (value.nil? || value == "") ? default_value : value
1427
- interp.stack_push(result)
1428
- end
1429
-
1430
- # ( value default_forthic -- value )
1431
- def word_star_DEFAULT(interp)
1432
- default_forthic = interp.stack_pop
1433
- value = interp.stack_pop
1434
-
1435
- if value.nil? || value == ""
1436
- interp.run(default_forthic)
1437
- value = interp.stack_pop
1438
- end
1439
- interp.stack_push(value)
1440
- end
1441
-
1442
- # ( item forthic num-times -- ? )
1443
- def word_l_REPEAT(interp)
1444
- num_times = interp.stack_pop
1445
- forthic = interp.stack_pop
1446
- string_location = interp.get_string_location
1447
-
1448
- num_times.times do
1449
- item = interp.stack_pop
1450
- interp.stack_push(item)
1451
-
1452
- interp.run(forthic, string_location)
1453
- res = interp.stack_pop
1454
-
1455
- interp.stack_push(item)
1456
- interp.stack_push(res)
1457
- end
1458
- end
1459
-
1460
- # ( value num_places -- str )
1461
- def word_to_FIXED(interp)
1462
- num_places = interp.stack_pop
1463
- value = interp.stack_pop
1464
- result = if value.nil?
1465
- ""
1466
- elsif !value.is_a?(Numeric)
1467
- value
1468
- else
1469
- # Round value to num_places
1470
- value.round(num_places)
1471
- end
1472
- interp.stack_push(result.to_s)
1473
- end
1474
-
1475
- # ( object -- json )
1476
- def word_to_JSON(interp)
1477
- object = interp.stack_pop
1478
- result = object.to_json
1479
- interp.stack_push(result)
1480
- end
1481
-
1482
- # ( json -- object )
1483
- def word_JSON_to(interp)
1484
- json = interp.stack_pop
1485
- result = JSON.parse(json)
1486
- interp.stack_push(result)
1487
- end
1488
-
1489
- # ( -- )
1490
- def word_dot_s(interp)
1491
- stack = interp.stack
1492
- puts "===> Stack <==="
1493
- if stack.length > 0
1494
- p stack[stack.length - 1]
1495
- else
1496
- puts "<STACK EMPTY>"
1497
- end
1498
- interp.halt
1499
- end
1500
-
1501
- # ( -- )
1502
- def word_dot_S(interp)
1503
- # Print full stack in reverse with index, pretty printing stack value
1504
- puts "===> Stack <==="
1505
- # Get reversed stack
1506
- reversed_stack = interp.stack.reverse
1507
- reversed_stack.each_with_index do |item, i|
1508
- puts "#{i}: #{item.inspect}"
1509
- end
1510
- end
1511
-
1512
- # ( time -- time )
1513
- def word_AM(interp)
1514
- time = interp.stack_pop
1515
- raise "AM expecting a time" unless time.is_a?(Time)
1516
-
1517
- result = time
1518
- if time.hour >= 12
1519
- result = Time.new(time.year, time.month, time.day, time.hour - 12, time.min, time.sec)
1520
- end
1521
- interp.stack_push(result)
1522
- end
1523
-
1524
- # ( time -- time )
1525
- def word_PM(interp)
1526
- time = interp.stack_pop
1527
- raise "PM expecting a time" unless time.is_a?(Time)
1528
-
1529
- result = time
1530
- if time.hour < 12
1531
- result = Time.new(time.year, time.month, time.day, time.hour + 12, time.min, time.sec)
1532
- end
1533
- interp.stack_push(result)
1534
- end
1535
-
1536
- # ( -- time )
1537
- def word_NOW(interp)
1538
- result = Time.now
1539
- interp.stack_push(result)
1540
- end
1541
-
1542
- # ( obj -- time )
1543
- def word_to_TIME(interp)
1544
- obj = interp.stack_pop
1545
- result = if obj.is_a?(Time)
1546
- obj
1547
- else
1548
- Time.parse(obj.to_s)
1549
- end
1550
- interp.stack_push(result)
1551
- end
1552
-
1553
- # ( obj -- date )
1554
- def word_to_DATE(interp)
1555
- obj = interp.stack_pop
1556
- result = if obj.is_a?(Date)
1557
- obj
1558
- else
1559
- Date.parse(obj.to_s)
1560
- end
1561
- interp.stack_push(result)
1562
- end
1563
-
1564
- # ( -- date )
1565
- def word_TODAY(interp)
1566
- result = Date.today
1567
- interp.stack_push(result)
1568
- end
1569
-
1570
- # ( -- date )
1571
- def word_MONDAY(interp)
1572
- result = get_day_this_week(0)
1573
- interp.stack_push(result)
1574
- end
1575
-
1576
- # ( -- date )
1577
- def word_TUESDAY(interp)
1578
- result = get_day_this_week(1)
1579
- interp.stack_push(result)
1580
- end
1581
-
1582
- # ( -- date )
1583
- def word_WEDNESDAY(interp)
1584
- result = get_day_this_week(2)
1585
- interp.stack_push(result)
1586
- end
1587
-
1588
- # ( -- date )
1589
- def word_THURSDAY(interp)
1590
- result = get_day_this_week(3)
1591
- interp.stack_push(result)
1592
- end
1593
-
1594
- # ( -- date )
1595
- def word_FRIDAY(interp)
1596
- result = get_day_this_week(4)
1597
- interp.stack_push(result)
1598
- end
1599
-
1600
- # ( -- date )
1601
- def word_SATURDAY(interp)
1602
- result = get_day_this_week(5)
1603
- interp.stack_push(result)
1604
- end
1605
-
1606
- # ( -- date )
1607
- def word_SUNDAY(interp)
1608
- result = get_day_this_week(6)
1609
- interp.stack_push(result)
1610
- end
1611
-
1612
- # ( date num-days -- date )
1613
- def word_ADD_DAYS(interp)
1614
- num_days = interp.stack_pop
1615
- date = interp.stack_pop
1616
-
1617
- result = date + num_days
1618
- interp.stack_push(result)
1619
- end
1620
-
1621
- # ( l_date r_date -- num_days )
1622
- def word_SUBTRACT_DATES(interp)
1623
- r_date = interp.stack_pop
1624
- l_date = interp.stack_pop
1625
- result = (l_date - r_date).to_i
1626
- interp.stack_push(result)
1627
- end
1628
-
1629
- # ( a b -- a+b )
1630
- # ( items -- sum )
1631
- def word_plus(interp)
1632
- items = interp.stack_pop
1633
-
1634
- if items.is_a?(Array)
1635
- sum = 0
1636
- items.each do |item|
1637
- sum += item
1638
- end
1639
- interp.stack_push(sum)
1640
- else
1641
- b = items
1642
- a = interp.stack_pop
1643
- interp.stack_push(a + b)
1644
- end
1645
- end
1646
-
1647
- # ( a b -- a - b )
1648
- def word_minus(interp)
1649
- b = interp.stack_pop
1650
- a = interp.stack_pop
1651
- interp.stack_push(a - b)
1652
- end
1653
-
1654
- # ( a b -- a*b )
1655
- def word_times(interp)
1656
- b = interp.stack_pop
1657
- result = 1
1658
- numbers = []
1659
-
1660
- if b.is_a?(Array)
1661
- numbers = b
1662
- else
1663
- a = interp.stack_pop
1664
- numbers = [a, b]
1665
- end
1666
-
1667
- nil_found = numbers.any?(&:nil?)
1668
- if nil_found
1669
- interp.stack_push(nil)
1670
- return
1671
- end
1672
-
1673
- numbers.each do |num|
1674
- result *= num
1675
- end
1676
-
1677
- interp.stack_push(result)
1678
- end
1679
-
1680
- # ( a b -- a/b )
1681
- def word_divide_by(interp)
1682
- b = interp.stack_pop
1683
- a = interp.stack_pop
1684
- result = a / b
1685
- interp.stack_push(result)
1686
- end
1687
-
1688
- # ( value mod -- remainder )
1689
- def word_MOD(interp)
1690
- mod = interp.stack_pop
1691
- value = interp.stack_pop
1692
- result = value % mod
1693
- interp.stack_push(result)
1694
- end
1695
-
1696
- # ( numbers -- mean )
1697
- # ( records -- mean_record )
1698
- def word_MEAN(interp)
1699
- values = interp.stack_pop
1700
-
1701
- if !values || values.empty?
1702
- interp.stack_push(0)
1703
- return
1704
- end
1705
-
1706
- result = compute_mean(values)
1707
- interp.stack_push(result)
1708
- end
1709
-
1710
- # ( num1 num2 -- num )
1711
- # ( numbers -- num )
1712
- def word_MAX(interp)
1713
- num2 = interp.stack_pop
1714
-
1715
- numbers = []
1716
- if num2.is_a?(Array)
1717
- numbers = num2
1718
- else
1719
- num1 = interp.stack_pop
1720
- numbers = [num1, num2]
1721
- end
1722
-
1723
- interp.stack_push(numbers.max)
1724
- end
1725
-
1726
- # ( num1 num2 -- num )
1727
- # ( numbers -- num )
1728
- def word_MIN(interp)
1729
- num2 = interp.stack_pop
1730
-
1731
- numbers = []
1732
- if num2.is_a?(Array)
1733
- numbers = num2
1734
- else
1735
- num1 = interp.stack_pop
1736
- numbers = [num1, num2]
1737
- end
1738
-
1739
- interp.stack_push(numbers.min)
1740
- end
1741
-
1742
- # ( a -- a )
1743
- def word_ROUND(interp)
1744
- a = interp.stack_pop
1745
- result = a.round
1746
- interp.stack_push(result)
1747
- end
1748
-
1749
- # ( a b -- a == b )
1750
- # ( items -- all_equal )
1751
- def word_equal_equal(interp)
1752
- items = interp.stack_pop
1753
-
1754
- if items.is_a?(Array)
1755
- all_equal = items.all? { |item| item == items[0] }
1756
- interp.stack_push(all_equal)
1757
- else
1758
- b = items
1759
- a = interp.stack_pop
1760
- interp.stack_push(a == b)
1761
- end
1762
- end
1763
-
1764
- # ( a b -- a != b )
1765
- # ( items -- all_not_equal )
1766
- def word_not_equal(interp)
1767
- items = interp.stack_pop
1768
-
1769
- if items.is_a?(Array)
1770
- all_not_equal = items.all? { |item| item != items[0] }
1771
- interp.stack_push(all_not_equal)
1772
- else
1773
- b = items
1774
- a = interp.stack_pop
1775
- interp.stack_push(a != b)
1776
- end
1777
- end
1778
-
1779
- # ( l r -- bool )
1780
- def word_greater_than(interp)
1781
- r = interp.stack_pop
1782
- l = interp.stack_pop
1783
-
1784
- if l.nil? || r.nil?
1785
- interp.stack_push(nil)
1786
- return
1787
- end
1788
-
1789
- result = l > r
1790
- interp.stack_push(result)
1791
- end
1792
-
1793
- # ( l r -- bool )
1794
- def word_greater_than_or_equal(interp)
1795
- r = interp.stack_pop
1796
- l = interp.stack_pop
1797
-
1798
- if l.nil? || r.nil?
1799
- interp.stack_push(nil)
1800
- return
1801
- end
1802
-
1803
- result = l >= r
1804
- interp.stack_push(result)
1805
- end
1806
-
1807
- # ( l r -- bool )
1808
- def word_less_than(interp)
1809
- r = interp.stack_pop
1810
- l = interp.stack_pop
1811
-
1812
- if l.nil? || r.nil?
1813
- interp.stack_push(nil)
1814
- return
1815
- end
1816
-
1817
- result = l < r
1818
- interp.stack_push(result)
1819
- end
1820
-
1821
- # ( l r -- bool )
1822
- # ( items -- bool )
1823
- def word_OR(interp)
1824
- r = interp.stack_pop
1825
-
1826
- items = r.is_a?(Array) ? r : [interp.stack_pop, r]
1827
- result = items.any? { |item| item }
1828
- interp.stack_push(result)
1829
- end
1830
-
1831
- # ( l r -- bool )
1832
- # ( items -- bool )
1833
- def word_AND(interp)
1834
- r = interp.stack_pop
1835
-
1836
- items = r.is_a?(Array) ? r : [interp.stack_pop, r]
1837
- result = items.all? { |item| item }
1838
- interp.stack_push(result)
1839
- end
1840
-
1841
- # ( bool -- bool )
1842
- def word_NOT(interp)
1843
- value = interp.stack_pop
1844
- interp.stack_push(!value)
1845
- end
1846
-
1847
- # ( item items -- bool )
1848
- def word_IN(interp)
1849
- items = interp.stack_pop
1850
- item = interp.stack_pop
1851
-
1852
- if !items
1853
- items = []
1854
- end
1855
- result = items.include?(item)
1856
- interp.stack_push(result)
1857
- end
1858
-
1859
- # ( vals required_vals -- bool )
1860
- def word_ANY(interp)
1861
- required_vals = interp.stack_pop
1862
- vals = interp.stack_pop
1863
-
1864
- result = false
1865
- required_vals.each do |rv|
1866
- if vals.include?(rv)
1867
- result = true
1868
- break
1869
- end
1870
- end
1871
-
1872
- # If nothing is required, then all values are true
1873
- result = true if required_vals.empty?
1874
-
1875
- interp.stack_push(result)
1876
- end
1877
-
1878
- # ( vals required_vals -- bool )
1879
- def word_ALL(interp)
1880
- required_vals = interp.stack_pop
1881
- vals = interp.stack_pop
1882
-
1883
- vals ||= []
1884
- required_vals ||= []
1885
-
1886
- result = required_vals.all? { |val| vals.include?(val) }
1887
- interp.stack_push(result)
1888
- end
1889
-
1890
- # ( item -- bool )
1891
- def word_to_BOOL(interp)
1892
- item = interp.stack_pop
1893
- result = if item.nil? || item == "" || item == 0
1894
- false
1895
- else
1896
- !!item
1897
- end
1898
- interp.stack_push(result)
1899
- end
1900
-
1901
- # ( item -- int )
1902
- def word_to_INT(interp)
1903
- str = interp.stack_pop
1904
- result = str.to_i
1905
- interp.stack_push(result)
1906
- end
1907
-
1908
- # ( item -- float )
1909
- def word_to_FLOAT(interp)
1910
- str = interp.stack_pop
1911
- result = str.to_f
1912
- interp.stack_push(result)
1913
- end
1914
-
1915
- # ( val start_ranges -- index )
1916
- def word_RANGE_INDEX(interp)
1917
- start_ranges = interp.stack_pop
1918
- val = interp.stack_pop
1919
-
1920
- # Cap off the value ranges with infinity
1921
- start_ranges.push(Float::INFINITY)
1922
-
1923
- if val.nil? || start_ranges.nil?
1924
- interp.stack_push(nil)
1925
- return
1926
- end
1927
-
1928
- if val < start_ranges[0]
1929
- interp.stack_push(nil)
1930
- return
1931
- end
1932
-
1933
- result = nil
1934
- (0...start_ranges.length - 1).each do |i|
1935
- if val >= start_ranges[i] && val < start_ranges[i + 1]
1936
- result = i
1937
- break
1938
- end
1939
- end
1940
-
1941
- interp.stack_push(result)
1942
- end
1943
-
1944
- # ( -- Infinity )
1945
- def word_INFINITY(interp)
1946
- interp.stack_push(Float::INFINITY)
1947
- end
1948
-
1949
- # ( l r -- bool )
1950
- def word_less_than_or_equal(interp)
1951
- r = interp.stack_pop
1952
- l = interp.stack_pop
1953
-
1954
- if l.nil? || r.nil?
1955
- interp.stack_push(nil)
1956
- return
1957
- end
1958
-
1959
- result = l <= r
1960
- interp.stack_push(result)
1961
- end
1962
-
1963
- # ( date -- str )
1964
- def word_DATE_to_STR(interp)
1965
- date = interp.stack_pop
1966
- result = date_to_string(date)
1967
- interp.stack_push(result)
1968
- end
1969
-
1970
- # ( time -- str )
1971
- def word_TIME_to_STR(interp)
1972
- time = interp.stack_pop
1973
- result = time.strftime("%H:%M")
1974
- interp.stack_push(result)
1975
- end
1976
-
1977
- # ( date time -- datetime )
1978
- def word_DATE_TIME_to_DATETIME(interp)
1979
- time = interp.stack_pop
1980
- date = interp.stack_pop
1981
- dt_string = "#{date.year}-#{date.month}-#{date.day} #{time.hour}:#{time.min}"
1982
- result = Time.parse(dt_string)
1983
- interp.stack_push(result)
1984
- end
1985
-
1986
- # ( datetime -- timestamp )
1987
- def word_DATETIME_to_TIMESTAMP(interp)
1988
- datetime = interp.stack_pop
1989
- result = datetime.to_i
1990
- interp.stack_push(result)
1991
- end
1992
-
1993
- # ( timestamp -- datetime )
1994
- def word_TIMESTAMP_to_DATETIME(interp)
1995
- timestamp = interp.stack_pop
1996
- result = Time.at(timestamp)
1997
- interp.stack_push(result)
1998
- end
1999
-
2000
- # ( str -- datetime )
2001
- def word_STR_to_DATETIME(interp)
2002
- s = interp.stack_pop
2003
- result = Time.parse(s)
2004
- interp.stack_push(result)
2005
- end
2006
-
2007
- # ( str -- timestamp )
2008
- def word_STR_to_TIMESTAMP(interp)
2009
- s = interp.stack_pop
2010
- datetime = Time.parse(s)
2011
- result = datetime.to_i
2012
- interp.stack_push(result)
2013
- end
2014
-
2015
- # ( -- )
2016
- def word_bang_PUSH_ERROR(interp)
2017
- interp.modify_flags(module_id, {push_error: true})
2018
- end
2019
-
2020
- # ( -- )
2021
- def word_bang_WITH_KEY(interp)
2022
- interp.modify_flags(module_id, {with_key: true})
2023
- end
2024
-
2025
- # ( comparator -- )
2026
- def word_bang_COMPARATOR(interp)
2027
- comparator = interp.stack_pop
2028
- interp.modify_flags(module_id, {comparator: comparator})
2029
- end
2030
-
2031
- # ( -- )
2032
- def word_bang_PUSH_REST(interp)
2033
- interp.modify_flags(module_id, {push_rest: true})
2034
- end
2035
-
2036
- # ( depth -- )
2037
- #
2038
- # NOTE: `depth` of 0 is the same not having set depth
2039
- def word_bang_DEPTH(interp)
2040
- depth = interp.stack_pop
2041
- interp.modify_flags(module_id, {depth: depth})
2042
- end
2043
-
2044
- # ( num_inteprs -- )
2045
- def word_bang_INTERPS(interp)
2046
- num_interps = interp.stack_pop
2047
- interp.modify_flags(module_id, {interps: num_interps})
2048
- end
2049
-
2050
- # ( -- )
2051
- def word_PROFILE_START(interp)
2052
- interp.start_profiling
2053
- end
2054
-
2055
- # ( -- )
2056
- def word_PROFILE_END(interp)
2057
- interp.stop_profiling
2058
- end
2059
-
2060
- # ( label -- )
2061
- def word_PROFILE_TIMESTAMP(interp)
2062
- label = interp.stack_pop
2063
- interp.add_timestamp(label)
2064
- end
2065
-
2066
- # ( -- )
2067
- def word_PROFILE_DATA(interp)
2068
- histogram = interp.word_histogram
2069
- timestamps = interp.profile_timestamps
2070
-
2071
- result = {
2072
- word_counts: [],
2073
- timestamps: []
2074
- }
2075
-
2076
- histogram.each do |val|
2077
- rec = {word: val[:word], count: val[:count]}
2078
- result[:word_counts].push(rec)
2079
- end
2080
-
2081
- prev_time = 0.0
2082
- timestamps.each do |t|
2083
- rec = {
2084
- label: t[:label],
2085
- time_ms: t[:time_ms],
2086
- delta: t[:time_ms] - prev_time
2087
- }
2088
- prev_time = t[:time_ms]
2089
- result[:timestamps].push(rec)
2090
- end
2091
-
2092
- interp.stack_push(result)
2093
- end
2094
-
2095
- # ( string -- symbol )
2096
- def word_to_SYM(interp)
2097
- string = interp.stack_pop
2098
- result = string.to_sym
2099
- interp.stack_push(result)
2100
- end
2101
-
2102
- # --------------------------------------------------
2103
- # Helpers
2104
- private
2105
-
2106
- def drill_for_value(rec, fields)
2107
- cur_rec = rec
2108
- fields.each do |f|
2109
- if cur_rec.is_a?(Array) || cur_rec.is_a?(Hash)
2110
- cur_rec = cur_rec[f]
2111
- else
2112
- return nil
2113
- end
2114
- end
2115
- cur_rec
2116
- end
2117
-
2118
- def date_to_string(date)
2119
- date.strftime("%Y-%m-%d")
2120
- end
2121
-
2122
- def group_items(items, group_size)
2123
- num_groups = (items.length / group_size.to_f).ceil
2124
- res = []
2125
- remaining = items.dup
2126
- num_groups.times do
2127
- res.push(remaining.slice!(0, group_size))
2128
- end
2129
- res
2130
- end
2131
-
2132
- def extract_rec(record, keys)
2133
- res = {}
2134
- keys.each { |k| res[k] = record[k] }
2135
- res
2136
- end
2137
-
2138
- def execute_returning_error(interp, forthic, string_location)
2139
- result = nil
2140
- begin
2141
- interp.run(forthic, string_location)
2142
- rescue => e
2143
- result = e
2144
- end
2145
- result
2146
- end
2147
-
2148
- def normalize_index(index, length)
2149
- res = index
2150
- res += length if index < 0
2151
- res
2152
- end
2153
-
2154
- # Default sort
2155
- def sort_without_comparator(container)
2156
- # Separate nil values from non-nil values
2157
- nils, non_nils = container.partition(&:nil?)
2158
-
2159
- # Sort non_nils and append nils
2160
- non_nils.sort + nils
2161
- end
2162
-
2163
- # Sort using a forthic string
2164
- def sort_with_forthic(interp, forthic, flag_string_position, container)
2165
- aug_array = make_aug_array(interp, forthic, flag_string_position, container)
2166
- aug_array.sort! { |l, r| cmp_items(l, r) }
2167
- de_aug_array(aug_array)
2168
- end
2169
-
2170
- def make_aug_array(interp, forthic, flag_string_position, vals)
2171
- res = []
2172
- vals.each do |val|
2173
- interp.stack_push(val)
2174
- interp.run(forthic, flag_string_position)
2175
- aug_val = interp.stack_pop
2176
- res.push([val, aug_val])
2177
- end
2178
- res
2179
- end
2180
-
2181
- def cmp_items(l, r)
2182
- l_val = l[1]
2183
- r_val = r[1]
2184
-
2185
- if l_val < r_val
2186
- -1
2187
- elsif l_val > r_val
2188
- 1
2189
- else
2190
- 0
2191
- end
2192
- end
2193
-
2194
- def de_aug_array(aug_vals)
2195
- aug_vals.map { |aug_val| aug_val[0] }
2196
- end
2197
-
2198
- # Sort with key func
2199
- def sort_with_key_func(container, key_func)
2200
- container.sort do |l, r|
2201
- l_val = key_func.call(l)
2202
- r_val = key_func.call(r)
2203
- if l_val < r_val
2204
- -1
2205
- elsif l_val > r_val
2206
- 1
2207
- else
2208
- 0
2209
- end
2210
- end
2211
- end
2212
-
2213
- def add_to_record_result(item, key, keys, result)
2214
- new_key = (keys + [key]).join("\t")
2215
- result[new_key] = item
2216
- end
2217
-
2218
- def fully_flatten_record(record, res, keys)
2219
- record.each do |k, item|
2220
- if is_record(item)
2221
- fully_flatten_record(item, res, keys + [k])
2222
- else
2223
- add_to_record_result(item, k, keys, res)
2224
- end
2225
- end
2226
- res
2227
- end
2228
-
2229
- def flatten_record(record, depth, res, keys)
2230
- return fully_flatten_record(record, res, keys) if depth.nil?
2231
-
2232
- record.each do |k, item|
2233
- if depth > 0 && is_record(item)
2234
- flatten_record(item, depth - 1, res, keys + [k])
2235
- else
2236
- add_to_record_result(item, k, keys, res)
2237
- end
2238
- end
2239
- res
2240
- end
2241
-
2242
- def flatten_array(array, depth)
2243
- return array.flatten(depth) if depth
2244
- array.flatten
2245
- end
2246
-
2247
- def is_record(obj)
2248
- obj.is_a?(Hash) && !obj.empty?
2249
- end
2250
-
2251
- def get_day_this_week(day_of_week)
2252
- # Assume the start of the week is Monday and a day_of_week of 0 means a Monday
2253
- # Get the current day of the week
2254
- today = Date.today
2255
- current_day_of_week = today.wday
2256
-
2257
- # Return the date of the day_of_week
2258
- today - (current_day_of_week - day_of_week)
2259
- end
2260
-
2261
- # NOTE: Monday is the start of the week
2262
- def normalize_day(day)
2263
- day
2264
- end
2265
-
2266
- def compute_number_mean(numbers)
2267
- sum = numbers.reduce(0) { |acc, num| acc + num }
2268
- sum.to_f / numbers.length
2269
- end
2270
-
2271
- def compute_non_number_mean(objects)
2272
- non_null_objects = objects.reject { |obj| obj.nil? }
2273
- res = Hash.new(0)
2274
-
2275
- non_null_objects.each do |obj|
2276
- obj_str = obj.is_a?(String) ? obj : obj.to_json
2277
- res[obj_str] += 1
2278
- end
2279
-
2280
- res.each do |key, value|
2281
- res[key] = value.to_f / non_null_objects.length
2282
- end
2283
- res
2284
- end
2285
-
2286
- def compute_object_mean(records)
2287
- res = {}
2288
- records.each do |record|
2289
- record.each do |key, value|
2290
- res[key] ||= []
2291
- res[key] << value
2292
- end
2293
- end
2294
-
2295
- res.each do |key, values|
2296
- res[key] = compute_mean(values)
2297
- end
2298
- res
2299
- end
2300
-
2301
- def is_all_numbers(values)
2302
- values.all? { |val| val.is_a?(Numeric) }
2303
- end
2304
-
2305
- def is_all_records(values)
2306
- values.all? { |val| val.is_a?(Hash) }
2307
- end
2308
-
2309
- def select_non_null_values(values)
2310
- values.reject { |val| val.nil? }
2311
- end
2312
-
2313
- def compute_mean(values)
2314
- result = nil
2315
- if values.is_a?(Array)
2316
- non_null_values = select_non_null_values(values)
2317
- result = if is_all_numbers(non_null_values)
2318
- compute_number_mean(non_null_values)
2319
- elsif is_all_records(non_null_values)
2320
- compute_object_mean(non_null_values)
2321
- else
2322
- compute_non_number_mean(non_null_values)
2323
- end
2324
- end
2325
- result
2326
- end
2327
- end
2328
- end