kumi 0.0.26 → 0.0.28

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 (177) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +17 -0
  3. data/CLAUDE.md +4 -0
  4. data/README.md +36 -12
  5. data/data/functions/core/arithmetic.yaml +28 -8
  6. data/data/functions/core/boolean.yaml +8 -3
  7. data/data/functions/core/comparison.yaml +12 -4
  8. data/data/functions/core/conversion.yaml +32 -0
  9. data/data/kernels/javascript/core/arithmetic.yaml +6 -2
  10. data/data/kernels/javascript/core/coercion.yaml +20 -0
  11. data/data/kernels/ruby/core/arithmetic.yaml +7 -2
  12. data/data/kernels/ruby/core/coercion.yaml +20 -0
  13. data/docs/ARCHITECTURE.md +277 -0
  14. data/docs/DEVELOPMENT.md +62 -0
  15. data/docs/FUNCTIONS.md +955 -0
  16. data/docs/SYNTAX.md +8 -0
  17. data/docs/VSCODE_EXTENSION.md +114 -0
  18. data/docs/functions-reference.json +1821 -0
  19. data/golden/array_element/expected/schema_ruby.rb +1 -1
  20. data/golden/array_index/expected/lir_00_unoptimized.txt +2 -2
  21. data/golden/array_index/expected/lir_01_hoist_scalar_references.txt +2 -2
  22. data/golden/array_index/expected/lir_02_inlined.txt +2 -2
  23. data/golden/array_index/expected/lir_03_cse.txt +2 -2
  24. data/golden/array_index/expected/lir_04_1_loop_fusion.txt +2 -2
  25. data/golden/array_index/expected/lir_04_loop_invcm.txt +2 -2
  26. data/golden/array_index/expected/lir_06_const_prop.txt +2 -2
  27. data/golden/array_index/expected/schema_ruby.rb +1 -1
  28. data/golden/array_index/expected/snast.txt +2 -2
  29. data/golden/array_operations/expected/lir_00_unoptimized.txt +2 -2
  30. data/golden/array_operations/expected/lir_01_hoist_scalar_references.txt +2 -2
  31. data/golden/array_operations/expected/lir_02_inlined.txt +2 -2
  32. data/golden/array_operations/expected/lir_03_cse.txt +2 -2
  33. data/golden/array_operations/expected/lir_04_1_loop_fusion.txt +2 -2
  34. data/golden/array_operations/expected/lir_04_loop_invcm.txt +2 -2
  35. data/golden/array_operations/expected/lir_06_const_prop.txt +2 -2
  36. data/golden/array_operations/expected/schema_ruby.rb +1 -1
  37. data/golden/array_operations/expected/snast.txt +2 -2
  38. data/golden/cascade_logic/expected/schema_ruby.rb +1 -1
  39. data/golden/chained_fusion/expected/schema_ruby.rb +1 -1
  40. data/golden/decimal_explicit/expected/ast.txt +38 -0
  41. data/golden/decimal_explicit/expected/input_plan.txt +3 -0
  42. data/golden/decimal_explicit/expected/lir_00_unoptimized.txt +30 -0
  43. data/golden/decimal_explicit/expected/lir_01_hoist_scalar_references.txt +30 -0
  44. data/golden/decimal_explicit/expected/lir_02_inlined.txt +44 -0
  45. data/golden/decimal_explicit/expected/lir_03_cse.txt +40 -0
  46. data/golden/decimal_explicit/expected/lir_04_1_loop_fusion.txt +40 -0
  47. data/golden/decimal_explicit/expected/lir_04_loop_invcm.txt +40 -0
  48. data/golden/decimal_explicit/expected/lir_06_const_prop.txt +40 -0
  49. data/golden/decimal_explicit/expected/nast.txt +30 -0
  50. data/golden/decimal_explicit/expected/schema_javascript.mjs +31 -0
  51. data/golden/decimal_explicit/expected/schema_ruby.rb +57 -0
  52. data/golden/decimal_explicit/expected/snast.txt +30 -0
  53. data/golden/decimal_explicit/expected.json +1 -0
  54. data/golden/decimal_explicit/input.json +5 -0
  55. data/golden/decimal_explicit/schema.kumi +14 -0
  56. data/golden/element_arrays/expected/schema_ruby.rb +1 -1
  57. data/golden/empty_and_null_inputs/expected/schema_ruby.rb +1 -1
  58. data/golden/function_overload/expected/schema_ruby.rb +1 -1
  59. data/golden/game_of_life/expected/schema_ruby.rb +1 -1
  60. data/golden/hash_keys/expected/schema_ruby.rb +1 -1
  61. data/golden/hash_value/expected/schema_ruby.rb +1 -1
  62. data/golden/hierarchical_complex/expected/lir_00_unoptimized.txt +3 -3
  63. data/golden/hierarchical_complex/expected/lir_01_hoist_scalar_references.txt +3 -3
  64. data/golden/hierarchical_complex/expected/lir_02_inlined.txt +3 -3
  65. data/golden/hierarchical_complex/expected/lir_03_cse.txt +3 -3
  66. data/golden/hierarchical_complex/expected/lir_04_1_loop_fusion.txt +3 -3
  67. data/golden/hierarchical_complex/expected/lir_04_loop_invcm.txt +3 -3
  68. data/golden/hierarchical_complex/expected/lir_06_const_prop.txt +3 -3
  69. data/golden/hierarchical_complex/expected/schema_ruby.rb +1 -1
  70. data/golden/hierarchical_complex/expected/snast.txt +3 -3
  71. data/golden/inline_rename_scope_leak/expected/schema_ruby.rb +1 -1
  72. data/golden/input_reference/expected/schema_ruby.rb +1 -1
  73. data/golden/interleaved_fusion/expected/lir_00_unoptimized.txt +1 -1
  74. data/golden/interleaved_fusion/expected/lir_01_hoist_scalar_references.txt +1 -1
  75. data/golden/interleaved_fusion/expected/lir_02_inlined.txt +2 -2
  76. data/golden/interleaved_fusion/expected/lir_03_cse.txt +2 -2
  77. data/golden/interleaved_fusion/expected/lir_04_1_loop_fusion.txt +2 -2
  78. data/golden/interleaved_fusion/expected/lir_04_loop_invcm.txt +2 -2
  79. data/golden/interleaved_fusion/expected/lir_06_const_prop.txt +2 -2
  80. data/golden/interleaved_fusion/expected/schema_ruby.rb +1 -1
  81. data/golden/interleaved_fusion/expected/snast.txt +1 -1
  82. data/golden/let_inline/expected/lir_00_unoptimized.txt +2 -2
  83. data/golden/let_inline/expected/lir_01_hoist_scalar_references.txt +2 -2
  84. data/golden/let_inline/expected/lir_02_inlined.txt +6 -6
  85. data/golden/let_inline/expected/lir_03_cse.txt +6 -6
  86. data/golden/let_inline/expected/lir_04_1_loop_fusion.txt +6 -6
  87. data/golden/let_inline/expected/lir_04_loop_invcm.txt +6 -6
  88. data/golden/let_inline/expected/lir_06_const_prop.txt +6 -6
  89. data/golden/let_inline/expected/schema_ruby.rb +1 -1
  90. data/golden/let_inline/expected/snast.txt +2 -2
  91. data/golden/loop_fusion/expected/schema_ruby.rb +1 -1
  92. data/golden/min_reduce_scope/expected/schema_ruby.rb +1 -1
  93. data/golden/mixed_dimensions/expected/schema_ruby.rb +1 -1
  94. data/golden/multirank_hoisting/expected/lir_00_unoptimized.txt +2 -2
  95. data/golden/multirank_hoisting/expected/lir_01_hoist_scalar_references.txt +2 -2
  96. data/golden/multirank_hoisting/expected/lir_02_inlined.txt +7 -7
  97. data/golden/multirank_hoisting/expected/lir_03_cse.txt +7 -7
  98. data/golden/multirank_hoisting/expected/lir_04_1_loop_fusion.txt +7 -7
  99. data/golden/multirank_hoisting/expected/lir_04_loop_invcm.txt +7 -7
  100. data/golden/multirank_hoisting/expected/lir_06_const_prop.txt +7 -7
  101. data/golden/multirank_hoisting/expected/schema_ruby.rb +1 -1
  102. data/golden/multirank_hoisting/expected/snast.txt +2 -2
  103. data/golden/nested_hash/expected/lir_00_unoptimized.txt +1 -1
  104. data/golden/nested_hash/expected/lir_01_hoist_scalar_references.txt +1 -1
  105. data/golden/nested_hash/expected/lir_02_inlined.txt +1 -1
  106. data/golden/nested_hash/expected/lir_03_cse.txt +1 -1
  107. data/golden/nested_hash/expected/lir_04_1_loop_fusion.txt +1 -1
  108. data/golden/nested_hash/expected/lir_04_loop_invcm.txt +1 -1
  109. data/golden/nested_hash/expected/lir_06_const_prop.txt +1 -1
  110. data/golden/nested_hash/expected/schema_ruby.rb +1 -1
  111. data/golden/nested_hash/expected/snast.txt +1 -1
  112. data/golden/reduction_broadcast/expected/schema_ruby.rb +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 +2 -2
  117. data/golden/simple_math/expected/lir_01_hoist_scalar_references.txt +2 -2
  118. data/golden/simple_math/expected/lir_02_inlined.txt +2 -2
  119. data/golden/simple_math/expected/lir_03_cse.txt +2 -2
  120. data/golden/simple_math/expected/lir_04_1_loop_fusion.txt +2 -2
  121. data/golden/simple_math/expected/lir_04_loop_invcm.txt +2 -2
  122. data/golden/simple_math/expected/lir_06_const_prop.txt +2 -2
  123. data/golden/simple_math/expected/schema_ruby.rb +1 -1
  124. data/golden/simple_math/expected/snast.txt +2 -2
  125. data/golden/streaming_basics/expected/lir_00_unoptimized.txt +3 -3
  126. data/golden/streaming_basics/expected/lir_01_hoist_scalar_references.txt +3 -3
  127. data/golden/streaming_basics/expected/lir_02_inlined.txt +9 -9
  128. data/golden/streaming_basics/expected/lir_03_cse.txt +7 -7
  129. data/golden/streaming_basics/expected/lir_04_1_loop_fusion.txt +7 -7
  130. data/golden/streaming_basics/expected/lir_04_loop_invcm.txt +7 -7
  131. data/golden/streaming_basics/expected/lir_06_const_prop.txt +7 -7
  132. data/golden/streaming_basics/expected/schema_ruby.rb +1 -1
  133. data/golden/streaming_basics/expected/snast.txt +3 -3
  134. data/golden/tuples/expected/schema_ruby.rb +1 -1
  135. data/golden/tuples_and_arrays/expected/schema_ruby.rb +1 -1
  136. data/golden/us_tax_2024/expected/lir_00_unoptimized.txt +6 -6
  137. data/golden/us_tax_2024/expected/lir_01_hoist_scalar_references.txt +6 -6
  138. data/golden/us_tax_2024/expected/lir_02_inlined.txt +71 -71
  139. data/golden/us_tax_2024/expected/lir_03_cse.txt +43 -43
  140. data/golden/us_tax_2024/expected/lir_04_1_loop_fusion.txt +48 -48
  141. data/golden/us_tax_2024/expected/lir_04_loop_invcm.txt +43 -43
  142. data/golden/us_tax_2024/expected/lir_06_const_prop.txt +43 -43
  143. data/golden/us_tax_2024/expected/schema_ruby.rb +1 -1
  144. data/golden/us_tax_2024/expected/snast.txt +6 -6
  145. data/golden/with_constants/expected/schema_ruby.rb +1 -1
  146. data/lib/kumi/configuration.rb +6 -0
  147. data/lib/kumi/core/analyzer/passes/nast_dimensional_analyzer_pass.rb +1 -1
  148. data/lib/kumi/core/error_reporter.rb +1 -1
  149. data/lib/kumi/core/errors.rb +1 -1
  150. data/lib/kumi/core/functions/overload_resolver.rb +57 -11
  151. data/lib/kumi/core/functions/type_categories.rb +44 -0
  152. data/lib/kumi/core/input/type_matcher.rb +8 -1
  153. data/lib/kumi/core/ruby_parser/input_builder.rb +2 -2
  154. data/lib/kumi/core/types/normalizer.rb +1 -0
  155. data/lib/kumi/core/types/validator.rb +2 -2
  156. data/lib/kumi/core/types.rb +2 -2
  157. data/lib/kumi/dev/golden/reporter.rb +9 -0
  158. data/lib/kumi/dev/golden/result.rb +3 -1
  159. data/lib/kumi/dev/golden/runtime_test.rb +25 -0
  160. data/lib/kumi/dev/golden/suite.rb +4 -4
  161. data/lib/kumi/dev/golden/value_normalizer.rb +80 -0
  162. data/lib/kumi/dev/golden.rb +21 -12
  163. data/lib/kumi/doc_generator/formatters/json.rb +39 -0
  164. data/lib/kumi/doc_generator/formatters/markdown.rb +175 -0
  165. data/lib/kumi/doc_generator/loader.rb +37 -0
  166. data/lib/kumi/doc_generator/merger.rb +54 -0
  167. data/lib/kumi/doc_generator.rb +4 -0
  168. data/lib/kumi/frontends/text.rb +33 -5
  169. data/lib/kumi/syntax/location.rb +5 -1
  170. data/lib/kumi/version.rb +1 -1
  171. data/vscode-extension/.gitignore +4 -0
  172. data/vscode-extension/README.md +59 -0
  173. data/vscode-extension/TESTING.md +151 -0
  174. data/vscode-extension/package.json +51 -0
  175. data/vscode-extension/src/extension.ts +295 -0
  176. data/vscode-extension/tsconfig.json +15 -0
  177. metadata +38 -1
