kumi 0.0.14 → 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 +40 -0
- data/README.md +0 -27
- data/docs/dev/vm-profiling.md +95 -0
- data/docs/features/README.md +0 -7
- data/lib/kumi/analyzer.rb +10 -2
- data/lib/kumi/compiler.rb +6 -5
- data/lib/kumi/core/analyzer/passes/ir_dependency_pass.rb +65 -0
- data/lib/kumi/core/analyzer/passes/ir_execution_schedule_pass.rb +67 -0
- data/lib/kumi/core/analyzer/passes/toposorter.rb +15 -50
- 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 +56 -189
- data/lib/kumi/core/ir/execution_engine/profiler.rb +139 -11
- data/lib/kumi/core/ir/execution_engine/values.rb +8 -8
- data/lib/kumi/core/ir/execution_engine.rb +5 -30
- data/lib/kumi/dev/parse.rb +12 -12
- data/lib/kumi/dev/profile_aggregator.rb +301 -0
- data/lib/kumi/dev/profile_runner.rb +199 -0
- data/lib/kumi/dev/runner.rb +3 -1
- data/lib/kumi/dev.rb +14 -0
- data/lib/kumi/runtime/executable.rb +32 -153
- data/lib/kumi/runtime/run.rb +105 -0
- data/lib/kumi/schema.rb +15 -14
- data/lib/kumi/version.rb +1 -1
- data/lib/kumi.rb +4 -2
- metadata +15 -3
- data/docs/features/analysis-cascade-mutual-exclusion.md +0 -89
@@ -37,40 +37,45 @@ module Kumi
|
|
37
37
|
# - DEBUG_VM_ARGS=1 to trace VM execution
|
38
38
|
# - Accessors can be debugged independently with DEBUG_ACCESSOR_OPS=1
|
39
39
|
class Executable
|
40
|
-
def self.from_analysis(state, registry: nil)
|
40
|
+
def self.from_analysis(state, registry: nil, schema_name: nil)
|
41
41
|
ir = state.fetch(:ir_module)
|
42
42
|
access_plans = state.fetch(:access_plans)
|
43
43
|
input_metadata = state[:input_metadata] || {}
|
44
44
|
dependents = state[:dependents] || {}
|
45
|
-
|
45
|
+
schedules = state[:ir_execution_schedules] || {}
|
46
|
+
|
47
|
+
accessors = Dev::Profiler.phase("compiler.access_builder") do
|
48
|
+
Kumi::Core::Compiler::AccessBuilder.build(access_plans)
|
49
|
+
end
|
46
50
|
|
47
51
|
access_meta = {}
|
48
|
-
field_to_plan_ids = Hash.new { |h, k| h[k] = [] }
|
49
52
|
|
50
|
-
access_plans.each_value do |plans|
|
51
|
-
|
52
|
-
|
53
|
+
# access_plans.each_value do |plans|
|
54
|
+
# plans.each do |p|
|
55
|
+
# access_meta[p.accessor_key] = { mode: p.mode, scope: p.scope }
|
53
56
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
end
|
57
|
+
# # Build precise field -> plan_ids mapping for invalidation
|
58
|
+
# root_field = p.accessor_key.to_s.split(":").first.split(".").first.to_sym
|
59
|
+
# field_to_plan_ids[root_field] << p.accessor_key
|
60
|
+
# end
|
61
|
+
# end
|
59
62
|
|
60
63
|
# Use the internal functions hash that VM expects
|
61
64
|
registry ||= Kumi::Registry.functions
|
62
65
|
new(ir: ir, accessors: accessors, access_meta: access_meta, registry: registry,
|
63
|
-
input_metadata: input_metadata,
|
66
|
+
input_metadata: input_metadata, dependents: dependents,
|
67
|
+
schema_name: schema_name, schedules: schedules)
|
64
68
|
end
|
65
69
|
|
66
|
-
def initialize(ir:, accessors:, access_meta:, registry:, input_metadata:,
|
70
|
+
def initialize(ir:, accessors:, access_meta:, registry:, input_metadata:, dependents: {}, schedules: {}, schema_name: nil)
|
67
71
|
@ir = ir.freeze
|
68
72
|
@acc = accessors.freeze
|
69
73
|
@meta = access_meta.freeze
|
70
74
|
@reg = registry
|
71
75
|
@input_metadata = input_metadata.freeze
|
72
|
-
@field_to_plan_ids = field_to_plan_ids.freeze
|
73
76
|
@dependents = dependents.freeze
|
77
|
+
@schema_name = schema_name
|
78
|
+
@schedules = schedules
|
74
79
|
@decl = @ir.decls.map { |d| [d.name, d] }.to_h
|
75
80
|
@accessor_cache = {} # Persistent accessor cache across evaluations
|
76
81
|
end
|
@@ -78,7 +83,7 @@ module Kumi
|
|
78
83
|
def decl?(name) = @decl.key?(name)
|
79
84
|
|
80
85
|
def read(input, mode: :ruby)
|
81
|
-
Run.new(self, input, mode: mode, input_metadata: @input_metadata, dependents: @dependents)
|
86
|
+
Run.new(self, input, mode: mode, input_metadata: @input_metadata, dependents: @dependents, declarations: @decl.keys)
|
82
87
|
end
|
83
88
|
|
84
89
|
# API compatibility for backward compatibility
|
@@ -93,26 +98,24 @@ module Kumi
|
|
93
98
|
end
|
94
99
|
end
|
95
100
|
|
96
|
-
def eval_decl(name, input, mode: :ruby, declaration_cache:
|
101
|
+
def eval_decl(name, input, mode: :ruby, declaration_cache: {})
|
97
102
|
raise Kumi::Core::Errors::RuntimeError, "unknown decl #{name}" unless decl?(name)
|
98
103
|
|
99
|
-
|
100
|
-
|
101
|
-
|
104
|
+
schedule = @schedules[name]
|
105
|
+
# If the caller asked for a specific binding, schedule deps once
|
106
|
+
|
107
|
+
runtime = {
|
102
108
|
accessor_cache: @accessor_cache,
|
103
|
-
declaration_cache: declaration_cache
|
109
|
+
declaration_cache: declaration_cache, # run-local cache
|
110
|
+
schema_name: @schema_name,
|
111
|
+
target: name
|
104
112
|
}
|
105
|
-
|
106
|
-
out = Kumi::Core::IR::ExecutionEngine.run(@ir, vm_context, accessors: @acc, registry: @reg).fetch(name)
|
107
113
|
|
108
|
-
|
109
|
-
|
114
|
+
out = Dev::Profiler.phase("vm.run", target: name) do
|
115
|
+
Kumi::Core::IR::ExecutionEngine.run(schedule, input: input, runtime: runtime, accessors: @acc, registry: @reg).fetch(name)
|
116
|
+
end
|
110
117
|
|
111
|
-
|
112
|
-
# Use precise field -> plan_ids mapping for exact invalidation
|
113
|
-
plan_ids = @field_to_plan_ids[field_name] || []
|
114
|
-
# Cache keys are [plan_id, input_object_id] arrays
|
115
|
-
@accessor_cache.delete_if { |(pid, _), _| plan_ids.include?(pid) }
|
118
|
+
mode == :ruby ? unwrap(@decl[name], out) : out
|
116
119
|
end
|
117
120
|
|
118
121
|
def unwrap(_decl, v)
|
@@ -128,129 +131,5 @@ module Kumi
|
|
128
131
|
raise Kumi::Errors::RuntimeError, "No binding named #{unknown_keys.first}"
|
129
132
|
end
|
130
133
|
end
|
131
|
-
|
132
|
-
class Run
|
133
|
-
def initialize(program, input, mode:, input_metadata:, dependents:)
|
134
|
-
@program = program
|
135
|
-
@input = input
|
136
|
-
@mode = mode
|
137
|
-
@input_metadata = input_metadata
|
138
|
-
@dependents = dependents
|
139
|
-
@cache = {}
|
140
|
-
end
|
141
|
-
|
142
|
-
def get(name)
|
143
|
-
unless @cache.key?(name)
|
144
|
-
# Get the result in VM internal format
|
145
|
-
vm_result = @program.eval_decl(name, @input, mode: :wrapped, declaration_cache: @cache)
|
146
|
-
# Store VM format for cross-VM caching
|
147
|
-
@cache[name] = vm_result
|
148
|
-
end
|
149
|
-
|
150
|
-
# Convert to requested format when returning
|
151
|
-
vm_result = @cache[name]
|
152
|
-
@mode == :wrapped ? vm_result : @program.unwrap(nil, vm_result)
|
153
|
-
end
|
154
|
-
|
155
|
-
def [](name)
|
156
|
-
get(name)
|
157
|
-
end
|
158
|
-
|
159
|
-
def slice(*keys)
|
160
|
-
return {} if keys.empty?
|
161
|
-
|
162
|
-
keys.each_with_object({}) { |key, result| result[key] = get(key) }
|
163
|
-
end
|
164
|
-
|
165
|
-
def compiled_schema
|
166
|
-
@program
|
167
|
-
end
|
168
|
-
|
169
|
-
def method_missing(sym, *args, **kwargs, &)
|
170
|
-
return super unless args.empty? && kwargs.empty? && @program.decl?(sym)
|
171
|
-
|
172
|
-
get(sym)
|
173
|
-
end
|
174
|
-
|
175
|
-
def respond_to_missing?(sym, priv = false)
|
176
|
-
@program.decl?(sym) || super
|
177
|
-
end
|
178
|
-
|
179
|
-
def update(**changes)
|
180
|
-
affected_declarations = Set.new
|
181
|
-
|
182
|
-
changes.each do |field, value|
|
183
|
-
# Validate field exists
|
184
|
-
raise ArgumentError, "unknown input field: #{field}" unless input_field_exists?(field)
|
185
|
-
|
186
|
-
# Validate domain constraints
|
187
|
-
validate_domain_constraint(field, value)
|
188
|
-
|
189
|
-
# Update the input data IN-PLACE to preserve object_id for cache keys
|
190
|
-
@input[field] = value
|
191
|
-
|
192
|
-
# Clear accessor cache for this specific field
|
193
|
-
@program.clear_field_accessor_cache(field)
|
194
|
-
|
195
|
-
# Collect all declarations that depend on this input field
|
196
|
-
field_dependents = @dependents[field] || []
|
197
|
-
affected_declarations.merge(field_dependents)
|
198
|
-
end
|
199
|
-
|
200
|
-
# Only clear cache for affected declarations, not all declarations
|
201
|
-
affected_declarations.each { |decl| @cache.delete(decl) }
|
202
|
-
|
203
|
-
self
|
204
|
-
end
|
205
|
-
|
206
|
-
def wrapped!
|
207
|
-
@mode = :wrapped
|
208
|
-
@cache.clear
|
209
|
-
self
|
210
|
-
end
|
211
|
-
|
212
|
-
def ruby!
|
213
|
-
@mode = :ruby
|
214
|
-
@cache.clear
|
215
|
-
self
|
216
|
-
end
|
217
|
-
|
218
|
-
private
|
219
|
-
|
220
|
-
def input_field_exists?(field)
|
221
|
-
# Check if field is declared in input block
|
222
|
-
@input_metadata.key?(field) || @input.key?(field)
|
223
|
-
end
|
224
|
-
|
225
|
-
def validate_domain_constraint(field, value)
|
226
|
-
field_meta = @input_metadata[field]
|
227
|
-
return unless field_meta&.dig(:domain)
|
228
|
-
|
229
|
-
domain = field_meta[:domain]
|
230
|
-
return unless violates_domain?(value, domain)
|
231
|
-
|
232
|
-
raise ArgumentError, "value #{value} is not in domain #{domain}"
|
233
|
-
end
|
234
|
-
|
235
|
-
def violates_domain?(value, domain)
|
236
|
-
case domain
|
237
|
-
when Range
|
238
|
-
!domain.include?(value)
|
239
|
-
when Array
|
240
|
-
!domain.include?(value)
|
241
|
-
when Proc
|
242
|
-
# For Proc domains, we can't statically analyze
|
243
|
-
false
|
244
|
-
else
|
245
|
-
false
|
246
|
-
end
|
247
|
-
end
|
248
|
-
|
249
|
-
def deep_merge(a, b)
|
250
|
-
return b unless a.is_a?(Hash) && b.is_a?(Hash)
|
251
|
-
|
252
|
-
a.merge(b) { |_k, v1, v2| deep_merge(v1, v2) }
|
253
|
-
end
|
254
|
-
end
|
255
134
|
end
|
256
135
|
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kumi
|
4
|
+
module Runtime
|
5
|
+
class Run
|
6
|
+
def initialize(program, input, mode:, input_metadata:, dependents:, declarations:)
|
7
|
+
@program = program
|
8
|
+
@input = input
|
9
|
+
@mode = mode
|
10
|
+
@input_metadata = input_metadata
|
11
|
+
@declarations = declarations
|
12
|
+
@dependents = dependents
|
13
|
+
@cache = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def key?(name)
|
17
|
+
@declarations.include? name
|
18
|
+
end
|
19
|
+
|
20
|
+
def get(name)
|
21
|
+
unless @cache.key?(name)
|
22
|
+
# Get the result in VM internal format
|
23
|
+
vm_result = @program.eval_decl(name, @input, mode: :wrapped, declaration_cache: @cache)
|
24
|
+
# Store VM format for cross-VM caching
|
25
|
+
@cache[name] = vm_result
|
26
|
+
end
|
27
|
+
|
28
|
+
# Convert to requested format when returning
|
29
|
+
vm_result = @cache[name]
|
30
|
+
@mode == :wrapped ? vm_result : @program.unwrap(nil, vm_result)
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_h
|
34
|
+
slice(*@declarations)
|
35
|
+
end
|
36
|
+
|
37
|
+
def [](name)
|
38
|
+
get(name)
|
39
|
+
end
|
40
|
+
|
41
|
+
def slice(*keys)
|
42
|
+
return {} if keys.empty?
|
43
|
+
|
44
|
+
keys.each_with_object({}) { |key, result| result[key] = get(key) }
|
45
|
+
end
|
46
|
+
|
47
|
+
def compiled_schema
|
48
|
+
@program
|
49
|
+
end
|
50
|
+
|
51
|
+
def method_missing(sym, *args, **kwargs, &)
|
52
|
+
return super unless args.empty? && kwargs.empty? && key?(sym)
|
53
|
+
|
54
|
+
get(sym)
|
55
|
+
end
|
56
|
+
|
57
|
+
def respond_to_missing?(sym, priv = false)
|
58
|
+
key?(sym) || super
|
59
|
+
end
|
60
|
+
|
61
|
+
def update(**changes)
|
62
|
+
affected_declarations = Set.new
|
63
|
+
|
64
|
+
changes.each do |field, value|
|
65
|
+
raise ArgumentError, "unknown input field: #{field}" unless input_field_exists?(field)
|
66
|
+
|
67
|
+
validate_domain_constraint(field, value)
|
68
|
+
|
69
|
+
@input[field] = value
|
70
|
+
if (deps = @dependents[field])
|
71
|
+
deps.each { |d| @cache.delete(d) }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
self
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def input_field_exists?(field)
|
81
|
+
# Check if field is declared in input block
|
82
|
+
@input_metadata.key?(field)
|
83
|
+
end
|
84
|
+
|
85
|
+
def validate_domain_constraint(field, value)
|
86
|
+
field_meta = @input_metadata[field]
|
87
|
+
return unless field_meta&.dig(:domain)
|
88
|
+
|
89
|
+
domain = field_meta[:domain]
|
90
|
+
return unless violates_domain?(value, domain)
|
91
|
+
|
92
|
+
raise ArgumentError, "value #{value} is not in domain #{domain}"
|
93
|
+
end
|
94
|
+
|
95
|
+
def violates_domain?(value, domain)
|
96
|
+
case domain
|
97
|
+
when Range, Array
|
98
|
+
!domain.include?(value)
|
99
|
+
else
|
100
|
+
false
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
data/lib/kumi/schema.rb
CHANGED
@@ -4,18 +4,12 @@ require "ostruct"
|
|
4
4
|
|
5
5
|
module Kumi
|
6
6
|
module Schema
|
7
|
-
attr_reader :__syntax_tree__, :__analyzer_result__, :
|
8
|
-
|
9
|
-
Inspector = Struct.new(:syntax_tree, :analyzer_result, :compiled_schema) do
|
10
|
-
def inspect
|
11
|
-
"#<#{self.class} syntax_tree: #{syntax_tree.inspect}, analyzer_result: #{analyzer_result.inspect}, compiled_schema: #{compiled_schema.inspect}>"
|
12
|
-
end
|
13
|
-
end
|
7
|
+
attr_reader :__syntax_tree__, :__analyzer_result__, :__executable__
|
14
8
|
|
15
9
|
def from(context)
|
16
10
|
# VERY IMPORTANT: This method is overriden on specs in order to use dual mode.
|
17
11
|
|
18
|
-
raise("No schema defined") unless @
|
12
|
+
raise("No schema defined") unless @__executable__
|
19
13
|
|
20
14
|
# Validate input types and domain constraints
|
21
15
|
input_meta = @__analyzer_result__.state[:input_metadata] || {}
|
@@ -23,11 +17,12 @@ module Kumi
|
|
23
17
|
|
24
18
|
raise Errors::InputValidationError, violations unless violations.empty?
|
25
19
|
|
26
|
-
|
20
|
+
# TODO: Lazily start a Runner
|
21
|
+
@__executable__.read(context, mode: :ruby)
|
27
22
|
end
|
28
23
|
|
29
24
|
def explain(context, *keys)
|
30
|
-
raise("No schema defined") unless @
|
25
|
+
raise("No schema defined") unless @__executable__
|
31
26
|
|
32
27
|
# Validate input types and domain constraints
|
33
28
|
input_meta = @__analyzer_result__.state[:input_metadata] || {}
|
@@ -49,14 +44,20 @@ module Kumi
|
|
49
44
|
def schema(&)
|
50
45
|
# from_location = caller_locations(1, 1).first
|
51
46
|
# raise "Called from #{from_location.path}:#{from_location.lineno}"
|
52
|
-
@__syntax_tree__ =
|
47
|
+
@__syntax_tree__ = Dev::Profiler.phase("frontend.parse") do
|
48
|
+
Core::RubyParser::Dsl.build_syntax_tree(&).freeze
|
49
|
+
end
|
53
50
|
|
54
51
|
puts Support::SExpressionPrinter.print(@__syntax_tree__, indent: 2) if ENV["KUMI_DEBUG"] || ENV["KUMI_PRINT_SYNTAX_TREE"]
|
55
52
|
|
56
|
-
@__analyzer_result__ =
|
57
|
-
|
53
|
+
@__analyzer_result__ = Dev::Profiler.phase("analyzer") do
|
54
|
+
Analyzer.analyze!(@__syntax_tree__).freeze
|
55
|
+
end
|
56
|
+
@__executable__ = Dev::Profiler.phase("compiler") do
|
57
|
+
Compiler.compile(@__syntax_tree__, analyzer: @__analyzer_result__, schema_name: name).freeze
|
58
|
+
end
|
58
59
|
|
59
|
-
|
60
|
+
nil
|
60
61
|
end
|
61
62
|
|
62
63
|
def schema_metadata
|
data/lib/kumi/version.rb
CHANGED
data/lib/kumi.rb
CHANGED
@@ -8,10 +8,12 @@ loader.ignore("#{__dir__}/kumi-cli")
|
|
8
8
|
loader.inflector.inflect(
|
9
9
|
"lower_to_ir_pass" => "LowerToIRPass",
|
10
10
|
"load_input_cse" => "LoadInputCSE",
|
11
|
+
"ir_dependency_pass" => "IRDependencyPass",
|
11
12
|
"vm" => "VM",
|
12
13
|
"ir" => "IR",
|
13
|
-
|
14
|
-
|
14
|
+
"ir_dump" => "IRDump",
|
15
|
+
"ir_render" => "IRRender",
|
16
|
+
"ir_execution_schedule_pass" => "IRExecutionSchedulePass"
|
15
17
|
)
|
16
18
|
loader.setup
|
17
19
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kumi
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.16
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- André Muta
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-08-
|
11
|
+
date: 2025-08-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: zeitwerk
|
@@ -48,10 +48,10 @@ files:
|
|
48
48
|
- docs/compiler_design_principles.md
|
49
49
|
- docs/dev/analyzer-debug.md
|
50
50
|
- docs/dev/parse-command.md
|
51
|
+
- docs/dev/vm-profiling.md
|
51
52
|
- docs/development/README.md
|
52
53
|
- docs/development/error-reporting.md
|
53
54
|
- docs/features/README.md
|
54
|
-
- docs/features/analysis-cascade-mutual-exclusion.md
|
55
55
|
- docs/features/analysis-type-inference.md
|
56
56
|
- docs/features/analysis-unsat-detection.md
|
57
57
|
- docs/features/hierarchical-broadcasting.md
|
@@ -93,6 +93,8 @@ files:
|
|
93
93
|
- lib/kumi/core/analyzer/passes/function_signature_pass.rb
|
94
94
|
- lib/kumi/core/analyzer/passes/input_access_planner_pass.rb
|
95
95
|
- lib/kumi/core/analyzer/passes/input_collector.rb
|
96
|
+
- lib/kumi/core/analyzer/passes/ir_dependency_pass.rb
|
97
|
+
- lib/kumi/core/analyzer/passes/ir_execution_schedule_pass.rb
|
96
98
|
- lib/kumi/core/analyzer/passes/join_reduce_planning_pass.rb
|
97
99
|
- lib/kumi/core/analyzer/passes/load_input_cse.rb
|
98
100
|
- lib/kumi/core/analyzer/passes/lower_to_ir_pass.rb
|
@@ -112,6 +114,12 @@ files:
|
|
112
114
|
- lib/kumi/core/analyzer/structs/input_meta.rb
|
113
115
|
- lib/kumi/core/atom_unsat_solver.rb
|
114
116
|
- lib/kumi/core/compiler/access_builder.rb
|
117
|
+
- lib/kumi/core/compiler/access_codegen.rb
|
118
|
+
- lib/kumi/core/compiler/access_emit/base.rb
|
119
|
+
- lib/kumi/core/compiler/access_emit/each_indexed.rb
|
120
|
+
- lib/kumi/core/compiler/access_emit/materialize.rb
|
121
|
+
- lib/kumi/core/compiler/access_emit/ravel.rb
|
122
|
+
- lib/kumi/core/compiler/access_emit/read.rb
|
115
123
|
- lib/kumi/core/compiler/access_planner.rb
|
116
124
|
- lib/kumi/core/compiler/accessors/base.rb
|
117
125
|
- lib/kumi/core/compiler/accessors/each_indexed_accessor.rb
|
@@ -188,8 +196,11 @@ files:
|
|
188
196
|
- lib/kumi/core/types/inference.rb
|
189
197
|
- lib/kumi/core/types/normalizer.rb
|
190
198
|
- lib/kumi/core/types/validator.rb
|
199
|
+
- lib/kumi/dev.rb
|
191
200
|
- lib/kumi/dev/ir.rb
|
192
201
|
- lib/kumi/dev/parse.rb
|
202
|
+
- lib/kumi/dev/profile_aggregator.rb
|
203
|
+
- lib/kumi/dev/profile_runner.rb
|
193
204
|
- lib/kumi/dev/runner.rb
|
194
205
|
- lib/kumi/errors.rb
|
195
206
|
- lib/kumi/frontends.rb
|
@@ -203,6 +214,7 @@ files:
|
|
203
214
|
- lib/kumi/kernels/ruby/vector_struct.rb
|
204
215
|
- lib/kumi/registry.rb
|
205
216
|
- lib/kumi/runtime/executable.rb
|
217
|
+
- lib/kumi/runtime/run.rb
|
206
218
|
- lib/kumi/schema.rb
|
207
219
|
- lib/kumi/schema_metadata.rb
|
208
220
|
- lib/kumi/support/diff.rb
|
@@ -1,89 +0,0 @@
|
|
1
|
-
# Cascade Mutual Exclusion Detection
|
2
|
-
|
3
|
-
Analyzes cascade expressions to allow safe recursive patterns when conditions are mutually exclusive.
|
4
|
-
|
5
|
-
## Overview
|
6
|
-
|
7
|
-
The cascade mutual exclusion detector identifies when all conditions in a cascade expression cannot be true simultaneously, enabling safe mutual recursion patterns that would otherwise be rejected as cycles.
|
8
|
-
|
9
|
-
## Core Mechanism
|
10
|
-
|
11
|
-
The system performs three-stage analysis:
|
12
|
-
|
13
|
-
1. **Conditional Dependency Tracking** - DependencyResolver marks base case dependencies as conditional
|
14
|
-
2. **Mutual Exclusion Analysis** - UnsatDetector determines if cascade conditions are mutually exclusive
|
15
|
-
3. **Safe Cycle Detection** - Toposorter allows cycles where all edges are conditional and conditions are mutually exclusive
|
16
|
-
|
17
|
-
## Example: Processing Workflow
|
18
|
-
|
19
|
-
```ruby
|
20
|
-
schema do
|
21
|
-
input do
|
22
|
-
string :operation # "forward", "reverse", "unknown"
|
23
|
-
integer :value
|
24
|
-
end
|
25
|
-
|
26
|
-
trait :is_forward, input.operation == "forward"
|
27
|
-
trait :is_reverse, input.operation == "reverse"
|
28
|
-
|
29
|
-
# Safe mutual recursion - conditions are mutually exclusive
|
30
|
-
value :forward_processor do
|
31
|
-
on is_forward, input.value * 2 # Direct calculation
|
32
|
-
on is_reverse, reverse_processor + 10 # Delegates to reverse (safe)
|
33
|
-
base "invalid operation" # Fallback for unknown operations
|
34
|
-
end
|
35
|
-
|
36
|
-
value :reverse_processor do
|
37
|
-
on is_forward, forward_processor - 5 # Delegates to forward (safe)
|
38
|
-
on is_reverse, input.value / 2 # Direct calculation
|
39
|
-
base "invalid operation" # Fallback for unknown operations
|
40
|
-
end
|
41
|
-
end
|
42
|
-
```
|
43
|
-
|
44
|
-
## Safety Guarantees
|
45
|
-
|
46
|
-
**Allowed**: Cycles where conditions are mutually exclusive
|
47
|
-
- `is_forward` and `is_reverse` cannot both be true (operation has single value)
|
48
|
-
- Each recursion executes exactly one step before hitting direct calculation
|
49
|
-
- Bounded recursion with guaranteed termination
|
50
|
-
|
51
|
-
**Rejected**: Cycles with overlapping conditions
|
52
|
-
```ruby
|
53
|
-
# This would be rejected - conditions can overlap
|
54
|
-
value :unsafe_cycle do
|
55
|
-
on input.n > 0, "positive"
|
56
|
-
on input.n > 5, "large" # Both can be true!
|
57
|
-
base fn(:not, unsafe_cycle)
|
58
|
-
end
|
59
|
-
```
|
60
|
-
|
61
|
-
## Implementation Details
|
62
|
-
|
63
|
-
### Conditional Dependencies
|
64
|
-
Base case dependencies are marked as conditional because they only execute when no explicit conditions match.
|
65
|
-
|
66
|
-
### Mutual Exclusion Analysis
|
67
|
-
Conditions are analyzed for mutual exclusion:
|
68
|
-
- Same field equality comparisons: `field == value1` vs `field == value2`
|
69
|
-
- Domain constraints ensuring impossibility
|
70
|
-
- All condition pairs must be mutually exclusive
|
71
|
-
|
72
|
-
### Metadata Generation
|
73
|
-
Analysis results stored in `cascade_metadata` state:
|
74
|
-
```ruby
|
75
|
-
{
|
76
|
-
condition_traits: [:is_forward, :is_reverse],
|
77
|
-
condition_count: 2,
|
78
|
-
all_mutually_exclusive: true,
|
79
|
-
exclusive_pairs: 1,
|
80
|
-
total_pairs: 1
|
81
|
-
}
|
82
|
-
```
|
83
|
-
|
84
|
-
## Use Cases
|
85
|
-
|
86
|
-
- Processing workflows with bidirectional logic
|
87
|
-
- State machine fallback patterns
|
88
|
-
- Recursive decision trees with termination conditions
|
89
|
-
- Complex business rules with safe delegation patterns
|