kumi 0.0.12 → 0.0.14

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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +0 -1
  3. data/BACKLOG.md +34 -0
  4. data/CHANGELOG.md +15 -0
  5. data/CLAUDE.md +4 -6
  6. data/README.md +0 -18
  7. data/config/functions.yaml +352 -0
  8. data/docs/dev/analyzer-debug.md +52 -0
  9. data/docs/dev/parse-command.md +64 -0
  10. data/docs/functions/analyzer_integration.md +199 -0
  11. data/docs/functions/signatures.md +171 -0
  12. data/examples/hash_objects_demo.rb +138 -0
  13. data/golden/array_operations/schema.kumi +17 -0
  14. data/golden/cascade_logic/schema.kumi +16 -0
  15. data/golden/mixed_nesting/schema.kumi +42 -0
  16. data/golden/simple_math/schema.kumi +10 -0
  17. data/lib/kumi/analyzer.rb +72 -21
  18. data/lib/kumi/core/analyzer/checkpoint.rb +72 -0
  19. data/lib/kumi/core/analyzer/debug.rb +167 -0
  20. data/lib/kumi/core/analyzer/passes/broadcast_detector.rb +1 -3
  21. data/lib/kumi/core/analyzer/passes/function_signature_pass.rb +199 -0
  22. data/lib/kumi/core/analyzer/passes/load_input_cse.rb +120 -0
  23. data/lib/kumi/core/analyzer/passes/lower_to_ir_pass.rb +99 -151
  24. data/lib/kumi/core/analyzer/passes/toposorter.rb +37 -1
  25. data/lib/kumi/core/analyzer/state_serde.rb +64 -0
  26. data/lib/kumi/core/analyzer/structs/access_plan.rb +12 -10
  27. data/lib/kumi/core/compiler/access_planner.rb +3 -2
  28. data/lib/kumi/core/function_registry/collection_functions.rb +3 -1
  29. data/lib/kumi/core/functions/dimension.rb +98 -0
  30. data/lib/kumi/core/functions/dtypes.rb +20 -0
  31. data/lib/kumi/core/functions/errors.rb +11 -0
  32. data/lib/kumi/core/functions/kernel_adapter.rb +45 -0
  33. data/lib/kumi/core/functions/loader.rb +119 -0
  34. data/lib/kumi/core/functions/registry_v2.rb +68 -0
  35. data/lib/kumi/core/functions/shape.rb +70 -0
  36. data/lib/kumi/core/functions/signature.rb +122 -0
  37. data/lib/kumi/core/functions/signature_parser.rb +86 -0
  38. data/lib/kumi/core/functions/signature_resolver.rb +272 -0
  39. data/lib/kumi/core/ir/execution_engine/interpreter.rb +98 -7
  40. data/lib/kumi/core/ir/execution_engine/profiler.rb +202 -0
  41. data/lib/kumi/core/ir/execution_engine.rb +30 -1
  42. data/lib/kumi/dev/ir.rb +75 -0
  43. data/lib/kumi/dev/parse.rb +105 -0
  44. data/lib/kumi/dev/runner.rb +83 -0
  45. data/lib/kumi/frontends/ruby.rb +28 -0
  46. data/lib/kumi/frontends/text.rb +46 -0
  47. data/lib/kumi/frontends.rb +29 -0
  48. data/lib/kumi/kernels/ruby/aggregate_core.rb +105 -0
  49. data/lib/kumi/kernels/ruby/datetime_scalar.rb +21 -0
  50. data/lib/kumi/kernels/ruby/mask_scalar.rb +15 -0
  51. data/lib/kumi/kernels/ruby/scalar_core.rb +63 -0
  52. data/lib/kumi/kernels/ruby/string_scalar.rb +19 -0
  53. data/lib/kumi/kernels/ruby/vector_struct.rb +39 -0
  54. data/lib/kumi/runtime/executable.rb +63 -20
  55. data/lib/kumi/schema.rb +4 -4
  56. data/lib/kumi/support/diff.rb +22 -0
  57. data/lib/kumi/support/ir_render.rb +61 -0
  58. data/lib/kumi/version.rb +1 -1
  59. data/lib/kumi.rb +2 -0
  60. data/performance_results.txt +63 -0
  61. data/scripts/test_mixed_nesting_performance.rb +206 -0
  62. metadata +45 -5
  63. data/docs/features/javascript-transpiler.md +0 -148
  64. data/lib/kumi/js.rb +0 -23
  65. data/lib/kumi/support/ir_dump.rb +0 -491
