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/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,62 @@ 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
+ You can use the `long()` function or Python-style `n` suffix literals:
1385
+
1386
+ ```python
1387
+ # Literal syntax (preferred)
1388
+ a = 10n
1389
+ b = 3n
1390
+ c = 0xFFn # hex
1391
+ d = 0b1010n # binary
1392
+ e = 0o77n # octal
1393
+
1394
+ # Function syntax
1395
+ a = long(10)
1396
+ b = long(3)
1397
+
1398
+ a + b # 13n
1399
+ a - b # 7n
1400
+ a * b # 30n
1401
+ a // b # 3n — floor division (Python semantics, not JS truncation)
1402
+ a % b # 1n — Python-style modulo (result has same sign as divisor)
1403
+ a ** b # 1000n
1404
+ long(-7) // long(2) # -4n — floors toward −∞ (JS BigInt would give −3)
1405
+ long(-7) % long(3) # 2n — result matches sign of divisor
1406
+ ```
1407
+
1408
+ #### Precision beyond JS Number
1409
+
1410
+ The main motivation for `long` is numbers outside the safe integer range of
1411
+ JavaScript `Number` (`2^53 − 1 = 9007199254740991`):
1412
+
1413
+ ```python
1414
+ n = 9007199254740993n # 2^53 + 1 — cannot be represented as a JS Number
1415
+ str(n) # '9007199254740993' (exact)
1416
+ str(n + 1n) # '9007199254740994' (still exact)
1417
+
1418
+ # equivalent using long():
1419
+ n = long('9007199254740993')
1420
+ str(n + long(1))
1421
+ ```
1422
+
1423
+ #### Operator overloading
1424
+
1425
+ `//`, `%`, and `**` require `overload_operators` (on by default) to emit the
1426
+ BigInt-aware helpers rather than the raw JS operators. If you have explicitly
1427
+ disabled operator overloading with `from __python__ import no_overload_operators`,
1428
+ `//` will call `Math.floor(a / b)` which throws a `TypeError` at runtime for
1429
+ `long` values.
1430
+
1431
+ #### Browser / environment support
1432
+
1433
+ `BigInt` is supported in all modern browsers (Chrome 67+, Firefox 68+, Safari
1434
+ 14+, Node.js 10.3+). Code using `long` will not run in Internet Explorer.
1435
+
1346
1436
  ### `issubclass`
1347
1437
 
1348
1438
  `issubclass(cls, classinfo)` checks whether a class is a subclass of another
@@ -3046,6 +3136,34 @@ By default, generators are down-converted to ES 5 switch statements. Pass
3046
3136
  compiler options) to emit native ES 6 generator functions instead, which are
3047
3137
  smaller and faster.
3048
3138
 
