kumi 0.0.15 → 0.0.16

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,50 @@
1
+ # frozen_string_literal: true
2
+ module Kumi::Core::Compiler::AccessEmit
3
+ module Ravel
4
+ extend Base
5
+ module_function
6
+ def build(plan)
7
+ policy = plan.on_missing || :error
8
+ key_policy = plan.key_policy || :indifferent
9
+ path_key = plan.path
10
+ segs = segment_ops(plan.operations)
11
+
12
+ code = +"lambda do |data|\n"
13
+ code << " out = []\n"
14
+ nodev, depth, loop_depth = "node0", 0, 0
15
+ code << " #{nodev} = data\n"
16
+
17
+ segs.each do |seg|
18
+ if seg == :array
19
+ code << " #{array_guard_code(node_var: nodev, mode: :ravel, policy: policy, path_key: path_key, map_depth: loop_depth)}\n"
20
+ code << " ary#{loop_depth} = #{nodev}\n"
21
+ code << " len#{loop_depth} = ary#{loop_depth}.length\n"
22
+ code << " i#{loop_depth} = -1\n"
23
+ code << " while (i#{loop_depth} += 1) < len#{loop_depth}\n"
24
+ child = "node#{depth + 1}"
25
+ code << " #{child} = ary#{loop_depth}[i#{loop_depth}]\n"
26
+ nodev = child; depth += 1; loop_depth += 1
27
+ else
28
+ seg.each do |(_, key, preview)|
29
+ code << " "
30
+ code << fetch_hash_code(node_var: nodev, key: key, key_policy: key_policy,
31
+ preview_array: preview, mode: :ravel, policy: policy,
32
+ path_key: path_key, map_depth: loop_depth)
33
+ code << "\n"
34
+ end
35
+ end
36
+ end
37
+
38
+ code << " out << #{nodev}\n"
39
+ while loop_depth.positive?
40
+ code << " end\n"
41
+ loop_depth -= 1
42
+ nodev = "node#{depth - 1}"
43
+ depth -= 1
44
+ end
45
+
46
+ code << " out\nend\n"
47
+ code
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+ module Kumi::Core::Compiler::AccessEmit
3
+ module Read
4
+ extend Base
5
+ module_function
6
+ def build(plan)
7
+ policy = plan.on_missing || :error
8
+ key_policy = plan.key_policy || :indifferent
9
+ path_key = plan.path
10
+ ops = plan.operations
11
+
12
+ body = ops.map do |op|
13
+ case op[:type]
14
+ when :enter_hash
15
+ fetch_hash_code(node_var: "node", key: op[:key], key_policy: key_policy,
16
+ preview_array: false, mode: :read, policy: policy,
17
+ path_key: path_key, map_depth: 0)
18
+ when :enter_array
19
+ %(raise TypeError, "Array encountered in :read accessor at '#{path_key}'")
20
+ end
21
+ end.join("\n ")
22
+
23
+ <<~RUBY
24
+ lambda do |data|
25
+ node = data
26
+ #{body}
27
+ node
28
+ end
29
+ RUBY
30
+ end
31
+ end
32
+ end
@@ -8,70 +8,34 @@ module Kumi
8
8
  module Interpreter
9
9
  PRODUCES_SLOT = %i[const load_input ref array map reduce lift align_to switch].freeze
10
10
  NON_PRODUCERS = %i[guard_push guard_pop assign store].freeze
11
+ EMPTY_ARY = [].freeze
11
12
 
12
- def self.build_name_index(ir_module)
13
- index = {}
14
- ir_module.decls.each do |decl|
15
- decl.ops.each do |op|
16
- next unless op.tag == :store
17
-
18
- name = op.attrs[:name]
19
- index[name] = decl if name
20
- end
21
- end
22
- index
23
- end
24
-
25
- def self.run(ir_module, ctx, accessors:, registry:)
26
- # Validate registry is properly initialized
27
- raise ArgumentError, "Registry cannot be nil" if registry.nil?
28
- raise ArgumentError, "Registry must be a Hash, got #{registry.class}" unless registry.is_a?(Hash)
29
-
13
+ def self.run(schedule, input:, runtime:, accessors:, registry:)
14
+ prof = Profiler.enabled?
30
15
  # --- PROFILER: init per run (but not in persistent mode) ---