@@ -42,7 +42,36 @@ module Kumi
42
42
  # - DEBUG_GROUP_ROWS=1 prints grouping decisions during Lift.
43
43
  module ExecutionEngine
44
44
  def self.run(ir_module, ctx, accessors:, registry:)
45
- Interpreter.run(ir_module, ctx, accessors: accessors, registry: registry)
45
+ # Use persistent accessor cache if available, otherwise create temporary one
46
+ if ctx[:accessor_cache]
47
+ # Include input data in cache key to avoid cross-context pollution
48
+ input_key = ctx[:input]&.hash || ctx["input"]&.hash || 0
49
+ memoized_accessors = add_persistent_memoization(accessors, ctx[:accessor_cache], input_key)
50
+ else
51
+ memoized_accessors = add_temporary_memoization(accessors)
52
+ end
53
+
54
+ Interpreter.run(ir_module, ctx, accessors: memoized_accessors, registry: registry)
55
+ 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
+
68
+ def self.add_temporary_memoization(accessors)
69
+ cache = {}
70
+ accessors.map do |plan_id, accessor_fn|
71
+ [plan_id, lambda do |input_data|
72
+ cache[plan_id] ||= accessor_fn.call(input_data)
73
+ end]
74
+ end.to_h
46
75
  end
47
76
  end
48
77
  end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Kumi