3139
+ ### Async generators (`async def` with `yield`)
3140
+
3141
+ Async generators — `async def` functions that contain `yield` — are also
3142
+ supported, with the same syntax as Python:
3143
+
3144
+ ```py
3145
+ async def stream(urls):
3146
+ for url in urls:
3147
+ body = await fetch(url)
3148
+ yield body
3149
+
3150
+ async def main():
3151
+ async for body in stream(['/a', '/b']):
3152
+ print(body)
3153
+ ```
3154
+
3155
+ Calling an async-generator function returns an async iterator immediately
3156
+ (not a `Promise`). Each `.next()` call returns a `Promise` that resolves to a
3157
+ `{value, done}` record, matching the JS async-iterator protocol. Manual
3158
+ iteration via `await it.next()` and `await it.asend()` works (`.asend` is
3159
+ aliased to `.next`, mirroring Python's async-generator API). Whole iterators
3160
+ are best consumed with `async for x in iter:`, which compiles to JS
3161
+ `for await ... of`.
3162
+
3163
+ `async for` is an ES2018 feature, so it is always emitted in modern form
3164
+ regardless of `--js-version`. The runtime must support `Symbol.asyncIterator`
3165
+ (Node 10+, Chrome 63+, Firefox 57+, Safari 11.1+).
3166
+
3049
3167
  Modules
3050
3168
  -------
3051
3169
 
@@ -3059,6 +3177,16 @@ You can import things from modules, just like you would in python:
3059
3177
  from mypackage.mymodule import something, something_else
3060
3178
  ```
3061
3179
 
3180
+ Multi-line parenthesized imports (Python 3.10+ style) are fully supported, including trailing commas:
3181
+
3182
+ ```py
3183
+ from mypackage.mymodule import (
3184
+ something,
3185
+ something_else as alias,
3186
+ another_thing,
3187
+ )
3188
+ ```
3189
+
3062
3190
  When you import modules, the RapydScript compiler automatically generates a
3063
3191
  single large JavaScript file containing all the imported packages/modules and
3064
3192
  their dependencies, recursively. This makes it very easy to integrate the
@@ -3340,7 +3468,7 @@ One of Python's main strengths is the number of libraries available to the devel
3340
3468
  # fields(), asdict(), astuple(), replace(), is_dataclass(), frozen=True, order=True
3341
3469
  abc # ABC base class, @abstractmethod, Protocol, @runtime_checkable;
3342
3470
  # abstract enforcement at instantiation; ABC.register() virtual subclasses
3343
- collections # namedtuple, deque, Counter, OrderedDict, defaultdict
3471
+ collections # namedtuple, deque, Counter, OrderedDict, defaultdict, ChainMap
3344
3472
  copy # copy (shallow), deepcopy; honours __copy__ / __deepcopy__ hooks
3345
3473
  typing # TYPE_CHECKING, Any, Union, Optional, List, Dict, Set, Tuple, TypeVar,
3346
3474
  # Generic, Protocol, Callable, Literal, Final, TypedDict, NamedTuple,
@@ -3349,6 +3477,9 @@ One of Python's main strengths is the number of libraries available to the devel
3349
3477
  # groupby, islice, pairwise, starmap, takewhile, zip_longest,
3350
3478
  # product, permutations, combinations, combinations_with_replacement
3351
3479
  io # StringIO (in-memory text stream), BytesIO (in-memory binary stream)
3480
+ base64 # b64encode/decode, urlsafe_b64encode/decode, b32encode/decode, b16encode/decode, encodebytes/decodebytes
3481
+ statistics # mean, median, mode, variance, stdev, quantiles, correlation,
3482
+ # linear_regression, NormalDist
3352
3483
 
3353
3484
  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
3485
 
@@ -3618,6 +3749,7 @@ with no flags enabled, pass ``--legacy-rapydscript`` on the command line.
3618
3749
  | `overload_getitem` | `obj[key]` dispatches to `__getitem__` / `__setitem__` / `__delitem__` on objects that define them. On by default. |
3619
3750
  | `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
3751
  | `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. |
3752
+ | `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
3753
  | `truthiness` | Boolean tests and `bool()` dispatch to `__bool__` and treat empty containers as falsy, matching Python semantics. On by default. |
3622
3754
  | `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
3755
  | `hash_literals` | `{k: v}` creates a Python `dict` (alias for `dict_literals`; kept for backward compatibility). On by default. |
@@ -3838,7 +3970,6 @@ original `.py` file with working breakpoints and correct error stack frames.
3838
3970
  | `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
3971
  | `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
3972
  | `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
3973
  | `write_name` | bool | `false` | Emit a `var __name__ = "…"` assignment at the top of the output. |
3843
3974
  | `tree_shake` | bool | `false` | Remove unused imported names from the output (requires stdlib imports). |
3844
3975
  | `filename` | string | `'<input>'` | Source filename embedded in the source map and used in error messages. |
@@ -3886,7 +4017,7 @@ Python Feature Coverage
3886
4017
  | `try / else` | `else` block runs only when no exception was raised |
3887
4018
  | `for / else` | `else` block runs when loop completes without `break`; nested break isolation works |
3888
4019
  | `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) |
4020
+ | `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
4021
  | `callable(fn)` | Works for plain functions and objects with `__call__` |
3891
4022
  | `round(x, ndigits=0)` | Full Python semantics including negative `ndigits` |
3892
4023
  | `enumerate(iterable, start=0)` | `start` parameter supported |
@@ -3904,10 +4035,13 @@ Python Feature Coverage
3904
4035
  | `list + list` concatenation | `[1,2] + [3,4]` returns `[1, 2, 3, 4]`; `+=` extends in-place. No flag required. |
3905
4036
  | `match / case` | Structural pattern matching (Python 3.10) fully supported |
3906
4037
  | Variable type annotations `x: int = 1` | Parsed and ignored (no runtime enforcement); annotated assignments work normally |
4038
+ | 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
4039
  | Ellipsis literal `...` as expression | Parsed as a valid expression; evaluates to JS `undefined` at runtime |
3908
4040
  | Generator `.throw()` | Works via JS generator protocol |
3909
4041
  | Generator `.send()` | Works via `g.next(value)` |
3910
4042
  | `yield from` | Works; return value of sub-generator is not accessible |
4043
+ | `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*`. |
4044
+ | `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
4045
  | `+=`, `-=`, `*=`, `/=`, `//=`, `**=`, `%=`, `&=`, `\|=`, `^=`, `<<=`, `>>=` | All augmented assignments work |
3912
4046
  | `raise X from Y` exception chaining | Sets `__cause__` on the thrown exception; `from None` also supported |
3913
4047
  | Starred assignment `a, *b, c = ...` | Works |
@@ -3922,9 +4056,10 @@ Python Feature Coverage
3922
4056
  | Classes, inheritance, decorators, `__dunder__` methods | Fully supported |
3923
4057
  | Nested class definitions | Accessible as `Outer.Inner` and via instance (`self.Inner`); arbitrary nesting depth; nested class may inherit from outer-scope classes |
3924
4058
  | List / dict / set comprehensions, generator expressions | Fully supported |
3925
- | f-strings, `str.format()`, `format()` builtin, all common `str.*` methods | Fully supported |
4059
+ | 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
4060
  | `abs()`, `divmod()`, `any()`, `all()`, `sum()`, `min()`, `max()` | All work |
3927
4061
  | `sorted()`, `reversed()`, `zip()`, `map()`, `filter()` | All work |
4062
+ | `list.sort()` / `sorted()` — `key`, `reverse`, comparators, `__lt__` | `key` and `reverse` keyword arguments both work; a positional **two-argument** function is auto-detected as a comparator (JS-style `.sort((a, b) => …)` / `functools.cmp_to_key` semantics) while a one-argument function is treated as a `key`; custom objects are ordered through their `__lt__` method (with a reflected `__gt__` fallback); the sort is stable, so equal elements keep their original order even under `reverse=True` |
3928
4063
  | `zip(strict=True)` | Raises `ValueError` when iterables have different lengths; equal-length iterables work normally |
3929
4064
  | `set` with full union/intersection/difference API | Fully supported |
3930
4065
  | `isinstance()`, `hasattr()`, `getattr()`, `setattr()`, `dir()` | All work |
@@ -3951,6 +4086,7 @@ Python Feature Coverage
3951
4086
  | `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
4087
  | `__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
4088
  | `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. |
4089
+ | 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
4090
  | `__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
4091
  | `ImportError`, `ModuleNotFoundError` | Both defined as runtime exception classes; `ModuleNotFoundError` is a subclass of `ImportError` (same as Python 3.6+). |
3956
4092
  | `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 +4094,7 @@ Python Feature Coverage
3958
4094
  | `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
4095
  | `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
4096
  | 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. |
4097
+ | `long(val[, base])` and `42n` literal — arbitrary-precision integers | Backed by JS `BigInt`. Literal syntax: `42n`, `0xFFn`, `0b1010n`, `0o77n`. Function syntax: `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
4098
  | `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
4099
  | `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
4100
  | `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,8 +4178,8 @@ Modules with a `src/lib/` implementation available are marked ✅. All others ar
4041
4178
  | `math` | ✅ | Full implementation in `src/lib/math.pyj` |
4042
4179
  | `random` | ✅ | RC4-seeded PRNG in `src/lib/random.pyj` |
4043
4180
  | `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 |
4045
- | `collections` | ✅ | `defaultdict`, `Counter`, `OrderedDict`, `deque` |
4181
+ | `encodings` | ✅ | UTF-8 encode/decode helpers and low-level base64 utilities; for the standard Python API use the `base64` module instead |
4182
+ | `collections` | ✅ | `defaultdict`, `Counter`, `OrderedDict`, `deque`, `namedtuple`, `ChainMap` |
4046
4183
  | `functools` | ✅ | `reduce`, `partial`, `wraps`, `lru_cache` |
4047
4184
  | `itertools` | ✅ | Common iteration tools |
4048
4185
  | `numpy` | ✅ | Full numpy-like library in `src/lib/numpy.pyj`; `numpy.random` and `numpy.linalg` sub-modules |
@@ -4055,19 +4192,26 @@ Modules with a `src/lib/` implementation available are marked ✅. All others ar
4055
4192
  | `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
4193
  | `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
4194
  | `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 |
4195
+ | `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` |
4196
+ | `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) |
4197
+ | `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` |
4198
+ | `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 |
4199
+ | `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 |
4200
+ | `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 |
4201
+ | `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 |
4202
+ | `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 |
4203
+ | `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 |
4204
+ | `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 |
4205
+ | `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` |
4206
+ | `pprint` | ✅ | `pformat(object, ...)` → str, `pprint(object[, stream], ...)`, `pp(object, ...)` (same as `pprint` but `sort_dicts=False` by default), `saferepr(object)`, `isreadable(object)`, `isrecursive(object)`, `PrettyPrinter` class in `src/lib/pprint.pyj`; keyword options `indent` (default 1), `width` (default 80), `depth`, `compact`, `sort_dicts` (default True), `underscore_numbers` (accepted, currently a no-op); a value's `repr()` is used when it fits within `width`, otherwise lists/tuples/dicts/sets/frozensets are broken across multiple lines with Python-style indentation; recursive references are detected and rendered as `<Recursion on … with id=…>`; pretty-prints RapydScript `dict`/`set`/`frozenset`, plain JS objects, and arrays; `pprint`/`pp` write to a `stream` object's `.write()` (defaulting to stdout via `print()`) |
4207
+ | `statistics` | ✅ | `mean`, `fmean`, `geometric_mean`, `harmonic_mean`, `median`, `median_low`, `median_high`, `median_grouped`, `mode`, `multimode`, `variance`, `pvariance`, `stdev`, `pstdev`, `quantiles`, `covariance`, `correlation`, `linear_regression` in `src/lib/statistics.pyj`; `NormalDist` class (`.mean`/`.median`/`.mode`/`.stdev`/`.variance` properties, `pdf()`, `cdf()`, `inv_cdf()`, `quantiles()`, `zscore()`, `overlap()`, `samples(n[, seed])`, `from_samples()`, plus arithmetic with scalars and other `NormalDist` instances); `StatisticsError` (a `ValueError` subclass); every data argument may be a list, a JS array, a string, or any iterable; `correlation()` supports `method='ranked'` (Spearman); `quantiles()` supports `method='inclusive'`/`'exclusive'`; `linear_regression()` returns an object with `.slope`/`.intercept`; all computation is IEEE-754 double precision (no `Fraction`/`Decimal` preservation), and `cdf()`/`overlap()` use a rational error-function approximation accurate to ~1e-7 |
4208
+ | `decimal` | ❌ | Fixed-precision decimal arithmetic not available; JS `Number` is IEEE-754 only |
4209
+ | `fractions` | ❌ | Rational arithmetic (`Fraction(num, den)`) not available |
4210
+ | `difflib` | ❌ | `unified_diff`, `SequenceMatcher`, `get_close_matches` not available |
4059
4211
  | `inspect` | ❌ | `signature`, `getmembers`, `isfunction` etc. not available |
4060
- | `asyncio` | ❌ | Event loop, `gather`, `sleep`, `Queue`, `Task` wrappers not available; use `async`/`await` |
4061
4212
  | `struct` | ❌ | Binary packing/unpacking not available |
4062
4213
  | `hashlib` | ❌ | MD5, SHA-256 etc. not available; use Web Crypto API via verbatim JS |
4063
4214
  | `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
