kumi 0.0.31 → 0.0.33

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.
Files changed (294) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +21 -1
  3. data/README.md +31 -99
  4. data/data/kernels/ruby/core/arithmetic.yaml +2 -2
  5. data/docs/COMPOSED_SCHEMAS.md +137 -0
  6. data/docs/SCHEMA_IMPORTS.md +275 -0
  7. data/docs/SYNTAX.md +48 -0
  8. data/golden/array_element/expected/schema_ruby.rb +2 -27
  9. data/golden/array_index/expected/nast.txt +6 -6
  10. data/golden/array_index/expected/schema_ruby.rb +4 -31
  11. data/golden/array_operations/expected/lir_06_const_prop.txt +4 -8
  12. data/golden/array_operations/expected/schema_javascript.mjs +4 -8
  13. data/golden/array_operations/expected/schema_ruby.rb +10 -43
  14. data/golden/cascade_logic/expected/lir_06_const_prop.txt +7 -14
  15. data/golden/cascade_logic/expected/schema_javascript.mjs +7 -14
  16. data/golden/cascade_logic/expected/schema_ruby.rb +11 -45
  17. data/golden/chained_fusion/expected/lir_06_const_prop.txt +8 -18
  18. data/golden/chained_fusion/expected/schema_javascript.mjs +8 -18
  19. data/golden/chained_fusion/expected/schema_ruby.rb +14 -53
  20. data/golden/decimal_explicit/expected/schema_ruby.rb +4 -31
  21. data/golden/element_arrays/expected/lir_06_const_prop.txt +5 -11
  22. data/golden/element_arrays/expected/schema_javascript.mjs +5 -11
  23. data/golden/element_arrays/expected/schema_ruby.rb +13 -50
  24. data/golden/empty_and_null_inputs/expected/schema_ruby.rb +4 -31
  25. data/golden/example_xpto/expected/ast.txt +23 -0
  26. data/golden/example_xpto/expected/input_plan.txt +1 -0
  27. data/golden/example_xpto/expected/lir_00_unoptimized.txt +16 -0
  28. data/golden/example_xpto/expected/lir_01_hoist_scalar_references.txt +16 -0
  29. data/golden/example_xpto/expected/lir_02_inlined.txt +16 -0
  30. data/golden/example_xpto/expected/lir_03_cse.txt +16 -0
  31. data/golden/example_xpto/expected/lir_04_1_loop_fusion.txt +16 -0
  32. data/golden/example_xpto/expected/lir_04_loop_invcm.txt +16 -0
  33. data/golden/example_xpto/expected/lir_06_const_prop.txt +13 -0
  34. data/golden/example_xpto/expected/nast.txt +17 -0
  35. data/golden/example_xpto/expected/schema_javascript.mjs +13 -0
  36. data/golden/example_xpto/expected/schema_ruby.rb +13 -0
  37. data/golden/example_xpto/expected/snast.txt +17 -0
  38. data/golden/example_xpto/expected.json +4 -0
  39. data/golden/example_xpto/input.json +3 -0
  40. data/golden/example_xpto/schema.kumi +8 -0
  41. data/golden/function_overload/expected/schema_ruby.rb +2 -27
  42. data/golden/game_of_life/expected/lir_06_const_prop.txt +236 -287
  43. data/golden/game_of_life/expected/schema_javascript.mjs +32 -39
  44. data/golden/game_of_life/expected/schema_ruby.rb +34 -66
  45. data/golden/hash_keys/expected/lir_06_const_prop.txt +4 -10
  46. data/golden/hash_keys/expected/schema_javascript.mjs +6 -12
  47. data/golden/hash_keys/expected/schema_ruby.rb +8 -39
  48. data/golden/hash_value/expected/lir_06_const_prop.txt +3 -6
  49. data/golden/hash_value/expected/schema_javascript.mjs +3 -6
  50. data/golden/hash_value/expected/schema_ruby.rb +7 -37
  51. data/golden/hierarchical_complex/expected/lir_06_const_prop.txt +9 -18
  52. data/golden/hierarchical_complex/expected/schema_javascript.mjs +9 -18
  53. data/golden/hierarchical_complex/expected/schema_ruby.rb +14 -51
  54. data/golden/inline_rename_scope_leak/expected/lir_06_const_prop.txt +2 -6
  55. data/golden/inline_rename_scope_leak/expected/schema_javascript.mjs +2 -6
  56. data/golden/inline_rename_scope_leak/expected/schema_ruby.rb +7 -39
  57. data/golden/input_reference/expected/schema_ruby.rb +6 -35
  58. data/golden/interleaved_fusion/expected/lir_06_const_prop.txt +6 -14
  59. data/golden/interleaved_fusion/expected/schema_javascript.mjs +6 -14
  60. data/golden/interleaved_fusion/expected/schema_ruby.rb +11 -47
  61. data/golden/let_inline/expected/lir_06_const_prop.txt +1 -2
  62. data/golden/let_inline/expected/schema_javascript.mjs +1 -2
  63. data/golden/let_inline/expected/schema_ruby.rb +3 -29
  64. data/golden/loop_fusion/expected/lir_06_const_prop.txt +4 -10
  65. data/golden/loop_fusion/expected/schema_javascript.mjs +4 -10
  66. data/golden/loop_fusion/expected/schema_ruby.rb +8 -41
  67. data/golden/min_reduce_scope/expected/lir_06_const_prop.txt +3 -6
  68. data/golden/min_reduce_scope/expected/schema_javascript.mjs +3 -6
  69. data/golden/min_reduce_scope/expected/schema_ruby.rb +8 -39
  70. data/golden/mixed_dimensions/expected/lir_06_const_prop.txt +1 -2
  71. data/golden/mixed_dimensions/expected/schema_javascript.mjs +1 -2
  72. data/golden/mixed_dimensions/expected/schema_ruby.rb +5 -33
  73. data/golden/multirank_hoisting/expected/lir_06_const_prop.txt +9 -18
  74. data/golden/multirank_hoisting/expected/schema_javascript.mjs +9 -18
  75. data/golden/multirank_hoisting/expected/schema_ruby.rb +16 -55
  76. data/golden/nested_hash/expected/lir_06_const_prop.txt +1 -2
  77. data/golden/nested_hash/expected/schema_javascript.mjs +1 -2
  78. data/golden/nested_hash/expected/schema_ruby.rb +3 -29
  79. data/golden/reduction_broadcast/expected/schema_ruby.rb +5 -33
  80. data/golden/roll/expected/lir_06_const_prop.txt +8 -15
  81. data/golden/roll/expected/schema_javascript.mjs +8 -15
  82. data/golden/roll/expected/schema_ruby.rb +13 -48
  83. data/golden/schema_imports_broadcasting_with_imports/expected/ast.txt +26 -0
  84. data/golden/schema_imports_broadcasting_with_imports/expected/input_plan.txt +5 -0
  85. data/golden/schema_imports_broadcasting_with_imports/expected/lir_00_unoptimized.txt +20 -0
  86. data/golden/schema_imports_broadcasting_with_imports/expected/lir_01_hoist_scalar_references.txt +20 -0
  87. data/golden/schema_imports_broadcasting_with_imports/expected/lir_02_inlined.txt +22 -0
  88. data/golden/schema_imports_broadcasting_with_imports/expected/lir_03_cse.txt +21 -0
  89. data/golden/schema_imports_broadcasting_with_imports/expected/lir_04_1_loop_fusion.txt +21 -0
  90. data/golden/schema_imports_broadcasting_with_imports/expected/lir_04_loop_invcm.txt +21 -0
  91. data/golden/schema_imports_broadcasting_with_imports/expected/lir_06_const_prop.txt +21 -0
  92. data/golden/schema_imports_broadcasting_with_imports/expected/nast.txt +12 -0
  93. data/golden/schema_imports_broadcasting_with_imports/expected/schema_javascript.mjs +22 -0
  94. data/golden/schema_imports_broadcasting_with_imports/expected/schema_ruby.rb +24 -0
  95. data/golden/schema_imports_broadcasting_with_imports/expected/snast.txt +12 -0
  96. data/golden/schema_imports_broadcasting_with_imports/expected.json +4 -0
  97. data/golden/schema_imports_broadcasting_with_imports/input.json +7 -0
  98. data/golden/schema_imports_broadcasting_with_imports/schema.kumi +14 -0
  99. data/golden/schema_imports_complex_order_calc/expected/ast.txt +82 -0
  100. data/golden/schema_imports_complex_order_calc/expected/input_plan.txt +16 -0
  101. data/golden/schema_imports_complex_order_calc/expected/lir_00_unoptimized.txt +94 -0
  102. data/golden/schema_imports_complex_order_calc/expected/lir_01_hoist_scalar_references.txt +94 -0
  103. data/golden/schema_imports_complex_order_calc/expected/lir_02_inlined.txt +187 -0
  104. data/golden/schema_imports_complex_order_calc/expected/lir_03_cse.txt +131 -0
  105. data/golden/schema_imports_complex_order_calc/expected/lir_04_1_loop_fusion.txt +131 -0
  106. data/golden/schema_imports_complex_order_calc/expected/lir_04_loop_invcm.txt +131 -0
  107. data/golden/schema_imports_complex_order_calc/expected/lir_06_const_prop.txt +131 -0
  108. data/golden/schema_imports_complex_order_calc/expected/nast.txt +56 -0
  109. data/golden/schema_imports_complex_order_calc/expected/schema_javascript.mjs +147 -0
  110. data/golden/schema_imports_complex_order_calc/expected/schema_ruby.rb +149 -0
  111. data/golden/schema_imports_complex_order_calc/expected/snast.txt +56 -0
  112. data/golden/schema_imports_complex_order_calc/expected.json +12 -0
  113. data/golden/schema_imports_complex_order_calc/input.json +20 -0
  114. data/golden/schema_imports_complex_order_calc/schema.kumi +33 -0
  115. data/golden/schema_imports_composed_order/expected/ast.txt +33 -0
  116. data/golden/schema_imports_composed_order/expected/input_plan.txt +3 -0
  117. data/golden/schema_imports_composed_order/expected/lir_00_unoptimized.txt +25 -0
  118. data/golden/schema_imports_composed_order/expected/lir_01_hoist_scalar_references.txt +25 -0
  119. data/golden/schema_imports_composed_order/expected/lir_02_inlined.txt +33 -0
  120. data/golden/schema_imports_composed_order/expected/lir_03_cse.txt +33 -0
  121. data/golden/schema_imports_composed_order/expected/lir_04_1_loop_fusion.txt +33 -0
  122. data/golden/schema_imports_composed_order/expected/lir_04_loop_invcm.txt +33 -0
  123. data/golden/schema_imports_composed_order/expected/lir_06_const_prop.txt +33 -0
  124. data/golden/schema_imports_composed_order/expected/nast.txt +25 -0
  125. data/golden/schema_imports_composed_order/expected/schema_javascript.mjs +35 -0
  126. data/golden/schema_imports_composed_order/expected/schema_ruby.rb +33 -0
  127. data/golden/schema_imports_composed_order/expected/snast.txt +25 -0
  128. data/golden/schema_imports_composed_order/expected.json +6 -0
  129. data/golden/schema_imports_composed_order/input.json +5 -0
  130. data/golden/schema_imports_composed_order/schema.kumi +15 -0
  131. data/golden/schema_imports_discount_with_tax/expected/ast.txt +37 -0
  132. data/golden/schema_imports_discount_with_tax/expected/input_plan.txt +2 -0
  133. data/golden/schema_imports_discount_with_tax/expected/lir_00_unoptimized.txt +30 -0
  134. data/golden/schema_imports_discount_with_tax/expected/lir_01_hoist_scalar_references.txt +30 -0
  135. data/golden/schema_imports_discount_with_tax/expected/lir_02_inlined.txt +37 -0
  136. data/golden/schema_imports_discount_with_tax/expected/lir_03_cse.txt +34 -0
  137. data/golden/schema_imports_discount_with_tax/expected/lir_04_1_loop_fusion.txt +34 -0
  138. data/golden/schema_imports_discount_with_tax/expected/lir_04_loop_invcm.txt +34 -0
  139. data/golden/schema_imports_discount_with_tax/expected/lir_06_const_prop.txt +34 -0
  140. data/golden/schema_imports_discount_with_tax/expected/nast.txt +30 -0
  141. data/golden/schema_imports_discount_with_tax/expected/schema_javascript.mjs +37 -0
  142. data/golden/schema_imports_discount_with_tax/expected/schema_ruby.rb +34 -0
  143. data/golden/schema_imports_discount_with_tax/expected/snast.txt +30 -0
  144. data/golden/schema_imports_discount_with_tax/expected.json +7 -0
  145. data/golden/schema_imports_discount_with_tax/input.json +4 -0
  146. data/golden/schema_imports_discount_with_tax/schema.kumi +15 -0
  147. data/golden/schema_imports_line_items/expected/ast.txt +35 -0
  148. data/golden/schema_imports_line_items/expected/input_plan.txt +8 -0
  149. data/golden/schema_imports_line_items/expected/lir_00_unoptimized.txt +19 -0
  150. data/golden/schema_imports_line_items/expected/lir_01_hoist_scalar_references.txt +19 -0
  151. data/golden/schema_imports_line_items/expected/lir_02_inlined.txt +24 -0
  152. data/golden/schema_imports_line_items/expected/lir_03_cse.txt +22 -0
  153. data/golden/schema_imports_line_items/expected/lir_04_1_loop_fusion.txt +22 -0
  154. data/golden/schema_imports_line_items/expected/lir_04_loop_invcm.txt +22 -0
  155. data/golden/schema_imports_line_items/expected/lir_06_const_prop.txt +22 -0
  156. data/golden/schema_imports_line_items/expected/nast.txt +19 -0
  157. data/golden/schema_imports_line_items/expected/schema_javascript.mjs +23 -0
  158. data/golden/schema_imports_line_items/expected/schema_ruby.rb +22 -0
  159. data/golden/schema_imports_line_items/expected/snast.txt +19 -0
  160. data/golden/schema_imports_line_items/expected.json +5 -0
  161. data/golden/schema_imports_line_items/input.json +13 -0
  162. data/golden/schema_imports_line_items/schema.kumi +17 -0
  163. data/golden/schema_imports_multiple/expected/ast.txt +35 -0
  164. data/golden/schema_imports_multiple/expected/input_plan.txt +2 -0
  165. data/golden/schema_imports_multiple/expected/lir_00_unoptimized.txt +29 -0
  166. data/golden/schema_imports_multiple/expected/lir_01_hoist_scalar_references.txt +29 -0
  167. data/golden/schema_imports_multiple/expected/lir_02_inlined.txt +41 -0
  168. data/golden/schema_imports_multiple/expected/lir_03_cse.txt +37 -0
  169. data/golden/schema_imports_multiple/expected/lir_04_1_loop_fusion.txt +37 -0
  170. data/golden/schema_imports_multiple/expected/lir_04_loop_invcm.txt +37 -0
  171. data/golden/schema_imports_multiple/expected/lir_06_const_prop.txt +37 -0
  172. data/golden/schema_imports_multiple/expected/nast.txt +28 -0
  173. data/golden/schema_imports_multiple/expected/schema_javascript.mjs +40 -0
  174. data/golden/schema_imports_multiple/expected/schema_ruby.rb +37 -0
  175. data/golden/schema_imports_multiple/expected/snast.txt +28 -0
  176. data/golden/schema_imports_multiple/expected.json +7 -0
  177. data/golden/schema_imports_multiple/input.json +4 -0
  178. data/golden/schema_imports_multiple/schema.kumi +15 -0
  179. data/golden/schema_imports_nested_expressions/expected/ast.txt +31 -0
  180. data/golden/schema_imports_nested_expressions/expected/input_plan.txt +3 -0
  181. data/golden/schema_imports_nested_expressions/expected/lir_00_unoptimized.txt +22 -0
  182. data/golden/schema_imports_nested_expressions/expected/lir_01_hoist_scalar_references.txt +22 -0
  183. data/golden/schema_imports_nested_expressions/expected/lir_02_inlined.txt +32 -0
  184. data/golden/schema_imports_nested_expressions/expected/lir_03_cse.txt +32 -0
  185. data/golden/schema_imports_nested_expressions/expected/lir_04_1_loop_fusion.txt +32 -0
  186. data/golden/schema_imports_nested_expressions/expected/lir_04_loop_invcm.txt +32 -0
  187. data/golden/schema_imports_nested_expressions/expected/lir_06_const_prop.txt +28 -0
  188. data/golden/schema_imports_nested_expressions/expected/nast.txt +23 -0
  189. data/golden/schema_imports_nested_expressions/expected/schema_javascript.mjs +29 -0
  190. data/golden/schema_imports_nested_expressions/expected/schema_ruby.rb +28 -0
  191. data/golden/schema_imports_nested_expressions/expected/snast.txt +23 -0
  192. data/golden/schema_imports_nested_expressions/expected.json +5 -0
  193. data/golden/schema_imports_nested_expressions/input.json +5 -0
  194. data/golden/schema_imports_nested_expressions/schema.kumi +13 -0
  195. data/golden/schema_imports_nested_with_reductions/expected/ast.txt +47 -0
  196. data/golden/schema_imports_nested_with_reductions/expected/input_plan.txt +12 -0
  197. data/golden/schema_imports_nested_with_reductions/expected/lir_00_unoptimized.txt +31 -0
  198. data/golden/schema_imports_nested_with_reductions/expected/lir_01_hoist_scalar_references.txt +31 -0
  199. data/golden/schema_imports_nested_with_reductions/expected/lir_02_inlined.txt +58 -0
  200. data/golden/schema_imports_nested_with_reductions/expected/lir_03_cse.txt +49 -0
  201. data/golden/schema_imports_nested_with_reductions/expected/lir_04_1_loop_fusion.txt +51 -0
  202. data/golden/schema_imports_nested_with_reductions/expected/lir_04_loop_invcm.txt +49 -0
  203. data/golden/schema_imports_nested_with_reductions/expected/lir_06_const_prop.txt +49 -0
  204. data/golden/schema_imports_nested_with_reductions/expected/nast.txt +23 -0
  205. data/golden/schema_imports_nested_with_reductions/expected/schema_javascript.mjs +49 -0
  206. data/golden/schema_imports_nested_with_reductions/expected/schema_ruby.rb +52 -0
  207. data/golden/schema_imports_nested_with_reductions/expected/snast.txt +23 -0
  208. data/golden/schema_imports_nested_with_reductions/expected.json +6 -0
  209. data/golden/schema_imports_nested_with_reductions/input.json +16 -0
  210. data/golden/schema_imports_nested_with_reductions/schema.kumi +23 -0
  211. data/golden/schema_imports_with_imports/expected/ast.txt +19 -0
  212. data/golden/schema_imports_with_imports/expected/input_plan.txt +1 -0
  213. data/golden/schema_imports_with_imports/expected/lir_00_unoptimized.txt +13 -0
  214. data/golden/schema_imports_with_imports/expected/lir_01_hoist_scalar_references.txt +13 -0
  215. data/golden/schema_imports_with_imports/expected/lir_02_inlined.txt +14 -0
  216. data/golden/schema_imports_with_imports/expected/lir_03_cse.txt +13 -0
  217. data/golden/schema_imports_with_imports/expected/lir_04_1_loop_fusion.txt +13 -0
  218. data/golden/schema_imports_with_imports/expected/lir_04_loop_invcm.txt +13 -0
  219. data/golden/schema_imports_with_imports/expected/lir_06_const_prop.txt +13 -0
  220. data/golden/schema_imports_with_imports/expected/nast.txt +13 -0
  221. data/golden/schema_imports_with_imports/expected/schema_javascript.mjs +13 -0
  222. data/golden/schema_imports_with_imports/expected/schema_ruby.rb +13 -0
  223. data/golden/schema_imports_with_imports/expected/snast.txt +13 -0
  224. data/golden/schema_imports_with_imports/expected.json +4 -0
  225. data/golden/schema_imports_with_imports/input.json +3 -0
  226. data/golden/schema_imports_with_imports/schema.kumi +10 -0
  227. data/golden/shift/expected/lir_06_const_prop.txt +18 -30
  228. data/golden/shift/expected/schema_javascript.mjs +18 -30
  229. data/golden/shift/expected/schema_ruby.rb +25 -67
  230. data/golden/shift_2d/expected/lir_06_const_prop.txt +36 -60
  231. data/golden/shift_2d/expected/schema_javascript.mjs +36 -60
  232. data/golden/shift_2d/expected/schema_ruby.rb +49 -109
  233. data/golden/simple_math/expected/lir_06_const_prop.txt +3 -6
  234. data/golden/simple_math/expected/schema_javascript.mjs +3 -6
  235. data/golden/simple_math/expected/schema_ruby.rb +8 -39
  236. data/golden/streaming_basics/expected/lir_06_const_prop.txt +6 -12
  237. data/golden/streaming_basics/expected/schema_javascript.mjs +6 -12
  238. data/golden/streaming_basics/expected/schema_ruby.rb +14 -51
  239. data/golden/tuples/expected/lir_06_const_prop.txt +5 -22
  240. data/golden/tuples/expected/schema_javascript.mjs +5 -22
  241. data/golden/tuples/expected/schema_ruby.rb +11 -57
  242. data/golden/tuples_and_arrays/expected/lir_06_const_prop.txt +4 -8
  243. data/golden/tuples_and_arrays/expected/schema_javascript.mjs +4 -8
  244. data/golden/tuples_and_arrays/expected/schema_ruby.rb +9 -41
  245. data/golden/us_tax_2024/expected/lir_06_const_prop.txt +94 -171
  246. data/golden/us_tax_2024/expected/schema_javascript.mjs +13 -21
  247. data/golden/us_tax_2024/expected/schema_ruby.rb +15 -48
  248. data/golden/with_constants/expected/lir_06_const_prop.txt +3 -8
  249. data/golden/with_constants/expected/schema_javascript.mjs +3 -8
  250. data/golden/with_constants/expected/schema_ruby.rb +5 -35
  251. data/lib/kumi/analyzer.rb +8 -7
  252. data/lib/kumi/configuration.rb +7 -6
  253. data/lib/kumi/core/analyzer/passes/attach_anchors_pass.rb +1 -1
  254. data/lib/kumi/core/analyzer/passes/attach_terminal_info_pass.rb +1 -1
  255. data/lib/kumi/core/analyzer/passes/codegen/js/declaration_emitter.rb +20 -0
  256. data/lib/kumi/core/analyzer/passes/codegen/ruby/declaration_emitter.rb +16 -7
  257. data/lib/kumi/core/analyzer/passes/codegen/ruby/output_buffer.rb +3 -35
  258. data/lib/kumi/core/analyzer/passes/dependency_resolver.rb +6 -0
  259. data/lib/kumi/core/analyzer/passes/import_analysis_pass.rb +90 -0
  260. data/lib/kumi/core/analyzer/passes/lir/constant_propagation_pass.rb +77 -36
  261. data/lib/kumi/core/analyzer/passes/lir/lower_pass.rb +26 -11
  262. data/lib/kumi/core/analyzer/passes/name_indexer.rb +20 -2
  263. data/lib/kumi/core/analyzer/passes/nast_dimensional_analyzer_pass.rb +44 -0
  264. data/lib/kumi/core/analyzer/passes/normalize_to_nast_pass.rb +30 -0
  265. data/lib/kumi/core/analyzer/passes/semantic_constraint_validator.rb +5 -1
  266. data/lib/kumi/core/analyzer/passes/snast_pass.rb +15 -0
  267. data/lib/kumi/core/lir/build.rb +27 -0
  268. data/lib/kumi/core/lir/peephole.rb +164 -0
  269. data/lib/kumi/core/nast.rb +16 -0
  270. data/lib/kumi/core/ruby_parser/build_context.rb +3 -1
  271. data/lib/kumi/core/ruby_parser/parser.rb +1 -1
  272. data/lib/kumi/core/ruby_parser/schema_builder.rb +33 -3
  273. data/lib/kumi/dev/golden/result.rb +9 -3
  274. data/lib/kumi/dev/golden/runtime_test.rb +16 -1
  275. data/lib/kumi/dev/golden.rb +18 -20
  276. data/lib/kumi/dev/golden_schema_modules.rb +8 -0
  277. data/lib/kumi/dev/golden_schema_wrapper.rb +116 -0
  278. data/lib/kumi/dev/support/kumi_runner.mjs +18 -0
  279. data/lib/kumi/schema.rb +44 -2
  280. data/lib/kumi/support/lir_printer.rb +21 -5
  281. data/lib/kumi/support/nast_printer.rb +11 -0
  282. data/lib/kumi/support/s_expression_printer.rb +9 -0
  283. data/lib/kumi/support/snast_printer.rb +6 -0
  284. data/lib/kumi/syntax/import_call.rb +11 -0
  285. data/lib/kumi/syntax/import_declaration.rb +11 -0
  286. data/lib/kumi/syntax/root.rb +2 -2
  287. data/lib/kumi/test_shared_schemas/compound.rb +21 -0
  288. data/lib/kumi/test_shared_schemas/discount.rb +19 -0
  289. data/lib/kumi/test_shared_schemas/price.rb +19 -0
  290. data/lib/kumi/test_shared_schemas/subtotal.rb +22 -0
  291. data/lib/kumi/test_shared_schemas/tax.rb +18 -0
  292. data/lib/kumi/version.rb +1 -1
  293. data/lib/kumi.rb +19 -4
  294. metadata +176 -3
