kumi 0.0.36 → 0.1.0

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 (544) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +39 -58
  3. data/.rubocop_todo.yml +931 -0
  4. data/CHANGELOG.md +39 -2
  5. data/README.md +75 -1
  6. data/data/functions/core/arithmetic.yaml +48 -0
  7. data/data/functions/core/conversion.yaml +13 -0
  8. data/data/functions/core/select.yaml +1 -1
  9. data/data/functions/core/stencil.yaml +32 -0
  10. data/data/kernels/javascript/agg/numeric.yaml +1 -0
  11. data/data/kernels/javascript/core/arithmetic.yaml +24 -0
  12. data/data/kernels/javascript/core/coercion.yaml +18 -4
  13. data/data/kernels/ruby/agg/numeric.yaml +2 -1
  14. data/data/kernels/ruby/core/arithmetic.yaml +31 -3
  15. data/data/kernels/ruby/core/coercion.yaml +8 -0
  16. data/docs/CROSS_TARGET_SEMANTICS.md +99 -0
  17. data/docs/FORM_SCHEMA.md +13 -8
  18. data/docs/FUNCTIONS.md +120 -13
  19. data/docs/INPUTS.md +164 -0
  20. data/docs/OUTPUT_SCHEMA.md +12 -8
  21. data/docs/PASSES.md +76 -0
  22. data/docs/PASS_AUDIT.md +179 -0
  23. data/docs/PORTAL.md +39 -0
  24. data/docs/SYNTAX.md +101 -120
  25. data/docs/SYNTAX_NOTES.md +612 -0
  26. data/docs/UNSAT_DETECTION.md +2 -2
  27. data/docs/functions-reference.json +276 -84
  28. data/docs/pairwise-design.md +125 -0
  29. data/docs/superpowers/plans/2026-06-12-pass-conventions-and-dedup.md +1559 -0
  30. data/docs/superpowers/specs/2026-06-12-pass-conventions-and-dedup-design.md +136 -0
  31. data/golden/algebraic_identities/expected/ast.txt +66 -0
  32. data/golden/algebraic_identities/expected/dfir.txt +47 -0
  33. data/golden/algebraic_identities/expected/dfir_optimized.txt +59 -0
  34. data/golden/algebraic_identities/expected/input_plan.txt +7 -0
  35. data/golden/algebraic_identities/expected/loopir.txt +61 -0
  36. data/golden/algebraic_identities/expected/nast.txt +51 -0
  37. data/golden/algebraic_identities/expected/runtime.json +24 -0
  38. data/golden/algebraic_identities/expected/schema_javascript.mjs +89 -0
  39. data/golden/algebraic_identities/expected/schema_ruby.rb +84 -0
  40. data/golden/algebraic_identities/expected/snast.txt +51 -0
  41. data/golden/algebraic_identities/expected/vecir.txt +47 -0
  42. data/golden/algebraic_identities/input.json +6 -0
  43. data/golden/algebraic_identities/schema.kumi +25 -0
  44. data/golden/array_element/expected/loopir.txt +1 -2
  45. data/golden/array_element/expected/runtime.json +5 -0
  46. data/golden/array_element/expected/schema_javascript.mjs +1 -2
  47. data/golden/array_element/expected/schema_ruby.rb +2 -3
  48. data/golden/array_index/expected/runtime.json +54 -0
  49. data/golden/array_index/expected/schema_ruby.rb +1 -1
  50. data/golden/array_operations/{expected.json → expected/runtime.json} +5 -15
  51. data/golden/array_operations/expected/schema_ruby.rb +1 -1
  52. data/golden/cascade_logic/expected/runtime.json +3 -0
  53. data/golden/cascade_logic/expected/schema_ruby.rb +1 -1
  54. data/golden/cascade_reuse_peephole/expected/ast.txt +193 -0
  55. data/golden/cascade_reuse_peephole/expected/dfir.txt +124 -0
  56. data/golden/cascade_reuse_peephole/expected/dfir_optimized.txt +320 -0
  57. data/golden/cascade_reuse_peephole/expected/input_plan.txt +2 -0
  58. data/golden/cascade_reuse_peephole/expected/loopir.txt +302 -0
  59. data/golden/cascade_reuse_peephole/expected/nast.txt +188 -0
  60. data/golden/cascade_reuse_peephole/expected/runtime.json +8 -0
  61. data/golden/cascade_reuse_peephole/expected/schema_javascript.mjs +335 -0
  62. data/golden/cascade_reuse_peephole/expected/schema_ruby.rb +337 -0
  63. data/golden/cascade_reuse_peephole/expected/snast.txt +188 -0
  64. data/golden/cascade_reuse_peephole/expected/vecir.txt +302 -0
  65. data/golden/cascade_reuse_peephole/input.json +4 -0
  66. data/golden/cascade_reuse_peephole/schema.kumi +31 -0
  67. data/golden/chained_fusion/expected/loopir.txt +5 -13
  68. data/golden/chained_fusion/expected/runtime.json +45 -0
  69. data/golden/chained_fusion/expected/schema_javascript.mjs +5 -13
  70. data/golden/chained_fusion/expected/schema_ruby.rb +6 -14
  71. data/golden/cross_import/expected/ast.txt +21 -0
  72. data/golden/cross_import/expected/dfir.txt +3 -0
  73. data/golden/cross_import/expected/dfir_optimized.txt +8 -0
  74. data/golden/cross_import/expected/input_plan.txt +5 -0
  75. data/golden/cross_import/expected/loopir.txt +14 -0
  76. data/golden/cross_import/expected/nast.txt +7 -0
  77. data/golden/cross_import/expected/runtime.json +10 -0
  78. data/golden/cross_import/expected/schema_javascript.mjs +19 -0
  79. data/golden/cross_import/expected/schema_ruby.rb +19 -0
  80. data/golden/cross_import/expected/snast.txt +7 -0
  81. data/golden/cross_import/expected/vecir.txt +8 -0
  82. data/golden/cross_import/input.json +1 -0
  83. data/golden/cross_import/schema.kumi +15 -0
  84. data/golden/cross_let/expected/ast.txt +62 -0
  85. data/golden/cross_let/expected/dfir.txt +35 -0
  86. data/golden/cross_let/expected/dfir_optimized.txt +51 -0
  87. data/golden/cross_let/expected/input_plan.txt +5 -0
  88. data/golden/cross_let/expected/loopir.txt +100 -0
  89. data/golden/cross_let/expected/nast.txt +46 -0
  90. data/golden/cross_let/expected/runtime.json +61 -0
  91. data/golden/cross_let/expected/schema_javascript.mjs +136 -0
  92. data/golden/cross_let/expected/schema_ruby.rb +123 -0
  93. data/golden/cross_let/expected/snast.txt +46 -0
  94. data/golden/cross_let/expected/vecir.txt +51 -0
  95. data/golden/cross_let/input.json +7 -0
  96. data/golden/cross_let/schema.kumi +25 -0
  97. data/golden/decimal_explicit/expected/dfir.txt +6 -6
  98. data/golden/decimal_explicit/expected/dfir_optimized.txt +6 -6
  99. data/golden/decimal_explicit/expected/loopir.txt +6 -6
  100. data/golden/decimal_explicit/expected/runtime.json +10 -0
  101. data/golden/decimal_explicit/expected/schema_ruby.rb +1 -1
  102. data/golden/decimal_explicit/expected/snast.txt +9 -9
  103. data/golden/decimal_explicit/expected/vecir.txt +6 -6
  104. data/golden/element_arrays/expected/loopir.txt +6 -13
  105. data/golden/element_arrays/expected/runtime.json +117 -0
  106. data/golden/element_arrays/expected/schema_javascript.mjs +7 -14
  107. data/golden/element_arrays/expected/schema_ruby.rb +8 -15
  108. data/golden/empty_and_null_inputs/expected/loopir.txt +4 -9
  109. data/golden/empty_and_null_inputs/expected/runtime.json +8 -0
  110. data/golden/empty_and_null_inputs/expected/schema_javascript.mjs +5 -10
  111. data/golden/empty_and_null_inputs/expected/schema_ruby.rb +6 -11
  112. data/golden/example_xpto/expected/runtime.json +4 -0
  113. data/golden/example_xpto/expected/schema_ruby.rb +1 -1
  114. data/golden/function_overload/expected/runtime.json +8 -0
  115. data/golden/function_overload/expected/schema_ruby.rb +1 -1
  116. data/golden/game_of_life/expected/loopir.txt +1 -58
  117. data/golden/game_of_life/expected/runtime.json +418 -0
  118. data/golden/game_of_life/expected/schema_javascript.mjs +1 -58
  119. data/golden/game_of_life/expected/schema_ruby.rb +2 -59
  120. data/golden/hash_keys/expected/runtime.json +20 -0
  121. data/golden/hash_keys/expected/schema_ruby.rb +1 -1
  122. data/golden/hash_value/expected/runtime.json +19 -0
  123. data/golden/hash_value/expected/schema_ruby.rb +1 -1
  124. data/golden/hierarchical_complex/expected/loopir.txt +0 -4
  125. data/golden/hierarchical_complex/expected/runtime.json +16 -0
  126. data/golden/hierarchical_complex/expected/schema_javascript.mjs +0 -4
  127. data/golden/hierarchical_complex/expected/schema_ruby.rb +1 -5
  128. data/golden/inline_rename_scope_leak/expected/loopir.txt +1 -5
  129. data/golden/inline_rename_scope_leak/expected/runtime.json +10 -0
  130. data/golden/inline_rename_scope_leak/expected/schema_javascript.mjs +3 -7
  131. data/golden/inline_rename_scope_leak/expected/schema_ruby.rb +4 -8
  132. data/golden/input_reference/expected/dfir.txt +2 -2
  133. data/golden/input_reference/expected/dfir_optimized.txt +2 -2
  134. data/golden/input_reference/expected/loopir.txt +2 -5
  135. data/golden/input_reference/expected/runtime.json +15 -0
  136. data/golden/input_reference/expected/schema_javascript.mjs +3 -6
  137. data/golden/input_reference/expected/schema_ruby.rb +4 -7
  138. data/golden/input_reference/expected/snast.txt +1 -1
  139. data/golden/input_reference/expected/vecir.txt +2 -2
  140. data/golden/interleaved_fusion/expected/loopir.txt +5 -10
  141. data/golden/interleaved_fusion/expected/runtime.json +37 -0
  142. data/golden/interleaved_fusion/expected/schema_javascript.mjs +5 -10
  143. data/golden/interleaved_fusion/expected/schema_ruby.rb +6 -11
  144. data/golden/let_inline/expected/runtime.json +6 -0
  145. data/golden/let_inline/expected/schema_ruby.rb +2 -2
  146. data/golden/loop_fusion/expected/loopir.txt +3 -7
  147. data/golden/loop_fusion/expected/runtime.json +29 -0
  148. data/golden/loop_fusion/expected/schema_javascript.mjs +3 -7
  149. data/golden/loop_fusion/expected/schema_ruby.rb +4 -8
  150. data/golden/min_max_empty_arrays/expected/loopir.txt +5 -12
  151. data/golden/min_max_empty_arrays/expected/runtime.json +16 -0
  152. data/golden/min_max_empty_arrays/expected/schema_javascript.mjs +7 -14
  153. data/golden/min_max_empty_arrays/expected/schema_ruby.rb +10 -17
  154. data/golden/min_reduce_scope/expected/loopir.txt +3 -7
  155. data/golden/min_reduce_scope/expected/runtime.json +11 -0
  156. data/golden/min_reduce_scope/expected/schema_javascript.mjs +4 -8
  157. data/golden/min_reduce_scope/expected/schema_ruby.rb +5 -9
  158. data/golden/mixed_dimensions/expected/loopir.txt +2 -5
  159. data/golden/mixed_dimensions/expected/runtime.json +34 -0
  160. data/golden/mixed_dimensions/expected/schema_javascript.mjs +3 -6
  161. data/golden/mixed_dimensions/expected/schema_ruby.rb +4 -7
  162. data/golden/mlp_backprop/expected/ast.txt +147 -0
  163. data/golden/mlp_backprop/expected/dfir.txt +99 -0
  164. data/golden/mlp_backprop/expected/dfir_optimized.txt +374 -0
  165. data/golden/mlp_backprop/expected/input_plan.txt +17 -0
  166. data/golden/mlp_backprop/expected/loopir.txt +440 -0
  167. data/golden/mlp_backprop/expected/nast.txt +120 -0
  168. data/golden/mlp_backprop/expected/runtime.json +43 -0
  169. data/golden/mlp_backprop/expected/schema_javascript.mjs +516 -0
  170. data/golden/mlp_backprop/expected/schema_ruby.rb +484 -0
  171. data/golden/mlp_backprop/expected/snast.txt +120 -0
  172. data/golden/mlp_backprop/expected/vecir.txt +374 -0
  173. data/golden/mlp_backprop/input.json +8 -0
  174. data/golden/mlp_backprop/schema.kumi +43 -0
  175. data/golden/multi_loop_reduction/expected/loopir.txt +2 -6
  176. data/golden/multi_loop_reduction/expected/runtime.json +5 -0
  177. data/golden/multi_loop_reduction/expected/schema_javascript.mjs +4 -8
  178. data/golden/multi_loop_reduction/expected/schema_ruby.rb +5 -9
  179. data/golden/multirank_hoisting/expected/loopir.txt +24 -42
  180. data/golden/multirank_hoisting/expected/runtime.json +47 -0
  181. data/golden/multirank_hoisting/expected/schema_javascript.mjs +24 -45
  182. data/golden/multirank_hoisting/expected/schema_ruby.rb +25 -43
  183. data/golden/nested_hash/expected/runtime.json +3 -0
  184. data/golden/nested_hash/expected/schema_ruby.rb +1 -1
  185. data/golden/outer_let/expected/ast.txt +106 -0
  186. data/golden/outer_let/expected/dfir.txt +66 -0
  187. data/golden/outer_let/expected/dfir_optimized.txt +145 -0
  188. data/golden/outer_let/expected/input_plan.txt +19 -0
  189. data/golden/outer_let/expected/loopir.txt +166 -0
  190. data/golden/outer_let/expected/nast.txt +78 -0
  191. data/golden/outer_let/expected/runtime.json +89 -0
  192. data/golden/outer_let/expected/schema_javascript.mjs +220 -0
  193. data/golden/outer_let/expected/schema_ruby.rb +201 -0
  194. data/golden/outer_let/expected/snast.txt +78 -0
  195. data/golden/outer_let/expected/vecir.txt +145 -0
  196. data/golden/outer_let/input.json +9 -0
  197. data/golden/outer_let/schema.kumi +32 -0
  198. data/golden/pairwise_cross/expected/ast.txt +62 -0
  199. data/golden/pairwise_cross/expected/dfir.txt +36 -0
  200. data/golden/pairwise_cross/expected/dfir_optimized.txt +50 -0
  201. data/golden/pairwise_cross/expected/input_plan.txt +8 -0
  202. data/golden/pairwise_cross/expected/loopir.txt +84 -0
  203. data/golden/pairwise_cross/expected/nast.txt +46 -0
  204. data/golden/pairwise_cross/expected/runtime.json +80 -0
  205. data/golden/pairwise_cross/expected/schema_javascript.mjs +113 -0
  206. data/golden/pairwise_cross/expected/schema_ruby.rb +104 -0
  207. data/golden/pairwise_cross/expected/snast.txt +46 -0
  208. data/golden/pairwise_cross/expected/vecir.txt +50 -0
  209. data/golden/pairwise_cross/input.json +8 -0
  210. data/golden/pairwise_cross/schema.kumi +18 -0
  211. data/golden/reduction_broadcast/expected/loopir.txt +4 -10
  212. data/golden/reduction_broadcast/expected/runtime.json +14 -0
  213. data/golden/reduction_broadcast/expected/schema_javascript.mjs +4 -10
  214. data/golden/reduction_broadcast/expected/schema_ruby.rb +5 -11
  215. data/golden/roll/expected/loopir.txt +4 -8
  216. data/golden/roll/expected/runtime.json +26 -0
  217. data/golden/roll/expected/schema_javascript.mjs +4 -8
  218. data/golden/roll/expected/schema_ruby.rb +5 -9
  219. data/golden/schema_imports_broadcasting_with_imports/expected/dfir.txt +3 -3
  220. data/golden/schema_imports_broadcasting_with_imports/expected/dfir_optimized.txt +3 -3
  221. data/golden/schema_imports_broadcasting_with_imports/expected/loopir.txt +3 -4
  222. data/golden/schema_imports_broadcasting_with_imports/expected/runtime.json +11 -0
  223. data/golden/schema_imports_broadcasting_with_imports/expected/schema_javascript.mjs +2 -3
  224. data/golden/schema_imports_broadcasting_with_imports/expected/schema_ruby.rb +3 -4
  225. data/golden/schema_imports_broadcasting_with_imports/expected/snast.txt +5 -5
  226. data/golden/schema_imports_broadcasting_with_imports/expected/vecir.txt +3 -3
  227. data/golden/schema_imports_complex_order_calc/expected/dfir.txt +20 -20
  228. data/golden/schema_imports_complex_order_calc/expected/dfir_optimized.txt +60 -60
  229. data/golden/schema_imports_complex_order_calc/expected/loopir.txt +34 -47
  230. data/golden/schema_imports_complex_order_calc/expected/runtime.json +33 -0
  231. data/golden/schema_imports_complex_order_calc/expected/schema_javascript.mjs +16 -29
  232. data/golden/schema_imports_complex_order_calc/expected/schema_ruby.rb +17 -30
  233. data/golden/schema_imports_complex_order_calc/expected/snast.txt +26 -26
  234. data/golden/schema_imports_complex_order_calc/expected/vecir.txt +60 -60
  235. data/golden/schema_imports_composed_order/expected/dfir.txt +3 -3
  236. data/golden/schema_imports_composed_order/expected/dfir_optimized.txt +6 -6
  237. data/golden/schema_imports_composed_order/expected/loopir.txt +6 -6
  238. data/golden/schema_imports_composed_order/expected/runtime.json +9 -0
  239. data/golden/schema_imports_composed_order/expected/schema_ruby.rb +1 -1
  240. data/golden/schema_imports_composed_order/expected/snast.txt +5 -5
  241. data/golden/schema_imports_composed_order/expected/vecir.txt +6 -6
  242. data/golden/schema_imports_discount_with_tax/expected/dfir.txt +7 -7
  243. data/golden/schema_imports_discount_with_tax/expected/dfir_optimized.txt +10 -10
  244. data/golden/schema_imports_discount_with_tax/expected/loopir.txt +10 -10
  245. data/golden/schema_imports_discount_with_tax/expected/runtime.json +10 -0
  246. data/golden/schema_imports_discount_with_tax/expected/schema_ruby.rb +1 -1
  247. data/golden/schema_imports_discount_with_tax/expected/snast.txt +11 -11
  248. data/golden/schema_imports_discount_with_tax/expected/vecir.txt +10 -10
  249. data/golden/schema_imports_line_items/expected/dfir.txt +1 -1
  250. data/golden/schema_imports_line_items/expected/dfir_optimized.txt +3 -3
  251. data/golden/schema_imports_line_items/expected/loopir.txt +3 -6
  252. data/golden/schema_imports_line_items/expected/runtime.json +8 -0
  253. data/golden/schema_imports_line_items/expected/schema_javascript.mjs +4 -7
  254. data/golden/schema_imports_line_items/expected/schema_ruby.rb +5 -8
  255. data/golden/schema_imports_line_items/expected/snast.txt +1 -1
  256. data/golden/schema_imports_line_items/expected/vecir.txt +3 -3
  257. data/golden/schema_imports_multiple/expected/dfir.txt +7 -7
  258. data/golden/schema_imports_multiple/expected/dfir_optimized.txt +13 -13
  259. data/golden/schema_imports_multiple/expected/loopir.txt +13 -13
  260. data/golden/schema_imports_multiple/expected/runtime.json +10 -0
  261. data/golden/schema_imports_multiple/expected/schema_ruby.rb +1 -1
  262. data/golden/schema_imports_multiple/expected/snast.txt +11 -11
  263. data/golden/schema_imports_multiple/expected/vecir.txt +13 -13
  264. data/golden/schema_imports_nested_expressions/expected/dfir.txt +4 -4
  265. data/golden/schema_imports_nested_expressions/expected/dfir_optimized.txt +6 -6
  266. data/golden/schema_imports_nested_expressions/expected/loopir.txt +6 -6
  267. data/golden/schema_imports_nested_expressions/expected/runtime.json +8 -0
  268. data/golden/schema_imports_nested_expressions/expected/schema_ruby.rb +1 -1
  269. data/golden/schema_imports_nested_expressions/expected/snast.txt +6 -6
  270. data/golden/schema_imports_nested_expressions/expected/vecir.txt +6 -6
  271. data/golden/schema_imports_nested_with_reductions/expected/dfir.txt +6 -6
  272. data/golden/schema_imports_nested_with_reductions/expected/dfir_optimized.txt +15 -15
  273. data/golden/schema_imports_nested_with_reductions/expected/loopir.txt +7 -14
  274. data/golden/schema_imports_nested_with_reductions/expected/runtime.json +12 -0
  275. data/golden/schema_imports_nested_with_reductions/expected/schema_javascript.mjs +8 -15
  276. data/golden/schema_imports_nested_with_reductions/expected/schema_ruby.rb +9 -16
  277. data/golden/schema_imports_nested_with_reductions/expected/snast.txt +6 -6
  278. data/golden/schema_imports_nested_with_reductions/expected/vecir.txt +15 -15
  279. data/golden/schema_imports_with_imports/expected/dfir.txt +3 -3
  280. data/golden/schema_imports_with_imports/expected/dfir_optimized.txt +3 -3
  281. data/golden/schema_imports_with_imports/expected/loopir.txt +3 -3
  282. data/golden/schema_imports_with_imports/expected/runtime.json +7 -0
  283. data/golden/schema_imports_with_imports/expected/schema_ruby.rb +1 -1
  284. data/golden/schema_imports_with_imports/expected/snast.txt +5 -5
  285. data/golden/schema_imports_with_imports/expected/vecir.txt +3 -3
  286. data/golden/shift/expected/loopir.txt +4 -8
  287. data/golden/shift/expected/runtime.json +38 -0
  288. data/golden/shift/expected/schema_javascript.mjs +4 -8
  289. data/golden/shift/expected/schema_ruby.rb +5 -9
  290. data/golden/shift_2d/expected/loopir.txt +8 -16
  291. data/golden/shift_2d/expected/runtime.json +146 -0
  292. data/golden/shift_2d/expected/schema_javascript.mjs +8 -16
  293. data/golden/shift_2d/expected/schema_ruby.rb +9 -17
  294. data/golden/simple_math/expected/runtime.json +10 -0
  295. data/golden/simple_math/expected/schema_ruby.rb +1 -1
  296. data/golden/streaming_basics/expected/loopir.txt +0 -3
  297. data/golden/streaming_basics/expected/runtime.json +25 -0
  298. data/golden/streaming_basics/expected/schema_javascript.mjs +3 -6
  299. data/golden/streaming_basics/expected/schema_ruby.rb +4 -7
  300. data/golden/transcendentals/expected/ast.txt +48 -0
  301. data/golden/transcendentals/expected/dfir.txt +31 -0
  302. data/golden/transcendentals/expected/dfir_optimized.txt +34 -0
  303. data/golden/transcendentals/expected/input_plan.txt +5 -0
  304. data/golden/transcendentals/expected/loopir.txt +46 -0
  305. data/golden/transcendentals/expected/nast.txt +34 -0
  306. data/golden/transcendentals/expected/runtime.json +27 -0
  307. data/golden/transcendentals/expected/schema_javascript.mjs +66 -0
  308. data/golden/transcendentals/expected/schema_ruby.rb +63 -0
  309. data/golden/transcendentals/expected/snast.txt +34 -0
  310. data/golden/transcendentals/expected/vecir.txt +34 -0
  311. data/golden/transcendentals/input.json +1 -0
  312. data/golden/transcendentals/schema.kumi +16 -0
  313. data/golden/tuples/expected/dfir.txt +4 -4
  314. data/golden/tuples/expected/runtime.json +12 -0
  315. data/golden/tuples/expected/schema_ruby.rb +1 -1
  316. data/golden/tuples_and_arrays/expected/dfir.txt +1 -1
  317. data/golden/tuples_and_arrays/expected/loopir.txt +0 -1
  318. data/golden/tuples_and_arrays/expected/runtime.json +13 -0
  319. data/golden/tuples_and_arrays/expected/schema_javascript.mjs +1 -2
  320. data/golden/tuples_and_arrays/expected/schema_ruby.rb +2 -3
  321. data/golden/type_promotion/expected/ast.txt +75 -0
  322. data/golden/type_promotion/expected/dfir.txt +38 -0
  323. data/golden/type_promotion/expected/dfir_optimized.txt +51 -0
  324. data/golden/type_promotion/expected/input_plan.txt +9 -0
  325. data/golden/type_promotion/expected/loopir.txt +57 -0
  326. data/golden/type_promotion/expected/nast.txt +57 -0
  327. data/golden/type_promotion/expected/runtime.json +23 -0
  328. data/golden/type_promotion/expected/schema_javascript.mjs +90 -0
  329. data/golden/type_promotion/expected/schema_ruby.rb +89 -0
  330. data/golden/type_promotion/expected/snast.txt +57 -0
  331. data/golden/type_promotion/expected/vecir.txt +51 -0
  332. data/golden/type_promotion/input.json +11 -0
  333. data/golden/type_promotion/schema.kumi +30 -0
  334. data/golden/us_tax_2024/expected/dfir.txt +6 -6
  335. data/golden/us_tax_2024/expected/loopir.txt +108 -217
  336. data/golden/us_tax_2024/expected/runtime.json +423 -0
  337. data/golden/us_tax_2024/expected/schema_javascript.mjs +108 -235
  338. data/golden/us_tax_2024/expected/schema_ruby.rb +109 -218
  339. data/golden/vector_make_object/expected/schema_ruby.rb +1 -1
  340. data/golden/with_constants/expected/schema_ruby.rb +1 -1
  341. data/lib/kumi/analyzer.rb +29 -34
  342. data/lib/kumi/configuration.rb +31 -0
  343. data/lib/kumi/core/analyzer/binder.rb +2 -2
  344. data/lib/kumi/core/analyzer/macro_expander.rb +3 -3
  345. data/lib/kumi/core/analyzer/pass_failure.rb +1 -1
  346. data/lib/kumi/core/analyzer/pass_manager.rb +211 -100
  347. data/lib/kumi/core/analyzer/passes/attach_anchors_pass.rb +44 -15
  348. data/lib/kumi/core/analyzer/passes/attach_terminal_info_pass.rb +6 -20
  349. data/lib/kumi/core/analyzer/passes/codegen/loop/js/emitter.rb +252 -14
  350. data/lib/kumi/core/analyzer/passes/codegen/loop/ruby/emitter.rb +6 -3
  351. data/lib/kumi/core/analyzer/passes/codegen/loop_js_pass.rb +5 -1
  352. data/lib/kumi/core/analyzer/passes/codegen/loop_ruby_pass.rb +3 -0
  353. data/lib/kumi/core/analyzer/passes/constant_folding_pass.rb +3 -0
  354. data/lib/kumi/core/analyzer/passes/contract_checker_pass.rb +1 -1
  355. data/lib/kumi/core/analyzer/passes/declaration_validator_pass.rb +83 -0
  356. data/lib/kumi/core/analyzer/passes/{dependency_resolver.rb → dependency_resolver_pass.rb} +8 -6
  357. data/lib/kumi/core/analyzer/passes/df_validate_pass.rb +3 -13
  358. data/lib/kumi/core/analyzer/passes/import_analysis_pass.rb +7 -2
  359. data/lib/kumi/core/analyzer/passes/input_access_planner_pass.rb +4 -28
  360. data/lib/kumi/core/analyzer/passes/{input_collector.rb → input_collector_pass.rb} +30 -3
  361. data/lib/kumi/core/analyzer/passes/input_form_schema_pass.rb +4 -1
  362. data/lib/kumi/core/analyzer/passes/ir_execution_schedule_pass.rb +4 -2
  363. data/lib/kumi/core/analyzer/passes/ir_lower_pass.rb +35 -0
  364. data/lib/kumi/core/analyzer/passes/ir_validate_pass.rb +43 -0
  365. data/lib/kumi/core/analyzer/passes/{load_input_cse.rb → load_input_cse_pass.rb} +1 -1
  366. data/lib/kumi/core/analyzer/passes/loop/lower_pass.rb +23 -10
  367. data/lib/kumi/core/analyzer/passes/loop_validate_pass.rb +2 -10
  368. data/lib/kumi/core/analyzer/passes/lower_to_dfir_pass.rb +4 -2
  369. data/lib/kumi/core/analyzer/passes/{name_indexer.rb → name_indexer_pass.rb} +3 -7
  370. data/lib/kumi/core/analyzer/passes/nast_dimensional_analyzer_pass.rb +174 -23
  371. data/lib/kumi/core/analyzer/passes/normalize_to_nast_pass.rb +19 -3
  372. data/lib/kumi/core/analyzer/passes/output_schema_pass.rb +3 -0
  373. data/lib/kumi/core/analyzer/passes/pass_base.rb +78 -5
  374. data/lib/kumi/core/analyzer/passes/precompute_access_paths_pass.rb +7 -1
  375. data/lib/kumi/core/analyzer/passes/{semantic_constraint_validator.rb → semantic_constraint_validator_pass.rb} +7 -14
  376. data/lib/kumi/core/analyzer/passes/snast_pass.rb +22 -48
  377. data/lib/kumi/core/analyzer/passes/{toposorter.rb → toposorter_pass.rb} +4 -5
  378. data/lib/kumi/core/analyzer/passes/{unsat_detector.rb → unsat_detector_pass.rb} +4 -15
  379. data/lib/kumi/core/analyzer/passes/vec/lower_pass.rb +6 -8
  380. data/lib/kumi/core/analyzer/passes/vec_validate_pass.rb +2 -10
  381. data/lib/kumi/core/analyzer/structs/input_meta.rb +1 -1
  382. data/lib/kumi/core/compiler/access_builder.rb +1 -1
  383. data/lib/kumi/core/compiler/access_codegen.rb +1 -1
  384. data/lib/kumi/core/compiler/access_emit/base.rb +1 -1
  385. data/lib/kumi/core/compiler/access_planner_v2.rb +2 -2
  386. data/lib/kumi/core/compiler/accessors/each_indexed_accessor.rb +1 -1
  387. data/lib/kumi/core/compiler/accessors/materialize_accessor.rb +1 -1
  388. data/lib/kumi/core/compiler/accessors/ravel_accessor.rb +1 -1
  389. data/lib/kumi/core/compiler/accessors/read_accessor.rb +1 -1
  390. data/lib/kumi/core/error_reporter.rb +6 -11
  391. data/lib/kumi/core/errors.rb +37 -11
  392. data/lib/kumi/core/expression_renderer.rb +97 -0
  393. data/lib/kumi/core/functions/overload_resolver.rb +79 -149
  394. data/lib/kumi/core/ir/execution_engine/combinators.rb +16 -10
  395. data/lib/kumi/core/nast.rb +24 -0
  396. data/lib/kumi/core/ruby_parser/build_context.rb +6 -1
  397. data/lib/kumi/core/ruby_parser/dsl.rb +12 -2
  398. data/lib/kumi/core/ruby_parser/dsl_cascade_builder.rb +14 -12
  399. data/lib/kumi/core/ruby_parser/guard_rails.rb +15 -1
  400. data/lib/kumi/core/ruby_parser/input_builder.rb +55 -48
  401. data/lib/kumi/core/ruby_parser/parser.rb +26 -18
  402. data/lib/kumi/core/ruby_parser/schema_builder.rb +60 -15
  403. data/lib/kumi/core/ruby_parser/sugar.rb +0 -86
  404. data/lib/kumi/core/types/dtype_rule.rb +76 -0
  405. data/lib/kumi/core/types/normalizer.rb +2 -2
  406. data/lib/kumi/core/types/profile.rb +82 -0
  407. data/lib/kumi/core/types/registry.rb +87 -0
  408. data/lib/kumi/core/types/system.rb +96 -0
  409. data/lib/kumi/core/types/value_objects.rb +14 -13
  410. data/lib/kumi/core/types.rb +41 -34
  411. data/lib/kumi/dev/golden_runtime.rb +163 -0
  412. data/lib/kumi/dev/golden_v2.rb +30 -5
  413. data/lib/kumi/dev/pretty_printer.rb +40 -73
  414. data/lib/kumi/frontends/ruby.rb +52 -12
  415. data/lib/kumi/frontends/source_frame.rb +71 -0
  416. data/lib/kumi/frontends/text.rb +4 -48
  417. data/lib/kumi/function_registry/loader.rb +101 -0
  418. data/lib/kumi/function_registry.rb +150 -0
  419. data/lib/kumi/ir/base/block.rb +11 -2
  420. data/lib/kumi/ir/base/builder.rb +1 -1
  421. data/lib/kumi/ir/base/function.rb +2 -4
  422. data/lib/kumi/ir/base/instruction.rb +10 -23
  423. data/lib/kumi/ir/base/module.rb +5 -4
  424. data/lib/kumi/ir/buf/lower.rb +1 -2
  425. data/lib/kumi/ir/df/access_contract.rb +3 -5
  426. data/lib/kumi/ir/df/import_inliner.rb +2 -5
  427. data/lib/kumi/ir/df/lower.rb +72 -51
  428. data/lib/kumi/ir/df/ops/axis_cross.rb +34 -0
  429. data/lib/kumi/ir/df/ops/axis_outer.rb +35 -0
  430. data/lib/kumi/ir/df/passes/broadcast_simplify.rb +1 -2
  431. data/lib/kumi/ir/df/passes/cse.rb +8 -3
  432. data/lib/kumi/ir/df/passes/decl_inlining.rb +5 -26
  433. data/lib/kumi/ir/df/passes/import_inlining.rb +42 -36
  434. data/lib/kumi/ir/df/passes/load_dedup.rb +9 -4
  435. data/lib/kumi/ir/df/passes/stencil_cse.rb +2 -5
  436. data/lib/kumi/ir/df/passes/support/instruction_cloner.rb +41 -14
  437. data/lib/kumi/ir/df/passes/tuple_fold_canonicalization.rb +8 -3
  438. data/lib/kumi/ir/df/passes/tuple_to_object.rb +1 -2
  439. data/lib/kumi/ir/df/validator.rb +3 -3
  440. data/lib/kumi/ir/df.rb +8 -0
  441. data/lib/kumi/ir/loop/lower.rb +231 -37
  442. data/lib/kumi/ir/loop/passes/array_contraction.rb +124 -0
  443. data/lib/kumi/ir/loop/passes/copy_cleanup.rb +112 -0
  444. data/lib/kumi/ir/loop/passes/loop_fusion.rb +125 -0
  445. data/lib/kumi/ir/loop/passes/support/structure.rb +100 -0
  446. data/lib/kumi/ir/loop/passes.rb +14 -0
  447. data/lib/kumi/ir/loop/pipeline.rb +9 -1
  448. data/lib/kumi/ir/loop/validator.rb +29 -4
  449. data/lib/kumi/ir/loop.rb +1 -0
  450. data/lib/kumi/ir/passes/register_generator.rb +32 -0
  451. data/lib/kumi/ir/testing/snast_factory.rb +2 -4
  452. data/lib/kumi/ir/vec/lower.rb +17 -7
  453. data/lib/kumi/ir/vec/ops/core_ops.rb +41 -0
  454. data/lib/kumi/ir/vec/passes/axis_canonicalization.rb +1 -2
  455. data/lib/kumi/ir/vec/passes/constant_propagation.rb +75 -47
  456. data/lib/kumi/ir/vec/passes/dce.rb +2 -5
  457. data/lib/kumi/ir/vec/passes/gvn.rb +8 -3
  458. data/lib/kumi/ir/vec/passes/peephole_simplify.rb +30 -13
  459. data/lib/kumi/ir/vec/passes/stencil_detection.rb +1 -2
  460. data/lib/kumi/ir/vec/passes/support/algebraic_identities.rb +70 -0
  461. data/lib/kumi/ir/vec/passes/support/instruction_cloner.rb +17 -3
  462. data/lib/kumi/ir/vec/validator.rb +26 -6
  463. data/lib/kumi/schema.rb +14 -10
  464. data/lib/kumi/schema_metadata/printer.rb +111 -0
  465. data/lib/kumi/schema_metadata.rb +289 -0
  466. data/lib/kumi/syntax/location.rb +14 -1
  467. data/lib/kumi/syntax/root.rb +35 -3
  468. data/lib/kumi/test_shared_schemas/pairwise.rb +27 -0
  469. data/lib/kumi/version.rb +1 -1
  470. data/lib/kumi.rb +3 -5
  471. data/tasks/docs_portal.rake +135 -0
  472. metadata +203 -81
  473. data/golden/array_element/expected.json +0 -5
  474. data/golden/array_index/expected.json +0 -5
  475. data/golden/cascade_logic/expected.json +0 -5
  476. data/golden/chained_fusion/expected.json +0 -45
  477. data/golden/decimal_explicit/expected.json +0 -1
  478. data/golden/element_arrays/expected.json +0 -55
  479. data/golden/empty_and_null_inputs/expected.json +0 -8
  480. data/golden/example_xpto/expected.json +0 -4
  481. data/golden/game_of_life/expected.json +0 -3
  482. data/golden/hash_keys/expected.json +0 -20
  483. data/golden/hash_value/expected.json +0 -19
  484. data/golden/hierarchical_complex/expected.json +0 -34
  485. data/golden/inline_rename_scope_leak/expected.json +0 -7
  486. data/golden/input_reference/expected.json +0 -7
  487. data/golden/interleaved_fusion/expected.json +0 -37
  488. data/golden/let_inline/expected.json +0 -1
  489. data/golden/loop_fusion/expected.json +0 -30
  490. data/golden/min_max_empty_arrays/expected.json +0 -7
  491. data/golden/min_reduce_scope/expected.json +0 -9
  492. data/golden/mixed_dimensions/expected.json +0 -6
  493. data/golden/multi_loop_reduction/expected.json +0 -5
  494. data/golden/multirank_hoisting/expected.json +0 -15
  495. data/golden/nested_hash/expected.json +0 -3
  496. data/golden/reduction_broadcast/expected.json +0 -25
  497. data/golden/roll/expected.json +0 -6
  498. data/golden/schema_imports_broadcasting_with_imports/expected.json +0 -4
  499. data/golden/schema_imports_complex_order_calc/expected.json +0 -12
  500. data/golden/schema_imports_composed_order/expected.json +0 -6
  501. data/golden/schema_imports_discount_with_tax/expected.json +0 -7
  502. data/golden/schema_imports_line_items/expected.json +0 -5
  503. data/golden/schema_imports_multiple/expected.json +0 -7
  504. data/golden/schema_imports_nested_expressions/expected.json +0 -5
  505. data/golden/schema_imports_nested_with_reductions/expected.json +0 -6
  506. data/golden/schema_imports_with_imports/expected.json +0 -4
  507. data/golden/shift/expected.json +0 -8
  508. data/golden/shift_2d/expected.json +0 -15
  509. data/golden/simple_math/expected.json +0 -1
  510. data/golden/streaming_basics/expected.json +0 -10
  511. data/golden/tuples/expected.json +0 -7
  512. data/golden/tuples_and_arrays/expected.json +0 -18
  513. data/golden/us_tax_2024/expected.json +0 -120
  514. data/lib/kumi/core/analyzer/passes/assemble_irv2_pass.rb +0 -130
  515. data/lib/kumi/core/analyzer/passes/declaration_validator.rb +0 -45
  516. data/lib/kumi/core/analyzer/passes/lower_to_irv2_pass.rb +0 -197
  517. data/lib/kumi/core/compiler/access_planner.rb +0 -258
  518. data/lib/kumi/core/functions/function_spec.rb +0 -17
  519. data/lib/kumi/core/functions/loader.rb +0 -63
  520. data/lib/kumi/core/functions/type_categories.rb +0 -44
  521. data/lib/kumi/core/functions/type_error_reporter.rb +0 -116
  522. data/lib/kumi/core/functions/type_rules.rb +0 -228
  523. data/lib/kumi/core/irv2/builder.rb +0 -48
  524. data/lib/kumi/core/irv2/declaration.rb +0 -28
  525. data/lib/kumi/core/irv2/module.rb +0 -108
  526. data/lib/kumi/core/irv2/value.rb +0 -28
  527. data/lib/kumi/core/types/validator.rb +0 -33
  528. data/lib/kumi/dev/codegen.rb +0 -194
  529. data/lib/kumi/dev/golden/generator.rb +0 -109
  530. data/lib/kumi/dev/golden/reporter.rb +0 -169
  531. data/lib/kumi/dev/golden/representation.rb +0 -40
  532. data/lib/kumi/dev/golden/result.rb +0 -106
  533. data/lib/kumi/dev/golden/runtime_test.rb +0 -131
  534. data/lib/kumi/dev/golden/suite.rb +0 -147
  535. data/lib/kumi/dev/golden/value_normalizer.rb +0 -78
  536. data/lib/kumi/dev/golden/verifier.rb +0 -76
  537. data/lib/kumi/dev/golden.rb +0 -82
  538. data/lib/kumi/dev/golden_schema_wrapper.rb +0 -116
  539. data/lib/kumi/dev/printer/irv2_formatter.rb +0 -163
  540. data/lib/kumi/kernel_registry.rb +0 -59
  541. data/lib/kumi/pack/builder.rb +0 -229
  542. data/lib/kumi/pack.rb +0 -15
  543. data/lib/kumi/registry_v2/loader.rb +0 -170
  544. data/lib/kumi/registry_v2.rb +0 -135
