Module: Stellwerk::Functions

Defined in:
lib/stellwerk/functions.rb

Class Method Summary collapse

Class Method Details

.ARRAY(*args) ⇒ Object

ARRAY function - creates an array from arguments Allows: values = ARRAY(1, 2, 3) which can then be used with SUM(values)



314
315
316
# File 'lib/stellwerk/functions.rb', line 314

def ARRAY(*args)
  args
end

.AVERAGE(*args) ⇒ Object Also known as: AVG



229
230
231
232
233
234
235
236
237
# File 'lib/stellwerk/functions.rb', line 229

def AVERAGE(*args)
  values = if args.length == 1 && args.first.is_a?(Array)
    coerce_numeric_list(args.first)
  else
    coerce_numeric_list(args)
  end
  return nil if values.empty?
  values.sum / values.length.to_f
end

.coerce_numeric_list(list) ⇒ Object



30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/stellwerk/functions.rb', line 30

def coerce_numeric_list(list)
  list.filter_map do |v|
    case v
    when Numeric then v.to_f
    when String
      v_strip = v.strip
      if v_strip.match?(/^[-+]?\d+(?:\.\d+)?$/)
        v_strip.to_f
      end
    else
      nil
    end
  end
end

.COUNT(*args) ⇒ Object

COUNT dual mode: counts non-nil entries. If single array arg, count its non-nil members.



125
126
127
128
129
130
131
# File 'lib/stellwerk/functions.rb', line 125

def COUNT(*args)
  if args.length == 1 && args.first.is_a?(Array)
    args.first.compact.length
  else
    args.compact.length
  end
end

.COUNTIF(collection, predicate_expr, calculator: Dentaku::Calculator.new) ⇒ Object



270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
# File 'lib/stellwerk/functions.rb', line 270

def COUNTIF(collection, predicate_expr, calculator: Dentaku::Calculator.new)
  arr = ensure_array(collection)
  return 0 if predicate_expr.nil? || predicate_expr.strip.empty?
  count = 0
  arr.each do |item|
    begin
      ctx = { "item" => item }
      if item.is_a?(Hash)
        item.each { |k, v| ctx[k.to_s] = v }
      end
      count += 1 if calculator.evaluate!(predicate_expr, ctx)
    rescue StandardError
      # ignore
    end
  end
  count
end

.DEEP_PROJECT(root, path_string, field_name) ⇒ Object

DEEP_PROJECT(root, path_string, field_name) Traverse a dotted path to reach an array, then extract field_name from each element. Example: DEEP_PROJECT(order, "items", "price") where order is an array of hashes.



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/stellwerk/functions.rb', line 74

def DEEP_PROJECT(root, path_string, field_name)
  return [] if root.nil?
  current = root
  path_segments = path_string.to_s.split(".")
  path_segments.each do |seg|
    break if current.nil?
    if current.is_a?(Hash)
      current = current[seg.to_sym] || current[seg.to_s]
    elsif current.respond_to?(seg)
      begin
        current = current.public_send(seg)
      rescue StandardError
        current = nil
      end
    else
      current = nil
    end
  end
  arr = ensure_array(current)
  arr.map do |item|
    if item.is_a?(Hash)
      item[field_name.to_sym] || item[field_name.to_s]
    elsif item.respond_to?(:[]) && !item.is_a?(String)
      begin
        item[field_name]
      rescue StandardError
        nil
      end
    elsif item.respond_to?(field_name)
      begin
        item.public_send(field_name)
      rescue StandardError
        nil
      end
    else
      nil
    end
  end
end

.DISTINCT(collection) ⇒ Object



225
226
227
# File 'lib/stellwerk/functions.rb', line 225

def DISTINCT(collection)
  ensure_array(collection).uniq
end

.ensure_array(arg) ⇒ Object



24
25
26
27
28
# File 'lib/stellwerk/functions.rb', line 24

def ensure_array(arg)
  return [] if arg.nil?
  return arg if arg.is_a?(Array)
  [arg]
end

.FILTER(collection, predicate_expr, calculator: Dentaku::Calculator.new) ⇒ Object



209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/stellwerk/functions.rb', line 209

def FILTER(collection, predicate_expr, calculator: Dentaku::Calculator.new)
  arr = ensure_array(collection)
  return arr if predicate_expr.nil? || predicate_expr.strip.empty?
  arr.select do |item|
    begin
      ctx = { "item" => item }
      if item.is_a?(Hash)
        item.each { |k, v| ctx[k.to_s] = v }
      end
      !!calculator.evaluate!(predicate_expr, ctx)
    rescue StandardError
      false
    end
  end
end

.FIRST(arg) ⇒ Object



153
154
155
156
# File 'lib/stellwerk/functions.rb', line 153

def FIRST(arg)
  arr = ensure_array(arg)
  arr.first
end

.LAST(arg) ⇒ Object



158
159
160
161
# File 'lib/stellwerk/functions.rb', line 158

def LAST(arg)
  arr = ensure_array(arg)
  arr.last
end

.MAP(collection, expr, calculator: Dentaku::Calculator.new) ⇒ Object


Phase 2 Functions