@@ -23,58 +23,99 @@ module Kumi
23
23
 
24
24
  def run(_errors)
25
25
  ops_by_decl = get_state(:lir_module)
26
- # out = {}
26
+ out = {}
27
27
 
28
- # ops_by_decl.each do |name, payload|
29
- # out[name] = { operations: optimize_decl(Array(payload[:operations])) }
30
- # end
28
+ ops_by_decl.each do |name, payload|
29
+ operations = Array(payload[:operations])
30
+ out[name] = { operations: optimize_decl(operations) }
31
+ end
31
32
 
32
- # TODO: - make it work with new gather/length
33
- # out.freeze
34
- out = ops_by_decl
35
- state.with(:lir_06_const_prop, out).with(:lir_module, out.freeze)
33
+ out.freeze
34
+ state.with(:lir_module, out).with(:lir_06_const_prop, out)
36
35
  end
37
36
 
38
37
  private
39
38
 
40
39
  def optimize_decl(ops)
41
- known_constants = {}
42
- new_ops = []
43
-
44
- ops.each do |ins|
45
- # --- START OF THE FIX ---
46
-
47
- new_inputs = []
48
- new_immediates = []
49
-
50
- # Process inputs, replacing known constants with a placeholder.
51
- Array(ins.inputs).each do |input_reg|
52
- if (literal = known_constants[input_reg])
53
- new_inputs << :__immediate_placeholder__
54
- new_immediates << literal
55
- else
56
- new_inputs << input_reg
40
+ rewritten = ops.map(&:dup)
41
+ use_counts = Hash.new(0)
42
+
43
+ rewritten.each do |ins|
44
+ Array(ins.inputs).each do |reg|
45
+ use_counts[reg] += 1 if reg
46
+ end
47
+ end
48
+
49
+ constants = {}
50
+
51
+ LIR::Peephole.run(rewritten) do |window|
52
+ ins = window.current
53
+ break unless ins
54
+
55
+ substitution = substitute_inputs(ins, constants)
56
+
57
+ if substitution
58
+ new_ins, replaced_regs = substitution
59
+ window.replace(1, with: new_ins)
60
+ replaced_regs.each do |reg|
61
+ use_counts[reg] -= 1
57
62
  end
