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,63 @@
1
+ # vim:fileencoding=utf-8
2
+ # License: BSD Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
3
+ # globals: ρσ_str, ρσ_last_exception
4
+
5
+
6
+ def _get_internal_traceback(err):
7
+ if isinstance(err, Exception) and err.stack:
8
+ lines = ρσ_str.splitlines(err.stack)
9
+ final_lines = v'[]'
10
+ found_sentinel = False
11
+ for i, line in enumerate(lines):
12
+ sline = ρσ_str.strip(line)
13
+ if i is 0:
14
+ final_lines.push(line)
15
+ continue
16
+ if found_sentinel:
17
+ final_lines.push(line)
18
+ continue
19
+ # These two conditions work on desktop Chrome and Firefox to identify the correct
20
+ # line in the traceback.
21
+ if sline.startsWith('at new ' + err.name) or sline.startsWith(err.name + '@'):
22
+ found_sentinel = True
23
+ return final_lines.join('\n')
24
+ return err and err.stack
25
+
26
+ def format_exception(exc, limit):
27
+ if jstype(exc) is 'undefined':
28
+ exc = ρσ_last_exception
29
+ if not isinstance(exc, Error):
30
+ if exc and exc.toString:
31
+ return [exc.toString()]
32
+ return []
33
+ tb = _get_internal_traceback(exc)
34
+ if tb:
35
+ lines = ρσ_str.splitlines(tb)
36
+ e = lines[0]
37
+ lines = lines[1:]
38
+ if limit:
39
+ lines = lines[:limit+1] if limit > 0 else lines[limit:]
40
+ lines.reverse()
41
+ lines.push(e)
42
+ lines.insert(0, 'Traceback (most recent call last):')
43
+ return [l+'\n' for l in lines]
44
+ return [exc.toString()]
45
+
46
+ def format_exc(limit):
47
+ return format_exception(ρσ_last_exception, limit).join('')
48
+
49
+ def print_exc(limit):
50
+ print(format_exc(limit))
51
+
52
+ def format_stack(limit):
53
+ stack = Error().stack
54
+ if not stack:
55
+ return []
56
+ lines = str.splitlines(stack)[2:]
57
+ lines.reverse()
58
+ if limit:
59
+ lines = lines[:limit+1] if limit > 0 else lines[limit:]
60
+ return [l + '\n' for l in lines]
61
+
62
+ def print_stack(limit):
63
+ print(format_stack(limit).join(''))
@@ -0,0 +1,77 @@
1
+ # vim:fileencoding=utf-8
2
+ # License: BSD Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
3
+ # globals: crypto
4
+ from __python__ import hash_literals
5
+
6
+ from encodings import hexlify, urlsafe_b64decode, urlsafe_b64encode
7
+
8
+ RFC_4122 = 1
9
+
10
+ if jstype(crypto) is 'object' and crypto.getRandomValues:
11
+ random_bytes = def (num):
12
+ ans = Uint8Array(num or 16)
13
+ crypto.getRandomValues(ans)
14
+ return ans
15
+ else:
16
+ random_bytes = def (num):
17
+ ans = Uint8Array(num or 16)
18
+ for i in range(ans.length):
19
+ ans[i] = Math.floor(Math.random() * 256)
20
+ return ans
21
+
22
+
23
+ def uuid4_bytes():
24
+ data = random_bytes()
25
+ data[6] = 0b01000000 | (data[6] & 0b1111)
26
+ data[8] = (((data[8] >> 4) & 0b11 | 0b1000) << 4) | (data[8] & 0b1111)
27
+ return data
28
+
29
+
30
+ def as_str():
31
+ h = this.hex
32
+ return h[:8] + '-' + h[8:12] + '-' + h[12:16] + '-' + h[16:20] + '-' + h[20:]
33
+
34
+
35
+ def uuid4():
36
+ b = uuid4_bytes()
37
+ return {
38
+ 'hex': hexlify(b),
39
+ 'bytes': b,
40
+ 'variant': RFC_4122,
41
+ 'version': 4,
42
+ '__str__': as_str,
43
+ 'toString': as_str,
44
+ }
45
+
46
+
47
+ def num_to_string(numbers, alphabet, pad_to_length):
48
+ ans = v'[]'
49
+ alphabet_len = alphabet.length
50
+ numbers = Array.prototype.slice.call(numbers)
51
+ for v'var i = 0; i < numbers.length - 1; i++':
52
+ x = divmod(numbers[i], alphabet_len)
53
+ numbers[i] = x[0]
54
+ numbers[i+1] += x[1]
55
+ for v'var i = 0; i < numbers.length; i++':
56
+ number = numbers[i]
57
+ while number:
58
+ x = divmod(number, alphabet_len)
59
+ number = x[0]
60
+ ans.push(alphabet[x[1]])
61
+ if pad_to_length and pad_to_length > ans.length:
62
+ ans.push(alphabet[0].repeat(pad_to_length - ans.length))
63
+ return ans.join('')
64
+
65
+
66
+ def short_uuid():
67
+ # A totally random uuid encoded using only URL and filename safe characters
68
+ return urlsafe_b64encode(random_bytes(), '')
69
+
70
+
71
+ def short_uuid4():
72
+ # A uuid4 encoded using only URL and filename safe characters
73
+ return urlsafe_b64encode(uuid4_bytes(), '')
74
+
75
+
76
+ def decode_short_uuid(val):
77
+ return urlsafe_b64decode(val + '==')
@@ -0,0 +1,526 @@
1
+ // analyzer.js — AST walker that builds a ScopeMap for a RapydScript source file.
2
+ //
3
+ // Usage:
4
+ // import { SourceAnalyzer } from './analyzer.js';
5
+ // const analyzer = new SourceAnalyzer(compiler);
6
+ // const scopeMap = analyzer.analyze(sourceCode, { virtualFiles: {...} });
7
+
8
+ import { ScopeMap, ScopeFrame, SymbolInfo } from './scope.js';
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // Helpers
12
+ // ---------------------------------------------------------------------------
13
+
14
+ function build_scoped_flags(flags_str) {
15
+ const result = Object.create(null);
16
+ if (!flags_str) return result;
17
+ flags_str.split(',').forEach(flag => {
18
+ flag = flag.trim();
19
+ if (!flag) return;
20
+ let val = true;
21
+ if (flag.startsWith('no_')) { val = false; flag = flag.slice(3); }
22
+ result[flag] = val;
23
+ });
24
+ return result;
25
+ }
26
+
27
+ /** Convert AST token position {line (1-indexed), col (0-indexed)} → {line, column} (both 1-indexed). */
28
+ function pos_from_token(tok) {
29
+ if (!tok) return { line: 1, column: 1 };
30
+ return { line: tok.line, column: tok.col + 1 };
31
+ }
32
+
33
+ /**
34
+ * Extract the first docstring value from a scope node, if present.
35
+ * The parser populates node.docstrings for AST_Scope nodes.
36
+ */
37
+ function extract_doc(node) {
38
+ if (node.docstrings && node.docstrings.length > 0) {
39
+ const ds = node.docstrings[0];
40
+ return ds.value !== undefined ? ds.value : null;
41
+ }
42
+ return null;
43
+ }
44
+
45
+ /**
46
+ * Recursively build a dot-path string from a chain of AST_Dot / AST_SymbolRef nodes.
47
+ * Returns null if the node is not a plain dot-chain (e.g. bracket subscript access).
48
+ * Examples:
49
+ * AST_SymbolRef{name:'ns'} → 'ns'
50
+ * AST_Dot{expr: AST_SymbolRef{name:'ns'}, prop: 'getServer'} → 'ns.getServer'
51
+ */
52
+ function dot_path_from_node(node, RS) {
53
+ if (node instanceof RS.AST_SymbolRef) return node.name;
54
+ if (node instanceof RS.AST_Dot) {
55
+ const left = dot_path_from_node(node.expression, RS);
56
+ return left ? left + '.' + node.property : null;
57
+ }
58
+ return null;
59
+ }
60
+
61
+ /**
62
+ * Collect parameter descriptors from an AST_Lambda node.
63
+ * Handles regular args, positional-only (/), keyword-only (*), *args, and **kwargs.
64
+ * Each entry: { name, is_rest, is_kwargs, is_posonly, is_kwonly, is_separator }
65
+ * Separator entries have is_separator=true and name='/' or '*'.
66
+ */
67
+ function extract_params(lambda_node) {
68
+ const params = [];
69
+ const argnames = lambda_node.argnames;
70
+ if (!argnames) return params;
71
+
72
+ let posonly_done = false;
73
+ argnames.forEach(arg => {
74
+ if (!arg || !arg.name) return;
75
+ // Emit '/' separator after all positional-only args
76
+ if (!posonly_done && !arg.posonly && (argnames.posonly_count || 0) > 0) {
77
+ params.push({ name: '/', is_rest: false, is_kwargs: false, is_posonly: false, is_kwonly: false, is_separator: true });
78
+ posonly_done = true;
79
+ } else if (!posonly_done && arg.posonly) {
80
+ posonly_done = false; // still in posonly section
81
+ }
82
+ params.push({ name: arg.name, is_rest: false, is_kwargs: false,
83
+ is_posonly: !!arg.posonly, is_kwonly: !!arg.kwonly, is_separator: false });
84
+ });
85
+ // If all args were posonly, still emit the '/' separator
86
+ if (!posonly_done && (argnames.posonly_count || 0) > 0 && !argnames.starargs && !argnames.bare_star) {
87
+ params.push({ name: '/', is_rest: false, is_kwargs: false, is_posonly: false, is_kwonly: false, is_separator: true });
88
+ }
89
+ if (argnames.bare_star) {
90
+ params.push({ name: '*', is_rest: false, is_kwargs: false, is_posonly: false, is_kwonly: false, is_separator: true });
91
+ }
92
+ if (argnames.starargs && argnames.starargs.name) {
93
+ params.push({ name: argnames.starargs.name, is_rest: true, is_kwargs: false, is_posonly: false, is_kwonly: false, is_separator: false });
94
+ }
95
+ if (argnames.kwargs && argnames.kwargs.name) {
96
+ params.push({ name: argnames.kwargs.name, is_rest: false, is_kwargs: true, is_posonly: false, is_kwonly: false, is_separator: false });
97
+ }
98
+ return params;
99
+ }
100
+
101
+ // ---------------------------------------------------------------------------
102
+ // ScopeBuilder — internal AST visitor
103
+ // Mirrors the structure of tools/lint.js's Linter but builds a ScopeMap
104
+ // instead of recording lint errors.
105
+ // ---------------------------------------------------------------------------
106
+
107
+ class ScopeBuilder {
108
+ constructor(RS, map) {
109
+ this._RS = RS;
110
+ this._map = map;
111
+ this._scopes = []; // stack of ScopeFrame
112
+ this.current_node = null;
113
+ }
114
+
115
+ _current_scope() {
116
+ return this._scopes.length ? this._scopes[this._scopes.length - 1] : null;
117
+ }
118
+
119
+ _push_scope(kind, name, node) {
120
+ const parent = this._current_scope();
121
+ const depth = parent ? parent.depth + 1 : 0;
122
+
123
+ const start_tok = node.start;
124
+ const end_tok = node.end;
125
+
126
+ const frame = new ScopeFrame({
127
+ kind,
128
+ name,
129
+ range: {
130
+ start: start_tok ? { line: start_tok.line, column: start_tok.col + 1 } : { line: 1, column: 1 },
131
+ end: end_tok ? { line: end_tok.line, column: end_tok.col + 1 } : { line: 999999, column: 999999 },
132
+ },
133
+ parent,
134
+ depth,
135
+ });
136
+
137
+ this._map.addFrame(frame);
138
+ this._scopes.push(frame);
139
+ return frame;
140
+ }
141
+
142
+ _pop_scope() {
143
+ return this._scopes.pop();
144
+ }
145
+
146
+ _add_symbol(opts) {
147
+ const scope = this._current_scope();
148
+ if (!scope || !opts.name) return null;
149
+ const sym = new SymbolInfo({
150
+ name: opts.name,
151
+ kind: opts.kind,
152
+ defined_at: opts.defined_at || { line: 1, column: 1 },
153
+ scope_depth: scope.depth,
154
+ doc: opts.doc || null,
155
+ params: opts.params || null,
156
+ inferred_class: opts.inferred_class || null,
157
+ dts_call_path: opts.dts_call_path || null,
158
+ });
159
+ scope.addSymbol(sym);
160
+ return sym;
161
+ }
162
+
163
+ // ---- The visitor function called by node.walk() ----------------------
164
+
165
+ _visit(node, cont) {
166
+ this.current_node = node;
167
+ const RS = this._RS;
168
+ const prev_depth = this._scopes.length;
169
+
170
+ // ------------------------------------------------------------------
171
+ // 1. Scope-creating nodes — push a frame BEFORE recursing.
172
+ // Order matters: more specific classes first (AST_Method < AST_Lambda < AST_Scope).
173
+ // ------------------------------------------------------------------
174
+
175
+ if (node instanceof RS.AST_Lambda) {
176
+ // Covers AST_Function and AST_Method.
177
+ const is_method = node instanceof RS.AST_Method;
178
+ const name = node.name ? node.name.name : null;
179
+
180
+ // Add the function/method symbol to the PARENT scope first.
181
+ if (name) {
182
+ const parent = this._current_scope();
183
+ if (parent) {
184
+ const sym = new SymbolInfo({
185
+ name,
186
+ kind: is_method ? 'method' : 'function',
187
+ defined_at: pos_from_token(node.start),
188
+ scope_depth: parent.depth,
189
+ doc: extract_doc(node),
190
+ params: extract_params(node),
191
+ });
192
+ parent.addSymbol(sym);
193
+ }
194
+ }
195
+
196
+ // Push a new scope for the function body.
197
+ this._push_scope(is_method ? 'function' : 'function', name, node);
198
+
199
+ } else if (node instanceof RS.AST_Class) {
200
+ const name = node.name ? node.name.name : null;
201
+
202
+ // Add class symbol to the parent scope.
203
+ if (name) {
204
+ const parent = this._current_scope();
205
+ if (parent) {
206
+ parent.addSymbol(new SymbolInfo({
207
+ name,
208
+ kind: 'class',
209
+ defined_at: pos_from_token(node.start),
210
+ scope_depth: parent.depth,
211
+ doc: extract_doc(node),
212
+ params: null,
213
+ }));
214
+ }
215
+ }
216
+
217
+ // Push class scope.
218
+ this._push_scope('class', name, node);
219
+
220
+ } else if (node instanceof RS.AST_ListComprehension) {
221
+ // AST_ListComprehension extends AST_ForIn, NOT AST_Scope.
222
+ // We give it its own scope so its loop variable doesn't leak.
223
+ this._push_scope('comprehension', null, node);
224
+
225
+ // The loop variable (node.init) is visited by the walk, but we
226
+ // add it now so it lands in the comprehension frame.
227
+ if (node.init instanceof RS.AST_SymbolRef) {
228
+ this._add_symbol({
229
+ name: node.init.name,
230
+ kind: 'variable',
231
+ defined_at: pos_from_token(node.init.start),
232
+ });
233
+ node.init.scope_builder_visited = true;
234
+ }
235
+
236
+ } else if (node instanceof RS.AST_Scope) {
237
+ // AST_Toplevel and any other raw scope nodes.
238
+ const kind = node instanceof RS.AST_Toplevel ? 'module' : 'block';
239
+ this._push_scope(kind, null, node);
240
+ }
241
+
242
+ // ------------------------------------------------------------------
243
+ // 2. Symbol-producing nodes (order doesn't matter for these).
244
+ // ------------------------------------------------------------------
245
+
246
+ if (node instanceof RS.AST_SymbolFunarg) {
247
+ // A regular function parameter; starargs/kwargs are also SymbolFunarg.
248
+ this._add_symbol({
249
+ name: node.name,
250
+ kind: 'parameter',
251
+ defined_at: pos_from_token(node.start),
252
+ });
253
+
254
+ } else if (node instanceof RS.AST_ImportedVar) {
255
+ // `from X import name` or `from X import name as alias`
256
+ const name = (node.alias && node.alias.name) ? node.alias.name : node.name;
257
+ if (name) {
258
+ this._add_symbol({
259
+ name,
260
+ kind: 'import',
261
+ defined_at: pos_from_token(node.start),
262
+ });
263
+ }
264
+
265
+ } else if (node instanceof RS.AST_Import && !node.argnames) {
266
+ // `import X` or `import X as Y` (no from-clause)
267
+ const name = (node.alias && node.alias.name)
268
+ ? node.alias.name
269
+ : (node.key ? node.key.split('.')[0] : null);
270
+ if (name) {
271
+ this._add_symbol({
272
+ name,
273
+ kind: 'import',
274
+ defined_at: pos_from_token(node.start),
275
+ });
276
+ }
277
+
278
+ } else if (node instanceof RS.AST_VarDef) {
279
+ // `var` declarations emitted by the compiler; name can be SymbolVar or SymbolNonlocal.
280
+ if (node.name && !(node.name instanceof RS.AST_SymbolNonlocal)) {
281
+ const name = typeof node.name.name === 'string' ? node.name.name : null;
282
+ if (name) {
283
+ this._add_symbol({
284
+ name,
285
+ kind: 'variable',
286
+ defined_at: pos_from_token(node.start),
287
+ });
288
+ }
289
+ }
290
+
291
+ } else if (node instanceof RS.AST_Assign && node.operator === '=') {
292
+ // Simple assignment: add left-hand symbol if new to this scope,
293
+ // or update inferred_class on an existing symbol (hoisted var declarations
294
+ // inside functions are added by AST_VarDef without type info; the
295
+ // assignment provides the type).
296
+ // Tuple/starred unpacking: register each variable in the LHS
297
+ if (node.left instanceof RS.AST_Array) {
298
+ const scope = this._current_scope();
299
+ if (scope) {
300
+ node.left.flatten().forEach(elem => {
301
+ let sym = null;
302
+ if (elem instanceof RS.AST_Starred && elem.expression instanceof RS.AST_SymbolRef) {
303
+ sym = elem.expression;
304
+ } else if (elem instanceof RS.AST_SymbolRef) {
305
+ sym = elem;
306
+ }
307
+ if (sym && !scope.getSymbol(sym.name)) {
308
+ this._add_symbol({
309
+ name: sym.name,
310
+ kind: 'variable',
311
+ defined_at: pos_from_token(sym.start),
312
+ inferred_class: elem instanceof RS.AST_Starred ? 'list' : null,
313
+ });
314
+ }
315
+ });
316
+ }
317
+ } else if (node.left instanceof RS.AST_SymbolRef) {
318
+ const name = node.left.name;
319
+ const scope = this._current_scope();
320
+ if (scope) {
321
+ // Detect the RHS type regardless of whether the symbol exists.
322
+ let inferred_class = null;
323
+ let dts_call_path = null;
324
+ if (node.right instanceof RS.AST_BaseCall &&
325
+ node.right.expression instanceof RS.AST_SymbolRef) {
326
+ inferred_class = node.right.expression.name;
327
+ } else if (node.right instanceof RS.AST_BaseCall &&
328
+ node.right.expression instanceof RS.AST_Dot) {
329
+ // x = obj.method(...) — capture the full dot-path for DTS type resolution.
330
+ const call_path = dot_path_from_node(node.right.expression, RS);
331
+ if (call_path) dts_call_path = call_path;
332
+ } else if (node.right instanceof RS.AST_Array) {
333
+ inferred_class = 'list';
334
+ } else if (node.right instanceof RS.AST_Object) {
335
+ inferred_class = 'dict';
336
+ } else if (node.right instanceof RS.AST_String) {
337
+ inferred_class = 'str';
338
+ } else if (node.right instanceof RS.AST_Number) {
339
+ inferred_class = 'number';
340
+ }
341
+ const existing = scope.getSymbol(name);
342
+ if (!existing) {
343
+ this._add_symbol({
344
+ name,
345
+ kind: 'variable',
346
+ defined_at: pos_from_token(node.left.start),
347
+ inferred_class,
348
+ dts_call_path,
349
+ });
350
+ } else if (inferred_class && !existing.inferred_class) {
351
+ // Update the hoisted-but-untyped symbol with the inferred type.
352
+ existing.inferred_class = inferred_class;
353
+ } else if (dts_call_path && !existing.dts_call_path) {
354
+ existing.dts_call_path = dts_call_path;
355
+ }
356
+ }
357
+ }
358
+
359
+ } else if (node instanceof RS.AST_NamedExpr) {
360
+ // Walrus operator: name := value — register the name in the current scope.
361
+ const name = node.name && node.name.name;
362
+ const scope = this._current_scope();
363
+ if (name && scope && !scope.getSymbol(name)) {
364
+ this._add_symbol({
365
+ name,
366
+ kind: 'variable',
367
+ defined_at: pos_from_token(node.name.start),
368
+ });
369
+ }
370
+
371
+ } else if (
372
+ node instanceof RS.AST_ForIn &&
373
+ !(node instanceof RS.AST_ListComprehension) &&
374
+ node.init instanceof RS.AST_SymbolRef &&
375
+ !node.init.scope_builder_visited
376
+ ) {
377
+ // Regular `for x in ...` loop variable.
378
+ this._add_symbol({
379
+ name: node.init.name,
380
+ kind: 'variable',
381
+ defined_at: pos_from_token(node.init.start),
382
+ });
383
+ node.init.scope_builder_visited = true;
384
+
385
+ } else if (node instanceof RS.AST_Except && node.argname) {
386
+ // `except SomeError as e`
387
+ this._add_symbol({
388
+ name: node.argname.name,
389
+ kind: 'variable',
390
+ defined_at: pos_from_token(node.argname.start),
391
+ });
392
+
393
+ } else if (node instanceof RS.AST_WithClause && node.alias) {
394
+ // `with ctx as alias`
395
+ this._add_symbol({
396
+ name: node.alias.name,
397
+ kind: 'variable',
398
+ defined_at: pos_from_token(node.alias.start),
399
+ });
400
+
401
+ } else if (RS.AST_MatchCapture && node instanceof RS.AST_MatchCapture) {
402
+ // match/case capture pattern: `case x:` → binds x
403
+ if (node.name) {
404
+ this._add_symbol({
405
+ name: node.name,
406
+ kind: 'variable',
407
+ defined_at: pos_from_token(node.start),
408
+ });
409
+ }
410
+
411
+ } else if (RS.AST_MatchAs && node instanceof RS.AST_MatchAs) {
412
+ // match/case AS pattern: `case pattern as name:` → binds name
413
+ if (node.name) {
414
+ this._add_symbol({
415
+ name: node.name,
416
+ kind: 'variable',
417
+ defined_at: pos_from_token(node.start),
418
+ });
419
+ }
420
+
421
+ } else if (RS.AST_MatchStar && node instanceof RS.AST_MatchStar) {
422
+ // Star element in sequence pattern: `case [*rest]` → binds rest
423
+ if (node.name) {
424
+ this._add_symbol({
425
+ name: node.name,
426
+ kind: 'variable',
427
+ defined_at: pos_from_token(node.start),
428
+ });
429
+ }
430
+
431
+ } else if (RS.AST_MatchMapping && node instanceof RS.AST_MatchMapping && node.rest_name) {
432
+ // Mapping rest: `case {"k": v, **rest}` → binds rest
433
+ this._add_symbol({
434
+ name: node.rest_name,
435
+ kind: 'variable',
436
+ defined_at: pos_from_token(node.start),
437
+ });
438
+
439
+ } else if (RS.AST_AnnotatedAssign && node instanceof RS.AST_AnnotatedAssign) {
440
+ // Variable type annotation: `x: int = value` or `x: int`
441
+ if (node.target instanceof RS.AST_SymbolRef) {
442
+ const name = node.target.name;
443
+ const scope = this._current_scope();
444
+ if (name && scope) {
445
+ let inferred_class = null;
446
+ // Use the annotation as the declared type for inferred_class
447
+ if (node.annotation instanceof RS.AST_SymbolRef) {
448
+ inferred_class = node.annotation.name;
449
+ }
450
+ const existing = scope.getSymbol(name);
451
+ if (!existing) {
452
+ this._add_symbol({
453
+ name,
454
+ kind: 'variable',
455
+ defined_at: pos_from_token(node.target.start),
456
+ inferred_class,
457
+ });
458
+ } else if (inferred_class && !existing.inferred_class) {
459
+ existing.inferred_class = inferred_class;
460
+ }
461
+ }
462
+ }
463
+ }
464
+
465
+ // ------------------------------------------------------------------
466
+ // 3. Recurse into child nodes.
467
+ // ------------------------------------------------------------------
468
+
469
+ if (cont) cont();
470
+
471
+ // ------------------------------------------------------------------
472
+ // 4. Pop the scope we pushed (if any).
473
+ // ------------------------------------------------------------------
474
+
475
+ if (this._scopes.length > prev_depth) {
476
+ this._pop_scope();
477
+ }
478
+ }
479
+ }
480
+
481
+ // ---------------------------------------------------------------------------
482
+ // SourceAnalyzer — public API
483
+ // ---------------------------------------------------------------------------
484
+
485
+ export class SourceAnalyzer {
486
+ /**
487
+ * @param {object} compiler - window.RapydScript (the compiled compiler bundle)
488
+ * @param {string} [pythonFlags] - comma-separated python flags to enable globally
489
+ */
490
+ constructor(compiler, pythonFlags) {
491
+ this._RS = compiler;
492
+ this._scoped_flags = build_scoped_flags(pythonFlags);
493
+ }
494
+
495
+ /**
496
+ * Parse `code` and walk the resulting AST to build a ScopeMap.
497
+ * Returns an empty ScopeMap if parsing fails.
498
+ *
499
+ * @param {string} code
500
+ * @param {object} [options]
501
+ * @param {object} [options.virtualFiles] - map of module-name → source
502
+ * @returns {ScopeMap}
503
+ */
504
+ analyze(code, options) {
505
+ options = options || {};
506
+ const RS = this._RS;
507
+ const map = new ScopeMap();
508
+
509
+ let toplevel;
510
+ try {
511
+ toplevel = RS.parse(code, {
512
+ filename: 'editor.pyj',
513
+ for_linting: true,
514
+ ...(options.virtualFiles ? { virtual_files: options.virtualFiles } : {}),
515
+ ...(Object.keys(this._scoped_flags).length ? { scoped_flags: this._scoped_flags } : {}),
516
+ });
517
+ } catch (_e) {
518
+ // Syntax/import error — return the empty map; diagnostics.js handles errors.
519
+ return map;
520
+ }
521
+
522
+ const builder = new ScopeBuilder(RS, map);
523
+ toplevel.walk(builder);
524
+ return map;
525
+ }
526
+ }