kumi 0.0.25 → 0.0.27

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 (223) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -0
  3. data/CLAUDE.md +4 -0
  4. data/README.md +86 -78
  5. data/data/functions/agg/boolean.yaml +6 -2
  6. data/data/functions/agg/numeric.yaml +32 -16
  7. data/data/functions/agg/string.yaml +4 -3
  8. data/data/functions/core/arithmetic.yaml +62 -14
  9. data/data/functions/core/boolean.yaml +12 -6
  10. data/data/functions/core/comparison.yaml +25 -13
  11. data/data/functions/core/constructor.yaml +16 -8
  12. data/data/functions/core/conversion.yaml +32 -0
  13. data/data/functions/core/select.yaml +3 -1
  14. data/data/functions/core/stencil.yaml +14 -5
  15. data/data/functions/core/string.yaml +9 -4
  16. data/data/kernels/javascript/core/coercion.yaml +20 -0
  17. data/data/kernels/ruby/agg/numeric.yaml +1 -1
  18. data/data/kernels/ruby/core/coercion.yaml +20 -0
  19. data/docs/ARCHITECTURE.md +277 -0
  20. data/docs/DEVELOPMENT.md +62 -0
  21. data/docs/FUNCTIONS.md +955 -0
  22. data/docs/SYNTAX.md +8 -0
  23. data/docs/UNSAT_DETECTION.md +83 -0
  24. data/docs/VSCODE_EXTENSION.md +114 -0
  25. data/docs/functions-reference.json +1821 -0
  26. data/golden/array_element/expected/nast.txt +1 -1
  27. data/golden/array_element/expected/schema_ruby.rb +1 -1
  28. data/golden/array_index/expected/nast.txt +7 -7
  29. data/golden/array_index/expected/schema_ruby.rb +1 -1
  30. data/golden/array_operations/expected/nast.txt +2 -2
  31. data/golden/array_operations/expected/schema_ruby.rb +1 -1
  32. data/golden/array_operations/expected/snast.txt +3 -3
  33. data/golden/cascade_logic/expected/schema_ruby.rb +1 -1
  34. data/golden/cascade_logic/expected/snast.txt +2 -2
  35. data/golden/chained_fusion/expected/nast.txt +2 -2
  36. data/golden/chained_fusion/expected/schema_ruby.rb +1 -1
  37. data/golden/decimal_explicit/expected/ast.txt +38 -0
  38. data/golden/decimal_explicit/expected/input_plan.txt +3 -0
  39. data/golden/decimal_explicit/expected/lir_00_unoptimized.txt +30 -0
  40. data/golden/decimal_explicit/expected/lir_01_hoist_scalar_references.txt +30 -0
  41. data/golden/decimal_explicit/expected/lir_02_inlined.txt +44 -0
  42. data/golden/decimal_explicit/expected/lir_03_cse.txt +40 -0
  43. data/golden/decimal_explicit/expected/lir_04_1_loop_fusion.txt +40 -0
  44. data/golden/decimal_explicit/expected/lir_04_loop_invcm.txt +40 -0
  45. data/golden/decimal_explicit/expected/lir_06_const_prop.txt +40 -0
  46. data/golden/decimal_explicit/expected/nast.txt +30 -0
  47. data/golden/decimal_explicit/expected/schema_javascript.mjs +31 -0
  48. data/golden/decimal_explicit/expected/schema_ruby.rb +57 -0
  49. data/golden/decimal_explicit/expected/snast.txt +30 -0
  50. data/golden/decimal_explicit/expected.json +1 -0
  51. data/golden/decimal_explicit/input.json +5 -0
  52. data/golden/decimal_explicit/schema.kumi +14 -0
  53. data/golden/element_arrays/expected/nast.txt +2 -2
  54. data/golden/element_arrays/expected/schema_ruby.rb +1 -1
  55. data/golden/element_arrays/expected/snast.txt +1 -1
  56. data/golden/empty_and_null_inputs/expected/nast.txt +3 -3
  57. data/golden/empty_and_null_inputs/expected/schema_ruby.rb +1 -1
  58. data/golden/function_overload/expected/ast.txt +29 -0
  59. data/golden/function_overload/expected/input_plan.txt +4 -0
  60. data/golden/function_overload/expected/lir_00_unoptimized.txt +18 -0
  61. data/golden/function_overload/expected/lir_01_hoist_scalar_references.txt +18 -0
  62. data/golden/function_overload/expected/lir_02_inlined.txt +20 -0
  63. data/golden/function_overload/expected/lir_03_cse.txt +20 -0
  64. data/golden/function_overload/expected/lir_04_1_loop_fusion.txt +20 -0
  65. data/golden/function_overload/expected/lir_04_loop_invcm.txt +20 -0
  66. data/golden/function_overload/expected/lir_06_const_prop.txt +20 -0
  67. data/golden/function_overload/expected/nast.txt +22 -0
  68. data/golden/function_overload/expected/schema_javascript.mjs +12 -0
  69. data/golden/function_overload/expected/schema_ruby.rb +39 -0
  70. data/golden/function_overload/expected/snast.txt +22 -0
  71. data/golden/function_overload/input.json +8 -0
  72. data/golden/function_overload/schema.kumi +19 -0
  73. data/golden/game_of_life/expected/lir_00_unoptimized.txt +4 -4
  74. data/golden/game_of_life/expected/lir_01_hoist_scalar_references.txt +4 -4
  75. data/golden/game_of_life/expected/lir_02_inlined.txt +16 -16
  76. data/golden/game_of_life/expected/lir_03_cse.txt +20 -16
  77. data/golden/game_of_life/expected/lir_04_1_loop_fusion.txt +20 -16
  78. data/golden/game_of_life/expected/lir_04_loop_invcm.txt +20 -16
  79. data/golden/game_of_life/expected/lir_06_const_prop.txt +20 -16
  80. data/golden/game_of_life/expected/nast.txt +4 -4
  81. data/golden/game_of_life/expected/schema_javascript.mjs +4 -2
  82. data/golden/game_of_life/expected/schema_ruby.rb +5 -3
  83. data/golden/game_of_life/expected/snast.txt +10 -10
  84. data/golden/hash_keys/expected/schema_ruby.rb +1 -1
  85. data/golden/hash_value/expected/nast.txt +1 -1
  86. data/golden/hash_value/expected/schema_ruby.rb +1 -1
  87. data/golden/hash_value/expected/snast.txt +1 -1
  88. data/golden/hierarchical_complex/expected/nast.txt +3 -3
  89. data/golden/hierarchical_complex/expected/schema_ruby.rb +1 -1
  90. data/golden/hierarchical_complex/expected/snast.txt +3 -3
  91. data/golden/inline_rename_scope_leak/expected/nast.txt +3 -3
  92. data/golden/inline_rename_scope_leak/expected/schema_ruby.rb +1 -1
  93. data/golden/input_reference/expected/nast.txt +2 -2
  94. data/golden/input_reference/expected/schema_ruby.rb +1 -1
  95. data/golden/interleaved_fusion/expected/nast.txt +2 -2
  96. data/golden/interleaved_fusion/expected/schema_ruby.rb +1 -1
  97. data/golden/let_inline/expected/nast.txt +4 -4
  98. data/golden/let_inline/expected/schema_ruby.rb +1 -1
  99. data/golden/loop_fusion/expected/nast.txt +1 -1
  100. data/golden/loop_fusion/expected/schema_ruby.rb +1 -1
  101. data/golden/min_reduce_scope/expected/nast.txt +3 -3
  102. data/golden/min_reduce_scope/expected/schema_ruby.rb +1 -1
  103. data/golden/min_reduce_scope/expected/snast.txt +1 -1
  104. data/golden/mixed_dimensions/expected/nast.txt +2 -2
  105. data/golden/mixed_dimensions/expected/schema_ruby.rb +1 -1
  106. data/golden/multirank_hoisting/expected/nast.txt +7 -7
  107. data/golden/multirank_hoisting/expected/schema_ruby.rb +1 -1
  108. data/golden/nested_hash/expected/nast.txt +1 -1
  109. data/golden/nested_hash/expected/schema_ruby.rb +1 -1
  110. data/golden/reduction_broadcast/expected/nast.txt +3 -3
  111. data/golden/reduction_broadcast/expected/schema_ruby.rb +1 -1
  112. data/golden/reduction_broadcast/expected/snast.txt +1 -1
  113. data/golden/roll/expected/schema_ruby.rb +1 -1
  114. data/golden/shift/expected/schema_ruby.rb +1 -1
  115. data/golden/shift_2d/expected/schema_ruby.rb +1 -1
  116. data/golden/simple_math/expected/lir_00_unoptimized.txt +1 -1
  117. data/golden/simple_math/expected/lir_01_hoist_scalar_references.txt +1 -1
  118. data/golden/simple_math/expected/lir_02_inlined.txt +1 -1
  119. data/golden/simple_math/expected/lir_03_cse.txt +1 -1
  120. data/golden/simple_math/expected/lir_04_1_loop_fusion.txt +1 -1
  121. data/golden/simple_math/expected/lir_04_loop_invcm.txt +1 -1
  122. data/golden/simple_math/expected/lir_06_const_prop.txt +1 -1
  123. data/golden/simple_math/expected/nast.txt +5 -5
  124. data/golden/simple_math/expected/schema_ruby.rb +1 -1
  125. data/golden/simple_math/expected/snast.txt +2 -2
  126. data/golden/streaming_basics/expected/nast.txt +8 -8
  127. data/golden/streaming_basics/expected/schema_ruby.rb +1 -1
  128. data/golden/streaming_basics/expected/snast.txt +1 -1
  129. data/golden/tuples/expected/lir_00_unoptimized.txt +5 -5
  130. data/golden/tuples/expected/lir_01_hoist_scalar_references.txt +5 -5
  131. data/golden/tuples/expected/lir_02_inlined.txt +5 -5
  132. data/golden/tuples/expected/lir_03_cse.txt +5 -5
  133. data/golden/tuples/expected/lir_04_1_loop_fusion.txt +5 -5
  134. data/golden/tuples/expected/lir_04_loop_invcm.txt +5 -5
  135. data/golden/tuples/expected/lir_06_const_prop.txt +5 -5
  136. data/golden/tuples/expected/nast.txt +4 -4
  137. data/golden/tuples/expected/schema_ruby.rb +1 -1
  138. data/golden/tuples/expected/snast.txt +6 -6
  139. data/golden/tuples_and_arrays/expected/lir_00_unoptimized.txt +1 -1
  140. data/golden/tuples_and_arrays/expected/lir_01_hoist_scalar_references.txt +1 -1
  141. data/golden/tuples_and_arrays/expected/lir_02_inlined.txt +2 -2
  142. data/golden/tuples_and_arrays/expected/lir_03_cse.txt +2 -2
  143. data/golden/tuples_and_arrays/expected/lir_04_1_loop_fusion.txt +2 -2
  144. data/golden/tuples_and_arrays/expected/lir_04_loop_invcm.txt +2 -2
  145. data/golden/tuples_and_arrays/expected/lir_06_const_prop.txt +2 -2
  146. data/golden/tuples_and_arrays/expected/nast.txt +3 -3
  147. data/golden/tuples_and_arrays/expected/schema_ruby.rb +1 -1
  148. data/golden/tuples_and_arrays/expected/snast.txt +2 -2
  149. data/golden/us_tax_2024/expected/ast.txt +63 -670
  150. data/golden/us_tax_2024/expected/input_plan.txt +8 -45
  151. data/golden/us_tax_2024/expected/lir_00_unoptimized.txt +253 -863
  152. data/golden/us_tax_2024/expected/lir_01_hoist_scalar_references.txt +253 -863
  153. data/golden/us_tax_2024/expected/lir_02_inlined.txt +1215 -5139
  154. data/golden/us_tax_2024/expected/lir_03_cse.txt +587 -2460
  155. data/golden/us_tax_2024/expected/lir_04_1_loop_fusion.txt +632 -2480
  156. data/golden/us_tax_2024/expected/lir_04_loop_invcm.txt +587 -2460
  157. data/golden/us_tax_2024/expected/lir_06_const_prop.txt +587 -2460
  158. data/golden/us_tax_2024/expected/nast.txt +123 -826
  159. data/golden/us_tax_2024/expected/schema_javascript.mjs +127 -581
  160. data/golden/us_tax_2024/expected/schema_ruby.rb +135 -610
  161. data/golden/us_tax_2024/expected/snast.txt +155 -858
  162. data/golden/us_tax_2024/expected.json +120 -1
  163. data/golden/us_tax_2024/input.json +18 -9
  164. data/golden/us_tax_2024/schema.kumi +48 -178
  165. data/golden/with_constants/expected/lir_00_unoptimized.txt +1 -1
  166. data/golden/with_constants/expected/lir_01_hoist_scalar_references.txt +1 -1
  167. data/golden/with_constants/expected/lir_02_inlined.txt +1 -1
  168. data/golden/with_constants/expected/lir_03_cse.txt +1 -1
  169. data/golden/with_constants/expected/lir_04_1_loop_fusion.txt +1 -1
  170. data/golden/with_constants/expected/lir_04_loop_invcm.txt +1 -1
  171. data/golden/with_constants/expected/lir_06_const_prop.txt +1 -1
  172. data/golden/with_constants/expected/nast.txt +2 -2
  173. data/golden/with_constants/expected/schema_ruby.rb +1 -1
  174. data/golden/with_constants/expected/snast.txt +2 -2
  175. data/lib/kumi/analyzer.rb +12 -12
  176. data/lib/kumi/configuration.rb +6 -0
  177. data/lib/kumi/core/analyzer/passes/formal_constraint_propagator.rb +236 -0
  178. data/lib/kumi/core/analyzer/passes/input_collector.rb +22 -4
  179. data/lib/kumi/core/analyzer/passes/nast_dimensional_analyzer_pass.rb +64 -18
  180. data/lib/kumi/core/analyzer/passes/normalize_to_nast_pass.rb +9 -4
  181. data/lib/kumi/core/analyzer/passes/snast_pass.rb +3 -1
  182. data/lib/kumi/core/analyzer/passes/unsat_detector.rb +172 -198
  183. data/lib/kumi/core/error_reporter.rb +36 -1
  184. data/lib/kumi/core/errors.rb +33 -1
  185. data/lib/kumi/core/functions/function_spec.rb +5 -4
  186. data/lib/kumi/core/functions/loader.rb +17 -1
  187. data/lib/kumi/core/functions/overload_resolver.rb +164 -0
  188. data/lib/kumi/core/functions/type_error_reporter.rb +118 -0
  189. data/lib/kumi/core/functions/type_rules.rb +155 -35
  190. data/lib/kumi/core/input/type_matcher.rb +8 -1
  191. data/lib/kumi/core/ruby_parser/input_builder.rb +2 -2
  192. data/lib/kumi/core/types/inference.rb +29 -22
  193. data/lib/kumi/core/types/normalizer.rb +30 -45
  194. data/lib/kumi/core/types/validator.rb +17 -28
  195. data/lib/kumi/core/types/value_objects.rb +116 -0
  196. data/lib/kumi/core/types.rb +45 -37
  197. data/lib/kumi/dev/golden/reporter.rb +9 -0
  198. data/lib/kumi/dev/golden/result.rb +3 -1
  199. data/lib/kumi/dev/golden/runtime_test.rb +25 -0
  200. data/lib/kumi/dev/golden/suite.rb +4 -4
  201. data/lib/kumi/dev/golden/value_normalizer.rb +80 -0
  202. data/lib/kumi/dev/golden.rb +21 -12
  203. data/lib/kumi/doc_generator/formatters/json.rb +39 -0
  204. data/lib/kumi/doc_generator/formatters/markdown.rb +175 -0
  205. data/lib/kumi/doc_generator/loader.rb +37 -0
  206. data/lib/kumi/doc_generator/merger.rb +54 -0
  207. data/lib/kumi/doc_generator.rb +4 -0
  208. data/lib/kumi/registry_v2/loader.rb +90 -0
  209. data/lib/kumi/registry_v2.rb +18 -1
  210. data/lib/kumi/version.rb +1 -1
  211. data/vscode-extension/.gitignore +4 -0
  212. data/vscode-extension/README.md +59 -0
  213. data/vscode-extension/TESTING.md +151 -0
  214. data/vscode-extension/package.json +51 -0
  215. data/vscode-extension/src/extension.ts +295 -0
  216. data/vscode-extension/tsconfig.json +15 -0
  217. metadata +57 -7
  218. data/lib/kumi/core/analyzer/unsat_constant_evaluator.rb +0 -59
  219. data/lib/kumi/core/atom_unsat_solver.rb +0 -396
  220. data/lib/kumi/core/constraint_relationship_solver.rb +0 -641
  221. data/lib/kumi/core/types/builder.rb +0 -23
  222. data/lib/kumi/core/types/compatibility.rb +0 -96
  223. data/lib/kumi/core/types/formatter.rb +0 -26