58
63
  end
59
64
 
60
- # Append any existing immediates after the new ones.
61
- new_immediates.concat(Array(ins.immediates))
65
+ register = window.result_register
66
+ constants.delete(register) if register
67
+
68
+ if register && window.const?
69
+ literal = window.literal
70
+ constants[register] = literal if literal
71
+ end
72
+
73
+ window.skip
74
+ end
62
75
 
63
- modified_ins = ins.dup
64
- modified_ins.inputs = new_inputs
65
- modified_ins.immediates = new_immediates
66
- new_ops << modified_ins
76
+ rewritten.reject! do |ins|
77
+ reg = ins.result_register
78
+ ins.opcode == :Constant && reg && use_counts[reg].zero?
79
+ end
67
80
 
68
- # --- END OF THE FIX ---
81
+ rewritten
82
+ end
69
83
 
70
- # Update the map based on the MODIFIED instruction.
71
- known_constants.delete(modified_ins.result_register) if modified_ins.result_register
72
- if modified_ins.opcode == :Constant && modified_ins.immediates&.first
73
- known_constants[modified_ins.result_register] = modified_ins.immediates.first
84
+ def substitute_inputs(ins, constants)
85
+ original_inputs = Array(ins.inputs)
86
+ original_imms = Array(ins.immediates)
87
+
88
+ new_inputs = []
89
+ new_imms = []
90
+ changed = false
91
+ replaced_regs = []
92
+
93
+ original_inputs.each do |reg|
94
+ if (literal = constants[reg])
95
+ new_inputs << :__immediate_placeholder__
96
+ new_imms << literal
97
+ changed = true
98
+ replaced_regs << reg
99
+ else
100
+ new_inputs << reg
74
101
  end