31
- if Profiler.enabled?
32
- schema_name = ctx[:schema_name] || "UnknownSchema"
33
- if Profiler.persistent?
34
- # In persistent mode, just update schema name without full reset
35
- Profiler.set_schema_name(schema_name)
36
- else
37
- # Normal mode: full reset with schema name
38
- Profiler.reset!(meta: { decls: ir_module.decls&.size || 0, schema_name: schema_name })
39
- end
16
+ if prof
17
+ schema_name = runtime[:schema_name] || "UnknownSchema"
18
+ # In persistent mode, just update schema name without full reset
19
+ Profiler.set_schema_name(schema_name)
40
20
  end
41
21
 
42
22
  outputs = {}
43
- target = ctx[:target]
23
+ target = runtime[:target]
44
24
  guard_stack = [true]
45
25
 
46
- # Always ensure we have a declaration cache - either from caller or new for this VM run
47
- declaration_cache = ctx[:declaration_cache] || {}
48
-
49
- # Build name index for targeting by stored names
50
- name_index = ctx[:name_index] || (target ? build_name_index(ir_module) : nil)
26
+ # Caches live in runtime (engine frame), not input
27
+ declaration_cache = runtime[:declaration_cache]
51
28
 
52
29
  # Choose declarations to execute - prefer explicit schedule if present
53
- decls_to_run =
54
- if ctx[:decls_to_run]
55
- ctx[:decls_to_run] # array of decl objects
56
- elsif target
57
- # Prefer a decl that STORES the target (covers __vec twins)
58
- d = name_index && name_index[target]
59
- # Fallback: allow targeting by decl name (legacy behavior)
60
- d ||= ir_module.decls.find { |dd| dd.name == target }
61
- raise "Unknown target: #{target}" unless d
62
-
63
- [d]
64
- else
65
- ir_module.decls
66
- end
30
+ # decls_to_run = runtime[:decls_to_run] || ir_module.decls
67
31
 
68
- decls_to_run.each do |decl|
32
+ schedule.each do |decl|
69
33
  slots = []
70
34
  guard_stack = [true] # reset per decl
71
35
 
72
36
  decl.ops.each_with_index do |op, op_index|
73
- t0 = Profiler.enabled? ? Profiler.t0 : nil
74
- cpu_t0 = Profiler.enabled? ? Profiler.cpu_t0 : nil
37
+ t0 = prof ? Profiler.t0 : nil
38
+ cpu_t0 = prof ? Profiler.cpu_t0 : nil
75
39
  rows_touched = nil
76
40
  if ENV["ASSERT_VM_SLOTS"] == "1"
77
41
  expected = op_index
@@ -97,7 +61,7 @@ module Kumi
97
61
  false
98
62
  end
99
63
  slots << nil # keep slot_id == op_index
100
- if t0
64
+ if prof
101
65
  Profiler.record!(decl: decl.name, idx: op_index, tag: op.tag, op: op, t0: t0, cpu_t0: cpu_t0, rows: 0,
102
66
  note: "enter")
103
67
  end
@@ -122,92 +86,46 @@ module Kumi
122
86
 
123
87
  case op.tag
124
88
 
125
- when :assign
126
- dst = op.attrs[:dst]
127
- src = op.attrs[:src]
128
- raise "assign: dst/src OOB" if dst >= slots.length || src >= slots.length
129
-
130
- slots[dst] = slots[src]
131
- Profiler.record!(decl: decl.name, idx: op_index, tag: :assign, op: op, t0: t0, cpu_t0: cpu_t0, rows: 1) if t0
132
-
133
89
  when :const
134
90
  result = Values.scalar(op.attrs[:value])
135
- puts "DEBUG Const #{op.attrs[:value].inspect}: result=#{result}" if ENV["DEBUG_VM_ARGS"]
136
91
  slots << result
137
92
  Profiler.record!(decl: decl.name, idx: op_index, tag: :const, op: op, t0: t0, cpu_t0: cpu_t0, rows: 1) if t0
138
93
 
139
94
  when :load_input
140
95
  plan_id = op.attrs[:plan_id]
141
- scope = op.attrs[:scope] || []
96
+ scope = op.attrs[:scope] || EMPTY_ARY
142
97
  scalar = op.attrs[:is_scalar]
143
98
  indexed = op.attrs[:has_idx]
144
99
 
145
- # NEW: consult runtime accessor cache
146
- acc_cache = ctx[:accessor_cache] || {}
147
- input_obj = ctx[:input] || ctx["input"]
148
- cache_key = [plan_id, input_obj.object_id]
100
+ raw = accessors[plan_id].call(input) # <- memoized by ExecutionEngine
149
101
 
