rapydscript-ns 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/.agignore +1 -0
  2. package/.gitattributes +4 -0
  3. package/.github/workflows/ci.yml +38 -0
  4. package/.github/workflows/web-repl-page-deploy.yml +42 -0
  5. package/=template.pyj +5 -0
  6. package/CHANGELOG.md +456 -0
  7. package/CONTRIBUTORS +13 -0
  8. package/HACKING.md +103 -0
  9. package/LICENSE +24 -0
  10. package/README.md +2512 -0
  11. package/TODO.md +327 -0
  12. package/add-toc-to-readme +2 -0
  13. package/bin/export +75 -0
  14. package/bin/rapydscript +70 -0
  15. package/bin/web-repl-export +102 -0
  16. package/build +3 -0
  17. package/package.json +46 -0
  18. package/publish.py +37 -0
  19. package/release/baselib-plain-pretty.js +4370 -0
  20. package/release/baselib-plain-ugly.js +3 -0
  21. package/release/compiler.js +18394 -0
  22. package/release/signatures.json +31 -0
  23. package/session.vim +4 -0
  24. package/setup.cfg +2 -0
  25. package/src/ast.pyj +1356 -0
  26. package/src/baselib-builtins.pyj +279 -0
  27. package/src/baselib-containers.pyj +723 -0
  28. package/src/baselib-errors.pyj +37 -0
  29. package/src/baselib-internal.pyj +421 -0
  30. package/src/baselib-itertools.pyj +97 -0
  31. package/src/baselib-str.pyj +798 -0
  32. package/src/compiler.pyj +36 -0
  33. package/src/errors.pyj +30 -0
  34. package/src/lib/aes.pyj +646 -0
  35. package/src/lib/collections.pyj +695 -0
  36. package/src/lib/elementmaker.pyj +83 -0
  37. package/src/lib/encodings.pyj +126 -0
  38. package/src/lib/functools.pyj +148 -0
  39. package/src/lib/gettext.pyj +569 -0
  40. package/src/lib/itertools.pyj +580 -0
  41. package/src/lib/math.pyj +193 -0
  42. package/src/lib/numpy.pyj +2101 -0
  43. package/src/lib/operator.pyj +11 -0
  44. package/src/lib/pythonize.pyj +20 -0
  45. package/src/lib/random.pyj +118 -0
  46. package/src/lib/re.pyj +470 -0
  47. package/src/lib/traceback.pyj +63 -0
  48. package/src/lib/uuid.pyj +77 -0
  49. package/src/monaco-language-service/analyzer.js +526 -0
  50. package/src/monaco-language-service/builtins.js +543 -0
  51. package/src/monaco-language-service/completions.js +498 -0
  52. package/src/monaco-language-service/diagnostics.js +643 -0
  53. package/src/monaco-language-service/dts.js +550 -0
  54. package/src/monaco-language-service/hover.js +121 -0
  55. package/src/monaco-language-service/index.js +386 -0
  56. package/src/monaco-language-service/scope.js +162 -0
  57. package/src/monaco-language-service/signature.js +144 -0
  58. package/src/output/__init__.pyj +0 -0
  59. package/src/output/classes.pyj +296 -0
  60. package/src/output/codegen.pyj +492 -0
  61. package/src/output/comments.pyj +45 -0
  62. package/src/output/exceptions.pyj +105 -0
  63. package/src/output/functions.pyj +491 -0
  64. package/src/output/literals.pyj +109 -0
  65. package/src/output/loops.pyj +444 -0
  66. package/src/output/modules.pyj +329 -0
  67. package/src/output/operators.pyj +429 -0
  68. package/src/output/statements.pyj +463 -0
  69. package/src/output/stream.pyj +309 -0
  70. package/src/output/treeshake.pyj +182 -0
  71. package/src/output/utils.pyj +72 -0
  72. package/src/parse.pyj +3106 -0
  73. package/src/string_interpolation.pyj +72 -0
  74. package/src/tokenizer.pyj +702 -0
  75. package/src/unicode_aliases.pyj +576 -0
  76. package/src/utils.pyj +192 -0
  77. package/test/_import_one.pyj +37 -0
  78. package/test/_import_two/__init__.pyj +11 -0
  79. package/test/_import_two/level2/__init__.pyj +0 -0
  80. package/test/_import_two/level2/deep.pyj +4 -0
  81. package/test/_import_two/other.pyj +6 -0
  82. package/test/_import_two/sub.pyj +13 -0
  83. package/test/aes_vectors.pyj +421 -0
  84. package/test/annotations.pyj +80 -0
  85. package/test/baselib.pyj +319 -0
  86. package/test/classes.pyj +452 -0
  87. package/test/collections.pyj +152 -0
  88. package/test/decorators.pyj +77 -0
  89. package/test/dict_spread.pyj +76 -0
  90. package/test/docstrings.pyj +39 -0
  91. package/test/elementmaker_test.pyj +45 -0
  92. package/test/ellipsis.pyj +49 -0
  93. package/test/functions.pyj +151 -0
  94. package/test/generators.pyj +41 -0
  95. package/test/generic.pyj +370 -0
  96. package/test/imports.pyj +72 -0
  97. package/test/internationalization.pyj +73 -0
  98. package/test/lint.pyj +164 -0
  99. package/test/loops.pyj +85 -0
  100. package/test/numpy.pyj +734 -0
  101. package/test/omit_function_metadata.pyj +20 -0
  102. package/test/regexp.pyj +55 -0
  103. package/test/repl.pyj +121 -0
  104. package/test/scoped_flags.pyj +76 -0
  105. package/test/starargs.pyj +506 -0
  106. package/test/starred_assign.pyj +104 -0
  107. package/test/str.pyj +198 -0
  108. package/test/subscript_tuple.pyj +53 -0
  109. package/test/unit/fixtures/fibonacci_expected.js +46 -0
  110. package/test/unit/index.js +2989 -0
  111. package/test/unit/language-service-builtins.js +815 -0
  112. package/test/unit/language-service-completions.js +1067 -0
  113. package/test/unit/language-service-dts.js +543 -0
  114. package/test/unit/language-service-hover.js +455 -0
  115. package/test/unit/language-service-scope.js +833 -0
  116. package/test/unit/language-service-signature.js +458 -0
  117. package/test/unit/language-service.js +705 -0
  118. package/test/unit/run-language-service.js +41 -0
  119. package/test/unit/web-repl.js +484 -0
  120. package/tools/build-language-service.js +190 -0
  121. package/tools/cli.js +547 -0
  122. package/tools/compile.js +219 -0
  123. package/tools/compiler.js +108 -0
  124. package/tools/completer.js +131 -0
  125. package/tools/embedded_compiler.js +251 -0
  126. package/tools/export.js +316 -0
  127. package/tools/gettext.js +185 -0
  128. package/tools/ini.js +65 -0
  129. package/tools/lint.js +705 -0
  130. package/tools/msgfmt.js +187 -0
  131. package/tools/repl.js +223 -0
  132. package/tools/self.js +162 -0
  133. package/tools/test.js +118 -0
  134. package/tools/utils.js +128 -0
  135. package/tools/web_repl.js +95 -0
  136. package/try +41 -0
  137. package/web-repl/env.js +74 -0
  138. package/web-repl/index.html +163 -0
  139. package/web-repl/language-service.js +4084 -0
  140. package/web-repl/main.js +254 -0
  141. package/web-repl/prism.css +139 -0
  142. package/web-repl/prism.js +113 -0
  143. package/web-repl/rapydscript.js +435 -0
  144. package/web-repl/sha1.js +25 -0