75
102
  end
76
103
 
77
- new_ops
104
+ new_imms.concat(original_imms)
105
+ changed ||= new_imms != original_imms
106
+
107
+ return unless changed
108
+
109
+ instruction = LIR::Instruction.new(
110
+ opcode: ins.opcode,
111
+ result_register: ins.result_register,
112
+ stamp: ins.stamp,
113
+ inputs: new_inputs.empty? ? [] : new_inputs,
114
+ immediates: new_imms.empty? ? [] : new_imms,
115
+ attributes: ins.attributes,
116
+ location: ins.location
117
+ )
118
+ [instruction, replaced_regs]
78
119
  end
79
120
  end
80
121
  end
@@ -75,16 +75,17 @@ module Kumi
75
75
 
76
76
  def lower_expr(node)
77
77
  case node
78
- when NAST::Const then emit_const(node)
79
- when NAST::InputRef then emit_input_ref(node)
80
- when NAST::Ref then emit_ref(node)
81
- when NAST::Tuple then emit_tuple(node)
82
- when NAST::Select then emit_select(node)
83
- when NAST::Fold then emit_fold(node)
84
- when NAST::Reduce then emit_reduce(node)
85
- when NAST::Call then call_emit_selection(node)
86
- when NAST::Hash then emit_hash(node)
87
- when NAST::IndexRef then emit_index_ref(node)
78
+ when NAST::Const then emit_const(node)
79
+ when NAST::InputRef then emit_input_ref(node)
80
+ when NAST::Ref then emit_ref(node)
81
+ when NAST::Tuple then emit_tuple(node)
82
+ when NAST::Select then emit_select(node)
83
+ when NAST::Fold then emit_fold(node)
84
+ when NAST::Reduce then emit_reduce(node)
85
+ when NAST::Call then call_emit_selection(node)
86
+ when NAST::Hash then emit_hash(node)
87
+ when NAST::IndexRef then emit_index_ref(node)
88
+ when NAST::ImportCall then emit_import_call(node)
88
89
  else raise "unknown node #{node.class}"