150
- if acc_cache.key?(cache_key)
151
- raw = acc_cache[cache_key]
152
- hit = true
153
- else
154
- raw = accessors.fetch(plan_id).call(input_obj)
155
- acc_cache[cache_key] = raw
156
- hit = false
157
- end
158
-
159
- puts "DEBUG LoadInput plan_id: #{plan_id} raw_values: #{raw.inspect} cache_hit: #{hit}" if ENV["DEBUG_VM_ARGS"]
160
102
  slots << if scalar
161
103
  Values.scalar(raw)
162
104
  elsif indexed
163
- rows_touched = raw.respond_to?(:size) ? raw.size : raw.count
105
+ rows_touched = prof && raw.respond_to?(:size) ? raw.size : raw.count
164
106
  Values.vec(scope, raw.map { |v, idx| { v: v, idx: Array(idx) } }, true)
165
107
  else
166
- rows_touched = raw.respond_to?(:size) ? raw.size : raw.count
108
+ rows_touched = prof && raw.respond_to?(:size) ? raw.size : raw.count
167
109
  Values.vec(scope, raw.map { |v| { v: v } }, false)
168
110
  end
169
111
  rows_touched ||= 1
170
- cache_note = hit ? "hit:#{plan_id}" : "miss:#{plan_id}"
171
112
  if t0
172
113
  Profiler.record!(decl: decl.name, idx: op_index, tag: :load_input, op: op, t0: t0, cpu_t0: cpu_t0,
173
- rows: rows_touched, note: cache_note)
114
+ rows: rows_touched, note: "ok")
174
115
  end
175
116
 
176
117
  when :ref
177
118
  name = op.attrs[:name]
