rapydscript-ns 0.9.1 → 0.9.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.
Files changed (82) hide show
  1. package/CHANGELOG.md +22 -1
  2. package/PYTHON_GAPS.md +420 -0
  3. package/README.md +154 -30
  4. package/TODO.md +22 -7
  5. package/language-service/index.js +241 -12
  6. package/language-service/language-service.d.ts +1 -1
  7. package/memory/project_string_impl.md +43 -0
  8. package/package.json +6 -2
  9. package/release/baselib-plain-pretty.js +248 -38
  10. package/release/baselib-plain-ugly.js +8 -8
  11. package/release/compiler.js +821 -305
  12. package/release/signatures.json +15 -15
  13. package/src/ast.pyj +4 -1
  14. package/src/baselib-builtins.pyj +56 -2
  15. package/src/baselib-containers.pyj +2 -0
  16. package/src/baselib-errors.pyj +7 -3
  17. package/src/baselib-internal.pyj +51 -6
  18. package/src/baselib-str.pyj +5 -3
  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 +1 -1
  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/pythonize.pyj +1 -1
  31. package/src/lib/string.pyj +357 -0
  32. package/src/lib/textwrap.pyj +329 -0
  33. package/src/lib/urllib/__init__.pyj +14 -0
  34. package/src/lib/urllib/error.pyj +66 -0
  35. package/src/lib/urllib/parse.pyj +475 -0
  36. package/src/lib/urllib/request.pyj +86 -0
  37. package/src/monaco-language-service/analyzer.js +5 -2
  38. package/src/monaco-language-service/completions.js +26 -0
  39. package/src/monaco-language-service/diagnostics.js +204 -5
  40. package/src/monaco-language-service/index.js +2 -2
  41. package/src/monaco-language-service/scope.js +1 -0
  42. package/src/output/functions.pyj +152 -6
  43. package/src/output/loops.pyj +26 -2
  44. package/src/output/modules.pyj +1 -1
  45. package/src/output/operators.pyj +15 -0
  46. package/src/output/stream.pyj +0 -1
  47. package/src/parse.pyj +80 -17
  48. package/src/tokenizer.pyj +1 -1
  49. package/test/async_generators.pyj +144 -0
  50. package/test/asyncio.pyj +307 -0
  51. package/test/base64.pyj +202 -0
  52. package/test/bisect.pyj +178 -0
  53. package/test/csv.pyj +405 -0
  54. package/test/float_special.pyj +64 -0
  55. package/test/heapq.pyj +174 -0
  56. package/test/html.pyj +212 -0
  57. package/test/http.pyj +259 -0
  58. package/test/imports.pyj +7 -0
  59. package/test/logging.pyj +356 -0
  60. package/test/long.pyj +130 -0
  61. package/test/parenthesized_with.pyj +141 -0
  62. package/test/python_compat.pyj +3 -5
  63. package/test/python_modulo.pyj +76 -0
  64. package/test/python_modulo_off.pyj +21 -0
  65. package/test/str.pyj +14 -0
  66. package/test/string.pyj +245 -0
  67. package/test/textwrap.pyj +172 -0
  68. package/test/type_display.pyj +48 -0
  69. package/test/type_enforcement.pyj +164 -0
  70. package/test/unit/index.js +80 -6
  71. package/test/unit/language-service-completions.js +119 -0
  72. package/test/unit/language-service-scope.js +32 -0
  73. package/test/unit/language-service.js +128 -4
  74. package/test/unit/run-language-service.js +17 -3
  75. package/test/unit/web-repl.js +2094 -29
  76. package/test/urllib.pyj +193 -0
  77. package/tools/compile.js +1 -1
  78. package/tools/compiler.d.ts +367 -0
  79. package/tools/embedded_compiler.js +7 -7
  80. package/web-repl/main.js +1 -1
  81. package/web-repl/rapydscript.js +3 -3
  82. package/test/omit_function_metadata.pyj +0 -20
package/README.md CHANGED
@@ -592,30 +592,64 @@ RapydScript supports Python's ``/`` and ``*`` parameter separators:
592
592
  greet("Bob", greeting="Hi") # Hi, Bob!
593
593
  greet("Carol", punctuation=".") # Hello, Carol.
594
594
  greet("Dave", greeting="Hey", punctuation="?") # Hey, Dave?