89
90
  end
90
91
  end
@@ -139,6 +140,20 @@ module Kumi
139
140
  ins.result_register
140
141
  end
141
142
 
143
+ def emit_import_call(n)
144
+ regs = n.args.map { lower_expr(_1) }
145
+ ins = Build.import_schema_call(
146
+ fn_name: n.fn_name,
147
+ source_module: n.source_module,
148
+ args: regs,
149
+ input_mapping_keys: n.input_mapping_keys,
150
+ out_dtype: dtype_of(n),
151
+ ids: @ids
152
+ )
153
+ @ops << ins
154
+ ins.result_register
155
+ end
156
+
142
157
  def emit_select(n)
143
158
  ax = axes_of(n)
144
159
  ensure_context_for!(ax, anchor_fqn: anchor_fqn_from_node!(n, need_prefix: ax)) unless ax.empty?
@@ -321,7 +336,7 @@ module Kumi
321
336
  walk.call(x.on_false)
322
337
  when NAST::Reduce, NAST::Fold
323
338
  walk.call(x.arg)
324
- when NAST::Call, NAST::Tuple
339
+ when NAST::Call, NAST::Tuple, NAST::ImportCall
325
340
  x.args.each { walk.call(_1) }
326
341
  when NAST::Hash
327
342
  x.pairs.each { walk.call(_1) }