@@ -0,0 +1,550 @@
1
+ // dts.js — Minimal .d.ts parser and type registry for the RapydScript language service.
2
+ //
3
+ // Parses a useful subset of TypeScript declaration syntax:
4
+ // declare var / let / const name: Type;
5
+ // declare function name(params): ReturnType;
6
+ // declare class Name { ... }
7
+ // interface Name { ... }
8
+ // declare namespace Name { ... }
9
+ // type Alias = Type;
10
+ //
11
+ // Usage:
12
+ // import { DtsRegistry } from './dts.js';
13
+ // const reg = new DtsRegistry();
14
+ // reg.addDts('lib.dom', dtsText);
15
+ // reg.getGlobalNames(); // string[]
16
+ // reg.getGlobal('alert'); // TypeInfo | null
17
+ // reg.getHoverMarkdown('alert'); // string | null
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // TypeInfo
21
+ // ---------------------------------------------------------------------------
22
+
23
+ export class TypeInfo {
24
+ /**
25
+ * @param {object} opts
26
+ * @param {string} opts.name
27
+ * @param {string} opts.kind 'var'|'function'|'class'|'interface'|'namespace'|'method'|'property'
28
+ * @param {Array|null} [opts.params] [{name,type,optional,rest}]
29
+ * @param {string|null} [opts.return_type]
30
+ * @param {Map|null} [opts.members] Map<string, TypeInfo>
31
+ * @param {string|null} [opts.doc]
32
+ * @param {string} [opts.source] 'dts'
33
+ */
34
+ constructor(opts) {
35
+ this.name = opts.name;
36
+ this.kind = opts.kind;
37
+ this.params = opts.params || null;
38
+ this.return_type = opts.return_type || null;
39
+ this.members = opts.members || null;
40
+ this.doc = opts.doc || null;
41
+ this.source = opts.source || 'dts';
42
+ }
43
+ }
44
+
45
+ // ---------------------------------------------------------------------------
46
+ // Low-level helpers
47
+ // ---------------------------------------------------------------------------
48
+
49
+ /**
50
+ * Find the index of the closing parenthesis that matches the opening one at
51
+ * `start` in `str`. Returns -1 if not found.
52
+ */
53
+ function find_close_paren(str, start) {
54
+ let depth = 0;
55
+ for (let i = start; i < str.length; i++) {
56
+ if (str[i] === '(') depth++;
57
+ else if (str[i] === ')') { depth--; if (depth === 0) return i; }
58
+ }
59
+ return -1;
60
+ }
61
+
62
+ /**
63
+ * Split a parameter list string on commas at depth 0, respecting
64
+ * generic brackets `<>`, parens `()`, and square brackets `[]`.
65
+ */
66
+ function split_params(s) {
67
+ const parts = [];
68
+ let depth = 0;
69
+ let cur = '';
70
+ for (let i = 0; i < s.length; i++) {
71
+ const ch = s[i];
72
+ if (ch === '<' || ch === '(' || ch === '[' || ch === '{') depth++;
73
+ else if (ch === '>' || ch === ')' || ch === ']' || ch === '}') depth--;
74
+ else if (ch === ',' && depth === 0) {
75
+ parts.push(cur.trim());
76
+ cur = '';
77
+ continue;
78
+ }
79
+ cur += ch;
80
+ }
81
+ if (cur.trim()) parts.push(cur.trim());
82
+ return parts;
83
+ }
84
+
85
+ /**
86
+ * Parse a single parameter token like `name: Type`, `name?: Type`,
87
+ * `...name: Type[]` into `{name, type, optional, rest}`.
88
+ */
89
+ function parse_one_param(s) {
90
+ const rest = s.startsWith('...');
91
+ if (rest) s = s.slice(3);
92
+
93
+ const colon = s.indexOf(':');
94
+ let name, type;
95
+ if (colon !== -1) {
96
+ name = s.slice(0, colon).replace(/\?$/, '').trim();
97
+ type = s.slice(colon + 1).trim();
98
+ } else {
99
+ name = s.replace(/\?$/, '').trim();
100
+ type = 'any';
101
+ }
102
+ const optional = colon !== -1
103
+ ? s.slice(0, colon).trimEnd().endsWith('?')
104
+ : s.trimEnd().endsWith('?');
105
+
106
+ return { name, type, optional, rest };
107
+ }
108
+
109
+ /**
110
+ * Parse a `(...)` parameter section from a declaration string.
111
+ * `decl` is the portion of text starting with `(`.
112
+ * Returns `{ params, rest }` where `rest` is the text after the closing `)`.
113
+ */
114
+ function parse_params_from(decl) {
115
+ if (!decl.startsWith('(')) return { params: [], rest: decl };
116
+ const close = find_close_paren(decl, 0);
117
+ if (close < 0) return { params: [], rest: '' };
118
+ const inner = decl.slice(1, close);
119
+ const rest = decl.slice(close + 1);
120
+ const params = inner.trim()
121
+ ? split_params(inner).map(parse_one_param)
122
+ : [];
123
+ return { params, rest };
124
+ }
125
+
126
+ /**
127
+ * Extract the return type from text like `: ReturnType;` or `): ReturnType {`.
128
+ * Returns null if no type annotation is found.
129
+ */
130
+ function parse_return_type(s) {
131
+ const m = s.match(/^\s*:\s*(.+?)(?:\s*[;{]|$)/);
132
+ return m ? m[1].trim() : null;
133
+ }
134
+
135
+ /**
136
+ * Collect lines from a `/**...* /` block and return the trimmed doc text.
137
+ */
138
+ function extract_jsdoc(lines) {
139
+ const text = lines
140
+ .map(l => l
141
+ .replace(/^\s*\/\*\*?\s?/, '')
142
+ .replace(/\*\/$/, '')
143
+ .replace(/^\s*\*\s?/, '')
144
+ .trim()
145
+ )
146
+ .filter(l => l.length > 0)
147
+ .join(' ');
148
+ return text || null;
149
+ }
150
+
151
+ // ---------------------------------------------------------------------------
152
+ // Block body collector
153
+ // ---------------------------------------------------------------------------
154
+
155
+ /**
156
+ * Starting at line index `i`, collect all text up to and including the
157
+ * matching closing `}`, tracking brace depth.
158
+ * Returns `{ body, next_i }`.
159
+ */
160
+ function collect_block(lines, i) {
161
+ const body_lines = [];
162
+ let depth = 0;
163
+ while (i < lines.length) {
164
+ const l = lines[i];
165
+ for (const ch of l) {
166
+ if (ch === '{') depth++;
167
+ else if (ch === '}') depth--;
168
+ }
169
+ body_lines.push(l);
170
+ i++;
171
+ if (depth === 0 && body_lines.length > 0) break;
172
+ }
173
+ const joined = body_lines.join('\n');
174
+ const open = joined.indexOf('{');
175
+ const close = joined.lastIndexOf('}');
176
+ const inner = (open >= 0 && close > open) ? joined.slice(open + 1, close) : '';
177
+ return { inner, next_i: i };
178
+ }
179
+
180
+ // ---------------------------------------------------------------------------
181
+ // Member parser (inside class / interface / namespace bodies)
182
+ // ---------------------------------------------------------------------------
183
+
184
+ function parse_members(text) {
185
+ const members = new Map();
186
+ const lines = text.split('\n');
187
+ let jsdoc_lines = [];
188
+ let i = 0;
189
+
190
+ while (i < lines.length) {
191
+ const raw = lines[i];
192
+ const line = raw.trim();
193
+
194
+ // JSDoc block
195
+ if (line.startsWith('/**') || (line.startsWith('/*') && !line.startsWith('*/')) ) {
196
+ jsdoc_lines = [line];
197
+ // If the block isn't closed on this line, keep reading
198
+ if (!line.includes('*/')) {
199
+ i++;
200
+ while (i < lines.length && !lines[i].includes('*/')) {
201
+ jsdoc_lines.push(lines[i].trim());
202
+ i++;
203
+ }
204
+ if (i < lines.length) { jsdoc_lines.push(lines[i].trim()); i++; }
205
+ } else {
206
+ i++;
207
+ }
208
+ continue;
209
+ }
210
+
211
+ if (line.startsWith('//') || !line) {
212
+ if (!line) jsdoc_lines = [];
213
+ i++;
214
+ continue;
215
+ }
216
+
217
+ const doc = extract_jsdoc(jsdoc_lines);
218
+ jsdoc_lines = [];
219
+
220
+ // Strip access modifiers and declaration keywords
221
+ let l = line.replace(
222
+ /^(?:static\s+|readonly\s+|abstract\s+|override\s+|protected\s+|private\s+|public\s+)*/,
223
+ ''
224
+ ).trim();
225
+ // Inside namespaces, members can be prefixed with function/const/let/var
226
+ const fn_kw = l.match(/^function\s+/);
227
+ if (fn_kw) l = l.slice(fn_kw[0].length).trim();
228
+ const var_kw = l.match(/^(?:const|let|var)\s+/);
229
+ if (var_kw) l = l.slice(var_kw[0].length).trim();
230
+
231
+ // Skip index signatures [key: Type]: Type
232
+ if (l.startsWith('[')) { i++; continue; }
233
+
234
+ // Skip nested blocks (e.g. nested namespace)
235
+ if (/^(?:class|interface|namespace|module)\s/.test(l)) { i++; continue; }
236
+
237
+ // method(...): ReturnType; or method(...) {
238
+ const paren_idx = l.indexOf('(');
239
+ if (paren_idx > 0) {
240
+ const name = l.slice(0, paren_idx).replace(/[?<].*$/, '').trim();
241
+ if (name && /^\w+$/.test(name) && name !== 'constructor') {
242
+ const after_name = l.slice(paren_idx);
243
+ const { params, rest } = parse_params_from(after_name);
244
+ const return_type = parse_return_type(rest);
245
+ members.set(name, new TypeInfo({
246
+ name,
247
+ kind: 'method',
248
+ params,
249
+ return_type,
250
+ doc,
251
+ }));
252
+ }
253
+ i++;
254
+ continue;
255
+ }
256
+
257
+ // property: Type;
258
+ const prop_m = l.match(/^(\w+)\??\s*:\s*(.+?)\s*[;,]?$/);
259
+ if (prop_m) {
260
+ members.set(prop_m[1], new TypeInfo({
261
+ name: prop_m[1],
262
+ kind: 'property',
263
+ return_type: prop_m[2],
264
+ doc,
265
+ }));
266
+ i++;
267
+ continue;
268
+ }
269
+
270
+ i++;
271
+ }
272
+
273
+ return members;
274
+ }
275
+
276
+ // ---------------------------------------------------------------------------
277
+ // Top-level parser
278
+ // ---------------------------------------------------------------------------
279
+
280
+ /**
281
+ * Parse a .d.ts file and return an array of TypeInfo objects (top-level only).
282
+ * @param {string} text
283
+ * @returns {TypeInfo[]}
284
+ */
285
+ export function parse_dts(text) {
286
+ const lines = text.split('\n');
287
+ const results = [];
288
+ let jsdoc_lines = [];
289
+ let i = 0;
290
+
291
+ while (i < lines.length) {
292
+ const raw = lines[i];
293
+ const line = raw.trim();
294
+
295
+ // Accumulate JSDoc
296
+ if (line.startsWith('/**') || (line.startsWith('/*') && !line.startsWith('*/'))) {
297
+ jsdoc_lines = [line];
298
+ if (!line.includes('*/')) {
299
+ i++;
300
+ while (i < lines.length && !lines[i].includes('*/')) {
301
+ jsdoc_lines.push(lines[i].trim());
302
+ i++;
303
+ }
304
+ if (i < lines.length) { jsdoc_lines.push(lines[i].trim()); i++; }
305
+ } else {
306
+ i++;
307
+ }
308
+ continue;
309
+ }
310
+
311
+ if (line.startsWith('//') || !line) {
312
+ if (!line) jsdoc_lines = [];
313
+ i++;
314
+ continue;
315
+ }
316
+
317
+ const doc = extract_jsdoc(jsdoc_lines);
318
+ jsdoc_lines = [];
319
+
320
+ // Strip `export` and `declare` prefixes
321
+ let l = line
322
+ .replace(/^export\s+default\s+/, '')
323
+ .replace(/^export\s+/, '')
324
+ .replace(/^declare\s+/, '')
325
+ .trim();
326
+
327
+ // ── var / let / const ──────────────────────────────────────────────
328
+ const var_m = l.match(/^(?:var|let|const)\s+(\w+)\s*(?::\s*(.+?))?\s*[;=]/);
329
+ if (var_m) {
330
+ results.push(new TypeInfo({
331
+ name: var_m[1],
332
+ kind: 'var',
333
+ return_type: var_m[2] ? var_m[2].trim() : null,
334
+ doc,
335
+ }));
336
+ i++;
337
+ continue;
338
+ }
339
+
340
+ // ── function ───────────────────────────────────────────────────────
341
+ const fn_paren = l.indexOf('(');
342
+ const fn_m = fn_paren > 0 && l.match(/^function\s+(\w+)/);
343
+ if (fn_m) {
344
+ const name = fn_m[1];
345
+ const after_name = l.slice(l.indexOf('('));
346
+ const { params, rest } = parse_params_from(after_name);
347
+ const return_type = parse_return_type(rest);
348
+ results.push(new TypeInfo({ name, kind: 'function', params, return_type, doc }));
349
+ i++;
350
+ continue;
351
+ }
352
+
353
+ // ── class / interface / abstract class / namespace / module ────────
354
+ const block_m = l.match(/^(abstract\s+class|class|interface|namespace|module)\s+(\w+)/);
355
+ if (block_m) {
356
+ const kind_raw = block_m[1].replace('abstract ', '').trim();
357
+ const kind = (kind_raw === 'module') ? 'namespace' : kind_raw;
358
+ const name = block_m[2];
359
+
360
+ if (l.includes('{')) {
361
+ // Block opens on this line — collect to matching '}'
362
+ const { inner, next_i } = collect_block(lines, i);
363
+ const members = parse_members(inner);
364
+ results.push(new TypeInfo({ name, kind, members, doc }));
365
+ i = next_i;
366
+ } else {
367
+ // Declaration without body (e.g. `declare class Foo;`)
368
+ results.push(new TypeInfo({ name, kind, doc }));
369
+ i++;
370
+ }
371
+ continue;
372
+ }
373
+
374
+ // ── type alias (no body extracted — just register the name) ────────
375
+ const type_m = l.match(/^type\s+(\w+)\s*(?:<[^>]*>)?\s*=/);
376
+ if (type_m) {
377
+ results.push(new TypeInfo({ name: type_m[1], kind: 'var', doc }));
378
+ i++;
379
+ continue;
380
+ }
381
+
382
+ i++;
383
+ jsdoc_lines = [];
384
+ }
385
+
386
+ return results;
387
+ }
388
+
389
+ // ---------------------------------------------------------------------------
390
+ // Type-string helpers
391
+ // ---------------------------------------------------------------------------
392
+
393
+ /**
394
+ * Extract the first concrete type name from a TypeScript type string.
395
+ * Handles union types (A | B), generic types (A<B>), and simple names.
396
+ * Returns null if no plain identifier can be extracted.
397
+ *
398
+ * Examples:
399
+ * 'Server' → 'Server'
400
+ * 'Server | (DarkServer & { isOnline: boolean })' → 'Server'
401
+ * 'Promise<number>' → 'Promise'
402
+ */
403
+ export function resolve_first_type(type_str) {
404
+ if (!type_str) return null;
405
+ const first = type_str.split('|')[0].trim();
406
+ const base = first.replace(/<.*$/, '').trim();
407
+ return /^\w+$/.test(base) ? base : null;
408
+ }
409
+
410
+ // ---------------------------------------------------------------------------
411
+ // DtsRegistry
412
+ // ---------------------------------------------------------------------------
413
+
414
+ export class DtsRegistry {
415
+ constructor() {
416
+ this._globals = new Map(); // name → TypeInfo
417
+ }
418
+
419
+ /**
420
+ * Parse and register a .d.ts file. Calling this multiple times merges
421
+ * all declarations into the same global namespace.
422
+ * @param {string} _name a label for this file (reserved for future dedup)
423
+ * @param {string} text the .d.ts source text
424
+ */
425
+ addDts(_name, text) {
426
+ const types = parse_dts(text);
427
+ for (const ti of types) {
428
+ this._globals.set(ti.name, ti);
429
+ }
430
+ }
431
+
432
+ /**
433
+ * Return all registered global symbol names.
434
+ * @returns {string[]}
435
+ */
436
+ getGlobalNames() {
437
+ return Array.from(this._globals.keys());
438
+ }
439
+
440
+ /**
441
+ * Return the TypeInfo for a name, or null.
442
+ * @param {string} name
443
+ * @returns {TypeInfo|null}
444
+ */
445
+ getGlobal(name) {
446
+ return this._globals.get(name) || null;
447
+ }
448
+
449
+ /**
450
+ * Return a Monaco hover markdown string for a name, or null.
451
+ * @param {string} name
452
+ * @returns {string|null}
453
+ */
454
+ getHoverMarkdown(name) {
455
+ const ti = this.getGlobal(name);
456
+ return ti ? build_dts_hover(ti) : null;
457
+ }
458
+
459
+ /**
460
+ * Return completion-style member names for a type (used for dot-completion
461
+ * on .d.ts-typed variables when Phase 6 type inference is wired up).
462
+ * @param {string} name
463
+ * @returns {string[]}
464
+ */
465
+ getMemberNames(name) {
466
+ const ti = this.getGlobal(name);
467
+ if (!ti || !ti.members) return [];
468
+ return Array.from(ti.members.keys());
469
+ }
470
+
471
+ /**
472
+ * Follow a dot-path through the type registry and return the TypeInfo
473
+ * for the named member at the end of the chain.
474
+ *
475
+ * Examples:
476
+ * getMemberInfo('ns', 'hack') → TypeInfo for NS.hack
477
+ * getMemberInfo('ns.hacknet', 'purchaseNode') → TypeInfo for Hacknet.purchaseNode
478
+ *
479
+ * @param {string} objectPath dot-separated path (e.g. 'ns' or 'ns.hacknet')
480
+ * @param {string} memberName the attribute being accessed
481
+ * @returns {TypeInfo|null}
482
+ */
483
+ getMemberInfo(objectPath, memberName) {
484
+ const parts = objectPath.split('.');
485
+ let ti = this._globals.get(parts[0]);
486
+ // Dereference var → type (e.g. var ns: NS → NS interface)
487
+ if (ti && !ti.members && ti.return_type) {
488
+ ti = this._globals.get(resolve_first_type(ti.return_type));
489
+ }
490
+ // Walk remaining path segments
491
+ for (let i = 1; i < parts.length && ti; i++) {
492
+ const member = ti.members ? ti.members.get(parts[i]) : null;
493
+ if (!member) { ti = null; break; }
494
+ if (member.members) {
495
+ ti = member;
496
+ } else if (member.return_type) {
497
+ ti = this._globals.get(resolve_first_type(member.return_type));
498
+ } else {
499
+ ti = null;
500
+ }
501
+ }
502
+ if (!ti || !ti.members) return null;
503
+ return ti.members.get(memberName) || null;
504
+ }
505
+
506
+ /**
507
+ * Return hover markdown for a member accessed via a dot-path, or null.
508
+ * @param {string} objectPath
509
+ * @param {string} memberName
510
+ * @returns {string|null}
511
+ */
512
+ getMemberHoverMarkdown(objectPath, memberName) {
513
+ const ti = this.getMemberInfo(objectPath, memberName);
514
+ return ti ? build_dts_hover(ti) : null;
515
+ }
516
+ }
517
+
518
+ // ---------------------------------------------------------------------------
519
+ // DTS hover rendering
520
+ // ---------------------------------------------------------------------------
521
+
522
+ function build_dts_sig(ti) {
523
+ if (ti.kind === 'function' || ti.kind === 'method') {
524
+ const param_str = ti.params
525
+ ? ti.params.map(function (p) {
526
+ let s = p.rest ? '...' : '';
527
+ s += p.name;
528
+ if (p.optional) s += '?';
529
+ if (p.type && p.type !== 'any') s += ': ' + p.type;
530
+ return s;
531
+ }).join(', ')
532
+ : '';
533
+ let sig = '(' + ti.kind + ') ' + ti.name + '(' + param_str + ')';
534
+ if (ti.return_type && ti.return_type !== 'void') sig += ': ' + ti.return_type;
535
+ return sig;
536
+ }
537
+ if (ti.kind === 'property' || ti.kind === 'var') {
538
+ let sig = '(' + ti.kind + ') ' + ti.name;
539
+ if (ti.return_type) sig += ': ' + ti.return_type;
540
+ return sig;
541
+ }
542
+ return '(' + ti.kind + ') ' + ti.name;
543
+ }
544
+
545
+ function build_dts_hover(ti) {
546
+ const sig = build_dts_sig(ti);
547
+ let md = '```typescript\n' + sig + '\n```';
548
+ if (ti.doc) md += '\n\n' + ti.doc;
549
+ return md;
550
+ }
@@ -0,0 +1,121 @@
1
+ // hover.js — Hover provider for the RapydScript language service.
2
+ //
3
+ // Usage:
4
+ // import { HoverEngine } from './hover.js';
5
+ // const engine = new HoverEngine();
6
+ // const hover = engine.getHover(scopeMap, position, word);
7
+
8
+ // ---------------------------------------------------------------------------
9
+ // Rendering helpers
10
+ // ---------------------------------------------------------------------------
11
+
12
+ /**
13
+ * Build a one-line signature string for a symbol.
14
+ * Examples:
15
+ * (function) add(x, y)
16
+ * (class) Dog
17
+ * (variable) my_var
18
+ * (import) math
19
+ *
20
+ * @param {import('./scope.js').SymbolInfo} sym
21
+ * @returns {string}
22
+ */
23
+ function build_signature(sym) {
24
+ const kind_label = '(' + sym.kind + ') ';
25
+
26
+ if ((sym.kind === 'function' || sym.kind === 'method') && sym.params) {
27
+ const param_str = sym.params.map(function (p) {
28
+ if (p.is_separator) return p.name; // '/' or '*'
29
+ if (p.is_kwargs) return '**' + p.name;
30
+ if (p.is_rest) return '*' + p.name;
31
+ return p.name;
32
+ }).join(', ');
33
+ return kind_label + sym.name + '(' + param_str + ')';
34
+ }
35
+
36
+ return kind_label + sym.name;
37
+ }
38
+
39
+ /**
40
+ * Build the full markdown hover string for a symbol.
41
+ * Returns a fenced code block for the signature, plus the docstring if present.
42
+ *
43
+ * @param {import('./scope.js').SymbolInfo} sym
44
+ * @returns {string}
45
+ */
46
+ function build_hover_markdown(sym) {
47
+ const sig = build_signature(sym);
48
+
49
+ // Use a code block so Monaco renders it with syntax highlighting
50
+ let md = '```\n' + sig + '\n```';
51
+
52
+ if (sym.doc) {
53
+ md += '\n\n' + sym.doc;
54
+ }
55
+
56
+ return md;
57
+ }
58
+
59
+ // ---------------------------------------------------------------------------
60
+ // HoverEngine
61
+ // ---------------------------------------------------------------------------
62
+
63
+ export class HoverEngine {
64
+
65
+ /**
66
+ * @param {import('./dts.js').DtsRegistry|null} [dtsRegistry]
67
+ * @param {import('./builtins.js').BuiltinsRegistry|null} [builtinsRegistry]
68
+ */
69
+ constructor(dtsRegistry, builtinsRegistry) {
70
+ this._dts = dtsRegistry || null;
71
+ this._builtins = builtinsRegistry || null;
72
+ }
73
+
74
+ /**
75
+ * Return a Monaco IHover for the word under the cursor.
76
+ * Priority: ScopeMap (user-defined) → DTS member (dot-chain) → DTS global → builtins.
77
+ * Returns null if the word is not found in any source.
78
+ *
79
+ * @param {import('./scope.js').ScopeMap|null} scopeMap
80
+ * @param {{lineNumber:number,column:number}} position 1-indexed Monaco position
81
+ * @param {string|null} word the identifier under the cursor (from model.getWordAtPosition)
82
+ * @param {string} [line_before_word] text on the current line before the word's start column
83
+ * @returns {{ contents: {value:string}[], range?: object }|null}
84
+ */
85
+ getHover(scopeMap, position, word, line_before_word) {
86
+ if (!word) return null;
87
+
88
+ // 1. ScopeMap lookup (user-defined symbols)
89
+ if (scopeMap) {
90
+ const sym = scopeMap.getSymbol(word, position.lineNumber, position.column);
91
+ if (sym) {
92
+ return { contents: [{ value: build_hover_markdown(sym) }] };
93
+ }
94
+ }
95
+
96
+ // 2. DTS member lookup — check for dot-chain access
97
+ // e.g. hovering 'hack' in 'ns.hack(' → line_before_word ends with 'ns.'
98
+ // e.g. hovering 'purchaseNode' in 'ns.hacknet.purchaseNode(' → ends with 'ns.hacknet.'
99
+ if (this._dts && line_before_word) {
100
+ const dot_match = line_before_word.match(/([\w.]+)\.$/);
101
+ if (dot_match) {
102
+ const md = this._dts.getMemberHoverMarkdown(dot_match[1], word);
103
+ if (md) return { contents: [{ value: md }] };
104
+ }
105
+ }
106
+
107
+ // 3. DTS global lookup (top-level declarations like `var ns: NS`)
108
+ if (this._dts) {
109
+ const md = this._dts.getHoverMarkdown(word);
110
+ if (md) return { contents: [{ value: md }] };
111
+ }
112
+
113
+ // 4. Built-in stubs
114
+ if (this._builtins) {
115
+ const md = this._builtins.getHoverMarkdown(word);
116
+ if (md) return { contents: [{ value: md }] };
117
+ }
118
+
119
+ return null;
120
+ }
121
+ }