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.
Files changed (88) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/PYTHON_GAPS.md +352 -0
  3. package/README.md +176 -32
  4. package/TODO.md +1 -128
  5. package/bin/rapydscript +70 -70
  6. package/language-service/index.js +242 -11
  7. package/memory/project_string_impl.md +43 -0
  8. package/package.json +1 -1
  9. package/release/baselib-plain-pretty.js +248 -38
  10. package/release/baselib-plain-ugly.js +8 -8
  11. package/release/compiler.js +778 -277
  12. package/release/signatures.json +30 -30
  13. package/src/ast.pyj +10 -1
  14. package/src/baselib-builtins.pyj +56 -2
  15. package/src/baselib-containers.pyj +25 -1
  16. package/src/baselib-errors.pyj +7 -3
  17. package/src/baselib-internal.pyj +51 -6
  18. package/src/baselib-str.pyj +18 -5
  19. package/src/lib/asyncio.pyj +534 -0
  20. package/src/lib/base64.pyj +399 -0
  21. package/src/lib/bisect.pyj +73 -0
  22. package/src/lib/collections.pyj +228 -4
  23. package/src/lib/csv.pyj +494 -0
  24. package/src/lib/heapq.pyj +98 -0
  25. package/src/lib/html.pyj +382 -0
  26. package/src/lib/http/__init__.pyj +98 -0
  27. package/src/lib/http/client.pyj +304 -0
  28. package/src/lib/http/cookies.pyj +236 -0
  29. package/src/lib/logging.pyj +672 -0
  30. package/src/lib/pprint.pyj +455 -0
  31. package/src/lib/pythonize.pyj +20 -20
  32. package/src/lib/statistics.pyj +0 -0
  33. package/src/lib/string.pyj +357 -0
  34. package/src/lib/textwrap.pyj +329 -0
  35. package/src/lib/urllib/__init__.pyj +14 -0
  36. package/src/lib/urllib/error.pyj +66 -0
  37. package/src/lib/urllib/parse.pyj +475 -0
  38. package/src/lib/urllib/request.pyj +86 -0
  39. package/src/monaco-language-service/analyzer.js +5 -2
  40. package/src/monaco-language-service/completions.js +26 -0
  41. package/src/monaco-language-service/diagnostics.js +203 -4
  42. package/src/monaco-language-service/scope.js +1 -0
  43. package/src/output/codegen.pyj +4 -1
  44. package/src/output/functions.pyj +152 -6
  45. package/src/output/loops.pyj +17 -2
  46. package/src/output/modules.pyj +1 -1
  47. package/src/output/operators.pyj +15 -0
  48. package/src/output/stream.pyj +0 -1
  49. package/src/parse.pyj +108 -24
  50. package/src/tokenizer.pyj +19 -3
  51. package/test/async_generators.pyj +144 -0
  52. package/test/asyncio.pyj +307 -0
  53. package/test/base64.pyj +202 -0
  54. package/test/baselib.pyj +23 -0
  55. package/test/bisect.pyj +178 -0
  56. package/test/chainmap.pyj +185 -0
  57. package/test/csv.pyj +405 -0
  58. package/test/float_special.pyj +64 -0
  59. package/test/heapq.pyj +174 -0
  60. package/test/html.pyj +212 -0
  61. package/test/http.pyj +259 -0
  62. package/test/imports.pyj +79 -72
  63. package/test/logging.pyj +356 -0
  64. package/test/long.pyj +130 -0
  65. package/test/parenthesized_with.pyj +141 -0
  66. package/test/pprint.pyj +232 -0
  67. package/test/python_compat.pyj +3 -5
  68. package/test/python_modulo.pyj +76 -0
  69. package/test/python_modulo_off.pyj +21 -0
  70. package/test/statistics.pyj +224 -0
  71. package/test/str.pyj +14 -0
  72. package/test/string.pyj +245 -0
  73. package/test/textwrap.pyj +172 -0
  74. package/test/type_display.pyj +48 -0
  75. package/test/type_enforcement.pyj +164 -0
  76. package/test/unit/index.js +94 -6
  77. package/test/unit/language-service-completions.js +121 -0
  78. package/test/unit/language-service-scope.js +32 -0
  79. package/test/unit/language-service.js +190 -5
  80. package/test/unit/run-language-service.js +17 -3
  81. package/test/unit/web-repl.js +2401 -13
  82. package/test/urllib.pyj +193 -0
  83. package/tools/compile.js +1 -1
  84. package/tools/embedded_compiler.js +7 -7
  85. package/tools/export.js +4 -2
  86. package/web-repl/main.js +1 -1
  87. package/web-repl/rapydscript.js +7 -5
  88. 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
- while True:
712
- if is_('eof'):
713
- unexpected()
714
- expr = expression()
715
- alias = None
716
- if is_('keyword', 'as'):
717
- next()
718
- alias = as_symbol(AST_SymbolAlias)
719
- clauses.push(new AST_WithClause({'expression':expr, 'alias':alias}))
720
- if is_('punc', ','):
721
- next()
722
- continue
723
- if not is_('punc', ':'):
724
- unexpected()
725
- break
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': new AST_Number({'start': start, 'value': val, 'end': tok}),
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
+ """