178
-
179
- if outputs.key?(name)
180
- referenced = outputs[name]
181
- hit = :outputs
182
- elsif declaration_cache.key?(name)
183
- referenced = declaration_cache[name]
184
- hit = :cache
185
- else
186
- raise "unscheduled ref #{name}: producer not executed or dependency analysis failed"
187
- end
188
-
189
- if ENV["DEBUG_VM_ARGS"]
190
- puts "DEBUG Ref #{name}: #{referenced[:k] == :scalar ? "scalar(#{referenced[:v].inspect})" : "#{referenced[:k]}(#{referenced[:rows]&.size || 0} rows)"}"
191
- end
119
+ referenced = outputs[name] { raise "unscheduled ref #{name}: producer not executed or dependency analysis failed" }
192
120
 
193
121
  slots << referenced
194
122
  rows_touched = referenced[:k] == :vec ? (referenced[:rows]&.size || 0) : 1
195
- if t0
123
+ if prof
196
124
  Profiler.record!(decl: decl.name, idx: op_index, tag: :ref, op: op, t0: t0, cpu_t0: cpu_t0,
197
125
  rows: rows_touched, note: hit)
198
126
  end
199
127
 
200
128
  when :array
201
- # Validate slot indices before accessing
202
- op.args.each do |slot_idx|
203
- if slot_idx >= slots.length
204
- raise "Array operation: slot index #{slot_idx} out of bounds (slots.length=#{slots.length})"
205
- elsif slots[slot_idx].nil?
206
- raise "Array operation: slot #{slot_idx} is nil " \
207
- "(available slots: #{slots.length}, non-nil slots: #{slots.compact.length})"
208
- end
209
- end
210
-
211
129
  parts = op.args.map { |i| slots[i] }
212
130
  if parts.all? { |p| p[:k] == :scalar }
213
131
  slots << Values.scalar(parts.map { |p| p[:v] })
@@ -231,63 +149,43 @@ module Kumi
231
149
  fn_name = op.attrs[:fn]
232
150
  fn_entry = registry[fn_name] or raise "Function #{fn_name} not found in registry"
233
151
  fn = fn_entry.fn
234
- puts "DEBUG Map #{fn_name}: args=#{op.args.inspect}" if ENV["DEBUG_VM_ARGS"]
235
152
 
236
153
  # Validate slot indices before accessing
237
- op.args.each do |slot_idx|
238
- if slot_idx >= slots.length
239
- raise "Map operation #{fn_name}: slot index #{slot_idx} out of bounds (slots.length=#{slots.length})"
240
- elsif slots[slot_idx].nil?
241
- raise "Map operation #{fn_name}: slot #{slot_idx} is nil " \
242
- "(available slots: #{slots.length}, non-nil slots: #{slots.compact.length})"
243
- end
244
- end
154
+ # op.args.each do |slot_idx|
155
+ # if slot_idx >= slots.length
156
+ # raise "Map operation #{fn_name}: slot index #{slot_idx} out of bounds (slots.length=#{slots.length})"
157
+ # elsif slots[slot_idx].nil?
158
+ # raise "Map operation #{fn_name}: slot #{slot_idx} is nil " \
159
+ # "(available slots: #{slots.length}, non-nil slots: #{slots.compact.length})"
160
+ # end
161
+ # end
245
162
 
246
163
  args = op.args.map { |slot_idx| slots[slot_idx] }
247
164
 
248
165
  if args.all? { |a| a[:k] == :scalar }
249
- puts "DEBUG Scalar call #{fn_name}: args=#{args.map { |a| a[:v] }.inspect}" if ENV["DEBUG_VM_ARGS"]
250
166
  scalar_args = args.map { |a| a[:v] }
251
167
  result = fn.call(*scalar_args)
252
168
  slots << Values.scalar(result)
253
169
  else
254
170
  base = args.find { |a| a[:k] == :vec } or raise "Map needs a vec carrier"
255
- puts "DEBUG Vec call #{fn_name}: base=#{base.inspect}" if ENV["DEBUG_VM_ARGS"]
256
171
  # Preserve original order: broadcast scalars in-place
257
172
  arg_vecs = args.map { |a| a[:k] == :scalar ? Combinators.broadcast_scalar(a, base) : a }
258
- puts "DEBUG Vec call #{fn_name}: arg_vecs=#{arg_vecs.inspect}" if ENV["DEBUG_VM_ARGS"]
259
173
  scopes = arg_vecs.map { |v| v[:scope] }.uniq
260
- puts "DEBUG Vec call #{fn_name}: scopes=#{scopes.inspect}" if ENV["DEBUG_VM_ARGS"]
261
174
  raise "Cross-scope Map without Join" unless scopes.size <= 1
262
175
 
263
176
  zipped = Combinators.zip_same_scope(*arg_vecs)
264
177
 
265
- # if ENV["DEBUG_VM_ARGS"] && fn_name == :if
266
- # puts "DEBUG Vec call #{fn_name}: zipped rows:"
267
- # zipped[:rows].each_with_index do |row, i|
268
- # puts " [#{i}] args=#{Array(row[:v]).inspect}"
269
- # end
270
- # end
271
-
272
- puts "DEBUG Vec call #{fn_name}: zipped rows=#{zipped[:rows].inspect}" if ENV["DEBUG_VM_ARGS"]
273
178
  rows = zipped[:rows].map do |row|
274
179
  row_args = Array(row[:v])
275
180
  vr = fn.call(*row_args)
276
181
  row.key?(:idx) ? { v: vr, idx: row[:idx] } : { v: vr }
277
182
  end
278
- puts "DEBUG Vec call #{fn_name}: result rows=#{rows.inspect}" if ENV["DEBUG_VM_ARGS"]
279
183
 
280
184
  slots << Values.vec(base[:scope], rows, base[:has_idx])
281
185
  end
282
186
 
283
187
  when :switch
284
188
  chosen = op.attrs[:cases].find do |(cond_slot, _)|
285
- if cond_slot >= slots.length
286
- raise "Switch operation: condition slot #{cond_slot} out of bounds (slots.length=#{slots.length})"
287
- elsif slots[cond_slot].nil?
288
- raise "Switch operation: condition slot #{cond_slot} is nil (available slots: #{slots.length}, non-nil slots: #{slots.compact.length})"
289
- end
290
-
291
189
  c = slots[cond_slot]
292
190
  if c[:k] == :scalar
293
191
  !!c[:v]
@@ -297,22 +195,12 @@ module Kumi
297
195
  end
298
196
  end
299
197
  result_slot = chosen ? chosen[1] : op.attrs[:default]
300
- if result_slot >= slots.length
301
- raise "Switch operation: result slot #{result_slot} out of bounds (slots.length=#{slots.length})"
302
- elsif slots[result_slot].nil?
303
- raise "Switch operation: result slot #{result_slot} is nil (available slots: #{slots.length}, non-nil slots: #{slots.compact.length})"
304
- end
305
198
 
306
199
  slots << slots[result_slot]
307
200
 
308
201
  when :store
309
202
  name = op.attrs[:name]
310
203
  src = op.args[0] or raise "store: missing source slot"
311
- if src >= slots.length
312
- raise "Store operation '#{name}': source slot #{src} out of bounds (slots.length=#{slots.length})"
313
- elsif slots[src].nil?
314
- raise "Store operation '#{name}': source slot #{src} is nil (available slots: #{slots.length}, non-nil slots: #{slots.compact.length})"
315
- end
316
204
 
317
205
  result = slots[src]
318
206
  outputs[name] = result
@@ -329,10 +217,8 @@ module Kumi
329
217
  fn = fn_entry.fn
330
218
 
331
219
  src = slots[op.args[0]]
332
- raise "Reduce expects Vec" unless src[:k] == :vec
333
-
334
- result_scope = Array(op.attrs[:result_scope] || [])
335
- axis = Array(op.attrs[:axis] || [])
220
+ result_scope = op.attrs[:result_scope]
221
+ axis = op.attrs[:axis]
336
222
 
337
223
  if result_scope.empty?
338
224
  # === GLOBAL REDUCE ===
@@ -340,12 +226,6 @@ module Kumi
340
226
  vals = src[:rows].map { |r| r[:v] }
341
227
  slots << Values.scalar(fn.call(vals))
342
228
  else
343
- # === GROUPED REDUCE ===
344
- # Must have indices to group by prefix keys.
345
- unless src[:has_idx]
346
- raise "Grouped reduce requires indexed input (got ravel) for #{op.attrs[:fn]} at #{result_scope.inspect}"
347
- end
348
-
349
229
  group_len = result_scope.length
350
230
 
351
231
  # Preserve stable source order so zips with other @result_scope vecs line up.
@@ -368,39 +248,17 @@ module Kumi
368
248
 
369
249
  when :lift
370
250
  src_slot = op.args[0]
371
- if src_slot >= slots.length
372
- raise "Lift operation: source slot #{src_slot} out of bounds (slots.length=#{slots.length})"
373
- elsif slots[src_slot].nil?
374
- raise "Lift operation: source slot #{src_slot} is nil (available slots: #{slots.length}, non-nil slots: #{slots.compact.length})"
375
- end
376
251
 
377
252
  v = slots[src_slot]
378
- to_scope = op.attrs[:to_scope] || []
253
+ to_scope = op.attrs[:to_scope] || EMPTY_ARY
379
254
  depth = [to_scope.length, v[:rank] || v[:rows].first&.dig(:idx)&.length || 0].min
380
255
  slots << Values.scalar(Combinators.group_rows(v[:rows], depth))
381
256
 
382
257
  when :align_to
383
- tgt_slot = op.args[0]
384
- src_slot = op.args[1]
385
-
386
- if tgt_slot >= slots.length
387
- raise "AlignTo operation: target slot #{tgt_slot} out of bounds (slots.length=#{slots.length})"
388
- elsif slots[tgt_slot].nil?
389
- raise "AlignTo operation: target slot #{tgt_slot} is nil " \
390
- "(available slots: #{slots.length}, non-nil slots: #{slots.compact.length})"
391
- end
392
-
393
- if src_slot >= slots.length
394
- raise "AlignTo operation: source slot #{src_slot} out of bounds (slots.length=#{slots.length})"
395
- elsif slots[src_slot].nil?
396
- raise "AlignTo operation: source slot #{src_slot} is nil " \
397
- "(available slots: #{slots.length}, non-nil slots: #{slots.compact.length})"
398
- end
258
+ tgt = slots[op.args[0]]
259
+ src = slots[op.args[1]]
399
260
 
400
- tgt = slots[tgt_slot]
401
- src = slots[src_slot]
402
-
403
- to_scope = op.attrs[:to_scope] || []
261
+ to_scope = op.attrs[:to_scope] || EMPTY_ARY
404
262
  require_unique = op.attrs[:require_unique] || false
405
263
  on_missing = op.attrs[:on_missing] || :error
406
264
 
@@ -409,9 +267,6 @@ module Kumi
409
267
  on_missing: on_missing)
