kumi 0.0.26 → 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 (85) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +7 -0
  3. data/CLAUDE.md +4 -0
  4. data/README.md +17 -8
  5. data/data/functions/core/conversion.yaml +32 -0
  6. data/data/kernels/javascript/core/coercion.yaml +20 -0
  7. data/data/kernels/ruby/core/coercion.yaml +20 -0
  8. data/docs/ARCHITECTURE.md +277 -0
  9. data/docs/DEVELOPMENT.md +62 -0
  10. data/docs/FUNCTIONS.md +955 -0
  11. data/docs/SYNTAX.md +8 -0
  12. data/docs/VSCODE_EXTENSION.md +114 -0
  13. data/docs/functions-reference.json +1821 -0
  14. data/golden/array_element/expected/schema_ruby.rb +1 -1
  15. data/golden/array_index/expected/schema_ruby.rb +1 -1
  16. data/golden/array_operations/expected/schema_ruby.rb +1 -1
  17. data/golden/cascade_logic/expected/schema_ruby.rb +1 -1
  18. data/golden/chained_fusion/expected/schema_ruby.rb +1 -1
  19. data/golden/decimal_explicit/expected/ast.txt +38 -0
  20. data/golden/decimal_explicit/expected/input_plan.txt +3 -0
  21. data/golden/decimal_explicit/expected/lir_00_unoptimized.txt +30 -0
  22. data/golden/decimal_explicit/expected/lir_01_hoist_scalar_references.txt +30 -0
  23. data/golden/decimal_explicit/expected/lir_02_inlined.txt +44 -0
  24. data/golden/decimal_explicit/expected/lir_03_cse.txt +40 -0
  25. data/golden/decimal_explicit/expected/lir_04_1_loop_fusion.txt +40 -0
  26. data/golden/decimal_explicit/expected/lir_04_loop_invcm.txt +40 -0
  27. data/golden/decimal_explicit/expected/lir_06_const_prop.txt +40 -0
  28. data/golden/decimal_explicit/expected/nast.txt +30 -0
  29. data/golden/decimal_explicit/expected/schema_javascript.mjs +31 -0
  30. data/golden/decimal_explicit/expected/schema_ruby.rb +57 -0
  31. data/golden/decimal_explicit/expected/snast.txt +30 -0
  32. data/golden/decimal_explicit/expected.json +1 -0
  33. data/golden/decimal_explicit/input.json +5 -0
  34. data/golden/decimal_explicit/schema.kumi +14 -0
  35. data/golden/element_arrays/expected/schema_ruby.rb +1 -1
  36. data/golden/empty_and_null_inputs/expected/schema_ruby.rb +1 -1
  37. data/golden/function_overload/expected/schema_ruby.rb +1 -1
  38. data/golden/game_of_life/expected/schema_ruby.rb +1 -1
  39. data/golden/hash_keys/expected/schema_ruby.rb +1 -1
  40. data/golden/hash_value/expected/schema_ruby.rb +1 -1
  41. data/golden/hierarchical_complex/expected/schema_ruby.rb +1 -1
  42. data/golden/inline_rename_scope_leak/expected/schema_ruby.rb +1 -1
  43. data/golden/input_reference/expected/schema_ruby.rb +1 -1
  44. data/golden/interleaved_fusion/expected/schema_ruby.rb +1 -1
  45. data/golden/let_inline/expected/schema_ruby.rb +1 -1
  46. data/golden/loop_fusion/expected/schema_ruby.rb +1 -1
  47. data/golden/min_reduce_scope/expected/schema_ruby.rb +1 -1
  48. data/golden/mixed_dimensions/expected/schema_ruby.rb +1 -1
  49. data/golden/multirank_hoisting/expected/schema_ruby.rb +1 -1
  50. data/golden/nested_hash/expected/schema_ruby.rb +1 -1
  51. data/golden/reduction_broadcast/expected/schema_ruby.rb +1 -1
  52. data/golden/roll/expected/schema_ruby.rb +1 -1
  53. data/golden/shift/expected/schema_ruby.rb +1 -1
  54. data/golden/shift_2d/expected/schema_ruby.rb +1 -1
  55. data/golden/simple_math/expected/schema_ruby.rb +1 -1
  56. data/golden/streaming_basics/expected/schema_ruby.rb +1 -1
  57. data/golden/tuples/expected/schema_ruby.rb +1 -1
  58. data/golden/tuples_and_arrays/expected/schema_ruby.rb +1 -1
  59. data/golden/us_tax_2024/expected/schema_ruby.rb +1 -1
  60. data/golden/with_constants/expected/schema_ruby.rb +1 -1
  61. data/lib/kumi/configuration.rb +6 -0
  62. data/lib/kumi/core/input/type_matcher.rb +8 -1
  63. data/lib/kumi/core/ruby_parser/input_builder.rb +2 -2
  64. data/lib/kumi/core/types/normalizer.rb +1 -0
  65. data/lib/kumi/core/types/validator.rb +2 -2
  66. data/lib/kumi/core/types.rb +2 -2
  67. data/lib/kumi/dev/golden/reporter.rb +9 -0
  68. data/lib/kumi/dev/golden/result.rb +3 -1
  69. data/lib/kumi/dev/golden/runtime_test.rb +25 -0
  70. data/lib/kumi/dev/golden/suite.rb +4 -4
  71. data/lib/kumi/dev/golden/value_normalizer.rb +80 -0
  72. data/lib/kumi/dev/golden.rb +21 -12
  73. data/lib/kumi/doc_generator/formatters/json.rb +39 -0
  74. data/lib/kumi/doc_generator/formatters/markdown.rb +175 -0
  75. data/lib/kumi/doc_generator/loader.rb +37 -0
  76. data/lib/kumi/doc_generator/merger.rb +54 -0
  77. data/lib/kumi/doc_generator.rb +4 -0
  78. data/lib/kumi/version.rb +1 -1
  79. data/vscode-extension/.gitignore +4 -0
  80. data/vscode-extension/README.md +59 -0
  81. data/vscode-extension/TESTING.md +151 -0
  82. data/vscode-extension/package.json +51 -0
  83. data/vscode-extension/src/extension.ts +295 -0
  84. data/vscode-extension/tsconfig.json +15 -0
  85. metadata +37 -1