595
-
596
- # name is positional-only: greet(name="Alice") would silently ignore the kwarg
597
- # punctuation is keyword-only: must be passed as punctuation="."
598
595
  ```
599
596
 
600
597
  The two separators can be combined, and each section can have its own default
601
598
  values. All combinations supported by Python 3.8+ are accepted.
602
599
 
603
- RapydScript is lenient: passing a positional-only parameter by keyword will not
604
- raise a ``TypeError`` at runtime (the named value is silently ignored), and
605
- passing a keyword-only parameter positionally will not raise an error either.
606
- This is consistent with RapydScript's general approach of favouring
607
- interoperability over strict enforcement.
600
+ By default RapydScript is lenient about argument conventions (to favour
601
+ JavaScript interoperability). Enable strict Python-style enforcement with the
602
+ ``type_enforcement`` flag described below.
608
603
 
609
604
  The Monaco language service correctly shows ``/`` and ``*`` separators in
610
605
  signature help and hover tooltips.
611
606
 
612
- One difference between RapydScript and Python is that RapydScript is not as
613
- strict as Python when it comes to validating function arguments. This is both
614
- for performance and to make it easier to interoperate with other JavaScript
615
- libraries. So if you do not pass enough arguments when calling a function, the
616
- extra arguments will be set to undefined instead of raising a TypeError, as in
617
- Python. Similarly, when mixing ``*args`` and optional arguments, RapydScript
618
- will not complain if an optional argument is specified twice.
607
+ ### Argument type enforcement
608
+
609
+ ``from __python__ import type_enforcement`` activates Python-compatible runtime
610
+ argument checking for every function defined in that scope:
611
+
612
+ | Check | Behaviour |
613
+ |---|---|
614
+ | Too many positional arguments | ``TypeError`` |
615
+ | Missing required positional argument | ``TypeError`` |
616
+ | Positional-only arg passed as a keyword argument | ``TypeError`` |
617
+ | Missing required keyword-only argument | ``TypeError`` |
618
+ | Argument value does not match its type annotation | ``TypeError`` |
619
+
620
+ ```py
621
+ from __python__ import type_enforcement
622
+
623
+ def greet(name, /, greeting="Hello", *, punctuation="!"):
624
+ return greeting + ", " + name + punctuation
625
+
626
+ greet("Alice") # Hello, Alice!
627
+ greet("Bob", greeting="Hi") # Hi, Bob!
628
+ greet("Carol", punctuation=".") # Hello, Carol.
629
+
630
+ greet(name="Dave") # TypeError: positional-only arg 'name' passed as kwarg
631
+ greet("Eve") # TypeError: missing required keyword-only argument 'punctuation'
632
+ # (only when punctuation has no default)
633
+
634
+ def add(a: int, b: int) -> int:
635
+ return a + b
636
+
637
+ add(1, 2) # 3
638
+ add("x", 2) # TypeError: argument 'a' must be int
639
+ ```
640
+
641
+ The flag applies to the function definitions that follow it in scope — it does
642
+ not retroactively affect imported functions or functions defined before the
643
+ import. It can be combined with any other ``from __python__ import`` flag.
644
+
645
+ The Monaco language service also reports violations **statically** for
646
+ calls to locally-defined enforced functions, underlining the problem at the
647
+ call site with an error marker.
648
+
649
+ One difference compared to un-enforced RapydScript: without
650
+ ``type_enforcement``, missing arguments become ``undefined`` (JavaScript
651
+ default) instead of raising ``TypeError``, and positional/keyword-only
652
+ conventions are not checked.
619
653
 
620
654
  When creating callbacks to pass to other JavaScript libraries, it is often the
621
655
  case that the external library expects a function that receives an *options
@@ -1343,6 +1377,49 @@ ba.clear() # remove all bytes
1343
1377
  ba += bytearray([7, 8]) # in-place concatenation
1344
1378
  ```
1345
1379
 
1380
+ ### `long` — arbitrary-precision integers
1381
+
1382
+ RapydScript provides a `long` builtin backed by JavaScript's native `BigInt`,
1383
+ giving you arbitrary-precision integers beyond the safe range of JS `Number`.
1384
+
1385
+ ```python
1386
+ a = long(10)
1387
+ b = long(3)
1388
+
1389
+ a + b # long(13)
1390
+ a - b # long(7)
1391
+ a * b # long(30)
1392
+ a // b # long(3) — floor division (Python semantics, not JS truncation)
1393
+ a % b # long(1) — Python-style modulo (result has same sign as divisor)
1394
+ a ** b # long(1000)
1395
+ long(-7) // long(2) # long(-4) — floors toward −∞ (JS BigInt would give −3)
1396
+ long(-7) % long(3) # long(2) — result matches sign of divisor
1397
+ ```
1398
+
1399
+ #### Precision beyond JS Number
1400
+
1401
+ The main motivation for `long` is numbers outside the safe integer range of
1402
+ JavaScript `Number` (`2^53 − 1 = 9007199254740991`):
1403
+
1404
+ ```python
1405
+ n = long('9007199254740993') # 2^53 + 1 — cannot be represented as a JS Number
1406
+ str(n) # '9007199254740993' (exact)
1407
+ str(n + long(1)) # '9007199254740994' (still exact)
1408
+ ```
1409
+
1410
+ #### Operator overloading
1411
+
1412
+ `//`, `%`, and `**` require `overload_operators` (on by default) to emit the
1413
+ BigInt-aware helpers rather than the raw JS operators. If you have explicitly
1414
+ disabled operator overloading with `from __python__ import no_overload_operators`,
1415
+ `//` will call `Math.floor(a / b)` which throws a `TypeError` at runtime for
1416
+ `long` values.
1417
+
1418
+ #### Browser / environment support
1419
+
1420
+ `BigInt` is supported in all modern browsers (Chrome 67+, Firefox 68+, Safari
1421
+ 14+, Node.js 10.3+). Code using `long` will not run in Internet Explorer.
1422
+
1346
1423
  ### `issubclass`
1347
1424
 
1348
1425
  `issubclass(cls, classinfo)` checks whether a class is a subclass of another
@@ -3046,6 +3123,34 @@ By default, generators are down-converted to ES 5 switch statements. Pass
3046
3123
  compiler options) to emit native ES 6 generator functions instead, which are
3047
3124
  smaller and faster.
3048
3125
 