@@ -0,0 +1,39 @@
1
+ require 'json'
2
+
3
+ module Kumi
4
+ module DocGenerator
5
+ module Formatters
6
+ class Json
7
+ def initialize(docs)
8
+ @docs = docs
9
+ end
10
+
11
+ def format
12
+ enriched = @docs.each_with_object({}) do |(alias_name, entry), acc|
13
+ kernel_ids = extract_kernel_ids(entry['kernels'])
14
+ acc[alias_name] = {
15
+ 'id' => entry['id'],
16
+ 'kind' => entry['kind'],
17
+ 'arity' => entry['arity'],
18
+ 'params' => entry['params'],
19
+ 'kernels' => kernel_ids,
20
+ 'dtype' => entry['dtype'],
21
+ 'aliases' => entry['aliases'],
22
+ 'reduction_strategy' => entry['reduction_strategy']
23
+ }
24
+ end
25
+
26
+ JSON.pretty_generate(enriched)
27
+ end
28
+
29
+ private
30
+
31
+ def extract_kernel_ids(kernels)
32
+ kernels.each_with_object({}) do |(target, kernel), acc|
33
+ acc[target] = kernel.is_a?(Hash) ? kernel['id'] : kernel
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,175 @@
1
+ module Kumi
2
+ module DocGenerator
3
+ module Formatters
4
+ class Markdown
5
+ def initialize(docs)
6
+ @docs = docs
7
+ end
8
+
9
+ def format
10
+ lines = [
11
+ "# Kumi Function Reference",
12
+ "",
13
+ "Auto-generated documentation for Kumi functions and their kernels.",
14
+ ""
15
+ ]
16
+
17
+ grouped = group_by_id(@docs)
18
+
19
+ grouped.sort.each do |id, aliases|
20
+ entry = @docs[aliases.first]
21
+ lines.concat(format_function(id, entry, aliases))
22
+ end
23
+
24
+ lines.join("\n")
25
+ end
26
+
27
+ private
28
+
29
+ def group_by_id(docs)
30
+ result = {}
31
+ docs.each do |alias_name, entry|
32
+ id = entry['id']
33
+ result[id] ||= []
34
+ result[id] << alias_name
35
+ end
36
+ result
37
+ end
38
+
39
+ def format_function(id, entry, aliases)
40
+ lines = [
41
+ "## `#{id}`",
42
+ ""
43
+ ]
44
+
45
+ if aliases.length > 1
46
+ lines << "**Aliases:** `#{aliases.sort.join('`, `')}`"
47
+ lines << ""
48
+ end
49
+
50
+ lines << "- **Arity:** #{entry['arity']}"
51
+
52
+ if entry['dtype']
53
+ dtype_str = format_dtype(entry['dtype'])
54
+ lines << "- **Type:** #{dtype_str}"
55
+ end
56
+
57
+ if is_reducer?(entry)
58
+ lines << "- **Behavior:** Reduces a dimension `[D] -> T`"
59
+ end
60
+ lines << ""
61
+
62
+ if entry['params'] && !entry['params'].empty?
63
+ lines << "### Parameters"
64
+ lines << ""
65
+ entry['params'].each do |param|
66
+ lines << "- `#{param['name']}`#{param['description'] ? ": #{param['description']}" : ""}"
67
+ end
68
+ lines << ""
69
+ end
70
+
71
+ if entry['kernels'] && !entry['kernels'].empty?
72
+ lines << "### Implementations"
73
+ lines << ""
74
+ entry['kernels'].each do |target, kernel|
75
+ lines.concat(format_kernel(target, kernel, entry['reduction_strategy']))
76
+ end
77
+ end
78
+
79
+ lines
80
+ end
81
+
82
+ def format_kernel(target, kernel, reduction_strategy = nil)
83
+ lines = []
84
+
85
+ if kernel.is_a?(Hash)
86
+ lines << "#### #{target.capitalize}"
87
+ lines << ""
88
+ lines << "`#{kernel['id']}`"
89
+ lines << ""
90
+
91
+ has_identity = kernel['identity'] && !kernel['identity'].empty?
92
+
93
+ if kernel['inline'] && has_identity
94
+ lines << "**Inline:** `#{escape_backticks(kernel['inline'])}` (`$0` = accumulator, `$1` = element)"
95
+ lines << ""
96
+ end
97
+
98
+ if kernel['impl']
99
+ lines << "**Implementation:**"
100
+ lines << ""
101
+ lines << "```ruby"
102
+ lines << format_impl(kernel['impl'])
103
+ lines << "```"
104
+ lines << ""
105
+ end
106
+
107
+ if kernel['fold_inline']
108
+ lines << "**Fold:** `#{escape_backticks(kernel['fold_inline'])}`"
109
+ lines << ""
110
+ end
111
+
112
+ if has_identity
113
+ lines << "**Identity:**"
114
+ kernel['identity'].each do |type, value|
115
+ lines << "- #{type}: `#{value}`"
116
+ end
117
+ lines << ""
118
+ elsif kernel['inline']
119
+ lines << "_Note: No identity value. First element initializes accumulator._"
120
+ lines << ""
121
+ end
122
+
123
+ # Show reduction strategy if available
124
+ if reduction_strategy
125
+ case reduction_strategy
126
+ when 'identity'
127
+ lines << "**Reduction:** Monoid operation with identity element"
128
+ when 'first_element'
129
+ lines << "**Reduction:** First element is initial value (no identity)"
130
+ else
131
+ lines << "**Reduction:** #{reduction_strategy}"
132
+ end
133
+ lines << ""
134
+ end
135
+ else
136
+ lines << "- **#{target}:** `#{kernel}`"
137
+ end
138
+
139
+ lines
140
+ end
141
+
142
+ def format_dtype(dtype)
143
+ return "any" if dtype.nil?
144
+
145
+ case dtype['rule']
146
+ when 'same_as'
147
+ "same as `#{dtype['param']}`"
148
+ when 'scalar'
149
+ dtype['kind'] || 'scalar'
150
+ when 'promote'
151
+ params = Array(dtype['params']).join('`, `')
152
+ "promoted from `#{params}`"
153
+ when 'element_of'
154
+ "element of `#{dtype['param']}`"
155
+ else
156
+ dtype['rule']
157
+ end
158
+ end
159
+
160
+ def format_impl(impl_str)
161
+ # Clean up multiline strings like "(a,b)\n a + b"
162
+ impl_str.gsub('\n', "\n").strip
163
+ end
164
+
165
+ def escape_backticks(str)
166
+ str.gsub('`', '\`')
167
+ end
168
+
169
+ def is_reducer?(entry)
170
+ entry['kind'] == 'reduce'
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,37 @@
1
+ require 'yaml'
2
+
3
+ module Kumi
4
+ module DocGenerator
5
+ class Loader
6
+ def initialize(functions_dir: nil, kernels_dir: nil)
7
+ @functions_dir = functions_dir
8
+ @kernels_dir = kernels_dir
9
+ end
10
+
11
+ def load_functions
12
+ return [] unless @functions_dir
13
+ load_yaml_dir(@functions_dir)
14
+ end
15
+
16
+ def load_kernels
17
+ return [] unless @kernels_dir
18
+ load_yaml_dir(@kernels_dir)
19
+ end
20
+
21
+ private
22
+
23
+ def load_yaml_dir(dir_path)
24
+ result = []
25
+ Dir.glob(File.join(dir_path, '**/*.yaml')).each do |file|
26
+ data = YAML.load_file(file)
27
+ if data && data['functions']
28
+ result.concat(data['functions'])
29
+ elsif data && data['kernels']
30
+ result.concat(data['kernels'])
31
+ end
32
+ end
33
+ result
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,54 @@
1
+ module Kumi
2
+ module DocGenerator
3
+ class Merger
4
+ def initialize(loader)
5
+ @loader = loader
6
+ end
7
+
8
+ def merge
9
+ functions = @loader.load_functions
10
+ kernels = @loader.load_kernels
11
+
12
+ result = {}
13
+
14
+ functions.each do |fn|
15
+ aliases = fn['aliases'] || []
16
+ aliases.each do |alias_name|
17
+ result[alias_name] = build_doc_entry(fn, kernels)
18
+ end
19
+ end
20
+
21
+ result
22
+ end
23
+
24
+ private
25
+
26
+ def build_doc_entry(function, kernels)
27
+ kernel_map = {}
28
+ kernels.each do |kernel|
29
+ if kernel['fn'] == function['id']
30
+ target = extract_target(kernel['id'])
31
+ kernel_map[target] = kernel
32
+ end
33
+ end
34
+
35
+ {
36
+ 'id' => function['id'],
37
+ 'kind' => function['kind'],
38
+ 'params' => function['params'] || [],
39
+ 'arity' => (function['params'] || []).length,
40
+ 'kernels' => kernel_map,
41
+ 'dtype' => function['dtype'],
42
+ 'aliases' => function['aliases'] || [],
43
+ 'reduction_strategy' => function['reduction_strategy']
44
+ }
45
+ end
46
+
47
+ def extract_target(kernel_id)
48
+ # kernel_id format: "agg.sum:ruby:v1" -> "ruby"
49
+ parts = kernel_id.split(':')
50
+ parts[1] if parts.length >= 2
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,4 @@
1
+ module Kumi
2
+ module DocGenerator
3
+ end
4
+ end
@@ -7,6 +7,96 @@ module Kumi
7
7
  module Loader
