rapydscript-ns 0.9.2 → 0.9.4
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 +28 -0
- package/PYTHON_GAPS.md +352 -0
- package/README.md +176 -32
- package/TODO.md +1 -128
- package/bin/rapydscript +70 -70
- package/language-service/index.js +242 -11
- package/memory/project_string_impl.md +43 -0
- package/package.json +1 -1
- package/release/baselib-plain-pretty.js +248 -38
- package/release/baselib-plain-ugly.js +8 -8
- package/release/compiler.js +778 -277
- package/release/signatures.json +30 -30
- package/src/ast.pyj +10 -1
- package/src/baselib-builtins.pyj +56 -2
- package/src/baselib-containers.pyj +25 -1
- package/src/baselib-errors.pyj +7 -3
- package/src/baselib-internal.pyj +51 -6
- package/src/baselib-str.pyj +18 -5
- package/src/lib/asyncio.pyj +534 -0
- package/src/lib/base64.pyj +399 -0
- package/src/lib/bisect.pyj +73 -0
- package/src/lib/collections.pyj +228 -4
- package/src/lib/csv.pyj +494 -0
- package/src/lib/heapq.pyj +98 -0
- package/src/lib/html.pyj +382 -0
- package/src/lib/http/__init__.pyj +98 -0
- package/src/lib/http/client.pyj +304 -0
- package/src/lib/http/cookies.pyj +236 -0
- package/src/lib/logging.pyj +672 -0
- package/src/lib/pprint.pyj +455 -0
- package/src/lib/pythonize.pyj +20 -20
- package/src/lib/statistics.pyj +0 -0
- package/src/lib/string.pyj +357 -0
- package/src/lib/textwrap.pyj +329 -0
- package/src/lib/urllib/__init__.pyj +14 -0
- package/src/lib/urllib/error.pyj +66 -0
- package/src/lib/urllib/parse.pyj +475 -0
- package/src/lib/urllib/request.pyj +86 -0
- package/src/monaco-language-service/analyzer.js +5 -2
- package/src/monaco-language-service/completions.js +26 -0
- package/src/monaco-language-service/diagnostics.js +203 -4
- package/src/monaco-language-service/scope.js +1 -0
- package/src/output/codegen.pyj +4 -1
- package/src/output/functions.pyj +152 -6
- package/src/output/loops.pyj +17 -2
- package/src/output/modules.pyj +1 -1
- package/src/output/operators.pyj +15 -0
- package/src/output/stream.pyj +0 -1
- package/src/parse.pyj +108 -24
- package/src/tokenizer.pyj +19 -3
- package/test/async_generators.pyj +144 -0
- package/test/asyncio.pyj +307 -0
- package/test/base64.pyj +202 -0
- package/test/baselib.pyj +23 -0
- package/test/bisect.pyj +178 -0
- package/test/chainmap.pyj +185 -0
- package/test/csv.pyj +405 -0
- package/test/float_special.pyj +64 -0
- package/test/heapq.pyj +174 -0
- package/test/html.pyj +212 -0
- package/test/http.pyj +259 -0
- package/test/imports.pyj +79 -72
- package/test/logging.pyj +356 -0
- package/test/long.pyj +130 -0
- package/test/parenthesized_with.pyj +141 -0
- package/test/pprint.pyj +232 -0
- package/test/python_compat.pyj +3 -5
- package/test/python_modulo.pyj +76 -0
- package/test/python_modulo_off.pyj +21 -0
- package/test/statistics.pyj +224 -0
- package/test/str.pyj +14 -0
- package/test/string.pyj +245 -0
- package/test/textwrap.pyj +172 -0
- package/test/type_display.pyj +48 -0
- package/test/type_enforcement.pyj +164 -0
- package/test/unit/index.js +94 -6
- package/test/unit/language-service-completions.js +121 -0
- package/test/unit/language-service-scope.js +32 -0
- package/test/unit/language-service.js +190 -5
- package/test/unit/run-language-service.js +17 -3
- package/test/unit/web-repl.js +2401 -13
- package/test/urllib.pyj +193 -0
- package/tools/compile.js +1 -1
- package/tools/embedded_compiler.js +7 -7
- package/tools/export.js +4 -2
- package/web-repl/main.js +1 -1
- package/web-repl/rapydscript.js +7 -5
- package/test/omit_function_metadata.pyj +0 -20
|
@@ -20,15 +20,20 @@ const MESSAGES = {
|
|
|
20
20
|
'def-after-use': 'The symbol "{name}" is defined (at line {line}) after it is used',
|
|
21
21
|
'dup-key': 'Duplicate key "{name}" in object literal',
|
|
22
22
|
'dup-method': 'The method "{name}" was defined previously at line: {line}',
|
|
23
|
+
'infinite-loop': 'This while loop may never exit (condition is always true and there is no await in the body)',
|
|
24
|
+
// type_enforcement diagnostics
|
|
25
|
+
'type-posonly-kwarg': '"{name}" is positional-only and cannot be passed as a keyword argument to {func}()',
|
|
26
|
+
'type-too-many-args': '{func}() takes at most {max} positional argument{plural} but {got} were given',
|
|
27
|
+
'type-missing-kwonly': '"{name}" is a required keyword-only argument of {func}() and must be passed by name',
|
|
23
28
|
};
|
|
24
29
|
|
|
25
30
|
// Built-in stdlib modules that are always available in RapydScript (bundled
|
|
26
31
|
// with the compiler from src/lib/). These should never produce 'Unknown module'
|
|
27
32
|
// errors regardless of what virtualFiles or stdlibFiles are configured.
|
|
28
33
|
export const STDLIB_MODULES = [
|
|
29
|
-
'abc', 'aes', 'collections', 'contextlib', 'copy', 'dataclasses', 'datetime', 'elementmaker', 'encodings', 'enum',
|
|
30
|
-
'functools', 'gettext', 'io', 'itertools', 'json', 'math', 'numpy', 'operator',
|
|
31
|
-
'pythonize', 'random', 're', 'react', 'traceback', 'typing', 'uuid',
|
|
34
|
+
'abc', 'aes', 'asyncio', 'base64', 'bisect', 'collections', 'contextlib', 'copy', 'csv', 'dataclasses', 'datetime', 'elementmaker', 'encodings', 'enum',
|
|
35
|
+
'functools', 'gettext', 'heapq', 'html', 'http', 'io', 'itertools', 'json', 'logging', 'math', 'numpy', 'operator',
|
|
36
|
+
'pprint', 'pythonize', 'random', 're', 'react', 'statistics', 'string', 'textwrap', 'traceback', 'typing', 'urllib', 'uuid',
|
|
32
37
|
// Pseudo-modules for language feature flags (from __python__ import ...)
|
|
33
38
|
'__python__', '__builtins__',
|
|
34
39
|
];
|
|
@@ -48,7 +53,7 @@ export const BASE_BUILTINS = (
|
|
|
48
53
|
' NodeList alert console Node Symbol NamedNodeMap ρσ_eslice ρσ_delslice Number' +
|
|
49
54
|
' Boolean encodeURIComponent decodeURIComponent setTimeout setInterval' +
|
|
50
55
|
' setImmediate clearTimeout clearInterval clearImmediate requestAnimationFrame' +
|
|
51
|
-
' id repr sorted __name__ equals get_module ρσ_str jstype divmod NaN super Ellipsis slice all any next __import__ ρσ_new ρσ_object_new hash ρσ_object_setattr ρσ_object_getattr ρσ_object_delattr ExceptionGroup BaseExceptionGroup tuple bytes bytearray format object vars locals globals'
|
|
56
|
+
' id repr sorted __name__ equals get_module ρσ_str jstype divmod NaN super Ellipsis slice all any next __import__ ρσ_new ρσ_object_new hash ρσ_object_setattr ρσ_object_getattr ρσ_object_delattr ExceptionGroup BaseExceptionGroup tuple bytes bytearray format object vars locals globals type'
|
|
52
57
|
).split(' ');
|
|
53
58
|
|
|
54
59
|
// ---------------------------------------------------------------------------
|
|
@@ -497,6 +502,42 @@ function Linter(RS, toplevel, code, builtins, knownModules) {
|
|
|
497
502
|
if (node.alias) this.add_binding(node.alias.name);
|
|
498
503
|
};
|
|
499
504
|
|
|
505
|
+
this.handle_while = function() {
|
|
506
|
+
const node = this.current_node;
|
|
507
|
+
const cond = node.condition;
|
|
508
|
+
const RS = this.RS;
|
|
509
|
+
|
|
510
|
+
const is_trivially_true = (
|
|
511
|
+
(cond instanceof RS.AST_True) ||
|
|
512
|
+
(cond instanceof RS.AST_Number && cond.value !== 0)
|
|
513
|
+
);
|
|
514
|
+
if (!is_trivially_true) return;
|
|
515
|
+
|
|
516
|
+
let has_await = false;
|
|
517
|
+
const await_finder = {
|
|
518
|
+
_visit: function(n, cont) {
|
|
519
|
+
if (n instanceof RS.AST_Await) { has_await = true; return; }
|
|
520
|
+
if (cont) cont();
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
if (node.body) node.body._walk(await_finder);
|
|
524
|
+
|
|
525
|
+
if (!has_await) {
|
|
526
|
+
const sline = node.start ? node.start.line : 1;
|
|
527
|
+
const scol = node.start ? node.start.col : 0;
|
|
528
|
+
const eline = (node.condition && node.condition.end) ? node.condition.end.line : sline;
|
|
529
|
+
const ecol = (node.condition && node.condition.end) ? node.condition.end.col : scol;
|
|
530
|
+
this.messages.push({
|
|
531
|
+
start_line: sline, start_col: scol,
|
|
532
|
+
end_line: eline, end_col: ecol,
|
|
533
|
+
ident: 'infinite-loop',
|
|
534
|
+
message: MESSAGES['infinite-loop'],
|
|
535
|
+
level: WARN,
|
|
536
|
+
name: '',
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
|
|
500
541
|
// The visitor function called by toplevel.walk()
|
|
501
542
|
this._visit = function(node, cont) {
|
|
502
543
|
if (node.lint_visited) return;
|
|
@@ -530,6 +571,7 @@ function Linter(RS, toplevel, code, builtins, knownModules) {
|
|
|
530
571
|
else if (node instanceof RS.AST_EmptyStatement) this.handle_empty_statement();
|
|
531
572
|
else if (node instanceof RS.AST_WithClause) this.handle_with_clause();
|
|
532
573
|
else if (node instanceof RS.AST_Object) this.handle_object_literal();
|
|
574
|
+
else if (node instanceof RS.AST_While) this.handle_while();
|
|
533
575
|
|
|
534
576
|
if (node instanceof RS.AST_Scope) this.handle_scope();
|
|
535
577
|
|
|
@@ -580,6 +622,143 @@ function Linter(RS, toplevel, code, builtins, knownModules) {
|
|
|
580
622
|
};
|
|
581
623
|
}
|
|
582
624
|
|
|
625
|
+
// ---------------------------------------------------------------------------
|
|
626
|
+
// Type-enforcement static checker
|
|
627
|
+
// Runs a two-pass walk when any function in the file has type_enforce=true:
|
|
628
|
+
// Pass 1 — collect signatures of enforced functions.
|
|
629
|
+
// Pass 2 — inspect call sites and report violations.
|
|
630
|
+
// ---------------------------------------------------------------------------
|
|
631
|
+
|
|
632
|
+
function check_type_enforcement(RS, toplevel, messages) {
|
|
633
|
+
// Pass 1: collect signatures of functions compiled with type_enforce=true
|
|
634
|
+
const sigs = Object.create(null);
|
|
635
|
+
|
|
636
|
+
const col1 = {
|
|
637
|
+
_visit: function(node, cont) {
|
|
638
|
+
if (node instanceof RS.AST_Lambda && node.type_enforce && node.name) {
|
|
639
|
+
const a = node.argnames;
|
|
640
|
+
const is_m = node instanceof RS.AST_Method;
|
|
641
|
+
const off = (is_m && !node.static) ? 1 : 0;
|
|
642
|
+
const posonly_names = [];
|
|
643
|
+
const kwonly_req = [];
|
|
644
|
+
let max_pos = 0;
|
|
645
|
+
const defs = (a.defaults) || {};
|
|
646
|
+
for (let i = off; i < a.length; i++) {
|
|
647
|
+
const arg = a[i];
|
|
648
|
+
if (arg.posonly) posonly_names.push(arg.name);
|
|
649
|
+
if (arg.kwonly) {
|
|
650
|
+
if (!has_prop(defs, arg.name)) kwonly_req.push(arg.name);
|
|
651
|
+
continue;
|
|
652
|
+
}
|
|
653
|
+
max_pos++;
|
|
654
|
+
}
|
|
655
|
+
sigs[node.name.name] = {
|
|
656
|
+
node,
|
|
657
|
+
posonly_names,
|
|
658
|
+
kwonly_req,
|
|
659
|
+
max_pos,
|
|
660
|
+
has_starargs: !!a.starargs,
|
|
661
|
+
has_kwargs: !!a.kwargs,
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
if (cont) cont();
|
|
665
|
+
}
|
|
666
|
+
};
|
|
667
|
+
toplevel.walk(col1);
|
|
668
|
+
|
|
669
|
+
if (Object.keys(sigs).length === 0) return;
|
|
670
|
+
|
|
671
|
+
// Pass 2: check call sites
|
|
672
|
+
const col2 = {
|
|
673
|
+
_visit: function(node, cont) {
|
|
674
|
+
if (node instanceof RS.AST_BaseCall) {
|
|
675
|
+
let fname = null;
|
|
676
|
+
const expr = node.expression;
|
|
677
|
+
if (expr instanceof RS.AST_SymbolRef) fname = expr.name;
|
|
678
|
+
else if (expr instanceof RS.AST_Dot) fname = expr.property;
|
|
679
|
+
|
|
680
|
+
if (fname && has_prop(sigs, fname)) {
|
|
681
|
+
const sig = sigs[fname];
|
|
682
|
+
const args = node.args;
|
|
683
|
+
const pos_count = args ? args.length : 0;
|
|
684
|
+
const named_kw = (args && args.kwargs) ? args.kwargs : [];
|
|
685
|
+
const kw_expand = args && args.kwarg_items && args.kwarg_items.length > 0;
|
|
686
|
+
|
|
687
|
+
// Positional-only args passed as named kwargs
|
|
688
|
+
sig.posonly_names.forEach(argname => {
|
|
689
|
+
for (let ki = 0; ki < named_kw.length; ki++) {
|
|
690
|
+
const knode = named_kw[ki][0];
|
|
691
|
+
const kname = knode.name || (knode.value !== undefined ? String(knode.value) : null);
|
|
692
|
+
if (kname === argname) {
|
|
693
|
+
const msg = MESSAGES['type-posonly-kwarg']
|
|
694
|
+
.replace('{name}', argname)
|
|
695
|
+
.replace('{func}', fname);
|
|
696
|
+
messages.push({
|
|
697
|
+
ident: 'type-posonly-kwarg',
|
|
698
|
+
message: msg,
|
|
699
|
+
level: ERROR,
|
|
700
|
+
name: argname,
|
|
701
|
+
start_line: knode.start ? knode.start.line : 1,
|
|
702
|
+
start_col: knode.start ? knode.start.col : 0,
|
|
703
|
+
end_line: knode.end ? knode.end.line : undefined,
|
|
704
|
+
end_col: knode.end ? knode.end.col : undefined,
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
});
|
|
709
|
+
|
|
710
|
+
// Too many positional args (skip when *args or **expansion present)
|
|
711
|
+
if (!sig.has_starargs && !kw_expand && pos_count > sig.max_pos) {
|
|
712
|
+
const plural = sig.max_pos === 1 ? '' : 's';
|
|
713
|
+
const msg = MESSAGES['type-too-many-args']
|
|
714
|
+
.replace('{func}', fname)
|
|
715
|
+
.replace('{max}', String(sig.max_pos))
|
|
716
|
+
.replace('{plural}', plural)
|
|
717
|
+
.replace('{got}', String(pos_count));
|
|
718
|
+
messages.push({
|
|
719
|
+
ident: 'type-too-many-args',
|
|
720
|
+
message: msg,
|
|
721
|
+
level: ERROR,
|
|
722
|
+
name: fname,
|
|
723
|
+
start_line: node.start ? node.start.line : 1,
|
|
724
|
+
start_col: node.start ? node.start.col : 0,
|
|
725
|
+
end_line: node.end ? node.end.line : undefined,
|
|
726
|
+
end_col: node.end ? node.end.col : undefined,
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// Required keyword-only args not provided (skip when **expansion present)
|
|
731
|
+
if (!kw_expand) {
|
|
732
|
+
sig.kwonly_req.forEach(argname => {
|
|
733
|
+
const provided = named_kw.some(pair => {
|
|
734
|
+
const kn = pair[0].name || (pair[0].value !== undefined ? String(pair[0].value) : null);
|
|
735
|
+
return kn === argname;
|
|
736
|
+
});
|
|
737
|
+
if (!provided) {
|
|
738
|
+
const msg = MESSAGES['type-missing-kwonly']
|
|
739
|
+
.replace('{name}', argname)
|
|
740
|
+
.replace('{func}', fname);
|
|
741
|
+
messages.push({
|
|
742
|
+
ident: 'type-missing-kwonly',
|
|
743
|
+
message: msg,
|
|
744
|
+
level: ERROR,
|
|
745
|
+
name: argname,
|
|
746
|
+
start_line: node.start ? node.start.line : 1,
|
|
747
|
+
start_col: node.start ? node.start.col : 0,
|
|
748
|
+
end_line: node.end ? node.end.line : undefined,
|
|
749
|
+
end_col: node.end ? node.end.col : undefined,
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
if (cont) cont();
|
|
757
|
+
}
|
|
758
|
+
};
|
|
759
|
+
toplevel.walk(col2);
|
|
760
|
+
}
|
|
761
|
+
|
|
583
762
|
// ---------------------------------------------------------------------------
|
|
584
763
|
// Convert lint message → Monaco IMarkerData
|
|
585
764
|
// markerSeverity should be { Error, Warning, Info, Hint } numeric values
|
|
@@ -709,6 +888,26 @@ export class Diagnostics {
|
|
|
709
888
|
toplevel.walk(linter);
|
|
710
889
|
messages = linter.resolve(noqa);
|
|
711
890
|
|
|
891
|
+
// --- Type enforcement static checks ---
|
|
892
|
+
// Run when type_enforcement is active at the file level (either via
|
|
893
|
+
// pythonFlags constructor arg or from __python__ import type_enforcement).
|
|
894
|
+
const te_active =
|
|
895
|
+
(this._scoped_flags && this._scoped_flags.type_enforcement) ||
|
|
896
|
+
(toplevel.scoped_flags && toplevel.scoped_flags.type_enforcement);
|
|
897
|
+
if (te_active) {
|
|
898
|
+
const te_messages = [];
|
|
899
|
+
check_type_enforcement(RS, toplevel, te_messages);
|
|
900
|
+
// Filter noqa, then convert to markers inline
|
|
901
|
+
te_messages.forEach(m => {
|
|
902
|
+
if (noqa && has_prop(noqa, m.ident)) return;
|
|
903
|
+
messages.push(m);
|
|
904
|
+
});
|
|
905
|
+
messages.sort((a, b) => {
|
|
906
|
+
const dl = (a.start_line || 0) - (b.start_line || 0);
|
|
907
|
+
return dl !== 0 ? dl : (a.start_col || 0) - (b.start_col || 0);
|
|
908
|
+
});
|
|
909
|
+
}
|
|
910
|
+
|
|
712
911
|
return messages.map(m => to_marker(m, markerSeverity));
|
|
713
912
|
}
|
|
714
913
|
}
|
|
@@ -33,6 +33,7 @@ export class SymbolInfo {
|
|
|
33
33
|
this.return_type = opts.return_type || null; // annotated return type, e.g. 'list', 'str', 'MyClass'
|
|
34
34
|
this.source_module = opts.source_module || null; // module name when kind='import' (from X import Y)
|
|
35
35
|
this.original_name = opts.original_name || null; // pre-alias name for imports (from X import Y as Z → 'Y')
|
|
36
|
+
this.is_bare_import = opts.is_bare_import || false; // true for `import X` (vs `from X import Y`)
|
|
36
37
|
}
|
|
37
38
|
}
|
|
38
39
|
|
package/src/output/codegen.pyj
CHANGED
|
@@ -7,7 +7,7 @@ from __python__ import hash_literals
|
|
|
7
7
|
from utils import noop
|
|
8
8
|
from parse import PRECEDENCE
|
|
9
9
|
from ast import (
|
|
10
|
-
AST_Array, AST_Assign, AST_BaseCall, AST_Binary, AST_BlockStatement, AST_Break,
|
|
10
|
+
AST_Array, AST_Assign, AST_BaseCall, AST_BigInt, AST_Binary, AST_BlockStatement, AST_Break,
|
|
11
11
|
AST_Class, AST_Conditional, AST_Constant, AST_Continue,
|
|
12
12
|
AST_Debugger, AST_Definitions, AST_Directive, AST_Do, AST_Dot, is_node_type,
|
|
13
13
|
AST_EmptyStatement, AST_Exit, AST_ExpressiveObject, AST_ForIn,
|
|
@@ -530,6 +530,9 @@ def generate_code():
|
|
|
530
530
|
DEFPRINT(AST_Number, def(self, output):
|
|
531
531
|
output.print(make_num(self.value))
|
|
532
532
|
)
|
|
533
|
+
DEFPRINT(AST_BigInt, def(self, output):
|
|
534
|
+
output.print(self.value + "n")
|
|
535
|
+
)
|
|
533
536
|
DEFPRINT(AST_RegExp, print_regexp)
|
|
534
537
|
|
|
535
538
|
# -----[ JSX ]-----
|
package/src/output/functions.pyj
CHANGED
|
@@ -45,11 +45,69 @@ def function_args(argnames, output, strip_first):
|
|
|
45
45
|
|
|
46
46
|
def function_preamble(node, output, offset):
|
|
47
47
|
a = node.argnames
|
|
48
|
-
if a is None or a is undefined
|
|
48
|
+
if a is None or a is undefined:
|
|
49
|
+
return
|
|
50
|
+
type_enforce = node.type_enforce
|
|
51
|
+
if a.is_simple_func and not type_enforce:
|
|
49
52
|
return
|
|
50
53
|
# If this function has optional parameters/*args/**kwargs declare it differently
|
|
51
54
|
fname = node.name.name if node.name else anonfunc
|
|
52
55
|
kw = 'arguments[arguments.length-1]'
|
|
56
|
+
|
|
57
|
+
# === Type enforcement: max positional-arg count check ===
|
|
58
|
+
if type_enforce:
|
|
59
|
+
max_pos = 0
|
|
60
|
+
for c, arg in enumerate(a):
|
|
61
|
+
if c >= offset and not arg.kwonly:
|
|
62
|
+
max_pos += 1
|
|
63
|
+
if a.is_simple_func:
|
|
64
|
+
# Simple function: named JS params; arguments.length gives true count
|
|
65
|
+
if not a.starargs:
|
|
66
|
+
# Strip a trailing kwargs-marker object so a `*args, **kwargs`
|
|
67
|
+
# caller does not get blamed for an extra positional argument
|
|
68
|
+
# (matches the complex-func variant below).
|
|
69
|
+
output.indent()
|
|
70
|
+
output.spaced('var', 'ρσ_nargs', '=', 'arguments.length')
|
|
71
|
+
output.end_statement()
|
|
72
|
+
output.indent()
|
|
73
|
+
output.print('if (ρσ_nargs > 0 && arguments[ρσ_nargs-1] !== null && typeof arguments[ρσ_nargs-1] === "object" && arguments[ρσ_nargs-1][ρσ_kwargs_symbol] === true) ρσ_nargs--')
|
|
74
|
+
output.end_statement()
|
|
75
|
+
max_msg = fname + '() takes ' + max_pos + ' positional argument' + ('' if max_pos == 1 else 's') + ' but '
|
|
76
|
+
output.indent()
|
|
77
|
+
output.print('if (ρσ_nargs > ' + max_pos + ') throw new TypeError(' + JSON.stringify(max_msg) + ' + ρσ_nargs + " were given")')
|
|
78
|
+
output.end_statement()
|
|
79
|
+
# Required-arg and type-annotation checks (variables are direct JS params)
|
|
80
|
+
for c, arg in enumerate(a):
|
|
81
|
+
if c < offset:
|
|
82
|
+
continue
|
|
83
|
+
if not arg.kwonly:
|
|
84
|
+
miss_msg = fname + '() missing required positional argument: \'' + arg.name + '\''
|
|
85
|
+
output.indent()
|
|
86
|
+
output.print('if (' + arg.name + ' === undefined) throw new TypeError(' + JSON.stringify(miss_msg) + ')')
|
|
87
|
+
output.end_statement()
|
|
88
|
+
if arg.annotation:
|
|
89
|
+
annot_name = arg.annotation.name or ''
|
|
90
|
+
type_msg = fname + '() argument \'' + arg.name + '\' must be ' + annot_name
|
|
91
|
+
output.indent()
|
|
92
|
+
output.print('if (' + arg.name + ' !== undefined && !ρσ_instanceof(' + arg.name + ', ')
|
|
93
|
+
arg.annotation.print(output)
|
|
94
|
+
output.print(')) throw new TypeError(' + JSON.stringify(type_msg) + ')')
|
|
95
|
+
output.end_statement()
|
|
96
|
+
return # simple func: only enforcement, no complex preamble
|
|
97
|
+
|
|
98
|
+
# Complex function: must handle arguments object; strip trailing kwargs marker
|
|
99
|
+
if not a.starargs:
|
|
100
|
+
output.indent()
|
|
101
|
+
output.spaced('var', 'ρσ_nargs', '=', 'arguments.length')
|
|
102
|
+
output.end_statement()
|
|
103
|
+
output.indent()
|
|
104
|
+
output.print('if (ρσ_nargs > 0 && arguments[ρσ_nargs-1] !== null && typeof arguments[ρσ_nargs-1] === "object" && arguments[ρσ_nargs-1][ρσ_kwargs_symbol] === true) ρσ_nargs--')
|
|
105
|
+
output.end_statement()
|
|
106
|
+
max_msg = fname + '() takes ' + max_pos + ' positional argument' + ('' if max_pos == 1 else 's') + ' but '
|
|
107
|
+
output.indent()
|
|
108
|
+
output.print('if (ρσ_nargs > ' + max_pos + ') throw new TypeError(' + JSON.stringify(max_msg) + ' + ρσ_nargs + " were given")')
|
|
109
|
+
output.end_statement()
|
|
110
|
+
|
|
53
111
|
# Define all formal parameters
|
|
54
112
|
positional_count = 0
|
|
55
113
|
for c, arg in enumerate(a):
|
|
@@ -89,6 +147,22 @@ def function_preamble(node, output, offset):
|
|
|
89
147
|
output.indent()
|
|
90
148
|
output.spaced('if', '(' + kw, '===', 'null', '||', 'typeof', kw, '!==', '"object"', '||', kw, '[ρσ_kwargs_symbol]', '!==', 'true)', kw, '=', '{}')
|
|
91
149
|
output.end_statement()
|
|
150
|
+
# === Type enforcement: positional-only args must not appear in kwargs ===
|
|
151
|
+
if type_enforce:
|
|
152
|
+
posonly_count = a.posonly_count or 0
|
|
153
|
+
for pi in range(posonly_count):
|
|
154
|
+
if pi < offset:
|
|
155
|
+
continue
|
|
156
|
+
parg = a[pi]
|
|
157
|
+
posonly_msg = fname + '() got some positional-only arguments passed as keyword arguments: \'' + parg.name + '\''
|
|
158
|
+
output.indent()
|
|
159
|
+
output.spaced('if', '(Object.prototype.hasOwnProperty.call(' + kw + ',', '"' + parg.name + '"))')
|
|
160
|
+
output.with_block(def():
|
|
161
|
+
output.indent()
|
|
162
|
+
output.print('throw new TypeError(' + JSON.stringify(posonly_msg) + ')')
|
|
163
|
+
output.end_statement()
|
|
164
|
+
)
|
|
165
|
+
output.newline()
|
|
92
166
|
# Read values from the kwargs object for non-positional-only formal parameters with defaults
|
|
93
167
|
if a.has_defaults:
|
|
94
168
|
for dname in Object.keys(a.defaults):
|
|
@@ -138,6 +212,29 @@ def function_preamble(node, output, offset):
|
|
|
138
212
|
output.end_statement()
|
|
139
213
|
)
|
|
140
214
|
output.newline()
|
|
215
|
+
elif type_enforce and (a.posonly_count or 0) > 0:
|
|
216
|
+
# No kwargs block, but function has positional-only params.
|
|
217
|
+
# A caller using foo(posonly_name=val) still passes a kwargs object as the
|
|
218
|
+
# last argument; detect and reject it here.
|
|
219
|
+
posonly_count = a.posonly_count or 0
|
|
220
|
+
posonly_names = []
|
|
221
|
+
for pi in range(posonly_count):
|
|
222
|
+
if pi >= offset:
|
|
223
|
+
posonly_names.push(a[pi].name)
|
|
224
|
+
if posonly_names.length > 0:
|
|
225
|
+
output.indent()
|
|
226
|
+
output.spaced('var', 'ρσ_kw_chk', '=', 'arguments[arguments.length-1]')
|
|
227
|
+
output.end_statement()
|
|
228
|
+
output.indent()
|
|
229
|
+
output.spaced('if', '(ρσ_kw_chk !== null && typeof ρσ_kw_chk === "object" && ρσ_kw_chk[ρσ_kwargs_symbol] === true)')
|
|
230
|
+
output.with_block(def():
|
|
231
|
+
for pname in posonly_names:
|
|
232
|
+
posonly_msg = fname + '() got some positional-only arguments passed as keyword arguments: \'' + pname + '\''
|
|
233
|
+
output.indent()
|
|
234
|
+
output.print('if (Object.prototype.hasOwnProperty.call(ρσ_kw_chk, "' + pname + '")) throw new TypeError(' + JSON.stringify(posonly_msg) + ')')
|
|
235
|
+
output.end_statement()
|
|
236
|
+
)
|
|
237
|
+
output.newline()
|
|
141
238
|
|
|
142
239
|
if a.starargs is not undefined:
|
|
143
240
|
# Define the *args parameter, putting in whatever is left after assigning the formal parameters and the options object
|
|
@@ -160,6 +257,44 @@ def function_preamble(node, output, offset):
|
|
|
160
257
|
output.spaced(kw, '=', 'ρσ_kwargs_to_dict(' + kw + ')')
|
|
161
258
|
output.end_statement()
|
|
162
259
|
|
|
260
|
+
# === Type enforcement: required-arg, required-kwonly, and type-annotation checks ===
|
|
261
|
+
# These run after all variable resolution (kwargs reading, starargs setup, dict conversion)
|
|
262
|
+
# so each variable holds its final value when checked.
|
|
263
|
+
if type_enforce:
|
|
264
|
+
# Required positional args (no default, not kwonly)
|
|
265
|
+
for c, arg in enumerate(a):
|
|
266
|
+
if c < offset:
|
|
267
|
+
continue
|
|
268
|
+
if arg.kwonly:
|
|
269
|
+
continue
|
|
270
|
+
if not Object.prototype.hasOwnProperty.call(a.defaults, arg.name):
|
|
271
|
+
miss_msg = fname + '() missing required positional argument: \'' + arg.name + '\''
|
|
272
|
+
output.indent()
|
|
273
|
+
output.print('if (' + arg.name + ' === undefined) throw new TypeError(' + JSON.stringify(miss_msg) + ')')
|
|
274
|
+
output.end_statement()
|
|
275
|
+
# Required keyword-only args (no default)
|
|
276
|
+
for c, arg in enumerate(a):
|
|
277
|
+
if not arg.kwonly:
|
|
278
|
+
continue
|
|
279
|
+
if not Object.prototype.hasOwnProperty.call(a.defaults, arg.name):
|
|
280
|
+
miss_kw = fname + '() missing required keyword-only argument: \'' + arg.name + '\''
|
|
281
|
+
output.indent()
|
|
282
|
+
output.print('if (' + arg.name + ' === undefined) throw new TypeError(' + JSON.stringify(miss_kw) + ')')
|
|
283
|
+
output.end_statement()
|
|
284
|
+
# Type annotation checks (skip *args and **kwargs)
|
|
285
|
+
for c, arg in enumerate(a):
|
|
286
|
+
if c < offset:
|
|
287
|
+
continue
|
|
288
|
+
if not arg.annotation:
|
|
289
|
+
continue
|
|
290
|
+
annot_name = arg.annotation.name or ''
|
|
291
|
+
type_msg = fname + '() argument \'' + arg.name + '\' must be ' + annot_name
|
|
292
|
+
output.indent()
|
|
293
|
+
output.print('if (' + arg.name + ' !== undefined && !ρσ_instanceof(' + arg.name + ', ')
|
|
294
|
+
arg.annotation.print(output)
|
|
295
|
+
output.print(')) throw new TypeError(' + JSON.stringify(type_msg) + ')')
|
|
296
|
+
output.end_statement()
|
|
297
|
+
|
|
163
298
|
def has_annotations(self):
|
|
164
299
|
if self.return_annotation:
|
|
165
300
|
return True
|
|
@@ -169,8 +304,6 @@ def has_annotations(self):
|
|
|
169
304
|
return False
|
|
170
305
|
|
|
171
306
|
def function_annotation(self, output, strip_first, name):
|
|
172
|
-
if output.options.omit_function_metadata:
|
|
173
|
-
return
|
|
174
307
|
fname = name or (self.name.name if self.name else anonfunc)
|
|
175
308
|
props = Object.create(None)
|
|
176
309
|
|
|
@@ -263,27 +396,37 @@ def function_definition(self, output, strip_first, as_expression):
|
|
|
263
396
|
output.set_indentation(output.next_indent())
|
|
264
397
|
output.spaced('(function()', '{'), output.newline()
|
|
265
398
|
output.indent(), output.spaced('var', anonfunc, '='), output.space()
|
|
266
|
-
|
|
399
|
+
# Async generators (async def with yield) are emitted as a sync wrapper that
|
|
400
|
+
# returns the async iterator directly (matching Python semantics, where calling
|
|
401
|
+
# an async generator function returns the iterator immediately without await).
|
|
402
|
+
# The inner js_generator is `async function*`. The outer wrapper is sync.
|
|
403
|
+
if self.is_async and not self.is_generator:
|
|
267
404
|
output.print("async"), output.space()
|
|
268
405
|
output.print("function"), output.space()
|
|
269
406
|
if self.name:
|
|
270
407
|
self.name.print(output)
|
|
271
408
|
|
|
272
409
|
if self.is_generator:
|
|
410
|
+
inner_decl = 'async function* js_generator' if self.is_async else 'function* js_generator'
|
|
273
411
|
output.print('()'), output.space()
|
|
274
412
|
output.with_block(def():
|
|
275
413
|
output.indent()
|
|
276
|
-
output.print(
|
|
414
|
+
output.print(inner_decl)
|
|
277
415
|
function_args(self.argnames, output, strip_first)
|
|
278
416
|
print_bracketed(self, output, True, function_preamble)
|
|
279
417
|
output.newline()
|
|
280
418
|
output.indent()
|
|
281
419
|
output.spaced('var', 'result', '=', 'js_generator.apply(this,', 'arguments)')
|
|
282
420
|
output.end_statement()
|
|
283
|
-
# Python's generator objects use a separate method to send data to the generator
|
|
421
|
+
# Python's generator objects use a separate method to send data to the generator.
|
|
422
|
+
# For async generators, asend() is the async equivalent — alias it to next as well.
|
|
284
423
|
output.indent()
|
|
285
424
|
output.spaced('result.send', '=', 'result.next')
|
|
286
425
|
output.end_statement()
|
|
426
|
+
if self.is_async:
|
|
427
|
+
output.indent()
|
|
428
|
+
output.spaced('result.asend', '=', 'result.next')
|
|
429
|
+
output.end_statement()
|
|
287
430
|
output.indent()
|
|
288
431
|
output.spaced('return', 'result')
|
|
289
432
|
output.end_statement()
|
|
@@ -443,12 +586,15 @@ def print_function_call(self, output):
|
|
|
443
586
|
)
|
|
444
587
|
elif is_node_type(self.expression, AST_SymbolRef) and self.expression.name is 'print':
|
|
445
588
|
# Bare print(...) → console.log(...) to avoid clobbering window.print
|
|
589
|
+
# Wrap each arg in ρσ_str() so type() and other objects display like Python
|
|
446
590
|
output.print('console.log')
|
|
447
591
|
output.with_parens(def():
|
|
448
592
|
for i, a in enumerate(self.args):
|
|
449
593
|
if i:
|
|
450
594
|
output.comma()
|
|
595
|
+
output.print('ρσ_str(')
|
|
451
596
|
a.print(output)
|
|
597
|
+
output.print(')')
|
|
452
598
|
)
|
|
453
599
|
elif (is_node_type(self.expression, AST_SymbolRef) and
|
|
454
600
|
self.expression.name is 'vars' and self.args.length is 0):
|
package/src/output/loops.pyj
CHANGED
|
@@ -119,7 +119,10 @@ def print_for_loop_body(output):
|
|
|
119
119
|
if not (self.simple_for_index or is_simple_for_in(self)):
|
|
120
120
|
# if we're using multiple iterators, unpack them
|
|
121
121
|
output.indent()
|
|
122
|
-
|
|
122
|
+
# `async for` always emits ES6+ `for await ... of` syntax (regardless
|
|
123
|
+
# of js_version), so the loop variable holds the value directly —
|
|
124
|
+
# not an integer index into an array.
|
|
125
|
+
if output.options.js_version is 5 and not self.is_async:
|
|
123
126
|
itervar = "ρσ_Iter" + output.index_counter + "[ρσ_Index" + output.index_counter + "]"
|
|
124
127
|
else:
|
|
125
128
|
itervar = "ρσ_Index" + output.index_counter
|
|
@@ -237,7 +240,19 @@ def print_for_in(self, output):
|
|
|
237
240
|
)
|
|
238
241
|
else:
|
|
239
242
|
# regular loop
|
|
240
|
-
if
|
|
243
|
+
if self.is_async:
|
|
244
|
+
# async for: emit native `for await ... of` regardless of js_version.
|
|
245
|
+
# Async iteration is an ES2018 feature; if the user wrote `async for`
|
|
246
|
+
# they have already opted into a modern runtime. The ρσ_Iterable
|
|
247
|
+
# bridge is skipped because async iterators don't expose .length
|
|
248
|
+
# or Map keys — the JS engine handles Symbol.asyncIterator natively.
|
|
249
|
+
itervar = "ρσ_Iter" + output.index_counter
|
|
250
|
+
output.assign("var " + itervar)
|
|
251
|
+
write_object()
|
|
252
|
+
output.end_statement()
|
|
253
|
+
output.indent()
|
|
254
|
+
output.spaced('for', 'await', '(var', 'ρσ_Index' + output.index_counter, 'of', itervar + ')')
|
|
255
|
+
elif output.options.js_version is 5:
|
|
241
256
|
output.assign("var ρσ_Iter" + output.index_counter)
|
|
242
257
|
output.print("ρσ_Iterable")
|
|
243
258
|
output.with_parens(write_object)
|
package/src/output/modules.pyj
CHANGED
|
@@ -94,7 +94,7 @@ def declare_exports(module_id, exports, output, docstrings):
|
|
|
94
94
|
def _inject_pythonize_strings(output):
|
|
95
95
|
str_funcs = ('capitalize strip lstrip rstrip islower isupper isspace lower upper swapcase title'
|
|
96
96
|
' center count endswith startswith find rfind index rindex format join ljust rjust'
|
|
97
|
-
' partition rpartition splitlines zfill').split(' ')
|
|
97
|
+
' partition rpartition replace splitlines zfill').split(' ')
|
|
98
98
|
output.newline()
|
|
99
99
|
output.indent()
|
|
100
100
|
output.print('(function(){var _f=' + JSON.stringify(str_funcs) + ';for(var _i=0;_i<_f.length;_i++)String.prototype[_f[_i]]=\u03c1\u03c3_str.prototype[_f[_i]];})()')
|
package/src/output/operators.pyj
CHANGED
|
@@ -321,6 +321,13 @@ def print_binary_op(self, output):
|
|
|
321
321
|
self.right.print(output)
|
|
322
322
|
output.print(')')
|
|
323
323
|
return
|
|
324
|
+
if self.operator is '%' and self.python_mod:
|
|
325
|
+
output.print('ρσ_op_mod_ns(')
|
|
326
|
+
self.left.print(output)
|
|
327
|
+
output.comma()
|
|
328
|
+
self.right.print(output)
|
|
329
|
+
output.print(')')
|
|
330
|
+
return
|
|
324
331
|
if function_ops[self.operator]:
|
|
325
332
|
output.print(function_ops[self.operator])
|
|
326
333
|
output.with_parens(def():
|
|
@@ -485,6 +492,14 @@ def print_assign(self, output):
|
|
|
485
492
|
self.right.print(output)
|
|
486
493
|
)
|
|
487
494
|
return
|
|
495
|
+
if self.operator is '%=' and self.python_mod:
|
|
496
|
+
output.assign(self.left)
|
|
497
|
+
output.print('ρσ_op_mod_ns(')
|
|
498
|
+
self.left.print(output)
|
|
499
|
+
output.comma()
|
|
500
|
+
self.right.print(output)
|
|
501
|
+
output.print(')')
|
|
502
|
+
return
|
|
488
503
|
if self.operator is '**=':
|
|
489
504
|
output.assign(self.left)
|
|
490
505
|
if output.options.js_version > 6:
|