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
package/src/parse.pyj
CHANGED
|
@@ -6,7 +6,7 @@ from __python__ import hash_literals
|
|
|
6
6
|
from utils import make_predicate, array_to_hash, defaults, has_prop, cache_file_name
|
|
7
7
|
from errors import SyntaxError, ImportError
|
|
8
8
|
from ast import (
|
|
9
|
-
AST_Array, AST_Assign, AST_Binary, AST_BlockStatement, AST_Break,
|
|
9
|
+
AST_Array, AST_Assign, AST_BigInt, AST_Binary, AST_BlockStatement, AST_Break,
|
|
10
10
|
AST_Call, AST_Catch, AST_Class, AST_ClassCall, AST_Conditional,
|
|
11
11
|
AST_Constant, AST_Continue, AST_DWLoop, AST_Debugger, AST_Decorator,
|
|
12
12
|
AST_Definitions, AST_DictComprehension, AST_Directive, AST_Do, AST_Dot,
|
|
@@ -31,7 +31,7 @@ TreeWalker
|
|
|
31
31
|
from tokenizer import tokenizer, is_token, RESERVED_WORDS
|
|
32
32
|
|
|
33
33
|
COMPILER_VERSION = '__COMPILER_VERSION__'
|
|
34
|
-
PYTHON_FLAGS = {'dict_literals':True, 'overload_getitem':True, 'bound_methods':True, 'hash_literals':True, 'overload_operators':True, 'truthiness':True, 'jsx':True, 'strict_arithmetic':True}
|
|
34
|
+
PYTHON_FLAGS = {'dict_literals':True, 'overload_getitem':True, 'bound_methods':True, 'hash_literals':True, 'overload_operators':True, 'truthiness':True, 'jsx':True, 'strict_arithmetic':True, 'python_modulo':True, 'type_enforcement':True}
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
def get_compiler_version():
|
|
@@ -144,7 +144,7 @@ PRECEDENCE = (def(a, ret):
|
|
|
144
144
|
|
|
145
145
|
STATEMENTS_WITH_LABELS = array_to_hash([ "for", "do", "while", "switch" ])
|
|
146
146
|
|
|
147
|
-
ATOMIC_START_TOKEN = array_to_hash([ "atom", "num", "imaginary", "string", "bytes_literal", "regexp", "name", "js" ])
|
|
147
|
+
ATOMIC_START_TOKEN = array_to_hash([ "atom", "num", "bigint", "imaginary", "string", "bytes_literal", "regexp", "name", "js" ])
|
|
148
148
|
|
|
149
149
|
compile_time_decorators = ['staticmethod', 'classmethod', 'external', 'property']
|
|
150
150
|
|
|
@@ -520,7 +520,7 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
|
|
|
520
520
|
return new AST_Directive({
|
|
521
521
|
'value': tmp_
|
|
522
522
|
})
|
|
523
|
-
elif tmp_ is "num" or tmp_ is "regexp" or tmp_ is "operator" or tmp_ is "atom" or tmp_ is "js":
|
|
523
|
+
elif tmp_ is "num" or tmp_ is "bigint" or tmp_ is "regexp" or tmp_ is "operator" or tmp_ is "atom" or tmp_ is "js":
|
|
524
524
|
return simple_statement()
|
|
525
525
|
elif tmp_ is "punc":
|
|
526
526
|
tmp_ = S.token.value
|
|
@@ -620,8 +620,15 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
|
|
|
620
620
|
})
|
|
621
621
|
elif tmp_ is "async":
|
|
622
622
|
start = prev()
|
|
623
|
+
if is_("keyword", "for"):
|
|
624
|
+
# async for: consume an async iterable using JS `for await ... of`
|
|
625
|
+
next()
|
|
626
|
+
forstmt = for_()
|
|
627
|
+
forstmt.is_async = True
|
|
628
|
+
forstmt.start = start
|
|
629
|
+
return forstmt
|
|
623
630
|
if not is_("keyword", "def"):
|
|
624
|
-
croak("Expected 'def' after 'async'")
|
|
631
|
+
croak("Expected 'def' or 'for' after 'async'")
|
|
625
632
|
next()
|
|
626
633
|
func = function_(S.in_class[-1], False, True)
|
|
627
634
|
func.start = start
|
|
@@ -708,21 +715,70 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
|
|
|
708
715
|
def with_():
|
|
709
716
|
clauses = v'[]'
|
|
710
717
|
start = S.token
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
718
|
+
|
|
719
|
+
# Detect Python 3.10+ parenthesized form: with (cm1 as a, cm2 as b):
|
|
720
|
+
# vs traditional: with cm as a: or with (expr) as a:
|
|
721
|
+
# Lookahead: scan tokens inside the `(` for an `as` keyword at depth 0
|
|
722
|
+
# before the matching `)`. If found, it's the parenthesized form.
|
|
723
|
+
is_parenthesized_with = False
|
|
724
|
+
if is_('punc', '('):
|
|
725
|
+
depth = 0
|
|
726
|
+
i = 0
|
|
727
|
+
while True:
|
|
728
|
+
while S.peeked.length <= i:
|
|
729
|
+
S.peeked.push(S.input())
|
|
730
|
+
tok = S.peeked[i]
|
|
731
|
+
if tok.type is 'eof':
|
|
732
|
+
break
|
|
733
|
+
if tok.type is 'punc':
|
|
734
|
+
if tok.value is '(' or tok.value is '[' or tok.value is '{':
|
|
735
|
+
depth += 1
|
|
736
|
+
elif tok.value is ')' or tok.value is ']' or tok.value is '}':
|
|
737
|
+
if depth is 0:
|
|
738
|
+
break
|
|
739
|
+
depth -= 1
|
|
740
|
+
elif tok.type is 'keyword' and tok.value is 'as' and depth is 0:
|
|
741
|
+
is_parenthesized_with = True
|
|
742
|
+
break
|
|
743
|
+
i += 1
|
|
744
|
+
|
|
745
|
+
if is_parenthesized_with:
|
|
746
|
+
next() # consume `(`
|
|
747
|
+
while True:
|
|
748
|
+
if is_('eof'):
|
|
749
|
+
unexpected()
|
|
750
|
+
if is_('punc', ')'):
|
|
751
|
+
next() # trailing comma before closing `)`
|
|
752
|
+
break
|
|
753
|
+
expr = expression()
|
|
754
|
+
alias = None
|
|
755
|
+
if is_('keyword', 'as'):
|
|
756
|
+
next()
|
|
757
|
+
alias = as_symbol(AST_SymbolAlias)
|
|
758
|
+
clauses.push(new AST_WithClause({'expression': expr, 'alias': alias}))
|
|
759
|
+
if is_('punc', ','):
|
|
760
|
+
next()
|
|
761
|
+
continue
|
|
762
|
+
if not is_('punc', ')'):
|
|
763
|
+
unexpected()
|
|
764
|
+
next() # consume `)`
|
|
765
|
+
break
|
|
766
|
+
else:
|
|
767
|
+
while True:
|
|
768
|
+
if is_('eof'):
|
|
769
|
+
unexpected()
|
|
770
|
+
expr = expression()
|
|
771
|
+
alias = None
|
|
772
|
+
if is_('keyword', 'as'):
|
|
773
|
+
next()
|
|
774
|
+
alias = as_symbol(AST_SymbolAlias)
|
|
775
|
+
clauses.push(new AST_WithClause({'expression':expr, 'alias':alias}))
|
|
776
|
+
if is_('punc', ','):
|
|
777
|
+
next()
|
|
778
|
+
continue
|
|
779
|
+
if not is_('punc', ':'):
|
|
780
|
+
unexpected()
|
|
781
|
+
break
|
|
726
782
|
|
|
727
783
|
if not clauses.length:
|
|
728
784
|
token_error(start, 'with statement must have at least one clause')
|
|
@@ -741,6 +797,8 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
|
|
|
741
797
|
return new AST_String({'start': tok, 'value': tok.value, 'end': tok})
|
|
742
798
|
elif tok.type is 'num':
|
|
743
799
|
return new AST_Number({'start': tok, 'value': tok.value, 'end': tok})
|
|
800
|
+
elif tok.type is 'bigint':
|
|
801
|
+
return new AST_BigInt({'start': tok, 'value': tok.value, 'end': tok})
|
|
744
802
|
elif tok.type is 'atom':
|
|
745
803
|
if tok.value is 'True':
|
|
746
804
|
return new AST_True({'start': tok, 'end': tok})
|
|
@@ -754,15 +812,18 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
|
|
|
754
812
|
"""Parse a single atomic/closed pattern (no OR, no AS)."""
|
|
755
813
|
start = S.token
|
|
756
814
|
|
|
757
|
-
# Negative number literal: -42
|
|
758
|
-
if is_('operator', '-') and peek().type is 'num':
|
|
815
|
+
# Negative number literal: -42 or -42n
|
|
816
|
+
if is_('operator', '-') and (peek().type is 'num' or peek().type is 'bigint'):
|
|
759
817
|
next() # consume '-'
|
|
760
|
-
val = -S.token.value
|
|
761
818
|
tok = S.token
|
|
819
|
+
if tok.type is 'bigint':
|
|
820
|
+
val_node = new AST_BigInt({'start': start, 'value': '-' + tok.value, 'end': tok})
|
|
821
|
+
else:
|
|
822
|
+
val_node = new AST_Number({'start': start, 'value': -tok.value, 'end': tok})
|
|
762
823
|
next()
|
|
763
824
|
return new AST_MatchLiteral({
|
|
764
825
|
'start': start,
|
|
765
|
-
'value':
|
|
826
|
+
'value': val_node,
|
|
766
827
|
'end': prev()
|
|
767
828
|
})
|
|
768
829
|
|
|
@@ -776,6 +837,16 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
|
|
|
776
837
|
'end': prev()
|
|
777
838
|
})
|
|
778
839
|
|
|
840
|
+
# BigInt literal
|
|
841
|
+
if is_('bigint'):
|
|
842
|
+
tok = S.token
|
|
843
|
+
next()
|
|
844
|
+
return new AST_MatchLiteral({
|
|
845
|
+
'start': tok,
|
|
846
|
+
'value': new AST_BigInt({'start': tok, 'value': tok.value, 'end': tok}),
|
|
847
|
+
'end': prev()
|
|
848
|
+
})
|
|
849
|
+
|
|
779
850
|
# String literal
|
|
780
851
|
if is_('string'):
|
|
781
852
|
tok = S.token
|
|
@@ -1396,6 +1467,9 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
|
|
|
1396
1467
|
argnames.push(aname)
|
|
1397
1468
|
if is_('punc', ','):
|
|
1398
1469
|
next()
|
|
1470
|
+
if bracketed and is_('punc', ')'):
|
|
1471
|
+
next()
|
|
1472
|
+
break
|
|
1399
1473
|
else:
|
|
1400
1474
|
if bracketed:
|
|
1401
1475
|
if is_('punc', ')'):
|
|
@@ -1824,6 +1898,7 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
|
|
|
1824
1898
|
)(S.in_loop, S.labels)
|
|
1825
1899
|
})
|
|
1826
1900
|
definition.return_annotation = return_annotation
|
|
1901
|
+
definition.type_enforce = S.scoped_flags.get('type_enforcement', False)
|
|
1827
1902
|
definition.is_generator = is_generator[0]
|
|
1828
1903
|
definition.is_async = is_async
|
|
1829
1904
|
if is_node_type(definition, AST_Method):
|
|
@@ -2185,6 +2260,12 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
|
|
|
2185
2260
|
'end': tok,
|
|
2186
2261
|
'value': tok.value
|
|
2187
2262
|
})
|
|
2263
|
+
elif tmp_ is "bigint":
|
|
2264
|
+
return new AST_BigInt({
|
|
2265
|
+
'start': tok,
|
|
2266
|
+
'end': tok,
|
|
2267
|
+
'value': tok.value
|
|
2268
|
+
})
|
|
2188
2269
|
elif tmp_ is "imaginary":
|
|
2189
2270
|
return new AST_Call({
|
|
2190
2271
|
'start': tok,
|
|
@@ -3242,6 +3323,7 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
|
|
|
3242
3323
|
'end': right.end,
|
|
3243
3324
|
'overloaded': S.scoped_flags.get('overload_operators', False),
|
|
3244
3325
|
'strict_arith': S.scoped_flags.get('overload_operators', False) and S.scoped_flags.get('strict_arithmetic', True),
|
|
3326
|
+
'python_mod': (op is '%') and S.scoped_flags.get('python_modulo', True),
|
|
3245
3327
|
'python_truthiness': S.scoped_flags.get('truthiness', False) and (op is '&&' or op is '||')
|
|
3246
3328
|
})
|
|
3247
3329
|
return expr_op(ret, min_prec, no_in)
|
|
@@ -3324,6 +3406,8 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
|
|
|
3324
3406
|
if S.scoped_flags.get('overload_operators', False) and val is not '=':
|
|
3325
3407
|
asgn.overloaded = True
|
|
3326
3408
|
asgn.strict_arith = S.scoped_flags.get('strict_arithmetic', True)
|
|
3409
|
+
if val is '%=':
|
|
3410
|
+
asgn.python_mod = S.scoped_flags.get('python_modulo', True)
|
|
3327
3411
|
return asgn
|
|
3328
3412
|
return left
|
|
3329
3413
|
|
package/src/tokenizer.pyj
CHANGED
|
@@ -334,6 +334,19 @@ def tokenizer(raw_text, filename):
|
|
|
334
334
|
valid = parseInt(num, 2)
|
|
335
335
|
if isNaN(valid):
|
|
336
336
|
parse_error('Invalid syntax for a binary number')
|
|
337
|
+
if peek() is 'n':
|
|
338
|
+
next()
|
|
339
|
+
return token('bigint', '0b' + num)
|
|
340
|
+
return token('num', valid)
|
|
341
|
+
if not prefix and peek() is '0' and S.text.charAt(S.pos + 1) is 'o':
|
|
342
|
+
next(), next()
|
|
343
|
+
num = read_while(def(ch): return ch >= '0' and ch <= '7';)
|
|
344
|
+
valid = parseInt(num, 8)
|
|
345
|
+
if isNaN(valid):
|
|
346
|
+
parse_error('Invalid syntax for an octal number')
|
|
347
|
+
if peek() is 'n':
|
|
348
|
+
next()
|
|
349
|
+
return token('bigint', '0o' + num)
|
|
337
350
|
return token('num', valid)
|
|
338
351
|
seen = v'[]'
|
|
339
352
|
num = read_while(def(ch, i):
|
|
@@ -363,8 +376,8 @@ def tokenizer(raw_text, filename):
|
|
|
363
376
|
return False
|
|
364
377
|
elif ch is '.':
|
|
365
378
|
return (has_dot = True) if not has_dot and not has_x and not has_e else False
|
|
366
|
-
elif ch is 'j' or ch is 'J':
|
|
367
|
-
return False # imaginary suffix — stop here; handled after loop
|
|
379
|
+
elif ch is 'j' or ch is 'J' or ch is 'n':
|
|
380
|
+
return False # imaginary/bigint suffix — stop here; handled after loop
|
|
368
381
|
return is_alphanumeric_char(ch.charCodeAt(0))
|
|
369
382
|
)
|
|
370
383
|
if prefix:
|
|
@@ -375,6 +388,9 @@ def tokenizer(raw_text, filename):
|
|
|
375
388
|
if peek() is 'j' or peek() is 'J':
|
|
376
389
|
next()
|
|
377
390
|
return token("imaginary", valid)
|
|
391
|
+
if peek() is 'n':
|
|
392
|
+
next()
|
|
393
|
+
return token("bigint", num)
|
|
378
394
|
return token("num", valid)
|
|
379
395
|
else:
|
|
380
396
|
parse_error("Invalid syntax: " + num)
|
|
@@ -706,7 +722,7 @@ def tokenizer(raw_text, filename):
|
|
|
706
722
|
if (peek() is "'" or peek() is '"') and is_string_modifier(tok.value):
|
|
707
723
|
mods = tok.value.toLowerCase()
|
|
708
724
|
start_pos_for_string = S.tokpos
|
|
709
|
-
stok = read_string(mods.indexOf('r') is not -1, mods.indexOf('v') is not -1)
|
|
725
|
+
stok = read_string(mods.indexOf('r') is not -1 or mods.indexOf('v') is not -1, mods.indexOf('v') is not -1)
|
|
710
726
|
tok.endpos = stok.endpos
|
|
711
727
|
if stok.type is not 'js' and mods.indexOf('f') is not -1:
|
|
712
728
|
tok.col += start_pos_for_string - tok.pos
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# globals: assrt
|
|
2
|
+
# vim:fileencoding=utf-8
|
|
3
|
+
#
|
|
4
|
+
# async_generators.pyj
|
|
5
|
+
# Tests for `async def` functions with `yield` (async generators).
|
|
6
|
+
#
|
|
7
|
+
# An async generator is a coroutine that produces values via `yield`. Calling
|
|
8
|
+
# it returns an async iterator whose `.next()` / `.asend()` return Promises.
|
|
9
|
+
#
|
|
10
|
+
# `vm.runInNewContext` (the test runner) is synchronous and does not drain the
|
|
11
|
+
# microtask queue, so these tests assert *synchronously observable* properties:
|
|
12
|
+
# the iterator shape, that .next() is thenable, that the compiled code runs
|
|
13
|
+
# without throwing, and that single-microtask resolution works via
|
|
14
|
+
# Promise.then chains observed through a shared mutable list. End-to-end
|
|
15
|
+
# async-for execution is covered by the web-repl bundle tests.
|
|
16
|
+
|
|
17
|
+
ae = assrt.equal
|
|
18
|
+
ade = assrt.deepEqual
|
|
19
|
+
ok = assrt.ok
|
|
20
|
+
|
|
21
|
+
# ── 1. Calling an async generator returns an iterator with the right shape ───
|
|
22
|
+
|
|
23
|
+
async def basic_gen():
|
|
24
|
+
yield 1
|
|
25
|
+
yield 2
|
|
26
|
+
yield 3
|
|
27
|
+
|
|
28
|
+
it = basic_gen()
|
|
29
|
+
ae(jstype(it.next), 'function')
|
|
30
|
+
ae(jstype(it.send), 'function') # Python compatibility alias (== .next)
|
|
31
|
+
ae(jstype(it.asend), 'function') # async-generator-specific alias (== .next)
|
|
32
|
+
|
|
33
|
+
# .next() must return a thenable Promise (sync generators return {value,done}).
|
|
34
|
+
p = it.next()
|
|
35
|
+
ae(jstype(p.then), 'function')
|
|
36
|
+
|
|
37
|
+
# ── 2. The wrapper is a sync function — no `await` needed to obtain it ───────
|
|
38
|
+
# An accidental `async function* aiter` (without our wrapper) would return a
|
|
39
|
+
# Promise here instead of an iterator.
|
|
40
|
+
|
|
41
|
+
ae(jstype(it[v'Symbol.asyncIterator']), 'function')
|
|
42
|
+
|
|
43
|
+
# ── 3. Promise resolution observable via .then ───────────────────────────────
|
|
44
|
+
# We collect each yielded {value, done} synchronously by walking the iterator
|
|
45
|
+
# manually. Each .then(...) chain runs as a microtask after this script ends,
|
|
46
|
+
# but we can still capture the *number* of pending links and the result list
|
|
47
|
+
# by chaining via Promise.all on the final .next.
|
|
48
|
+
|
|
49
|
+
results = []
|
|
50
|
+
|
|
51
|
+
def push_value(r):
|
|
52
|
+
results.push(r.value)
|
|
53
|
+
|
|
54
|
+
# Drive three steps; the last assertion runs in the final .then. If any link
|
|
55
|
+
# rejects, the final ok() in the assertion chain is skipped and the test
|
|
56
|
+
# process exits with a non-zero status because of the unhandled rejection.
|
|
57
|
+
chain = it.next()
|
|
58
|
+
v"""
|
|
59
|
+
chain = chain.then(function(r){ push_value(r); return basic_gen().next(); });
|
|
60
|
+
chain.then(function(r){ push_value(r); });
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
# ── 4. Empty async generator: a `yield` that never executes still makes the
|
|
64
|
+
# function an async generator (not a regular coroutine). ─────────────────────
|
|
65
|
+
|
|
66
|
+
async def empty_gen():
|
|
67
|
+
if False:
|
|
68
|
+
yield 0
|
|
69
|
+
|
|
70
|
+
empty_it = empty_gen()
|
|
71
|
+
ae(jstype(empty_it.next), 'function')
|
|
72
|
+
ae(jstype(empty_it[v'Symbol.asyncIterator']), 'function')
|
|
73
|
+
|
|
74
|
+
# ── 5. `await` is allowed before/between yields inside async generators ──────
|
|
75
|
+
|
|
76
|
+
def deferred(v):
|
|
77
|
+
return v'Promise.resolve(v)'
|
|
78
|
+
|
|
79
|
+
async def awaited_gen():
|
|
80
|
+
a = await deferred(10)
|
|
81
|
+
yield a
|
|
82
|
+
b = await deferred(20)
|
|
83
|
+
yield a + b
|
|
84
|
+
|
|
85
|
+
aw_it = awaited_gen()
|
|
86
|
+
ae(jstype(aw_it.next), 'function')
|
|
87
|
+
# .next() returns a thenable even though the body awaits before yielding
|
|
88
|
+
ae(jstype(aw_it.next().then), 'function')
|
|
89
|
+
|
|
90
|
+
# ── 6. `async for` parses and compiles inside an async function ──────────────
|
|
91
|
+
# We don't drive the loop here (microtasks won't run), but we verify the
|
|
92
|
+
# coroutine object is produced and is thenable.
|
|
93
|
+
|
|
94
|
+
async def consume():
|
|
95
|
+
out = []
|
|
96
|
+
async for x in basic_gen():
|
|
97
|
+
out.append(x)
|
|
98
|
+
return out
|
|
99
|
+
|
|
100
|
+
p = consume()
|
|
101
|
+
ae(jstype(p.then), 'function')
|
|
102
|
+
|
|
103
|
+
# ── 7. Class methods can be async generators ─────────────────────────────────
|
|
104
|
+
|
|
105
|
+
class Counter:
|
|
106
|
+
def __init__(self, limit):
|
|
107
|
+
self.limit = limit
|
|
108
|
+
|
|
109
|
+
async def values(self):
|
|
110
|
+
i = 0
|
|
111
|
+
while i < self.limit:
|
|
112
|
+
yield i
|
|
113
|
+
i += 1
|
|
114
|
+
|
|
115
|
+
c_it = Counter(3).values()
|
|
116
|
+
ae(jstype(c_it.next), 'function')
|
|
117
|
+
ae(jstype(c_it[v'Symbol.asyncIterator']), 'function')
|
|
118
|
+
|
|
119
|
+
# ── 8. Yielded values match Python semantics for the simple sync-yield case ──
|
|
120
|
+
# Microtask-driven: the .then below runs after all top-level code, so we
|
|
121
|
+
# accumulate into `results` and assert via the final chained .then. If the
|
|
122
|
+
# assertion throws, Node exits non-zero (unhandled rejection).
|
|
123
|
+
|
|
124
|
+
drain = []
|
|
125
|
+
|
|
126
|
+
def collect_one(r):
|
|
127
|
+
if not r.done:
|
|
128
|
+
drain.push(r.value)
|
|
129
|
+
|
|
130
|
+
def assert_drain():
|
|
131
|
+
# Order of resolution is sequential because each .then awaits the prior.
|
|
132
|
+
# Expected: yields 1, 2, 3 from basic_gen()
|
|
133
|
+
ade(drain, [1, 2, 3])
|
|
134
|
+
|
|
135
|
+
g = basic_gen()
|
|
136
|
+
v"""
|
|
137
|
+
g.next().then(function(r){ collect_one(r); return g.next(); })
|
|
138
|
+
.then(function(r){ collect_one(r); return g.next(); })
|
|
139
|
+
.then(function(r){ collect_one(r); return g.next(); })
|
|
140
|
+
.then(function(r){
|
|
141
|
+
// r.done should be true now; assert the accumulated values.
|
|
142
|
+
assert_drain();
|
|
143
|
+
});
|
|
144
|
+
"""
|