8
8
  module_function
9
9
 
10
+ # Build dtype rule from YAML specification (structured or legacy string format)
11
+ def build_dtype_rule_from_yaml(dtype_spec)
12
+ case dtype_spec
13
+ when String
14
+ # Legacy string format: "same_as(x)", "promote(a,b)", "integer", etc.
15
+ Kumi::Core::Functions::TypeRules.compile_dtype_rule(dtype_spec, [])
16
+ when Hash
17
+ # Structured format: { rule: 'same_as', param: 'x' }
18
+ build_dtype_rule_from_hash(dtype_spec)
19
+ else
20
+ raise "Invalid dtype specification: #{dtype_spec.inspect}"
21
+ end
22
+ end
23
+
24
+ # Build dtype rule from structured hash
25
+ def build_dtype_rule_from_hash(spec)
26
+ rule_type = spec.fetch('rule') { raise "dtype hash requires 'rule' key" }
27
+
28
+ case rule_type
29
+ when 'same_as'
30
+ param = spec.fetch('param') { raise "same_as rule requires 'param' key" }
31
+ Kumi::Core::Functions::TypeRules.build_same_as(param.to_sym)
32
+
33
+ when 'promote'
34
+ params = spec.fetch('params') { raise "promote rule requires 'params' key" }
35
+ param_syms = Array(params).map { |p| p.to_sym }
36
+ Kumi::Core::Functions::TypeRules.build_promote(*param_syms)
37
+
38
+ when 'element_of'
39
+ param = spec.fetch('param') { raise "element_of rule requires 'param' key" }
40
+ Kumi::Core::Functions::TypeRules.build_element_of(param.to_sym)
41
+
42
+ when 'unify'
43
+ param1 = spec.fetch('param1') { raise "unify rule requires 'param1' key" }
44
+ param2 = spec.fetch('param2') { raise "unify rule requires 'param2' key" }
45
+ Kumi::Core::Functions::TypeRules.build_unify(param1.to_sym, param2.to_sym)
46
+
47
+ when 'common_type'
48
+ param = spec.fetch('param') { raise "common_type rule requires 'param' key" }
49
+ Kumi::Core::Functions::TypeRules.build_common_type(param.to_sym)
50
+
51
+ when 'array'
52
+ if spec.key?('element_type')
53
+ element_type_spec = spec['element_type']
54
+ element_type = if element_type_spec.is_a?(Hash)
55
+ # Nested structured format
56
+ build_dtype_rule_from_hash(element_type_spec).call({})
57
+ else
58
+ # String or symbol
59
+ element_type_spec.to_sym
60
+ end
61
+ Kumi::Core::Functions::TypeRules.build_array(element_type)
62
+ elsif spec.key?('element_type_param')
63
+ element_type_param = spec['element_type_param'].to_sym
64
+ Kumi::Core::Functions::TypeRules.build_array(element_type_param)
65
+ else
66
+ raise "array rule requires either 'element_type' or 'element_type_param' key"
67
+ end
68
+
69
+ when 'tuple'
70
+ if spec.key?('element_types')
71
+ element_types_spec = spec['element_types']
72
+ element_types = Array(element_types_spec).map do |et|
73
+ if et.is_a?(Hash)
74
+ build_dtype_rule_from_hash(et).call({})
75
+ else
76
+ et.to_sym
77
+ end
78
+ end
79
+ Kumi::Core::Functions::TypeRules.build_tuple(*element_types)
80
+ elsif spec.key?('element_types_param')
81
+ element_types_param = spec['element_types_param'].to_sym
82
+ Kumi::Core::Functions::TypeRules.build_tuple(element_types_param)
83
+ else
84
+ raise "tuple rule requires either 'element_types' or 'element_types_param' key"
85
+ end
86
+
87
+ when 'scalar'
88
+ kind = spec.fetch('kind') { raise "scalar rule requires 'kind' key" }
89
+ kind_sym = kind.to_sym
90
+ unless Kumi::Core::Types::Validator.valid_kind?(kind_sym)
91
+ raise "scalar rule has unknown kind: #{kind}"
92
+ end
93
+ Kumi::Core::Functions::TypeRules.build_scalar(kind_sym)
94
+
95
+ else
96
+ raise "unknown dtype rule: #{rule_type}"
97
+ end
98
+ end
99
+
10
100
  # { "core.mul" => Function(id: "core.mul", kind: :elementwise, params: [...]) }