@@ -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
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.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.
@@ -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
+ }
@@ -0,0 +1,295 @@
1
+ import * as vscode from 'vscode';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+
5
+ interface FunctionDef {
6
+ id: string;
7
+ arity: number;
8
+ params: Array<{ name: string }>;
9
+ dtype: any;
10
+ kernels: Record<string, string>;
11
+ aliases: string[];
12
+ kind?: string;
13
+ }
14
+
15
+ let functionsData: Record<string, FunctionDef> = {};
16
+
17
+ export function activate(context: vscode.ExtensionContext) {
18
+ // Load functions reference JSON from kumi/docs/
19
+ const refPath = path.join(
20
+ context.extensionPath,
21
+ '..',
22
+ '..',
23
+ 'docs',
24
+ 'functions-reference.json'
25
+ );
26
+
27
+ try {
28
+ if (fs.existsSync(refPath)) {
29
+ const content = fs.readFileSync(refPath, 'utf-8');
30
+ functionsData = JSON.parse(content);
31
+ console.log('Kumi functions reference loaded');
32
+ } else {
33
+ vscode.window.showWarningMessage(
34
+ 'Kumi: functions-reference.json not found. Run "bin/kumi-doc-gen" to generate it.'
35
+ );
36
+ }
37
+ } catch (e) {
38
+ console.error('Failed to load functions reference:', e);
39
+ vscode.window.showErrorMessage(
40
+ 'Kumi: Error loading functions reference: ' + String(e)
41
+ );
42
+ }
43
+
44
+ // Register completion provider for both Ruby and Kumi files
45
+ const completionProvider = vscode.languages.registerCompletionItemProvider(
46
+ [
47
+ { language: 'ruby', scheme: 'file' },
48
+ { language: 'kumi', scheme: 'file' }
49
+ ],
50
+ new FunctionCompletionProvider(),
51
+ ':' // trigger on ':'
52
+ );
53
+
54
+ // Register hover provider for both Ruby and Kumi files
55
+ const hoverProvider = vscode.languages.registerHoverProvider(
56
+ [
57
+ { language: 'ruby', scheme: 'file' },
58
+ { language: 'kumi', scheme: 'file' }
59
+ ],
60
+ new FunctionHoverProvider()
61
+ );
62
+
63
+ context.subscriptions.push(completionProvider, hoverProvider);
64
+ }
65
+
66
+ class FunctionCompletionProvider implements vscode.CompletionItemProvider {
67
+ provideCompletionItems(
68
+ document: vscode.TextDocument,
69
+ position: vscode.Position,
70
+ token: vscode.CancellationToken,
71
+ context: vscode.CompletionContext
72
+ ): vscode.CompletionItem[] {
73
+ // Check if we're in an fn(:...) context
74
+ const lineText = document.lineAt(position).text;
75
+ const beforeCursor = lineText.substring(0, position.character);
76
+
77
+ if (!beforeCursor.match(/fn\(\s*:\s*$/)) {
78
+ return [];
79
+ }
80
+
81
+ // For Ruby files, check if we're inside a schema block
82
+ if (document.languageId === 'ruby') {
83
+ if (!this.isInSchemaBlock(document, position)) {
84
+ return [];
85
+ }
86
+ }
87
+
88
+ const completions: vscode.CompletionItem[] = [];
89
+
90
+ for (const [alias, funcDef] of Object.entries(functionsData)) {
91
+ const item = new vscode.CompletionItem(
92
+ alias,
93
+ vscode.CompletionItemKind.Function
94
+ );
95
+
96
+ item.detail = `${funcDef.id} (arity: ${funcDef.arity})`;
97
+ item.documentation = new vscode.MarkdownString(
98
+ this.getDocumentation(funcDef)
99
+ );
100
+ item.sortText = alias;
101
+
102
+ completions.push(item);
103
+ }
104
+
105
+ return completions;
106
+ }
107
+
108
+ private getDocumentation(funcDef: FunctionDef): string {
109
+ const lines: string[] = [
110
+ `**${funcDef.id}**`,
111
+ '',
112
+ `**Arity:** ${funcDef.arity}`,
113
+ ];
114
+
115
+ if (funcDef.params && funcDef.params.length > 0) {
116
+ lines.push('**Parameters:**');
117
+ funcDef.params.forEach((p) => {
118
+ lines.push(`- \`${p.name}\``);
119
+ });
120
+ }
121
+
122
+ if (funcDef.dtype) {
123
+ lines.push(`**Type:** ${this.formatType(funcDef.dtype)}`);
124
+ }
125
+
126
+ if (funcDef.kernels && Object.keys(funcDef.kernels).length > 0) {
127
+ lines.push('**Kernels:**');
128
+ Object.entries(funcDef.kernels).forEach(([target, id]) => {
129
+ lines.push(`- ${target}: \`${id}\``);
130
+ });
131
+ }
132
+
133
+ return lines.join('\n');
134
+ }
135
+
136
+ private formatType(dtype: any): string {
137
+ if (!dtype) return 'unknown';
138
+
139
+ switch (dtype.rule) {
140
+ case 'same_as':
141
+ return `same as \`${dtype.param}\``;
142
+ case 'scalar':
143
+ return dtype.kind || 'scalar';
144
+ case 'promote':
145
+ return `promoted from ${dtype.params.map((p: string) => `\`${p}\``).join(', ')}`;
146
+ case 'element_of':
147
+ return `element of \`${dtype.param}\``;
148
+ default:
149
+ return dtype.rule;
150
+ }
151
+ }
152
+
153
+ private isInSchemaBlock(document: vscode.TextDocument, position: vscode.Position): boolean {
154
+ let braceCount = 0;
155
+ let schemaFound = false;
156
+
157
+ // Search backwards from current position to find schema block
158
+ for (let i = position.line; i >= 0; i--) {
159
+ const line = document.lineAt(i).text;
160
+
161
+ // Count braces from end of line backwards
162
+ if (i === position.line) {
163
+ // Only count braces before cursor
164
+ for (let j = position.character - 1; j >= 0; j--) {
165
+ if (line[j] === '}') braceCount++;
166
+ if (line[j] === '{') braceCount--;
167
+ }
168
+ } else {
169
+ // Count all braces in the line (right to left)
170
+ for (let j = line.length - 1; j >= 0; j--) {
171
+ if (line[j] === '}') braceCount++;
172
+ if (line[j] === '{') braceCount--;
173
+ }
174
+ }
175
+
176
+ // Look for 'schema do' or 'schema {'
177
+ if (line.match(/\bschema\s+(do|{)/)) {
178
+ schemaFound = braceCount <= 0;
179
+ break;
180
+ }
181
+ }
182
+
183
+ return schemaFound;
184
+ }
185
+ }
186
+
187
+ class FunctionHoverProvider implements vscode.HoverProvider {
188
+ provideHover(
189
+ document: vscode.TextDocument,
190
+ position: vscode.Position,
191
+ token: vscode.CancellationToken
192
+ ): vscode.ProviderResult<vscode.Hover> {
193
+ const range = document.getWordRangeAtPosition(position);
194
+ if (!range) return null;
195
+
196
+ const word = document.getText(range);
197
+
198
+ // Check if we're in fn(:word, ...)
199
+ const lineText = document.lineAt(position).text;
200
+ if (!lineText.match(/fn\s*\(\s*:/)) {
201
+ return null;
202
+ }
203
+
204
+ // For Ruby files, check if we're inside a schema block
205
+ if (document.languageId === 'ruby') {
206
+ if (!this.isInSchemaBlock(document, position)) {
207
+ return null;
208
+ }
209
+ }
210
+
211
+ const funcDef = functionsData[word];
212
+ if (!funcDef) {
213
+ return null;
214
+ }
215
+
216
+ const markdown = new vscode.MarkdownString();
217
+ markdown.appendMarkdown(`### \`${funcDef.id}\`\n\n`);
218
+
219
+ if (funcDef.aliases && funcDef.aliases.length > 0) {
220
+ markdown.appendMarkdown(
221
+ `**Aliases:** ${funcDef.aliases.map((a) => `\`${a}\``).join(', ')}\n\n`
222
+ );
223
+ }
224
+
225
+ markdown.appendMarkdown(`**Arity:** ${funcDef.arity}\n\n`);
226
+
227
+ if (funcDef.dtype) {
228
+ markdown.appendMarkdown(`**Type:** ${this.formatType(funcDef.dtype)}\n\n`);
229
+ }
230
+
231
+ if (funcDef.params && funcDef.params.length > 0) {
232
+ markdown.appendMarkdown('**Parameters:**\n\n');
233
+ funcDef.params.forEach((p) => {
234
+ markdown.appendMarkdown(`- \`${p.name}\`\n`);
235
+ });
236
+ markdown.appendMarkdown('\n');
237
+ }
238
+
239
+ return new vscode.Hover(markdown);
240
+ }
241
+
242
+ private formatType(dtype: any): string {
243
+ if (!dtype) return 'unknown';
244
+
245
+ switch (dtype.rule) {
246
+ case 'same_as':
247
+ return `same as \`${dtype.param}\``;
248
+ case 'scalar':
249
+ return dtype.kind || 'scalar';
250
+ case 'promote':
251
+ return `promoted from ${dtype.params
252
+ .map((p: string) => `\`${p}\``)
253
+ .join(', ')}`;
254
+ case 'element_of':
255
+ return `element of \`${dtype.param}\``;
256
+ default:
257
+ return dtype.rule;
258
+ }
259
+ }
260
+
261
+ private isInSchemaBlock(document: vscode.TextDocument, position: vscode.Position): boolean {
262
+ let braceCount = 0;
263
+ let schemaFound = false;
264
+
265
+ // Search backwards from current position to find schema block
266
+ for (let i = position.line; i >= 0; i--) {
267
+ const line = document.lineAt(i).text;
268
+
269
+ // Count braces from end of line backwards
270
+ if (i === position.line) {
271
+ // Only count braces before cursor
272
+ for (let j = position.character - 1; j >= 0; j--) {
273
+ if (line[j] === '}') braceCount++;
274
+ if (line[j] === '{') braceCount--;
275
+ }
276
+ } else {
277
+ // Count all braces in the line (right to left)
278
+ for (let j = line.length - 1; j >= 0; j--) {
279
+ if (line[j] === '}') braceCount++;
280
+ if (line[j] === '{') braceCount--;
281
+ }
282
+ }
283
+
284
+ // Look for 'schema do' or 'schema {'
285
+ if (line.match(/\bschema\s+(do|{)/)) {
286
+ schemaFound = braceCount <= 0;
287
+ break;
288
+ }
289
+ }
290
+
291
+ return schemaFound;
292
+ }
293
+ }
294
+
295
+ export function deactivate() {}
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "commonjs",
4
+ "target": "ES2020",
5
+ "lib": ["ES2020"],
6
+ "outDir": "./out",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "resolveJsonModule": true
13
+ },
14
+ "exclude": ["node_modules", ".vscode-test"]
15
+ }