MAP(collection, expr_string, safe: false)

  • Applies a Dentaku expression to each element with variable 'item' bound to element FILTER(collection, predicate_expr)
  • Keeps elements where predicate_expr evaluates truthy DISTINCT(collection)
  • Removes duplicates via Ruby equality AVERAGE/AVG dual-mode like SUM but returns nil if no numeric values SUMIF(collection, predicate_expr, projection_expr=nil)
  • If projection_expr provided, sum of projection for items where predicate matches
  • Else sum of numeric-looking items themselves COUNTIF(collection, predicate_expr)
  • Count of elements where predicate is truthy TRACE(value, label=nil)
  • Returns value unchanged; placeholder for future instrumentation hook


192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/stellwerk/functions.rb', line 192

def MAP(collection, expr, calculator: Dentaku::Calculator.new)
  arr = ensure_array(collection)
  return [] if expr.nil? || expr.strip.empty?
  arr.map do |item|
    begin
      # Provide 'item' binding; if item is a Hash, flatten simple keys
      ctx = { "item" => item }
      if item.is_a?(Hash)
        item.each { |k, v| ctx[k.to_s] = v }
      end
      calculator.evaluate!(expr, ctx)
    rescue StandardError
      nil
    end
  end
end

.MAX(*args) ⇒ Object

MAX dual mode: returns nil if no numeric values



144
145
146
147
148
149
150
151
# File 'lib/stellwerk/functions.rb', line 144

def MAX(*args)
  values = if args.length == 1 && args.first.is_a?(Array)
    coerce_numeric_list(args.first)
  else
    coerce_numeric_list(args)
  end
  values.max
end

.MIN(*args) ⇒ Object

MIN dual mode: returns nil if no numeric values



134
135
136
137
138
139
140
141
# File 'lib/stellwerk/functions.rb', line 134

def MIN(*args)
  values = if args.length == 1 && args.first.is_a?(Array)
    coerce_numeric_list(args.first)
  else
    coerce_numeric_list(args)
  end
  values.min
end

.PROJECT(collection, field_name) ⇒ Object

Internal projection helper: given an array of hashes (or objects responding to [] / .public_send) and a field name, extract that field, allowing nils. Named PROJECT (instead of __project) because Dentaku will constantize function names.



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/stellwerk/functions.rb', line 48

def PROJECT(collection, field_name)
  arr = ensure_array(collection)
  arr.map do |item|
    if item.is_a?(Hash)
      item[field_name.to_sym] || item[field_name.to_s]
    elsif item.respond_to?(:[]) && !item.is_a?(String)
      begin
        item[field_name]
      rescue StandardError
        nil
      end
    elsif item.respond_to?(field_name)
      begin
        item.public_send(field_name)
      rescue StandardError
        nil
      end
    else
      nil
    end
  end
end

.REDUCE(collection, initial, expr, calculator: Dentaku::Calculator.new) ⇒ Object



288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
# File 'lib/stellwerk/functions.rb', line 288

def REDUCE(collection, initial, expr, calculator: Dentaku::Calculator.new)
  arr = ensure_array(collection)
  return initial if arr.empty?

  accumulator = initial
  arr.each do |item|
    begin
      ctx = { "item" => item, "accumulator" => accumulator, "acc" => accumulator }
      if item.is_a?(Hash)
        item.each { |k, v| ctx[k.to_s] = v }
      end
      accumulator = calculator.evaluate!(expr, ctx)
    rescue StandardError
      nil
    end
  end
  accumulator
end

.SUM(*args) ⇒ Object

SUM dual mode



115
116
117
118
119
120
121
122
# File 'lib/stellwerk/functions.rb', line 115

def SUM(*args)
  values = if args.length == 1 && args.first.is_a?(Array)
    coerce_numeric_list(args.first)
  else
    coerce_numeric_list(args)
  end
  values.sum
end

.SUMIF(collection, predicate_expr, projection_expr = nil, calculator: Dentaku::Calculator.new) ⇒ Object



240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
# File 'lib/stellwerk/functions.rb', line 240

def SUMIF(collection, predicate_expr, projection_expr = nil, calculator: Dentaku::Calculator.new)
  arr = ensure_array(collection)
  return 0 if predicate_expr.nil? || predicate_expr.strip.empty?
  total = 0.0
  arr.each do |item|
    begin
      ctx = { "item" => item }
      if item.is_a?(Hash)
        item.each { |k, v| ctx[k.to_s] = v }
      end
      next unless calculator.evaluate!(predicate_expr, ctx)
      value = if projection_expr && !projection_expr.strip.empty?
        calculator.evaluate!(projection_expr, ctx) rescue nil
      else
        item
      end
      case value
      when Numeric
        total += value.to_f
      when String
        v = value.strip
        total += v.to_f if v.match?(/^[-+]?\d+(?:\.\d+)?$/)
      end
    rescue StandardError
      next
    end
  end
  total
end

.TAKE(arg, n) ⇒ Object



163
164
165
166
167
168
169
170
171
172
# File 'lib/stellwerk/functions.rb', line 163

def TAKE(arg, n)
  arr = ensure_array(arg)
  limit = begin
    Integer(n)
  rescue StandardError
    0
  end
  return [] if limit <= 0
  arr.first(limit)
end

.TRACE(value, _label = nil) ⇒ Object



307
308
309
310
# File 'lib/stellwerk/functions.rb', line 307

def TRACE(value, _label = nil)
  # Future: push to instrumentation buffer
  value
end