rapydscript-ng 0.7.23 → 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 (114) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +72 -26
  3. package/bin/package.json +1 -0
  4. package/bin/rapydscript +65 -62
  5. package/editor-plugins/nvim/rapydscript/README.md +184 -0
  6. package/editor-plugins/nvim/rapydscript/bin/rapydscript.so +0 -0
  7. package/editor-plugins/nvim/rapydscript/ftdetect/rapydscript.lua +10 -0
  8. package/editor-plugins/nvim/rapydscript/lua/rapydscript/health.lua +88 -0
  9. package/editor-plugins/nvim/rapydscript/lua/rapydscript/init.lua +279 -0
  10. package/local-agent.md +28 -0
  11. package/package.json +4 -5
  12. package/release/baselib-plain-pretty.js +448 -326
  13. package/release/baselib-plain-ugly.js +3 -3
  14. package/release/compiler.js +2528 -1672
  15. package/release/signatures.json +17 -17
  16. package/src/ast.pyj +73 -25
  17. package/src/baselib-builtins.pyj +2 -2
  18. package/src/baselib-containers.pyj +153 -151
  19. package/src/baselib-internal.pyj +3 -3
  20. package/src/baselib-str.pyj +5 -5
  21. package/src/lib/aes.pyj +1 -1
  22. package/src/lib/encodings.pyj +1 -1
  23. package/src/lib/pythonize.pyj +1 -1
  24. package/src/lib/re.pyj +2 -2
  25. package/src/lib/traceback.pyj +165 -28
  26. package/src/lib/uuid.pyj +1 -1
  27. package/src/output/codegen.pyj +10 -3
  28. package/src/output/functions.pyj +31 -40
  29. package/src/output/literals.pyj +11 -14
  30. package/src/output/loops.pyj +25 -87
  31. package/src/output/modules.pyj +5 -47
  32. package/src/output/statements.pyj +1 -1
  33. package/src/output/stream.pyj +58 -31
  34. package/src/parse.pyj +777 -427
  35. package/src/tokenizer.pyj +16 -4
  36. package/src/utils.pyj +1 -1
  37. package/test/_treeshake_mod.pyj +30 -0
  38. package/test/_treeshake_side_effect.pyj +9 -0
  39. package/test/ast_serialization.pyj +392 -0
  40. package/test/async.pyj +95 -0
  41. package/test/baselib.pyj +1 -2
  42. package/test/embedded_compiler.pyj +57 -0
  43. package/test/fmt.pyj +201 -0
  44. package/test/generic.pyj +7 -3
  45. package/test/imports.pyj +8 -6
  46. package/test/internationalization.pyj +38 -37
  47. package/test/lint.pyj +120 -115
  48. package/test/lsp.pyj +222 -0
  49. package/test/repl.pyj +70 -65
  50. package/test/sourcemaps.pyj +315 -0
  51. package/test/starargs.pyj +46 -39
  52. package/test/traceback.pyj +263 -0
  53. package/test/treeshaking.pyj +181 -0
  54. package/test/web_repl_export.pyj +88 -0
  55. package/tools/ast_serialize.mjs +557 -0
  56. package/tools/cli.mjs +510 -0
  57. package/tools/compile.mjs +201 -0
  58. package/tools/compiler.mjs +127 -0
  59. package/tools/{completer.js → completer.mjs} +6 -6
  60. package/tools/{embedded_compiler.js → embedded_compiler.mjs} +17 -13
  61. package/tools/export.js +109 -56
  62. package/tools/fmt.mjs +735 -0
  63. package/tools/{gettext.js → gettext.mjs} +46 -53
  64. package/tools/{ini.js → ini.mjs} +9 -10
  65. package/tools/{lint.js → lint.mjs} +107 -56
  66. package/tools/lsp.mjs +914 -0
  67. package/tools/lsp_protocol.mjs +259 -0
  68. package/tools/lsp_symbols.mjs +418 -0
  69. package/tools/{msgfmt.js → msgfmt.mjs} +18 -18
  70. package/tools/{repl.js → repl.mjs} +52 -41
  71. package/tools/{self.js → self.mjs} +56 -46
  72. package/tools/sourcemap.mjs +123 -0
  73. package/tools/test.mjs +152 -0
  74. package/tools/treeshake.mjs +400 -0
  75. package/tools/{utils.js → utils.mjs} +26 -23
  76. package/tools/{web_repl.js → web_repl.mjs} +15 -13
  77. package/tools/web_repl_export.mjs +93 -0
  78. package/tree-sitter/README.md +101 -0
  79. package/tree-sitter/grammar.js +992 -0
  80. package/tree-sitter/package.json +43 -0
  81. package/tree-sitter/queries/rapydscript/highlights.scm +232 -0
  82. package/tree-sitter/queries/rapydscript/indents.scm +64 -0
  83. package/tree-sitter/queries/rapydscript/injections.scm +14 -0
  84. package/tree-sitter/queries/rapydscript/locals.scm +108 -0
  85. package/tree-sitter/src/grammar.json +4976 -0
  86. package/tree-sitter/src/node-types.json +2901 -0
  87. package/tree-sitter/src/parser.c +196465 -0
  88. package/tree-sitter/src/scanner.c +294 -0
  89. package/tree-sitter/src/tree_sitter/alloc.h +54 -0
  90. package/tree-sitter/src/tree_sitter/array.h +330 -0
  91. package/tree-sitter/src/tree_sitter/parser.h +286 -0
  92. package/tree-sitter/test/corpus/chaining.txt +99 -0
  93. package/tree-sitter/test/corpus/classes.txt +147 -0
  94. package/tree-sitter/test/corpus/comprehensions.txt +155 -0
  95. package/tree-sitter/test/corpus/containers.txt +242 -0
  96. package/tree-sitter/test/corpus/control_flow.txt +361 -0
  97. package/tree-sitter/test/corpus/decorators.txt +64 -0
  98. package/tree-sitter/test/corpus/existential.txt +102 -0
  99. package/tree-sitter/test/corpus/functions.txt +293 -0
  100. package/tree-sitter/test/corpus/imports.txt +117 -0
  101. package/tree-sitter/test/corpus/literals.txt +135 -0
  102. package/tree-sitter/test/corpus/operators.txt +296 -0
  103. package/tree-sitter/test/corpus/statements.txt +189 -0
  104. package/tree-sitter/test/corpus/strings.txt +90 -0
  105. package/tree-sitter/test/corpus/subscripts.txt +227 -0
  106. package/tree-sitter/tree-sitter.json +36 -0
  107. package/web-repl/env.js +35 -23
  108. package/web-repl/main.js +8 -8
  109. package/bin/export +0 -75
  110. package/bin/web-repl-export +0 -102
  111. package/tools/cli.js +0 -523
  112. package/tools/compile.js +0 -184
  113. package/tools/compiler.js +0 -84
  114. package/tools/test.js +0 -110