@@ -8,19 +8,37 @@ module Kumi
8
8
  # DEPENDENCIES: None (first pass in pipeline)
9
9
  # PRODUCES: :declarations - Hash mapping names to declaration nodes
10
10
  # - annotates hints to declarations (e.g. inlining)
11
+ # :imported_declarations - Hash of lazy import references
11
12
  # INTERFACE: new(schema, state).run(errors)
12
13
  class NameIndexer < PassBase
13
14
  def run(errors)
14
15
  definitions = {}
16
+ imported_declarations = {}
15
17
  hints = {}
16
18
 
19
+ # Phase 1: Register imports as lazy references
20
+ (schema.imports || []).each do |import_decl|
21
+ import_decl.names.each do |name|
22
+ imported_declarations[name] = {
23
+ type: :import,
24
+ from_module: import_decl.module_ref,
25
+ loc: import_decl.loc
26
+ }
27
+ end
28
+ end
29
+
30
+ # Phase 2: Index local declarations
17
31
  each_decl do |decl|
18
- report_error(errors, "duplicated definition `#{decl.name}`", location: decl.loc) if definitions.key?(decl.name)
32
+ if definitions.key?(decl.name) || imported_declarations.key?(decl.name)
33
+ report_error(errors, "duplicated definition `#{decl.name}`", location: decl.loc)
34
+ end
19
35
  definitions[decl.name] = decl
20
36
  hints[decl.name] = decl.hints
21
37
  end
22
38
 
23
- state.with(:declarations, definitions.freeze).with(:hints, hints)
39
+ state.with(:declarations, definitions.freeze)
40
+ .with(:imported_declarations, imported_declarations.freeze)
41
+ .with(:hints, hints)
24
42
  end
25
43
  end
26
44
  end
@@ -54,6 +54,7 @@ module Kumi
54
54
  def analyze_expression(expr, errors)
55
55
  case expr
56
56
  when Kumi::Core::NAST::Call then analyze_call_expression(expr, errors)
57
+ when Kumi::Core::NAST::ImportCall then analyze_import_call(expr, errors)
57
58
  when Kumi::Core::NAST::Tuple then analyze_tuple(expr, errors)
58
59
  when Kumi::Core::NAST::InputRef then analyze_input_ref(expr)
59
60
  when Kumi::Core::NAST::IndexRef then analyze_index_ref(expr, errors)
@@ -163,6 +164,49 @@ module Kumi
163
164
  { type: result_type, scope: result_scope }
164
165
  end
165
166
 
167
+ def analyze_import_call(call, errors)
168
+ # Analyze arguments
169
+ arg_metadata = call.args.map { |arg| analyze_expression(arg, errors) }
170
+ arg_types = arg_metadata.map { |m| m[:type] }
171
+ arg_scopes = arg_metadata.map { |m| m[:scope] }
172
+
173
+ # Get the imported schemas from state
174
+ imported_schemas = get_state(:imported_schemas, required: false) || {}
175
+ import_meta = imported_schemas[call.fn_name]
176
+
177
+ unless import_meta
178
+ report_error(errors, "imported function `#{call.fn_name}` not found", location: call.loc)
179
+ return { type: Types.scalar(:any), scope: [] }
180
+ end
181
+
182
+ # Get the analyzed state of the source schema
183
+ analyzed_state = import_meta[:analyzed_state]
184
+ src_declaration_table = analyzed_state[:declaration_table] || {}
185
+
186
+ # Look up the imported declaration in the source schema
187
+ src_decl_meta = src_declaration_table[call.fn_name]
188
+ unless src_decl_meta
189
+ report_error(errors, "declaration `#{call.fn_name}` not found in imported schema", location: call.loc)
190
+ return { type: Types.scalar(:any), scope: [] }
191
+ end
192
+
193
+ result_type = src_decl_meta[:result_type] || Types.scalar(:any)
194
+ # ImportCall broadcasts over argument scopes (like elementwise functions)
195
+ result_scope = lub_by_prefix(arg_scopes)
196
+
197
+ @metadata_table[node_id(call)] = {
198
+ kind: :import_call,
199
+ result_type: result_type,
200
+ result_scope: result_scope,
201
+ imported_fn: call.fn_name,
202
+ arg_types: arg_types,
203
+ arg_scopes: arg_scopes
204
+ }.freeze
205
+
206
+ debug " ImportCall #{call.fn_name}: -> #{result_type} in #{result_scope.inspect}"
207
+ { type: result_type, scope: result_scope }
208
+ end
209
+
166
210
  def analyze_tuple(node, errors)
167
211
  elems = node.args.map { |e| analyze_expression(e, errors) }
168
212
  element_types = elems.map { |m| m[:type] }
@@ -42,6 +42,9 @@ module Kumi
42
42
  when Kumi::Syntax::DeclarationReference
43
43
  NAST::Ref.new(name: node.name, loc: node.loc)
44
44
 
45
+ when Kumi::Syntax::ImportCall
46
+ normalize_import_call(node, errors)
47
+
45
48
  when Kumi::Syntax::CallExpression