data/CHANGELOG.md CHANGED
@@ -1,8 +1,45 @@
1
1
  ## [Unreleased]
2
2
 
3
- ## [0.0.36] – 2026-06-11
3
+ ## [0.1.0] – 2026-06-20
4
+ ### Added
5
+ - **Parser rewrite integration** (kumi-parser ≥ 0.1.0): new lexer/parser/grammar with located, framed parse errors. Semantic and parse errors now render on one `file:line:col` format; no more `?:?`.
6
+ - **Cross-target semantics doc + parity tests** (`docs/CROSS_TARGET_SEMANTICS.md`): pinned Ruby/JS contracts for `to_string(float)`, `to_integer`/`to_float` of strings, and `pow` with a negative base and fractional exponent, each backed by a spec running Ruby against real node JS.
7
+ - **Cross-references** beyond plain input fields: cross-axis (`A × A'`) iteration in Loop lowering, usable through `let`.
8
+ - **Optimization passes**: algebraic identity folding (`x*1`, `x/1`, `x-0`, integer `x+0`/`x*0`) in Vec constant propagation and a Loop-layer copy-cleanup pass.
9
+ - `SchemaMetadata`: the schema's algebra exposed as read-only data.
10
+
11
+ ### Changed
12
+ - **Type system consolidated** under one `Core::Types` namespace (Registry/Profile/System/DtypeRule) with a per-target `Profile` seam; decimal > float > integer promotion lattice.
13
+ - Compiler invariants, backend capability limits, and registry/schema/IR faults are now typed errors that break loudly instead of silently degrading.
14
+ - `__select__` condition is typed `boolean`; the JS Loop emitter supports `impl`-only kernels (emitted once as a module-level helper), mirroring Ruby.
15
+
16
+ ### Fixed
17
+ - `to_string(float)`, string `to_integer`/`to_float`, and `pow` of a negative base produced divergent results between Ruby and JS; kernels rewritten so both targets agree.
18
+ - Typo'd input paths (`input.nope`) report a clean located user error instead of a compiler bug deep in dimensional analysis.
19
+ - `PeepholeSimplify` could drop a def while keeping a stale use.
20
+
21
+ ### Removed
22
+ - Vestigial `return_stamp` IR field, dead IRv2/pack tooling, and duplicated `RegisterGenerator`/terminal-instruction lookups across IR passes.
23
+
24
+ ## [0.0.40] – 2026-06-15
25
+ ### Added
26
+ - LoopIR optimization pipeline (`Kumi::IR::Loop::Pipeline`), previously empty, now runs two passes on every compile:
27
+ - **LoopFusion** — merges sibling loops over the same axis and source into one traversal, hoisting independent scalar barriers above the loop. Stencil consumers (shifts, lengths, re-iteration of a partially built array) and accumulator reads correctly block fusion.
28
+ - **ArrayContraction** — replaces intermediate arrays that are filled and read back at the same index within one loop with the scalar that fills them, eliminating the materialization entirely.
29
+ - `outer(...)` usable from text schemas (requires kumi-parser ≥ 0.0.33, now the pinned version). Golden `outer_let` covers the text path with Ruby/JS parity.
30
+
31
+ ### Changed
32
+ - Streaming exports throw a `TypeError` when the target aliases an input array (silent zero-length corruption before; feedback loops must double-buffer) and when a typed-array target is passed for record outputs (silently produced an empty buffer before).
33
+ - Array input arity errors now explain the element rule (Kumi maps over arrays by default, so the element must be named) and show the correct single-child form, instead of a cryptic type error.
34
+ - The schema digest (compiled module name + compile-cache key) no longer folds in `RUBY_VERSION`. Generated code is plain Ruby with identical semantics across supported Rubies, so the digest is now Ruby-version-independent; this also lets the codegen goldens verify across the full CI Ruby matrix.
35
+
4
36
  ### Fixed