410
268
  slots << aligned
411
269
 
412
- when :join
413
- raise NotImplementedError, "Join not implemented yet"
414
-
415
270
  else
416
271
  raise "Unknown operation: #{op.tag}"
417
272
  end
@@ -13,14 +13,14 @@ module Kumi
13
13
 
14
14
  # Create a vector with scope and rows
15
15
  def self.vec(scope, rows, has_idx)
16
- if has_idx
17
- rank = rows.empty? ? 0 : rows.first[:idx].length
18
- # TODO: > Make sure this is not costly
19
- # raise if rows.any? { |r| r[:idx].length != rank }
20
- rows = rows.sort_by { |r| r[:idx] } # one-time sort
21
- else
22
- rank = 0
23
- end
16
+ rank = if has_idx
17
+ rows.empty? ? 0 : rows.first[:idx].length
18
+ # TODO: > Make sure this is not costly
19
+ # raise if rows.any? { |r| r[:idx].length != rank }
20
+ # rows = rows.sort_by { |r| r[:idx] } # one-time sort
21
+ else
22
+ 0
23
+ end
24
24
 
25
25
  { k: :vec, scope: scope, rows: rows, has_idx: has_idx, rank: rank }
26
26
  end
@@ -41,29 +41,13 @@ module Kumi
41
41
  # - DEBUG_VM_ARGS=1 prints per-op execution and arguments.