46
49
  # Special handling section - very clear what's happening
47
50
  case node.fn_name
@@ -158,6 +161,33 @@ module Kumi
158
161
  NAST::Call.new(fn: :"core.and", args: [left, right], loc: loc)
159
162
  end
160
163
  end
164
+
165
+ def normalize_import_call(node, errors)
166
+ imported_schemas = get_state(:imported_schemas, required: false) || {}
167
+
168
+ import_meta = imported_schemas[node.fn_name]
169
+ unless import_meta
170
+ add_error(errors, node.loc, "imported function `#{node.fn_name}` not found in imported_schemas")
171
+ return NAST::Const.new(value: nil, loc: node.loc)
172
+ end
173
+
174
+ # Don't inline the source expression. Instead, create an NAST::ImportCall node
175
+ # that represents a call to the compiled schema function.
176
+ # The compiled schema handles its own internal dependencies.
177
+
178
+ # Normalize the arguments (the values being passed)
179
+ args = node.input_mapping.map do |param_name, caller_expr|
180
+ normalize_expr(caller_expr, errors)
181
+ end
182
+
183
+ NAST::ImportCall.new(
184
+ fn_name: node.fn_name,
185
+ args: args,
186
+ input_mapping_keys: node.input_mapping.keys,
187
+ source_module: import_meta[:source_module],
188
+ loc: node.loc
189
+ )
190
+ end
161
191
  end
162
192
  end
163
193
  end
@@ -89,7 +89,11 @@ module Kumi
89
89
  skip = [:cascade_and] # TODO: - hack
90
90
  return if skip.include? fn_name
91
91
 
92
- # binding.pry
92
+ # Skip validation for imported functions - they're pure schema methods
93
+ imported_schemas = state[:imported_schemas] || {}
94
+ return if imported_schemas.key?(fn_name)
95
+
96
+ # Check if it's a built-in function in the registry
93
97
  return if @registry.resolve_function(fn_name)
94
98
 
95
99
  report_error(
@@ -180,6 +180,21 @@ module Kumi
180
180
  stamp!(out, m[:result_scope], m[:result_type])
181
181
  end
182
182
 
183
+ def visit_import_call(n)
184
+ args = n.args.map { _1.accept(self) }
185
+ m = meta_for(n)
186
+ out = n.class.new(
187
+ id: n.id,
188
+ fn_name: n.fn_name,
189
+ args: args,
190
+ input_mapping_keys: n.input_mapping_keys,
191
+ source_module: n.source_module,
192
+ loc: n.loc,
193
+ meta: n.meta.dup
194
+ )
195
+ stamp!(out, m[:result_scope], m[:result_type])
196
+ end
197
+
183
198
  # ---------- Helpers ----------
184
199
 
185
200
  def stamp!(node, axes, dtype)
@@ -146,6 +146,33 @@ module Kumi
146
146
  )
147
147
  end
148
148
 
149
+ # ImportSchemaCall
150
+ # Params:
151
+ # fn_name: imported function name
152
+ # source_module: the module containing the imported schema
153
+ # args: argument registers (mapped parameter values)
154
+ # input_mapping_keys:keys of input mapping in order of args
155
+ # out_dtype: dtype of the result
156
+ # as: result register
157
+ # location: optional Location
158
+ # Result: produces
159
+ def import_schema_call(fn_name:, source_module:, args:, input_mapping_keys:, out_dtype:, as: nil, ids: nil, location: nil)
160
+ as ||= ids.generate_temp
161
+ Instruction.new(
162
+ opcode: :ImportSchemaCall,
163
+ result_register: as,
164
+ stamp: Stamp.new(dtype: out_dtype),
165
+ inputs: args,
166
+ immediates: [],
167
+ attributes: {
168
+ fn_name: fn_name.to_s,
169
+ source_module: source_module.to_s,
170
+ input_mapping_keys: Array(input_mapping_keys).map(&:to_s)
171
+ },
172
+ location:
173
+ )
174
+ end
175
+
149
176
  # Fold
150
177
  # Params:
151
178
  # function: String function id (e.g., "core.mul")
@@ -0,0 +1,164 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Core
5
+ module LIR
6
+ # Peephole provides a small helper for implementing local LIR rewrites.
7
+ # It iterates over an instruction array and yields a Window object that
8
+ # exposes the current instruction alongside a handful of mutation helpers.
9
+ #
10
+ # Example:
11
+ # Peephole.run(ops) do |window|
12
+ # next unless window.match?(:Constant, :Constant, :KernelCall)
13
+ #
14
+ # last = window.instruction(2)
15
+ # const = Build.constant(
16
+ # value: 3,
17
+ # dtype: last.stamp.dtype,
18
+ # as: last.result_register,
19
+ # ids: ids
20
+ # )
21
+ # window.replace(3, with: const)
22
+ # end
23
+ class Peephole
24
+ attr_reader :ops
25
+
26
+ def self.run(ops, &)
27
+ new(ops).run(&)
28
+ end
29
+
30
+ def initialize(ops)
31
+ @ops = ops
32
+ end
33
+
34
+ def run
35
+ index = 0
36
+ while index < @ops.length
37
+ window = Window.new(@ops, index)
38
+ yield window
39
+ index = window.next_index
40
+ end
41
+ @ops
42
+ end
43
+
44
+ # A mutable view over the instruction stream anchored at a given index.
45
+ class Window
46
+ attr_reader :index
47
+
48
+ def initialize(ops, index)
49
+ @ops = ops
50
+ @index = index
51
+ @next_index = index + 1
52
+ end
53
+
54
+ def current = instruction(0)
55
+
56
+ def instruction(offset = 0) = @ops[@index + offset]
57
+
58
+ def opcode(offset = 0)
59
+ instruction(offset)&.opcode
60
+ end
61
+
62
+ def instructions(count)
63
+ @ops[@index, count].compact
64
+ end
65
+
66
+ def match?(*opcodes)
67
+ opcodes.each_with_index.all? do |opcode, off|
68
+ ins = instruction(off)
69
+ ins&.respond_to?(:opcode) && ins.opcode == opcode
70
+ end
71
+ end
72
+
73
+ def result_register(offset = 0)
74
+ instruction(offset)&.result_register
75
+ end
76
+
77
+ def const?(offset = 0, value: nil)
78
+ ins = instruction(offset)
79
+ return false unless ins&.opcode == :Constant
80
+
81
+ return true if value.nil?
82
+
83
+ literal_value(offset) == value
84
+ end
85
+
86
+ def zero?(offset = 0)
87
+ const?(offset, value: 0)
88
+ end
89
+
90
+ def literal(offset = 0)
91
+ ins = instruction(offset)
92
+ Array(ins&.immediates).first
93
+ end
94
+
95
+ def literal_value(offset = 0)
96
+ literal(offset)&.value
97
+ end
98
+
99
+ def replace(count, with:)
100
+ replacements = normalize(with)
101
+ @ops[@index, count] = replacements
102
+ @next_index = @index
103
+ replacements
104
+ end
105
+
106
+ def delete(count = 1)
107
+ replace(count, with: [])
108
+ end
109
+
110
+ def insert_before(*new_ops)
111
+ new_ops = normalize(new_ops)
112
+ return @next_index = @index if new_ops.empty?
113
+
114
+ @ops.insert(@index, *new_ops)
115
+ @index += new_ops.length
116
+ @next_index = @index
117
+ new_ops
118
+ end
119
+
120
+ def insert_after(*new_ops)
121
+ new_ops = normalize(new_ops)
122
+ return @next_index = @index + 1 if new_ops.empty?
123
+
124
+ @ops.insert(@index + 1, *new_ops)
125
+ @next_index = @index + 1
126
+ new_ops
127
+ end
128
+
129
+ def skip(count = 1)
130
+ count = 1 if count.nil? || count < 1
131
+ @next_index = @index + count
132
+ nil
133
+ end
134
+
135
+ def rewind(count = 1)
136
+ count = 1 if count.nil? || count < 1
137
+ @next_index = [@index - count, 0].max
138
+ nil
139
+ end
140
+
141
+ def next_index
142
+ @next_index = 0 if @next_index.negative?
143
+ @next_index = @ops.length if @next_index > @ops.length
144
+ @next_index
145
+ end
146
+
147
+ def size
148
+ @ops.length
149
+ end
150
+
151
+ private
152
+
153
+ def normalize(value)
154
+ if value.is_a?(Array)
155
+ value.compact
156
+ else
157
+ value ? [value] : []
158
+ end
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
@@ -101,6 +101,22 @@ module Kumi
101
101
  end