5
- - Keep `rdoc` as a development-only dependency so Ruby 3.1 installs do not resolve `erb >= 6`, which requires Ruby 3.2+.
37
+ - Ruby DSL `let` raised `ArgumentError: struct size differs` since 0.0.37 (`inline: true` leaked into the `ValueDeclaration` struct instead of the `hints:` channel; the text frontend was unaffected).
38
+ - `outer`/`cross` value used through a `let` that lives purely on the inner pairing axis (no dependence on the outer-consuming axis) read `nil` past the inner array's length (`nil can't be coerced into Float`). The materialized read indexed by positional loop depth instead of by the value's own axis; it now matches by axis. Affected both Ruby and JS codegen.
39
+
40
+ ## [0.0.37] – 2026-06-11
41
+ ### Added
42
+ - Streaming hints for JS codegen
6
43
 
7
44
  ## [0.0.35] – 2026-06-11
8
45
  ### Added
data/README.md CHANGED
@@ -93,18 +93,92 @@ Double.write_source("output.mjs", platform: :javascript)
93
93
  You can also override the compilation strategy without touching code by setting
94
94
  `KUMI_COMPILATION_MODE` to `jit` or `aot` (e.g. `export KUMI_COMPILATION_MODE=aot`).
95
95
 
96
+ ## Composing Schemas
97
+
98
+ Schemas can import other schemas — and the compiler **inlines everything at compile time**. The generated code has no runtime dependency on the imported module; imported logic participates in broadcasting and loop fusion like any locally written expression.
99
+
100
+ ```ruby
101
+ module TaxPolicy
102
+ extend Kumi::Schema
103
+
104
+ schema do
105
+ input { decimal :amount }
106
+ value :tax, input.amount * 0.15
107
+ end
108
+ end
109
+
110
+ module Order
111
+ extend Kumi::Schema
112
+
113
+ schema do
114
+ import :tax, from: TaxPolicy
115
+
116
+ input do
117
+ array :items do
118
+ hash :item do
119
+ decimal :price
120
+ end
121
+ end
122
+ end
123
+
124
+ # TaxPolicy only knows a scalar :amount — passing a vectorized
125
+ # argument applies it per item automatically.
126
+ value :item_taxes, fn(:tax, amount: input.items.item.price)
127
+ value :total_tax, fn(:sum, item_taxes)
128
+ end
129
+ end
130
+
131
+ Order.from(items: [{ price: 100 }, { price: 200 }, { price: 300 }])[:total_tax]
132
+ # => 90.0
133
+ ```
134
+
135
+ The generated JavaScript for `total_tax` shows what "inline everything" means — the imported tax rule **and** the `sum` reduction are fused into one loop, with no call to `TaxPolicy`, no intermediate array, and no runtime to ship:
136
+
137
+ ```js
138
+ export function _total_tax(input) {
139
+ let t13 = input["items"];
140
+ let acc18 = 0.0;
141
+ for (let items_i15 = 0; items_i15 < t13.length; items_i15++) {
142
+ let items_el14 = t13[items_i15];
143
+ let t16 = items_el14["price"];
144
+ let t17 = 0.15;
145
+ let t12 = t16 * t17;
146
+ acc18 += t12;
147
+ }
148
+ let t6 = acc18;
149
+ return t6;
150
+ }
151
+ ```
152
+
153
+ See [Schema Imports](docs/SCHEMA_IMPORTS.md) for renamed inputs, multiple imports, and imports over nested arrays.
154
+
155
+ ## Performance
156
+
157
+ Compiled schemas are straight-line code with fused loops — there is no interpreter, rule engine, or library call in the artifact, so the runtime cost is whatever the host language charges for a `for` loop.
158
+
159
+ Two playground examples make this tangible:
160
+
161
+ - **[Payroll at Scale](https://kumi-play-web.fly.dev/?example=payroll-at-scale)** — a full payroll run (overtime split, progressive withholding bands, ten aggregates) over **10,000 employees in ~0.2 ms per call** in desktop Chrome. Press **Benchmark ×100** in the Execute tab to measure median / p95 on your own machine.
162
+ - **[Game of Life XL](https://kumi-play-web.fly.dev/?example=life-xl)** — Conway's rules over an **83,000-cell grid**, stepping live in a worker with the nine neighbor reads fused into a single pass — millions of cell updates per second, rendered in real time with a live counter.
163
+
96
164
  ## Examples
97
165
 
98
166
  - **US Tax Calculator (2024)** — a single schema computes federal, state, and FICA taxes across multiple filing statuses. [Open in the demo](https://kumi-play-web.fly.dev/?example=us-federal-tax-2024).
167
+ - **Payroll at Scale** — overtime, withholding bands, and aggregates over 10,000 employees with per-run timing. [Open in the demo](https://kumi-play-web.fly.dev/?example=payroll-at-scale).
99
168
  - **Monte Carlo Portfolio** — probabilistic simulations and table visualizations. [Open in the demo](https://kumi-play-web.fly.dev/?example=monte-carlo-simulation).
100
- - **Conway's Game of Life** — array operations powering a grid-based simulation. [Open in the demo](https://kumi-play-web.fly.dev/?example=game-of-life).
169
+ - **Conway's Game of Life** — array operations powering a grid-based simulation ([XL version](https://kumi-play-web.fly.dev/?example=life-xl) runs 83k cells live). [Open in the demo](https://kumi-play-web.fly.dev/?example=game-of-life).
170
+
171
+ The playground's example picker groups these by theme (language tour, business logic, scale & speed, simulations) — the [Language Intro](https://kumi-play-web.fly.dev/?example=language-intro) is the best starting point.
101
172
 
102
173
  ---
103
174
 
104
175
  ## Documentation
105
176
 
106
177
  - **[Syntax Reference](docs/SYNTAX.md)** — DSL syntax, types, operators, functions
178
+ - **[Syntax Notes](docs/SYNTAX_NOTES.md)** — parser differences, nested input recipes, expression literals, and post-parse errors
179
+ - **[Input Shapes](docs/INPUTS.md)** — declaring scalars, arrays, hashes, and the element rule
107
180
  - **[Functions Reference](docs/FUNCTIONS.md)** — auto-generated docs for all functions and kernels ([machine-readable JSON](docs/functions-reference.json))
181
+ - **[Cross-Target Semantics](docs/CROSS_TARGET_SEMANTICS.md)** — where Ruby and JS would diverge (float `to_string`, string conversions, `pow`) and how the kernels keep them identical
108
182
  - **[Schema Imports](docs/SCHEMA_IMPORTS.md)** — composing and reusing schemas
109
183
  - **[Architecture](docs/ARCHITECTURE.md)** — the compiler pipeline and IR stack
110
184
  - **[Golden Tests](docs/GOLDEN_TESTS.md)** — the end-to-end test harness
@@ -7,6 +7,54 @@ functions:
7
7
  param: number
8
8
  aliases: ["abs"]
9
9
 
10
+ - id: core.sqrt
11
+ kind: elementwise
12
+ params: [{ name: number, dtype: numeric }]
13
+ dtype:
14
+ rule: scalar
15
+ kind: float
16
+ aliases: ["sqrt"]
17
+
18
+ - id: core.sin
19
+ kind: elementwise
20
+ params: [{ name: angle, dtype: numeric }]
21
+ dtype:
22
+ rule: scalar
23
+ kind: float
24
+ aliases: ["sin"]
25
+
26
+ - id: core.cos
27
+ kind: elementwise
28
+ params: [{ name: angle, dtype: numeric }]
29
+ dtype:
30
+ rule: scalar
31
+ kind: float
32
+ aliases: ["cos"]
33
+
34
+ - id: core.exp
35
+ kind: elementwise
36
+ params: [{ name: number, dtype: numeric }]
37
+ dtype:
38
+ rule: scalar
39
+ kind: float
40
+ aliases: ["exp"]
41
+
42
+ - id: core.log
43
+ kind: elementwise
44
+ params: [{ name: number, dtype: numeric }]
45
+ dtype:
46
+ rule: scalar
47
+ kind: float
48
+ aliases: ["log"]
49
+
50
+ - id: core.tanh
51
+ kind: elementwise
52
+ params: [{ name: number, dtype: numeric }]
53
+ dtype:
54
+ rule: scalar
55
+ kind: float
56
+ aliases: ["tanh"]
57
+
10
58
  - id: core.max
11
59
  kind: elementwise
12
60
  params:
@@ -30,3 +30,16 @@ functions:
30
30
  rule: scalar
31
31
  kind: string
32
32
  aliases: ["to_string", "to_str"]
33
+
34
+ # Float overload: a whole-valued float must stringify as "3.0", not "3", and
35
+ # both targets must agree. Ruby's Float#to_s already does this; the JS kernel
36
+ # reproduces Ruby's exact float formatting (see data/kernels/.../coercion.yaml
37
+ # and docs/CROSS_TARGET_SEMANTICS.md). Picked over the generic overload for
38
+ # float arguments by dtype-based overload resolution.
39
+ - id: core.to_string:float
40
+ kind: elementwise
41
+ params: [{ name: value, dtype: float }]
42
+ dtype:
43
+ rule: scalar
44
+ kind: string
45
+ aliases: ["to_string", "to_str"]
@@ -1,7 +1,7 @@
1
1
  functions:
2
2
  - id: __select__
3
3
  kind: elementwise
4
- params: [{ name: condition_mask }, { name: value_when_true }, { name: value_when_false }]
4
+ params: [{ name: condition_mask, dtype: boolean }, { name: value_when_true }, { name: value_when_false }]
5
5
  dtype:
6
6
  rule: same_as
7
7
  param: value_when_true
@@ -9,6 +9,38 @@ functions:
9
9
  policy: wrap # wrap|clamp
10
10
  axis_offset: 0
11
11
 
12
+ # Re-exposes the source array under a fresh, independent axis (the
13
+ # broadcast-dual of a reduction). Lowered specially: it introduces a new
14
+ # innermost loop over the same carrier so an array can be combined with
15
+ # itself (e.g. all-pairs / N-body). Axis stamping lives in the dimensional
16
+ # analyzer; this entry exists so resolution/validation treat cross as a
17
+ # known unary call.
18
+ - id: cross
19
+ kind: elementwise
20
+ params: [{ name: source }]
21
+ dtype:
22
+ rule: same_as
23
+ param: source
24
+ constraint_semantics:
25
+ domain_effect: NONE
26
+ pure_combiner: false
27
+
28
+ # Re-exposes a value from a DIFFERENT array as a fresh inner axis, so two
29
+ # distinct arrays can be paired all-pairs (A x B). Where `cross` self-joins one
30
+ # array, `outer` joins two. Lowered specially: it opens a new innermost loop
31
+ # over the *other* array's carrier. Axis stamping lives in the dimensional
32
+ # analyzer; this entry exists so resolution/validation treat outer as a known
33
+ # unary call.
34
+ - id: outer
35
+ kind: elementwise
36
+ params: [{ name: source }]
37
+ dtype:
38
+ rule: same_as
39
+ param: source
40
+ constraint_semantics:
41
+ domain_effect: NONE
42
+ pure_combiner: false
43
+
12
44
  - id: shift
13
45
  kind: elementwise
14
46
  params: [{ name: source }, { name: offset }]
@@ -6,6 +6,7 @@ kernels:
6
6
  identity:
7
7
  float: 0.0
8
8
  integer: 0
9
+ decimal: 0.0
9
10
 
10
11
  - id: agg.count:javascript:v1
11
12
  fn: agg.count
@@ -3,6 +3,30 @@ kernels:
3
3
  fn: core.abs
4
4
  inline: "= Math.abs($0)"
5
5
 
6
+ - id: sqrt:javascript:v1
7
+ fn: core.sqrt
8
+ inline: "= Math.sqrt($0)"
9
+
10
+ - id: sin:javascript:v1
11
+ fn: core.sin
12
+ inline: "= Math.sin($0)"
13
+
14
+ - id: cos:javascript:v1
15
+ fn: core.cos
16
+ inline: "= Math.cos($0)"
17
+
18
+ - id: exp:javascript:v1
19
+ fn: core.exp
20
+ inline: "= Math.exp($0)"
21
+
22
+ - id: log:javascript:v1
23
+ fn: core.log
24
+ inline: "= Math.log($0)"
25
+
26
+ - id: tanh:javascript:v1
27
+ fn: core.tanh
28
+ inline: "= Math.tanh($0)"
29
+
6
30
  - id: mod:javascript:v1
7
31
  fn: core.mod
8
32
  inline: "= (($0 % $1) + $1) % $1"
@@ -4,17 +4,31 @@ kernels:
4
4
  inline: "= typeof $0 === 'string' ? parseFloat($0) : Number($0)"
5
5
  impl: "(value) {\n if (typeof value === 'string') return parseFloat(value);\n return Number(value);\n}"
6
6
 
7
+ # Match Ruby String#to_i / Numeric#to_i (and to_f): a non-numeric string is 0
8
+ # (not NaN), parsing is base-10 (so "0x1f" is 0, not 31), and a numeric value
9
+ # truncates toward zero without scientific-notation surprises (Math.trunc, not
10
+ # parseInt — parseInt(1e-7) is 1). Emitted as module-level helpers (no inline).
11
+ # Contract documented in docs/CROSS_TARGET_SEMANTICS.md.
7
12
  - id: to_integer:javascript:v1
8
13
  fn: core.to_integer
9
- inline: "= parseInt($0)"
10
- impl: "(value) { return parseInt(value); }"
14
+ impl: "(value) {\n if (typeof value === 'number') return Math.trunc(value) || 0;\n const m = String(value).trim().match(/^[+-]?\\d+/);\n return m ? parseInt(m[0], 10) : 0;\n}"
11
15
 
12
16
  - id: to_float:javascript:v1
13
17
  fn: core.to_float
14
- inline: "= parseFloat($0)"
15
- impl: "(value) { return parseFloat(value); }"
18
+ impl: "(value) {\n if (typeof value === 'number') return value;\n const f = parseFloat(value);\n return Number.isNaN(f) ? 0.0 : f;\n}"
16
19
 
17
20
  - id: to_string:javascript:v1
18
21
  fn: core.to_string
19
22
  inline: "= String($0)"
20
23
  impl: "(value) { return String(value); }"
24
+
25
+ # Reproduce Ruby's Float#to_s so to_string(float) matches across targets:
26
+ # - whole floats keep ".0" (3.0 -> "3.0", not JS's "3")
27
+ # - negative zero keeps its sign (-0.0 -> "-0.0")
28
+ # - exponent form is "1.0e+21" / "1.0e-07" (mantissa gets ".0", exponent
29
+ # is signed and zero-padded to >= 2 digits)
30
+ # No `inline`: this is emitted once as a module-level helper function. The
31
+ # contract is documented in docs/CROSS_TARGET_SEMANTICS.md.
32
+ - id: to_string_float:javascript:v1
33
+ fn: core.to_string:float
34
+ impl: "(value) {\n if (Number.isNaN(value)) return 'NaN';\n if (value === Infinity) return 'Infinity';\n if (value === -Infinity) return '-Infinity';\n if (Object.is(value, -0)) return '-0.0';\n let s = String(value);\n const e = s.indexOf('e');\n if (e !== -1) {\n let mant = s.slice(0, e);\n let exp = s.slice(e + 1);\n if (!mant.includes('.')) mant += '.0';\n const sign = exp[0] === '-' ? '-' : '+';\n let digits = exp.replace(/^[+-]/, '');\n if (digits.length < 2) digits = '0' + digits;\n return mant + 'e' + sign + digits;\n }\n return s.includes('.') ? s : s + '.0';\n}"
@@ -7,7 +7,8 @@ kernels:
7
7
  identity:
8
8
  float: 0.0
9
9
  integer: 0
10
-
10
+ decimal: 0
11
+
11
12
  - id: agg.count:ruby:v1
12
13
  fn: agg.count
13
14
  inline: "+= 1"
@@ -2,7 +2,31 @@ kernels:
2
2
  - id: abs:ruby:v1
3
3
  fn: core.abs
4
4
  inline: "= $0.abs"
5
-
5
+
6
+ - id: sqrt:ruby:v1
7
+ fn: core.sqrt
8
+ inline: "= Math.sqrt($0)"
9
+
10
+ - id: sin:ruby:v1
11
+ fn: core.sin
12
+ inline: "= Math.sin($0)"
13
+
14
+ - id: cos:ruby:v1
15
+ fn: core.cos
16
+ inline: "= Math.cos($0)"
17
+
18
+ - id: exp:ruby:v1
19
+ fn: core.exp
20
+ inline: "= Math.exp($0)"
21
+
22
+ - id: log:ruby:v1
23
+ fn: core.log
24
+ inline: "= Math.log($0)"
25
+
26
+ - id: tanh:ruby:v1
27
+ fn: core.tanh
28
+ inline: "= Math.tanh($0)"
29
+
6
30
  - id: mod.ruby:v1
7
31
  fn: core.mod
8
32
  inline: "= $0 % $1"
@@ -37,10 +61,14 @@ kernels:
37
61
  inline: "= $0 * $1"
38
62
  impl: "(str, count)\n str * count"
39
63
 
64
+ # A negative base with a fractional exponent makes Ruby return a Complex,
65
+ # whereas JS Math.pow returns NaN. A numeric DSL should not silently produce
66
+ # complex numbers, so collapse the Complex case to NaN to match JS.
67
+ # Contract documented in docs/CROSS_TARGET_SEMANTICS.md.
40
68
  - id: pow:ruby:v1
41
69
  fn: core.pow
42
- inline: "= $0 ** $1"
43
- impl: "(a, b)\n a ** b"
70
+ inline: "= (($0) ** ($1)).then { |r| r.is_a?(Complex) ? Float::NAN : r }"
71
+ impl: "(a, b)\n r = a ** b\n r.is_a?(Complex) ? Float::NAN : r"
44
72
 
45
73
  - id: div:ruby:v1
46
74
  fn: core.div
@@ -18,3 +18,11 @@ kernels:
18
18
  fn: core.to_string
19
19
  inline: "= $0.to_s"
20
20
  impl: "(value)\n value.to_s"
21
+
22
+ # Float#to_s already renders Ruby's canonical float form ("3.0", "1.0e+21"),
23
+ # which IS the cross-target contract; the JS kernel reproduces it exactly.
24
+ # See docs/CROSS_TARGET_SEMANTICS.md.
25
+ - id: to_string_float:ruby:v1
26
+ fn: core.to_string:float
27
+ inline: "= $0.to_s"
28
+ impl: "(value)\n value.to_s"
@@ -0,0 +1,99 @@
1
+ # Cross-Target Semantics
2
+
3
+ Kumi compiles one schema to **both Ruby and JavaScript**, and the central promise
4
+ is that the two targets compute **identical results**. Most operations are
5
+ identical for free (arithmetic, comparisons, array iteration). A handful are not,
6
+ because the two host languages disagree on edge-case behavior. This document is
7
+ the single place that records those cases and where each is pinned.
8
+
9
+ ## Where the contract lives
10
+
11
+ Per-operation semantics live in **kernels**, one YAML entry per `(function,
12
+ target)`:
13
+
14
+ ```
15
+ data/kernels/ruby/**/*.yaml # Ruby implementation of each function
16
+ data/kernels/javascript/**/*.yaml # JavaScript implementation of each function
17
+ ```
18
+
19
+ A kernel has an `inline` form (expanded at the call site) and/or an `impl` form
20
+ (a named helper function emitted once per module). When Ruby and JS would
21
+ otherwise diverge, the kernels are written so **both produce the agreed result**,
22
+ and the YAML carries a comment pointing back here.
23
+
24
+ This is the configurable seam: changing a cross-target rule means editing the two
25
+ kernels (and the golden snapshots), not the compiler. Nothing in the analyzer or
26
+ emitters hard-codes these semantics.
27
+
28
+ > The Ruby and JS Loop emitters both support `impl`-only kernels (a kernel with
29
+ > no `inline` is emitted once as a module-level helper, `__<fn_id>(...)`), so a
30
+ > non-trivial rule can be a clean named function on both targets instead of an
31
+ > inline blob. See `codegen/loop/{ruby,js}/emitter.rb`.
32
+
33
+ ## The pinned cases
34
+
35
+ ### `to_string` of a float — keep the `.0`
36
+
37
+ A whole-valued float must stringify the same way on both targets.
38
+
39
+ | value | Ruby `to_s` | JS `String()` | Kumi (both) |
40
+ | --------- | ----------- | ------------- | ----------- |
41
+ | `3.0` | `"3.0"` | `"3"` | `"3.0"` |
42
+ | `100.0` | `"100.0"` | `"100"` | `"100.0"` |
43
+ | `-0.0` | `"-0.0"` | `"0"` | `"-0.0"` |
44
+ | `1e21` | `"1.0e+21"` | `"1e+21"` | `"1.0e+21"` |
45
+ | `1e-7` | `"1.0e-07"` | `"1e-7"` | `"1.0e-07"` |
46
+
47
+ The contract is **Ruby's `Float#to_s`** (the float dtype stays visible in the
48
+ string). JS cannot distinguish `3` (integer) from `3.0` (float) at runtime, so
49
+ this is a **dtype-dispatched overload**: `core.to_string:float` (param
50
+ `dtype: float`) is selected for float arguments by overload resolution, and its
51
+ JS kernel reproduces Ruby's formatting (mantissa `.0`, signed zero-padded
52
+ exponent, `-0.0` sign). Non-float `to_string` keeps the plain `String($0)`.
53
+
54
+ Kernels: `to_string_float:ruby:v1`, `to_string_float:javascript:v1`.
55
+
56
+ ### `to_integer` / `to_float` of a string — Ruby parsing, not JS parsing
57
+
58
+ `String#to_i` / `String#to_f` and JS `parseInt` / `parseFloat` disagree:
59
+
60
+ | input | Ruby `to_i` | JS `parseInt` | Kumi (both) |
61
+ | ---------- | ----------- | ------------- | ----------- |
62
+ | `"abc"` | `0` | `NaN` | `0` |
63
+ | `""` | `0` | `NaN` | `0` |
64
+ | `"0x1f"` | `0` | `31` (hex) | `0` |
65
+ | `"12abc"` | `12` | `12` | `12` |
66
+
67
+ The contract is **Ruby's** behavior: a non-numeric string is the type's zero
68
+ (`0` / `0.0`), parsing is base-10 (no `0x` hex), and a numeric value truncates
69
+ toward zero with `Math.trunc` (JS `parseInt(1e-7)` is the wrong `1`). The JS
70
+ kernels are written to match.
71
+
72
+ Kernels: `to_integer:javascript:v1`, `to_float:javascript:v1`.
73
+
74
+ ### `pow` of a negative base with a fractional exponent — NaN, not Complex
75
+
76
+ `(-8.0) ** (1.0/3)` is mathematically complex. Ruby returns a `Complex`; JS
77
+ `Math.pow` returns `NaN`. A numeric DSL should not silently produce complex
78
+ numbers, so the **contract is `NaN`** (matching JS). The Ruby kernel collapses a
79
+ `Complex` result to `Float::NAN`.
80
+
81
+ Kernel: `pow:ruby:v1`.
82
+
83
+ ## Not divergences (documented to prevent false alarms)
84
+
85
+ - **`mean` / `sum` / `min` / `max` of an empty array.** `mean([])` is `NaN` on
86
+ *both* targets (`0.0 / 0`). When probing via JSON, note `JSON.stringify(NaN)`
87
+ is `"null"`, so a Ruby `NaN` looks like a JS `null` through a JSON round-trip —
88
+ that is a serialization artifact, not a semantic difference.
89
+
90
+ ## Adding or changing a rule
91
+
92
+ 1. Decide the agreed result for both targets.
93
+ 2. Edit the Ruby kernel and the JS kernel so each produces it (add a `# see
94
+ docs/CROSS_TARGET_SEMANTICS.md` comment and a row in this file).
95
+ 3. Regenerate the golden snapshots: `bin/kumi golden_v2 update`. The
96
+ `runtime.json` of any affected golden should change only where the old
97
+ behavior was actually wrong.
98
+ 4. Add a parity case (Ruby vs generated-JS over edge inputs) so the contract is
99
+ tested, not just documented.
data/docs/FORM_SCHEMA.md CHANGED
@@ -52,13 +52,18 @@ bin/kumi analyze schema.kumi --dump input_form_schema
52
52
 
53
53
  **Schema:**
54
54
  ```kumi
55
- input items: array {
56
- item: hash {
57
- name: string
58
- price: float
59
- }
60
- }
61
- input discount: float
55
+ schema do
56
+ input do
57
+ array :items do
58
+ hash :item do
59
+ string :name
60
+ float :price
61
+ end
62
+ end
63
+
64
+ float :discount
65
+ end
66
+ end
62
67
  ```
63
68
 
64
69
  **Generated Form Schema:**
@@ -82,4 +87,4 @@ input discount: float
82
87
 
83
88
  ## Implementation
84
89
 
85
- The `InputFormSchemaPass` runs after `InputCollector` in the analyzer pipeline and produces a clean schema containing only type information needed for form generation, excluding internal metadata like access modes and navigation steps.
90
+ The `InputFormSchemaPass` runs after `InputCollectorPass` in the analyzer pipeline and produces a clean schema containing only type information needed for form generation, excluding internal metadata like access modes and navigation steps.