11
101
  def load_functions(dir, func_struct)
12
102
  files = Dir.glob(File.join(dir, "**", "*.y{a,}ml")).sort
@@ -22,7 +22,7 @@ module Kumi
22
22
  end
23
23
 
24
24
  def dtype_rule
25
- @dtype_rule ||= Core::Functions::TypeRules.compile_dtype_rule(dtype, param_names)
25
+ @dtype_rule ||= Loader.build_dtype_rule_from_yaml(dtype)
26
26
  end
27
27
  end
28
28
 
@@ -32,6 +32,7 @@ module Kumi
32
32
  def initialize(functions_by_id, kernels_by_key)
33
33
  @functions = functions_by_id # "core.mul" => Function<...>
34
34
  @alias = build_alias(@functions) # "count" => "agg.count"
35
+ @overload_resolver = Core::Functions::OverloadResolver.new(@functions)
35
36
  @kernels = kernels_by_key # [fn_id, target_sym] => Kernel
36
37
  @by_id = @kernels.values.to_h { |k| [k.id, k] }
37
38
  end
@@ -50,6 +51,12 @@ module Kumi
50
51
  end
51
52
  end
52
53
 
54
+ # Type-aware function resolution for overloads
55
+ # Returns the function_id that best matches the given argument types
56
+ def resolve_function_with_types(alias_or_id, arg_types)
57
+ @overload_resolver.resolve(alias_or_id, arg_types)
58
+ end
59
+
53
60
  def function_kind(id) = function(id).kind
