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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -0
- data/CLAUDE.md +4 -0
- data/README.md +36 -12
- data/data/functions/core/arithmetic.yaml +28 -8
- data/data/functions/core/boolean.yaml +8 -3
- data/data/functions/core/comparison.yaml +12 -4
- data/data/functions/core/conversion.yaml +32 -0
- data/data/kernels/javascript/core/arithmetic.yaml +6 -2
- data/data/kernels/javascript/core/coercion.yaml +20 -0
- data/data/kernels/ruby/core/arithmetic.yaml +7 -2
- data/data/kernels/ruby/core/coercion.yaml +20 -0
- data/docs/ARCHITECTURE.md +277 -0
- data/docs/DEVELOPMENT.md +62 -0
- data/docs/FUNCTIONS.md +955 -0
- data/docs/SYNTAX.md +8 -0
- data/docs/VSCODE_EXTENSION.md +114 -0
- data/docs/functions-reference.json +1821 -0
- data/golden/array_element/expected/schema_ruby.rb +1 -1
- data/golden/array_index/expected/lir_00_unoptimized.txt +2 -2
- data/golden/array_index/expected/lir_01_hoist_scalar_references.txt +2 -2
- data/golden/array_index/expected/lir_02_inlined.txt +2 -2
- data/golden/array_index/expected/lir_03_cse.txt +2 -2
- data/golden/array_index/expected/lir_04_1_loop_fusion.txt +2 -2
- data/golden/array_index/expected/lir_04_loop_invcm.txt +2 -2
- data/golden/array_index/expected/lir_06_const_prop.txt +2 -2
- data/golden/array_index/expected/schema_ruby.rb +1 -1
- data/golden/array_index/expected/snast.txt +2 -2
- data/golden/array_operations/expected/lir_00_unoptimized.txt +2 -2
- data/golden/array_operations/expected/lir_01_hoist_scalar_references.txt +2 -2
- data/golden/array_operations/expected/lir_02_inlined.txt +2 -2
- data/golden/array_operations/expected/lir_03_cse.txt +2 -2
- data/golden/array_operations/expected/lir_04_1_loop_fusion.txt +2 -2
- data/golden/array_operations/expected/lir_04_loop_invcm.txt +2 -2
- data/golden/array_operations/expected/lir_06_const_prop.txt +2 -2
- data/golden/array_operations/expected/schema_ruby.rb +1 -1
- data/golden/array_operations/expected/snast.txt +2 -2
- data/golden/cascade_logic/expected/schema_ruby.rb +1 -1
- data/golden/chained_fusion/expected/schema_ruby.rb +1 -1
- data/golden/decimal_explicit/expected/ast.txt +38 -0
- data/golden/decimal_explicit/expected/input_plan.txt +3 -0
- data/golden/decimal_explicit/expected/lir_00_unoptimized.txt +30 -0
- data/golden/decimal_explicit/expected/lir_01_hoist_scalar_references.txt +30 -0
- data/golden/decimal_explicit/expected/lir_02_inlined.txt +44 -0
- data/golden/decimal_explicit/expected/lir_03_cse.txt +40 -0
- data/golden/decimal_explicit/expected/lir_04_1_loop_fusion.txt +40 -0
- data/golden/decimal_explicit/expected/lir_04_loop_invcm.txt +40 -0
- data/golden/decimal_explicit/expected/lir_06_const_prop.txt +40 -0
- data/golden/decimal_explicit/expected/nast.txt +30 -0
- data/golden/decimal_explicit/expected/schema_javascript.mjs +31 -0
- data/golden/decimal_explicit/expected/schema_ruby.rb +57 -0
- data/golden/decimal_explicit/expected/snast.txt +30 -0
- data/golden/decimal_explicit/expected.json +1 -0
- data/golden/decimal_explicit/input.json +5 -0
- data/golden/decimal_explicit/schema.kumi +14 -0
- data/golden/element_arrays/expected/schema_ruby.rb +1 -1
- data/golden/empty_and_null_inputs/expected/schema_ruby.rb +1 -1
- data/golden/function_overload/expected/schema_ruby.rb +1 -1
- data/golden/game_of_life/expected/schema_ruby.rb +1 -1
- data/golden/hash_keys/expected/schema_ruby.rb +1 -1
- data/golden/hash_value/expected/schema_ruby.rb +1 -1
- data/golden/hierarchical_complex/expected/lir_00_unoptimized.txt +3 -3
- data/golden/hierarchical_complex/expected/lir_01_hoist_scalar_references.txt +3 -3
- data/golden/hierarchical_complex/expected/lir_02_inlined.txt +3 -3
- data/golden/hierarchical_complex/expected/lir_03_cse.txt +3 -3
- data/golden/hierarchical_complex/expected/lir_04_1_loop_fusion.txt +3 -3
- data/golden/hierarchical_complex/expected/lir_04_loop_invcm.txt +3 -3
- data/golden/hierarchical_complex/expected/lir_06_const_prop.txt +3 -3
- data/golden/hierarchical_complex/expected/schema_ruby.rb +1 -1
- data/golden/hierarchical_complex/expected/snast.txt +3 -3
- data/golden/inline_rename_scope_leak/expected/schema_ruby.rb +1 -1
- data/golden/input_reference/expected/schema_ruby.rb +1 -1
- data/golden/interleaved_fusion/expected/lir_00_unoptimized.txt +1 -1
- data/golden/interleaved_fusion/expected/lir_01_hoist_scalar_references.txt +1 -1
- data/golden/interleaved_fusion/expected/lir_02_inlined.txt +2 -2
- data/golden/interleaved_fusion/expected/lir_03_cse.txt +2 -2
- data/golden/interleaved_fusion/expected/lir_04_1_loop_fusion.txt +2 -2
- data/golden/interleaved_fusion/expected/lir_04_loop_invcm.txt +2 -2
- data/golden/interleaved_fusion/expected/lir_06_const_prop.txt +2 -2
- data/golden/interleaved_fusion/expected/schema_ruby.rb +1 -1
- data/golden/interleaved_fusion/expected/snast.txt +1 -1
- data/golden/let_inline/expected/lir_00_unoptimized.txt +2 -2
- data/golden/let_inline/expected/lir_01_hoist_scalar_references.txt +2 -2
- data/golden/let_inline/expected/lir_02_inlined.txt +6 -6
- data/golden/let_inline/expected/lir_03_cse.txt +6 -6
- data/golden/let_inline/expected/lir_04_1_loop_fusion.txt +6 -6
- data/golden/let_inline/expected/lir_04_loop_invcm.txt +6 -6
- data/golden/let_inline/expected/lir_06_const_prop.txt +6 -6
- data/golden/let_inline/expected/schema_ruby.rb +1 -1
- data/golden/let_inline/expected/snast.txt +2 -2
- data/golden/loop_fusion/expected/schema_ruby.rb +1 -1
- data/golden/min_reduce_scope/expected/schema_ruby.rb +1 -1
- data/golden/mixed_dimensions/expected/schema_ruby.rb +1 -1
- data/golden/multirank_hoisting/expected/lir_00_unoptimized.txt +2 -2
- data/golden/multirank_hoisting/expected/lir_01_hoist_scalar_references.txt +2 -2
- data/golden/multirank_hoisting/expected/lir_02_inlined.txt +7 -7
- data/golden/multirank_hoisting/expected/lir_03_cse.txt +7 -7
- data/golden/multirank_hoisting/expected/lir_04_1_loop_fusion.txt +7 -7
- data/golden/multirank_hoisting/expected/lir_04_loop_invcm.txt +7 -7
- data/golden/multirank_hoisting/expected/lir_06_const_prop.txt +7 -7
- data/golden/multirank_hoisting/expected/schema_ruby.rb +1 -1
- data/golden/multirank_hoisting/expected/snast.txt +2 -2
- data/golden/nested_hash/expected/lir_00_unoptimized.txt +1 -1
- data/golden/nested_hash/expected/lir_01_hoist_scalar_references.txt +1 -1
- data/golden/nested_hash/expected/lir_02_inlined.txt +1 -1
- data/golden/nested_hash/expected/lir_03_cse.txt +1 -1
- data/golden/nested_hash/expected/lir_04_1_loop_fusion.txt +1 -1
- data/golden/nested_hash/expected/lir_04_loop_invcm.txt +1 -1
- data/golden/nested_hash/expected/lir_06_const_prop.txt +1 -1
- data/golden/nested_hash/expected/schema_ruby.rb +1 -1
- data/golden/nested_hash/expected/snast.txt +1 -1
- data/golden/reduction_broadcast/expected/schema_ruby.rb +1 -1
- data/golden/roll/expected/schema_ruby.rb +1 -1
- data/golden/shift/expected/schema_ruby.rb +1 -1
- data/golden/shift_2d/expected/schema_ruby.rb +1 -1
- data/golden/simple_math/expected/lir_00_unoptimized.txt +2 -2
- data/golden/simple_math/expected/lir_01_hoist_scalar_references.txt +2 -2
- data/golden/simple_math/expected/lir_02_inlined.txt +2 -2
- data/golden/simple_math/expected/lir_03_cse.txt +2 -2
- data/golden/simple_math/expected/lir_04_1_loop_fusion.txt +2 -2
- data/golden/simple_math/expected/lir_04_loop_invcm.txt +2 -2
- data/golden/simple_math/expected/lir_06_const_prop.txt +2 -2
- data/golden/simple_math/expected/schema_ruby.rb +1 -1
- data/golden/simple_math/expected/snast.txt +2 -2
- data/golden/streaming_basics/expected/lir_00_unoptimized.txt +3 -3
- data/golden/streaming_basics/expected/lir_01_hoist_scalar_references.txt +3 -3
- data/golden/streaming_basics/expected/lir_02_inlined.txt +9 -9
- data/golden/streaming_basics/expected/lir_03_cse.txt +7 -7
- data/golden/streaming_basics/expected/lir_04_1_loop_fusion.txt +7 -7
- data/golden/streaming_basics/expected/lir_04_loop_invcm.txt +7 -7
- data/golden/streaming_basics/expected/lir_06_const_prop.txt +7 -7
- data/golden/streaming_basics/expected/schema_ruby.rb +1 -1
- data/golden/streaming_basics/expected/snast.txt +3 -3
- data/golden/tuples/expected/schema_ruby.rb +1 -1
- data/golden/tuples_and_arrays/expected/schema_ruby.rb +1 -1
- data/golden/us_tax_2024/expected/lir_00_unoptimized.txt +6 -6
- data/golden/us_tax_2024/expected/lir_01_hoist_scalar_references.txt +6 -6
- data/golden/us_tax_2024/expected/lir_02_inlined.txt +71 -71
- data/golden/us_tax_2024/expected/lir_03_cse.txt +43 -43
- data/golden/us_tax_2024/expected/lir_04_1_loop_fusion.txt +48 -48
- data/golden/us_tax_2024/expected/lir_04_loop_invcm.txt +43 -43
- data/golden/us_tax_2024/expected/lir_06_const_prop.txt +43 -43
- data/golden/us_tax_2024/expected/schema_ruby.rb +1 -1
- data/golden/us_tax_2024/expected/snast.txt +6 -6
- data/golden/with_constants/expected/schema_ruby.rb +1 -1
- data/lib/kumi/configuration.rb +6 -0
- data/lib/kumi/core/analyzer/passes/nast_dimensional_analyzer_pass.rb +1 -1
- data/lib/kumi/core/error_reporter.rb +1 -1
- data/lib/kumi/core/errors.rb +1 -1
- data/lib/kumi/core/functions/overload_resolver.rb +57 -11
- data/lib/kumi/core/functions/type_categories.rb +44 -0
- data/lib/kumi/core/input/type_matcher.rb +8 -1
- data/lib/kumi/core/ruby_parser/input_builder.rb +2 -2
- data/lib/kumi/core/types/normalizer.rb +1 -0
- data/lib/kumi/core/types/validator.rb +2 -2
- data/lib/kumi/core/types.rb +2 -2
- data/lib/kumi/dev/golden/reporter.rb +9 -0
- data/lib/kumi/dev/golden/result.rb +3 -1
- data/lib/kumi/dev/golden/runtime_test.rb +25 -0
- data/lib/kumi/dev/golden/suite.rb +4 -4
- data/lib/kumi/dev/golden/value_normalizer.rb +80 -0
- data/lib/kumi/dev/golden.rb +21 -12
- data/lib/kumi/doc_generator/formatters/json.rb +39 -0
- data/lib/kumi/doc_generator/formatters/markdown.rb +175 -0
- data/lib/kumi/doc_generator/loader.rb +37 -0
- data/lib/kumi/doc_generator/merger.rb +54 -0
- data/lib/kumi/doc_generator.rb +4 -0
- data/lib/kumi/frontends/text.rb +33 -5
- data/lib/kumi/syntax/location.rb +5 -1
- data/lib/kumi/version.rb +1 -1
- data/vscode-extension/.gitignore +4 -0
- data/vscode-extension/README.md +59 -0
- data/vscode-extension/TESTING.md +151 -0
- data/vscode-extension/package.json +51 -0
- data/vscode-extension/src/extension.ts +295 -0
- data/vscode-extension/tsconfig.json +15 -0
- 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
|
data/lib/kumi/frontends/text.rb
CHANGED
@@ -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 || "
|
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
|
-
|
27
|
-
line, col =
|
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
|
-
|
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
|
|
data/lib/kumi/syntax/location.rb
CHANGED
data/lib/kumi/version.rb
CHANGED
@@ -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
|
+
}
|