Module: Kumi::Pack::Builder
- Defined in:
- lib/kumi/pack/builder.rb
Constant Summary collapse
- VERSION =
"0.1"
Class Method Summary collapse
- .accessor_name_for(name) ⇒ Object
- .assemble_pack(module_id, ir, planning, bindings, inputs, _targets, include_ir) ⇒ Object
- .build(schema:, out_dir:, targets: %w[ruby],, include_ir: false) ⇒ Object
- .build_for_golden(schema_path, golden_dir, targets: %w[ruby])) ⇒ Object
-
.canonical_json(obj) ⇒ Object
Stable JSON for hashing.
-
.compute_hashes(pack) ⇒ Object
Compute canonical section hashes (sorted-key JSON).
- .extract_inputs(input_table) ⇒ Object
- .extract_ops_by_decl(ir) ⇒ Object
- .extract_ops_by_decl_with_planning(ir, plan_declarations) ⇒ Object
- .format_bindings_for_pack(bindings) ⇒ Object
- .format_ruby_lambda(impl) ⇒ Object
- .generate_artifacts(schema_path) ⇒ Object
- .print(schema:, targets: %w[ruby],, include_ir: false) ⇒ Object
- .sha256(obj) ⇒ Object
- .stringify_keys(obj) ⇒ Object
- .write_json(file_path, data) ⇒ Object
Class Method Details
.accessor_name_for(name) ⇒ Object
194 195 196 |
# File 'lib/kumi/pack/builder.rb', line 194 def accessor_name_for(name) name.gsub(/[^a-zA-Z0-9_]/, "_").downcase end |
.assemble_pack(module_id, ir, planning, bindings, inputs, _targets, include_ir) ⇒ Object
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/kumi/pack/builder.rb', line 62 def assemble_pack(module_id, ir, planning, bindings, inputs, _targets, include_ir) plan_obj = planning["plan"] || planning plan_declarations = plan_obj["declarations"] || {} pack = { "pack_version" => VERSION, "module_id" => module_id, "declarations" => extract_ops_by_decl_with_planning(ir, plan_declarations), "inputs" => extract_inputs(inputs), "bindings" => format_bindings_for_pack(bindings), "capabilities" => { "layout" => "nested_array" } } pack["ir_debug"] = ir if include_ir pack["hashes"] = compute_hashes(pack) pack end |
.build(schema:, out_dir:, targets: %w[ruby],, include_ir: false) ⇒ Object
14 15 16 17 18 19 20 21 22 23 24 25 |
# File 'lib/kumi/pack/builder.rb', line 14 def build(schema:, out_dir:, targets: %w[ruby], include_ir: false) ir, planning, bindings, inputs, module_id = generate_artifacts(schema) FileUtils.mkdir_p(out_dir) write_json("#{out_dir}/irv2.json", ir) write_json("#{out_dir}/planning.json", planning) write_json("#{out_dir}/bindings.json", bindings) write_json("#{out_dir}/inputs.json", inputs) pack = assemble_pack(module_id, ir, planning, bindings, inputs, targets, include_ir) write_json("#{out_dir}/pack.json", pack) canonical_json(pack) end |
.build_for_golden(schema_path, golden_dir, targets: %w[ruby])) ⇒ Object
33 34 35 36 37 38 39 40 41 42 43 |
# File 'lib/kumi/pack/builder.rb', line 33 def build_for_golden(schema_path, golden_dir, targets: %w[ruby]) ir, planning, bindings, inputs, module_id = generate_artifacts(schema_path) targets.each do |target| pack = assemble_pack(module_id, ir, planning, bindings, inputs, [target], false) filename = targets.size == 1 ? "pack.json" : "pack_#{target}.json" pack_file = File.join(golden_dir, filename) File.write(pack_file, canonical_json(pack)) end end |
.canonical_json(obj) ⇒ Object
Stable JSON for hashing
210 211 212 213 214 215 216 217 218 219 220 221 222 |
# File 'lib/kumi/pack/builder.rb', line 210 def canonical_json(obj) case obj when Hash "{#{obj.keys.map(&:to_s).sort.map { |k| "\"#{k}\":#{canonical_json(obj[k])}" }.join(',')}}" when Array "[#{obj.map { |v| canonical_json(v) }.join(',')}]" when String then JSON.generate(obj) when Numeric then obj.to_s when true, false then obj.to_s when NilClass then "null" else JSON.generate(obj) end end |
.compute_hashes(pack) ⇒ Object
Compute canonical section hashes (sorted-key JSON)
203 204 205 206 207 |
# File 'lib/kumi/pack/builder.rb', line 203 def compute_hashes(pack) keys = %w[declarations inputs bindings] keys << "ir_debug" if pack.key?("ir_debug") keys.to_h { |k| [k, sha256(pack[k])] } end |
.extract_inputs(input_table) ⇒ Object
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
# File 'lib/kumi/pack/builder.rb', line 127 def extract_inputs(input_table) # input_table is already in the new format with navigation_steps input_table.map do |entry| path_fqn = entry.path_fqn = stringify_keys(entry.) { "name" => path_fqn, "path_fqn" => path_fqn, "axes" => entry.axes.map(&:to_s), "dtype" => entry.dtype.to_s, "accessor_name" => accessor_name_for(path_fqn), "navigation_steps" => } end end |
.extract_ops_by_decl(ir) ⇒ Object
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
# File 'lib/kumi/pack/builder.rb', line 108 def extract_ops_by_decl(ir) declarations = ir["declarations"] || {} declarations.keys.sort.map do |name| d = declarations[name] ops = (d["operations"] || []).map do |op| { "id" => op["id"], "op" => op["op"] || op["kind"], "args" => op["args"] || [], "attrs" => op["attrs"] || {} } end result = { "name" => name, "operations" => ops } result["result_op_id"] = d["result"] if d.key?("result") result["axes"] = d["axes"] if d.key?("axes") result end end |
.extract_ops_by_decl_with_planning(ir, plan_declarations) ⇒ Object
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
# File 'lib/kumi/pack/builder.rb', line 79 def extract_ops_by_decl_with_planning(ir, plan_declarations) declarations = ir["declarations"] || {} declarations.keys.map do |name| d = declarations[name] ops = (d["operations"] || []).map do |op| { "id" => op["id"], "op" => op["op"] || op["kind"], "args" => op["args"] || [], "attrs" => op["attrs"] || {} } end # Start with IRV2 operations result = { "name" => name, "operations" => ops } result["result_op_id"] = d["result"] if d.key?("result") result["axes"] = d["axes"] if d.key?("axes") # Merge in planning metadata in sorted key order plan_data = plan_declarations[name] || {} plan_data.keys.sort.each do |key| # Don't override IRV2 fields, but add planning fields result[key] = plan_data[key] unless result.key?(key) end result end end |
.format_bindings_for_pack(bindings) ⇒ Object
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 |
# File 'lib/kumi/pack/builder.rb', line 144 def format_bindings_for_pack(bindings) return {} unless bindings.is_a?(Hash) formatted = {} target = bindings["target"] kernels_array = bindings["kernels"].map do |kernel_data| kernel_id = kernel_data["kernel_id"] impl = kernel_data["impl"] attrs = kernel_data["attrs"] || {} # Extract the function name from kernel_id (e.g., "core.add:ruby:v1" -> "core.add") fn_name = kernel_id.split(":").first # Format the implementation as proper Ruby lambda ruby_impl = format_ruby_lambda(impl) { "kernel_id" => fn_name, "impl" => ruby_impl, "attrs" => attrs } end formatted[target] = { "kernels" => kernels_array } formatted end |
.format_ruby_lambda(impl) ⇒ Object
170 171 172 173 174 175 176 177 178 179 180 181 |
# File 'lib/kumi/pack/builder.rb', line 170 def format_ruby_lambda(impl) # Convert "(a, b)\n a + b" to "->(a, b) { a + b }" lines = impl.strip.split("\n") if lines.length >= 2 params = lines[0].strip.gsub(/^\(|\)$/, "") # Remove outer parentheses body = lines[1..-1].map(&:strip).join("; ") "->(#{params}) { #{body} }" else # Fallback for single line "->(#{impl.strip})" end end |
.generate_artifacts(schema_path) ⇒ Object
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
# File 'lib/kumi/pack/builder.rb', line 45 def generate_artifacts(schema_path) schema, = Kumi::Frontends.load(path: schema_path) res = Kumi::Analyzer.analyze!(schema, side_tables: true) irv2 = stringify_keys(res.state[:irv2]) or raise "No IRV2" module_id = irv2["module"] || irv2["module_id"] || "kumi_module" require_relative "../codegen/planning" plan_bundle = Kumi::Codegen::Planning.from_ir(res.state[:irv2]) planning = Kumi::Codegen::Planning.to_json(plan_bundle) bindings = stringify_keys(res.state[:binding_manifest] || {}) inputs = stringify_keys(res.state[:input_table] || {}) [irv2, planning, bindings, inputs, module_id] end |
.print(schema:, targets: %w[ruby],, include_ir: false) ⇒ Object
27 28 29 30 31 |
# File 'lib/kumi/pack/builder.rb', line 27 def print(schema:, targets: %w[ruby], include_ir: false) ir, planning, bindings, inputs, module_id = generate_artifacts(schema) pack = assemble_pack(module_id, ir, planning, bindings, inputs, targets, include_ir) canonical_json(pack) end |
.sha256(obj) ⇒ Object
224 225 226 |
# File 'lib/kumi/pack/builder.rb', line 224 def sha256(obj) Digest::SHA256.hexdigest(canonical_json(obj)) end |
.stringify_keys(obj) ⇒ Object
183 184 185 186 187 188 189 190 191 192 |
# File 'lib/kumi/pack/builder.rb', line 183 def stringify_keys(obj) case obj when Hash obj.transform_keys(&:to_s).transform_values { |v| stringify_keys(v) } when Array obj.map { |v| stringify_keys(v) } else obj end end |
.write_json(file_path, data) ⇒ Object
198 199 200 |
# File 'lib/kumi/pack/builder.rb', line 198 def write_json(file_path, data) File.write(file_path, JSON.pretty_generate(data)) end |