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.
- package/.agignore +1 -0
- package/.gitattributes +4 -0
- package/.github/workflows/ci.yml +38 -0
- package/.github/workflows/web-repl-page-deploy.yml +42 -0
- package/=template.pyj +5 -0
- package/CHANGELOG.md +456 -0
- package/CONTRIBUTORS +13 -0
- package/HACKING.md +103 -0
- package/LICENSE +24 -0
- package/README.md +2512 -0
- package/TODO.md +327 -0
- package/add-toc-to-readme +2 -0
- package/bin/export +75 -0
- package/bin/rapydscript +70 -0
- package/bin/web-repl-export +102 -0
- package/build +3 -0
- package/package.json +46 -0
- package/publish.py +37 -0
- package/release/baselib-plain-pretty.js +4370 -0
- package/release/baselib-plain-ugly.js +3 -0
- package/release/compiler.js +18394 -0
- package/release/signatures.json +31 -0
- package/session.vim +4 -0
- package/setup.cfg +2 -0
- package/src/ast.pyj +1356 -0
- package/src/baselib-builtins.pyj +279 -0
- package/src/baselib-containers.pyj +723 -0
- package/src/baselib-errors.pyj +37 -0
- package/src/baselib-internal.pyj +421 -0
- package/src/baselib-itertools.pyj +97 -0
- package/src/baselib-str.pyj +798 -0
- package/src/compiler.pyj +36 -0
- package/src/errors.pyj +30 -0
- package/src/lib/aes.pyj +646 -0
- package/src/lib/collections.pyj +695 -0
- package/src/lib/elementmaker.pyj +83 -0
- package/src/lib/encodings.pyj +126 -0
- package/src/lib/functools.pyj +148 -0
- package/src/lib/gettext.pyj +569 -0
- package/src/lib/itertools.pyj +580 -0
- package/src/lib/math.pyj +193 -0
- package/src/lib/numpy.pyj +2101 -0
- package/src/lib/operator.pyj +11 -0
- package/src/lib/pythonize.pyj +20 -0
- package/src/lib/random.pyj +118 -0
- package/src/lib/re.pyj +470 -0
- package/src/lib/traceback.pyj +63 -0
- package/src/lib/uuid.pyj +77 -0
- package/src/monaco-language-service/analyzer.js +526 -0
- package/src/monaco-language-service/builtins.js +543 -0
- package/src/monaco-language-service/completions.js +498 -0
- package/src/monaco-language-service/diagnostics.js +643 -0
- package/src/monaco-language-service/dts.js +550 -0
- package/src/monaco-language-service/hover.js +121 -0
- package/src/monaco-language-service/index.js +386 -0
- package/src/monaco-language-service/scope.js +162 -0
- package/src/monaco-language-service/signature.js +144 -0
- package/src/output/__init__.pyj +0 -0
- package/src/output/classes.pyj +296 -0
- package/src/output/codegen.pyj +492 -0
- package/src/output/comments.pyj +45 -0
- package/src/output/exceptions.pyj +105 -0
- package/src/output/functions.pyj +491 -0
- package/src/output/literals.pyj +109 -0
- package/src/output/loops.pyj +444 -0
- package/src/output/modules.pyj +329 -0
- package/src/output/operators.pyj +429 -0
- package/src/output/statements.pyj +463 -0
- package/src/output/stream.pyj +309 -0
- package/src/output/treeshake.pyj +182 -0
- package/src/output/utils.pyj +72 -0
- package/src/parse.pyj +3106 -0
- package/src/string_interpolation.pyj +72 -0
- package/src/tokenizer.pyj +702 -0
- package/src/unicode_aliases.pyj +576 -0
- package/src/utils.pyj +192 -0
- package/test/_import_one.pyj +37 -0
- package/test/_import_two/__init__.pyj +11 -0
- package/test/_import_two/level2/__init__.pyj +0 -0
- package/test/_import_two/level2/deep.pyj +4 -0
- package/test/_import_two/other.pyj +6 -0
- package/test/_import_two/sub.pyj +13 -0
- package/test/aes_vectors.pyj +421 -0
- package/test/annotations.pyj +80 -0
- package/test/baselib.pyj +319 -0
- package/test/classes.pyj +452 -0
- package/test/collections.pyj +152 -0
- package/test/decorators.pyj +77 -0
- package/test/dict_spread.pyj +76 -0
- package/test/docstrings.pyj +39 -0
- package/test/elementmaker_test.pyj +45 -0
- package/test/ellipsis.pyj +49 -0
- package/test/functions.pyj +151 -0
- package/test/generators.pyj +41 -0
- package/test/generic.pyj +370 -0
- package/test/imports.pyj +72 -0
- package/test/internationalization.pyj +73 -0
- package/test/lint.pyj +164 -0
- package/test/loops.pyj +85 -0
- package/test/numpy.pyj +734 -0
- package/test/omit_function_metadata.pyj +20 -0
- package/test/regexp.pyj +55 -0
- package/test/repl.pyj +121 -0
- package/test/scoped_flags.pyj +76 -0
- package/test/starargs.pyj +506 -0
- package/test/starred_assign.pyj +104 -0
- package/test/str.pyj +198 -0
- package/test/subscript_tuple.pyj +53 -0
- package/test/unit/fixtures/fibonacci_expected.js +46 -0
- package/test/unit/index.js +2989 -0
- package/test/unit/language-service-builtins.js +815 -0
- package/test/unit/language-service-completions.js +1067 -0
- package/test/unit/language-service-dts.js +543 -0
- package/test/unit/language-service-hover.js +455 -0
- package/test/unit/language-service-scope.js +833 -0
- package/test/unit/language-service-signature.js +458 -0
- package/test/unit/language-service.js +705 -0
- package/test/unit/run-language-service.js +41 -0
- package/test/unit/web-repl.js +484 -0
- package/tools/build-language-service.js +190 -0
- package/tools/cli.js +547 -0
- package/tools/compile.js +219 -0
- package/tools/compiler.js +108 -0
- package/tools/completer.js +131 -0
- package/tools/embedded_compiler.js +251 -0
- package/tools/export.js +316 -0
- package/tools/gettext.js +185 -0
- package/tools/ini.js +65 -0
- package/tools/lint.js +705 -0
- package/tools/msgfmt.js +187 -0
- package/tools/repl.js +223 -0
- package/tools/self.js +162 -0
- package/tools/test.js +118 -0
- package/tools/utils.js +128 -0
- package/tools/web_repl.js +95 -0
- package/try +41 -0
- package/web-repl/env.js +74 -0
- package/web-repl/index.html +163 -0
- package/web-repl/language-service.js +4084 -0
- package/web-repl/main.js +254 -0
- package/web-repl/prism.css +139 -0
- package/web-repl/prism.js +113 -0
- package/web-repl/rapydscript.js +435 -0
- 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(''))
|
package/src/lib/uuid.pyj
ADDED
|
@@ -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
|
+
}
|