kumi 0.0.15 → 0.0.17
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 +13 -0
- data/golden/cascade_logic/schema.kumi +3 -1
- data/lib/kumi/analyzer.rb +11 -9
- data/lib/kumi/core/analyzer/passes/broadcast_detector.rb +0 -81
- 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/lower_to_ir_pass.rb +0 -36
- data/lib/kumi/core/analyzer/passes/toposorter.rb +1 -39
- data/lib/kumi/core/analyzer/passes/unsat_detector.rb +8 -191
- data/lib/kumi/core/compiler/access_builder.rb +20 -10
- 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 -25
- data/BACKLOG.md +0 -34
- data/config/functions.yaml +0 -352
- data/docs/functions/analyzer_integration.md +0 -199
- data/docs/functions/signatures.md +0 -171
- data/examples/hash_objects_demo.rb +0 -138
- data/lib/kumi/core/analyzer/passes/function_signature_pass.rb +0 -199
- data/lib/kumi/core/analyzer/passes/type_consistency_checker.rb +0 -48
- data/lib/kumi/core/functions/dimension.rb +0 -98
- data/lib/kumi/core/functions/dtypes.rb +0 -20
- data/lib/kumi/core/functions/errors.rb +0 -11
- data/lib/kumi/core/functions/kernel_adapter.rb +0 -45
- data/lib/kumi/core/functions/loader.rb +0 -119
- data/lib/kumi/core/functions/registry_v2.rb +0 -68
- data/lib/kumi/core/functions/shape.rb +0 -70
- data/lib/kumi/core/functions/signature.rb +0 -122
- data/lib/kumi/core/functions/signature_parser.rb +0 -86
- data/lib/kumi/core/functions/signature_resolver.rb +0 -272
- data/lib/kumi/kernels/ruby/aggregate_core.rb +0 -105
- data/lib/kumi/kernels/ruby/datetime_scalar.rb +0 -21
- data/lib/kumi/kernels/ruby/mask_scalar.rb +0 -15
- data/lib/kumi/kernels/ruby/scalar_core.rb +0 -63
- data/lib/kumi/kernels/ruby/string_scalar.rb +0 -19
- data/lib/kumi/kernels/ruby/vector_struct.rb +0 -39
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3ffbd312f8f3706fb9accbec91a07cc41af173f0d0fcf2a308c2730e730d08ff
|
4
|
+
data.tar.gz: 75d5d08b1bb6c9d2aaa4bd175cc3be59a7074e631e2093bf33f5d499a4d7ff43
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3634f946464d08da52743531023a4edca7a6780f010689109abb478679b4f89e8c249181cfc9e5d484c9f5dfeafa8727d582d95d6bc9798ff0df90fe2c2ae2ca
|
7
|
+
data.tar.gz: dcada1e031b7bc1114297ea9704251bcc4ab009c42a1f7d282fc926f7f59c963812a5850f4b274846d8c267be35b61bbae29d4604e031d74e50da9887159750a
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,18 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.0.17] – 2025-09-03
|
4
|
+
|
5
|
+
### Removed
|
6
|
+
- Reverted experimental function registry v2 implementation
|
7
|
+
- Cleaned up unused analyzer passes and simplified unsat detector logic
|
8
|
+
|
9
|
+
## [0.0.16] – 2025-08-22
|
10
|
+
|
11
|
+
### Performance
|
12
|
+
- Input accessor code generation replaces nested lambda chains with compiled Ruby methods
|
13
|
+
- Fix cache handling in Runtime - it was being recreated on updates
|
14
|
+
- Add early shortcut for Analyzer Passes.
|
15
|
+
|
3
16
|
## [0.0.15] – 2025-08-21
|
4
17
|
### Added
|
5
18
|
- (DX) Schema-aware VM profiling with multi-schema performance analysis
|
@@ -6,9 +6,11 @@ schema do
|
|
6
6
|
|
7
7
|
trait :x_positive, input.x > 0
|
8
8
|
trait :y_positive, input.y > 0
|
9
|
+
|
10
|
+
trait :both_positive, y_positive & x_positive
|
9
11
|
|
10
12
|
value :status do
|
11
|
-
on
|
13
|
+
on both_positive, "both positive"
|
12
14
|
on x_positive, "x positive"
|
13
15
|
on y_positive, "y positive"
|
14
16
|
base "neither positive"
|
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.
|
@@ -14,15 +15,14 @@ module Kumi
|
|
14
15
|
Core::Analyzer::Passes::Toposorter, # 8. Creates the final evaluation order, allowing safe cycles.
|
15
16
|
Core::Analyzer::Passes::BroadcastDetector, # 9. Detects which operations should be broadcast over arrays.
|
16
17
|
Core::Analyzer::Passes::TypeInferencerPass, # 10. Infers types for all declarations (uses vectorization metadata).
|
17
|
-
Core::Analyzer::Passes::
|
18
|
-
Core::Analyzer::Passes::
|
19
|
-
Core::Analyzer::Passes::
|
20
|
-
Core::Analyzer::Passes::
|
21
|
-
Core::Analyzer::Passes::
|
22
|
-
Core::Analyzer::Passes::
|
23
|
-
Core::Analyzer::Passes::
|
24
|
-
Core::Analyzer::Passes::
|
25
|
-
Core::Analyzer::Passes::IRDependencyPass # 19. Extracts IR-level dependencies for VM execution optimization
|
18
|
+
Core::Analyzer::Passes::TypeChecker, # 11. Validates types using inferred information.
|
19
|
+
Core::Analyzer::Passes::InputAccessPlannerPass, # 12. Plans access strategies for input fields.
|
20
|
+
Core::Analyzer::Passes::ScopeResolutionPass, # 13. Plans execution scope and lifting needs for declarations.
|
21
|
+
Core::Analyzer::Passes::JoinReducePlanningPass, # 14. Plans join/reduce operations (Generates IR Structs)
|
22
|
+
Core::Analyzer::Passes::LowerToIRPass, # 15. Lowers the schema to IR (Generates IR Structs)
|
23
|
+
Core::Analyzer::Passes::LoadInputCSE, # 16. Eliminates redundant load_input operations
|
24
|
+
Core::Analyzer::Passes::IRDependencyPass, # 17. Extracts IR-level dependencies for VM execution optimization
|
25
|
+
Core::Analyzer::Passes::IRExecutionSchedulePass # 18. Builds a precomputed execution schedule.
|
26
26
|
].freeze
|
27
27
|
|
28
28
|
def self.analyze!(schema, passes: DEFAULT_PASSES, **opts)
|
@@ -44,6 +44,8 @@ module Kumi
|
|
44
44
|
skipping = !!resume_at
|
45
45
|
|
46
46
|
passes.each_with_index do |pass_class, idx|
|
47
|
+
raise handle_analysis_errors(errors) if (ERROR_THRESHOLD_PASS == pass_class) && !errors.empty?
|
48
|
+
|
47
49
|
pass_name = pass_class.name.split("::").last
|
48
50
|
|
49
51
|
if skipping
|
@@ -656,87 +656,6 @@ module Kumi
|
|
656
656
|
end
|
657
657
|
end
|
658
658
|
|
659
|
-
def extract_dimensional_info_with_context(info, _array_fields, _nested_paths, vectorized_values)
|
660
|
-
case info[:source]
|
661
|
-
when :array_field_access, :nested_array_access
|
662
|
-
# Direct array field access - use the path
|
663
|
-
source = info[:path]&.first
|
664
|
-
dimension = info[:path]
|
665
|
-
[source, dimension]
|
666
|
-
when :vectorized_declaration
|
667
|
-
# Reference to another vectorized declaration - look it up
|
668
|
-
if info[:name] && vectorized_values[info[:name]]
|
669
|
-
vectorized_info = vectorized_values[info[:name]]
|
670
|
-
if vectorized_info[:array_source]
|
671
|
-
# This declaration references an array field, use that source
|
672
|
-
[vectorized_info[:array_source], [vectorized_info[:array_source]]]
|
673
|
-
else
|
674
|
-
# This is a derived vectorized value, try to trace its source
|
675
|
-
[:vectorized_reference, [:vectorized_reference]]
|
676
|
-
end
|
677
|
-
else
|
678
|
-
[:unknown_declaration, [:unknown_declaration]]
|
679
|
-
end
|
680
|
-
else
|
681
|
-
# Operations and other cases - try to extract from operation args
|
682
|
-
if info[:operation] && info[:vectorized_args]
|
683
|
-
# This is an operation result - trace the vectorized arguments
|
684
|
-
# For now, assume operations inherit the dimension of their first vectorized arg
|
685
|
-
[:operation_result, [:operation_result]]
|
686
|
-
else
|
687
|
-
[:unknown, [:unknown]]
|
688
|
-
end
|
689
|
-
end
|
690
|
-
end
|
691
|
-
|
692
|
-
def extract_dimensional_source(info, _array_fields)
|
693
|
-
case info[:source]
|
694
|
-
when :array_field_access
|
695
|
-
info[:path]&.first
|
696
|
-
when :nested_array_access
|
697
|
-
info[:path]&.first
|
698
|
-
when :vectorized_declaration, :vectorized_value
|
699
|
-
# Try to extract from the vectorized value info if available
|
700
|
-
if info[:name] && info.dig(:info, :path)
|
701
|
-
info[:info][:path].first
|
702
|
-
else
|
703
|
-
:vectorized_reference
|
704
|
-
end
|
705
|
-
else
|
706
|
-
# For operations and other cases, try to infer from vectorized args
|
707
|
-
if info[:vectorized_args]
|
708
|
-
# This is likely an operation - we should look at its arguments
|
709
|
-
:operation_result
|
710
|
-
else
|
711
|
-
:unknown
|
712
|
-
end
|
713
|
-
end
|
714
|
-
end
|
715
|
-
|
716
|
-
def extract_dimensions(info, _array_fields, _nested_paths)
|
717
|
-
case info[:source]
|
718
|
-
when :array_field_access
|
719
|
-
info[:path]
|
720
|
-
when :nested_array_access
|
721
|
-
info[:path]
|
722
|
-
when :vectorized_declaration, :vectorized_value
|
723
|
-
# Try to extract from the vectorized value info if available
|
724
|
-
if info[:name] && info.dig(:info, :path)
|
725
|
-
info[:info][:path]
|
726
|
-
else
|
727
|
-
[:vectorized_reference]
|
728
|
-
end
|
729
|
-
else
|
730
|
-
# For operations, try to infer from the operation context
|
731
|
-
if info[:vectorized_args]
|
732
|
-
# This is likely an operation - we should trace its arguments
|
733
|
-
[:operation_result]
|
734
|
-
else
|
735
|
-
[:unknown]
|
736
|
-
end
|
737
|
-
end
|
738
|
-
end
|
739
|
-
|
740
659
|
def extract_nested_paths_from_dimensions(dimension, nested_paths)
|
741
660
|
return nil unless dimension.is_a?(Array)
|
742
661
|
|
@@ -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
|
@@ -426,9 +426,6 @@ module Kumi
|
|
426
426
|
when Syntax::CallExpression
|
427
427
|
entry = Kumi::Registry.entry(expr.fn_name)
|
428
428
|
|
429
|
-
# Validate signature metadata from FunctionSignaturePass (read-only assertions)
|
430
|
-
validate_signature_metadata(expr, entry)
|
431
|
-
|
432
429
|
# Constant folding optimization: evaluate expressions with all literal arguments
|
433
430
|
if can_constant_fold?(expr, entry)
|
434
431
|
folded_value = constant_fold(expr, entry)
|
@@ -888,39 +885,6 @@ module Kumi
|
|
888
885
|
expr.args.all? { |arg| arg.is_a?(Syntax::Literal) }
|
889
886
|
end
|
890
887
|
|
891
|
-
def validate_signature_metadata(expr, entry)
|
892
|
-
# Get the node index to access signature metadata
|
893
|
-
node_index = get_state(:node_index, required: false)
|
894
|
-
return unless node_index
|
895
|
-
|
896
|
-
node_entry = node_index[expr.object_id]
|
897
|
-
return unless node_entry
|
898
|
-
|
899
|
-
metadata = node_entry[:metadata]
|
900
|
-
return unless metadata
|
901
|
-
|
902
|
-
# Validate that dropped axes make sense for reduction functions
|
903
|
-
if entry&.reducer && metadata[:dropped_axes]
|
904
|
-
dropped_axes = metadata[:dropped_axes]
|
905
|
-
unless dropped_axes.is_a?(Array)
|
906
|
-
raise "Invalid dropped_axes metadata for reducer #{expr.fn_name}: expected Array, got #{dropped_axes.class}"
|
907
|
-
end
|
908
|
-
|
909
|
-
# For reductions, we should have at least one dropped axis (or empty for scalar reductions)
|
910
|
-
puts " SIGNATURE[#{expr.fn_name}] dropped_axes: #{dropped_axes.inspect}" if ENV["DEBUG_LOWER"]
|
911
|
-
end
|
912
|
-
|
913
|
-
# Validate join_policy is recognized
|
914
|
-
if metadata[:join_policy] && !%i[zip product].include?(metadata[:join_policy])
|
915
|
-
raise "Invalid join_policy for #{expr.fn_name}: #{metadata[:join_policy].inspect}"
|
916
|
-
end
|
917
|
-
|
918
|
-
# Warn about join_policy when no join op exists yet (future integration point)
|
919
|
-
return unless metadata[:join_policy] && ENV["DEBUG_LOWER"]
|
920
|
-
|
921
|
-
puts " SIGNATURE[#{expr.fn_name}] join_policy: #{metadata[:join_policy]} (join op not yet implemented)"
|
922
|
-
end
|
923
|
-
|
924
888
|
def constant_fold(expr, entry)
|
925
889
|
literal_values = expr.args.map(&:value)
|
926
890
|
|
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "pry"
|
4
3
|
module Kumi
|
5
4
|
module Core
|
6
5
|
module Analyzer
|
@@ -8,54 +7,18 @@ module Kumi
|
|
8
7
|
# RESPONSIBILITY: Compute topological ordering of declarations, blocking all cycles
|
9
8
|
# DEPENDENCIES: :dependencies from DependencyResolver, :declarations from NameIndexer
|
10
9
|
# PRODUCES: :evaluation_order - Array of declaration names in evaluation order
|
11
|
-
# :node_index - Hash mapping object_id to node metadata for later passes
|
12
10
|
# INTERFACE: new(schema, state).run(errors)
|
13
11
|
class Toposorter < PassBase
|
14
12
|
def run(errors)
|
15
13
|
dependency_graph = get_state(:dependencies, required: false) || {}
|
16
14
|
definitions = get_state(:declarations, required: false) || {}
|
17
15
|
|
18
|
-
# Create node index for later passes to use
|
19
|
-
node_index = build_node_index(definitions)
|
20
16
|
order = compute_topological_order(dependency_graph, definitions, errors)
|
21
|
-
|
22
|
-
state.with(:evaluation_order, order).with(:node_index, node_index)
|
17
|
+
state.with(:evaluation_order, order)
|
23
18
|
end
|
24
19
|
|
25
20
|
private
|
26
21
|
|
27
|
-
def build_node_index(definitions)
|
28
|
-
index = {}
|
29
|
-
|
30
|
-
# Walk all declarations and their expressions to index every node
|
31
|
-
definitions.each_value do |decl|
|
32
|
-
index_node_recursive(decl, index)
|
33
|
-
end
|
34
|
-
|
35
|
-
index
|
36
|
-
end
|
37
|
-
|
38
|
-
def index_node_recursive(node, index)
|
39
|
-
return unless node
|
40
|
-
|
41
|
-
# Index this node by its object_id
|
42
|
-
index[node.object_id] = {
|
43
|
-
node: node,
|
44
|
-
type: node.class.name.split('::').last,
|
45
|
-
metadata: {}
|
46
|
-
}
|
47
|
-
|
48
|
-
# Use the same approach as the visitor pattern - recursively index all children
|
49
|
-
if node.respond_to?(:children)
|
50
|
-
node.children.each { |child| index_node_recursive(child, index) }
|
51
|
-
end
|
52
|
-
|
53
|
-
# Index expression for declaration nodes
|
54
|
-
if node.respond_to?(:expression)
|
55
|
-
index_node_recursive(node.expression, index)
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
22
|
def compute_topological_order(graph, definitions, errors)
|
60
23
|
temp_marks = Set.new
|
61
24
|
perm_marks = Set.new
|
@@ -96,7 +59,6 @@ module Kumi
|
|
96
59
|
order.freeze
|
97
60
|
end
|
98
61
|
|
99
|
-
|
100
62
|
def report_unexpected_cycle(temp_marks, current_node, errors)
|
101
63
|
cycle_path = temp_marks.to_a.join(" → ") + " → #{current_node}"
|
102
64
|
|