@@ -0,0 +1,259 @@
1
+ /* vim:fileencoding=utf-8
2
+ *
3
+ * lsp_protocol.mjs -- Minimal Language Server Protocol transport for RapydScript.
4
+ *
5
+ * Copyright (C) 2026 Kovid Goyal <kovid at kovidgoyal.net>
6
+ *
7
+ * Distributed under terms of the BSD license.
8
+ *
9
+ * This implements just enough of the LSP wire protocol (JSON-RPC 2.0 with
10
+ * Content-Length framing over a byte stream) to drive an editor. We deliberately
11
+ * do not depend on the vscode-languageserver npm package: the surface we need is
12
+ * small and the project prefers to avoid external dependencies.
13
+ */
14
+ "use strict";
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // JSON-RPC error codes (subset used here)
18
+ // ---------------------------------------------------------------------------
19
+ export var ErrorCodes = {
20
+ ParseError: -32700,
21
+ InvalidRequest: -32600,
22
+ MethodNotFound: -32601,
23
+ InvalidParams: -32602,
24
+ InternalError: -32603,
25
+ RequestCancelled: -32800,
26
+ };
27
+
28
+ // A handler may throw this to produce a specific JSON-RPC error response.
29
+ export function ResponseError(code, message, data) {
30
+ var e = new Error(message);
31
+ e.code = code;
32
+ e.data = data;
33
+ e.is_response_error = true;
34
+ return e;
35
+ }
36
+
37
+ // ---------------------------------------------------------------------------
38
+ // Message framing: parse a byte stream of `Content-Length: N\r\n\r\n<body>`
39
+ // ---------------------------------------------------------------------------
40
+ function MessageReader(stream, on_message, on_error) {
41
+ var buffer = Buffer.alloc(0);
42
+ var content_length = -1;
43
+
44
+ function try_parse() {
45
+ // Loop so that multiple messages contained in one chunk are all handled.
46
+ while (true) {
47
+ if (content_length < 0) {
48
+ var idx = buffer.indexOf('\r\n\r\n');
49
+ if (idx < 0) return;
50
+ var header = buffer.slice(0, idx).toString('ascii');
51
+ content_length = -1;
52
+ header.split('\r\n').forEach(function (line) {
53
+ var sep = line.indexOf(':');
54
+ if (sep < 0) return;
55
+ var name = line.slice(0, sep).trim().toLowerCase();
56
+ var value = line.slice(sep + 1).trim();
57
+ if (name === 'content-length') content_length = parseInt(value, 10);
58
+ });
59
+ buffer = buffer.slice(idx + 4);
60
+ if (isNaN(content_length) || content_length < 0) {
61
+ content_length = -1;
62
+ if (on_error) on_error(new Error('Invalid or missing Content-Length header'));
63
+ return;
64
+ }
65
+ }
66
+ if (buffer.length < content_length) return; // wait for more bytes
67
+ var body = buffer.slice(0, content_length).toString('utf-8');
68
+ buffer = buffer.slice(content_length);
69
+ content_length = -1;
70
+ var msg;
71
+ try { msg = JSON.parse(body); }
72
+ catch (e) { if (on_error) on_error(e); continue; }
73
+ on_message(msg);
74
+ }
75
+ }
76
+
77
+ stream.on('data', function (chunk) {
78
+ buffer = Buffer.concat([buffer, chunk]);
79
+ try { try_parse(); }
80
+ catch (e) { if (on_error) on_error(e); }
81
+ });
82
+ return {};
83
+ }
84
+
85
+ function write_message(stream, msg) {
86
+ var body = JSON.stringify(msg);
87
+ var payload = Buffer.from(body, 'utf-8');
88
+ stream.write('Content-Length: ' + payload.length + '\r\n\r\n');
89
+ stream.write(payload);
90
+ }
91
+
92
+ // ---------------------------------------------------------------------------
93
+ // Connection: dispatch requests / notifications to registered handlers
94
+ // ---------------------------------------------------------------------------
95
+ export function create_connection(input, output, log) {
96
+ var request_handlers = Object.create(null);
97
+ var notification_handlers = Object.create(null);
98
+ var cancelled = Object.create(null); // request id -> true
99
+ var pending_requests = Object.create(null); // id -> {resolve, reject}
100
+ var next_request_id = 1;
101
+
102
+ function send(msg) { write_message(output, msg); }
103
+
104
+ function send_error(id, code, message, data) {
105
+ send({ jsonrpc: '2.0', id: id, error: { code: code, message: message, data: data } });
106
+ }
107
+
108
+ function handle_response(msg) {
109
+ var pending = pending_requests[msg.id];
110
+ if (!pending) return;
111
+ delete pending_requests[msg.id];
112
+ if (msg.error) {
113
+ var err = new Error(msg.error.message || 'Request failed');
114
+ err.code = msg.error.code;
115
+ pending.reject(err);
116
+ } else {
117
+ pending.resolve(msg.result);
118
+ }
119
+ }
120
+
121
+ async function handle_request(msg) {
122
+ var handler = request_handlers[msg.method];
123
+ if (!handler) {
124
+ // Unknown request: respond with MethodNotFound (required by spec).
125
+ send_error(msg.id, ErrorCodes.MethodNotFound, 'Unhandled method: ' + msg.method);
126
+ return;
127
+ }
128
+ try {
129
+ if (cancelled[msg.id]) {
130
+ delete cancelled[msg.id];
131
+ send_error(msg.id, ErrorCodes.RequestCancelled, 'Request cancelled');
132
+ return;
133
+ }
134
+ var result = await handler(msg.params, msg.id);
135
+ if (cancelled[msg.id]) { delete cancelled[msg.id]; send_error(msg.id, ErrorCodes.RequestCancelled, 'Request cancelled'); return; }
136
+ send({ jsonrpc: '2.0', id: msg.id, result: (result === undefined) ? null : result });
137
+ } catch (e) {
138
+ if (e && e.is_response_error) send_error(msg.id, e.code, e.message, e.data);
139
+ else {
140
+ if (log) log('Error handling ' + msg.method + ': ' + (e && e.stack ? e.stack : e));
141
+ send_error(msg.id, ErrorCodes.InternalError, (e && e.message) ? e.message : String(e));
142
+ }
143
+ }
144
+ }
145
+
146
+ async function handle_notification(msg) {
147
+ if (msg.method === '$/cancelRequest' && msg.params && msg.params.id !== undefined) {
148
+ cancelled[msg.params.id] = true;
149
+ return;
150
+ }
151
+ var handler = notification_handlers[msg.method];
152
+ if (!handler) return; // Unknown notifications are silently ignored per spec.
153
+ try { await handler(msg.params); }
154
+ catch (e) { if (log) log('Error handling notification ' + msg.method + ': ' + (e && e.stack ? e.stack : e)); }
155
+ }
156
+
157
+ function on_message(msg) {
158
+ if (!msg || msg.jsonrpc !== '2.0') return;
159
+ if (msg.method !== undefined && msg.id !== undefined) handle_request(msg);
160
+ else if (msg.method !== undefined) handle_notification(msg);
161
+ else if (msg.id !== undefined) handle_response(msg);
162
+ }
163
+
164
+ MessageReader(input, on_message, function (e) { if (log) log('Message parse error: ' + e); });
165
+
166
+ return {
167
+ on_request: function (method, handler) { request_handlers[method] = handler; },
168
+ on_notification: function (method, handler) { notification_handlers[method] = handler; },
169
+ send_notification: function (method, params) { send({ jsonrpc: '2.0', method: method, params: params }); },
170
+ send_request: function (method, params) {
171
+ return new Promise(function (resolve, reject) {
172
+ var id = next_request_id++;
173
+ pending_requests[id] = { resolve: resolve, reject: reject };
174
+ send({ jsonrpc: '2.0', id: id, method: method, params: params });
175
+ });
176
+ },
177
+ dispose: function () { request_handlers = Object.create(null); notification_handlers = Object.create(null); },
178
+ };
179
+ }
180
+
181
+ // ---------------------------------------------------------------------------
182
+ // TextDocument: in-memory buffer with UTF-16 position <-> offset conversion.
183
+ // LSP positions are 0-based (line, character) where character counts UTF-16
184
+ // code units. JavaScript strings are already UTF-16, so a plain index into the
185
+ // string is the correct offset.
186
+ // ---------------------------------------------------------------------------
187
+ export function TextDocument(uri, language_id, version, text) {
188
+ this.uri = uri;
189
+ this.language_id = language_id;
190
+ this.version = version;
191
+ this._set_text(text);
192
+ }
193
+
194
+ TextDocument.prototype._set_text = function (text) {
195
+ this.text = text;
196
+ // Precompute the offset at which each line starts so position<->offset
197
+ // conversion is O(log n) / O(1) instead of rescanning the whole buffer.
198
+ var starts = [0];
199
+ for (var i = 0; i < text.length; i++) {
200
+ var c = text.charCodeAt(i);
201
+ if (c === 10 /*\n*/) starts.push(i + 1);
202
+ else if (c === 13 /*\r*/) {
203
+ if (text.charCodeAt(i + 1) === 10) { i++; }
204
+ starts.push(i + 1);
205
+ }
206
+ }
207
+ this._line_starts = starts;
208
+ };
209
+
210
+ TextDocument.prototype.update = function (text, version) {
211
+ if (version !== undefined && version !== null) this.version = version;
212
+ this._set_text(text);
213
+ };
214
+
215
+ TextDocument.prototype.line_count = function () { return this._line_starts.length; };
216
+
217
+ // {line, character} -> integer offset into the buffer.
218
+ TextDocument.prototype.offset_at = function (position) {
219
+ var starts = this._line_starts;
220
+ if (position.line >= starts.length) return this.text.length;
221
+ if (position.line < 0) return 0;
222
+ var line_start = starts[position.line];
223
+ var line_end = (position.line + 1 < starts.length) ? starts[position.line + 1] : this.text.length;
224
+ var offset = line_start + Math.max(0, position.character);
225
+ return Math.min(offset, line_end);
226
+ };
227
+
228
+ // integer offset -> {line, character}
229
+ TextDocument.prototype.position_at = function (offset) {
230
+ offset = Math.max(0, Math.min(offset, this.text.length));
231
+ var starts = this._line_starts;
232
+ // binary search for the last line start <= offset
233
+ var low = 0, high = starts.length - 1, line = 0;
234
+ while (low <= high) {
235
+ var mid = (low + high) >> 1;
236
+ if (starts[mid] <= offset) { line = mid; low = mid + 1; }
237
+ else high = mid - 1;
238
+ }
239
+ return { line: line, character: offset - starts[line] };
240
+ };
241
+
242
+ // Convenience: 1-based (line, col0) parser coordinates -> LSP 0-based position.
243
+ TextDocument.prototype.lsp_position = function (parser_line, parser_col) {
244
+ return { line: Math.max(0, (parser_line || 1) - 1), character: Math.max(0, parser_col || 0) };
245
+ };
246
+
247
+ // A document store keyed by uri.
248
+ export function DocumentStore() { this.docs = Object.create(null); }
249
+ DocumentStore.prototype.open = function (uri, language_id, version, text) {
250
+ var d = new TextDocument(uri, language_id, version, text);
251
+ this.docs[uri] = d;
252
+ return d;
253
+ };
254
+ DocumentStore.prototype.get = function (uri) { return this.docs[uri]; };
255
+ DocumentStore.prototype.close = function (uri) { delete this.docs[uri]; };
256
+ DocumentStore.prototype.all = function () {
257
+ var self = this;
258
+ return Object.keys(this.docs).map(function (k) { return self.docs[k]; });
259
+ };
@@ -0,0 +1,418 @@
1
+ /* vim:fileencoding=utf-8
2
+ *
3
+ * lsp_symbols.mjs -- Scope and symbol analysis for the RapydScript LSP.
4
+ *
5
+ * Copyright (C) 2026 Kovid Goyal <kovid at kovidgoyal.net>
6
+ *
7
+ * Distributed under terms of the BSD license.
8
+ *
9
+ * Walks a parsed AST once and records, for every binding, its definition
10
+ * identifier node(s) and every reference to it (resolved to the nearest
11
+ * enclosing scope). This single index powers go-to-definition, find-references,
12
+ * rename, hover and scope aware completion. The traversal mirrors the linter's
13
+ * scope handling (tools/lint.mjs) but, unlike the linter, it keeps the nodes so
14
+ * they can be queried by source position.
15
+ */
16
+ "use strict";
17
+
18
+ // Reuse the single compiler instance created synchronously by bin/rapydscript.
19
+ const RapydScript = globalThis.create_rapydscript_compiler();
20
+ var has_prop = Object.prototype.hasOwnProperty.call.bind(Object.prototype.hasOwnProperty);
21
+
22
+ // Symbol kinds also map to LSP CompletionItemKind / SymbolKind in lsp.mjs.
23
+ export var KIND = {
24
+ IMPORT: 'import', IMPORTED_NAME: 'imported-name', FUNCTION: 'function',
25
+ CLASS: 'class', METHOD: 'method', PARAMETER: 'parameter', VARIABLE: 'variable',
26
+ LOOPVAR: 'loopvar', EXCEPT: 'except',
27
+ };
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // Position helpers. Tokenizer positions (`pos`, `endpos`) are character offsets
31
+ // into the newline-normalized source, which is exactly what we analyze.
32
+ // ---------------------------------------------------------------------------
33
+ export function symbol_range(node, name) {
34
+ // For a symbol/identifier node, the start token IS the identifier token.
35
+ if (!node) return null;
36
+ var s = node.start || node;
37
+ if (s && typeof s.pos === 'number') {
38
+ var end = (typeof s.endpos === 'number') ? s.endpos : (s.pos + (name ? name.length : 0));
39
+ return [s.pos, end];
40
+ }
41
+ return null;
42
+ }
43
+
44
+ export function node_span(node) {
45
+ if (!node || !node.start) return null;
46
+ var s = node.start;
47
+ var e = node.end || node.start;
48
+ if (typeof s.pos !== 'number') return null;
49
+ var end = (typeof e.endpos === 'number') ? e.endpos : (typeof e.pos === 'number' ? e.pos : s.pos);
50
+ return [s.pos, end];
51
+ }
52
+
53
+ // The identifier token of a `.property` access is the AST_Dot's end token.
54
+ function dot_property_range(node) {
55
+ var e = node.end;
56
+ if (e && typeof e.pos === 'number' && typeof e.endpos === 'number') return [e.pos, e.endpos];
57
+ return null;
58
+ }
59
+
60
+ // ---------------------------------------------------------------------------
61
+ // Scope / SymbolDef model
62
+ // ---------------------------------------------------------------------------
63
+ function SymbolDef(name, scope, kind) {
64
+ this.name = name;
65
+ this.scope = scope;
66
+ this.kind = kind;
67
+ this.def_nodes = []; // identifier nodes that (re)define the symbol
68
+ this.ref_nodes = []; // identifier nodes that reference the symbol
69
+ this.docstring = null; // populated for functions/classes
70
+ }
71
+
72
+ function Scope(node, parent, kind) {
73
+ this.node = node;
74
+ this.parent = parent;
75
+ this.kind = kind; // 'toplevel' | 'function' | 'class' | 'comprehension'
76
+ this.is_class = kind === 'class';
77
+ this.is_toplevel = kind === 'toplevel';
78
+ this.bindings = Object.create(null); // name -> SymbolDef
79
+ this.nonlocals = Object.create(null);
80
+ this.children = [];
81
+ }
82
+
83
+ Scope.prototype.define = function (name, node, kind) {
84
+ var def = this.bindings[name];
85
+ if (!def) { def = new SymbolDef(name, this, kind); this.bindings[name] = def; }
86
+ else if (kind && def.kind === KIND.VARIABLE) def.kind = kind; // prefer a more specific kind
87
+ if (node) { node._rs_def = def; def.def_nodes.push(node); }
88
+ return def;
89
+ };
90
+
91
+ // ---------------------------------------------------------------------------
92
+ // The analyzer walks the AST building scopes, definitions and references.
93
+ // ---------------------------------------------------------------------------
94
+ function Analyzer(toplevel, filename) {
95
+ this.filename = filename;
96
+ this.toplevel = toplevel;
97
+ this.scopes = [];
98
+ this.pending_refs = []; // {name, node, scope}
99
+ this.member_accesses = []; // {object_name, property, range, node}
100
+ this.all_defs = [];
101
+ this.all_refs = []; // resolved references {name, node, range, def}
102
+ this.imports = []; // {key, kind, node, alias, argnames, module_node}
103
+ }
104
+
105
+ Analyzer.prototype.current = function () { return this.scopes[this.scopes.length - 1]; };
106
+
107
+ Analyzer.prototype.define = function (name, node, kind) {
108
+ var d = this.current().define(name, node, kind);
109
+ if (this.all_defs.indexOf(d) < 0) this.all_defs.push(d);
110
+ return d;
111
+ };
112
+
113
+ Analyzer.prototype.reference = function (name, node) {
114
+ this.pending_refs.push({ name: name, node: node, scope: this.current() });
115
+ };
116
+
117
+ Analyzer.prototype.push_scope = function (node, kind) {
118
+ var s = new Scope(node, this.current() || null, kind);
119
+ if (this.scopes.length) this.current().children.push(s);
120
+ this.scopes.push(s);
121
+ if (node) node._rs_scope = s;
122
+ return s;
123
+ };
124
+
125
+ Analyzer.prototype.pop_scope = function () { return this.scopes.pop(); };
126
+
127
+ // -- individual node handlers (mirrors lint.mjs) ----------------------------
128
+
129
+ Analyzer.prototype.handle_import = function (node) {
130
+ if (!node.argnames) {
131
+ // `import M` / `import M.sub` / `import M as alias`
132
+ var name = (node.alias) ? node.alias.name : node.key.split('.', 1)[0];
133
+ var idnode = node.alias || node.module || node;
134
+ var d = this.define(name, idnode, KIND.IMPORT);
135
+ d.import_key = node.key;
136
+ this.imports.push({ key: node.key, kind: 'module', node: node, alias: node.alias, argnames: null, def: d });
137
+ } else {
138
+ // `from M import a, b as c` -- the argnames are visited as AST_ImportedVar.
139
+ this.imports.push({ key: node.key, kind: 'from', node: node, alias: node.alias, argnames: node.argnames });
140
+ }
141
+ };
142
+
143
+ Analyzer.prototype.handle_imported_var = function (node) {
144
+ // `node` is an AST_ImportedVar: node.name is the original exported name, and
145
+ // node.alias (if present) is the local name it is bound to.
146
+ var name = (node.alias) ? node.alias.name : node.name;
147
+ var idnode = node.alias || node;
148
+ var d = this.define(name, idnode, KIND.IMPORTED_NAME);
149
+ d.import_original_name = node.name;
150
+ d.import_alias = node.alias ? node.alias.name : null;
151
+ // The token spelling the *original* name is `node` itself (start token).
152
+ d.import_original_node = node;
153
+ // import_key is filled in during finalize (the ImportedVar does not carry it).
154
+ node._rs_imported_var_def = d;
155
+ };
156
+
157
+ Analyzer.prototype.handle_lambda = function (node) {
158
+ var name = (node.name) ? node.name.name : undefined;
159
+ if (!name) return;
160
+ if (node instanceof RapydScript.AST_Method) {
161
+ // Methods live in the enclosing class scope.
162
+ var d = this.define(name, node.name, KIND.METHOD);
163
+ d.docstring = docstring_of(node);
164
+ } else {
165
+ var fd = this.define(name, node.name, KIND.FUNCTION);
166
+ fd.docstring = docstring_of(node);
167
+ }
168
+ };
169
+
170
+ Analyzer.prototype.handle_class = function (node) {
171
+ if (node.name) {
172
+ node.name._rs_is_binding = true;
173
+ var d = this.define(node.name.name, node.name, KIND.CLASS);
174
+ d.docstring = docstring_of(node);
175
+ }
176
+ };
177
+
178
+ Analyzer.prototype.handle_assign = function (node) {
179
+ var self = this;
180
+ function destructured(flat) {
181
+ for (var i = 0; i < flat.length; i++) {
182
+ var cnode = flat[i];
183
+ if (cnode instanceof RapydScript.AST_SymbolRef) { cnode._rs_is_binding = true; self.define(cnode.name, cnode, KIND.VARIABLE); }
184
+ }
185
+ }
186
+ if (node.left instanceof RapydScript.AST_SymbolRef) {
187
+ if (node.operator === '=') { node.left._rs_is_binding = true; this.define(node.left.name, node.left, KIND.VARIABLE); }
188
+ // compound assignment (+=) references the existing binding, handled by the generic ref path
189
+ } else if (node.left instanceof RapydScript.AST_Array) {
190
+ destructured(node.left.flatten());
191
+ } else if (node.left instanceof RapydScript.AST_Seq && node.left.car instanceof RapydScript.AST_SymbolRef) {
192
+ destructured(node.left.to_array());
193
+ }
194
+ };
195
+
196
+ Analyzer.prototype.handle_vardef = function (node) {
197
+ if (node.name instanceof RapydScript.AST_SymbolNonlocal) {
198
+ this.current().nonlocals[node.name.name] = true;
199
+ } else {
200
+ this.define(node.name.name, node.name, KIND.VARIABLE);
201
+ }
202
+ };
203
+
204
+ Analyzer.prototype.handle_symbol_funarg = function (node) {
205
+ this.define(node.name, node, KIND.PARAMETER);
206
+ };
207
+
208
+ Analyzer.prototype.handle_for_in = function (node) {
209
+ var self = this;
210
+ if (node.init instanceof RapydScript.AST_SymbolRef) {
211
+ node.init._rs_is_binding = true;
212
+ this.define(node.init.name, node.init, KIND.LOOPVAR);
213
+ } else if (node.init instanceof RapydScript.AST_Array) {
214
+ for (var i = 0; i < node.init.elements.length; i++) {
215
+ var cnode = node.init.elements[i];
216
+ if (cnode instanceof RapydScript.AST_Seq) cnode = cnode.to_array();
217
+ if (cnode instanceof RapydScript.AST_SymbolRef) cnode = [cnode];
218
+ if (Array.isArray(cnode)) cnode.forEach(function (elem) {
219
+ if (elem instanceof RapydScript.AST_SymbolRef) { elem._rs_is_binding = true; self.define(elem.name, elem, KIND.LOOPVAR); }
220
+ });
221
+ }
222
+ }
223
+ };
224
+
225
+ Analyzer.prototype.handle_for_js = function (node) {
226
+ var js = node.condition.value;
227
+ var decl = js.split(';')[0].trim();
228
+ if (decl.slice(0, 4) === 'var ') decl = decl.slice(4);
229
+ var self = this;
230
+ decl.split(',').forEach(function (part) {
231
+ var m = /^[a-zA-Z0-9_$]+/.exec(part.replace(/^\s+/, ''));
232
+ if (m) self.define(m[0], node, KIND.VARIABLE);
233
+ });
234
+ };
235
+
236
+ Analyzer.prototype.handle_except = function (node) {
237
+ if (node.argname) this.define(node.argname.name, node.argname, KIND.EXCEPT);
238
+ };
239
+
240
+ Analyzer.prototype.handle_with_clause = function (node) {
241
+ if (node.alias) this.define(node.alias.name, node.alias, KIND.VARIABLE);
242
+ };
243
+
244
+ Analyzer.prototype.handle_symbol_ref = function (node) {
245
+ if (node._rs_is_binding) return; // already consumed as a definition
246
+ this.reference(node.name, node);
247
+ };
248
+
249
+ Analyzer.prototype.handle_dot = function (node) {
250
+ // Record `object.property` where object is a simple name, for cross-file
251
+ // member resolution (e.g. `mod.func`).
252
+ if (node.expression instanceof RapydScript.AST_SymbolRef) {
253
+ var r = dot_property_range(node);
254
+ if (r) this.member_accesses.push({ object_node: node.expression, property: node.property, range: r, node: node });
255
+ }
256
+ };
257
+
258
+ // -- the visitor -------------------------------------------------------------
259
+
260
+ Analyzer.prototype.visit = function (node, cont) {
261
+ var scope_count = this.scopes.length;
262
+
263
+ if (node instanceof RapydScript.AST_Lambda) this.handle_lambda(node);
264
+ else if (node instanceof RapydScript.AST_Import) this.handle_import(node);
265
+ else if (node instanceof RapydScript.AST_ImportedVar) this.handle_imported_var(node);
266
+ else if (node instanceof RapydScript.AST_Class) this.handle_class(node);
267
+ else if (node instanceof RapydScript.AST_BaseCall) this.handle_call(node);
268
+ else if (node instanceof RapydScript.AST_Assign) this.handle_assign(node);
269
+ else if (node instanceof RapydScript.AST_VarDef) this.handle_vardef(node);
270
+ else if (node instanceof RapydScript.AST_Dot) this.handle_dot(node);
271
+ else if (node instanceof RapydScript.AST_SymbolRef) this.handle_symbol_ref(node);
272
+ else if (node instanceof RapydScript.AST_Decorator) this.handle_decorator(node);
273
+ else if (node instanceof RapydScript.AST_SymbolFunarg) this.handle_symbol_funarg(node);
274
+ else if (node instanceof RapydScript.AST_ListComprehension) { this.push_scope(node, 'comprehension'); this.handle_for_in(node); }
275
+ else if (node instanceof RapydScript.AST_ForIn) this.handle_for_in(node);
276
+ else if (node instanceof RapydScript.AST_ForJS) this.handle_for_js(node);
277
+ else if (node instanceof RapydScript.AST_Except) this.handle_except(node);
278
+ else if (node instanceof RapydScript.AST_WithClause) this.handle_with_clause(node);
279
+
280
+ if (!(node instanceof RapydScript.AST_ListComprehension) && node instanceof RapydScript.AST_Scope) {
281
+ this.push_scope(node, node instanceof RapydScript.AST_Class ? 'class' : (node instanceof RapydScript.AST_Toplevel ? 'toplevel' : 'function'));
282
+ }
283
+
284
+ if (cont !== undefined) cont();
285
+
286
+ if (this.scopes.length > scope_count) this.pop_scope();
287
+ };
288
+
289
+ Analyzer.prototype.handle_call = function (node) {
290
+ if (node.args && node.args.kwargs) node.args.kwargs.forEach(function (kw) { if (kw[0]) kw[0]._rs_is_binding = true; });
291
+ };
292
+
293
+ Analyzer.prototype.handle_decorator = function (node) {
294
+ var e = node.expression;
295
+ if (e instanceof RapydScript.AST_SymbolRef && RapydScript.compile_time_decorators.indexOf(e.name) !== -1) e._rs_is_binding = true;
296
+ };
297
+
298
+ // Resolve a reference to the nearest enclosing scope that binds `name`.
299
+ function resolve_in_scopes(scope, name) {
300
+ for (var s = scope; s; s = s.parent) {
301
+ if (has_prop(s.bindings, name)) return s.bindings[name];
302
+ }
303
+ return null;
304
+ }
305
+
306
+ Analyzer.prototype.finalize = function () {
307
+ var self = this;
308
+ this.pending_refs.forEach(function (r) {
309
+ if (r.node._rs_is_binding) return;
310
+ var def = resolve_in_scopes(r.scope, r.name);
311
+ var range = symbol_range(r.node, r.name);
312
+ var rec = { name: r.name, node: r.node, range: range, def: def || null };
313
+ self.all_refs.push(rec);
314
+ if (def) def.ref_nodes.push(r.node);
315
+ r.node._rs_ref = rec;
316
+ });
317
+ };
318
+
319
+ function docstring_of(node) {
320
+ // Functions/classes keep their docstring on node.docstring (AST_String) when present.
321
+ if (node && node.docstrings && node.docstrings.length) {
322
+ var d = node.docstrings[0];
323
+ return (d && typeof d.value === 'string') ? d.value : (typeof d === 'string' ? d : null);
324
+ }
325
+ if (node && node.docstring && typeof node.docstring.value === 'string') return node.docstring.value;
326
+ return null;
327
+ }
328
+
329
+ // ---------------------------------------------------------------------------
330
+ // Public: build a SymbolIndex from an already-parsed toplevel.
331
+ // ---------------------------------------------------------------------------
332
+ function SymbolIndex(analyzer) {
333
+ this.toplevel = analyzer.toplevel;
334
+ this.filename = analyzer.filename;
335
+ this.defs = analyzer.all_defs;
336
+ this.refs = analyzer.all_refs;
337
+ this.imports = analyzer.imports;
338
+ this.member_accesses = analyzer.member_accesses;
339
+ this.import_names = analyzer.import_names || []; // aliased `from M import ORIG as x` ORIG spans
340
+ this.toplevel_scope = analyzer.scopes_root;
341
+ }
342
+
343
+ export function build_index(toplevel, filename) {
344
+ var a = new Analyzer(toplevel, filename);
345
+ var visitor = { _visit: function (node, descend) { a.visit(node, descend); } };
346
+ toplevel.walk(visitor);
347
+ a.scopes_root = toplevel._rs_scope || null;
348
+ a.finalize();
349
+ // Link `from M import name` bindings to their module key so cross-file
350
+ // resolution knows the exported symbol they came from.
351
+ a.import_names = [];
352
+ a.imports.forEach(function (imp) {
353
+ if (imp.kind === 'from' && imp.argnames) imp.argnames.forEach(function (av) {
354
+ if (av._rs_imported_var_def) av._rs_imported_var_def.import_key = imp.key;
355
+ if (av.alias) {
356
+ // The original exported name has its own span, distinct from the alias.
357
+ var r = symbol_range(av, av.name);
358
+ if (r) a.import_names.push({ range: r, key: imp.key, original: av.name, def: av._rs_imported_var_def });
359
+ }
360
+ });
361
+ });
362
+ return new SymbolIndex(a);
363
+ }
364
+
365
+ // Which def/ref covers `offset`? Returns {kind:'def'|'ref'|'member', def, node, range}.
366
+ export function locate(index, offset) {
367
+ function contains(range) { return range && offset >= range[0] && offset <= range[1]; }
368
+ // Prefer the innermost/most-specific: defs and refs are identifier sized so no nesting.
369
+ for (var i = 0; i < index.defs.length; i++) {
370
+ var d = index.defs[i];
371
+ for (var j = 0; j < d.def_nodes.length; j++) {
372
+ var r = symbol_range(d.def_nodes[j], d.name);
373
+ if (contains(r)) return { kind: 'def', def: d, node: d.def_nodes[j], range: r };
374
+ }
375
+ }
376
+ for (var k = 0; k < index.refs.length; k++) {
377
+ if (contains(index.refs[k].range)) return { kind: 'ref', def: index.refs[k].def, node: index.refs[k].node, range: index.refs[k].range, ref: index.refs[k] };
378
+ }
379
+ for (var m = 0; m < index.member_accesses.length; m++) {
380
+ if (contains(index.member_accesses[m].range)) return { kind: 'member', member: index.member_accesses[m], range: index.member_accesses[m].range };
381
+ }
382
+ for (var n = 0; n < index.import_names.length; n++) {
383
+ if (contains(index.import_names[n].range)) return { kind: 'import-name', import_name: index.import_names[n], range: index.import_names[n].range };
384
+ }
385
+ return null;
386
+ }
387
+
388
+ // Collect the set of visible symbol names at a given offset (for completion):
389
+ // walk the scope tree to the deepest scope containing the offset, then gather
390
+ // bindings from that scope outward.
391
+ export function visible_symbols(index, offset) {
392
+ var root = index.toplevel_scope;
393
+ var out = [];
394
+ var seen = Object.create(null);
395
+ function span_contains(scope) {
396
+ var sp = node_span(scope.node);
397
+ return sp && offset >= sp[0] && offset <= sp[1];
398
+ }
399
+ function deepest(scope) {
400
+ for (var i = 0; i < scope.children.length; i++) {
401
+ if (span_contains(scope.children[i])) { var d = deepest(scope.children[i]); if (d) return d; }
402
+ }
403
+ return scope;
404
+ }
405
+ if (!root) return out;
406
+ var scope = deepest(root);
407
+ for (var s = scope; s; s = s.parent) {
408
+ if (s.is_class && s !== scope) continue; // class scope not visible to nested functions
409
+ for (var name in s.bindings) {
410
+ if (seen[name]) continue;
411
+ seen[name] = true;
412
+ out.push({ name: name, def: s.bindings[name] });
413
+ }
414
+ }
415
+ return out;
416
+ }
417
+
418
+ export { RapydScript };