@@ -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
@@ -13,20 +13,48 @@ module Kumi
13
13
  raise ArgumentError, "provide either :path or :src" if (path.nil? && src.nil?) || (path && src)
14
14
 
15
15
  src ||= File.read(path)
16
- file_label = path || "(string)"
16
+ file_label = path || "schema"
17
17
 
18
18
  begin
19
19
  require "kumi-parser"
20
- ast = Kumi::Parser::TextParser.parse(src)
20
+ ast = Kumi::Parser::TextParser.parse(src, source_file: path || "schema")
21
21
  Core::Analyzer::Debug.info(:parse, kind: :text, file: file_label, ok: true) if Core::Analyzer::Debug.enabled?
22
22
  [ast, inputs]
23
23
  rescue LoadError
24
24
  raise "kumi-parser gem not available. Install: gem install kumi-parser"
25
25
  rescue StandardError => e
26
- loc = (e.respond_to?(:location) && e.location) || {}
27
- line, col = loc.values_at(:line, :column)
26
+ # Try to extract line/column from exception object first
27
+ line, col = extract_line_column(e)
28
28
  snippet = code_frame(src, line, col)
29
- raise StandardError, "#{file_label}:#{line || '?'}:#{col || '?'}: #{e.message}\n#{snippet}"
29
+
30
+ # Strip file:line:col prefix from e.message if it exists (from parser)
31
+ # Also strip embedded "at FILE line=N column=M" to avoid duplication
32
+ error_message = e.message
33
+ .sub(/^\S+:\d+:\d+:\s+/, '')
34
+ .gsub(/\s+at\s+\S+\s+line=\d+\s+column=\d+/, '')
35
+ .strip
36
+ raise StandardError, "#{file_label}:#{line || '?'}:#{col || '?'}: #{error_message}\n#{snippet}"
37
+ end
38
+ end
39
+
40
+ def self.extract_line_column(exception)
41
+ # Try to access Location object from exception
42
+ if exception.respond_to?(:location) && exception.location
43
+ loc = exception.location
44
+ if loc.respond_to?(:line) && loc.respond_to?(:column)
45
+ return [loc.line, loc.column]
46
+ end
47
+ end
48
+
49
+ # Fall back to parsing error message if no Location object
50
+ extract_line_column_from_message(exception.message)
51
+ end
52
+
53
+ def self.extract_line_column_from_message(message)
54
+ if message =~ /line=(\d+)\s+column=(\d+)/
55
+ [::Regexp.last_match(1).to_i, ::Regexp.last_match(2).to_i]
56
+ else
57
+ [nil, nil]
30
58
  end