54
61
  def function_reduce?(id) = function(id).reduce?
55
62
  def function_elementwise?(id) = function(id).elementwise?
@@ -104,6 +111,16 @@ module Kumi
104
111
  func.aliases.each { |al| acc[al] = func.id }
105
112
  end
106
113
  end
114
+
115
+ def build_alias_overloads(functions)
116
+ # Maps each alias to an array of all function_ids that have that alias
117
+ functions.values.each_with_object({}) do |func, acc|
118
+ func.aliases.each do |al|
119
+ acc[al] ||= []
120
+ acc[al] << func.id
121
+ end
122
+ end
123
+ end
107
124
  end
108
125
 
109
126
  module_function
data/lib/kumi/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kumi
4
- VERSION = "0.0.25"
4
+ VERSION = "0.0.27"
5
5
  end
@@ -0,0 +1,4 @@
1
+ node_modules/
2
+ out/
3
+ *.vsix
4
+ .DS_Store
@@ -0,0 +1,59 @@
1
+ # Kumi Language Support for VSCode
2
+
3
+ VSCode extension providing autocomplete, hover information, and documentation for Kumi functions.
4
+
5
+ ## Features
6
+
7
+ - **Autocomplete** - Function suggestions when typing `fn(:` in Ruby files
8
+ - **Hover Information** - Type signatures, arity, and parameter info on hover
9
+ - **Function Reference** - Auto-generated from `docs/functions-reference.json`
10
+
11
+ ## Installation
12
+
13
+ 1. Build the extension:
14
+ ```bash
15
+ npm install
16
+ npm run compile
17
+ ```
18
+
19
+ 2. Install in VSCode:
20
+ - Open VSCode Command Palette: `Cmd+Shift+P` (Mac) or `Ctrl+Shift+P` (Linux/Windows)
21
+ - Type "Extensions: Install from VSIX"
22
+ - Select the built `.vsix` file
23
+
24
+ Or load as development extension:
25
+ - Open VSCode with this folder
26
+ - Press `F5` to start debugging
27
+
28
+ ## Usage
29
+
30
+ While editing a Ruby file with Kumi schemas:
31
+
32
+ ```ruby
33
+ schema do
34
+ input { float :x }
35
+
36
+ # Type `fn(:` and get autocomplete suggestions
37
+ let :doubled, fn(:mul, input.x, 2)
38
+
39
+ # Hover over `mul` to see type info
40
+ value :result, doubled
41
+ end
42
+ ```
43
+
44
+ ## Data Source
45
+
46
+ Function definitions are loaded from `../../docs/functions-reference.json`, which is auto-generated by:
47
+
48
+ ```bash
49
+ bin/kumi-doc-gen
50
+ ```
51
+
52
+ Always regenerate the JSON after modifying function definitions!
53
+
54
+ ## Development
55
+
56
+ ```bash
57
+ npm run watch # Watch for TypeScript changes
58
+ npm run compile # Build once
59
+ ```
@@ -0,0 +1,151 @@
1
+ # Testing the Kumi VSCode Extension
2
+
3
+ ## Quick Start
4
+
5
+ ### 1. Build the Extension
6
+
7
+ ```bash
8
+ cd vscode-extension
9
+ npm install
10
+ npm run compile
11
+ ```
12
+
13
+ ### 2. Generate Function Data
14
+
15
+ Before testing, generate the function reference JSON:
16
+
17
+ ```bash
18
+ # From kumi root
19
+ bin/kumi-doc-gen
20
+ ```
21
+
22
+ This creates `docs/functions-reference.json` that the extension reads.
23
+
24
+ ### 3. Launch Extension in Debug Mode
25
+
26
+ ```bash
27
+ # From vscode-extension directory
28
+ code ..
29
+ ```
30
+
31
+ Or just open the kumi repo root in VSCode, then:
32
+ - Press `F5` to start debugging
33
+ - A new VSCode window will open with the extension loaded
34
+
35
+ ### 4. Test Autocomplete and Hover
36
+
37
+ Open `examples/demo-extension.kumi` in the debug window.
38
+
39
+ Position cursor after `fn(:` and type to trigger autocomplete:
40
+
41
+ ```kumi
42
+ # Example 1: Basic arithmetic
43
+ let :sum, fn(:add, x, y)
44
+
45
+ Type here and wait for suggestions
46
+ ```
47
+
48
+ **Expected behavior:**
49
+ - Autocomplete shows `add`, `sub`, `mul`, `div`, etc.
50
+ - Each suggestion shows arity and function ID
51
+ - Press Escape to close, or select with Enter
52
+
53
+ ### 5. Test Hover Information
54
+
55
+ Hover over function names to see documentation:
56
+
57
+ ```kumi
58
+ let :sum, fn(:sum, input.values.item.price)
59
+
60
+ Hover here to see type info
61
+ ```
62
+
63
+ **Expected behavior:**
64
+ - Popup shows:
65
+ - Function name: `agg.sum`
66
+ - Arity: `1`
67
+ - Type: `same as source_value`
68
+ - Parameters: `source_value`
69
+ - Kernels: `ruby: agg.sum:ruby:v1`
70
+
71
+ ### 6. Test Different Function Types
72
+
73
+ Try these in the demo file:
74
+
75
+ **Functions with identity:**
76
+ ```kumi
77
+ fn(:sum, ...) # Shows Inline: += $1
78
+ fn(:count, ...) # Shows Inline: += 1
79
+ fn(:any, ...) # Shows Inline: = $0 || $1
80
+ ```
81
+
82
+ **Functions without identity:**
83
+ ```kumi
84
+ fn(:min, ...) # No Inline, shows note about first element
85
+ fn(:max, ...) # No Inline, shows note about first element
86
+ ```
87
+
88
+ **Functions with multiple aliases:**
89
+ ```kumi
90
+ fn(:add, ...) # Has alias: add
91
+ fn(:mul, ...) # Has aliases: mul, multiply
92
+ fn(:sum_if, ...) # Complex aggregation
93
+ ```
94
+
95
+ ### 7. Watch for Recompilation
96
+
97
+ In the debug window, TypeScript changes auto-compile:
98
+
99
+ ```bash
100
+ npm run watch
101
+ ```
102
+
103
+ Make a change to `src/extension.ts`, save, and reload the debug window (Cmd+R / Ctrl+R) to see changes.
104
+
105
+ ## Troubleshooting
106
+
107
+ ### Extension doesn't load
108
+
109
+ Check the Debug Console for errors:
110
+ - `Cmd+Shift+J` (Mac) or `Ctrl+Shift+J` (Linux/Windows)
111
+
112
+ ### No autocomplete suggestions
113
+
114
+ 1. Verify `docs/functions-reference.json` exists
115
+ 2. Check extension loaded: Look for "Kumi functions reference loaded" in Debug Console
116
+ 3. Make sure cursor is after `fn(:`
117
+
118
+ ### JSON loading errors
119
+
120
+ If you see "Could not find functions-reference.json":
121
+ ```bash
122
+ # Regenerate the JSON
123
+ bin/kumi-doc-gen
124
+ ```
125
+
126
+ ### Type suggestions not showing
127
+
128
+ 1. Ensure you're in a `.kumi` or `.rb` file
129
+ 2. Check the file language is recognized (bottom-right of editor shows language)
130
+ 3. Try clicking on a function name and pressing `Cmd+K Cmd+I` to force hover
131
+
132
+ ## File Locations
133
+
134
+ - Extension code: `vscode-extension/src/extension.ts`
135
+ - Function data: `docs/functions-reference.json`
136
+ - Demo file: `examples/demo-extension.kumi`
137
+ - VSCode config: `vscode-extension/package.json`
138
+
139
+ ## Testing on Different File Types
140
+
141
+ ### Kumi Files (.kumi)
142
+ ```kumi
143
+ fn(:add, x, y) # Autocomplete and hover work
144
+ ```
145
+
146
+ ### Ruby Files (.rb)
147
+ ```ruby
148
+ fn(:add, x, y) # Also works if inside Kumi schema
149
+ ```
150
+
151
+ Both file types activate the extension and provide completions/hover.