rapydscript-ns 0.8.0 → 0.8.2
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/CONTRIBUTORS +3 -2
- package/PYTHON_FEATURE_COVERAGE.md +109 -0
- package/README.md +320 -34
- package/TODO.md +17 -48
- package/hack_demo.pyj +112 -0
- package/package.json +7 -5
- package/src/ast.pyj +30 -6
- package/src/baselib-builtins.pyj +104 -10
- package/src/baselib-containers.pyj +146 -1
- package/src/baselib-errors.pyj +3 -0
- package/src/baselib-internal.pyj +10 -0
- package/src/baselib-str.pyj +34 -0
- package/src/monaco-language-service/analyzer.js +1 -13
- package/src/monaco-language-service/builtins.js +10 -0
- package/src/monaco-language-service/completions.js +54 -2
- package/src/monaco-language-service/diagnostics.js +54 -4
- package/src/monaco-language-service/index.js +9 -5
- package/src/output/codegen.pyj +37 -2
- package/src/output/functions.pyj +31 -9
- package/src/output/loops.pyj +64 -2
- package/src/output/operators.pyj +53 -1
- package/src/output/statements.pyj +7 -3
- package/src/output/stream.pyj +6 -0
- package/src/parse.pyj +77 -13
- package/src/tokenizer.pyj +1 -0
- package/test/python_features.pyj +1184 -0
- package/test/unit/language-service-bundle.js +83 -0
- package/test/unit/language-service-completions.js +109 -0
- package/test/unit/language-service.js +123 -1
- package/test/unit/run-language-service.js +1 -0
- package/tools/lint.js +1 -1
- package/tools/self.js +1 -9
- package/web-repl/language-service.js +129 -26
- package/web-repl/rapydscript.js +3 -3
|
@@ -468,7 +468,7 @@ Object.defineProperties(ρσ_set.prototype, {
|
|
|
468
468
|
return '{' + list(this).join(', ') + '}'
|
|
469
469
|
|
|
470
470
|
ρσ_set.prototype.__eq__ = def(other):
|
|
471
|
-
if
|
|
471
|
+
if v'!other || !other.jsset':
|
|
472
472
|
return False
|
|
473
473
|
if other.size is not this.size:
|
|
474
474
|
return False
|
|
@@ -490,6 +490,142 @@ def ρσ_set_wrap(x):
|
|
|
490
490
|
v'var set = ρσ_set, set_wrap = ρσ_set_wrap'
|
|
491
491
|
# }}}
|
|
492
492
|
|
|
493
|
+
# frozenset {{{
|
|
494
|
+
def ρσ_frozenset(iterable):
|
|
495
|
+
if v'this instanceof ρσ_frozenset':
|
|
496
|
+
this.jsset = new ρσ_set_implementation() # noqa:undef
|
|
497
|
+
ans = this
|
|
498
|
+
if iterable is undefined:
|
|
499
|
+
return ans
|
|
500
|
+
s = ans.jsset
|
|
501
|
+
if ρσ_arraylike(iterable):
|
|
502
|
+
for v'var i = 0; i < iterable.length; i++':
|
|
503
|
+
s.add(iterable[i])
|
|
504
|
+
elif jstype(iterable[ρσ_iterator_symbol]) is 'function':
|
|
505
|
+
iterator = iterable.keys() if jstype(Map) is 'function' and v'iterable instanceof Map' else iterable[ρσ_iterator_symbol]()
|
|
506
|
+
result = iterator.next()
|
|
507
|
+
while not result.done:
|
|
508
|
+
s.add(result.value)
|
|
509
|
+
result = iterator.next()
|
|
510
|
+
else:
|
|
511
|
+
keys = Object.keys(iterable)
|
|
512
|
+
for v'var j=0; j < keys.length; j++':
|
|
513
|
+
s.add(keys[j])
|
|
514
|
+
return ans
|
|
515
|
+
else:
|
|
516
|
+
return new ρσ_frozenset(iterable)
|
|
517
|
+
ρσ_frozenset.prototype.__name__ = 'frozenset'
|
|
518
|
+
|
|
519
|
+
Object.defineProperties(ρσ_frozenset.prototype, {
|
|
520
|
+
'length': { 'get': def(): return this.jsset.size; },
|
|
521
|
+
'size': { 'get': def(): return this.jsset.size; },
|
|
522
|
+
})
|
|
523
|
+
|
|
524
|
+
ρσ_frozenset.prototype.__len__ = def(): return this.jsset.size
|
|
525
|
+
ρσ_frozenset.prototype.has = ρσ_frozenset.prototype.__contains__ = def(x): return this.jsset.has(x)
|
|
526
|
+
ρσ_frozenset.prototype.copy = def(): return ρσ_frozenset(this)
|
|
527
|
+
ρσ_frozenset.prototype[ρσ_iterator_symbol] = def(): return this.jsset.values()
|
|
528
|
+
|
|
529
|
+
ρσ_frozenset.prototype.difference = def():
|
|
530
|
+
ans = new ρσ_frozenset()
|
|
531
|
+
s = ans.jsset
|
|
532
|
+
iterator = this.jsset.values()
|
|
533
|
+
r = iterator.next()
|
|
534
|
+
while not r.done:
|
|
535
|
+
x = r.value
|
|
536
|
+
has = False
|
|
537
|
+
for v'var i = 0; i < arguments.length; i++':
|
|
538
|
+
if arguments[i].has(x): # noqa:undef
|
|
539
|
+
has = True
|
|
540
|
+
break
|
|
541
|
+
if not has:
|
|
542
|
+
s.add(x)
|
|
543
|
+
r = iterator.next()
|
|
544
|
+
return ans
|
|
545
|
+
|
|
546
|
+
ρσ_frozenset.prototype.intersection = def():
|
|
547
|
+
ans = new ρσ_frozenset()
|
|
548
|
+
s = ans.jsset
|
|
549
|
+
iterator = this.jsset.values()
|
|
550
|
+
r = iterator.next()
|
|
551
|
+
while not r.done:
|
|
552
|
+
x = r.value
|
|
553
|
+
has = True
|
|
554
|
+
for v'var i = 0; i < arguments.length; i++':
|
|
555
|
+
if not arguments[i].has(x): # noqa:undef
|
|
556
|
+
has = False
|
|
557
|
+
break
|
|
558
|
+
if has:
|
|
559
|
+
s.add(x)
|
|
560
|
+
r = iterator.next()
|
|
561
|
+
return ans
|
|
562
|
+
|
|
563
|
+
ρσ_frozenset.prototype.isdisjoint = def(other):
|
|
564
|
+
iterator = this.jsset.values()
|
|
565
|
+
r = iterator.next()
|
|
566
|
+
while not r.done:
|
|
567
|
+
x = r.value
|
|
568
|
+
if other.has(x):
|
|
569
|
+
return False
|
|
570
|
+
r = iterator.next()
|
|
571
|
+
return True
|
|
572
|
+
|
|
573
|
+
ρσ_frozenset.prototype.issubset = def(other):
|
|
574
|
+
iterator = this.jsset.values()
|
|
575
|
+
r = iterator.next()
|
|
576
|
+
while not r.done:
|
|
577
|
+
x = r.value
|
|
578
|
+
if not other.has(x):
|
|
579
|
+
return False
|
|
580
|
+
r = iterator.next()
|
|
581
|
+
return True
|
|
582
|
+
|
|
583
|
+
ρσ_frozenset.prototype.issuperset = def(other):
|
|
584
|
+
s = this.jsset
|
|
585
|
+
iterator = other[ρσ_iterator_symbol]()
|
|
586
|
+
r = iterator.next()
|
|
587
|
+
while not r.done:
|
|
588
|
+
x = r.value
|
|
589
|
+
if not s.has(x):
|
|
590
|
+
return False
|
|
591
|
+
r = iterator.next()
|
|
592
|
+
return True
|
|
593
|
+
|
|
594
|
+
ρσ_frozenset.prototype.symmetric_difference = def(other):
|
|
595
|
+
return this.union(other).difference(this.intersection(other))
|
|
596
|
+
|
|
597
|
+
ρσ_frozenset.prototype.union = def():
|
|
598
|
+
ans = ρσ_frozenset(this)
|
|
599
|
+
s = ans.jsset
|
|
600
|
+
for v'var i=0; i < arguments.length; i++':
|
|
601
|
+
iterator = arguments[i][ρσ_iterator_symbol]() # noqa:undef
|
|
602
|
+
r = iterator.next()
|
|
603
|
+
while not r.done:
|
|
604
|
+
s.add(r.value)
|
|
605
|
+
r = iterator.next()
|
|
606
|
+
return ans
|
|
607
|
+
|
|
608
|
+
ρσ_frozenset.prototype.toString = ρσ_frozenset.prototype.__repr__ = ρσ_frozenset.prototype.__str__ = ρσ_frozenset.prototype.inspect = def():
|
|
609
|
+
return 'frozenset({' + list(this).join(', ') + '})'
|
|
610
|
+
|
|
611
|
+
ρσ_frozenset.prototype.__eq__ = def(other):
|
|
612
|
+
if v'!other || !other.jsset':
|
|
613
|
+
return False
|
|
614
|
+
if other.size is not this.size:
|
|
615
|
+
return False
|
|
616
|
+
if other.size is 0:
|
|
617
|
+
return True
|
|
618
|
+
iterator = other[ρσ_iterator_symbol]()
|
|
619
|
+
r = iterator.next()
|
|
620
|
+
while not r.done:
|
|
621
|
+
if not this.has(r.value):
|
|
622
|
+
return False
|
|
623
|
+
r = iterator.next()
|
|
624
|
+
return True
|
|
625
|
+
|
|
626
|
+
v'var frozenset = ρσ_frozenset'
|
|
627
|
+
# }}}
|
|
628
|
+
|
|
493
629
|
# dict {{{
|
|
494
630
|
v'var ρσ_dict_implementation'
|
|
495
631
|
|
|
@@ -704,6 +840,15 @@ Object.defineProperties(ρσ_dict.prototype, {
|
|
|
704
840
|
r = iterator.next()
|
|
705
841
|
return True
|
|
706
842
|
|
|
843
|
+
ρσ_dict.prototype.__or__ = def(other):
|
|
844
|
+
result = ρσ_dict(this)
|
|
845
|
+
result.update(other)
|
|
846
|
+
return result
|
|
847
|
+
|
|
848
|
+
ρσ_dict.prototype.__ior__ = def(other):
|
|
849
|
+
this.update(other)
|
|
850
|
+
return this
|
|
851
|
+
|
|
707
852
|
ρσ_dict.prototype.as_object = def(other):
|
|
708
853
|
ans = {}
|
|
709
854
|
iterator = this.jsmap.entries()
|
package/src/baselib-errors.pyj
CHANGED
package/src/baselib-internal.pyj
CHANGED
|
@@ -282,6 +282,16 @@ def ρσ_op_mul(a, b):
|
|
|
282
282
|
if b is not None and jstype(b.__rmul__) is 'function': return b.__rmul__(a)
|
|
283
283
|
if (jstype(a) is 'string' or v'a instanceof String') and jstype(b) is 'number': return a.repeat(b)
|
|
284
284
|
if (jstype(b) is 'string' or v'b instanceof String') and jstype(a) is 'number': return b.repeat(a)
|
|
285
|
+
if Array.isArray(a) and jstype(b) is 'number':
|
|
286
|
+
result = v'[]'
|
|
287
|
+
for v'var ρσ_mi = 0; ρσ_mi < b; ρσ_mi++':
|
|
288
|
+
result = result.concat(a) # noqa:undef
|
|
289
|
+
return ρσ_list_constructor(result)
|
|
290
|
+
if Array.isArray(b) and jstype(a) is 'number':
|
|
291
|
+
result = v'[]'
|
|
292
|
+
for v'var ρσ_mi = 0; ρσ_mi < a; ρσ_mi++':
|
|
293
|
+
result = result.concat(b) # noqa:undef
|
|
294
|
+
return ρσ_list_constructor(result)
|
|
285
295
|
return a * b
|
|
286
296
|
|
|
287
297
|
def ρσ_op_truediv(a, b):
|
package/src/baselib-str.pyj
CHANGED
|
@@ -525,6 +525,40 @@ define_str_func('isspace', def():
|
|
|
525
525
|
return this.length > 0 and /^\s+$/.test(this)
|
|
526
526
|
)
|
|
527
527
|
|
|
528
|
+
define_str_func('isalpha', def():
|
|
529
|
+
return this.length > 0 and /^[a-zA-Z]+$/.test(this)
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
define_str_func('isdigit', def():
|
|
533
|
+
return this.length > 0 and /^\d+$/.test(this)
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
define_str_func('isalnum', def():
|
|
537
|
+
return this.length > 0 and /^[a-zA-Z0-9]+$/.test(this)
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
define_str_func('isidentifier', def():
|
|
541
|
+
return this.length > 0 and /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(this)
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
define_str_func('casefold', def():
|
|
545
|
+
return this.toLowerCase()
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
define_str_func('removeprefix', def(prefix):
|
|
549
|
+
s = this.toString()
|
|
550
|
+
if s.startsWith(prefix):
|
|
551
|
+
return s.slice(prefix.length)
|
|
552
|
+
return s
|
|
553
|
+
)
|
|
554
|
+
|
|
555
|
+
define_str_func('removesuffix', def(suffix):
|
|
556
|
+
s = this.toString()
|
|
557
|
+
if suffix.length and s.endsWith(suffix):
|
|
558
|
+
return s.slice(0, s.length - suffix.length)
|
|
559
|
+
return s
|
|
560
|
+
)
|
|
561
|
+
|
|
528
562
|
define_str_func('join', def(iterable):
|
|
529
563
|
if Array.isArray(iterable):
|
|
530
564
|
return iterable.join(this)
|
|
@@ -6,24 +6,12 @@
|
|
|
6
6
|
// const scopeMap = analyzer.analyze(sourceCode, { virtualFiles: {...} });
|
|
7
7
|
|
|
8
8
|
import { ScopeMap, ScopeFrame, SymbolInfo } from './scope.js';
|
|
9
|
+
import { build_scoped_flags } from './diagnostics.js';
|
|
9
10
|
|
|
10
11
|
// ---------------------------------------------------------------------------
|
|
11
12
|
// Helpers
|
|
12
13
|
// ---------------------------------------------------------------------------
|
|
13
14
|
|
|
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
15
|
/** Convert AST token position {line (1-indexed), col (0-indexed)} → {line, column} (both 1-indexed). */
|
|
28
16
|
function pos_from_token(tok) {
|
|
29
17
|
if (!tok) return { line: 1, column: 1 };
|
|
@@ -114,6 +114,11 @@ const STUBS = [
|
|
|
114
114
|
return_type: 'any',
|
|
115
115
|
doc: 'Return the value of the named attribute of obj. If not found, return default (or raise AttributeError).' }),
|
|
116
116
|
|
|
117
|
+
new BuiltinInfo({ name: 'hash',
|
|
118
|
+
params: [p('obj')],
|
|
119
|
+
return_type: 'int',
|
|
120
|
+
doc: 'Return the hash value of obj. Strings and numbers are hashed by value; class instances by identity. Raises TypeError for unhashable types (list, set, dict). Objects with __hash__ dispatch to it.' }),
|
|
121
|
+
|
|
117
122
|
new BuiltinInfo({ name: 'hasattr',
|
|
118
123
|
params: [p('obj'), p('name', { type: 'str' })],
|
|
119
124
|
return_type: 'bool',
|
|
@@ -139,6 +144,11 @@ const STUBS = [
|
|
|
139
144
|
return_type: 'bool',
|
|
140
145
|
doc: 'Return True if obj is an instance of classinfo (or a subclass thereof).' }),
|
|
141
146
|
|
|
147
|
+
new BuiltinInfo({ name: 'issubclass',
|
|
148
|
+
params: [p('cls'), p('classinfo')],
|
|
149
|
+
return_type: 'bool',
|
|
150
|
+
doc: 'Return True if cls is a subclass of classinfo. classinfo may be a class or tuple of classes.' }),
|
|
151
|
+
|
|
142
152
|
new BuiltinInfo({ name: 'iter',
|
|
143
153
|
params: [p('obj')],
|
|
144
154
|
return_type: 'iterator',
|
|
@@ -8,6 +8,25 @@
|
|
|
8
8
|
import { SourceAnalyzer } from './analyzer.js';
|
|
9
9
|
import { resolve_first_type } from './dts.js';
|
|
10
10
|
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Language keywords
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* RapydScript / Python keywords that are valid identifier-completion tokens.
|
|
17
|
+
* Shown in the `identifier` completion context only (not dot / import contexts).
|
|
18
|
+
*/
|
|
19
|
+
export const KEYWORDS = [
|
|
20
|
+
'and', 'as', 'assert', 'async', 'await',
|
|
21
|
+
'break', 'class', 'continue', 'def', 'del',
|
|
22
|
+
'elif', 'else', 'except', 'finally', 'for',
|
|
23
|
+
'from', 'global', 'if', 'import', 'in', 'is',
|
|
24
|
+
'lambda', 'nonlocal', 'not', 'or', 'pass',
|
|
25
|
+
'raise', 'return', 'try', 'while', 'with',
|
|
26
|
+
'yield',
|
|
27
|
+
'True', 'False', 'None',
|
|
28
|
+
];
|
|
29
|
+
|
|
11
30
|
// ---------------------------------------------------------------------------
|
|
12
31
|
// Context detection
|
|
13
32
|
// ---------------------------------------------------------------------------
|
|
@@ -267,7 +286,25 @@ export class CompletionEngine {
|
|
|
267
286
|
const seen = new Set();
|
|
268
287
|
|
|
269
288
|
if (scopeMap) {
|
|
270
|
-
|
|
289
|
+
let symbols = scopeMap.getSymbolsAtPosition(position.lineNumber, position.column);
|
|
290
|
+
|
|
291
|
+
// Fallback: cursor is on a new line not yet covered by the last debounced parse
|
|
292
|
+
// (e.g. the user typed Return and is now on a line past the end of the last AST).
|
|
293
|
+
// Collect all symbols from all frames, innermost-first, so locals remain visible.
|
|
294
|
+
if (symbols.length === 0 && scopeMap.frames.length > 0) {
|
|
295
|
+
const all_frames = scopeMap.frames.slice().sort((a, b) => b.depth - a.depth);
|
|
296
|
+
const fallback_seen = new Set();
|
|
297
|
+
symbols = [];
|
|
298
|
+
for (const frame of all_frames) {
|
|
299
|
+
for (const [name, sym] of frame.symbols) {
|
|
300
|
+
if (!fallback_seen.has(name)) {
|
|
301
|
+
fallback_seen.add(name);
|
|
302
|
+
symbols.push(sym);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
271
308
|
for (const sym of symbols) {
|
|
272
309
|
if (!ctx.prefix || sym.name.startsWith(ctx.prefix)) {
|
|
273
310
|
if (!seen.has(sym.name)) {
|
|
@@ -279,7 +316,7 @@ export class CompletionEngine {
|
|
|
279
316
|
}
|
|
280
317
|
}
|
|
281
318
|
|
|
282
|
-
// Builtins (
|
|
319
|
+
// Builtins (lower priority) — use rich stub item when available
|
|
283
320
|
for (const name of this._builtinNames) {
|
|
284
321
|
if (!seen.has(name) && (!ctx.prefix || name.startsWith(ctx.prefix))) {
|
|
285
322
|
seen.add(name);
|
|
@@ -291,6 +328,21 @@ export class CompletionEngine {
|
|
|
291
328
|
}
|
|
292
329
|
}
|
|
293
330
|
|
|
331
|
+
// Keywords (lowest priority)
|
|
332
|
+
const kw_kind = monacoKind.Keyword !== undefined ? monacoKind.Keyword : monacoKind.Variable;
|
|
333
|
+
for (const kw of KEYWORDS) {
|
|
334
|
+
if (!seen.has(kw) && (!ctx.prefix || kw.startsWith(ctx.prefix))) {
|
|
335
|
+
seen.add(kw);
|
|
336
|
+
items.push({
|
|
337
|
+
label: kw,
|
|
338
|
+
kind: kw_kind,
|
|
339
|
+
sortText: '3_' + kw,
|
|
340
|
+
insertText: kw,
|
|
341
|
+
range,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
294
346
|
return items;
|
|
295
347
|
}
|
|
296
348
|
|
|
@@ -16,6 +16,7 @@ const MESSAGES = {
|
|
|
16
16
|
'func-in-branch': 'Named functions/classes inside a branch are not allowed in strict mode',
|
|
17
17
|
'syntax-err': 'Syntax error: {name}',
|
|
18
18
|
'import-err': 'Import error: {name}',
|
|
19
|
+
'bad-import': 'Unknown module: "{name}"',
|
|
19
20
|
'def-after-use': 'The symbol "{name}" is defined (at line {line}) after it is used',
|
|
20
21
|
'dup-key': 'Duplicate key "{name}" in object literal',
|
|
21
22
|
'dup-method': 'The method "{name}" was defined previously at line: {line}',
|
|
@@ -27,7 +28,7 @@ export const BASE_BUILTINS = (
|
|
|
27
28
|
' eval undefined arguments abs max min enumerate pow callable reversed sum' +
|
|
28
29
|
' getattr isFinite setattr hasattr parseInt parseFloat options_object' +
|
|
29
30
|
' isNaN JSON Math list set list_wrap ρσ_modules require bool int bin' +
|
|
30
|
-
' float iter Error EvalError set_wrap RangeError ReferenceError SyntaxError' +
|
|
31
|
+
' float iter Error EvalError set_wrap frozenset RangeError ReferenceError SyntaxError' +
|
|
31
32
|
' str TypeError URIError Exception AssertionError IndexError AttributeError KeyError' +
|
|
32
33
|
' ValueError ZeroDivisionError map hex filter zip dict dict_wrap UnicodeDecodeError HTMLCollection' +
|
|
33
34
|
' NodeList alert console Node Symbol NamedNodeMap ρσ_eslice ρσ_delslice Number' +
|
|
@@ -44,7 +45,7 @@ function has_prop(obj, name) {
|
|
|
44
45
|
return Object.prototype.hasOwnProperty.call(obj, name);
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
function build_scoped_flags(flags_str) {
|
|
48
|
+
export function build_scoped_flags(flags_str) {
|
|
48
49
|
const result = Object.create(null);
|
|
49
50
|
if (!flags_str) return result;
|
|
50
51
|
flags_str.split(',').forEach(flag => {
|
|
@@ -205,7 +206,7 @@ Scope.prototype.messages = function() {
|
|
|
205
206
|
// Linter — mirrors the Linter in tools/lint.js
|
|
206
207
|
// ---------------------------------------------------------------------------
|
|
207
208
|
|
|
208
|
-
function Linter(RS, toplevel, code, builtins) {
|
|
209
|
+
function Linter(RS, toplevel, code, builtins, knownModules) {
|
|
209
210
|
this.RS = RS;
|
|
210
211
|
this.scopes = [];
|
|
211
212
|
this.walked_scopes = [];
|
|
@@ -213,6 +214,7 @@ function Linter(RS, toplevel, code, builtins) {
|
|
|
213
214
|
this.branches = [];
|
|
214
215
|
this.messages = [];
|
|
215
216
|
this.builtins = builtins;
|
|
217
|
+
this._knownModules = knownModules || null;
|
|
216
218
|
|
|
217
219
|
this.add_binding = function(name, binding_node) {
|
|
218
220
|
const scope = this.scopes[this.scopes.length - 1];
|
|
@@ -240,6 +242,26 @@ function Linter(RS, toplevel, code, builtins) {
|
|
|
240
242
|
const name = node.alias ? node.alias.name : node.key.split('.', 1)[0];
|
|
241
243
|
this.add_binding(name, node.alias || node);
|
|
242
244
|
}
|
|
245
|
+
// Flag imports of modules not present in the known-module registry.
|
|
246
|
+
// Only active when at least one virtualFile or stdlibFile is registered,
|
|
247
|
+
// to avoid false positives when no module system is configured.
|
|
248
|
+
if (this._knownModules) {
|
|
249
|
+
const root = node.key.split('.')[0];
|
|
250
|
+
if (!has_prop(this._knownModules, root)) {
|
|
251
|
+
const sline = node.start ? node.start.line : 1;
|
|
252
|
+
const scol = node.start ? node.start.col : 0;
|
|
253
|
+
this.messages.push({
|
|
254
|
+
start_line: sline,
|
|
255
|
+
start_col: scol,
|
|
256
|
+
end_line: sline,
|
|
257
|
+
end_col: scol + node.key.length - 1,
|
|
258
|
+
ident: 'bad-import',
|
|
259
|
+
message: MESSAGES['bad-import'].replace('{name}', node.key),
|
|
260
|
+
level: ERROR,
|
|
261
|
+
name: node.key,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}
|
|
243
265
|
};
|
|
244
266
|
|
|
245
267
|
this.handle_imported_var = function() {
|
|
@@ -312,6 +334,23 @@ function Linter(RS, toplevel, code, builtins) {
|
|
|
312
334
|
}
|
|
313
335
|
};
|
|
314
336
|
|
|
337
|
+
this.handle_annotated_assign = function() {
|
|
338
|
+
const node = this.current_node;
|
|
339
|
+
// Variable type annotation: `x: int = value` or `x: int`
|
|
340
|
+
// Register the target as a binding (with or without a value).
|
|
341
|
+
if (node.target instanceof RS.AST_SymbolRef) {
|
|
342
|
+
node.target.lint_visited = true;
|
|
343
|
+
this.current_node = node.target;
|
|
344
|
+
this.add_binding(node.target.name);
|
|
345
|
+
this.current_node = node;
|
|
346
|
+
}
|
|
347
|
+
// Mark annotation symbol refs as visited so they aren't treated as uses
|
|
348
|
+
// of undefined variables (e.g. `int`, `str` are type names, not runtime refs).
|
|
349
|
+
if (node.annotation instanceof RS.AST_SymbolRef) {
|
|
350
|
+
node.annotation.lint_visited = true;
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
|
|
315
354
|
this.handle_vardef = function() {
|
|
316
355
|
const node = this.current_node;
|
|
317
356
|
if (node.name instanceof RS.AST_SymbolNonlocal) {
|
|
@@ -461,6 +500,7 @@ function Linter(RS, toplevel, code, builtins) {
|
|
|
461
500
|
else if (node instanceof RS.AST_Assign) this.handle_assign();
|
|
462
501
|
else if (node instanceof RS.AST_NamedExpr) this.handle_named_expr();
|
|
463
502
|
else if (node instanceof RS.AST_VarDef) this.handle_vardef();
|
|
503
|
+
else if (RS.AST_AnnotatedAssign && node instanceof RS.AST_AnnotatedAssign) this.handle_annotated_assign();
|
|
464
504
|
else if (node instanceof RS.AST_SymbolRef) this.handle_symbol_ref();
|
|
465
505
|
else if (node instanceof RS.AST_Decorator) this.handle_decorator();
|
|
466
506
|
else if (node instanceof RS.AST_SymbolFunarg) this.handle_symbol_funarg();
|
|
@@ -634,7 +674,17 @@ export class Diagnostics {
|
|
|
634
674
|
}
|
|
635
675
|
|
|
636
676
|
// --- Lint ---
|
|
637
|
-
|
|
677
|
+
// Build known-module set for import validation.
|
|
678
|
+
// Only activates when the caller has registered at least one module.
|
|
679
|
+
let knownModules = null;
|
|
680
|
+
const vf = options.virtualFiles;
|
|
681
|
+
const sf = options.stdlibFiles;
|
|
682
|
+
if ((vf && Object.keys(vf).length > 0) || (sf && Object.keys(sf).length > 0)) {
|
|
683
|
+
knownModules = Object.create(null);
|
|
684
|
+
if (vf) Object.keys(vf).forEach(k => { knownModules[k] = true; });
|
|
685
|
+
if (sf) Object.keys(sf).forEach(k => { knownModules[k] = true; });
|
|
686
|
+
}
|
|
687
|
+
const linter = new Linter(RS, toplevel, code, this._builtins, knownModules);
|
|
638
688
|
toplevel.walk(linter);
|
|
639
689
|
messages = linter.resolve(noqa);
|
|
640
690
|
|
|
@@ -53,7 +53,7 @@ class ModelState {
|
|
|
53
53
|
_run() {
|
|
54
54
|
const service = this._service;
|
|
55
55
|
const code = this._model.getValue();
|
|
56
|
-
const opts = { virtualFiles: service._virtualFiles };
|
|
56
|
+
const opts = { virtualFiles: service._virtualFiles, stdlibFiles: service._stdlibFiles };
|
|
57
57
|
|
|
58
58
|
// Diagnostics (syntax errors + lint markers)
|
|
59
59
|
const markers = service._diagnostics.check(code, {
|
|
@@ -108,9 +108,12 @@ class RapydScriptLanguageService {
|
|
|
108
108
|
this._builtins = new BuiltinsRegistry();
|
|
109
109
|
this._builtins.enablePythonizeStrings();
|
|
110
110
|
|
|
111
|
-
// Merge BASE_BUILTINS +
|
|
111
|
+
// Merge BASE_BUILTINS + DTS globals for completions.
|
|
112
|
+
// NOTE: _extraBuiltinNames (NS API keys like grow, growthAnalyze, etc.) are
|
|
113
|
+
// intentionally excluded here — they are handled by the DTS registry as dot-
|
|
114
|
+
// completions on ns.* and should NOT appear as free-floating identifier
|
|
115
|
+
// suggestions. They are still used for tokenization and diagnostics suppression.
|
|
112
116
|
const builtin_names = BASE_BUILTINS
|
|
113
|
-
.concat(this._extraBuiltinNames)
|
|
114
117
|
.concat(this._dts.getGlobalNames());
|
|
115
118
|
|
|
116
119
|
this._completions = new CompletionEngine(this._analyzer, {
|
|
@@ -318,8 +321,9 @@ class RapydScriptLanguageService {
|
|
|
318
321
|
const new_names = this._dts.getGlobalNames();
|
|
319
322
|
// Suppress undef warnings for newly added globals
|
|
320
323
|
this._diagnostics.addGlobals(new_names);
|
|
321
|
-
// Rebuild builtin list so completions include new DTS names
|
|
322
|
-
|
|
324
|
+
// Rebuild builtin list so completions include new DTS names.
|
|
325
|
+
// _extraBuiltinNames are excluded — they are dot-completions only (see constructor).
|
|
326
|
+
const builtin_names = BASE_BUILTINS.concat(new_names);
|
|
323
327
|
this._completions = new CompletionEngine(this._analyzer, {
|
|
324
328
|
virtualFiles: this._virtualFiles,
|
|
325
329
|
stdlibFiles: this._stdlibFiles,
|
package/src/output/codegen.pyj
CHANGED
|
@@ -279,7 +279,28 @@ def generate_code():
|
|
|
279
279
|
self._do_print(output, "return")
|
|
280
280
|
)
|
|
281
281
|
DEFPRINT(AST_Throw, def(self, output):
|
|
282
|
-
self.
|
|
282
|
+
if self.cause:
|
|
283
|
+
# raise X from Y: set __cause__ on exception before throwing
|
|
284
|
+
output.print('var ρσ_throw_tmp')
|
|
285
|
+
output.space()
|
|
286
|
+
output.print('=')
|
|
287
|
+
output.space()
|
|
288
|
+
self.value.print(output)
|
|
289
|
+
output.semicolon()
|
|
290
|
+
output.newline()
|
|
291
|
+
output.indent()
|
|
292
|
+
output.print('ρσ_throw_tmp.__cause__')
|
|
293
|
+
output.space()
|
|
294
|
+
output.print('=')
|
|
295
|
+
output.space()
|
|
296
|
+
self.cause.print(output)
|
|
297
|
+
output.semicolon()
|
|
298
|
+
output.newline()
|
|
299
|
+
output.indent()
|
|
300
|
+
output.print('throw ρσ_throw_tmp')
|
|
301
|
+
output.semicolon()
|
|
302
|
+
else:
|
|
303
|
+
self._do_print(output, "throw")
|
|
283
304
|
)
|
|
284
305
|
|
|
285
306
|
# -----[ loop control ]-----
|
|
@@ -292,6 +313,17 @@ def generate_code():
|
|
|
292
313
|
output.semicolon()
|
|
293
314
|
|
|
294
315
|
DEFPRINT(AST_Break, def(self, output):
|
|
316
|
+
# If we are directly inside a for/else labeled block and this break has
|
|
317
|
+
# no explicit label, redirect it to the forelse label so the else clause
|
|
318
|
+
# is skipped.
|
|
319
|
+
if not self.label and output.forelse_stack and output.forelse_stack.length:
|
|
320
|
+
top = output.forelse_stack[output.forelse_stack.length - 1]
|
|
321
|
+
if top:
|
|
322
|
+
output.print('break')
|
|
323
|
+
output.space()
|
|
324
|
+
output.print(top)
|
|
325
|
+
output.semicolon()
|
|
326
|
+
return
|
|
295
327
|
self._do_print(output, "break")
|
|
296
328
|
)
|
|
297
329
|
DEFPRINT(AST_Continue, def(self, output):
|
|
@@ -340,7 +372,10 @@ def generate_code():
|
|
|
340
372
|
DEFPRINT(AST_If, def(self, output):
|
|
341
373
|
output.print("if")
|
|
342
374
|
output.space()
|
|
343
|
-
|
|
375
|
+
if self.python_truthiness:
|
|
376
|
+
output.with_parens(def(): output.print('ρσ_bool('), self.condition.print(output), output.print(')');)
|
|
377
|
+
else:
|
|
378
|
+
output.with_parens(def(): self.condition.print(output);)
|
|
344
379
|
output.space()
|
|
345
380
|
if self.alternative:
|
|
346
381
|
make_then(self, output)
|
package/src/output/functions.pyj
CHANGED
|
@@ -45,7 +45,7 @@ def function_args(argnames, output, strip_first):
|
|
|
45
45
|
|
|
46
46
|
def function_preamble(node, output, offset):
|
|
47
47
|
a = node.argnames
|
|
48
|
-
if
|
|
48
|
+
if a is None or a is undefined or a.is_simple_func:
|
|
49
49
|
return
|
|
50
50
|
# If this function has optional parameters/*args/**kwargs declare it differently
|
|
51
51
|
fname = node.name.name if node.name else anonfunc
|
|
@@ -433,20 +433,42 @@ def print_function_call(self, output):
|
|
|
433
433
|
return # new A is the same as new A() in javascript
|
|
434
434
|
|
|
435
435
|
if not has_kwargs and not self.args.starargs:
|
|
436
|
-
# A simple function call
|
|
436
|
+
# A simple function call
|
|
437
437
|
if is_new:
|
|
438
438
|
output.print('new'), output.space()
|
|
439
|
-
|
|
440
|
-
|
|
439
|
+
print_function_name()
|
|
440
|
+
output.with_parens(def():
|
|
441
|
+
for i, a in enumerate(self.args):
|
|
442
|
+
if i:
|
|
443
|
+
output.comma()
|
|
444
|
+
a.print(output)
|
|
445
|
+
)
|
|
446
|
+
elif is_node_type(self.expression, AST_SymbolRef) and self.expression.name is 'print':
|
|
447
|
+
# Bare print(...) → console.log(...) to avoid clobbering window.print
|
|
441
448
|
output.print('console.log')
|
|
442
|
-
|
|
449
|
+
output.with_parens(def():
|
|
450
|
+
for i, a in enumerate(self.args):
|
|
451
|
+
if i:
|
|
452
|
+
output.comma()
|
|
453
|
+
a.print(output)
|
|
454
|
+
)
|
|
455
|
+
elif is_node_type(self.expression, AST_SymbolRef) and self.python_truthiness:
|
|
456
|
+
# __call__-aware dispatch when from __python__ import truthiness is active
|
|
457
|
+
output.print('ρσ_callable_call(')
|
|
443
458
|
print_function_name()
|
|
444
|
-
output.with_parens(def():
|
|
445
459
|
for i, a in enumerate(self.args):
|
|
446
|
-
|
|
447
|
-
output.comma()
|
|
460
|
+
output.comma()
|
|
448
461
|
a.print(output)
|
|
449
|
-
|
|
462
|
+
output.print(')')
|
|
463
|
+
else:
|
|
464
|
+
# Method calls and other complex expressions — keep existing behaviour
|
|
465
|
+
print_function_name()
|
|
466
|
+
output.with_parens(def():
|
|
467
|
+
for i, a in enumerate(self.args):
|
|
468
|
+
if i:
|
|
469
|
+
output.comma()
|
|
470
|
+
a.print(output)
|
|
471
|
+
)
|
|
450
472
|
return
|
|
451
473
|
|
|
452
474
|
is_repeatable = is_new or not has_calls(self.expression)
|