kumi 0.0.10 → 0.0.11
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 +18 -0
- data/CLAUDE.md +7 -231
- data/README.md +1 -1
- data/docs/VECTOR_SEMANTICS.md +286 -0
- data/docs/features/hierarchical-broadcasting.md +1 -1
- data/docs/features/s-expression-printer.md +2 -2
- data/examples/deep_schema_compilation_and_evaluation_benchmark.rb +21 -15
- data/lib/kumi/analyzer.rb +34 -12
- data/lib/kumi/compiler.rb +2 -12
- data/lib/kumi/core/analyzer/passes/broadcast_detector.rb +157 -64
- data/lib/kumi/core/analyzer/passes/dependency_resolver.rb +1 -1
- data/lib/kumi/core/analyzer/passes/input_access_planner_pass.rb +47 -0
- data/lib/kumi/core/analyzer/passes/input_collector.rb +118 -101
- data/lib/kumi/core/analyzer/passes/join_reduce_planning_pass.rb +293 -0
- data/lib/kumi/core/analyzer/passes/lower_to_ir_pass.rb +993 -0
- data/lib/kumi/core/analyzer/passes/pass_base.rb +2 -2
- data/lib/kumi/core/analyzer/passes/scope_resolution_pass.rb +346 -0
- data/lib/kumi/core/analyzer/passes/semantic_constraint_validator.rb +2 -1
- data/lib/kumi/core/analyzer/passes/toposorter.rb +9 -3
- data/lib/kumi/core/analyzer/passes/type_checker.rb +3 -3
- data/lib/kumi/core/analyzer/passes/type_consistency_checker.rb +2 -2
- data/lib/kumi/core/analyzer/passes/{type_inferencer.rb → type_inferencer_pass.rb} +4 -4
- data/lib/kumi/core/analyzer/passes/unsat_detector.rb +2 -2
- data/lib/kumi/core/analyzer/plans.rb +52 -0
- data/lib/kumi/core/analyzer/structs/access_plan.rb +20 -0
- data/lib/kumi/core/analyzer/structs/input_meta.rb +29 -0
- data/lib/kumi/core/compiler/access_builder.rb +36 -0
- data/lib/kumi/core/compiler/access_planner.rb +219 -0
- data/lib/kumi/core/compiler/accessors/base.rb +69 -0
- data/lib/kumi/core/compiler/accessors/each_indexed_accessor.rb +84 -0
- data/lib/kumi/core/compiler/accessors/materialize_accessor.rb +55 -0
- data/lib/kumi/core/compiler/accessors/ravel_accessor.rb +73 -0
- data/lib/kumi/core/compiler/accessors/read_accessor.rb +41 -0
- data/lib/kumi/core/compiler_base.rb +2 -2
- data/lib/kumi/core/error_reporter.rb +6 -5
- data/lib/kumi/core/errors.rb +4 -0
- data/lib/kumi/core/explain.rb +157 -205
- data/lib/kumi/core/export/node_builders.rb +2 -2
- data/lib/kumi/core/export/node_serializers.rb +1 -1
- data/lib/kumi/core/function_registry/collection_functions.rb +21 -10
- data/lib/kumi/core/function_registry/conditional_functions.rb +14 -4
- data/lib/kumi/core/function_registry/function_builder.rb +142 -55
- data/lib/kumi/core/function_registry/logical_functions.rb +5 -5
- data/lib/kumi/core/function_registry/stat_functions.rb +2 -2
- data/lib/kumi/core/function_registry.rb +126 -108
- data/lib/kumi/core/ir/execution_engine/combinators.rb +117 -0
- data/lib/kumi/core/ir/execution_engine/interpreter.rb +336 -0
- data/lib/kumi/core/ir/execution_engine/values.rb +46 -0
- data/lib/kumi/core/ir/execution_engine.rb +50 -0
- data/lib/kumi/core/ir.rb +58 -0
- data/lib/kumi/core/ruby_parser/build_context.rb +2 -2
- data/lib/kumi/core/ruby_parser/declaration_reference_proxy.rb +0 -12
- data/lib/kumi/core/ruby_parser/dsl_cascade_builder.rb +36 -15
- data/lib/kumi/core/ruby_parser/input_builder.rb +5 -5
- data/lib/kumi/core/ruby_parser/parser.rb +1 -1
- data/lib/kumi/core/ruby_parser/schema_builder.rb +2 -2
- data/lib/kumi/core/ruby_parser/sugar.rb +7 -0
- data/lib/kumi/registry.rb +14 -79
- data/lib/kumi/runtime/executable.rb +213 -0
- data/lib/kumi/schema.rb +14 -3
- data/lib/kumi/schema_metadata.rb +2 -2
- data/lib/kumi/support/ir_dump.rb +491 -0
- data/lib/kumi/support/s_expression_printer.rb +1 -1
- data/lib/kumi/syntax/location.rb +5 -0
- data/lib/kumi/syntax/node.rb +0 -1
- data/lib/kumi/syntax/root.rb +2 -2
- data/lib/kumi/version.rb +1 -1
- data/lib/kumi.rb +6 -15
- metadata +26 -15
- data/lib/kumi/core/cascade_executor_builder.rb +0 -132
- data/lib/kumi/core/compiled_schema.rb +0 -43
- data/lib/kumi/core/compiler/expression_compiler.rb +0 -146
- data/lib/kumi/core/compiler/function_invoker.rb +0 -55
- data/lib/kumi/core/compiler/path_traversal_compiler.rb +0 -158
- data/lib/kumi/core/compiler/reference_compiler.rb +0 -46
- data/lib/kumi/core/evaluation_wrapper.rb +0 -40
- data/lib/kumi/core/nested_structure_utils.rb +0 -78
- data/lib/kumi/core/schema_instance.rb +0 -115
- data/lib/kumi/core/vectorized_function_builder.rb +0 -88
- data/lib/kumi/js/compiler.rb +0 -878
- data/lib/kumi/js/function_registry.rb +0 -333
- data/migrate_to_core_iterative.rb +0 -938
data/lib/kumi/js/compiler.rb
DELETED
@@ -1,878 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Kumi
|
4
|
-
module Js
|
5
|
-
# JavaScript compiler that extends the base Kumi compiler
|
6
|
-
# Outputs JavaScript code instead of Ruby lambdas
|
7
|
-
class Compiler < Kumi::Core::CompilerBase
|
8
|
-
# JavaScript expression compilers - same logic, different output format
|
9
|
-
module JSExprCompilers
|
10
|
-
def compile_literal(expr)
|
11
|
-
value_js = case expr.value
|
12
|
-
when String
|
13
|
-
expr.value.inspect # Use inspect for proper escaping
|
14
|
-
when Numeric
|
15
|
-
expr.value.to_s
|
16
|
-
when TrueClass, FalseClass
|
17
|
-
expr.value.to_s
|
18
|
-
when NilClass
|
19
|
-
"null"
|
20
|
-
when Array
|
21
|
-
"[#{expr.value.map(&:inspect).join(', ')}]"
|
22
|
-
else
|
23
|
-
expr.value.inspect
|
24
|
-
end
|
25
|
-
"(ctx) => #{value_js}"
|
26
|
-
end
|
27
|
-
|
28
|
-
def compile_field_node(expr)
|
29
|
-
compile_field(expr)
|
30
|
-
end
|
31
|
-
|
32
|
-
def compile_element_field_reference(expr)
|
33
|
-
path = expr.path
|
34
|
-
|
35
|
-
# Check if we have nested paths metadata for this path
|
36
|
-
nested_paths = @analysis.state[:broadcasts]&.dig(:nested_paths)
|
37
|
-
unless nested_paths && nested_paths[path]
|
38
|
-
raise Errors::CompilationError, "Missing nested path metadata for #{path.inspect}. This indicates an analyzer bug."
|
39
|
-
end
|
40
|
-
|
41
|
-
# Determine operation mode based on context
|
42
|
-
operation_mode = determine_operation_mode_for_path(path)
|
43
|
-
generate_js_nested_path_traversal(path, operation_mode)
|
44
|
-
|
45
|
-
# ERROR: All nested paths should have metadata from the analyzer
|
46
|
-
# If we reach here, it means the BroadcastDetector didn't process this path
|
47
|
-
end
|
48
|
-
|
49
|
-
def compile_binding_node(expr)
|
50
|
-
name = expr.name
|
51
|
-
# Reference to other compiled bindings
|
52
|
-
"(ctx) => bindings.#{name}(ctx)"
|
53
|
-
end
|
54
|
-
|
55
|
-
def compile_list(expr)
|
56
|
-
element_fns = expr.elements.map { |e| compile_expr(e) }
|
57
|
-
|
58
|
-
# Generate JavaScript array creation
|
59
|
-
elements_js = element_fns.map.with_index { |_, i| " fn#{i}(ctx)" }.join(",\n")
|
60
|
-
|
61
|
-
# Create function declarations for each element
|
62
|
-
fn_declarations = element_fns.map.with_index do |fn_code, i|
|
63
|
-
" const fn#{i} = #{fn_code};"
|
64
|
-
end.join("\n")
|
65
|
-
|
66
|
-
"(ctx) => {\n#{fn_declarations}\n return [\n#{elements_js}\n ];\n}"
|
67
|
-
end
|
68
|
-
|
69
|
-
def compile_call(expr)
|
70
|
-
fn_name = expr.fn_name
|
71
|
-
arg_exprs = expr.args.map { |a| compile_expr(a) }
|
72
|
-
|
73
|
-
# Get compilation metadata once
|
74
|
-
compilation_meta = @analysis.state[:broadcasts]&.dig(:compilation_metadata, @current_declaration)
|
75
|
-
|
76
|
-
# Check if this is a vectorized operation
|
77
|
-
if vectorized_operation?(expr)
|
78
|
-
# Build vectorized executor JavaScript code
|
79
|
-
compile_js_vectorized_call(fn_name, arg_exprs, compilation_meta)
|
80
|
-
else
|
81
|
-
# Use pre-computed function call strategy
|
82
|
-
function_strategy = compilation_meta&.dig(:function_call_strategy) || {}
|
83
|
-
|
84
|
-
if function_strategy[:flattening_required]
|
85
|
-
compile_js_call_with_flattening(fn_name, arg_exprs, function_strategy)
|
86
|
-
else
|
87
|
-
compile_regular_call(fn_name, arg_exprs, expr)
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
def compile_cascade(expr)
|
93
|
-
# Use metadata to determine if this cascade is vectorized
|
94
|
-
if is_cascade_vectorized?(expr)
|
95
|
-
compile_js_vectorized_cascade(expr)
|
96
|
-
else
|
97
|
-
compile_js_regular_cascade(expr)
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
private
|
102
|
-
|
103
|
-
def generate_js_nested_path_traversal(path, operation_mode)
|
104
|
-
# Get path metadata to determine access mode (element vs object)
|
105
|
-
nested_paths = @analysis.state[:broadcasts]&.dig(:nested_paths)
|
106
|
-
path_metadata = nested_paths && nested_paths[path]
|
107
|
-
access_mode = path_metadata&.dig(:access_mode) || :object
|
108
|
-
|
109
|
-
if access_mode == :element
|
110
|
-
# For element access, generate direct flattening based on path depth
|
111
|
-
root_field = path.first
|
112
|
-
flatten_depth = path.length - 1 # Flatten (path_depth - 1) levels
|
113
|
-
|
114
|
-
if flatten_depth.positive?
|
115
|
-
# Generate direct flat() call
|
116
|
-
"(ctx) => ctx.#{root_field}.flat(#{flatten_depth})"
|
117
|
-
else
|
118
|
-
# No flattening needed
|
119
|
-
"(ctx) => ctx.#{root_field}"
|
120
|
-
end
|
121
|
-
elsif path.length == 1
|
122
|
-
# For object access, generate proper nested mapping
|
123
|
-
# Simple field access
|
124
|
-
"(ctx) => ctx.#{path.first}"
|
125
|
-
elsif path.length == 2
|
126
|
-
# Common case: array.field -> array.map(item => item.field)
|
127
|
-
root_field, nested_field = path
|
128
|
-
"(ctx) => ctx.#{root_field}.map(item => item.#{nested_field})"
|
129
|
-
else
|
130
|
-
# Complex nested object access - use the traversal
|
131
|
-
path_js = "[#{path.map { |p| "'#{p}'" }.join(', ')}]"
|
132
|
-
case operation_mode
|
133
|
-
when :flatten
|
134
|
-
"(ctx) => kumiRuntime.traverseNestedPath(ctx, #{path_js}, 'flatten', 'object')"
|
135
|
-
else
|
136
|
-
"(ctx) => kumiRuntime.traverseNestedPath(ctx, #{path_js}, 'broadcast', 'object')"
|
137
|
-
end
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
|
-
def compile_js_vectorized_call(fn_name, arg_exprs, compilation_meta)
|
142
|
-
# Generate vectorized function call using metadata
|
143
|
-
args_js = arg_exprs.map.with_index { |_, i| "arg#{i}(ctx)" }.join(", ")
|
144
|
-
|
145
|
-
arg_declarations = arg_exprs.map.with_index do |arg_code, i|
|
146
|
-
" const arg#{i} = #{arg_code};"
|
147
|
-
end.join("\n")
|
148
|
-
|
149
|
-
# Convert compilation metadata to JSON string, handling nil case
|
150
|
-
meta_json = compilation_meta ? compilation_meta.to_json : "null"
|
151
|
-
|
152
|
-
"(ctx) => {\n#{arg_declarations}\n return kumiRuntime.vectorizedFunctionCall('#{fn_name}', [#{args_js}], #{meta_json});\n}"
|
153
|
-
end
|
154
|
-
|
155
|
-
def compile_js_call_with_flattening(fn_name, arg_exprs, function_strategy)
|
156
|
-
# Generate function call with selective argument flattening
|
157
|
-
flatten_indices = function_strategy[:flatten_argument_indices] || []
|
158
|
-
|
159
|
-
args_js = arg_exprs.map.with_index do |_, i|
|
160
|
-
if flatten_indices.include?(i)
|
161
|
-
"kumiRuntime.flattenCompletely(arg#{i}(ctx))"
|
162
|
-
else
|
163
|
-
"arg#{i}(ctx)"
|
164
|
-
end
|
165
|
-
end.join(", ")
|
166
|
-
|
167
|
-
arg_declarations = arg_exprs.map.with_index do |arg_code, i|
|
168
|
-
" const arg#{i} = #{arg_code};"
|
169
|
-
end.join("\n")
|
170
|
-
|
171
|
-
fn_accessor = if fn_name.to_s.match?(/^[a-zA-Z_$][a-zA-Z0-9_$]*$/)
|
172
|
-
"kumiRegistry.#{fn_name}"
|
173
|
-
else
|
174
|
-
"kumiRegistry[\"#{fn_name}\"]"
|
175
|
-
end
|
176
|
-
|
177
|
-
"(ctx) => {\n#{arg_declarations}\n return #{fn_accessor}(#{args_js});\n}"
|
178
|
-
end
|
179
|
-
|
180
|
-
def compile_js_vectorized_cascade(expr)
|
181
|
-
# Generate JavaScript vectorized cascade logic
|
182
|
-
|
183
|
-
# Get pre-computed cascade strategy
|
184
|
-
get_cascade_compilation_metadata
|
185
|
-
strategy = get_cascade_strategy
|
186
|
-
|
187
|
-
# Separate conditional cases from base case
|
188
|
-
conditional_cases = expr.cases.select(&:condition)
|
189
|
-
base_case = expr.cases.find { |c| c.condition.nil? }
|
190
|
-
|
191
|
-
# Compile conditional pairs with vectorized condition handling
|
192
|
-
condition_compilations = conditional_cases.map.with_index do |c, i|
|
193
|
-
condition_fn = if is_cascade_vectorized?(expr)
|
194
|
-
compile_js_vectorized_condition(c.condition)
|
195
|
-
else
|
196
|
-
compile_expr(c.condition)
|
197
|
-
end
|
198
|
-
" const condition#{i} = #{condition_fn};"
|
199
|
-
end.join("\n")
|
200
|
-
|
201
|
-
result_compilations = conditional_cases.map.with_index do |c, i|
|
202
|
-
result_fn = compile_expr(c.result)
|
203
|
-
" const result#{i} = #{result_fn};"
|
204
|
-
end.join("\n")
|
205
|
-
|
206
|
-
base_compilation = base_case ? " const baseResult = #{compile_expr(base_case.result)};" : " const baseResult = () => null;"
|
207
|
-
|
208
|
-
# Convert strategy to JSON, handling symbols and complex objects
|
209
|
-
strategy_json = if strategy
|
210
|
-
strategy.to_json
|
211
|
-
else
|
212
|
-
"null"
|
213
|
-
end
|
214
|
-
|
215
|
-
<<~JAVASCRIPT
|
216
|
-
(ctx) => {
|
217
|
-
#{condition_compilations}
|
218
|
-
#{result_compilations}
|
219
|
-
#{base_compilation}
|
220
|
-
#{' '}
|
221
|
-
const condResults = [#{conditional_cases.map.with_index { |_, i| "condition#{i}(ctx)" }.join(', ')}];
|
222
|
-
const resResults = [#{conditional_cases.map.with_index { |_, i| "result#{i}(ctx)" }.join(', ')}];
|
223
|
-
const baseRes = baseResult(ctx);
|
224
|
-
#{' '}
|
225
|
-
return kumiRuntime.executeVectorizedCascade(condResults, resResults, baseRes, #{strategy_json});
|
226
|
-
}
|
227
|
-
JAVASCRIPT
|
228
|
-
end
|
229
|
-
|
230
|
-
def compile_js_regular_cascade(expr)
|
231
|
-
# Generate standard JavaScript cascade logic
|
232
|
-
cases_js = expr.cases.map.with_index do |case_expr, i|
|
233
|
-
if case_expr.condition
|
234
|
-
condition_fn = compile_expr(case_expr.condition)
|
235
|
-
result_fn = compile_expr(case_expr.result)
|
236
|
-
" const condition#{i} = #{condition_fn};\n " \
|
237
|
-
"const result#{i} = #{result_fn};\n " \
|
238
|
-
"if (condition#{i}(ctx)) return result#{i}(ctx);"
|
239
|
-
else
|
240
|
-
# Base case
|
241
|
-
result_fn = compile_expr(case_expr.result)
|
242
|
-
" const baseResult = #{result_fn};\n " \
|
243
|
-
"return baseResult(ctx);"
|
244
|
-
end
|
245
|
-
end.join("\n")
|
246
|
-
|
247
|
-
"(ctx) => {\n#{cases_js}\n return null;\n}"
|
248
|
-
end
|
249
|
-
|
250
|
-
def compile_js_vectorized_condition(condition_expr)
|
251
|
-
if condition_expr.is_a?(Kumi::Syntax::CallExpression) &&
|
252
|
-
condition_expr.fn_name == :cascade_and
|
253
|
-
# For cascade_and in vectorized contexts, use hierarchical broadcasting
|
254
|
-
compile_js_cascade_and_for_hierarchical_broadcasting(condition_expr)
|
255
|
-
else
|
256
|
-
# Otherwise compile normally
|
257
|
-
compile_expr(condition_expr)
|
258
|
-
end
|
259
|
-
end
|
260
|
-
|
261
|
-
def compile_js_cascade_and_for_hierarchical_broadcasting(condition_expr)
|
262
|
-
# Compile individual trait references
|
263
|
-
trait_compilations = condition_expr.args.map.with_index do |arg, i|
|
264
|
-
trait_fn = compile_expr(arg)
|
265
|
-
" const trait#{i} = #{trait_fn};"
|
266
|
-
end.join("\n")
|
267
|
-
|
268
|
-
trait_args = condition_expr.args.map.with_index { |_, i| "trait#{i}(ctx)" }.join(", ")
|
269
|
-
|
270
|
-
<<~JAVASCRIPT
|
271
|
-
(ctx) => {
|
272
|
-
#{trait_compilations}
|
273
|
-
const traitValues = [#{trait_args}];
|
274
|
-
return kumiRuntime.cascadeAndHierarchicalBroadcasting(traitValues);
|
275
|
-
}
|
276
|
-
JAVASCRIPT
|
277
|
-
end
|
278
|
-
|
279
|
-
def compile_regular_call(fn_name, arg_exprs, _expr)
|
280
|
-
# Functions that expect spread arrays instead of array arguments
|
281
|
-
spread_functions = %w[concat]
|
282
|
-
|
283
|
-
# Generate function call with arguments
|
284
|
-
args_js = if spread_functions.include?(fn_name.to_s) && arg_exprs.length == 1
|
285
|
-
# For spread functions with single array argument, spread the array
|
286
|
-
"...arg0(ctx)"
|
287
|
-
else
|
288
|
-
arg_exprs.map.with_index { |_, i| "arg#{i}(ctx)" }.join(", ")
|
289
|
-
end
|
290
|
-
|
291
|
-
arg_declarations = arg_exprs.map.with_index do |arg_code, i|
|
292
|
-
" const arg#{i} = #{arg_code};"
|
293
|
-
end.join("\n")
|
294
|
-
|
295
|
-
# Handle function names that need bracket notation (operators with special chars)
|
296
|
-
fn_accessor = if fn_name.to_s.match?(/^[a-zA-Z_$][a-zA-Z0-9_$]*$/)
|
297
|
-
"kumiRegistry.#{fn_name}"
|
298
|
-
else
|
299
|
-
"kumiRegistry[\"#{fn_name}\"]"
|
300
|
-
end
|
301
|
-
|
302
|
-
"(ctx) => {\n#{arg_declarations}\n return #{fn_accessor}(#{args_js});\n}"
|
303
|
-
end
|
304
|
-
|
305
|
-
def compile_vectorized_call(fn_name, arg_exprs, _expr)
|
306
|
-
# Generate vectorized function call (broadcasting)
|
307
|
-
args_js = arg_exprs.map.with_index { |_, i| "arg#{i}(ctx)" }.join(", ")
|
308
|
-
|
309
|
-
arg_declarations = arg_exprs.map.with_index do |arg_code, i|
|
310
|
-
" const arg#{i} = #{arg_code};"
|
311
|
-
end.join("\n")
|
312
|
-
|
313
|
-
"(ctx) => {\n#{arg_declarations}\n return kumiRuntime.vectorizedCall('#{fn_name}', [#{args_js}]);\n}"
|
314
|
-
end
|
315
|
-
|
316
|
-
def compile_field(node)
|
317
|
-
name = node.name
|
318
|
-
"(ctx) => {\n " \
|
319
|
-
"if (ctx.hasOwnProperty('#{name}')) return ctx.#{name};\n " \
|
320
|
-
"throw new Error(`Key '#{name}' not found. Available: ${Object.keys(ctx).join(', ')}`);\n" \
|
321
|
-
"}"
|
322
|
-
end
|
323
|
-
end
|
324
|
-
|
325
|
-
include JSExprCompilers
|
326
|
-
|
327
|
-
def initialize(syntax_tree, analyzer_result)
|
328
|
-
super
|
329
|
-
@js_bindings = {}
|
330
|
-
end
|
331
|
-
|
332
|
-
def compile(**options)
|
333
|
-
build_index
|
334
|
-
|
335
|
-
# Compile each declaration to JavaScript
|
336
|
-
@analysis.topo_order.each do |name|
|
337
|
-
decl = @index[name] or raise("Unknown binding #{name}")
|
338
|
-
compile_js_declaration(decl)
|
339
|
-
end
|
340
|
-
|
341
|
-
# Generate complete JavaScript module
|
342
|
-
generate_js_module(@js_bindings, **options)
|
343
|
-
end
|
344
|
-
|
345
|
-
private
|
346
|
-
|
347
|
-
def compile_js_declaration(decl)
|
348
|
-
@current_declaration = decl.name
|
349
|
-
kind = decl.is_a?(Kumi::Syntax::TraitDeclaration) ? :trait : :attr
|
350
|
-
js_fn = compile_expr(decl.expression)
|
351
|
-
@js_bindings[decl.name] = { type: kind, function: js_fn }
|
352
|
-
@current_declaration = nil
|
353
|
-
end
|
354
|
-
|
355
|
-
def generate_js_module(bindings, format: :standalone, **options)
|
356
|
-
case format
|
357
|
-
when :standalone
|
358
|
-
generate_standalone_js(bindings, **options)
|
359
|
-
else
|
360
|
-
raise ArgumentError, "Unknown format: #{format}"
|
361
|
-
end
|
362
|
-
end
|
363
|
-
|
364
|
-
def generate_standalone_js(bindings, **options)
|
365
|
-
# Generate a complete standalone JavaScript file
|
366
|
-
functions_js = bindings.map do |name, meta|
|
367
|
-
" #{name}: #{meta[:function]}"
|
368
|
-
end.join(",\n")
|
369
|
-
|
370
|
-
<<~JAVASCRIPT
|
371
|
-
// Generated by Kumi JavaScript Transpiler
|
372
|
-
|
373
|
-
// Kumi Runtime and Function Registry
|
374
|
-
#{js_runtime_code}
|
375
|
-
|
376
|
-
// Compiled Schema Bindings
|
377
|
-
const bindings = {
|
378
|
-
#{functions_js}
|
379
|
-
};
|
380
|
-
|
381
|
-
// Schema Runner
|
382
|
-
class KumiRunner {
|
383
|
-
constructor(input) {
|
384
|
-
this.input = input;
|
385
|
-
this.cache = new Map();
|
386
|
-
this.functionsUsed = [#{(@analysis.state[:functions_required] || Set.new).to_a.sort.map { |f| "\"#{f}\"" }.join(', ')}];
|
387
|
-
}
|
388
|
-
#{' '}
|
389
|
-
fetch(key) {
|
390
|
-
if (this.cache.has(key)) {
|
391
|
-
return this.cache.get(key);
|
392
|
-
}
|
393
|
-
#{' '}
|
394
|
-
if (!bindings[key]) {
|
395
|
-
throw new Error(`Unknown binding: ${key}`);
|
396
|
-
}
|
397
|
-
#{' '}
|
398
|
-
const value = bindings[key](this.input);
|
399
|
-
this.cache.set(key, value);
|
400
|
-
return value;
|
401
|
-
}
|
402
|
-
#{' '}
|
403
|
-
slice(...keys) {
|
404
|
-
const result = {};
|
405
|
-
keys.forEach(key => {
|
406
|
-
result[key] = this.fetch(key);
|
407
|
-
});
|
408
|
-
return result;
|
409
|
-
}
|
410
|
-
}
|
411
|
-
|
412
|
-
// Export interface
|
413
|
-
const schema = {
|
414
|
-
from: (input) => new KumiRunner(input)
|
415
|
-
};
|
416
|
-
|
417
|
-
// CommonJS export
|
418
|
-
if (typeof module !== 'undefined' && module.exports) {
|
419
|
-
module.exports = { schema };
|
420
|
-
}
|
421
|
-
|
422
|
-
// Browser global
|
423
|
-
if (typeof window !== 'undefined') {
|
424
|
-
window.schema = schema;
|
425
|
-
#{"window.#{options[:export_name]} = schema;" if options[:export_name]}
|
426
|
-
}
|
427
|
-
JAVASCRIPT
|
428
|
-
end
|
429
|
-
|
430
|
-
def js_runtime_code
|
431
|
-
# JavaScript runtime with sophisticated broadcasting support
|
432
|
-
functions_required = @analysis.state[:functions_required] || Set.new
|
433
|
-
<<~JAVASCRIPT
|
434
|
-
// Function Registry
|
435
|
-
#{FunctionRegistry.generate_js_code(functions_required: functions_required)}
|
436
|
-
|
437
|
-
// Enhanced Kumi Runtime for sophisticated vectorized operations
|
438
|
-
const kumiRuntime = {
|
439
|
-
// Nested path traversal matching Ruby implementation exactly
|
440
|
-
traverseNestedPath: function(data, path, operationMode, accessMode = 'object') {
|
441
|
-
let result;
|
442
|
-
#{' '}
|
443
|
-
// Use specialized traversal for element access mode
|
444
|
-
if (accessMode === 'element') {
|
445
|
-
result = this.traverseElementPath(data, path, operationMode);
|
446
|
-
} else {
|
447
|
-
result = this.traversePathRecursive(data, path, operationMode, accessMode);
|
448
|
-
}
|
449
|
-
#{' '}
|
450
|
-
// Post-process result based on operation mode
|
451
|
-
if (operationMode === 'flatten') {
|
452
|
-
return this.flattenCompletely(result);
|
453
|
-
}
|
454
|
-
return result;
|
455
|
-
},
|
456
|
-
#{' '}
|
457
|
-
// Specialized traversal for element access mode (matches Ruby exactly)
|
458
|
-
traverseElementPath: function(data, path, operationMode) {
|
459
|
-
// Handle context wrapper by extracting the specific field
|
460
|
-
if (data && typeof data === 'object' && !Array.isArray(data)) {
|
461
|
-
const fieldName = path[0];
|
462
|
-
const arrayData = data[fieldName];
|
463
|
-
#{' '}
|
464
|
-
// Always apply progressive traversal based on path depth
|
465
|
-
// This gives us the structure at the correct nesting level for both
|
466
|
-
// broadcast operations and structure operations
|
467
|
-
if (Array.isArray(arrayData) && path.length > 1) {
|
468
|
-
// Flatten exactly (path_depth - 1) levels to get the desired nesting level
|
469
|
-
return this.flattenToDepth(arrayData, path.length - 1);
|
470
|
-
} else {
|
471
|
-
return arrayData;
|
472
|
-
}
|
473
|
-
} else {
|
474
|
-
return data;
|
475
|
-
}
|
476
|
-
},
|
477
|
-
#{' '}
|
478
|
-
// Flatten array to specific depth (matches Ruby array.flatten(n))
|
479
|
-
flattenToDepth: function(arr, depth) {
|
480
|
-
if (depth <= 0 || !Array.isArray(arr)) {
|
481
|
-
return arr;
|
482
|
-
}
|
483
|
-
#{' '}
|
484
|
-
let result = arr.slice(); // Copy array
|
485
|
-
#{' '}
|
486
|
-
for (let i = 0; i < depth; i++) {
|
487
|
-
let hasNestedArrays = false;
|
488
|
-
const newResult = [];
|
489
|
-
#{' '}
|
490
|
-
for (const item of result) {
|
491
|
-
if (Array.isArray(item)) {
|
492
|
-
newResult.push(...item);
|
493
|
-
hasNestedArrays = true;
|
494
|
-
} else {
|
495
|
-
newResult.push(item);
|
496
|
-
}
|
497
|
-
}
|
498
|
-
#{' '}
|
499
|
-
result = newResult;
|
500
|
-
#{' '}
|
501
|
-
// If no nested arrays found, no need to continue flattening
|
502
|
-
if (!hasNestedArrays) {
|
503
|
-
break;
|
504
|
-
}
|
505
|
-
}
|
506
|
-
#{' '}
|
507
|
-
return result;
|
508
|
-
},
|
509
|
-
#{' '}
|
510
|
-
traversePathRecursive: function(data, path, operationMode, accessMode = 'object', originalPathLength = null) {
|
511
|
-
// Track original path length to determine traversal depth
|
512
|
-
originalPathLength = originalPathLength || path.length;
|
513
|
-
const currentDepth = originalPathLength - path.length;
|
514
|
-
#{' '}
|
515
|
-
if (path.length === 0) return data;
|
516
|
-
#{' '}
|
517
|
-
const field = path[0];
|
518
|
-
const remainingPath = path.slice(1);
|
519
|
-
#{' '}
|
520
|
-
if (remainingPath.length === 0) {
|
521
|
-
// Final field - extract based on operation mode
|
522
|
-
if (operationMode === 'broadcast' || operationMode === 'flatten') {
|
523
|
-
// Extract field preserving array structure
|
524
|
-
return this.extractFieldPreservingStructure(data, field, accessMode, currentDepth);
|
525
|
-
} else {
|
526
|
-
// Simple field access
|
527
|
-
return Array.isArray(data) ?#{' '}
|
528
|
-
data.map(item => this.accessField(item, field, accessMode, currentDepth)) :#{' '}
|
529
|
-
this.accessField(data, field, accessMode, currentDepth);
|
530
|
-
}
|
531
|
-
} else if (Array.isArray(data)) {
|
532
|
-
// Intermediate step - traverse deeper
|
533
|
-
// Array of items - traverse each item
|
534
|
-
return data.map(item =>#{' '}
|
535
|
-
this.traversePathRecursive(
|
536
|
-
this.accessField(item, field, accessMode, currentDepth),#{' '}
|
537
|
-
remainingPath,#{' '}
|
538
|
-
operationMode,#{' '}
|
539
|
-
accessMode,#{' '}
|
540
|
-
originalPathLength
|
541
|
-
)
|
542
|
-
);
|
543
|
-
} else {
|
544
|
-
// Single item - traverse directly
|
545
|
-
return this.traversePathRecursive(
|
546
|
-
this.accessField(data, field, accessMode, currentDepth),#{' '}
|
547
|
-
remainingPath,#{' '}
|
548
|
-
operationMode,#{' '}
|
549
|
-
accessMode,#{' '}
|
550
|
-
originalPathLength
|
551
|
-
);
|
552
|
-
}
|
553
|
-
},
|
554
|
-
#{' '}
|
555
|
-
extractFieldPreservingStructure: function(data, field, accessMode = 'object', depth = 0) {
|
556
|
-
if (Array.isArray(data)) {
|
557
|
-
return data.map(item => this.extractFieldPreservingStructure(item, field, accessMode, depth));
|
558
|
-
} else {
|
559
|
-
return this.accessField(data, field, accessMode, depth);
|
560
|
-
}
|
561
|
-
},
|
562
|
-
#{' '}
|
563
|
-
accessField: function(data, field, accessMode, depth = 0) {
|
564
|
-
if (accessMode === 'element') {
|
565
|
-
// Element access mode - for nested arrays, we need to traverse one level deeper
|
566
|
-
// This enables progressive path traversal like input.cube.layer.row.value
|
567
|
-
if (data && typeof data === 'object' && !Array.isArray(data)) {
|
568
|
-
return data[field];
|
569
|
-
} else if (Array.isArray(data)) {
|
570
|
-
// For element access, flatten one level to traverse deeper into nested structure
|
571
|
-
return this.flattenToDepth(data, 1);
|
572
|
-
} else {
|
573
|
-
// If not an array, return as-is (leaf level)
|
574
|
-
return data;
|
575
|
-
}
|
576
|
-
} else {
|
577
|
-
// Object access mode - normal hash/object field access
|
578
|
-
return data[field];
|
579
|
-
}
|
580
|
-
},
|
581
|
-
#{' '}
|
582
|
-
flattenCompletely: function(data) {
|
583
|
-
const result = [];
|
584
|
-
this.flattenRecursive(data, result);
|
585
|
-
return result;
|
586
|
-
},
|
587
|
-
#{' '}
|
588
|
-
flattenRecursive: function(data, result) {
|
589
|
-
if (Array.isArray(data)) {
|
590
|
-
data.forEach(item => this.flattenRecursive(item, result));
|
591
|
-
} else {
|
592
|
-
result.push(data);
|
593
|
-
}
|
594
|
-
},
|
595
|
-
#{' '}
|
596
|
-
// Sophisticated vectorized function call with metadata support
|
597
|
-
vectorizedFunctionCall: function(fnName, args, compilationMeta) {
|
598
|
-
const fn = kumiRegistry[fnName];
|
599
|
-
if (!fn) throw new Error(`Unknown function: ${fnName}`);
|
600
|
-
#{' '}
|
601
|
-
// Check if any argument is vectorized (array)
|
602
|
-
const hasVectorizedArgs = args.some(Array.isArray);
|
603
|
-
#{' '}
|
604
|
-
if (hasVectorizedArgs) {
|
605
|
-
return this.vectorizedBroadcastingCall(fn, args, compilationMeta);
|
606
|
-
} else {
|
607
|
-
// All arguments are scalars - regular function call
|
608
|
-
return fn(...args);
|
609
|
-
}
|
610
|
-
},
|
611
|
-
#{' '}
|
612
|
-
vectorizedBroadcastingCall: function(fn, values, compilationMeta) {
|
613
|
-
// Find array dimensions for broadcasting
|
614
|
-
const arrayValues = values.filter(v => Array.isArray(v));
|
615
|
-
if (arrayValues.length === 0) return fn(...values);
|
616
|
-
#{' '}
|
617
|
-
// Check if we have deeply nested arrays (arrays containing arrays)
|
618
|
-
const hasNestedArrays = arrayValues.some(arr =>#{' '}
|
619
|
-
arr.some(item => Array.isArray(item))
|
620
|
-
);
|
621
|
-
#{' '}
|
622
|
-
if (hasNestedArrays) {
|
623
|
-
return this.hierarchicalBroadcasting(fn, values);
|
624
|
-
} else {
|
625
|
-
return this.simpleBroadcasting(fn, values);
|
626
|
-
}
|
627
|
-
},
|
628
|
-
#{' '}
|
629
|
-
simpleBroadcasting: function(fn, values) {
|
630
|
-
const arrayValues = values.filter(v => Array.isArray(v));
|
631
|
-
const arrayLength = arrayValues[0].length;
|
632
|
-
const result = [];
|
633
|
-
#{' '}
|
634
|
-
for (let i = 0; i < arrayLength; i++) {
|
635
|
-
const elementArgs = values.map(arg =>
|
636
|
-
Array.isArray(arg) ? arg[i] : arg
|
637
|
-
);
|
638
|
-
result.push(fn(...elementArgs));
|
639
|
-
}
|
640
|
-
#{' '}
|
641
|
-
return result;
|
642
|
-
},
|
643
|
-
#{' '}
|
644
|
-
hierarchicalBroadcasting: function(fn, values) {
|
645
|
-
// Handle hierarchical broadcasting for nested arrays
|
646
|
-
const arrayValues = values.filter(v => Array.isArray(v));
|
647
|
-
const maxDepthArray = arrayValues.reduce((max, arr) =>#{' '}
|
648
|
-
this.calculateArrayDepth(arr) > this.calculateArrayDepth(max) ? arr : max
|
649
|
-
);
|
650
|
-
#{' '}
|
651
|
-
return this.mapNestedStructure(maxDepthArray, (indices) => {
|
652
|
-
const elementArgs = values.map(arg =>#{' '}
|
653
|
-
this.navigateNestedIndices(arg, indices)
|
654
|
-
);
|
655
|
-
return fn(...elementArgs);
|
656
|
-
});
|
657
|
-
},
|
658
|
-
#{' '}
|
659
|
-
calculateArrayDepth: function(arr) {
|
660
|
-
if (!Array.isArray(arr)) return 0;
|
661
|
-
return 1 + Math.max(0, ...arr.map(item => this.calculateArrayDepth(item)));
|
662
|
-
},
|
663
|
-
#{' '}
|
664
|
-
mapNestedStructure: function(structure, fn) {
|
665
|
-
if (!Array.isArray(structure)) {
|
666
|
-
return fn([]);
|
667
|
-
}
|
668
|
-
#{' '}
|
669
|
-
return structure.map((item, index) => {
|
670
|
-
if (Array.isArray(item)) {
|
671
|
-
return this.mapNestedStructure(item, (innerIndices) =>#{' '}
|
672
|
-
fn([index, ...innerIndices])
|
673
|
-
);
|
674
|
-
} else {
|
675
|
-
return fn([index]);
|
676
|
-
}
|
677
|
-
});
|
678
|
-
},
|
679
|
-
#{' '}
|
680
|
-
navigateNestedIndices: function(data, indices) {
|
681
|
-
let current = data;
|
682
|
-
for (const index of indices) {
|
683
|
-
if (Array.isArray(current)) {
|
684
|
-
current = current[index];
|
685
|
-
} else {
|
686
|
-
return current; // Scalar value - return as is
|
687
|
-
}
|
688
|
-
}
|
689
|
-
return current;
|
690
|
-
},
|
691
|
-
#{' '}
|
692
|
-
// Vectorized cascade execution with strategy support
|
693
|
-
executeVectorizedCascade: function(condResults, resResults, baseResult, strategy) {
|
694
|
-
if (!strategy) {
|
695
|
-
// Fallback to simple cascade evaluation
|
696
|
-
for (let i = 0; i < condResults.length; i++) {
|
697
|
-
if (condResults[i]) return resResults[i];
|
698
|
-
}
|
699
|
-
return baseResult;
|
700
|
-
}
|
701
|
-
#{' '}
|
702
|
-
switch (strategy.mode) {
|
703
|
-
case 'hierarchical':
|
704
|
-
return this.executeHierarchicalCascade(condResults, resResults, baseResult);
|
705
|
-
case 'nested_array':
|
706
|
-
case 'deep_nested_array':
|
707
|
-
return this.executeNestedArrayCascade(condResults, resResults, baseResult);
|
708
|
-
case 'simple_array':
|
709
|
-
return this.executeSimpleArrayCascade(condResults, resResults, baseResult);
|
710
|
-
default:
|
711
|
-
return this.executeScalarCascade(condResults, resResults, baseResult);
|
712
|
-
}
|
713
|
-
},
|
714
|
-
#{' '}
|
715
|
-
executeHierarchicalCascade: function(condResults, resResults, baseResult) {
|
716
|
-
// Find the result structure to use as template (deepest structure)
|
717
|
-
const allValues = [...resResults, ...condResults, baseResult].filter(v => Array.isArray(v));
|
718
|
-
const resultTemplate = allValues.reduce((max, v) =>#{' '}
|
719
|
-
this.calculateArrayDepth(v) > this.calculateArrayDepth(max) ? v : max,#{' '}
|
720
|
-
allValues[0] || []
|
721
|
-
);
|
722
|
-
#{' '}
|
723
|
-
if (!resultTemplate || !Array.isArray(resultTemplate)) {
|
724
|
-
return this.executeScalarCascade(condResults, resResults, baseResult);
|
725
|
-
}
|
726
|
-
#{' '}
|
727
|
-
// Apply hierarchical cascade logic using the result structure as template
|
728
|
-
return this.mapNestedStructure(resultTemplate, (indices) => {
|
729
|
-
// Check conditional cases first with hierarchical broadcasting for conditions
|
730
|
-
for (let i = 0; i < condResults.length; i++) {
|
731
|
-
const condVal = this.navigateWithHierarchicalBroadcasting(condResults[i], indices, resultTemplate);
|
732
|
-
if (condVal) {
|
733
|
-
const resVal = this.navigateNestedIndices(resResults[i], indices);
|
734
|
-
return resVal;
|
735
|
-
}
|
736
|
-
}
|
737
|
-
#{' '}
|
738
|
-
// If no conditions matched, use base result
|
739
|
-
return this.navigateNestedIndices(baseResult, indices);
|
740
|
-
});
|
741
|
-
},
|
742
|
-
#{' '}
|
743
|
-
navigateWithHierarchicalBroadcasting: function(value, indices, template) {
|
744
|
-
// Navigate through value with hierarchical broadcasting to match template structure
|
745
|
-
const valueDepth = this.calculateArrayDepth(value);
|
746
|
-
const templateDepth = this.calculateArrayDepth(template);
|
747
|
-
#{' '}
|
748
|
-
if (valueDepth < templateDepth) {
|
749
|
-
// Value is at parent level - broadcast to child level by using fewer indices
|
750
|
-
const parentIndices = indices.slice(0, valueDepth);
|
751
|
-
return this.navigateNestedIndices(value, parentIndices);
|
752
|
-
} else {
|
753
|
-
// Same or deeper level - navigate normally
|
754
|
-
return this.navigateNestedIndices(value, indices);
|
755
|
-
}
|
756
|
-
},
|
757
|
-
#{' '}
|
758
|
-
executeNestedArrayCascade: function(condResults, resResults, baseResult) {
|
759
|
-
// Handle nested array cascades with structure preservation
|
760
|
-
const firstArrayResult = resResults.find(r => Array.isArray(r)) ||#{' '}
|
761
|
-
condResults.find(c => Array.isArray(c)) ||#{' '}
|
762
|
-
(Array.isArray(baseResult) ? baseResult : []);
|
763
|
-
#{' '}
|
764
|
-
return this.mapNestedStructure(firstArrayResult, (indices) => {
|
765
|
-
for (let i = 0; i < condResults.length; i++) {
|
766
|
-
const condVal = this.navigateNestedIndices(condResults[i], indices);
|
767
|
-
if (condVal) {
|
768
|
-
return this.navigateNestedIndices(resResults[i], indices);
|
769
|
-
}
|
770
|
-
}
|
771
|
-
return this.navigateNestedIndices(baseResult, indices);
|
772
|
-
});
|
773
|
-
},
|
774
|
-
#{' '}
|
775
|
-
executeSimpleArrayCascade: function(condResults, resResults, baseResult) {
|
776
|
-
// Handle simple array cascades (flat arrays)
|
777
|
-
const arrayLength = Math.max(
|
778
|
-
...condResults.filter(Array.isArray).map(arr => arr.length),
|
779
|
-
...resResults.filter(Array.isArray).map(arr => arr.length),
|
780
|
-
Array.isArray(baseResult) ? baseResult.length : 0
|
781
|
-
);
|
782
|
-
#{' '}
|
783
|
-
const result = [];
|
784
|
-
for (let i = 0; i < arrayLength; i++) {
|
785
|
-
let matched = false;
|
786
|
-
for (let j = 0; j < condResults.length; j++) {
|
787
|
-
const condVal = Array.isArray(condResults[j]) ? condResults[j][i] : condResults[j];
|
788
|
-
if (condVal) {
|
789
|
-
const resVal = Array.isArray(resResults[j]) ? resResults[j][i] : resResults[j];
|
790
|
-
result[i] = resVal;
|
791
|
-
matched = true;
|
792
|
-
break;
|
793
|
-
}
|
794
|
-
}
|
795
|
-
if (!matched) {
|
796
|
-
result[i] = Array.isArray(baseResult) ? baseResult[i] : baseResult;
|
797
|
-
}
|
798
|
-
}
|
799
|
-
return result;
|
800
|
-
},
|
801
|
-
#{' '}
|
802
|
-
executeScalarCascade: function(condResults, resResults, baseResult) {
|
803
|
-
// Handle scalar cascades (no arrays involved)
|
804
|
-
for (let i = 0; i < condResults.length; i++) {
|
805
|
-
if (condResults[i]) return resResults[i];
|
806
|
-
}
|
807
|
-
return baseResult;
|
808
|
-
},
|
809
|
-
#{' '}
|
810
|
-
// Cascade AND with hierarchical broadcasting support
|
811
|
-
cascadeAndHierarchicalBroadcasting: function(traitValues) {
|
812
|
-
if (traitValues.length === 0) return true;
|
813
|
-
if (traitValues.length === 1) return traitValues[0];
|
814
|
-
#{' '}
|
815
|
-
// Use the cascade_and function directly on the array structures
|
816
|
-
return kumiRegistry.cascade_and(...traitValues);
|
817
|
-
},
|
818
|
-
#{' '}
|
819
|
-
// Element-wise AND with hierarchical broadcasting (matches Ruby implementation)
|
820
|
-
elementWiseAnd: function(a, b) {
|
821
|
-
// Handle different type combinations
|
822
|
-
if (Array.isArray(a) && Array.isArray(b)) {
|
823
|
-
// Both are arrays - handle hierarchical broadcasting
|
824
|
-
if (this.hierarchicalBroadcastingNeeded(a, b)) {
|
825
|
-
return this.performHierarchicalAnd(a, b);
|
826
|
-
} else {
|
827
|
-
// Same structure - use zip for element-wise operations
|
828
|
-
return a.map((elemA, idx) => this.elementWiseAnd(elemA, b[idx]));
|
829
|
-
}
|
830
|
-
} else if (Array.isArray(a)) {
|
831
|
-
// Broadcast scalar b to array a
|
832
|
-
return a.map(elem => this.elementWiseAnd(elem, b));
|
833
|
-
} else if (Array.isArray(b)) {
|
834
|
-
// Broadcast scalar a to array b
|
835
|
-
return b.map(elem => this.elementWiseAnd(a, elem));
|
836
|
-
} else {
|
837
|
-
// Both are scalars - simple AND
|
838
|
-
return a && b;
|
839
|
-
}
|
840
|
-
},
|
841
|
-
#{' '}
|
842
|
-
hierarchicalBroadcastingNeeded: function(a, b) {
|
843
|
-
// Check if arrays have different nesting depths
|
844
|
-
const depthA = this.calculateArrayDepth(a);
|
845
|
-
const depthB = this.calculateArrayDepth(b);
|
846
|
-
return depthA !== depthB;
|
847
|
-
},
|
848
|
-
#{' '}
|
849
|
-
performHierarchicalAnd: function(a, b) {
|
850
|
-
// Determine which is parent (lower depth) and which is child (higher depth)
|
851
|
-
const depthA = this.calculateArrayDepth(a);
|
852
|
-
const depthB = this.calculateArrayDepth(b);
|
853
|
-
#{' '}
|
854
|
-
if (depthA < depthB) {
|
855
|
-
// a is parent, b is child
|
856
|
-
return this.broadcastParentToChild(a, b);
|
857
|
-
} else {
|
858
|
-
// b is parent, a is child
|
859
|
-
return this.broadcastParentToChild(b, a);
|
860
|
-
}
|
861
|
-
},
|
862
|
-
#{' '}
|
863
|
-
broadcastParentToChild: function(parent, child) {
|
864
|
-
// Map over child structure, broadcasting parent values appropriately
|
865
|
-
return this.mapNestedStructure(child, (indices) => {
|
866
|
-
// Navigate to appropriate parent element using fewer indices
|
867
|
-
const parentIndices = indices.slice(0, this.calculateArrayDepth(parent));
|
868
|
-
const parentValue = this.navigateNestedIndices(parent, parentIndices);
|
869
|
-
const childValue = this.navigateNestedIndices(child, indices);
|
870
|
-
return parentValue && childValue;
|
871
|
-
});
|
872
|
-
}
|
873
|
-
};
|
874
|
-
JAVASCRIPT
|
875
|
-
end
|
876
|
-
end
|
877
|
-
end
|
878
|
-
end
|