rapydscript-ns 0.8.2 → 0.8.3
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/CHANGELOG.md +31 -0
- package/PYTHON_DIFFERENCES_REPORT.md +291 -0
- package/PYTHON_FEATURE_COVERAGE.md +96 -5
- package/README.md +161 -46
- package/TODO.md +2 -283
- package/language-service/index.js +4474 -0
- package/language-service/language-service.d.ts +40 -0
- package/package.json +9 -7
- package/src/baselib-builtins.pyj +77 -1
- package/src/baselib-containers.pyj +8 -4
- package/src/baselib-internal.pyj +30 -1
- package/src/baselib-str.pyj +8 -1
- package/src/lib/collections.pyj +1 -1
- package/src/lib/numpy.pyj +10 -10
- package/src/monaco-language-service/analyzer.js +131 -9
- package/src/monaco-language-service/builtins.js +12 -2
- package/src/monaco-language-service/completions.js +170 -1
- package/src/monaco-language-service/diagnostics.js +1 -1
- package/src/monaco-language-service/index.js +17 -0
- package/src/monaco-language-service/scope.js +3 -0
- package/src/output/classes.pyj +20 -3
- package/src/output/codegen.pyj +1 -1
- package/src/output/functions.pyj +4 -16
- package/src/output/loops.pyj +0 -9
- package/src/output/modules.pyj +1 -4
- package/src/output/operators.pyj +14 -0
- package/src/output/stream.pyj +0 -13
- package/src/parse.pyj +17 -1
- package/test/baselib.pyj +4 -4
- package/test/classes.pyj +56 -17
- package/test/collections.pyj +5 -5
- package/test/python_compat.pyj +326 -0
- package/test/python_features.pyj +110 -23
- package/test/slice.pyj +105 -0
- package/test/str.pyj +25 -0
- package/test/unit/fixtures/fibonacci_expected.js +1 -1
- package/test/unit/index.js +119 -7
- package/test/unit/language-service-builtins.js +70 -0
- package/test/unit/language-service-bundle.js +5 -5
- package/test/unit/language-service-completions.js +180 -0
- package/test/unit/language-service-index.js +350 -0
- package/test/unit/language-service-scope.js +255 -0
- package/test/unit/language-service.js +35 -0
- package/test/unit/run-language-service.js +1 -0
- package/test/unit/web-repl.js +134 -0
- package/tools/build-language-service.js +2 -2
- package/tools/compiler.js +0 -24
- package/tools/export.js +3 -37
- package/web-repl/rapydscript.js +6 -40
- package/web-repl/language-service.js +0 -4187
|
@@ -45,6 +45,13 @@ export const KEYWORDS = [
|
|
|
45
45
|
* @returns {{ type: string, objectName?: string, moduleName?: string, prefix: string }}
|
|
46
46
|
*/
|
|
47
47
|
export function detect_context(linePrefix) {
|
|
48
|
+
// `obj.method().attr` — dot access on a call result (single-level call expression).
|
|
49
|
+
// Must be checked before the plain dot match since `[\w.]+` cannot span `()`.
|
|
50
|
+
const call_result_match = linePrefix.match(/([\w.]+)\([^)]*\)\.([\w]*)$/);
|
|
51
|
+
if (call_result_match) {
|
|
52
|
+
return { type: 'call_result', callPath: call_result_match[1], prefix: call_result_match[2] };
|
|
53
|
+
}
|
|
54
|
+
|
|
48
55
|
// `obj.attr` or `obj.sub.attr` — dot access (captures multi-level path)
|
|
49
56
|
const dot_match = linePrefix.match(/([\w.]+)\.([\w]*)$/);
|
|
50
57
|
if (dot_match) {
|
|
@@ -265,6 +272,9 @@ export class CompletionEngine {
|
|
|
265
272
|
getCompletions(scopeMap, position, linePrefix, monacoKind) {
|
|
266
273
|
const ctx = detect_context(linePrefix);
|
|
267
274
|
|
|
275
|
+
if (ctx.type === 'call_result') {
|
|
276
|
+
return { suggestions: this._call_result_completions(position, ctx, monacoKind, scopeMap) };
|
|
277
|
+
}
|
|
268
278
|
if (ctx.type === 'dot') {
|
|
269
279
|
return { suggestions: this._dot_completions(scopeMap, position, ctx, monacoKind) };
|
|
270
280
|
}
|
|
@@ -346,6 +356,82 @@ export class CompletionEngine {
|
|
|
346
356
|
return items;
|
|
347
357
|
}
|
|
348
358
|
|
|
359
|
+
// ---- Return-type helpers -----------------------------------------------
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Analyze a module and return the `return_type` of a named function.
|
|
363
|
+
* @param {string} func_name
|
|
364
|
+
* @param {string} module_name
|
|
365
|
+
* @returns {string|null}
|
|
366
|
+
*/
|
|
367
|
+
_get_return_type_from_module(func_name, module_name) {
|
|
368
|
+
const src = this._virtualFiles[module_name] || this._stdlibFiles[module_name];
|
|
369
|
+
if (!src) return null;
|
|
370
|
+
let mod_map;
|
|
371
|
+
try {
|
|
372
|
+
mod_map = this._analyzer.analyze(src, {});
|
|
373
|
+
} catch (_e) {
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
376
|
+
const mod_frame = mod_map.frames.find(f => f.kind === 'module');
|
|
377
|
+
if (!mod_frame) return null;
|
|
378
|
+
const fn_sym = mod_frame.getSymbol(func_name);
|
|
379
|
+
if (fn_sym && fn_sym.return_type) return fn_sym.return_type;
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Resolve a type name (e.g. from inferred_class) through function return annotations,
|
|
385
|
+
* including cross-file imports. Returns the resolved type name, or null if unresolvable.
|
|
386
|
+
* @param {string} name
|
|
387
|
+
* @param {import('./scope.js').ScopeMap} scopeMap
|
|
388
|
+
* @returns {string|null}
|
|
389
|
+
*/
|
|
390
|
+
_resolve_type_through_functions(name, scopeMap) {
|
|
391
|
+
let sym = null;
|
|
392
|
+
for (const frame of scopeMap.frames) {
|
|
393
|
+
const s = frame.getSymbol(name);
|
|
394
|
+
if (s) { sym = s; break; }
|
|
395
|
+
}
|
|
396
|
+
if (!sym) return null;
|
|
397
|
+
|
|
398
|
+
if (sym.kind === 'function' || sym.kind === 'method') {
|
|
399
|
+
// return_type is set by explicit annotation or inferred by the analyzer
|
|
400
|
+
if (sym.return_type) return sym.return_type;
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (sym.kind === 'import' && sym.source_module) {
|
|
405
|
+
const orig = sym.original_name || sym.name;
|
|
406
|
+
return this._get_return_type_from_module(orig, sym.source_module);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return null;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Get the member map for a given type name.
|
|
414
|
+
* Checks builtins, then user class frames. Returns null if unknown.
|
|
415
|
+
* @param {string} type_name
|
|
416
|
+
* @param {import('./scope.js').ScopeMap|null} scopeMap
|
|
417
|
+
* @returns {Map|null}
|
|
418
|
+
*/
|
|
419
|
+
_get_members_for_type(type_name, scopeMap) {
|
|
420
|
+
if (!type_name) return null;
|
|
421
|
+
if (this._builtins) {
|
|
422
|
+
const m = this._builtins.getTypeMembers(type_name);
|
|
423
|
+
if (m) return m;
|
|
424
|
+
}
|
|
425
|
+
if (scopeMap) {
|
|
426
|
+
for (const frame of scopeMap.frames) {
|
|
427
|
+
if (frame.kind === 'class' && frame.name === type_name) {
|
|
428
|
+
return frame.symbols;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
|
|
349
435
|
// ---- Dot completions ---------------------------------------------------
|
|
350
436
|
|
|
351
437
|
_dot_completions(scopeMap, position, ctx, monacoKind) {
|
|
@@ -413,6 +499,13 @@ export class CompletionEngine {
|
|
|
413
499
|
}
|
|
414
500
|
}
|
|
415
501
|
|
|
502
|
+
// Resolve class_name through function return type annotations.
|
|
503
|
+
// e.g. x = getAllServers() → inferred_class='getAllServers' → resolve 'list'
|
|
504
|
+
if (class_name) {
|
|
505
|
+
const resolved = this._resolve_type_through_functions(class_name, scopeMap);
|
|
506
|
+
if (resolved) class_name = resolved;
|
|
507
|
+
}
|
|
508
|
+
|
|
416
509
|
if (class_name) {
|
|
417
510
|
for (const frame of scopeMap.frames) {
|
|
418
511
|
if (frame.kind === 'class' && frame.name === class_name) {
|
|
@@ -433,7 +526,13 @@ export class CompletionEngine {
|
|
|
433
526
|
// 1.5. Built-in type members — list, str, dict, number.
|
|
434
527
|
// Used when inferred_class names a built-in type, not a user class.
|
|
435
528
|
if (!scope_matched && this._builtins && obj_sym && obj_sym.inferred_class) {
|
|
436
|
-
|
|
529
|
+
// Re-resolve in case inferred_class itself is a function name.
|
|
530
|
+
let type_name = obj_sym.inferred_class;
|
|
531
|
+
if (scopeMap) {
|
|
532
|
+
const resolved = this._resolve_type_through_functions(type_name, scopeMap);
|
|
533
|
+
if (resolved) type_name = resolved;
|
|
534
|
+
}
|
|
535
|
+
const members = this._builtins.getTypeMembers(type_name);
|
|
437
536
|
if (members) {
|
|
438
537
|
scope_matched = true;
|
|
439
538
|
for (const [name, member] of members) {
|
|
@@ -496,6 +595,76 @@ export class CompletionEngine {
|
|
|
496
595
|
return items;
|
|
497
596
|
}
|
|
498
597
|
|
|
598
|
+
// ---- Call-result dot completions (e.g. ns.self().attr) -----------------
|
|
599
|
+
|
|
600
|
+
_call_result_completions(position, ctx, monacoKind, scopeMap) {
|
|
601
|
+
const range = word_range(position, ctx.prefix);
|
|
602
|
+
const items = [];
|
|
603
|
+
const seen = new Set();
|
|
604
|
+
const call_path = ctx.callPath;
|
|
605
|
+
const last_dot = call_path.lastIndexOf('.');
|
|
606
|
+
|
|
607
|
+
// --- DTS path (existing logic) ---
|
|
608
|
+
if (this._dts) {
|
|
609
|
+
let member_ti = null;
|
|
610
|
+
if (last_dot > 0) {
|
|
611
|
+
const object_path = call_path.slice(0, last_dot);
|
|
612
|
+
const method_name = call_path.slice(last_dot + 1);
|
|
613
|
+
member_ti = this._dts.getMemberInfo(object_path, method_name);
|
|
614
|
+
} else {
|
|
615
|
+
member_ti = this._dts.getGlobal(call_path);
|
|
616
|
+
}
|
|
617
|
+
if (member_ti && member_ti.return_type) {
|
|
618
|
+
const return_ti = this._dts.getGlobal(resolve_first_type(member_ti.return_type));
|
|
619
|
+
if (return_ti && return_ti.members) {
|
|
620
|
+
for (const [name, member] of return_ti.members) {
|
|
621
|
+
if (!ctx.prefix || name.startsWith(ctx.prefix)) {
|
|
622
|
+
seen.add(name);
|
|
623
|
+
items.push(_dts_member_to_item(member, range, monacoKind));
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// --- User-defined function return types (scopeMap) ---
|
|
631
|
+
if (scopeMap && last_dot < 0) {
|
|
632
|
+
// Simple call like getAllServers() — look up function in scope
|
|
633
|
+
let sym = null;
|
|
634
|
+
for (const frame of scopeMap.frames) {
|
|
635
|
+
const s = frame.getSymbol(call_path);
|
|
636
|
+
if (s) { sym = s; break; }
|
|
637
|
+
}
|
|
638
|
+
if (sym) {
|
|
639
|
+
let return_type = null;
|
|
640
|
+
if (sym.kind === 'function' || sym.kind === 'method') {
|
|
641
|
+
return_type = sym.return_type;
|
|
642
|
+
} else if (sym.kind === 'import' && sym.source_module) {
|
|
643
|
+
const orig = sym.original_name || sym.name;
|
|
644
|
+
return_type = this._get_return_type_from_module(orig, sym.source_module);
|
|
645
|
+
}
|
|
646
|
+
if (return_type) {
|
|
647
|
+
const members = this._get_members_for_type(return_type, scopeMap);
|
|
648
|
+
if (members) {
|
|
649
|
+
for (const [name, member] of members) {
|
|
650
|
+
if (!seen.has(name) && (!ctx.prefix || name.startsWith(ctx.prefix))) {
|
|
651
|
+
seen.add(name);
|
|
652
|
+
// members may be SymbolInfo (class frame) or TypeInfo/BuiltinInfo
|
|
653
|
+
const item = member.kind === 'function' || member.kind === 'method' ||
|
|
654
|
+
member.kind === 'variable' || member.kind === 'parameter'
|
|
655
|
+
? symbol_to_item(member, range, monacoKind, '0')
|
|
656
|
+
: _dts_member_to_item(member, range, monacoKind);
|
|
657
|
+
items.push(item);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
return items;
|
|
666
|
+
}
|
|
667
|
+
|
|
499
668
|
// ---- from X import completions -----------------------------------------
|
|
500
669
|
|
|
501
670
|
_from_import_completions(position, ctx, monacoKind) {
|
|
@@ -34,7 +34,7 @@ export const BASE_BUILTINS = (
|
|
|
34
34
|
' NodeList alert console Node Symbol NamedNodeMap ρσ_eslice ρσ_delslice Number' +
|
|
35
35
|
' Boolean encodeURIComponent decodeURIComponent setTimeout setInterval' +
|
|
36
36
|
' setImmediate clearTimeout clearInterval clearImmediate requestAnimationFrame' +
|
|
37
|
-
' id repr sorted __name__ equals get_module ρσ_str jstype divmod NaN super Ellipsis'
|
|
37
|
+
' id repr sorted __name__ equals get_module ρσ_str jstype divmod NaN super Ellipsis slice'
|
|
38
38
|
).split(' ');
|
|
39
39
|
|
|
40
40
|
// ---------------------------------------------------------------------------
|
|
@@ -36,6 +36,13 @@ class ModelState {
|
|
|
36
36
|
this._timer = null;
|
|
37
37
|
this._scopeMap = null; // most recent ScopeMap for this model
|
|
38
38
|
|
|
39
|
+
// Derive the module name from the model URI so this model's content can
|
|
40
|
+
// be registered in _virtualFiles for cross-file import resolution.
|
|
41
|
+
// e.g. URI path "/scripts/utils.py" → module name "utils"
|
|
42
|
+
const uri_path = (model.uri && (model.uri.path || model.uri.fsPath)) || '';
|
|
43
|
+
const basename = uri_path.split('/').pop() || '';
|
|
44
|
+
this._module_name = basename.replace(/\.pyj?x?$/, '') || null;
|
|
45
|
+
|
|
39
46
|
// Run immediately on first attach
|
|
40
47
|
this._schedule(0);
|
|
41
48
|
|
|
@@ -55,6 +62,12 @@ class ModelState {
|
|
|
55
62
|
const code = this._model.getValue();
|
|
56
63
|
const opts = { virtualFiles: service._virtualFiles, stdlibFiles: service._stdlibFiles };
|
|
57
64
|
|
|
65
|
+
// Register this model's content in the shared virtualFiles pool so other
|
|
66
|
+
// open models can resolve cross-file imports from it.
|
|
67
|
+
if (this._module_name) {
|
|
68
|
+
service._virtualFiles[this._module_name] = code;
|
|
69
|
+
}
|
|
70
|
+
|
|
58
71
|
// Diagnostics (syntax errors + lint markers)
|
|
59
72
|
const markers = service._diagnostics.check(code, {
|
|
60
73
|
...opts,
|
|
@@ -69,6 +82,10 @@ class ModelState {
|
|
|
69
82
|
dispose() {
|
|
70
83
|
clearTimeout(this._timer);
|
|
71
84
|
this._subscription.dispose();
|
|
85
|
+
// Remove this model's content from the virtualFiles pool on detach.
|
|
86
|
+
if (this._module_name) {
|
|
87
|
+
delete this._service._virtualFiles[this._module_name];
|
|
88
|
+
}
|
|
72
89
|
// Clear markers when detaching
|
|
73
90
|
this._service._monaco.editor.setModelMarkers(this._model, LANGUAGE_ID, []);
|
|
74
91
|
}
|
|
@@ -30,6 +30,9 @@ export class SymbolInfo {
|
|
|
30
30
|
this.inferred_class = opts.inferred_class || null; // 'ClassName' when x = ClassName(...)
|
|
31
31
|
this.dts_call_path = opts.dts_call_path || null; // 'ns.getServer' when x = ns.getServer(...)
|
|
32
32
|
this.type = null; // Phase 6: TypeInfo
|
|
33
|
+
this.return_type = opts.return_type || null; // annotated return type, e.g. 'list', 'str', 'MyClass'
|
|
34
|
+
this.source_module = opts.source_module || null; // module name when kind='import' (from X import Y)
|
|
35
|
+
this.original_name = opts.original_name || null; // pre-alias name for imports (from X import Y as Z → 'Y')
|
|
33
36
|
}
|
|
34
37
|
}
|
|
35
38
|
|
package/src/output/classes.pyj
CHANGED
|
@@ -214,7 +214,7 @@ def print_class(output):
|
|
|
214
214
|
output.end_statement()
|
|
215
215
|
|
|
216
216
|
elif is_node_type(stmt, AST_Class):
|
|
217
|
-
|
|
217
|
+
pass # nested classes are emitted in the statements section below
|
|
218
218
|
|
|
219
219
|
if not defined_methods['__repr__']:
|
|
220
220
|
define_default_method('__repr__', def():
|
|
@@ -271,9 +271,26 @@ def print_class(output):
|
|
|
271
271
|
output.print(JSON.stringify(create_doctring(self.docstrings)))
|
|
272
272
|
)
|
|
273
273
|
|
|
274
|
-
# Other statements in the class context
|
|
274
|
+
# Other statements in the class context (including nested class definitions)
|
|
275
275
|
for stmt in self.statements:
|
|
276
|
-
if
|
|
276
|
+
if is_node_type(stmt, AST_Class):
|
|
277
|
+
# Print the nested class in full, then attach it to the outer class.
|
|
278
|
+
# Two assignments mirror Python semantics:
|
|
279
|
+
# Outer.Inner — class-level access (Outer.Inner())
|
|
280
|
+
# Outer.prototype.Inner — instance-level access (self.Inner() inside methods)
|
|
281
|
+
output.indent()
|
|
282
|
+
stmt.print(output)
|
|
283
|
+
output.newline()
|
|
284
|
+
nested_name = stmt.name.name
|
|
285
|
+
output.indent()
|
|
286
|
+
self.name.print(output)
|
|
287
|
+
output.print('.' + nested_name + ' = ' + nested_name)
|
|
288
|
+
output.end_statement()
|
|
289
|
+
output.indent()
|
|
290
|
+
self.name.print(output)
|
|
291
|
+
output.print('.prototype.' + nested_name + ' = ' + nested_name)
|
|
292
|
+
output.end_statement()
|
|
293
|
+
elif not is_node_type(stmt, AST_Method):
|
|
277
294
|
output.indent()
|
|
278
295
|
stmt.print(output)
|
|
279
296
|
output.newline()
|
package/src/output/codegen.pyj
CHANGED
package/src/output/functions.pyj
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# vim:fileencoding=utf-8
|
|
2
2
|
# License: BSD Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
|
3
|
-
# globals:regenerate
|
|
4
3
|
from __python__ import hash_literals
|
|
5
4
|
|
|
6
5
|
from ast import AST_ClassCall, AST_New, has_calls, AST_Dot, AST_SymbolRef, is_node_type
|
|
@@ -263,21 +262,10 @@ def function_definition(self, output, strip_first, as_expression):
|
|
|
263
262
|
if self.is_generator:
|
|
264
263
|
output.print('()'), output.space()
|
|
265
264
|
output.with_block(def():
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
print_bracketed(self, output, True, function_preamble)
|
|
271
|
-
else:
|
|
272
|
-
temp = OutputStream({'beautify':True})
|
|
273
|
-
temp.print('function* js_generator')
|
|
274
|
-
function_args(self.argnames, temp, strip_first)
|
|
275
|
-
print_bracketed(self, temp, True, function_preamble)
|
|
276
|
-
transpiled = regenerate(temp.get(), output.options.beautify).replace(/regeneratorRuntime.(wrap|mark)/g, 'ρσ_regenerator.regeneratorRuntime.$1')
|
|
277
|
-
if output.options.beautify:
|
|
278
|
-
ci = output.make_indent(0)
|
|
279
|
-
transpiled = [ci + x for x in transpiled.split('\n')].join('\n')
|
|
280
|
-
output.print(transpiled)
|
|
265
|
+
output.indent()
|
|
266
|
+
output.print('function* js_generator')
|
|
267
|
+
function_args(self.argnames, output, strip_first)
|
|
268
|
+
print_bracketed(self, output, True, function_preamble)
|
|
281
269
|
output.newline()
|
|
282
270
|
output.indent()
|
|
283
271
|
output.spaced('var', 'result', '=', 'js_generator.apply(this,', 'arguments)')
|
package/src/output/loops.pyj
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# vim:fileencoding=utf-8
|
|
2
2
|
# License: BSD Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
|
3
|
-
# globals: regenerate
|
|
4
3
|
from __python__ import hash_literals
|
|
5
4
|
|
|
6
5
|
from ast import AST_BaseCall, AST_SymbolRef, AST_Array, AST_Unary, AST_Number, has_calls, AST_Seq, AST_ListComprehension, AST_Starred, is_node_type
|
|
@@ -436,8 +435,6 @@ def print_list_comprehension(self, output):
|
|
|
436
435
|
output.with_block(def():
|
|
437
436
|
body_out = output
|
|
438
437
|
if is_generator:
|
|
439
|
-
if es5:
|
|
440
|
-
body_out = OutputStream({'beautify':True})
|
|
441
438
|
body_out.indent()
|
|
442
439
|
body_out.print('function* js_generator()'), body_out.space(), body_out.print('{')
|
|
443
440
|
body_out.newline()
|
|
@@ -485,12 +482,6 @@ def print_list_comprehension(self, output):
|
|
|
485
482
|
if is_generator:
|
|
486
483
|
output.set_indentation(previous_indentation)
|
|
487
484
|
body_out.newline(), body_out.indent(), body_out.print('}') # end js_generator
|
|
488
|
-
if es5:
|
|
489
|
-
transpiled = regenerate(body_out.get(), output.options.beautify).replace(/regeneratorRuntime.(wrap|mark)/g, 'ρσ_regenerator.regeneratorRuntime.$1')
|
|
490
|
-
if output.options.beautify:
|
|
491
|
-
ci = output.make_indent(0)
|
|
492
|
-
transpiled = [ci + x for x in transpiled.split('\n')].join('\n')
|
|
493
|
-
output.print(transpiled)
|
|
494
485
|
output.newline(), output.indent()
|
|
495
486
|
output.spaced('var', 'result', '=', 'js_generator.call(this)')
|
|
496
487
|
output.end_statement()
|
package/src/output/modules.pyj
CHANGED
|
@@ -18,7 +18,7 @@ def write_imports(module, output):
|
|
|
18
18
|
imports = []
|
|
19
19
|
for import_id in Object.keys(module.imports):
|
|
20
20
|
imports.push(module.imports[import_id])
|
|
21
|
-
imports.
|
|
21
|
+
imports.jssort(def(a, b):
|
|
22
22
|
a, b = a.import_order, b.import_order
|
|
23
23
|
return -1 if a < b else (1 if a > b else 0)
|
|
24
24
|
)
|
|
@@ -122,9 +122,6 @@ def prologue(module, output):
|
|
|
122
122
|
output.indent(), output.spaced('if(', 'typeof', 'HTMLCollection', '!==', '"undefined"', '&&', 'typeof', 'Symbol', '===', '"function")',
|
|
123
123
|
'NodeList.prototype[Symbol.iterator]', '=', 'HTMLCollection.prototype[Symbol.iterator]', '=', 'NamedNodeMap.prototype[Symbol.iterator]', '=', 'Array.prototype[Symbol.iterator]')
|
|
124
124
|
output.end_statement()
|
|
125
|
-
needs_yield = output.options.js_version < 6 and module.baselib['yield']
|
|
126
|
-
if needs_yield:
|
|
127
|
-
output.dump_yield()
|
|
128
125
|
# output the baselib
|
|
129
126
|
if not output.options.baselib_plain:
|
|
130
127
|
raise ValueError('The baselib is missing! Remember to set the baselib_plain field on the options for OutputStream')
|
package/src/output/operators.pyj
CHANGED
|
@@ -280,6 +280,12 @@ def print_binary_op(self, output):
|
|
|
280
280
|
write_smart_equality(self, output)
|
|
281
281
|
elif self.operator is 'instanceof':
|
|
282
282
|
write_instanceof(self.left, self.right, output)
|
|
283
|
+
elif self.operator is '+':
|
|
284
|
+
output.print('ρσ_list_add(')
|
|
285
|
+
self.left.print(output)
|
|
286
|
+
output.comma()
|
|
287
|
+
self.right.print(output)
|
|
288
|
+
output.print(')')
|
|
283
289
|
elif self.operator is '*' and is_node_type(self.left, AST_String):
|
|
284
290
|
self.left.print(output), output.print('.repeat('), self.right.print(output), output.print(')')
|
|
285
291
|
elif self.operator is '===' or self.operator is '!==':
|
|
@@ -415,6 +421,14 @@ def print_assign(self, output):
|
|
|
415
421
|
else:
|
|
416
422
|
output.print('Math.pow('), self.left.print(output), output.comma(), self.right.print(output), output.print(')')
|
|
417
423
|
return
|
|
424
|
+
if self.operator is '+=':
|
|
425
|
+
output.assign(self.left)
|
|
426
|
+
output.print('ρσ_list_iadd(')
|
|
427
|
+
self.left.print(output)
|
|
428
|
+
output.comma()
|
|
429
|
+
self.right.print(output)
|
|
430
|
+
output.print(')')
|
|
431
|
+
return
|
|
418
432
|
if self.operator is '=' and self.is_chained():
|
|
419
433
|
left_hand_sides, rhs = self.traverse_chain()
|
|
420
434
|
is_compound_assign = False
|
package/src/output/stream.pyj
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# vim:fileencoding=utf-8
|
|
2
2
|
# License: BSD Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
|
3
|
-
# globals:regenerate
|
|
4
3
|
from __python__ import hash_literals
|
|
5
4
|
|
|
6
5
|
from utils import make_predicate, defaults, repeat_string
|
|
@@ -255,18 +254,6 @@ class OutputStream:
|
|
|
255
254
|
if self.options.space_colon:
|
|
256
255
|
self.space()
|
|
257
256
|
|
|
258
|
-
def dump_yield(self):
|
|
259
|
-
self.indent()
|
|
260
|
-
self.spaced('var', 'ρσ_regenerator', '=', '{}')
|
|
261
|
-
self.end_statement()
|
|
262
|
-
code = 'ρσ_regenerator.regeneratorRuntime = ' + regenerate(False, self.options.beautify)
|
|
263
|
-
if self.options.beautify:
|
|
264
|
-
code = code.replace(/\/\/.*$/mg, '\n').replace(/^\s*$/gm, '') # strip comments
|
|
265
|
-
ci = self.make_indent(0)
|
|
266
|
-
code = [ci + x for x in code.split('\n')].join('\n')
|
|
267
|
-
self.print(code + '})(ρσ_regenerator)')
|
|
268
|
-
self.end_statement()
|
|
269
|
-
|
|
270
257
|
def get(self):
|
|
271
258
|
return self.OUTPUT
|
|
272
259
|
toString = get
|
package/src/parse.pyj
CHANGED
|
@@ -1416,6 +1416,18 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
|
|
|
1416
1416
|
bases = v'[]'
|
|
1417
1417
|
class_parent = None
|
|
1418
1418
|
|
|
1419
|
+
# If this class is nested inside another class, register it under its
|
|
1420
|
+
# full dotted path (e.g. "Outer.Inner") in the module-level class scope
|
|
1421
|
+
# (S.classes[0]). This lets get_class_in_scope() find "Outer.Inner" as
|
|
1422
|
+
# a class and produce a correct AST_New constructor call instead of
|
|
1423
|
+
# treating it as a method call on Outer.
|
|
1424
|
+
outer_class_parts = []
|
|
1425
|
+
for _outer in S.in_class:
|
|
1426
|
+
if _outer:
|
|
1427
|
+
outer_class_parts.push(_outer)
|
|
1428
|
+
if outer_class_parts.length > 0:
|
|
1429
|
+
S.classes[0][outer_class_parts.join('.') + '.' + name.name] = class_details
|
|
1430
|
+
|
|
1419
1431
|
# read the bases of the class, if any
|
|
1420
1432
|
if is_("punc", "("):
|
|
1421
1433
|
S.in_parenthesized_expr = True
|
|
@@ -1521,7 +1533,11 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
|
|
|
1521
1533
|
visitor = new walker()
|
|
1522
1534
|
|
|
1523
1535
|
for stmt in definition.body:
|
|
1524
|
-
if
|
|
1536
|
+
if is_node_type(stmt, AST_Class):
|
|
1537
|
+
# Nested class: include in statements but don't walk through the
|
|
1538
|
+
# class-var mangling visitor (nested classes have their own scope).
|
|
1539
|
+
definition.statements.push(stmt)
|
|
1540
|
+
else:
|
|
1525
1541
|
stmt.walk(visitor)
|
|
1526
1542
|
definition.statements.push(stmt)
|
|
1527
1543
|
return definition
|
package/test/baselib.pyj
CHANGED
|
@@ -117,8 +117,8 @@ assrt.throws(def():a.index(8);, ValueError)
|
|
|
117
117
|
assrt.throws(def():a.index(1, 1);, ValueError)
|
|
118
118
|
assrt.throws(def():a.index(4, 1, 2);, ValueError)
|
|
119
119
|
assrt.equal(1, a.index(2, 1, 2))
|
|
120
|
-
assrt.throws(def():a.
|
|
121
|
-
assrt.equal(a.
|
|
120
|
+
assrt.throws(def():a.pop(10);, IndexError)
|
|
121
|
+
assrt.equal(a.pop(-1), 4)
|
|
122
122
|
assrt.deepEqual(a, [1,2,3])
|
|
123
123
|
assrt.equal(a.remove(2), None)
|
|
124
124
|
assrt.deepEqual(a, [1, 3])
|
|
@@ -141,9 +141,9 @@ assrt.ok(a.as_array().extend == undefined)
|
|
|
141
141
|
a = [1, 2, 1]
|
|
142
142
|
assrt.equal(a.count(1), 2)
|
|
143
143
|
a = [3, 2, 4, 1]
|
|
144
|
-
a.
|
|
144
|
+
a.sort()
|
|
145
145
|
assrt.deepEqual(a, [1,2,3,4])
|
|
146
|
-
a.
|
|
146
|
+
a.sort(reverse=True)
|
|
147
147
|
assrt.deepEqual(a, [4,3,2,1])
|
|
148
148
|
assrt.deepEqual(a, a.slice())
|
|
149
149
|
assrt.ok(a is not a.slice())
|
package/test/classes.pyj
CHANGED
|
@@ -67,7 +67,7 @@ bound = angela.get_bound_method()
|
|
|
67
67
|
assrt.equal(bound(), angela.how_long())
|
|
68
68
|
|
|
69
69
|
# function methods
|
|
70
|
-
assrt.deepEqual(dir(angela)
|
|
70
|
+
assrt.deepEqual(sorted(dir(angela)), [
|
|
71
71
|
'HAIRS',
|
|
72
72
|
"__init__",
|
|
73
73
|
'__repr__',
|
|
@@ -137,22 +137,61 @@ inc()
|
|
|
137
137
|
assrt.equal(c.count, 6)
|
|
138
138
|
|
|
139
139
|
# nested classes
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
140
|
+
class Molecule:
|
|
141
|
+
class Atom:
|
|
142
|
+
def __init__(self, element):
|
|
143
|
+
self.element = element
|
|
144
|
+
def __repr__(self):
|
|
145
|
+
return 'Atom(' + self.element + ')'
|
|
146
|
+
|
|
147
|
+
def __init__(self, elements):
|
|
148
|
+
self.structure = []
|
|
149
|
+
for e in elements:
|
|
150
|
+
self.structure.push(Molecule.Atom(e))
|
|
151
|
+
|
|
152
|
+
water = Molecule(['H', 'H', 'O'])
|
|
153
|
+
assrt.equal(len(water.structure), 3)
|
|
154
|
+
assrt.equal(water.structure[0].element, 'H')
|
|
155
|
+
assrt.equal(water.structure[2].element, 'O')
|
|
156
|
+
for atom in water.structure:
|
|
157
|
+
assrt.ok(isinstance(atom, Molecule.Atom))
|
|
158
|
+
|
|
159
|
+
# nested class accessible via class reference and via instance attribute
|
|
160
|
+
assrt.ok(Molecule.Atom is water.structure[0].__class__)
|
|
161
|
+
# nested class can also be accessed via instance (self.Inner semantics)
|
|
162
|
+
mol2 = Molecule(['C'])
|
|
163
|
+
assrt.ok(isinstance(mol2.structure[0], mol2.Atom))
|
|
164
|
+
|
|
165
|
+
# two levels of nesting
|
|
166
|
+
class Universe:
|
|
167
|
+
class Galaxy:
|
|
168
|
+
class Star:
|
|
169
|
+
def __init__(self, name):
|
|
170
|
+
self.name = name
|
|
171
|
+
def __init__(self, star_name):
|
|
172
|
+
self.star = Universe.Galaxy.Star(star_name)
|
|
173
|
+
def __init__(self, star_name):
|
|
174
|
+
self.galaxy = Universe.Galaxy(star_name)
|
|
175
|
+
|
|
176
|
+
u = Universe('Sol')
|
|
177
|
+
assrt.equal(u.galaxy.star.name, 'Sol')
|
|
178
|
+
assrt.ok(isinstance(u.galaxy.star, Universe.Galaxy.Star))
|
|
179
|
+
|
|
180
|
+
# nested class with inheritance from outer-scope class
|
|
181
|
+
class Animal:
|
|
182
|
+
def __init__(self, sound):
|
|
183
|
+
self.sound = sound
|
|
184
|
+
|
|
185
|
+
class Zoo:
|
|
186
|
+
class Dog(Animal):
|
|
187
|
+
def __init__(self):
|
|
188
|
+
Animal.__init__(self, 'woof')
|
|
189
|
+
|
|
190
|
+
zoo = Zoo()
|
|
191
|
+
fido = Zoo.Dog()
|
|
192
|
+
assrt.equal(fido.sound, 'woof')
|
|
193
|
+
assrt.ok(isinstance(fido, Animal))
|
|
194
|
+
assrt.ok(isinstance(fido, Zoo.Dog))
|
|
156
195
|
|
|
157
196
|
# starargs and method decorators
|
|
158
197
|
def negate(fn):
|
package/test/collections.pyj
CHANGED
|
@@ -62,18 +62,18 @@ assrt.equal(s[4:1:-2], 'ec')
|
|
|
62
62
|
|
|
63
63
|
# sorting
|
|
64
64
|
a = [2,1,3]
|
|
65
|
-
a.
|
|
65
|
+
a.sort(key=def(x):return 0;)
|
|
66
66
|
assrt.deepEqual(a, [2,1,3]) # stable sort
|
|
67
|
-
a.
|
|
67
|
+
a.sort(reverse=True)
|
|
68
68
|
assrt.deepEqual(a, [3,2,1])
|
|
69
|
-
a.
|
|
69
|
+
a.sort()
|
|
70
70
|
assrt.deepEqual(a, [1,2,3])
|
|
71
71
|
|
|
72
72
|
# misc interface
|
|
73
73
|
a = [1,2,3]
|
|
74
|
-
assrt.equal(a.
|
|
74
|
+
assrt.equal(a.pop(), 3)
|
|
75
75
|
assrt.deepEqual(a, [1,2])
|
|
76
|
-
assrt.equal(a.
|
|
76
|
+
assrt.equal(a.pop(0), 1)
|
|
77
77
|
assrt.deepEqual(a, [2])
|
|
78
78
|
|
|
79
79
|
# strings
|