31
59
  end
32
60
 
@@ -1,5 +1,9 @@
1
1
  module Kumi
2
2
  module Syntax
3
- Location = Struct.new(:file, :line, :column, keyword_init: true)
3
+ class Location < Struct.new(:file, :line, :column, keyword_init: true)
4
+ def to_s
5
+ "#{file} line=#{line} column=#{column}"
6
+ end
7
+ end
4
8
  end
5
9
  end
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.26"
4
+ VERSION = "0.0.28"
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.
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "kumi-language-support",
3
+ "displayName": "Kumi Language Support",
4
+ "description": "Language support for Kumi DSL with autocomplete and hover information",
5
+ "version": "0.1.0",
6
+ "publisher": "kumi",
7
+ "engines": {
8
+ "vscode": "^1.80.0"
9
+ },
10
+ "categories": [
11
+ "Programming Languages",
12
+ "Snippets"
13
+ ],
14
+ "activationEvents": [
15
+ "onLanguage:ruby",
16
+ "onLanguage:kumi"
17
+ ],
18
+ "main": "./out/extension.js",
19
+ "contributes": {
20
+ "languages": [
21
+ {
22
+ "id": "kumi",
23
+ "aliases": [
24
+ "Kumi",
25
+ "kumi"
26
+ ],
27
+ "extensions": [
28
+ ".kumi"
29
+ ],
30
+ "configuration": "./language-configuration.json"
31
+ }
32
+ ],
33
+ "grammars": [
34
+ {
35
+ "language": "kumi",
36
+ "scopeName": "source.kumi",
37
+ "path": "./syntaxes/kumi.tmLanguage.json"
38
+ }
39
+ ]
40
+ },
41
+ "scripts": {
42
+ "vscode:prepublish": "npm run compile",
43
+ "compile": "tsc -p ./",
44
+ "watch": "tsc -watch -p ./"
45
+ },
46
+ "devDependencies": {
47
+ "@types/node": "^20.0.0",
48
+ "@types/vscode": "^1.80.0",
49
+ "typescript": "^5.0.0"
50
+ }
51
+ }