3126
+ ### Async generators (`async def` with `yield`)
3127
+
3128
+ Async generators — `async def` functions that contain `yield` — are also
3129
+ supported, with the same syntax as Python:
3130
+
3131
+ ```py
3132
+ async def stream(urls):
3133
+ for url in urls:
3134
+ body = await fetch(url)
3135
+ yield body
3136
+
3137
+ async def main():
3138
+ async for body in stream(['/a', '/b']):
3139
+ print(body)
3140
+ ```
3141
+
3142
+ Calling an async-generator function returns an async iterator immediately
3143
+ (not a `Promise`). Each `.next()` call returns a `Promise` that resolves to a
3144
+ `{value, done}` record, matching the JS async-iterator protocol. Manual
3145
+ iteration via `await it.next()` and `await it.asend()` works (`.asend` is
3146
+ aliased to `.next`, mirroring Python's async-generator API). Whole iterators
3147
+ are best consumed with `async for x in iter:`, which compiles to JS
3148
+ `for await ... of`.
3149
+
3150
+ `async for` is an ES2018 feature, so it is always emitted in modern form
3151
+ regardless of `--js-version`. The runtime must support `Symbol.asyncIterator`
3152
+ (Node 10+, Chrome 63+, Firefox 57+, Safari 11.1+).
3153
+
3049
3154
  Modules
3050
3155
  -------
3051
3156
 
@@ -3059,6 +3164,16 @@ You can import things from modules, just like you would in python:
3059
3164
  from mypackage.mymodule import something, something_else
3060
3165
  ```
3061
3166
 
3167
+ Multi-line parenthesized imports (Python 3.10+ style) are fully supported, including trailing commas:
3168
+
3169
+ ```py
3170
+ from mypackage.mymodule import (
3171
+ something,
3172
+ something_else as alias,
3173
+ another_thing,
3174
+ )
3175
+ ```
3176
+
3062
3177
  When you import modules, the RapydScript compiler automatically generates a
3063
3178
  single large JavaScript file containing all the imported packages/modules and
3064
3179
  their dependencies, recursively. This makes it very easy to integrate the
@@ -3349,6 +3464,7 @@ One of Python's main strengths is the number of libraries available to the devel
3349
3464
  # groupby, islice, pairwise, starmap, takewhile, zip_longest,
3350
3465
  # product, permutations, combinations, combinations_with_replacement
3351
3466
  io # StringIO (in-memory text stream), BytesIO (in-memory binary stream)
3467
+ base64 # b64encode/decode, urlsafe_b64encode/decode, b32encode/decode, b16encode/decode, encodebytes/decodebytes
3352
3468
 
3353
3469
  For the most part, the logic implemented in these libraries functions identically to the Python versions. I'd be happy to include more libraries, if other members of the community want them. However, unlike most other Python-to-JavaScript compilers, RapydScript doesn't need them to be complete since there are already tons of available JavaScript libraries that it can use natively.
3354
3470
 
@@ -3618,6 +3734,7 @@ with no flags enabled, pass ``--legacy-rapydscript`` on the command line.
3618
3734
  | `overload_getitem` | `obj[key]` dispatches to `__getitem__` / `__setitem__` / `__delitem__` on objects that define them. On by default. |
3619
3735
  | `overload_operators` | Arithmetic and bitwise operators (`+`, `-`, `*`, `/`, `//`, `%`, `**`, `&`, `\|`, `^`, `<<`, `>>`) dispatch to dunder methods (`__add__`, `__sub__`, etc.) and their reflected variants. Unary `-`/`+`/`~` dispatch to `__neg__`/`__pos__`/`__invert__`. On by default. |
3620
3736
  | `strict_arithmetic` | When `overload_operators` is active, incompatible operand types (e.g. `int + str`) raise `TypeError` instead of silently coercing as JavaScript would. On by default; disable with `from __python__ import no_strict_arithmetic` to revert to JavaScript coercion behaviour. Internal RapydScript library code is unaffected. |
3737
+ | `python_modulo` | The `%` operator follows Python semantics: the result takes the sign of the divisor (`-7 % 3 == 2`, `7 % -3 == -2`). On by default. Custom `__mod__` / `__rmod__` are dispatched even without `overload_operators`. Disable with `from __python__ import no_python_modulo` to revert to raw JS remainder. Has no effect when `overload_operators` is active (the operator dispatch path always uses Python semantics). |
3621
3738
  | `truthiness` | Boolean tests and `bool()` dispatch to `__bool__` and treat empty containers as falsy, matching Python semantics. On by default. |
3622
3739
  | `bound_methods` | Method references (`obj.method`) are automatically bound to their object, so they can be passed as callbacks without losing `self`. On by default. |
3623
3740
  | `hash_literals` | `{k: v}` creates a Python `dict` (alias for `dict_literals`; kept for backward compatibility). On by default. |
@@ -3688,7 +3805,7 @@ when the editor is torn down.
3688
3805
  | `stdlibFiles` | `{name: source}` | `{}` | Like `virtualFiles` but treated as stdlib — always available and never produce bad-import warnings. |
3689
3806
  | `dtsFiles` | `[{name, content}]` | `[]` | TypeScript `.d.ts` files loaded at startup. |
3690
3807
  | `loadDts` | `(name) => Promise<string>` | — | Async callback for lazy-loading `.d.ts` content on demand. |
3691
- | `extraBuiltins` | `{name: true}` | `{}` | Extra global names that suppress undefined-symbol warnings. |
3808
+ | `extraBuiltins` | `string[]` | `[]` | Extra global names that suppress undefined-symbol warnings. |
3692
3809
  | `pythonFlags` | string | — | Comma-separated Python flags to enable globally (e.g. `"dict_literals,overload_getitem"`). See [Python Flags](#python-flags) above. |
3693
3810
 
3694
3811
  ### Runtime API
@@ -3838,7 +3955,6 @@ original `.py` file with working breakpoints and correct error stack frames.
3838
3955
  | `python_flags` | string | — | Comma-separated Python flags to enable for this compilation (e.g. `"dict_literals,overload_operators"`). See [Python Flags](#python-flags) above. Flags set here override any inherited from a previous `compile()` call on a streaming compiler. |
3839
3956
  | `virtual_files` | `{name: source}` | — | Map of module-name → RapydScript source for modules importable via `import`. Only used when the underlying streaming compiler was created with a virtual-file context (as `web_repl()` does). |
3840
3957
  | `discard_asserts` | bool | `false` | Strip all `assert` statements from the output. |
3841
- | `omit_function_metadata` | bool | `false` | Omit per-function metadata (e.g. argument names) from the output for smaller bundles. |
3842
3958
  | `write_name` | bool | `false` | Emit a `var __name__ = "…"` assignment at the top of the output. |
3843
3959
  | `tree_shake` | bool | `false` | Remove unused imported names from the output (requires stdlib imports). |
3844
3960
  | `filename` | string | `'<input>'` | Source filename embedded in the source map and used in error messages. |
@@ -3886,7 +4002,7 @@ Python Feature Coverage
3886
4002
  | `try / else` | `else` block runs only when no exception was raised |
3887
4003
  | `for / else` | `else` block runs when loop completes without `break`; nested break isolation works |
3888
4004
  | `while / else` | `else` block runs when loop condition becomes `False` without a `break`; nested `break` isolation correct |
3889
- | `with A() as a, B() as b:` | Multiple context managers in one statement; exits in LIFO order (Python-correct) |
4005
+ | `with A() as a, B() as b:` / `with (A() as a, B() as b):` | Multiple context managers in one statement (flat or parenthesized 3.10+ form); exits in LIFO order (Python-correct); multi-line and trailing comma supported |
3890
4006
  | `callable(fn)` | Works for plain functions and objects with `__call__` |
3891
4007
  | `round(x, ndigits=0)` | Full Python semantics including negative `ndigits` |
3892
4008
  | `enumerate(iterable, start=0)` | `start` parameter supported |
@@ -3904,10 +4020,13 @@ Python Feature Coverage
3904
4020
  | `list + list` concatenation | `[1,2] + [3,4]` returns `[1, 2, 3, 4]`; `+=` extends in-place. No flag required. |
3905
4021
  | `match / case` | Structural pattern matching (Python 3.10) fully supported |
3906
4022
  | Variable type annotations `x: int = 1` | Parsed and ignored (no runtime enforcement); annotated assignments work normally |
4023
+ | Argument type enforcement (`from __python__ import type_enforcement`) | Activates runtime `TypeError` for wrong argument count, positional-only kwargs, missing required keyword-only args, and type-annotated arg mismatches; Monaco language service reports violations statically at call sites |
3907
4024
  | Ellipsis literal `...` as expression | Parsed as a valid expression; evaluates to JS `undefined` at runtime |
3908
4025
  | Generator `.throw()` | Works via JS generator protocol |
3909
4026
  | Generator `.send()` | Works via `g.next(value)` |
3910
4027
  | `yield from` | Works; return value of sub-generator is not accessible |
4028
+ | `async def` with `yield` (async generators) | Combining `async def` with `yield` produces an async generator. Calling it returns an async iterator immediately; `.next()` / `.asend()` return `Promise` objects, matching Python's `__anext__` / `asend` API (`.send` and `.asend` are aliased to `.next`). `await` may appear before, between, or after `yield`. Compiled to a sync wrapper around `async function*`. |
4029
+ | `async for x in iter:` | Drives async iterators (`Symbol.asyncIterator`) and async generators. Compiles to native `for await ... of` (ES2018) regardless of `--js-version`. Works with sync iterables that yield Promises as well. |
3911
4030
  | `+=`, `-=`, `*=`, `/=`, `//=`, `**=`, `%=`, `&=`, `\|=`, `^=`, `<<=`, `>>=` | All augmented assignments work |
3912
4031
  | `raise X from Y` exception chaining | Sets `__cause__` on the thrown exception; `from None` also supported |
3913
4032
  | Starred assignment `a, *b, c = ...` | Works |
@@ -3922,7 +4041,7 @@ Python Feature Coverage
3922
4041
  | Classes, inheritance, decorators, `__dunder__` methods | Fully supported |
3923
4042
  | Nested class definitions | Accessible as `Outer.Inner` and via instance (`self.Inner`); arbitrary nesting depth; nested class may inherit from outer-scope classes |
3924
4043
  | List / dict / set comprehensions, generator expressions | Fully supported |
3925
- | f-strings, `str.format()`, `format()` builtin, all common `str.*` methods | Fully supported |
4044
+ | f-strings, `str.format()`, `format()` builtin, all common `str.*` methods | Fully supported; includes `f'{x=}'` debug format (prints `x=<value>`), format-spec (`f'{x=:.2f}'`), and conversion (`f'{x=!r}'`) |
3926
4045
  | `abs()`, `divmod()`, `any()`, `all()`, `sum()`, `min()`, `max()` | All work |
3927
4046
  | `sorted()`, `reversed()`, `zip()`, `map()`, `filter()` | All work |
3928
4047
  | `zip(strict=True)` | Raises `ValueError` when iterables have different lengths; equal-length iterables work normally |
@@ -3951,6 +4070,7 @@ Python Feature Coverage
3951
4070
  | `dict \| dict` and `dict \|= dict` (Python 3.9+) | Dict merge via `\|` creates a new merged dict (right-side values win); `\|=` updates in-place. Active via `overload_operators` + `dict_literals` (both on by default). |
3952
4071
  | `__format__` dunder | `format()`, `str.format()`, and f-strings all dispatch to `__format__`; default `__format__` auto-generated for classes (returns `__str__()` for empty spec, raises `TypeError` for non-empty spec); `!r`/`!s`/`!a` transformers bypass `__format__` correctly |
3953
4072
  | `slice(start, stop[, step])` | Full Python `slice` class: 1-, 2-, and 3-argument forms; `.start`, `.stop`, `.step` attributes; `.indices(length)` → `(start, stop, step)`; `str()` / `repr()`; `isinstance(s, slice)`; equality `==`; use as subscript `lst[s]` (read, write, `del`) all work. |
4073
+ | Multi-line parenthesized `from … import (…)` with optional trailing comma | Each imported name (with optional `as` alias) may appear on its own line; a trailing comma before `)` is accepted — identical to Python syntax |
3954
4074
  | `__import__(name[, globals, locals, fromlist, level])` | Runtime lookup in the compiled module registry (`ρσ_modules`). Without `fromlist` (or empty `fromlist`) returns the top-level package, matching Python's semantics. `ImportError` / `ModuleNotFoundError` raised for unknown modules. **Constraint**: the module must have been statically imported elsewhere in the source so it is present in `ρσ_modules`. |
3955
4075
  | `ImportError`, `ModuleNotFoundError` | Both defined as runtime exception classes; `ModuleNotFoundError` is a subclass of `ImportError` (same as Python 3.6+). |
3956
4076
  | `bytes(source[, encoding[, errors]])` and `bytearray(source[, encoding[, errors]])` | Full Python semantics: construction from integer (n zero bytes), list/iterable of ints (0–255), string + encoding (`utf-8`, `latin-1`, `ascii`), `Uint8Array`, or another `bytes`/`bytearray`. Key methods: `hex([sep[, bytes_per_sep]])`, `decode(encoding)`, `fromhex(s)` (static), `count`, `find`, `rfind`, `index`, `rindex`, `startswith`, `endswith`, `join`, `split`, `replace`, `strip`, `lstrip`, `rstrip`, `upper`, `lower`, `copy`. `bytearray` adds: `append`, `extend`, `insert`, `pop`, `remove`, `reverse`, `clear`, `__setitem__` (single and slice). Slicing returns a new `bytes`/`bytearray`. `+` concatenates; `*` repeats; `==` compares element-wise; `in` tests integer or subsequence membership; `isinstance(x, bytes)` / `isinstance(x, bytearray)` work; `bytearray` is a subclass of `bytes`. `repr()` returns `b'...'` notation. `Uint8Array` values may be passed anywhere a `bytes`-like object is accepted. |
@@ -3958,6 +4078,7 @@ Python Feature Coverage
3958
4078
  | `float.is_integer()` | Returns `True` if the float has no fractional part (i.e. is a whole number), `False` otherwise. `float('inf').is_integer()` and `float('nan').is_integer()` both return `False`, matching Python semantics. Added to `Number.prototype` in the baselib so it works on any numeric literal or variable. |
3959
4079
  | `int.bit_length()` | Returns the number of bits needed to represent the integer in binary, excluding the sign and leading zeros. `(0).bit_length()` → `0`; `(255).bit_length()` → `8`; `(256).bit_length()` → `9`; sign is ignored (`(-5).bit_length()` → `3`). Added to `Number.prototype` in the baselib. |
3960
4080
  | Arithmetic type coercion — `TypeError` on incompatible operands | `1 + '1'` raises `TypeError: unsupported operand type(s) for +: 'int' and 'str'`; all arithmetic operators (`+`, `-`, `*`, `/`, `//`, `%`, `**`) enforce compatible types in their `ρσ_op_*` helpers. `bool` is treated as numeric (like Python's `int` subclass). Activated by `overload_operators` (on by default). String `+` string and numeric `+` numeric are allowed; mixed types raise `TypeError` with a Python-style message. |
4081
+ | `long(val[, base])` — arbitrary-precision integers | Backed by JS `BigInt`. `long(42)`, `long('ff', 16)`, `long('1010', 2)`, `long(True)`. Arithmetic: `+`, `-`, `*`, `//`, `%`, `**` — `//` and `%` follow Python floor-division semantics (floor toward −∞), not JS BigInt truncation. `/` raises `TypeError` (use `//`). Bitwise: `&`, `\|`, `^`, `<<`, `>>`. Comparisons: `<`, `<=`, `>`, `>=`, `==`, `!=`. `isinstance(x, long)` works. Mixing `long` with `int` or `float` raises `TypeError`. Requires `BigInt` (Chrome 67+, Firefox 68+, Safari 14+, Node 10.3+). |
3961
4082
  | `complex(real=0, imag=0)` and complex literals `3+4j` | Full complex number type via `ρσ_complex` class. `complex(real, imag)`, `complex(string)` (parses `'3+4j'`), and `j`/`J` imaginary literal suffix (e.g. `4j`, `3.5J`). Attributes: `.real`, `.imag`. Methods: `conjugate()`, `__abs__()`, `__bool__()`, `__repr__()`, `__str__()`. Arithmetic: `+`, `-`, `*`, `/`, `**` via dunder methods (or operator overloading with `overload_operators`). `abs(z)` dispatches `__abs__`. `isinstance(z, complex)` works. String representation matches Python: `(3+4j)`, `4j`, `(3-0j)`. |
3962
4083
  | `eval(expr[, globals[, locals]])` | String literals are compiled as **RapydScript source** at compile time (the compiler parses and transpiles the string, just like Python's `eval` takes Python source). `eval(expr)` maps to native JS direct `eval` for scope access. `eval(expr, globals)` / `eval(expr, globals, locals)` use `Function` constructor with explicit bindings; `locals` override `globals`. Runtime `ρσ_` helpers referenced in the compiled string are automatically injected into the Function scope. Only string *literals* are transformed at compile time; dynamic strings are passed through unchanged. |
3963
4084
  | `exec(code[, globals[, locals]])` | String literals are compiled as **RapydScript source** at compile time. Executes the compiled code string; always returns `None`. Without `globals`/`locals` uses native `eval` (scope access). With `globals`/`locals` uses `Function` constructor — mutable objects (lists, dicts) passed in `globals` are accessible by reference, so side-effects are visible after the call. `ρσ_dict` instances (created when `dict_literals` flag is active) are correctly unwrapped via their `jsmap` backing store. |
@@ -4041,7 +4162,7 @@ Modules with a `src/lib/` implementation available are marked ✅. All others ar
4041
4162
  | `math` | ✅ | Full implementation in `src/lib/math.pyj` |
4042
4163
  | `random` | ✅ | RC4-seeded PRNG in `src/lib/random.pyj` |
4043
4164
  | `re` | ✅ | Regex wrapper in `src/lib/re.pyj`; uses the JS engine — full PCRE-level support on modern runtimes: positive/negative lookbehind (ES2018+, including variable-width), unicode via automatic `u` flag (ES2015+), `re.fullmatch()`, `re.S`/`re.NOFLAG` aliases. `MatchObject.start()`/`.end()` return exact positions on runtimes with the ES2022 `d` flag (Node 18+); heuristic fallback on older runtimes. Conditional groups `(?(id)yes\|no)` are not supported (JS limitation) and raise `re.error`. |
4044
- | `encodings` | ✅ | Base64 and encoding helpers; partial `base64` coverage |
4165
+ | `encodings` | ✅ | UTF-8 encode/decode helpers and low-level base64 utilities; for the standard Python API use the `base64` module instead |
4045
4166
  | `collections` | ✅ | `defaultdict`, `Counter`, `OrderedDict`, `deque` |
4046
4167
  | `functools` | ✅ | `reduce`, `partial`, `wraps`, `lru_cache` |
4047
4168
  | `itertools` | ✅ | Common iteration tools |
@@ -4055,19 +4176,22 @@ Modules with a `src/lib/` implementation available are marked ✅. All others ar
4055
4176
  | `datetime` | ✅ | `date`, `time`, `datetime`, `timedelta`, `MINYEAR`, `MAXYEAR` in `src/lib/datetime.pyj`; construction, `isoformat()`, `fromisoformat()`, `today()`/`now()`, `combine()`, arithmetic (via `__add__`/`__sub__`; operators need `overload_operators`), comparisons, `replace()`, `strftime()` (%Y %m %d %H %M %S %f %A %a %B %b %j %p %I %%); no tzinfo/timezone support |
4056
4177
  | `json` | ✅ | `dumps()`, `loads()`, `dump()`, `load()`, `JSONDecodeError` in `src/lib/json.pyj`; `indent`, `sort_keys`, `separators`, `dflt` (Python's `default`) callback, `object_hook`, `object_pairs_hook`, `parse_float`, `parse_int` supported; backed by the JS `JSON` global; note: `default` is a JS reserved word — use `dflt=` instead |
4057
4178
  | `io` | ✅ | `StringIO`, `BytesIO`, `UnsupportedOperation` in `src/lib/io.pyj`; `read([size])`, `readline([size])`, `readlines([hint])`, `write()`, `writelines()`, `seek(pos[, whence])`, `tell()`, `truncate([pos])`, `getvalue()`, `close()`, `closed`; context manager support; `readable()`, `writable()`, `seekable()` return True; `newline` parameter accepted for API compatibility |
4058
- | `string` | | Character constants, `Template`, `Formatter` not available |
4179
+ | `base64` | | `b64encode(s[, altchars])`, `b64decode(s[, altchars][, validate])`, `standard_b64encode`, `standard_b64decode`, `urlsafe_b64encode`, `urlsafe_b64decode`, `b32encode`, `b32decode([casefold][, map01])`, `b16encode`, `b16decode([casefold])`, `encodebytes`, `decodebytes`, `Error` in `src/lib/base64.pyj`; pure-JS implementation, works in browser and Node; all encode functions return `bytes` |
4180
+ | `string` | ✅ | Character constants (`ascii_letters`, `ascii_lowercase`, `ascii_uppercase`, `digits`, `hexdigits`, `octdigits`, `punctuation`, `whitespace`, `printable`), `Template` ($-substitution with `substitute(mapping)` and `safe_substitute(mapping)`), `Formatter` (`format(*args)`, `vformat(fmt, args, kwargs)`, `format_field`, `convert_field`, `get_value`, `get_field`, `parse`) in `src/lib/string.pyj`; `Formatter.format()` accepts positional args — for named-field substitution use `vformat(fmt, [], {'key': val})`; `Template.substitute()` and `safe_substitute()` accept a dict (or plain object) |
4181
+ | `html` | ✅ | `escape(s[, quote=True])`, `unescape(s)`, `HTMLParser` (event-driven parser; subclass and override `handle_starttag`, `handle_endtag`, `handle_data`, `handle_comment`, `handle_decl`, etc.) in `src/lib/html.pyj`; full HTML4 + common HTML5 named entity table; `convert_charrefs=True` by default (entities in text and attributes auto-decoded); `html.parser` sub-module not available as a separate import — use `from html import HTMLParser` |
4182
+ | `asyncio` | ✅ | `sleep()`, `gather()`, `create_task()`, `ensure_future()`, `run()`, `shield()`, `wait_for()`, `wait()`, `iscoroutine()`, `iscoroutinefunction()`, `current_task()`, `all_tasks()`, `get_event_loop()`, `get_running_loop()`, `new_event_loop()` in `src/lib/asyncio.pyj`; synchronization primitives: `Lock`, `Event`, `Semaphore`, `BoundedSemaphore`, `Queue`, `LifoQueue`, `PriorityQueue`; exceptions: `CancelledError`, `TimeoutError`, `InvalidStateError`, `QueueEmpty`, `QueueFull`; `async def` / `await` syntax compiles to native JS async functions; `async def` with `yield` (async generators) and `async for` are supported (see "Async generators" section); `asyncio.run(coro)` returns the Promise (cannot block in JS); `async with` is not supported — use `.acquire()`/`.release()` directly |
4183
+ | `urllib` | ✅ | `urllib.parse`: `quote()`, `unquote()`, `quote_plus()`, `unquote_plus()`, `urlencode()`, `urlsplit()` → `SplitResult`, `urlparse()` → `ParseResult`, `urlunsplit()`, `urlunparse()`, `urljoin()`, `parse_qs()`, `parse_qsl()` in `src/lib/urllib/parse.pyj`; `urllib.error`: `URLError`, `HTTPError` in `src/lib/urllib/error.pyj`; `urllib.request`: `urlopen(url[, data[, timeout]])` returns a Promise wrapping the Fetch API in `src/lib/urllib/request.pyj` — use with `await` in an async function; `SplitResult`/`ParseResult` expose `.scheme`, `.netloc`, `.path`, `.query`, `.fragment` plus `.hostname`, `.port`, `.username`, `.password` and `.geturl()`; backed by the JS `URL` constructor and `encodeURIComponent`/`decodeURIComponent`; RFC 3986 unreserved characters (`A-Z a-z 0-9 - _ . ~`) never encoded |
4184
+ | `bisect` | ✅ | `bisect_left(a, x[, lo[, hi[, key]]])`, `bisect_right(a, x[, lo[, hi[, key]]])`, `bisect` (alias for `bisect_right`), `insort_left(a, x[, lo[, hi[, key]]])`, `insort_right(a, x[, lo[, hi[, key]]])`, `insort` (alias for `insort_right`) in `src/lib/bisect.pyj`; pure binary-search implementation matching Python semantics; optional `lo`/`hi` bounds for sub-slice search; optional `key` function (Python 3.10+ API) applied to array elements only — `x` must be directly comparable to `key(a[i])` results |
4185
+ | `http` | ✅ | `http`: `HTTPStatus` class with integer constants for all standard status codes (`OK=200`, `NOT_FOUND=404`, `IM_A_TEAPOT=418`, etc.) in `src/lib/http/__init__.pyj`; `http.client`: `HTTPConnection(host[, port[, timeout]])`, `HTTPSConnection(host[, port[, timeout]])`, `HTTPResponse` (`.status`, `.reason`, `.headers`, `.url`, `getheader(name[, default])`, `getheaders()`, `read()` → Promise, `json()` → Promise), `HTTPException`, `NotConnected`, `InvalidURL`, `RemoteDisconnected`, `HTTP_PORT=80`, `HTTPS_PORT=443` in `src/lib/http/client.pyj`; `http.cookies`: `SimpleCookie` (dict-like cookie container with `load(rawdata)`, `output([header, sep])`, `items()`, `keys()`, `values()`), `Morsel` (`.key`, `.value`, `.coded_value`, `OutputString()`, `output([header])`), `CookieError` in `src/lib/http/cookies.pyj`; `getresponse()` wraps the Fetch API and returns a Promise — use with `await`; non-2xx responses are returned normally (check `resp.status` yourself, unlike `urllib.request`); `http.server` not available |
4186
+ | `csv` | ✅ | `reader(csvfile[, dialect][, **fmtparams])`, `writer(csvfile[, dialect][, **fmtparams])`, `DictReader(f[, fieldnames[, restkey[, restval[, dialect, ...]]]])`, `DictWriter(f, fieldnames[, restval[, extrasaction[, dialect, ...]]])` in `src/lib/csv.pyj`; `Dialect`, `excel`, `excel_tab`, `unix_dialect` classes; `register_dialect`, `unregister_dialect`, `get_dialect`, `list_dialects`, `field_size_limit`; `QUOTE_MINIMAL`, `QUOTE_ALL`, `QUOTE_NONNUMERIC`, `QUOTE_NONE` constants; `Error` exception; backed by pure-JS parser with full support for quoted fields (including multi-line), custom delimiters, `skipinitialspace`, double-quote escaping, escape characters; accepts list-of-strings, file-like objects (with `.read()`), or any iterable; note: `csv.Error` shadows the native JS Error if imported via `from csv import Error` in web-repl mode — catch via `Exception` instead |
4187
+ | `textwrap` | ✅ | `wrap(text[, width=70[, ...]])`, `fill(text[, width=70[, ...]])`, `shorten(text, width[, ...])`, `dedent(text)`, `indent(text, prefix[, predicate])`, `TextWrapper` class in `src/lib/textwrap.pyj`; `TextWrapper` supports all standard options: `width`, `initial_indent`, `subsequent_indent`, `expand_tabs`, `tabsize`, `replace_whitespace`, `fix_sentence_endings`, `break_long_words`, `break_on_hyphens`, `drop_whitespace`, `max_lines`, `placeholder`; module-level `wrap`/`fill`/`shorten` accept the same options as explicit keyword parameters; `shorten()` normalises internal whitespace before truncating; `break_on_hyphens=True` (default) splits on em-dashes (2+ hyphens) between word characters, matching Python 3 behaviour |
4188
+ | `logging` | ✅ | `getLogger(name)`, `basicConfig(**kwargs)`, `debug/info/warning/error/critical/exception/log()` module-level shortcuts, `disable(level)`, `addLevelName(level, name)`, `getLevelName(level)`, `captureWarnings(capture)`, `makeLogRecord(dict)`, `setLogRecordFactory(factory)`, `getLogRecordFactory()` in `src/lib/logging.pyj`; `Logger` class with `setLevel`, `isEnabledFor`, `getEffectiveLevel`, `addHandler`, `removeHandler`, `hasHandlers`, `addFilter`, `removeFilter`, `debug/info/warning/error/critical/exception/log` methods; `Handler` base class; `StreamHandler(stream=None)` (writes to `console.debug/info/warn/error` when no stream given, or to any object with `.write(s)`); `NullHandler`; `Formatter(fmt, datefmt, style)` with full `%(attr)s/d/f` record formatting and `formatTime(record, datefmt)` (supports `%Y %m %d %H %M %S %f` strftime codes); `Filter(name)` and `Filterer` mixin; `LogRecord` with `getMessage()` supporting `%s/%d/%f/%e/%g/%x/%o/%%` %-formatting; level constants `NOTSET=0`, `DEBUG=10`, `INFO=20`, `WARNING=30`, `ERROR=40`, `CRITICAL=50`; full logger hierarchy (dotted names, `propagate`, `parent`); `lastResort` handler; note: `FileHandler` raises `NotImplementedError` (no file I/O in JS); `exception()` logs at ERROR without automatic exc_info capture |
4189
+ | `heapq` | ✅ | `heappush(heap, item)`, `heappop(heap)`, `heapify(x)`, `heapreplace(heap, item)`, `heappushpop(heap, item)`, `nsmallest(n, iterable[, key])`, `nlargest(n, iterable[, key])` in `src/lib/heapq.pyj`; min-heap operations on plain lists; heap invariant maintained by sift-up/sift-down (ported from CPython); `nsmallest`/`nlargest` support an optional `key` function; `heappop` and `heapreplace` raise `IndexError` on an empty heap; original iterable is not mutated by `nsmallest`/`nlargest` |
4059
4190
  | `inspect` | ❌ | `signature`, `getmembers`, `isfunction` etc. not available |
4060
- | `asyncio` | ❌ | Event loop, `gather`, `sleep`, `Queue`, `Task` wrappers not available; use `async`/`await` |
4061
4191
  | `struct` | ❌ | Binary packing/unpacking not available |
4062
4192
  | `hashlib` | ❌ | MD5, SHA-256 etc. not available; use Web Crypto API via verbatim JS |
4063
4193
  | `hmac` | ❌ | Keyed hashing not available |
4064
- | `base64` | ❌ (partial) | Partial coverage via `encodings` module; no full `base64` module |
4065
- | `urllib` | ❌ | URL parsing/encoding (`urllib.parse`) not available; use JS `URL` API |
4066
- | `html` | ❌ | `escape`, `unescape` not available; use JS DOM APIs |
4067
- | `csv` | ❌ | CSV parsing not available |
4068
- | `textwrap` | ❌ | `wrap`, `fill`, `dedent`, `indent` not available |
4069
4194
  | `pprint` | ❌ | Pretty-printing not available |
4070
- | `logging` | ❌ | Logging framework not available; use `console.*` directly |
4071
4195
  | `unittest` | ❌ | Not available; RapydScript uses a custom test runner (`node bin/rapydscript test`) |
4072
4196
 
4073
4197
  ---
@@ -4080,7 +4204,7 @@ Features that exist in RapydScript but behave differently from standard Python:
4080
4204
  |---|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
4081
4205
  | `is` / `is not` | Object identity | Strict equality `===` / `!==`, which is not object/pointer comparison for JS primitives |
4082
4206
  | `//` floor division on floats | `math.floor(a/b)` always | uses `Math.floor` - same result for well-behaved floats |
4083
- | `%` on negative numbers | Python modulo (always non-negative) | JS remainder (can be negative) |
4207
+ | `%` on negative numbers | Result takes the sign of the divisor (`-7 % 3 == 2`) | Same as Python by default (via `python_modulo`, on by default). Disable with `from __python__ import no_python_modulo` to revert to raw JS remainder semantics. |
4084
4208
  | `global` / `nonlocal` scoping | Full cross-scope declaration | `global` works for module-level; if a variable exists in both an intermediate outer scope **and** the module-level scope, the outer scope takes precedence (differs from Python where `global` always forces module-level) |
4085
4209
  | `Exception.message` | Not standard; use `.args[0]` | `.message` is the standard attribute (JS `Error` style) |
4086
4210
  | Function call argument count | Too few args → `TypeError`; too many → `TypeError` | Too few args → extra params are `undefined`; too many → extras silently discarded. No `TypeError` is raised in either case. |
@@ -4093,7 +4217,7 @@ Features that exist in RapydScript but behave differently from standard Python:
4093
4217
  | Generators — output format | Native Python generator objects | Down-compiled to ES5 state-machine switch statements by default; pass `--js-version 6` for native ES6 generators (smaller and faster) |
4094
4218
  | `dict` key ordering | Insertion order guaranteed (3.7+) | Depends on JS engine (V8 preserves insertion order in practice) |
4095
4219
  | Reserved keywords | Python keywords only | All JavaScript reserved words (`default`, `switch`, `delete`, `void`, `typeof`, etc.) are also reserved in RapydScript, since it compiles to JS |
4096
- | `parenthesized with (A() as a, B() as b):` | Multiple context managers in parenthesized form (3.10+) | Not meaningful in a browser/event-driven context; multi-context `with` without parens works |
4220
+ | `parenthesized with (A() as a, B() as b):` | Multiple context managers in parenthesized form (3.10+) | Fully supported; multi-line and trailing comma accepted; semantics identical to the flat form |
4097
4221
 
4098
4222
  ---
4099
4223
 
package/TODO.md CHANGED
@@ -1,19 +1,34 @@
1
1
 
2
2
  ### libraries
3
3
 
4
+ - The 7n literal isn't supported by the parser — use BigInt()
4
5
 
5
6
  - remove repl_mode and make repl make a new context for each "run" press
6
- - `.replace(str, str)` replaces only the first occurrence.
7
- - eval is js
8
7
 
9
- - rework tests to use jest
8
+ - vscode plugin based on language service?
10
9
 
11
- - omit_function_metadata breaks imports - it needs to be changed to only affect imported modules, maybe?
12
10
 
13
- - vscode plugin based on language service?
11
+ I would like you to add support for [python async def functions with yield (async generators)] to rapydscript. It should have the same syntax as the Python implementation, and be transpiled into equivalent javascript. Ensure with unit tests that it transpiles and the output JS runs correctly, and that the language service correctly handles it in parsed code. Make sure it works in the web-repl. Update the README if it has any outdated info about this, and the PYTHON_FEATURE_COVERAGE report. Add a simple example to the bottom of the TODO document using this feature (make no other changes to that file). Remove the suggestion from PYTHON_GAPS if it is there.
12
+
13
+ ### Async generator example
14
+
15
+ ```py
16
+ # `async def` + `yield` makes an async generator. The function returns an
17
+ # async iterator immediately; values are pulled with `await it.next()` or
18
+ # consumed with `async for`.
19
+
20
+ from asyncio import sleep
14
21
 
15
- - examples of using js libraries in rapydscript in readme?
22
+ async def countdown(n):
23
+ while n > 0:
24
+ await sleep(0) # cooperative yield to the event loop
25
+ yield n
26
+ n -= 1
16
27
 
28
+ async def main():
29
+ async for x in countdown(3):
30
+ print(x) # prints 3, then 2, then 1
17
31
 
18
- I would like you to add support for [the python io.stringIO and io.bytesio library ] to rapydscript. It should have the same syntax as the Python implementation, and be transpiled into equivalent javascript. Please ensure with unit tests that it transpiles and the output JS runs correctly, and that the language service correctly handles it in parsed code. Please make sure it works in the web-repl too. Please also update the README if it has any outdated info about this, and the PYTHON_FEATURE_COVERAGE report. Please also add a simple example to the bottom of the TODO document using this feature (make no other changes to that file).
32
+ main()
33
+ ```
19
34