102
102
  end
103
103
 
104
+ class ImportCall < Node
105
+ attr_reader :fn_name, :args, :input_mapping_keys, :source_module
106
+
107
+ def initialize(fn_name:, args:, input_mapping_keys:, source_module:, **k)
108
+ super(**k)
109
+ @fn_name = fn_name.to_sym
110
+ @args = args
111
+ @input_mapping_keys = input_mapping_keys
112
+ @source_module = source_module
113
+ end
114
+
115
+ def accept(visitor)
116
+ visitor.respond_to?(:visit_import_call) ? visitor.visit_import_call(self) : super
117
+ end
118
+ end
119
+
104
120
  class Tuple < Node
105
121
  attr_reader :args
106
122
 
@@ -4,13 +4,15 @@ module Kumi
4
4
  module Core
5
5
  module RubyParser
6
6
  class BuildContext
7
- attr_reader :inputs, :values, :traits
7
+ attr_reader :inputs, :values, :traits, :imports, :imported_names
8
8
  attr_accessor :current_location
9
9
 
10
10
  def initialize
11
11
  @inputs = []
12
12
  @values = []
13
13
  @traits = []
14
+ @imports = []
15
+ @imported_names = Set.new
14
16
  @input_block_defined = false
15
17
  end
16
18
 
@@ -45,7 +45,7 @@ module Kumi
45
45
  end
46
46
 
47
47
  def build_syntax_tree
48
- Root.new(@context.inputs, @context.values, @context.traits)
48
+ Root.new(@context.inputs, @context.values, @context.traits, @context.imports)
49
49
  end
50
50
 
51
51
  def handle_parse_error(error)
@@ -8,7 +8,7 @@ module Kumi
8
8
  include Syntax
9
9
  include ErrorReporting
10
10
 
11
- DSL_METHODS = %i[value trait input ref literal fn select shift roll].freeze
11
+ DSL_METHODS = %i[value trait input ref literal fn select shift roll import].freeze
12
12
 
13
13
  def initialize(context)
14
14
  @context = context
@@ -57,10 +57,31 @@ module Kumi
57
57
  Kumi::Syntax::Literal.new(value, loc: @context.current_location)
58
58
  end
59
59
 
60
+ def import(*names, from:)
61
+ update_location
62
+
63
+ unless from.is_a?(Module) || from.is_a?(Class)
64
+ raise_syntax_error(
65
+ "import 'from:' must reference a module or class",
66
+ location: @context.current_location
67
+ )
68
+ end
69
+
70
+ import_decl = Kumi::Syntax::ImportDeclaration.new(names, from, loc: @context.current_location)
71
+ @context.imports << import_decl
72
+ @context.imported_names.merge(names)
73
+ end
74
+
60
75
  def fn(fn_name, *args, **kwargs)
61
76
  update_location
62
- expr_args = args.map { ensure_syntax(_1) }
63
- Kumi::Syntax::CallExpression.new(fn_name, expr_args, kwargs, loc: @context.current_location)
77
+
78
+ if args.empty? && !kwargs.empty? && @context.imported_names.include?(fn_name)
79
+ mapping = build_import_mapping(kwargs)
80
+ Kumi::Syntax::ImportCall.new(fn_name, mapping, loc: @context.current_location)
81
+ else
82
+ expr_args = args.map { ensure_syntax(_1) }
83
+ Kumi::Syntax::CallExpression.new(fn_name, expr_args, kwargs, loc: @context.current_location)
84
+ end
64
85
  end
65
86
 
66
87
  def select(condition, value_when_true, value_when_false)
@@ -97,6 +118,15 @@ module Kumi
97
118
 
98
119
  private
99
120
 
121
+ def build_import_mapping(kwargs)
122
+ converter = ExpressionConverter.new(@context)
123
+ mapping = {}
124
+ kwargs.each do |field_name, expr|
125
+ mapping[field_name] = converter.ensure_syntax(expr)
126
+ end
127
+ mapping
128
+ end
129
+
100
130
  def update_location
101
131
  # Use caller_locations(2, 1) to skip the DSL method and get the actual user code location
102
132
  # Stack: [0] update_location, [1] DSL method (value/trait/etc), [2] user's DSL code