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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/lib/kumi/analyzer.rb +6 -1
- data/lib/kumi/core/analyzer/passes/ir_dependency_pass.rb +18 -20
- data/lib/kumi/core/analyzer/passes/ir_execution_schedule_pass.rb +67 -0
- data/lib/kumi/core/analyzer/passes/toposorter.rb +12 -15
- data/lib/kumi/core/compiler/access_builder.rb +22 -9
- data/lib/kumi/core/compiler/access_codegen.rb +61 -0
- data/lib/kumi/core/compiler/access_emit/base.rb +173 -0
- data/lib/kumi/core/compiler/access_emit/each_indexed.rb +56 -0
- data/lib/kumi/core/compiler/access_emit/materialize.rb +45 -0
- data/lib/kumi/core/compiler/access_emit/ravel.rb +50 -0
- data/lib/kumi/core/compiler/access_emit/read.rb +32 -0
- data/lib/kumi/core/ir/execution_engine/interpreter.rb +36 -181
- data/lib/kumi/core/ir/execution_engine/values.rb +8 -8
- data/lib/kumi/core/ir/execution_engine.rb +3 -19
- data/lib/kumi/dev/parse.rb +12 -12
- data/lib/kumi/runtime/executable.rb +22 -175
- data/lib/kumi/runtime/run.rb +105 -0
- data/lib/kumi/schema.rb +8 -13
- data/lib/kumi/version.rb +1 -1
- data/lib/kumi.rb +3 -2
- metadata +10 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9f51be8774c472629e599ce3edd4ab02551edfd52fea8676e2ee93eed5198800
|
4
|
+
data.tar.gz: 26532cec84e5c59553031dc86d82abff310d2a79c857776e1d793af2b35e00ea
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3ba28da3acbf5cd430902c29e34e5786562e7121f9c3851a2e9db3a04a13fc8b1a5e9729649f3daadb523ea55f2c97a1e4560139b9809aa636eff21e9716abbf
|
7
|
+
data.tar.gz: 6683358d25786a5e15cba975e785b3178ec0ea13f40a3eeb940e72f25886f5004c499f3e80ddded31fdd23f3981cbe5244db1ab0aec1405b3c52acb68c4b1e60
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.0.16] – 2025-08-22
|
4
|
+
|
5
|
+
### Performance
|
6
|
+
- Input accessor code generation replaces nested lambda chains with compiled Ruby methods
|
7
|
+
- Fix cache handling in Runtime - it was being recreated on updates
|
8
|
+
- Add early shortcut for Analyzer Passes.
|
9
|
+
|
3
10
|
## [0.0.15] – 2025-08-21
|
4
11
|
### Added
|
5
12
|
- (DX) Schema-aware VM profiling with multi-schema performance analysis
|
data/lib/kumi/analyzer.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
module Kumi
|
4
4
|
module Analyzer
|
5
5
|
Result = Struct.new(:definitions, :dependency_graph, :leaf_map, :topo_order, :decl_types, :state, keyword_init: true)
|
6
|
+
ERROR_THRESHOLD_PASS = Core::Analyzer::Passes::LowerToIRPass
|
6
7
|
|
7
8
|
DEFAULT_PASSES = [
|
8
9
|
Core::Analyzer::Passes::NameIndexer, # 1. Finds all names and checks for duplicates.
|
@@ -22,7 +23,9 @@ module Kumi
|
|
22
23
|
Core::Analyzer::Passes::JoinReducePlanningPass, # 16. Plans join/reduce operations (Generates IR Structs)
|
23
24
|
Core::Analyzer::Passes::LowerToIRPass, # 17. Lowers the schema to IR (Generates IR Structs)
|
24
25
|
Core::Analyzer::Passes::LoadInputCSE, # 18. Eliminates redundant load_input operations
|
25
|
-
Core::Analyzer::Passes::IRDependencyPass
|
26
|
+
Core::Analyzer::Passes::IRDependencyPass, # 19. Extracts IR-level dependencies for VM execution optimization
|
27
|
+
Core::Analyzer::Passes::IRExecutionSchedulePass # 20. Builds a precomputed execution schedule.
|
28
|
+
|
26
29
|
].freeze
|
27
30
|
|
28
31
|
def self.analyze!(schema, passes: DEFAULT_PASSES, **opts)
|
@@ -44,6 +47,8 @@ module Kumi
|
|
44
47
|
skipping = !!resume_at
|
45
48
|
|
46
49
|
passes.each_with_index do |pass_class, idx|
|
50
|
+
raise handle_analysis_errors(errors) if (ERROR_THRESHOLD_PASS == pass_class) && !errors.empty?
|
51
|
+
|
47
52
|
pass_name = pass_class.name.split("::").last
|
48
53
|
|
49
54
|
if skipping
|
@@ -7,19 +7,19 @@ module Kumi
|
|
7
7
|
# RESPONSIBILITY: Extract IR-level dependencies for VM execution optimization
|
8
8
|
# DEPENDENCIES: :ir_module from LowerToIRPass
|
9
9
|
# PRODUCES: :ir_dependencies - Hash mapping declaration names to referenced bindings
|
10
|
-
# :
|
10
|
+
# :ir_name_index - Hash mapping stored binding names to producing declarations
|
11
11
|
# INTERFACE: new(schema, state).run(errors)
|
12
|
-
#
|
12
|
+
#
|
13
13
|
# NOTE: This pass extracts actual IR-level dependencies by analyzing :ref operations
|
14
14
|
# in the generated IR, providing the dependency information needed for optimized VM scheduling.
|
15
15
|
class IRDependencyPass < PassBase
|
16
16
|
def run(errors)
|
17
17
|
ir_module = get_state(:ir_module, required: true)
|
18
|
-
|
18
|
+
|
19
19
|
ir_dependencies = build_ir_dependency_map(ir_module)
|
20
|
-
|
21
|
-
|
22
|
-
state.with(:ir_dependencies, ir_dependencies).with(:
|
20
|
+
ir_name_index = build_ir_name_index(ir_module)
|
21
|
+
|
22
|
+
state.with(:ir_dependencies, ir_dependencies).with(:ir_name_index, ir_name_index)
|
23
23
|
end
|
24
24
|
|
25
25
|
private
|
@@ -27,41 +27,39 @@ module Kumi
|
|
27
27
|
# Build a map of declaration -> [stored_bindings_it_references] from the IR
|
28
28
|
def build_ir_dependency_map(ir_module)
|
29
29
|
deps_map = {}
|
30
|
-
|
30
|
+
|
31
31
|
ir_module.decls.each do |decl|
|
32
32
|
refs = []
|
33
33
|
decl.ops.each do |op|
|
34
|
-
if op.tag == :ref
|
35
|
-
refs << op.attrs[:name]
|
36
|
-
end
|
34
|
+
refs << op.attrs[:name] if op.tag == :ref
|
37
35
|
end
|
38
36
|
deps_map[decl.name] = refs
|
39
37
|
end
|
40
|
-
|
38
|
+
|
41
39
|
deps_map.freeze
|
42
40
|
end
|
43
41
|
|
44
42
|
# Build name index to map stored binding names to their producing declarations
|
45
|
-
def
|
46
|
-
|
47
|
-
|
43
|
+
def build_ir_name_index(ir_module)
|
44
|
+
ir_name_index = {}
|
45
|
+
|
48
46
|
ir_module.decls.each do |decl|
|
49
47
|
# Map the primary declaration name
|
50
|
-
|
51
|
-
|
48
|
+
ir_name_index[decl.name] = decl
|
49
|
+
|
52
50
|
# Also map any vectorized twin names produced by this declaration
|
53
51
|
decl.ops.each do |op|
|
54
52
|
if op.tag == :store
|
55
53
|
stored_name = op.attrs[:name]
|
56
|
-
|
54
|
+
ir_name_index[stored_name] = decl
|
57
55
|
end
|
58
56
|
end
|
59
57
|
end
|
60
|
-
|
61
|
-
|
58
|
+
|
59
|
+
ir_name_index.freeze
|
62
60
|
end
|
63
61
|
end
|
64
62
|
end
|
65
63
|
end
|
66
64
|
end
|
67
|
-
end
|
65
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kumi
|
4
|
+
module Core
|
5
|
+
module Analyzer
|
6
|
+
module Passes
|
7
|
+
# PRODUCES: :execution_schedules => { store_name(Symbol) => [Decl, ...] }
|
8
|
+
class IRExecutionSchedulePass < PassBase
|
9
|
+
def run(errors)
|
10
|
+
ir = get_state(:ir_module, required: true)
|
11
|
+
deps = get_state(:ir_dependencies, required: true) # decl_name => [binding_name, ...]
|
12
|
+
name_index = get_state(:ir_name_index, required: true) # binding_name => Decl (← use IR-specific index)
|
13
|
+
|
14
|
+
by_name = ir.decls.to_h { |d| [d.name, d] }
|
15
|
+
pos = ir.decls.each_with_index.to_h # for deterministic ordering
|
16
|
+
|
17
|
+
closure_cache = {}
|
18
|
+
visiting = {}
|
19
|
+
|
20
|
+
visit = lambda do |dn|
|
21
|
+
return closure_cache[dn] if closure_cache.key?(dn)
|
22
|
+
|
23
|
+
raise Kumi::Core::Errors::TypeError, "cycle detected in IR at #{dn.inspect}" if visiting[dn]
|
24
|
+
|
25
|
+
visiting[dn] = true
|
26
|
+
|
27
|
+
# Resolve binding refs -> producing decl names
|
28
|
+
preds = Array(deps[dn]).filter_map { |b| name_index[b]&.name }.uniq
|
29
|
+
|
30
|
+
# Deterministic order: earlier IR decls first
|
31
|
+
preds.sort_by! { |n| pos[n] || Float::INFINITY }
|
32
|
+
|
33
|
+
order = []
|
34
|
+
preds.each do |p|
|
35
|
+
next if p == dn # guard against self-deps; treat as error if you prefer
|
36
|
+
|
37
|
+
order.concat(visit.call(p))
|
38
|
+
end
|
39
|
+
order << dn unless order.last == dn
|
40
|
+
|
41
|
+
visiting.delete(dn)
|
42
|
+
closure_cache[dn] = order.uniq.freeze
|
43
|
+
end
|
44
|
+
|
45
|
+
schedules = {}
|
46
|
+
|
47
|
+
ir.decls.each do |decl|
|
48
|
+
target_names = [decl.name] + decl.ops.select { _1.tag == :store }.map { _1.attrs[:name] }
|
49
|
+
|
50
|
+
seq = visit.call(decl.name).map { |dn| by_name.fetch(dn) }.freeze
|
51
|
+
|
52
|
+
target_names.each do |t|
|
53
|
+
if schedules.key?(t) && schedules[t] != seq
|
54
|
+
raise Kumi::Core::Errors::TypeError,
|
55
|
+
"duplicate schedule target #{t.inspect} produced by #{schedules[t].last.name} and #{decl.name}"
|
56
|
+
end
|
57
|
+
schedules[t] = seq
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
state.with(:ir_execution_schedules, schedules.freeze)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -18,7 +18,7 @@ module Kumi
|
|
18
18
|
# Create node index for later passes to use
|
19
19
|
node_index = build_node_index(definitions)
|
20
20
|
order = compute_topological_order(dependency_graph, definitions, errors)
|
21
|
-
|
21
|
+
|
22
22
|
state.with(:evaluation_order, order).with(:node_index, node_index)
|
23
23
|
end
|
24
24
|
|
@@ -26,34 +26,32 @@ module Kumi
|
|
26
26
|
|
27
27
|
def build_node_index(definitions)
|
28
28
|
index = {}
|
29
|
-
|
29
|
+
|
30
30
|
# Walk all declarations and their expressions to index every node
|
31
31
|
definitions.each_value do |decl|
|
32
32
|
index_node_recursive(decl, index)
|
33
33
|
end
|
34
|
-
|
34
|
+
|
35
35
|
index
|
36
36
|
end
|
37
|
-
|
37
|
+
|
38
38
|
def index_node_recursive(node, index)
|
39
39
|
return unless node
|
40
|
-
|
40
|
+
|
41
41
|
# Index this node by its object_id
|
42
42
|
index[node.object_id] = {
|
43
43
|
node: node,
|
44
|
-
type: node.class.name.split(
|
44
|
+
type: node.class.name.split("::").last,
|
45
45
|
metadata: {}
|
46
46
|
}
|
47
|
-
|
47
|
+
|
48
48
|
# Use the same approach as the visitor pattern - recursively index all children
|
49
|
-
if node.respond_to?(:children)
|
50
|
-
|
51
|
-
end
|
52
|
-
|
49
|
+
node.children.each { |child| index_node_recursive(child, index) } if node.respond_to?(:children)
|
50
|
+
|
53
51
|
# Index expression for declaration nodes
|
54
|
-
|
55
|
-
|
56
|
-
|
52
|
+
return unless node.respond_to?(:expression)
|
53
|
+
|
54
|
+
index_node_recursive(node.expression, index)
|
57
55
|
end
|
58
56
|
|
59
57
|
def compute_topological_order(graph, definitions, errors)
|
@@ -96,7 +94,6 @@ module Kumi
|
|
96
94
|
order.freeze
|
97
95
|
end
|
98
96
|
|
99
|
-
|
100
97
|
def report_unexpected_cycle(temp_marks, current_node, errors)
|
101
98
|
cycle_path = temp_marks.to_a.join(" → ") + " → #{current_node}"
|
102
99
|
|
@@ -2,18 +2,32 @@ module Kumi
|
|
2
2
|
module Core
|
3
3
|
module Compiler
|
4
4
|
class AccessBuilder
|
5
|
+
class << self
|
6
|
+
attr_accessor :strategy
|
7
|
+
end
|
8
|
+
|
9
|
+
self.strategy = if defined?(RubyVM::YJIT) && RubyVM::YJIT.enabled?
|
10
|
+
:interp
|
11
|
+
else
|
12
|
+
:codegen
|
13
|
+
end
|
14
|
+
|
5
15
|
def self.build(plans)
|
6
16
|
accessors = {}
|
7
17
|
plans.each_value do |variants|
|
8
18
|
variants.each do |plan|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
19
|
+
accessors[plan.accessor_key] =
|
20
|
+
case strategy
|
21
|
+
when :codegen then AccessCodegen.fetch_or_compile(plan)
|
22
|
+
else
|
23
|
+
build_proc_for(
|
24
|
+
mode: plan.mode,
|
25
|
+
path_key: plan.path,
|
26
|
+
missing: (plan.on_missing || :error).to_sym,
|
27
|
+
key_policy: (plan.key_policy || :indifferent).to_sym,
|
28
|
+
operations: plan.operations
|
29
|
+
)
|
30
|
+
end
|
17
31
|
end
|
18
32
|
end
|
19
33
|
accessors.freeze
|
@@ -25,7 +39,6 @@ module Kumi
|
|
25
39
|
when :materialize then Accessors::MaterializeAccessor.build(operations, path_key, missing, key_policy)
|
26
40
|
when :ravel then Accessors::RavelAccessor.build(operations, path_key, missing, key_policy)
|
27
41
|
when :each_indexed then Accessors::EachIndexedAccessor.build(operations, path_key, missing, key_policy, true)
|
28
|
-
when :each then Accessors::EachAccessor.build(operations, path_key, missing, key_policy)
|
29
42
|
else
|
30
43
|
raise "Unknown accessor mode: #{mode.inspect}"
|
31
44
|
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "digest/sha1"
|
4
|
+
|
5
|
+
module Kumi
|
6
|
+
module Core
|
7
|
+
module Compiler
|
8
|
+
class AccessCodegen
|
9
|
+
CACHE = {}
|
10
|
+
CACHE_MUTEX = Mutex.new
|
11
|
+
|
12
|
+
def self.fetch_or_compile(plan)
|
13
|
+
key = Digest::SHA1.hexdigest(Marshal.dump([plan.mode, plan.operations, plan.on_missing, plan.key_policy, plan.path]))
|
14
|
+
CACHE_MUTEX.synchronize do
|
15
|
+
CACHE[key] ||= compile(plan).tap(&:freeze)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.compile(plan)
|
20
|
+
case plan.mode
|
21
|
+
when :read then gen_read(plan)
|
22
|
+
when :materialize then gen_materialize(plan)
|
23
|
+
when :ravel then gen_ravel(plan)
|
24
|
+
when :each_indexed then gen_each_indexed(plan)
|
25
|
+
else
|
26
|
+
raise "Unknown accessor mode: #{plan.mode.inspect}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private_class_method def self.gen_read(plan)
|
31
|
+
code = AccessEmit::Read.build(plan)
|
32
|
+
debug_code(code, plan, "READ") if ENV["DEBUG_CODEGEN"]
|
33
|
+
eval(code, TOPLEVEL_BINDING)
|
34
|
+
end
|
35
|
+
|
36
|
+
private_class_method def self.gen_materialize(plan)
|
37
|
+
code = AccessEmit::Materialize.build(plan)
|
38
|
+
debug_code(code, plan, "MATERIALIZE") if ENV["DEBUG_CODEGEN"]
|
39
|
+
eval(code, TOPLEVEL_BINDING)
|
40
|
+
end
|
41
|
+
|
42
|
+
private_class_method def self.gen_ravel(plan)
|
43
|
+
code = AccessEmit::Ravel.build(plan)
|
44
|
+
debug_code(code, plan, "RAVEL") if ENV["DEBUG_CODEGEN"]
|
45
|
+
eval(code, TOPLEVEL_BINDING)
|
46
|
+
end
|
47
|
+
|
48
|
+
private_class_method def self.gen_each_indexed(plan)
|
49
|
+
code = AccessEmit::EachIndexed.build(plan)
|
50
|
+
debug_code(code, plan, "EACH_INDEXED") if ENV["DEBUG_CODEGEN"]
|
51
|
+
eval(code, TOPLEVEL_BINDING)
|
52
|
+
end
|
53
|
+
|
54
|
+
private_class_method def self.debug_code(code, plan, mode_name)
|
55
|
+
puts "=== Generated #{mode_name} code for #{plan.path}:#{plan.mode} ==="
|
56
|
+
puts code
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Kumi
|
3
|
+
module Core
|
4
|
+
module Compiler
|
5
|
+
module AccessEmit
|
6
|
+
module Base
|
7
|
+
module_function
|
8
|
+
|
9
|
+
# ---------- IR segmentation ----------
|
10
|
+
def segment_ops(ops)
|
11
|
+
segs, cur = [], []
|
12
|
+
i = 0
|
13
|
+
while i < ops.length
|
14
|
+
case ops[i][:type]
|
15
|
+
when :enter_hash
|
16
|
+
preview = (i + 1 < ops.length) && ops[i + 1][:type] == :enter_array
|
17
|
+
cur << [:enter_hash, ops[i][:key].to_s, preview]
|
18
|
+
when :enter_array
|
19
|
+
segs << cur unless cur.empty?
|
20
|
+
segs << :array
|
21
|
+
cur = []
|
22
|
+
else
|
23
|
+
raise "Unknown operation: #{ops[i].inspect}"
|
24
|
+
end
|
25
|
+
i += 1
|
26
|
+
end
|
27
|
+
segs << cur unless cur.empty?
|
28
|
+
segs
|
29
|
+
end
|
30
|
+
|
31
|
+
# ---------- codegen helpers ----------
|
32
|
+
def fetch_hash_code(node_var:, key:, key_policy:, preview_array:, mode:, policy:, path_key:, map_depth:)
|
33
|
+
effective_policy = preview_array ? :indifferent : (key_policy || :indifferent)
|
34
|
+
str = key.to_s.inspect
|
35
|
+
sym = key.to_sym.inspect
|
36
|
+
|
37
|
+
fetch =
|
38
|
+
case effective_policy
|
39
|
+
when :string
|
40
|
+
%(next_node = #{node_var}.key?(#{str}) ? #{node_var}[#{str}] : :__missing__)
|
41
|
+
when :symbol
|
42
|
+
%(next_node = #{node_var}.key?(#{sym}) ? #{node_var}[#{sym}] : :__missing__)
|
43
|
+
else # :indifferent
|
44
|
+
<<~RB.chomp
|
45
|
+
next_node =
|
46
|
+
if #{node_var}.key?(#{str}); #{node_var}[#{str}]
|
47
|
+
elsif #{node_var}.key?(#{sym}); #{node_var}[#{sym}]
|
48
|
+
elsif #{node_var}.key?(#{str}); #{node_var}[#{str}] # (string twice ok / predictable)
|
49
|
+
else :__missing__
|
50
|
+
end
|
51
|
+
RB
|
52
|
+
end
|
53
|
+
|
54
|
+
miss_action = build_miss_action(policy, mode, map_depth, preview_array, key: key, path_key: path_key)
|
55
|
+
|
56
|
+
<<~RB.chomp
|
57
|
+
raise TypeError, "Expected Hash at '#{path_key}' (#{mode})" unless #{node_var}.is_a?(Hash)
|
58
|
+
#{fetch}
|
59
|
+
if next_node == :__missing__
|
60
|
+
#{miss_action}
|
61
|
+
end
|
62
|
+
#{node_var} = next_node
|
63
|
+
RB
|
64
|
+
end
|
65
|
+
|
66
|
+
def array_guard_code(node_var:, mode:, policy:, path_key:, map_depth:)
|
67
|
+
miss_action = build_array_miss_action(policy, mode, map_depth, path_key)
|
68
|
+
<<~RB.chomp
|
69
|
+
if #{node_var}.nil?
|
70
|
+
#{miss_action}
|
71
|
+
end
|
72
|
+
unless #{node_var}.is_a?(Array)
|
73
|
+
raise TypeError, "Expected Array at '#{path_key}' (#{mode}); got \#{#{node_var}.class}"
|
74
|
+
end
|
75
|
+
RB
|
76
|
+
end
|
77
|
+
|
78
|
+
# ---------- missing behaviors ----------
|
79
|
+
def build_miss_action(policy, mode, map_depth, preview_array, key:, path_key:)
|
80
|
+
case policy
|
81
|
+
when :nil
|
82
|
+
if mode == :ravel
|
83
|
+
base = "out << nil"
|
84
|
+
cont = map_depth.positive? ? "next" : "return out"
|
85
|
+
"#{base}\n#{cont}"
|
86
|
+
elsif mode == :each_indexed
|
87
|
+
if map_depth.positive?
|
88
|
+
<<~RB.chomp
|
89
|
+
if block
|
90
|
+
block.call(nil, idx_vec.dup)
|
91
|
+
next
|
92
|
+
else
|
93
|
+
out << [nil, idx_vec.dup]
|
94
|
+
next
|
95
|
+
end
|
96
|
+
RB
|
97
|
+
else
|
98
|
+
<<~RB.chomp
|
99
|
+
if block
|
100
|
+
block.call(nil, idx_vec.dup)
|
101
|
+
return nil
|
102
|
+
else
|
103
|
+
out << [nil, idx_vec.dup]
|
104
|
+
return out
|
105
|
+
end
|
106
|
+
RB
|
107
|
+
end
|
108
|
+
else # :materialize, :read
|
109
|
+
# Important: for :materialize this is ALWAYS nil (never [])
|
110
|
+
return_val = 'nil'
|
111
|
+
map_depth.positive? ? "next #{return_val}" : "return #{return_val}"
|
112
|
+
end
|
113
|
+
when :skip
|
114
|
+
if mode == :materialize
|
115
|
+
return_val = preview_array ? '[]' : 'nil'
|
116
|
+
map_depth.positive? ? "next #{return_val}" : "return #{return_val}"
|
117
|
+
else
|
118
|
+
map_depth.positive? ? "next" : (mode == :each_indexed ? "if block; return nil; else; return out; end" : "return out")
|
119
|
+
end
|
120
|
+
else # :error
|
121
|
+
%(raise KeyError, "Missing key '#{key}' at '#{path_key}' (#{mode})")
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def build_array_miss_action(policy, mode, map_depth, path_key)
|
126
|
+
case policy
|
127
|
+
when :nil
|
128
|
+
if mode == :materialize
|
129
|
+
map_depth.positive? ? "next nil" : "return nil"
|
130
|
+
elsif mode == :each_indexed
|
131
|
+
if map_depth.positive?
|
132
|
+
<<~RB.chomp
|
133
|
+
if block
|
134
|
+
block.call(nil, idx_vec.dup)
|
135
|
+
next
|
136
|
+
else
|
137
|
+
out << [nil, idx_vec.dup]
|
138
|
+
next
|
139
|
+
end
|
140
|
+
RB
|
141
|
+
else
|
142
|
+
<<~RB.chomp
|
143
|
+
if block
|
144
|
+
block.call(nil, idx_vec.dup)
|
145
|
+
return nil
|
146
|
+
else
|
147
|
+
out << [nil, idx_vec.dup]
|
148
|
+
return out
|
149
|
+
end
|
150
|
+
RB
|
151
|
+
end
|
152
|
+
else # :ravel / others
|
153
|
+
base = "out << nil"
|
154
|
+
cont = map_depth.positive? ? "next" : "return out"
|
155
|
+
"#{base}\n#{cont}"
|
156
|
+
end
|
157
|
+
when :skip
|
158
|
+
if mode == :materialize
|
159
|
+
map_depth.positive? ? "next []" : "return []"
|
160
|
+
elsif mode == :each_indexed
|
161
|
+
map_depth.positive? ? "next" : "if block; return nil; else; return out; end"
|
162
|
+
else # :ravel
|
163
|
+
map_depth.positive? ? "next" : "return out"
|
164
|
+
end
|
165
|
+
else
|
166
|
+
%(raise TypeError, "Missing array at '#{path_key}' (#{mode})")
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Kumi::Core::Compiler::AccessEmit
|
3
|
+
module EachIndexed
|
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, &block|\n"
|
13
|
+
code << " out = []\n"
|
14
|
+
code << " node0 = data\n"
|
15
|
+
code << " idx_vec = []\n"
|
16
|
+
nodev, depth, loop_depth = "node0", 0, 0
|
17
|
+
|
18
|
+
segs.each do |seg|
|
19
|
+
if seg == :array
|
20
|
+
code << " #{array_guard_code(node_var: nodev, mode: :each_indexed, policy: policy, path_key: path_key, map_depth: loop_depth)}\n"
|
21
|
+
code << " ary#{loop_depth} = #{nodev}\n"
|
22
|
+
code << " len#{loop_depth} = ary#{loop_depth}.length\n"
|
23
|
+
code << " i#{loop_depth} = -1\n"
|
24
|
+
code << " while (i#{loop_depth} += 1) < len#{loop_depth}\n"
|
25
|
+
code << " idx_vec[#{loop_depth}] = i#{loop_depth}\n"
|
26
|
+
child = "node#{depth + 1}"
|
27
|
+
code << " #{child} = ary#{loop_depth}[i#{loop_depth}]\n"
|
28
|
+
nodev = child; depth += 1; loop_depth += 1
|
29
|
+
else
|
30
|
+
seg.each do |(_, key, preview)|
|
31
|
+
code << fetch_hash_code(node_var: nodev, key: key, key_policy: key_policy,
|
32
|
+
preview_array: preview, mode: :each_indexed, policy: policy,
|
33
|
+
path_key: path_key, map_depth: loop_depth)
|
34
|
+
code << "\n"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
code << " if block\n"
|
40
|
+
code << " block.call(#{nodev}, idx_vec.dup)\n"
|
41
|
+
code << " else\n"
|
42
|
+
code << " out << [#{nodev}, idx_vec.dup]\n"
|
43
|
+
code << " end\n"
|
44
|
+
|
45
|
+
while loop_depth.positive?
|
46
|
+
code << " end\n"
|
47
|
+
loop_depth -= 1
|
48
|
+
nodev = "node#{depth - 1}"
|
49
|
+
depth -= 1
|
50
|
+
end
|
51
|
+
|
52
|
+
code << " block ? nil : out\nend\n"
|
53
|
+
code
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Kumi::Core::Compiler::AccessEmit
|
3
|
+
module Materialize
|
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
|
+
nodev, depth, map_depth = "node0", 0, 0
|
14
|
+
code << " #{nodev} = data\n"
|
15
|
+
|
16
|
+
segs.each do |seg|
|
17
|
+
if seg == :array
|
18
|
+
code << " #{array_guard_code(node_var: nodev, mode: :materialize, policy: policy, path_key: path_key, map_depth: map_depth)}\n"
|
19
|
+
child = "node#{depth + 1}"
|
20
|
+
code << " #{nodev} = #{nodev}.map do |__e#{depth}|\n"
|
21
|
+
code << " #{child} = __e#{depth}\n"
|
22
|
+
nodev = child; depth += 1; map_depth += 1
|
23
|
+
else
|
24
|
+
seg.each do |(_, key, preview)|
|
25
|
+
code << " "
|
26
|
+
code << fetch_hash_code(node_var: nodev, key: key, key_policy: key_policy,
|
27
|
+
preview_array: preview, mode: :materialize, policy: policy,
|
28
|
+
path_key: path_key, map_depth: map_depth)
|
29
|
+
code << "\n"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
while map_depth.positive?
|
35
|
+
code << " " * map_depth + "#{nodev}\n"
|
36
|
+
code << " " * (map_depth - 1) + "end\n"
|
37
|
+
nodev = "node#{depth - 1}"
|
38
|
+
depth -= 1
|
39
|
+
map_depth -= 1
|
40
|
+
end
|
41
|
+
code << " #{nodev}\nend\n"
|
42
|
+
code
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|