42
42
  # - DEBUG_GROUP_ROWS=1 prints grouping decisions during Lift.
43
43
  module ExecutionEngine
44
- def self.run(ir_module, ctx, accessors:, registry:)
45
- # Use persistent accessor cache if available, otherwise create temporary one
46
- memoized_accessors = Dev::Profiler.phase("engine.memoization") do
47
- # Include input data in cache key to avoid cross-context pollution
48
- input_key = ctx[:input]&.hash || ctx["input"]&.hash || 0
49
- add_persistent_memoization(accessors, ctx[:accessor_cache], input_key)
50
- end
44
+ def self.run(schedule, input:, accessors:, registry:, runtime: {})
45
+ runtime[:accessor_cache] ||= {}
51
46
 
52
47
  Dev::Profiler.phase("engine.interpreter") do
53
- Interpreter.run(ir_module, ctx, accessors: memoized_accessors, registry: registry)
48
+ Interpreter.run(schedule, input: input, runtime: runtime, accessors: accessors, registry: registry)
54
49
  end
55
50
  end
56
-
57
- private
58
-
59
- def self.add_persistent_memoization(accessors, cache, input_key)
60
- accessors.map do |plan_id, accessor_fn|
61
- [plan_id, lambda do |input_data|
62
- cache_key = [plan_id, input_key]
63
- cache[cache_key] ||= accessor_fn.call(input_data)
64
- end]
65
- end.to_h
66
- end
67
51
  end
68
52
  end
69
53
  end
@@ -35,20 +35,20 @@ module Kumi
35
35
  end
36
36
 
37
37
  # Report trace file if enabled
38
- if opts[:trace] && res.respond_to?(:trace_file)
39
- puts "Trace written to: #{res.trace_file}"
40
- end
38
+ puts "Trace written to: #{res.trace_file}" if opts[:trace] && res.respond_to?(:trace_file)
41
39
 
42
40
  # Determine file extension and renderer
43
41
  extension = opts[:json] ? "json" : "txt"
44
- golden_path = File.join(File.dirname(schema_path), "expected", "ir.#{extension}")
42
+
43
+ file_name = File.basename(schema_path)
44
+ golden_path = File.join(File.dirname(schema_path), "expected", "#{file_name}_ir.#{extension}")
45
45
 
46
46
  # Render IR
47
47
  rendered = if opts[:json]
48
- Dev::IR.to_json(res.ir, pretty: true)
49
- else
50
- Dev::IR.to_text(res.ir)
51
- end
48
+ Dev::IR.to_json(res.ir, pretty: true)
49
+ else
50
+ Dev::IR.to_text(res.ir)
51
+ end
52
52
 
53
53
  # Handle write mode
54
54
  if opts[:write]
@@ -71,7 +71,7 @@ module Kumi
71
71
  end
72
72
  end
73
73
 
74
- # Handle no-diff mode
74
+ # Handle no-diff mode
75
75
  if opts[:no_diff]
76
76
  puts rendered
77
77
  return true
@@ -84,7 +84,7 @@ module Kumi
84
84
  Tempfile.create(["actual", File.extname(golden_path)]) do |actual_file|
85
85
  actual_file.write(rendered)
86
86
  actual_file.flush
87
-
87
+
88
88
  result = `diff -u --label=expected --label=actual #{golden_path} #{actual_file.path}`
89
89
  if result.empty?
90
90
  puts "No changes (#{golden_path})"
@@ -97,9 +97,9 @@ module Kumi
97
97
  else
98
98
  # No golden file exists, just print the output
99
99
  puts rendered
100
- return true
100
+ true
101
101
  end
102
102
  end
103
103
  end
104
104
  end
105
- end
105
+ end