- | `pprint` | ❌ | Pretty-printing not available |
4070
- | `logging` | ❌ | Logging framework not available; use `console.*` directly |
4071
4215
  | `unittest` | ❌ | Not available; RapydScript uses a custom test runner (`node bin/rapydscript test`) |
4072
4216
 
4073
4217
  ---
@@ -4080,7 +4224,7 @@ Features that exist in RapydScript but behave differently from standard Python:
4080
4224
  |---|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
4081
4225
  | `is` / `is not` | Object identity | Strict equality `===` / `!==`, which is not object/pointer comparison for JS primitives |
4082
4226
  | `//` 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) |
4227
+ | `%` 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
4228
  | `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
4229
  | `Exception.message` | Not standard; use `.args[0]` | `.message` is the standard attribute (JS `Error` style) |
4086
4230
  | 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 +4237,7 @@ Features that exist in RapydScript but behave differently from standard Python:
4093
4237
  | 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
4238
  | `dict` key ordering | Insertion order guaranteed (3.7+) | Depends on JS engine (V8 preserves insertion order in practice) |
4095
4239
  | 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 |
4240
+ | `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
4241
 
4098
4242
  ---
4099
4243
 
package/TODO.md CHANGED
@@ -1,136 +1,9 @@
1
1
 
2
- ### libraries
3
-
4
2
 