6
+ module Dev
7
+ module IR
8
+ module_function
9
+
10
+ def to_text(ir_module)
11
+ raise "nil IR" unless ir_module
12
+
13
+ lines = []
14
+ lines << "IR Module"
15
+ lines << "decls: #{ir_module.decls.size}"
16
+
17
+ ir_module.decls.each_with_index do |decl, i|
18
+ lines << "decl[#{i}] #{decl.kind}:#{decl.name} shape=#{decl.shape} ops=#{decl.ops.size}"
19
+
20
+ decl.ops.each_with_index do |op, j|
21
+ # Sort attribute keys for deterministic output
22
+ sorted_attrs = op.attrs.keys.sort.map { |k| "#{k}=#{format_value(op.attrs[k])}" }.join(" ")
23
+ args_str = op.args.inspect
24
+ lines << " #{j}: #{op.tag} #{sorted_attrs} #{args_str}".rstrip
25
+ end
26
+ end
27
+
28
+ lines.join("\n") + "\n"
29
+ end
30
+
31
+ private
32
+
33
+ def self.format_value(val)
34
+ case val
35
+ when true, false
36
+ val.to_s
37
+ when Symbol
38
+ ":#{val}"
39
+ when Array
40
+ val.inspect
41
+ else
42
+ val.to_s
43
+ end
44
+ end
45
+
46
+ def to_json(ir_module, pretty: true)
47
+ raise "nil IR" unless ir_module
48
+
49
+ data = {
50
+ inputs: ir_module.inputs,
51
+ decls: ir_module.decls.map do |decl|
52
+ {
53
+ name: decl.name,
54
+ kind: decl.kind,
55
+ shape: decl.shape,
56
+ ops: decl.ops.map do |op|
57
+ {
58
+ tag: op.tag,
59
+ attrs: op.attrs,
60
+ args: op.args
61
+ }
62
+ end
63
+ }
64
+ end
65
+ }
66
+
67
+ if pretty
68
+ JSON.pretty_generate(data)
69
+ else
70
+ JSON.generate(data)
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+
5
+ module Kumi
6
+ module Dev
7
+ module Parse
8
+ module_function
9
+
10
+ def run(schema_path, opts = {})
11
+ # Load schema via text frontend
12
+ begin
13
+ schema, _inputs = Kumi::Frontends::Text.load(path: schema_path)
14
+ rescue LoadError => e
15
+ puts "Error: kumi-parser gem not available. Install: gem install kumi-parser"
16
+ return false
17
+ rescue StandardError => e
18
+ puts "Parse error: #{e.message}"
19
+ return false
20
+ end
21
+
22
+ # Run analyzer
23
+ runner_opts = opts.slice(:trace, :snap, :snap_dir, :resume_from, :resume_at, :stop_after)
24
+ res = Dev::Runner.run(schema, runner_opts)
25
+
26
+ unless res.ok?
27
+ puts "Analysis errors:"
28
+ res.errors.each { |err| puts " #{err}" }
29
+ return false
30
+ end
31
+
32
+ unless res.ir
33
+ puts "Error: No IR generated"
34
+ return false
35
+ end
36
+
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
41
+
42
+ # Determine file extension and renderer
43
+ extension = opts[:json] ? "json" : "txt"
44
+ golden_path = File.join(File.dirname(schema_path), "expected", "ir.#{extension}")
45
+
46
+ # Render IR
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
52
+
53
+ # Handle write mode
54
+ if opts[:write]
55
+ FileUtils.mkdir_p(File.dirname(golden_path))
56
+ File.write(golden_path, rendered)
57
+ puts "Wrote: #{golden_path}"
58
+ return true
59
+ end
60
+
61
+ # Handle update mode (write only if different)
62
+ if opts[:update]
63
+ if File.exist?(golden_path) && File.read(golden_path) == rendered
64
+ puts "No changes (#{golden_path})"
65
+ return true
66
+ else
67
+ FileUtils.mkdir_p(File.dirname(golden_path))
68
+ File.write(golden_path, rendered)
69
+ puts "Updated: #{golden_path}"
70
+ return true
71
+ end
72
+ end
73
+
74
+ # Handle no-diff mode
75
+ if opts[:no_diff]
76
+ puts rendered
77
+ return true
78
+ end
79
+
80
+ # Default: diff mode (same as write but show diff instead)
81
+ if File.exist?(golden_path)
82
+ # Use diff directly with the golden file path
83
+ require "tempfile"
84
+ Tempfile.create(["actual", File.extname(golden_path)]) do |actual_file|
85
+ actual_file.write(rendered)
86
+ actual_file.flush
87
+
88
+ result = `diff -u --label=expected --label=actual #{golden_path} #{actual_file.path}`
89
+ if result.empty?
90
+ puts "No changes (#{golden_path})"
91
+ return true
92
+ else
93
+ puts result.chomp
94
+ return false
95
+ end
96
+ end
97
+ else
98
+ # No golden file exists, just print the output
99
+ puts rendered
100
+ return true
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Dev
5
+ module Runner
6
+ Result = Struct.new(:state, :ir, :errors, keyword_init: true) do
7
+ def ok?
8
+ errors.empty?
9
+ end
10
+ end
11
+
12
+ module_function
13
+
14
+ def run(schema, opts = {})
15
+ # Set ENV vars for debug/checkpoint based on opts
16
+ setup_env_vars(opts)
17
+
18
+ state = Core::Analyzer::AnalysisState.new
19
+ errors = []
20
+
21
+ begin
22
+ final_state = Kumi::Analyzer.run_analysis_passes(schema, Kumi::Analyzer::DEFAULT_PASSES, state, errors)
23
+ ir = final_state[:ir_module]
24
+
25
+ result = Result.new(
26
+ state: final_state,
27
+ ir: ir,
28
+ errors: errors
29
+ )
30
+
31
+ # Report trace file if enabled
32
+ if opts[:trace] && defined?(@trace_file) && @trace_file
33
+ trace_file_path = @trace_file
34
+ result.define_singleton_method(:trace_file) { trace_file_path }
35
+ end
36
+
37
+ result
38
+ rescue StandardError => e
39
+ # Convert exception to error if not already captured
40
+ errors << e.message unless errors.include?(e.message)
41
+ Result.new(
42
+ state: state,
43
+ ir: nil,
44
+ errors: errors
45
+ )
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def self.setup_env_vars(opts)
52
+ if opts[:trace]
53
+ ENV["KUMI_DEBUG_STATE"] = "1"
54
+ trace_file = ENV["KUMI_DEBUG_FILE"] || "tmp/state_trace.jsonl"
55
+ ENV["KUMI_DEBUG_FILE"] = trace_file
56
+
57
+ # Store for later reporting
58
+ @trace_file = trace_file
59
+ end
60
+
61
+ if opts[:snap]
62
+ ENV["KUMI_CHECKPOINT_PHASES"] = opts[:snap]
63
+ end
64
+
65
+ if opts[:snap_dir]
66
+ ENV["KUMI_CHECKPOINT_DIR"] = opts[:snap_dir]
67
+ end
68
+
69
+ if opts[:resume_from]
70
+ ENV["KUMI_CHECKPOINT_RESUME_FROM"] = opts[:resume_from]
71
+ end
72
+
73
+ if opts[:resume_at]
74
+ ENV["KUMI_CHECKPOINT_RESUME_AT"] = opts[:resume_at]
75
+ end
76
+
77
+ if opts[:stop_after]
78
+ ENV["KUMI_CHECKPOINT_STOP_AFTER"] = opts[:stop_after]
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Frontends
5
+ module Ruby
6
+ module_function
7
+
8
+ def load(path:, inputs: {})
9
+ mod = Module.new
10
+ mod.extend(Kumi::Schema)
11
+ mod.module_eval(File.read(path), path)
12
+
13
+ # Extract just the syntax tree AST (same as Text frontend)
14
+ schema_ast = if mod.const_defined?(:GoldenSchema)
15
+ golden = mod.const_get(:GoldenSchema)
16
+ golden.build if golden.respond_to?(:build)
17
+ golden.__syntax_tree__
18
+ elsif mod.__syntax_tree__
19
+ mod.__syntax_tree__
20
+ else
21
+ raise "No schema AST found. Make sure the .rb file calls 'schema do...end'"
22
+ end
23
+
24
+ [schema_ast, inputs]
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Frontends
5
+ module Text
6
+ module_function
7
+
8
+ def load(path:, inputs: {})
9
+ src = File.read(path)
10
+
11
+ begin
12
+ require "kumi-parser"
13
+ ast = Kumi::Parser::TextParser.parse(src)
14
+ Core::Analyzer::Debug.info(:parse, kind: :text, file: path, ok: true) if Core::Analyzer::Debug.enabled?
15
+ [ast, inputs]
16
+ rescue LoadError
17
+ raise "kumi-parser gem not available. Install: gem install kumi-parser"
18
+ rescue StandardError => e
19
+ loc = (e.respond_to?(:location) && e.location) || {}
20
+ line, col = loc.values_at(:line, :column)
21
+ snippet = self.code_frame(src, line, col)
22
+ raise StandardError, "#{path}:#{line || '?'}:#{col || '?'}: #{e.message}\n#{snippet}"
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def self.code_frame(src, line, col, context: 2)
29
+ return "" unless line
30
+
31
+ lines = src.lines
32
+ from = [line - 1 - context, 0].max
33
+ to = [line - 1 + context, lines.length - 1].min
34
+ out = []
35
+
36
+ (from..to).each do |i|
37
+ prefix = (i + 1 == line) ? "➤" : " "
38
+ out << "#{prefix} %4d | %s" % [i + 1, lines[i].rstrip]
39
+ out << " | %s^" % (" " * (col - 1)) if i + 1 == line && col
40
+ end
41
+
42
+ out.join("\n")
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Frontends
5
+ def self.load(path:, inputs: {})
6
+ mode = (ENV["KUMI_PARSER"] || "auto") # auto|text|ruby
7
+ ext = File.extname(path)
8
+
9
+ # Explicit mode selection
10
+ return Text.load(path:, inputs:) if mode == "text"
11
+ return Ruby.load(path:, inputs:) if mode == "ruby"
12
+
13
+ # Auto mode: prefer .kumi if present
14
+ if mode == "auto" && ext == ".rb"
15
+ kumi_path = path.sub(/\.rb\z/, ".kumi")
16
+ if File.exist?(kumi_path)
17
+ return Text.load(path: kumi_path, inputs: inputs)
18
+ end
19
+ end
20
+
21
+ # File extension based selection
22
+ return Text.load(path:, inputs:) if ext == ".kumi"
23
+ return Ruby.load(path:, inputs:) if ext == ".rb"
24
+
25
+ # Default fallback
26
+ Ruby.load(path:, inputs:)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Kernels
5
+ module Ruby
6
+ module AggregateCore
7
+ module_function
8
+
9
+ def kumi_sum(enum, skip_nulls: true, min_count: 0)
10
+ total = 0
11
+ count = 0
12
+ enum.each do |x|
13
+ next if skip_nulls && x.nil?
14
+
15
+ total += x
16
+ count += 1
17
+ end
18
+ return nil if count < min_count
19
+
20
+ total
21
+ end
22
+
23
+ def kumi_min(enum, skip_nulls: true, min_count: 0)
24
+ best = nil
25
+ count = 0
26
+ enum.each do |x|
27
+ next if skip_nulls && x.nil?
28
+
29
+ best = x if best.nil? || x < best
30
+ count += 1
31
+ end
32
+ return nil if count < min_count
33
+
34
+ best
35
+ end
36
+
37
+ def kumi_max(enum, skip_nulls: true, min_count: 0)
38
+ best = nil
39
+ count = 0
40
+ enum.each do |x|
41
+ next if skip_nulls && x.nil?
42
+
43
+ best = x if best.nil? || x > best
44
+ count += 1
45
+ end
46
+ return nil if count < min_count
47
+
48
+ best
49
+ end
50
+
51
+ def kumi_mean(enum, skip_nulls: true, min_count: 0)
52
+ total = 0.0
53
+ count = 0
54
+ enum.each do |x|
55
+ next if skip_nulls && x.nil?
56
+
57
+ total += x
58
+ count += 1
59
+ end
60
+ return nil if count < [min_count, 1].max
61
+
62
+ total / count
63
+ end
64
+
65
+ def kumi_any(enum, skip_nulls: true, min_count: 0)
66
+ count = 0
67
+ enum.each do |x|
68
+ next if skip_nulls && x.nil?
69
+
70
+ return true if x
71
+ count += 1
72
+ end
73
+ return nil if count < min_count
74
+
75
+ false
76
+ end
77
+
78
+ def kumi_all(enum, skip_nulls: true, min_count: 0)
79
+ count = 0
80
+ enum.each do |x|
81
+ next if skip_nulls && x.nil?
82
+
83
+ return false unless x
84
+ count += 1
85
+ end
86
+ return nil if count < min_count
87
+
88
+ true
89
+ end
90
+
91
+ def kumi_count(enum, skip_nulls: true, min_count: 0)
92
+ count = 0
93
+ enum.each do |x|
94
+ next if skip_nulls && x.nil?
95
+
96
+ count += 1
97
+ end
98
+ return nil if count < min_count
99
+
100
+ count
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
4
+
5
+ module Kumi
6
+ module Kernels
7
+ module Ruby
8
+ module DatetimeScalar
9
+ module_function
10
+
11
+ def dt_add_days(d, n)
12
+ d + n
13
+ end
14
+
15
+ def dt_diff_days(d1, d2)
16
+ (d1 - d2).to_i
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Kernels
5
+ module Ruby
6
+ module MaskScalar
7
+ module_function
8
+
9
+ def where(condition, if_true, if_false)
10
+ condition ? if_true : if_false
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Kernels
5
+ module Ruby
6
+ module ScalarCore
7
+ module_function
8
+
9
+ def kumi_add(a, b)
10
+ a + b
11
+ end
12
+
13
+ def kumi_sub(a, b)
14
+ a - b
15
+ end
16
+
17
+ def kumi_mul(a, b)
18
+ a * b
19
+ end
20
+
21
+ def kumi_div(a, b)
22
+ a / b.to_f
23
+ end
24
+
25
+ def kumi_eq(a, b)
26
+ a == b
27
+ end
28
+
29
+ def kumi_gt(a, b)
30
+ a > b
31
+ end
32
+
33
+ def kumi_gte(a, b)
34
+ a >= b
35
+ end
36
+
37
+ def kumi_lt(a, b)
38
+ a < b
39
+ end
40
+
41
+ def kumi_lte(a, b)
42
+ a <= b
43
+ end
44
+
45
+ def kumi_ne(a, b)
46
+ a != b
47
+ end
48
+
49
+ def kumi_and(a, b)
50
+ a && b
51
+ end
52
+
53
+ def kumi_or(a, b)
54
+ a || b
55
+ end
56
+
57
+ def kumi_not(a)
58
+ !a
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Kernels
5
+ module Ruby
6
+ module StringScalar
7
+ module_function
8
+
9
+ def str_concat(a, b)
10
+ "#{a}#{b}"
11
+ end
12
+
13
+ def str_length(s)
14
+ s&.length
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Kernels
5
+ module Ruby
6
+ module VectorStruct
7
+ module_function
8
+
9
+ def size(vec)
10
+ vec&.size
11
+ end
12
+
13
+ def join_zip(left, right)
14
+ raise NotImplementedError, "join operations should be implemented in IR/VM"
15
+ end
16
+
17
+ def join_product(left, right)
18
+ raise NotImplementedError, "join operations should be implemented in IR/VM"
19
+ end
20
+
21
+ def align_to(vec, target_axes)
22
+ raise NotImplementedError, "align_to should be implemented in IR/VM"
23
+ end
24
+
25
+ def lift(vec, indices)
26
+ raise NotImplementedError, "lift should be implemented in IR/VM"
27
+ end
28
+
29
+ def flatten(*args)
30
+ raise NotImplementedError, "flatten should be implemented in IR/VM"
31
+ end
32
+
33
+ def take(values, indices)
34
+ raise NotImplementedError, "take should be implemented in IR/VM"
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end