5
3
  - 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
-
9
- - rework tests to use jest
10
-
11
- - omit_function_metadata breaks imports - it needs to be changed to only affect imported modules, maybe?
12
4
 
13
5
  - vscode plugin based on language service?
14
6
 
15
- - examples of using js libraries in rapydscript in readme?
16
-
17
-
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).
19
-
20
-
21
- ```py
22
- # Over-engineered RapydScript hacking script — Python features on parade
23
-
24
- class HackAction:
25
- """Enum-like action namespace."""
26
- WEAKEN = 'weaken'
27
- GROW = 'grow'
28
- HACK = 'hack'
29
-
30
- @staticmethod
31
- def all():
32
- return [HackAction.WEAKEN, HackAction.GROW, HackAction.HACK]
33
-
34
-
35
- class ServerConfig:
36
- """Data-class-style target config with property + fluent builder."""
37
-
38
- def __init__(self, name, money_factor=1.0, sec_buffer=0.0):
39
- self._name = name
40
- self.money_factor = money_factor
41
- self.sec_buffer = sec_buffer
42
- self._tags = {'auto', 'hack'} # set literal
43
-
44
- @property
45
- def name(self):
46
- return self._name
47
-
48
- def __repr__(self):
49
- return "ServerConfig(name={}, tags={})".format(
50
- repr(self._name), sorted(list(self._tags))
51
- )
52
-
53
- def add_tags(self, *tags): # *args + fluent return
54
- for t in tags:
55
- self._tags.add(t)
56
- return self
57
-
58
-
59
- def decide_action(ns, target, money_thresh, sec_thresh):
60
- """next() + generator expression to pick highest-priority action."""
61
- checks = [
62
- (ns.getServerSecurityLevel(target) > sec_thresh, HackAction.WEAKEN),
63
- (ns.getServerMoneyAvailable(target) < money_thresh, HackAction.GROW),
64
- ]
65
- return next((action for cond, action in checks if cond), HackAction.HACK)
66
-
67
-
68
- async def breach(ns, target, exploits):
69
- """Apply available exploits via (exe, fn) tuple list, nuke, return summary."""
70
- # list of (exe filename, bound ns method) tuples
71
- exe_methods = [
72
- ('BruteSSH.exe', ns.brutessh),
73
- ('FTPCrack.exe', ns.ftpcrack),
74
- ('relaySMTP.exe', ns.relaysmtp),
75
- ]
76
- applied = [fn for exe, fn in exe_methods if ns.fileExists(exe, "home")]
77
- missing = [exe for exe, fn in exe_methods if not ns.fileExists(exe, "home")]
78
-
79
- if missing:
80
- ns.tprint("Missing: {}".format(', '.join(missing)))
81
-
82
- for fn in applied:
83
- fn(target)
84
- ns.nuke(target)
85
-
86
- return {'applied': len(applied), 'total': len(exploits),
87
- 'rooted': ns.hasRootAccess(target)}
88
-
89
-
90
- async def main(ns):
91
- config = ServerConfig("n00dles").add_tags('target', 'beginner') # fluent
92
- ns.tprint(repr(config)) # __repr__
93
-
94
- target = config.name # @property
95
- money_thresh = ns.getServerMaxMoney(target) * config.money_factor
96
- sec_thresh = ns.getServerMinSecurityLevel(target) + config.sec_buffer
97
-
98
- def by_name_len(p): # named key fn (required for
99
- return len(p[0]) # anonymous fns in call args)
100
- exploits = sorted([
101
- ('brutessh', 'BruteSSH.exe'),
102
- ('ftpcrack', 'FTPCrack.exe'),
103
- ('relaysmtp', 'relaySMTP.exe'),
104
- ], key=by_name_len)
105
-
106
- result = await breach(ns, target, exploits)
107
- ns.tprint("Breach: {applied}/{total} applied, rooted={rooted}".format(**result))
108
- assert result['rooted'], "Root access denied on {}!".format(target)
109
-
110
- dispatch = { # dict literal
111
- 'weaken': ns.weaken,
112
- 'grow': ns.grow,
113
- 'hack': ns.hack,
114
- }
115
- stats = {a: 0 for a in HackAction.all()} # dict comprehension
116
-
117
- cycle = 0
118
- while True:
119
- cycle += 1
120
- action = decide_action(ns, target, money_thresh, sec_thresh)
121
- stats[action] += 1
122
-
123
- if cycle % 10 is 0: # periodic summary
124
- counts = [stats[a] for a in HackAction.all()]
125
- summary = ', '.join(["{}: {}".format(a, v)
126
- for a, v in zip(HackAction.all(), counts)])
127
- ns.tprint("Cycle {} | {} any={} all={}".format(
128
- cycle, summary,
129
- any([v > 0 for v in counts]), # any()
130
- all([v > 0 for v in counts]) # all()
131
- ))
132
7
 
133
- await dispatch[action](target)
134
- ```
8
+ I would like you to add support for [python 7n style literal] 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.
135
9
 
136
- `ρσ_unpack is not defined`