rapydscript-ns 0.8.4 → 0.9.1

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 (141) hide show
  1. package/.agignore +1 -1
  2. package/.github/workflows/ci.yml +38 -38
  3. package/=template.pyj +5 -5
  4. package/CHANGELOG.md +26 -0
  5. package/HACKING.md +103 -103
  6. package/LICENSE +24 -24
  7. package/README.md +716 -169
  8. package/TODO.md +7 -2
  9. package/add-toc-to-readme +2 -2
  10. package/bin/export +75 -75
  11. package/bin/rapydscript +70 -70
  12. package/bin/web-repl-export +102 -102
  13. package/build +2 -2
  14. package/language-service/index.js +36 -27
  15. package/package.json +1 -1
  16. package/publish.py +37 -37
  17. package/release/baselib-plain-pretty.js +2358 -168
  18. package/release/baselib-plain-ugly.js +73 -3
  19. package/release/compiler.js +6283 -3093
  20. package/release/signatures.json +31 -30
  21. package/session.vim +4 -4
  22. package/setup.cfg +2 -2
  23. package/src/ast.pyj +1 -0
  24. package/src/baselib-builtins.pyj +340 -2
  25. package/src/baselib-bytes.pyj +664 -0
  26. package/src/baselib-errors.pyj +1 -1
  27. package/src/baselib-internal.pyj +267 -60
  28. package/src/baselib-itertools.pyj +110 -97
  29. package/src/baselib-str.pyj +22 -4
  30. package/src/compiler.pyj +36 -36
  31. package/src/errors.pyj +30 -30
  32. package/src/lib/abc.pyj +317 -0
  33. package/src/lib/aes.pyj +646 -646
  34. package/src/lib/contextlib.pyj +379 -0
  35. package/src/lib/copy.pyj +120 -120
  36. package/src/lib/dataclasses.pyj +532 -0
  37. package/src/lib/datetime.pyj +712 -0
  38. package/src/lib/elementmaker.pyj +83 -83
  39. package/src/lib/encodings.pyj +126 -126
  40. package/src/lib/enum.pyj +125 -0
  41. package/src/lib/gettext.pyj +569 -569
  42. package/src/lib/io.pyj +500 -0
  43. package/src/lib/itertools.pyj +580 -580
  44. package/src/lib/json.pyj +227 -0
  45. package/src/lib/math.pyj +193 -193
  46. package/src/lib/operator.pyj +11 -11
  47. package/src/lib/pythonize.pyj +20 -20
  48. package/src/lib/random.pyj +118 -118
  49. package/src/lib/re.pyj +504 -470
  50. package/src/lib/react.pyj +74 -74
  51. package/src/lib/traceback.pyj +63 -63
  52. package/src/lib/typing.pyj +577 -0
  53. package/src/lib/uuid.pyj +77 -77
  54. package/src/monaco-language-service/builtins.js +14 -4
  55. package/src/monaco-language-service/diagnostics.js +19 -20
  56. package/src/monaco-language-service/dts.js +550 -550
  57. package/src/output/classes.pyj +62 -26
  58. package/src/output/comments.pyj +45 -45
  59. package/src/output/exceptions.pyj +201 -201
  60. package/src/output/functions.pyj +78 -5
  61. package/src/output/jsx.pyj +164 -164
  62. package/src/output/loops.pyj +5 -2
  63. package/src/output/operators.pyj +100 -34
  64. package/src/output/treeshake.pyj +182 -182
  65. package/src/output/utils.pyj +72 -72
  66. package/src/parse.pyj +80 -16
  67. package/src/string_interpolation.pyj +72 -72
  68. package/src/tokenizer.pyj +10 -5
  69. package/src/unicode_aliases.pyj +576 -576
  70. package/src/utils.pyj +192 -192
  71. package/test/_import_one.pyj +37 -37
  72. package/test/_import_two/__init__.pyj +11 -11
  73. package/test/_import_two/level2/deep.pyj +4 -4
  74. package/test/_import_two/other.pyj +6 -6
  75. package/test/_import_two/sub.pyj +13 -13
  76. package/test/abc.pyj +291 -0
  77. package/test/aes_vectors.pyj +421 -421
  78. package/test/annotations.pyj +80 -80
  79. package/test/arithmetic_nostrict.pyj +88 -0
  80. package/test/arithmetic_types.pyj +169 -0
  81. package/test/baselib.pyj +91 -0
  82. package/test/bytes.pyj +467 -0
  83. package/test/classes.pyj +1 -0
  84. package/test/comparison_ops.pyj +173 -0
  85. package/test/contextlib.pyj +362 -0
  86. package/test/dataclasses.pyj +253 -0
  87. package/test/datetime.pyj +500 -0
  88. package/test/debugger_stmt.pyj +41 -0
  89. package/test/decorators.pyj +77 -77
  90. package/test/docstrings.pyj +39 -39
  91. package/test/elementmaker_test.pyj +45 -45
  92. package/test/enum.pyj +134 -0
  93. package/test/eval_exec.pyj +56 -0
  94. package/test/format.pyj +148 -0
  95. package/test/functions.pyj +151 -151
  96. package/test/generators.pyj +41 -41
  97. package/test/generic.pyj +370 -370
  98. package/test/imports.pyj +72 -72
  99. package/test/internationalization.pyj +73 -73
  100. package/test/io.pyj +316 -0
  101. package/test/json.pyj +196 -0
  102. package/test/lint.pyj +164 -164
  103. package/test/loops.pyj +85 -85
  104. package/test/numpy.pyj +734 -734
  105. package/test/object.pyj +64 -0
  106. package/test/omit_function_metadata.pyj +20 -20
  107. package/test/python_compat.pyj +17 -15
  108. package/test/python_features.pyj +70 -15
  109. package/test/regexp.pyj +83 -55
  110. package/test/repl.pyj +121 -121
  111. package/test/scoped_flags.pyj +76 -76
  112. package/test/tuples.pyj +96 -0
  113. package/test/typing.pyj +469 -0
  114. package/test/unit/index.js +116 -7
  115. package/test/unit/language-service-dts.js +543 -543
  116. package/test/unit/language-service-hover.js +455 -455
  117. package/test/unit/language-service.js +84 -0
  118. package/test/unit/web-repl.js +1337 -1
  119. package/test/vars_locals_globals.pyj +94 -0
  120. package/tools/cli.js +558 -547
  121. package/tools/compile.js +224 -219
  122. package/tools/completer.js +131 -131
  123. package/tools/embedded_compiler.js +262 -251
  124. package/tools/gettext.js +185 -185
  125. package/tools/ini.js +65 -65
  126. package/tools/lint.js +16 -19
  127. package/tools/msgfmt.js +187 -187
  128. package/tools/repl.js +223 -223
  129. package/tools/test.js +118 -118
  130. package/tools/utils.js +128 -128
  131. package/tools/web_repl.js +95 -95
  132. package/try +41 -41
  133. package/web-repl/env.js +196 -196
  134. package/web-repl/index.html +163 -163
  135. package/web-repl/main.js +252 -252
  136. package/web-repl/prism.css +139 -139
  137. package/web-repl/prism.js +113 -113
  138. package/web-repl/rapydscript.js +224 -224
  139. package/web-repl/sha1.js +25 -25
  140. package/PYTHON_DIFFERENCES_REPORT.md +0 -291
  141. package/PYTHON_FEATURE_COVERAGE.md +0 -200
package/README.md CHANGED
@@ -6,11 +6,14 @@ RapydScript
6
6
  [![Current Release](https://img.shields.io/npm/v/rapydscript-ns)](https://www.npmjs.com/package/rapydscript-ns)
7
7
  [![Known Vulnerabilities](https://snyk.io/test/github/ficocelliguy/rapydscript-ns/badge.svg)](https://snyk.io/test/github/ficocelliguy/rapydscript-ns)
8
8
 
9
- This is a fork of the original RapydScript that adds many new (not always
10
- backwards compatible) features. For more on the forking, [see the bottom of this file](#reasons-for-the-fork)
11
-
9
+ RapydScript is a pre-compiler for Javascript that uses syntax [identical](#python-feature-coverage) to modern Python. It transpiles
10
+ to native JS (with source maps) that reads like your Python code, but runs natively in the browser or node.
12
11
  [Try RapydScript-ns live via an in-browser REPL!](https://ficocelliguy.github.io/rapydscript-ns/)
13
12
 
13
+ This is a [fork of the original RapydScript](#reasons-for-the-fork) that adds many new features. The most notable
14
+ change is that all the Python features that are optional in RapydScript are now enabled by default.
15
+
16
+
14
17
  <!-- START doctoc generated TOC please keep comment here to allow auto update -->
15
18
  <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
16
19
  **Contents**
@@ -71,6 +74,7 @@ backwards compatible) features. For more on the forking, [see the bottom of this
71
74
  - [Virtual modules](#virtual-modules)
72
75
  - [Source maps](#source-maps)
73
76
  - [Running the tests](#running-the-tests)
77
+ - [Python Feature Coverage](#python-feature-coverage)
74
78
  - [Reasons for the fork](#reasons-for-the-fork)
75
79
 
76
80
  <!-- END doctoc generated TOC please keep comment here to allow auto update -->
@@ -83,20 +87,17 @@ RapydScript (pronounced 'RapidScript') is a pre-compiler for JavaScript,
83
87
  similar to CoffeeScript, but with cleaner, more readable syntax. The syntax is
84
88
  almost identical to Python, but RapydScript has a focus on performance and
85
89
  interoperability with external JavaScript libraries. This means that the
86
- JavaScript that RapydScript generates is performant and quite close to hand
90
+ JavaScript that RapydScript generates is performant and quite close to hand-
87
91
  written JavaScript.
88
92
 
89
93
  RapydScript allows to write your front-end in Python without the overhead that
90
- other similar frameworks introduce (the performance is the same as with pure
91
- JavaScript). To those familiar with CoffeeScript, RapydScript is like
92
- CoffeeScript, but inspired by Python's readability rather than Ruby's
93
- cleverness. To those familiar with Pyjamas, RapydScript brings many of the same
94
- features and support for Python syntax without the same overhead. Don't worry
95
- if you've never used either of the above-mentioned compilers, if you've ever
96
- had to write your code in pure JavaScript you'll appreciate RapydScript.
97
- RapydScript combines the best features of Python as well as JavaScript,
98
- bringing you features most other Pythonic JavaScript replacements overlook.
99
- Here are a few features of RapydScript:
94
+ other similar frameworks introduce - the performance is the same as with pure
95
+ JavaScript. To those familiar with CoffeeScript, RapydScript is similar, but
96
+ inspired by Python's readability rather than Ruby's cleverness. To those familiar
97
+ with Pyjamas, RapydScript brings many of the same features and support for Python
98
+ syntax without the same overhead. RapydScript combines the best features of Python
99
+ as well as JavaScript, bringing you features most other Pythonic JavaScript
100
+ replacements overlook. Here are a few features of RapydScript:
100
101
 
101
102
  - classes that work and feel similar to Python
102
103
  - an import system for modules/packages that works just like Python's
@@ -446,7 +447,6 @@ Class decorators are also supported with the caveat that the class properties
446
447
  must be accessed via the prototype property. For example:
447
448
 
448
449
  ```py
449
-
450
450
  def add_x(cls):
451
451
  cls.prototype.x = 1
452
452
 
@@ -699,6 +699,29 @@ head, *mid, tail = [1, 2, 3, 4, 5] # head=1, mid=[2, 3, 4], tail=5
699
699
 
700
700
  Starred assignment works with any iterable, including generators and strings (which are unpacked character by character). The starred variable always receives a list, even if it captures zero elements.
701
701
 
702
+ **Explicit tuple literals** using parentheses work the same as in Python and compile to JavaScript arrays:
703
+
704
+ ```py
705
+ empty = () # []
706
+ single = (42,) # [42] — trailing comma required for single-element tuple
707
+ pair = (1, 2) # [1, 2]
708
+ triple = ('a', 'b', 'c') # ['a', 'b', 'c']
709
+ nested = ((1, 2), (3, 4)) # [[1, 2], [3, 4]]
710
+ ```
711
+
712
+ A parenthesised expression without a trailing comma is **not** a tuple — `(x)` is just `x`. Add a comma to make it one: `(x,)`.
713
+
714
+ Tuple literals work naturally everywhere arrays do: as return values, function arguments, in `isinstance` checks, and in destructuring assignments:
715
+
716
+ ```py
717
+ def bounding_box(points):
718
+ return (min(p[0] for p in points), max(p[0] for p in points))
719
+
720
+ ok = isinstance(value, (int, str)) # tuple of types
721
+
722
+ (a, b), c = (1, 2), 3
723
+ ```
724
+
702
725
  Operators and keywords
703
726
  ------------------------
704
727
 
@@ -856,11 +879,11 @@ differences between RapydScript dicts and Python dicts.
856
879
  RapydScript dict objects in ```for..in``` loops.
857
880
 
858
881
  Fortunately, there is a builtin ```dict``` type that behaves just like Python's
859
- ```dict``` with all the same methods. The only caveat is that you have to add
860
- a special line to your RapydScript code to use these dicts, as shown below:
882
+ ```dict``` with all the same methods. The ``dict_literals`` and
883
+ ``overload_getitem`` flags are **on by default**, so dict literals and the
884
+ ``[]`` operator already behave like Python:
861
885
 
862
886
  ```py
863
- from __python__ import dict_literals, overload_getitem
864
887
  a = {1:1, 2:2}
865
888
  a[1] # == 1
866
889
  a[3] = 3
@@ -868,17 +891,10 @@ list(a.keys()) == [1, 2, 3]
868
891
  a['3'] # raises a KeyError as this is a proper python dict, not a JavaScript object
869
892
  ```
870
893
 
871
- The special line, called a *scoped flag* tells the compiler that from
872
- that point on, you want it to treat dict literals and the getitem operator `[]`
873
- as they are treated in python, not JavaScript.
874
-
875
- The scoped flags are local to each scope, that means that if you use it in a
876
- module, it will only affect code in that module, it you use it in a function,
877
- it will only affect code in that function. In fact, you can even use it to
878
- surround a few lines of code, like this:
894
+ These are *scoped flags* local to the scope where they appear. You can
895
+ disable them for a region of code using the ``no_`` prefix:
879
896
 
880
897
  ```py
881
- from __python__ import dict_literals, overload_getitem
882
898
  a = {1:1, 2:2}
883
899
  isinstance(a, dict) == True
884
900
  from __python__ import no_dict_literals, no_overload_getitem
@@ -962,11 +978,10 @@ result = {**defaults, 'weight': 5}
962
978
  # result == {'color': 'blue', 'size': 10, 'weight': 5}
963
979
  ```
964
980
 
965
- This works for both plain JavaScript-object dicts (the default) and Python
966
- `dict` objects (enabled via `from __python__ import dict_literals`):
981
+ This works for both plain JavaScript-object dicts and Python `dict` objects
982
+ (``dict_literals`` is on by default):
967
983
 
968
984
  ```py
969
- from __python__ import dict_literals, overload_getitem
970
985
  pd1 = {'a': 1}
971
986
  pd2 = {'b': 2}
972
987
  merged = {**pd1, **pd2} # isinstance(merged, dict) == True
@@ -977,12 +992,10 @@ and `dict.update()` for Python dicts.
977
992
 
978
993
  ### Dict merge operator `|` and `|=` (Python 3.9+)
979
994
 
980
- When `from __python__ import overload_operators, dict_literals` is active,
981
- Python dicts support the `|` (merge) and `|=` (update in-place) operators:
995
+ Python dicts support the `|` (merge) and `|=` (update in-place) operators
996
+ (requires ``overload_operators`` and ``dict_literals``, both on by default):
982
997
 
983
998
  ```py
984
- from __python__ import overload_operators, dict_literals
985
-
986
999
  d1 = {'x': 1, 'y': 2}
987
1000
  d2 = {'y': 99, 'z': 3}
988
1001
 
@@ -997,17 +1010,15 @@ d1 |= d2 # d1 is now {'x': 1, 'y': 99, 'z': 3}
997
1010
  `d1 |= d2` merges `d2` into `d1` and returns `d1`.
998
1011
 
999
1012
  Without `overload_operators` the `|` symbol is bitwise OR — use
1000
- `{**d1, **d2}` spread syntax as a flag-free alternative.
1013
+ `{**d1, **d2}` spread syntax as an alternative if the flag is disabled.
1001
1014
 
1002
1015
 
1003
1016
  ### Arithmetic operator overloading
1004
1017
 
1005
1018
  RapydScript supports Python-style arithmetic operator overloading via the
1006
- ``overload_operators`` scoped flag:
1019
+ ``overload_operators`` flag, which is **on by default**:
1007
1020
 
1008
1021
  ```py
1009
- from __python__ import overload_operators
1010
-
1011
1022
  class Vector:
1012
1023
  def __init__(self, x, y):
1013
1024
  self.x = x
@@ -1048,31 +1059,52 @@ The supported dunder methods are:
1048
1059
  Augmented assignment (``+=``, ``-=``, etc.) first tries the in-place method
1049
1060
  (``__iadd__``, ``__isub__``, …) and then falls back to the binary method.
1050
1061
 
1051
- If neither operand defines the relevant dunder method the operation falls back
1052
- to the native JavaScript operator, so plain numbers, strings, and booleans
1053
- continue to work as expected with no performance penalty when no dunder method
1054
- is defined.
1062
+ If neither operand defines the relevant dunder method, the operation enforces
1063
+ Python-style type compatibility before falling back to the native JavaScript
1064
+ operator:
1065
+
1066
+ - ``number`` ± ``number`` → allowed (including ``bool``, which is treated as an integer subclass)
1067
+ - ``str`` + ``str`` → allowed
1068
+ - ``str`` * ``int`` and ``list`` * ``int`` → allowed (and also with ``bool`` in place of ``int``)
1069
+ - Anything else raises ``TypeError`` with a Python-style message, e.g.:
1070
+
1071
+ ```py
1072
+ 1 + 'x' # TypeError: unsupported operand type(s) for +: 'int' and 'str'
1073
+ 'a' - 1 # TypeError: unsupported operand type(s) for -: 'str' and 'int'
1074
+ [1] + 'b' # TypeError: unsupported operand type(s) for +: 'list' and 'str'
1075
+ ```
1076
+
1077
+ This type-checking is controlled by the ``strict_arithmetic`` flag, which is
1078
+ **on by default** when ``overload_operators`` is active. To revert to
1079
+ JavaScript's silent coercion behaviour (e.g. ``1 + 'x'`` → ``'1x'``) without
1080
+ disabling dunder dispatch, use:
1081
+
1082
+ ```py
1083
+ from __python__ import no_strict_arithmetic
1084
+ ```
1085
+
1086
+ When ``overload_operators`` is
1087
+ disabled (``from __python__ import no_overload_operators``) the operators
1088
+ compile directly to JavaScript and no type checking is performed.
1055
1089
 
1056
1090
  When `overload_operators` is active, string and list repetition with `*` works just like Python:
1057
1091
 
1058
1092
  ```py
1059
- from __python__ import overload_operators
1060
1093
  'ha' * 3 # 'hahaha'
1061
1094
  3 * 'ha' # 'hahaha'
1062
1095
  [0] * 4 # [0, 0, 0, 0]
1063
1096
  [1, 2] * 2 # [1, 2, 1, 2]
1064
1097
  ```
1065
1098
 
1066
- Because the dispatch adds one or two property lookups per operation, the flag
1067
- is **opt-in** rather than always-on. Enable it only in the files or scopes
1068
- where you need it.
1099
+ Because the dispatch adds one or two property lookups per operation, you can
1100
+ disable it in scopes where it is not needed with
1101
+ ``from __python__ import no_overload_operators``.
1069
1102
 
1070
1103
  The ``collections.Counter`` class defines ``__add__``, ``__sub__``, ``__or__``,
1071
1104
  and ``__and__``. With ``overload_operators`` you can use the natural operator
1072
1105
  syntax:
1073
1106
 
1074
1107
  ```py
1075
- from __python__ import overload_getitem, overload_operators
1076
1108
  from collections import Counter
1077
1109
 
1078
1110
  c1 = Counter('aab')
@@ -1090,9 +1122,35 @@ RapydScript dicts (but not arbitrary javascript objects). You can also define
1090
1122
  the ``__eq__(self, other)`` method in your classes to have these operators work
1091
1123
  for your own types.
1092
1124
 
1093
- RapydScript does not overload the ordering operators ```(>, <, >=,
1094
- <=)``` as doing so would be a big performance impact (function calls in
1095
- JavaScript are very slow). So using them on containers is useless.
1125
+ The ordering operators ``<``, ``>``, ``<=``, ``>=`` dispatch to Python-style
1126
+ dunder methods and compare lists lexicographically just like Python:
1127
+
1128
+ ```py
1129
+ from __python__ import overload_operators # on by default
1130
+
1131
+ # List comparison — lexicographic order
1132
+ assert [1, 2] < [1, 3] # True (first differing element: 2 < 3)
1133
+ assert [1, 2] < [1, 2, 0] # True (prefix is smaller)
1134
+ assert [2] > [1, 99] # True (first element dominates)
1135
+
1136
+ # Works with custom __lt__ / __gt__ / __le__ / __ge__ on objects
1137
+ class Version:
1138
+ def __init__(self, major, minor):
1139
+ self.major = major
1140
+ self.minor = minor
1141
+ def __lt__(self, other):
1142
+ return (self.major, self.minor) < (other.major, other.minor)
1143
+
1144
+ v1 = Version(1, 5)
1145
+ v2 = Version(2, 0)
1146
+ assert v1 < v2 # dispatches to __lt__
1147
+
1148
+ # Incompatible types raise TypeError, just like Python
1149
+ try:
1150
+ result = [1] < 42
1151
+ except TypeError as e:
1152
+ print(e) # '<' not supported between instances of 'list' and 'int'
1153
+ ```
1096
1154
 
1097
1155
  Chained comparisons work just like Python — each middle operand is evaluated only once:
1098
1156
 
@@ -1100,18 +1158,13 @@ Chained comparisons work just like Python — each middle operand is evaluated o
1100
1158
  # All of these work correctly, including mixed-direction chains
1101
1159
  assert 1 < 2 < 3 # True
1102
1160
  assert 1 < 2 > 0 # True (1<2 AND 2>0)
1103
- assert 1 < 2 > 3 == False # 1<2 AND 2>3 = True AND False = False
1161
+ assert [1] < [2] < [3] # True (lexicographic chain)
1104
1162
  ```
1105
1163
 
1106
1164
  ### Python Truthiness and `__bool__`
1107
1165
 
1108
- By default RapydScript uses JavaScript truthiness, where empty arrays `[]` and
1109
- empty objects `{}` are **truthy**. Activate full Python truthiness semantics
1110
- with:
1111
-
1112
- ```py
1113
- from __python__ import truthiness
1114
- ```
1166
+ RapydScript uses Python truthiness semantics by default (``truthiness`` is
1167
+ **on by default**):
1115
1168
 
1116
1169
  When this flag is active:
1117
1170
 
@@ -1122,8 +1175,6 @@ When this flag is active:
1122
1175
  - **All condition positions** (`if`, `while`, `assert`, `not`, ternary) use Python semantics.
1123
1176
 
1124
1177
  ```py
1125
- from __python__ import truthiness
1126
-
1127
1178
  class Empty:
1128
1179
  def __bool__(self): return False
1129
1180
 
@@ -1137,16 +1188,14 @@ z = [1] and 'ok' # z == 'ok'
1137
1188
 
1138
1189
  The flag is **scoped** — it applies until the end of the enclosing
1139
1190
  function or class body. Use `from __python__ import no_truthiness` to
1140
- disable it in a sub-scope.
1191
+ disable it in a sub-scope where JavaScript truthiness is needed.
1141
1192
 
1142
1193
  ### Callable Objects (`__call__`)
1143
1194
 
1144
1195
  Any class that defines `__call__` can be invoked directly with `obj(args)`,
1145
- just like Python callable objects. This requires `from __python__ import truthiness`:
1196
+ just like Python callable objects:
1146
1197
 
1147
1198
  ```python
1148
- from __python__ import truthiness
1149
-
1150
1199
  class Multiplier:
1151
1200
  def __init__(self, factor):
1152
1201
  self.factor = factor
@@ -1192,6 +1241,108 @@ Mutation methods (`add`, `remove`, `discard`, `clear`, `update`) are not
1192
1241
  present on `frozenset` instances, enforcing immutability at the API level.
1193
1242
  `frozenset` objects can be iterated and copied with `.copy()`.
1194
1243
 
1244
+ ### `bytes` and `bytearray`
1245
+
1246
+ RapydScript provides `bytes` (immutable) and `bytearray` (mutable) builtins
1247
+ that match Python's semantics and are backed by plain JS arrays of integers
1248
+ in the range 0–255.
1249
+
1250
+ #### `b'...'` bytes literals
1251
+
1252
+ RapydScript supports Python `b'...'` bytes literal syntax. The prefix may be
1253
+ `b` or `B` (and `rb`/`br` for raw bytes where backslash sequences are not
1254
+ interpreted). Adjacent bytes literals are automatically concatenated, just
1255
+ like adjacent string literals.
1256
+
1257
+ ```python
1258
+ b'Hello' # bytes([72, 101, 108, 108, 111])
1259
+ b'\x00\xff' # bytes([0, 255]) — hex escape sequences work
1260
+ b'\n\t\r' # bytes([10, 9, 13]) — control-char escapes work
1261
+ b'foo' b'bar' # bytes([102, 111, 111, 98, 97, 114]) — concatenation
1262
+ rb'\n\t' # bytes([92, 110, 92, 116]) — raw: backslashes literal
1263
+ B'ABC' # bytes([65, 66, 67]) — uppercase B also accepted
1264
+ ```
1265
+
1266
+ Each `b'...'` literal is compiled to a `bytes(str, 'latin-1')` call, so the
1267
+ full `bytes` API is available on the result.
1268
+
1269
+ #### Construction
1270
+
1271
+ ```python
1272
+ bytes() # empty bytes
1273
+ bytes(4) # b'\x00\x00\x00\x00' (4 zero bytes)
1274
+ b'\x00\x00\x00\x00' # same — bytes literal syntax
1275
+ bytes([72, 101, 108, 111]) # b'Hello'
1276
+ b'Hell\x6f' # same — mix of ASCII and hex escapes
1277
+ bytes('Hello', 'utf-8') # encode a string
1278
+ bytes('ABC', 'ascii') # ASCII / latin-1 encoding also accepted
1279
+ bytes.fromhex('48656c6c6f') # from hex string → b'Hello'
1280
+
1281
+ bytearray() # empty mutable byte sequence
1282
+ bytearray(3) # bytearray(b'\x00\x00\x00')
1283
+ bytearray([1, 2, 3]) # from list of ints
1284
+ bytearray('Hi', 'utf-8') # from string
1285
+ bytearray(some_bytes) # mutable copy of a bytes object
1286
+ ```
1287
+
1288
+ `Uint8Array` values may also be passed as the source argument.
1289
+
1290
+ #### Common operations (both `bytes` and `bytearray`)
1291
+
1292
+ ```python
1293
+ b = bytes('Hello', 'utf-8')
1294
+
1295
+ len(b) # 5
1296
+ b[0] # 72 (integer)
1297
+ b[-1] # 111
1298
+ b[1:4] # bytes([101, 108, 108]) (slice → new bytes)
1299
+ b[::2] # every other byte
1300
+
1301
+ b + bytes([33]) # concatenate → b'Hello!'
1302
+ b * 2 # repeat → b'HelloHello'
1303
+ 72 in b # True (integer membership)
1304
+ bytes([101, 108]) in b # True (subsequence membership)
1305
+ b == bytes([72, 101, 108, 108, 111]) # True
1306
+
1307
+ b.hex() # '48656c6c6f'
1308
+ b.hex(':', 2) # '48:65:6c:6c:6f' (separator every 2 bytes)
1309
+ b.decode('utf-8') # 'Hello'
1310
+ b.decode('ascii') # works for ASCII-range bytes
1311
+
1312
+ b.find(bytes([108, 108])) # 2
1313
+ b.index(101) # 1
1314
+ b.rfind(108) # 3
1315
+ b.count(108) # 2
1316
+ b.startswith(bytes([72])) # True
1317
+ b.endswith(bytes([111])) # True
1318
+ b.split(bytes([108])) # [b'He', b'', b'o']
1319
+ b.replace(bytes([108]), bytes([76])) # b'HeLLo'
1320
+ b.strip() # strip leading/trailing whitespace bytes
1321
+ b.upper() # b'HELLO'
1322
+ b.lower() # b'hello'
1323
+ bytes(b' ').join([bytes('a', 'ascii'), bytes('b', 'ascii')]) # b'a b'
1324
+
1325
+ repr(b) # "b'Hello'"
1326
+ isinstance(b, bytes) # True
1327
+ isinstance(bytearray([1]), bytes) # True (bytearray is a subclass of bytes)
1328
+ ```
1329
+
1330
+ #### `bytearray`-only mutation methods
1331
+
1332
+ ```python
1333
+ ba = bytearray([1, 2, 3])
1334
+ ba[0] = 99 # item assignment
1335
+ ba[1:3] = bytes([20, 30]) # slice assignment
1336
+ ba.append(4) # add one byte
1337
+ ba.extend([5, 6]) # add multiple bytes
1338
+ ba.insert(0, 0) # insert at index
1339
+ ba.pop() # remove and return last byte (or ba.pop(i))
1340
+ ba.remove(20) # remove first occurrence of value
1341
+ ba.reverse() # reverse in place
1342
+ ba.clear() # remove all bytes
1343
+ ba += bytearray([7, 8]) # in-place concatenation
1344
+ ```
1345
+
1195
1346
  ### `issubclass`
1196
1347
 
1197
1348
  `issubclass(cls, classinfo)` checks whether a class is a subclass of another
@@ -1260,6 +1411,152 @@ class Bar:
1260
1411
  hash(Bar()) # TypeError: unhashable type: 'Bar'
1261
1412
  ```
1262
1413
 
1414
+ ### `eval` and `exec`
1415
+
1416
+ Both `eval` and `exec` are supported with Python-compatible signatures.
1417
+ String literals passed to them are treated as **RapydScript source code**: the
1418
+ compiler parses and transpiles the string at compile time, so you write
1419
+ RapydScript (not raw JavaScript) inside the quotes — just like Python's
1420
+ `eval`/`exec` take Python source strings.
1421
+
1422
+ #### `eval(expr[, globals[, locals]])`
1423
+
1424
+ * **One argument** — the compiled expression is passed to the native JS `eval`,
1425
+ giving direct scope access to module-level variables:
1426
+
1427
+ ```python
1428
+ result = eval("1 + 2") # 3
1429
+ x = 7
1430
+ sq = eval("x * x") # 49 (x is in scope)
1431
+ ```
1432
+
1433
+ * **Two or three arguments** — uses the `Function` constructor with explicit
1434
+ variable bindings. `locals` override `globals` when both are given:
1435
+
1436
+ ```python
1437
+ eval("x + y", {"x": 10, "y": 5}) # 15
1438
+ eval("x", {"x": 1}, {"x": 99}) # 99 (local overrides global)
1439
+ ```
1440
+
1441
+ #### `exec(code[, globals[, locals]])`
1442
+
1443
+ Executes a RapydScript code string and always returns `None`, like Python's
1444
+ `exec`.
1445
+
1446
+ * **One argument** — the compiled code runs via native `eval`:
1447
+
1448
+ ```python
1449
+ exec("print('hi')") # prints hi
1450
+ exec("_x = 42") # _x is discarded after exec returns
1451
+ ```
1452
+
1453
+ * **Two or three arguments** — uses the `Function` constructor. Mutable
1454
+ objects (arrays, dicts) passed in `globals` are accessible by reference, so
1455
+ mutations are visible in the caller:
1456
+
1457
+ ```python
1458
+ log = []
1459
+ exec("log.append(1 + 2)", {"log": log})
1460
+ print(log[0]) # 3
1461
+
1462
+ def add(a, b): log.append(a + b);
1463
+ exec("fn(10, 7)", {"fn": add, "log": log})
1464
+ print(log[1]) # 17
1465
+ ```
1466
+
1467
+ > **Note:** Because strings are compiled at compile time, only **string
1468
+ > literals** are transformed — dynamic strings assembled at runtime are passed
1469
+ > through unchanged. `exec(code)` cannot modify the caller's local variables,
1470
+ > matching Python 3 semantics.
1471
+
1472
+ ### `vars`, `locals`, and `globals`
1473
+
1474
+ #### `vars(obj)`
1475
+
1476
+ Returns a `dict` snapshot of the object's own instance attributes, mirroring
1477
+ Python's `obj.__dict__`. Internal RapydScript properties (prefixed `ρσ`) are
1478
+ excluded automatically. Mutating the returned dict does **not** affect the
1479
+ original object.
1480
+
1481
+ ```python
1482
+ class Point:
1483
+ def __init__(self, x, y):
1484
+ self.x = x
1485
+ self.y = y
1486
+
1487
+ p = Point(3, 4)
1488
+ d = vars(p)
1489
+ print(d['x'], d['y']) # 3 4
1490
+ print(list(d.keys())) # ['x', 'y']
1491
+ ```
1492
+
1493
+ #### `vars()` and `locals()`
1494
+
1495
+ Both return an empty `dict`. JavaScript has no runtime mechanism for
1496
+ introspecting the local call-frame's variables, so a faithful implementation is
1497
+ not possible. Use them as placeholders in patterns that require a dict, or
1498
+ pass explicit dicts where you need named-value lookup.
1499
+
1500
+ ```python
1501
+ loc = locals() # {}
1502
+ v = vars() # {}
1503
+ ```
1504
+
1505
+ #### `globals()`
1506
+
1507
+ Returns a `dict` snapshot of the JS global object (`globalThis` / `window` /
1508
+ `global`). Module-level RapydScript variables compiled inside an IIFE or
1509
+ module wrapper will **not** appear here; use a shared plain dict for that
1510
+ pattern instead.
1511
+
1512
+ ```python
1513
+ g = globals()
1514
+ # g contains JS runtime globals such as Math, console, etc.
1515
+ print('Math' in g) # True (in a browser or Node context)
1516
+ ```
1517
+
1518
+ ### Complex numbers
1519
+
1520
+ RapydScript supports Python's complex number type via the `complex` builtin and
1521
+ the `j`/`J` imaginary literal suffix.
1522
+
1523
+ ```py
1524
+ # Imaginary literal suffix
1525
+ z = 4j # complex(0, 4)
1526
+ w = 3 + 4j # complex(3, 4) — parsed as 3 + complex(0, 4)
1527
+
1528
+ # Constructor
1529
+ z1 = complex(3, 4) # real=3, imag=4
1530
+ z2 = complex(5) # real=5, imag=0
1531
+ z3 = complex() # 0+0j
1532
+ z4 = complex('2-3j') # string parsing
1533
+
1534
+ # Attributes
1535
+ print(z1.real) # 3
1536
+ print(z1.imag) # 4
1537
+
1538
+ # Methods
1539
+ print(z1.conjugate()) # (3-4j)
1540
+ print(abs(z1)) # 5.0 — dispatches __abs__
1541
+
1542
+ # Arithmetic (requires overload_operators, which is on by default)
1543
+ from __python__ import overload_operators
1544
+ print(z1 + z2) # (8+4j)
1545
+ print(z1 - z2) # (-2+4j)
1546
+ print(z1 * z2) # (15+20j)
1547
+ print(z1 / z2) # (0.6+0.8j)
1548
+
1549
+ # Truthiness, repr, isinstance
1550
+ print(bool(complex(0, 0))) # False
1551
+ print(repr(z1)) # (3+4j)
1552
+ print(isinstance(z1, complex)) # True
1553
+ ```
1554
+
1555
+ The `j`/`J` suffix is handled at the tokenizer level: `4j` is parsed into an
1556
+ `AST_Call(complex, 0, 4)` node, so it composes naturally with all other
1557
+ expressions. Mixed expressions like `3 + 4j` work without `overload_operators`
1558
+ because `ρσ_list_add` dispatches `__radd__` on the right operand.
1559
+
1263
1560
  ### Attribute-Access Dunders
1264
1561
 
1265
1562
  RapydScript supports the four Python attribute-interception hooks:
@@ -1495,8 +1792,33 @@ format('hi', '>10') # ' hi' — right-aligned in 10-char field
1495
1792
  format(42) # '42' — no spec: same as str(42)
1496
1793
  ```
1497
1794
 
1498
- Objects with a `__format__` method are dispatched to it, matching Python's
1499
- protocol exactly.
1795
+ Objects with a `__format__` method are dispatched to it in all three contexts
1796
+ `format(obj, spec)`, `str.format('{:spec}', obj)`, and `f'{obj:spec}'` —
1797
+ matching Python's protocol exactly. Every user-defined class automatically
1798
+ gets a default `__format__` that returns `str(self)` for an empty spec and
1799
+ raises `TypeError` for any other spec, just like `object.__format__` in
1800
+ Python:
1801
+
1802
+ ```py
1803
+ class Money:
1804
+ def __init__(self, amount):
1805
+ self.amount = amount
1806
+ def __str__(self):
1807
+ return str(self.amount)
1808
+ def __format__(self, spec):
1809
+ if spec == 'usd':
1810
+ return '$' + str(self.amount)
1811
+ return format(self.amount, spec) # delegate numeric specs
1812
+
1813
+ m = Money(42)
1814
+ format(m, 'usd') # '$42'
1815
+ str.format('{:usd}', m) # '$42'
1816
+ f'{m:usd}' # '$42'
1817
+ f'{m:.2f}' # '42.00'
1818
+ ```
1819
+
1820
+ The `!r`, `!s`, and `!a` conversion flags apply `repr()`/`str()`/`repr()` to the
1821
+ value before formatting, bypassing `__format__` (same as Python).
1500
1822
 
1501
1823
  String predicate methods are also available:
1502
1824
 
@@ -1785,18 +2107,22 @@ Regular Expressions
1785
2107
  ----------------------
1786
2108
 
1787
2109
  RapydScript includes a ```re``` module that mimics the interface of the Python
1788
- re module. However, it uses the JavaScript regular expression functionality
1789
- under the hood, which has several differences from the Python regular
1790
- expression engine. Most importantly:
1791
-
1792
- - it does not support lookbehind and group existence assertions
1793
- - it does not support unicode (on ES 6 runtimes, unicode is supported, but
1794
- with a different syntax). You can test for the presence of unicode support with
1795
- ```re.supports_unicode```.
1796
- - The ``MatchObject``'s ``start()`` and ``end()`` method cannot return correct values
1797
- for subgroups for some kinds of regular expressions, for example, those
1798
- with nested captures. This is because the JavaScript regex API does not expose
1799
- this information, so it has to be guessed via a heuristic.
2110
+ re module. It uses the JavaScript regular expression engine under the hood, so
2111
+ it supports the full feature set available in modern JS runtimes:
2112
+
2113
+ - **Lookbehind assertions** — both positive `(?<=...)` and negative `(?<!...)`
2114
+ are fully supported (ES2018+), including variable-width lookbehind.
2115
+ - **Unicode** the `u` flag is added automatically on ES2015+ runtimes.
2116
+ `re.supports_unicode` reflects whether the runtime supports it.
2117
+ - **`re.fullmatch()`** — matches the entire string against the pattern.
2118
+ - **`re.S` / `re.DOTALL`** make `.` match newlines; `re.S` is now the
2119
+ canonical alias (matching Python), with `re.D` kept for compatibility.
2120
+ - **`re.NOFLAG`** (= 0) the Python 3.11 no-flags sentinel.
2121
+ - **`MatchObject.start()`/`.end()`** return accurate positions for all
2122
+ sub-groups on runtimes that support the ES2022 `d` (hasIndices) flag
2123
+ (Node 18+, Chrome 90+). On older runtimes a heuristic is used.
2124
+ - **Conditional groups** `(?(id)yes|no)` — not supported in JavaScript;
2125
+ an `re.error` is raised if they appear in the pattern.
1800
2126
 
1801
2127
  You can use the JavaScript regex literal syntax, including verbose regex
1802
2128
  literals, as shown below. In verbose mode, whitespace is ignored and # comments
@@ -1811,6 +2137,11 @@ re.match(///
1811
2137
  a # a comment
1812
2138
  b # Another comment
1813
2139
  ///, 'ab')
2140
+
2141
+ # Lookbehind and fullmatch
2142
+ re.sub(r'(?<=\d)px', '', '12px 3em') # '12 3em'
2143
+ re.fullmatch(r'\w+', 'hello') # MatchObject
2144
+ re.fullmatch(r'\w+', 'hello world') # None
1814
2145
  ```
1815
2146
 
1816
2147
  JSX Support
@@ -1818,11 +2149,8 @@ JSX Support
1818
2149
 
1819
2150
  RapydScript supports JSX syntax for building React UI components. JSX elements compile directly to `React.createElement()` calls, so the output is plain JavaScript — no Babel or JSX transform step is needed.
1820
2151
 
1821
- Enable JSX support with:
1822
-
1823
- ```py
1824
- from __python__ import jsx
1825
- ```
2152
+ JSX support is **on by default**. The ``jsx`` flag can be disabled with
2153
+ ``from __python__ import no_jsx`` if needed.
1826
2154
 
1827
2155
  ### Requirements
1828
2156
 
@@ -1838,8 +2166,6 @@ from __python__ import jsx
1838
2166
  RapydScript compiles JSX to `React.createElement()` calls. For example:
1839
2167
 
1840
2168
  ```py
1841
- from __python__ import jsx
1842
-
1843
2169
  def Greeting(props):
1844
2170
  return <h1>Hello, {props.name}!</h1>
1845
2171
  ```
@@ -1859,8 +2185,6 @@ Lowercase tags (`div`, `span`, `h1`, …) become string arguments. Capitalised n
1859
2185
  String, expression, boolean, and hyphenated attribute names all work:
1860
2186
 
1861
2187
  ```py
1862
- from __python__ import jsx
1863
-
1864
2188
  def Form(props):
1865
2189
  return (
1866
2190
  <form>
@@ -1880,8 +2204,6 @@ Hyphenated names (e.g. `aria-label`, `data-id`) are automatically quoted as obje
1880
2204
  ### Nested elements and expressions
1881
2205
 
1882
2206
  ```py
1883
- from __python__ import jsx
1884
-
1885
2207
  def UserList(users):
1886
2208
  return (
1887
2209
  <ul className="user-list">
@@ -1895,8 +2217,6 @@ def UserList(users):
1895
2217
  Use `<>...</>` to return multiple elements without a wrapper node. Fragments compile to `React.createElement(React.Fragment, null, ...)`:
1896
2218
 
1897
2219
  ```py
1898
- from __python__ import jsx
1899
-
1900
2220
  def TwoItems():
1901
2221
  return (
1902
2222
  <>
@@ -1909,8 +2229,6 @@ def TwoItems():
1909
2229
  ### Self-closing elements
1910
2230
 
1911
2231
  ```py
1912
- from __python__ import jsx
1913
-
1914
2232
  def Avatar(props):
1915
2233
  return <img src={props.url} alt={props.name} />
1916
2234
  ```
@@ -1918,8 +2236,6 @@ def Avatar(props):
1918
2236
  ### Spread attributes
1919
2237
 
1920
2238
  ```py
1921
- from __python__ import jsx
1922
-
1923
2239
  def Button(props):
1924
2240
  return <button {...props}>Click me</button>
1925
2241
  ```
@@ -1931,8 +2247,6 @@ Compiles to `React.createElement("button", {...props}, "Click me")`.
1931
2247
  Capitalised names and dot-notation are treated as component references (not quoted strings):
1932
2248
 
1933
2249
  ```py
1934
- from __python__ import jsx
1935
-
1936
2250
  def App():
1937
2251
  return (
1938
2252
  <Router.Provider>
@@ -1965,7 +2279,6 @@ RapydScript ships a `react` standard library module that re-exports every standa
1965
2279
  ### Importing hooks
1966
2280
 
1967
2281
  ```py
1968
- from __python__ import jsx
1969
2282
  from react import useState, useEffect, useCallback, useMemo, useRef
1970
2283
 
1971
2284
  def Counter():
@@ -2096,7 +2409,6 @@ def ThemedButton():
2096
2409
  **useRef**
2097
2410
 
2098
2411
  ```py
2099
- from __python__ import jsx
2100
2412
  from react import useRef
2101
2413
 
2102
2414
  def FocusInput():
@@ -2109,7 +2421,6 @@ def FocusInput():
2109
2421
  **memo**
2110
2422
 
2111
2423
  ```py
2112
- from __python__ import jsx
2113
2424
  from react import memo
2114
2425
 
2115
2426
  def Row(props):
@@ -2121,7 +2432,6 @@ MemoRow = memo(Row)
2121
2432
  **forwardRef**
2122
2433
 
2123
2434
  ```py
2124
- from __python__ import jsx
2125
2435
  from react import forwardRef
2126
2436
 
2127
2437
  def FancyInput(props, ref):
@@ -2135,7 +2445,6 @@ FancyInputWithRef = forwardRef(FancyInput)
2135
2445
  You can extend `React.Component` directly without importing it, or import `Component` from the `react` module:
2136
2446
 
2137
2447
  ```py
2138
- from __python__ import jsx
2139
2448
  from react import Component
2140
2449
 
2141
2450
  class Greeter(Component):
@@ -2601,67 +2910,51 @@ You could also use `external` decorator to bypass improperly imported RapydScrip
2601
2910
 
2602
2911
  ### Method Binding
2603
2912
 
2604
- By default, RapydScript does not bind methods to the classes they're declared under. This behavior is unlike Python, but very much like the rest of JavaScript. For example, consider this code:
2605
-
2606
- ```py
2607
- class Boy:
2608
- def __init__(self, name):
2609
- self.name = name
2610
-
2611
- def greet(self):
2612
- if self:
2613
- print('My name is' + self.name)
2913
+ RapydScript automatically binds methods to their objects by default (the
2914
+ ``bound_methods`` flag is **on by default**). This means method references
2915
+ like ``getattr(obj, 'method')`` work correctly when called later.
2614
2916
 
2615
- tod = Boy('Tod')
2616
- tod.greet() # Hello, my name is Tod
2617
- getattr(tod, 'greet')() # prints nothing
2618
- ```
2917
+ If you need to disable auto-binding in a scope, use
2918
+ ``from __python__ import no_bound_methods``.
2619
2919
 
2620
- In some cases, however, you may wish for the functions in the class to be
2621
- automatically bound when the objects of that class are instantiated. In order
2622
- to do that, use a *scoped flag*, which is a simple instruction to the compiler
2623
- telling it to auto-bind methods, as shown below:
2920
+ For example:
2624
2921
 
2625
2922
  ```py
2923
+ class C:
2924
+ def __init__(self):
2925
+ self.a = 3
2626
2926
 
2627
- class AutoBound:
2628
- from __python__ import bound_methods
2629
-
2630
- def __init__(self):
2631
- self.a = 3
2632
-
2633
- def val(self):
2634
- return self.a
2927
+ def val(self):
2928
+ return self.a
2635
2929
 
2636
- getattr(AutoBound(), 'val')() == 3
2930
+ getattr(C(), 'val')() == 3 # works because bound_methods is on by default
2637
2931
  ```
2638
2932
 
2639
- If you want all classes in a module to be auto-bound simply put the scoped flag
2640
- at the top of the module. You can even choose to have only a few methods of the
2641
- class auto-bound, like this:
2933
+ You can mix bound and unbound methods within a class using ``no_bound_methods``
2934
+ and ``bound_methods`` to toggle at any point:
2642
2935
 
2643
2936
  ```py
2644
2937
  class C:
2645
2938
 
2646
- def unbound1(self):
2647
- pass # this method will not be auto-bound
2648
-
2649
- from __python__ import bound_methods
2650
- # Methods below this line will be auto-bound
2651
-
2652
- def bound(self):
2653
- pass # This method will be auto-bound
2939
+ def bound1(self):
2940
+ pass # auto-bound (default)
2654
2941
 
2655
2942
  from __python__ import no_bound_methods
2656
2943
  # Methods below this line will not be auto-bound
2657
2944
 
2658
- def unbound2(self):
2659
- pass # this method will be unbound
2945
+ def unbound(self):
2946
+ pass # not auto-bound
2947
+
2948
+ from __python__ import bound_methods
2949
+ # Methods below this line will be auto-bound again
2950
+
2951
+ def bound2(self):
2952
+ pass # auto-bound
2660
2953
  ```
2661
2954
 
2662
2955
  Scoped flags apply only to the scope they are defined in, so if you define them
2663
2956
  inside a class declaration, they only apply to that class. If you define it at
2664
- the module level, it will only apply to all classes in the module that occur
2957
+ the module level, it will apply to all classes in the module that occur
2665
2958
  below that line, and so on.
2666
2959
 
2667
2960
  Iterators
@@ -3042,11 +3335,20 @@ One of Python's main strengths is the number of libraries available to the devel
3042
3335
  gettext # Support for internationalization of your RapydScript app
3043
3336
  operator # a subset of Python's operator module
3044
3337
  functools # reduce, partial, wraps, lru_cache, cache, total_ordering, cmp_to_key
3338
+ enum # Enum base class — class Color(Enum): RED=1 with .name/.value, iteration
3339
+ dataclasses # @dataclass decorator — auto-generates __init__, __repr__, __eq__; field(),
3340
+ # fields(), asdict(), astuple(), replace(), is_dataclass(), frozen=True, order=True
3341
+ abc # ABC base class, @abstractmethod, Protocol, @runtime_checkable;
3342
+ # abstract enforcement at instantiation; ABC.register() virtual subclasses
3045
3343
  collections # namedtuple, deque, Counter, OrderedDict, defaultdict
3046
3344
  copy # copy (shallow), deepcopy; honours __copy__ / __deepcopy__ hooks
3345
+ typing # TYPE_CHECKING, Any, Union, Optional, List, Dict, Set, Tuple, TypeVar,
3346
+ # Generic, Protocol, Callable, Literal, Final, TypedDict, NamedTuple,
3347
+ # ByteString, AnyStr (str | bytes), cast, …
3047
3348
  itertools # count, cycle, repeat, accumulate, chain, compress, dropwhile, filterfalse,
3048
3349
  # groupby, islice, pairwise, starmap, takewhile, zip_longest,
3049
3350
  # product, permutations, combinations, combinations_with_replacement
3351
+ io # StringIO (in-memory text stream), BytesIO (in-memory binary stream)
3050
3352
 
3051
3353
  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.
3052
3354
 
@@ -3251,18 +3553,21 @@ As a result, there are some things in RapydScript that might come as surprises
3251
3553
  to an experienced Python developer. The most important such gotchas are listed
3252
3554
  below:
3253
3555
 
3254
- - Truthiness in JavaScript is very different from Python. Empty lists and dicts
3255
- are ``False`` in Python but ``True`` in JavaScript. You can opt in to full
3256
- Python truthiness semantics (where empty containers are falsy and ``__bool__``
3257
- is dispatched) with ``from __python__ import truthiness``. Without that flag,
3258
- test the length explicitly instead of the container directly.
3259
-
3260
- - Operators in JavaScript are very different from Python. ``1 + '1'`` would be
3261
- an error in Python, but results in ``'11'`` in JavaScript. Similarly, ``[1] +
3262
- [1]`` is a new list in Python, but a string in JavaScript. Keep that in mind
3263
- as you write code. By default, RapydScript does not implement operator
3264
- overloading for performance reasons. You can opt in via the
3265
- ``overload_operators`` scoped flag (see below).
3556
+ - RapydScript uses Python truthiness semantics by default: empty lists and dicts
3557
+ are falsy and ``__bool__`` is dispatched. This is controlled by the
3558
+ ``truthiness`` flag, which is on by default. Use
3559
+ ``from __python__ import no_truthiness`` to fall back to JavaScript truthiness
3560
+ in a scope.
3561
+
3562
+ - Operator overloading is enabled by default via the ``overload_operators``
3563
+ flag, so ``[1] + [1]`` produces a new list and ``'ha' * 3`` produces
3564
+ ``'hahaha'``. Type-checking is controlled by the separate ``strict_arithmetic``
3565
+ flag (also on by default): mixing incompatible types raises ``TypeError``
3566
+ (e.g. ``1 + 'x'`` ``TypeError: unsupported operand type(s) for +: 'int'
3567
+ and 'str'``). Use ``from __python__ import no_strict_arithmetic`` to keep
3568
+ dunder dispatch but revert to JavaScript's silent coercion for unrecognised
3569
+ type combinations. Use ``from __python__ import no_overload_operators`` to
3570
+ disable operator overloading entirely.
3266
3571
 
3267
3572
  - There are many more keywords than in Python. Because RapydScript compiles
3268
3573
  down to JavaScript, the set of keywords is all the keywords of Python + all
@@ -3283,16 +3588,18 @@ below:
3283
3588
  yourself. Similarly, the compiler will try to convert SomeClass.method() into
3284
3589
  SomeClass.prototype.method() for you, but again, this is not 100% reliable.
3285
3590
 
3286
- - The {"a":b} syntax is used to create JavaScript hashes. These do not behave
3287
- like python dictionaries. To create python like dictionary objects, you
3288
- should use a scoped flag. See the section on dictionaries above for details.
3591
+ - The ``{"a":b}`` syntax creates Python ``dict`` objects by default (the
3592
+ ``dict_literals`` flag is on by default). Use
3593
+ ``from __python__ import no_dict_literals`` to get plain JavaScript objects
3594
+ in a scope. See the section on dictionaries above for details.
3289
3595
 
3290
3596
 
3291
3597
  Python Flags
3292
3598
  ------------
3293
3599
 
3294
- Python flags are scoped compiler directives that opt in to stricter Python
3295
- semantics or language features. In source code they are written as:
3600
+ Python flags are scoped compiler directives that control Python semantics.
3601
+ All flags are **on by default**. They can be turned off in a scope with
3602
+ the ``no_`` prefix. In source code they are written as:
3296
3603
 
3297
3604
  ```py
3298
3605
  from __python__ import flag_name
@@ -3300,17 +3607,21 @@ from __python__ import flag_name
3300
3607
 
3301
3608
  At the top level they take effect for the rest of the file; inside a function
3302
3609
  or class body they apply only to that scope. Prefix a flag with `no_` to turn
3303
- it off in a nested scope.
3610
+ it off in a scope (e.g. `from __python__ import no_truthiness`).
3611
+
3612
+ All flags are **on by default**. To revert to legacy RapydScript behavior
3613
+ with no flags enabled, pass ``--legacy-rapydscript`` on the command line.
3304
3614
 
3305
3615
  | Flag | Description |
3306
3616
  |---|---|
3307
- | `dict_literals` | `{k: v}` literals create Python `dict` objects instead of plain JS objects. |
3308
- | `overload_getitem` | `obj[key]` dispatches to `__getitem__` / `__setitem__` / `__delitem__` on objects that define them. |
3309
- | `overload_operators` | Arithmetic and bitwise operators (`+`, `-`, `*`, `/`, `//`, `%`, `**`, `&`, `\|`, `^`, `<<`, `>>`) dispatch to dunder methods (`__add__`, `__sub__`, etc.) and their reflected variants. Unary `-`/`+`/`~` dispatch to `__neg__`/`__pos__`/`__invert__`. |
3310
- | `truthiness` | Boolean tests and `bool()` dispatch to `__bool__` and treat empty containers as falsy, matching Python semantics. |
3311
- | `bound_methods` | Method references (`obj.method`) are automatically bound to their object, so they can be passed as callbacks without losing `self`. |
3312
- | `hash_literals` | `{k: v}` creates a Python `dict` (alias for `dict_literals`; kept for backward compatibility). |
3313
- | `jsx` | Enable JSX syntax (`<Tag attr={expr}>children</Tag>`). Required for files that contain JSX elements. |
3617
+ | `dict_literals` | `{k: v}` literals create Python `dict` objects instead of plain JS objects. On by default. |
3618
+ | `overload_getitem` | `obj[key]` dispatches to `__getitem__` / `__setitem__` / `__delitem__` on objects that define them. On by default. |
3619
+ | `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
+ | `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. |
3621
+ | `truthiness` | Boolean tests and `bool()` dispatch to `__bool__` and treat empty containers as falsy, matching Python semantics. On by default. |
3622
+ | `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
+ | `hash_literals` | `{k: v}` creates a Python `dict` (alias for `dict_literals`; kept for backward compatibility). On by default. |
3624
+ | `jsx` | JSX syntax (`<Tag attr={expr}>children</Tag>`) is enabled. On by default. |
3314
3625
 
3315
3626
 
3316
3627
  Monaco Language Service
@@ -3561,6 +3872,242 @@ This runs all seven language-service test suites (diagnostics, scope analysis,
3561
3872
  completions, signature help, hover, DTS registry, and built-in stubs).
3562
3873
 
3563
3874
 
3875
+ Python Feature Coverage
3876
+ -----------------------
3877
+
3878
+ ### Fully Supported
3879
+
3880
+ | Feature | Notes |
3881
+ |---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---|
3882
+ | `super()` — 0-arg and 2-arg forms | `super().method()` and `super(Cls, self).method()` both work |
3883
+ | `except TypeA, TypeB as e:` | RapydScript comma-separated form; catches multiple exception types |
3884
+ | `except (TypeA, TypeError) as e:` | Tuple form also supported |
3885
+ | `except*` / `ExceptionGroup` (Python 3.11+) | Full support: `ExceptionGroup` class with `subgroup()`/`split()`; `except*` dispatches to typed handlers, re-raises unmatched; bare `except*:` catches all remaining |
3886
+ | `try / else` | `else` block runs only when no exception was raised |
3887
+ | `for / else` | `else` block runs when loop completes without `break`; nested break isolation works |
3888
+ | `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) |
3890
+ | `callable(fn)` | Works for plain functions and objects with `__call__` |
3891
+ | `round(x, ndigits=0)` | Full Python semantics including negative `ndigits` |
3892
+ | `enumerate(iterable, start=0)` | `start` parameter supported |
3893
+ | `str.isspace()`, `str.islower()`, `str.isupper()` | Working string predicates |
3894
+ | `str.isalpha()` | Regex-based; empty string returns `False` |
3895
+ | `str.isdigit()` | Regex-based (`\d+`) |
3896
+ | `str.isalnum()` | Regex-based |
3897
+ | `str.isidentifier()` | Checks `^[a-zA-Z_][a-zA-Z0-9_]*$` |
3898
+ | `str.casefold()` | Maps to `.toLowerCase()` |
3899
+ | `str.removeprefix(prefix)` | Returns unchanged string if prefix not found |
3900
+ | `str.removesuffix(suffix)` | Returns unchanged string if suffix not found |
3901
+ | `str.expandtabs(tabsize=8)` | Replaces `\t` with spaces to the next tab stop; `\n`/`\r` reset the column counter; `tabsize=0` removes all tabs; available as an instance method on any string (via the default `pythonize_strings` patch) |
3902
+ | `str * n` string repetition | Works (via `overload_operators`, on by default) |
3903
+ | `list * n` / `n * list` | Works (via `overload_operators`); returns a proper RapydScript list |
3904
+ | `list + list` concatenation | `[1,2] + [3,4]` returns `[1, 2, 3, 4]`; `+=` extends in-place. No flag required. |
3905
+ | `match / case` | Structural pattern matching (Python 3.10) fully supported |
3906
+ | Variable type annotations `x: int = 1` | Parsed and ignored (no runtime enforcement); annotated assignments work normally |
3907
+ | Ellipsis literal `...` as expression | Parsed as a valid expression; evaluates to JS `undefined` at runtime |
3908
+ | Generator `.throw()` | Works via JS generator protocol |
3909
+ | Generator `.send()` | Works via `g.next(value)` |
3910
+ | `yield from` | Works; return value of sub-generator is not accessible |
3911
+ | `+=`, `-=`, `*=`, `/=`, `//=`, `**=`, `%=`, `&=`, `\|=`, `^=`, `<<=`, `>>=` | All augmented assignments work |
3912
+ | `raise X from Y` exception chaining | Sets `__cause__` on the thrown exception; `from None` also supported |
3913
+ | Starred assignment `a, *b, c = ...` | Works |
3914
+ | `[*a, 1, *b]` list spread | Works; any iterable; translates to `[...a, 1, ...b]` |
3915
+ | `{*a, 1, *b}` set spread | Works; translates to `ρσ_set([...a, 1, ...b])` |
3916
+ | `**expr` in function calls | Works with any expression (variable, attr access, call, dict literal), not just plain names |
3917
+ | `@classmethod`, `@staticmethod`, `@property` / `@prop.setter` | All work |
3918
+ | `{**dict1, **dict2}` dict spread | Works as merge replacement for the missing `\|` operator |
3919
+ | `dict.fromkeys()` | Works (via `dict_literals`, on by default) |
3920
+ | Chained comparisons `a < b < c` and `a < b > c` | Same-direction and mixed-direction chains both work; middle operand evaluated once; with `overload_operators` each comparison dispatches via `ρσ_op_lt` etc. |
3921
+ | `for`, `while`, `try/except/finally`, `with`, `match/case` | All control-flow constructs work |
3922
+ | Classes, inheritance, decorators, `__dunder__` methods | Fully supported |
3923
+ | Nested class definitions | Accessible as `Outer.Inner` and via instance (`self.Inner`); arbitrary nesting depth; nested class may inherit from outer-scope classes |
3924
+ | List / dict / set comprehensions, generator expressions | Fully supported |
3925
+ | f-strings, `str.format()`, `format()` builtin, all common `str.*` methods | Fully supported |
3926
+ | `abs()`, `divmod()`, `any()`, `all()`, `sum()`, `min()`, `max()` | All work |
3927
+ | `sorted()`, `reversed()`, `zip()`, `map()`, `filter()` | All work |
3928
+ | `zip(strict=True)` | Raises `ValueError` when iterables have different lengths; equal-length iterables work normally |
3929
+ | `set` with full union/intersection/difference API | Fully supported |
3930
+ | `isinstance()`, `hasattr()`, `getattr()`, `setattr()`, `dir()` | All work |
3931
+ | `bin()`, `hex()`, `oct()`, `chr()`, `ord()` | All work |
3932
+ | `int(x, base)`, `float(x)` with ValueError on bad input | Works |
3933
+ | `lambda` keyword | Full support: args, defaults, `*args`, ternary body, closures, nesting |
3934
+ | Arithmetic operator overloading — `__add__`, `__sub__`, `__mul__`, `__truediv__`, `__floordiv__`, `__mod__`, `__pow__`, `__neg__`, `__pos__`, `__abs__`, `__invert__`, `__lshift__`, `__rshift__`, `__and__`, `__or__`, `__xor__`, `__radd__`, `__iadd__` etc. | Dispatched via `overload_operators` (on by default) |
3935
+ | Ordered-comparison operator overloading — `__lt__`, `__gt__`, `__le__`, `__ge__` | `<`, `>`, `<=`, `>=` dispatch to dunder methods (forward then reflected); lists compared lexicographically (like Python); incompatible types raise `TypeError`; chained comparisons (`a < b < c`) fully supported. Active via `overload_operators` (on by default). |
3936
+ | Nested comprehensions (multi-`for` clause) | `[x for row in matrix for x in row if cond]`; works for list, set, and dict comprehensions |
3937
+ | Positional-only parameters `def f(a, b, /):` | Full support — parser enforces placement; runtime passes positional args correctly |
3938
+ | Keyword-only parameters `def f(a, *, b):` | Full support — bare `*` separator enforced; `b` must be passed as keyword |
3939
+ | Walrus operator `:=` | Fully supported: hoisted in `if`/`while` conditions at any scope; comprehension filter assigns to enclosing scope (Python-correct). |
3940
+ | `__call__` dunder dispatch | `obj()` dispatches to `obj.__call__(args)` for callable objects; `callable(obj)` also returns `True`; both forms work. Active via `truthiness` (on by default). |
3941
+ | **Truthiness / `__bool__`** | Full Python truthiness via `truthiness` (on by default): empty `[]`, `{}`, `set()`, `''` are falsy; `__bool__` is dispatched; `and`/`or` return operand values; `not`, `if`, `while`, `assert`, ternary all use `ρσ_bool()`. |
3942
+ | `frozenset(iterable)` | Immutable set: construction from list/set/iterable; `in`, `len()`, iteration, `copy()`, `union()`, `intersection()`, `difference()`, `symmetric_difference()`, `issubset()`, `issuperset()`, `isdisjoint()` — all return `frozenset`. `isinstance(x, frozenset)` works. Compares equal to a `set` with the same elements via `__eq__`. No mutation methods (`add`, `remove`, etc.). |
3943
+ | `issubclass(cls, classinfo)` | Checks prototype chain; `classinfo` may be a class or tuple of classes; every class is a subclass of itself; raises `TypeError` for non-class arguments. |
3944
+ | `hash(obj)` and `__hash__` dunder | Numbers hash by value (int identity, float → int form if whole); strings use djb2; `None` → 0; booleans → 0/1; `def __hash__(self)` in a class is dispatched by `hash()`; class instances without `__hash__` get a stable identity hash; defining `__eq__` without `__hash__` makes the class unhashable (Python semantics — `hash()` raises `TypeError`); `list`, `set`, `dict` raise `TypeError`. |
3945
+ | `__getattr__` / `__setattr__` / `__delattr__` / `__getattribute__` dunders | Full attribute-access interception via JS `Proxy`. Classes defining any of these automatically wrap instances. `__getattr__` is called only for missing attributes; `__getattribute__` overrides all lookups; `__setattr__` intercepts every assignment (including those in `__init__`); `__delattr__` intercepts `del obj.attr`. Use `object.__setattr__(self, name, value)` / `object.__getattribute__(self, name)` / `object.__delattr__(self, name)` (compiled to `ρσ_object_setattr` / `ρσ_object_getattr` / `ρσ_object_delattr`) to bypass the hooks and avoid infinite recursion. Subclasses automatically inherit proxy wrapping. Requires a JS environment that supports `Proxy`; gracefully degrades to plain attribute access in environments without `Proxy`. |
3946
+ | `__class_getitem__` dunder | `Class[item]` dispatches at compile time to `Class.__class_getitem__(item)`. Behaves as an implicit `@classmethod`: `cls` is bound to the calling class. Subclasses inherit `__class_getitem__` and receive the subclass as `cls`. Multi-argument subscripts (`Class[A, B]`) are passed as a JS array. |
3947
+ | `__init_subclass__` hook | Called automatically on the parent class whenever a subclass is created (e.g. `class Child(Base):`). Implicit `@classmethod`: `cls` receives the new subclass. Keyword arguments in the class header (`class Child(Base, tag='x'):`) are forwarded to `__init_subclass__` as keyword arguments. `super().__init_subclass__(**kwargs)` propagates up the hierarchy. No explicit call needed — the compiler emits it after inheritance setup and identity properties are assigned. |
3948
+ | `next(iterator[, default])` | Advances a JS-protocol iterator (`{done, value}`); returns `default` when exhausted if provided, otherwise raises `StopIteration`. Works with `iter()`, `range()`, `enumerate()`, generators, and any object with a `.next()` or `__next__()` method. |
3949
+ | `StopIteration` exception | Defined as a builtin exception class; raised by `next()` when an iterator is exhausted and no default is given. |
3950
+ | `iter(callable, sentinel)` | Two-argument form calls `callable` (no args) repeatedly until the return value equals `sentinel` (strict `===`). Returns a lazy iterator compatible with `for` loops, `next()`, `list()`, and all iterator consumers. Works with plain functions and callable objects (`__call__`). |
3951
+ | `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
+ | `__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
+ | `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. |
3954
+ | `__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
+ | `ImportError`, `ModuleNotFoundError` | Both defined as runtime exception classes; `ModuleNotFoundError` is a subclass of `ImportError` (same as Python 3.6+). |
3956
+ | `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. |
3957
+ | `object()` | Featureless base-class instance: `object()` returns a unique instance; `isinstance(x, object)` works; `class Foo(object):` explicit base works; `repr()` → `'<object object at 0x…>'`; `hash()` returns a stable identity hash; each call returns a distinct object suitable as a sentinel value. Note: unlike CPython, JS objects are open, so arbitrary attributes can be set on `object()` instances. |
3958
+ | `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
+ | `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
+ | 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. |
3961
+ | `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
+ | `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
+ | `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. |
3964
+ | `vars(obj)` | Returns a Python `dict` snapshot of the object's own instance attributes (own enumerable JS properties, filtering internal `ρσ`-prefixed keys) — equivalent to Python's `obj.__dict__`. Mutating the returned dict does not affect the original object. Returns a proper `ρσ_dict` instance when `dict_literals` is active (default), so `.keys()`, `.values()`, `.items()`, and `[]` access work as expected. |
3965
+ | `vars()` | Zero-argument form is rewritten by the compiler to `vars(this)`, so calling `vars()` inside a method returns a snapshot of the current instance's attributes — equivalent to Python's no-arg `vars()` inside a method. |
3966
+ | `globals()` | Returns a `dict` snapshot of the JS global object's own enumerable keys (`globalThis` / `window` / `global`). Note that module-level RapydScript variables compiled inside an IIFE/module wrapper will not appear here. |
3967
+
3968
+ ---
3969
+
3970
+ ### Python Compatibility Flags (Default-On)
3971
+
3972
+ All flags below are enabled by default. They can be turned off per-file, per-scope, or globally via the CLI.
3973
+
3974
+ #### Opt-out: per-file or per-scope
3975
+
3976
+ Place at the top of a file to affect the whole file, or inside a function to affect only that scope:
3977
+
3978
+ ```python
3979
+ from __python__ import no_truthiness # single flag
3980
+ from __python__ import no_dict_literals, no_overload_operators # multiple flags
3981
+ ```
3982
+
3983
+ To re-enable a flag in a nested scope after an outer scope turned it off:
3984
+
3985
+ ```python
3986
+ from __python__ import truthiness
3987
+ ```
3988
+
3989
+ #### Opt-out: CLI (all files)
3990
+
3991
+ ```sh
3992
+ rapydscript compile --python-flags=no_dict_literals,no_truthiness input.pyj
3993
+ ```
3994
+
3995
+ Flags in `--python-flags` are comma-separated. Prefix a flag with `no_` to disable it; omit the prefix to force-enable it (useful when combining with `--legacy-rapydscript`).
3996
+
3997
+ #### Disable all flags (legacy mode)
3998
+
3999
+ ```sh
4000
+ rapydscript compile --legacy-rapydscript input.pyj
4001
+ ```
4002
+
4003
+ This restores the original RapydScript behavior: plain JS objects for `{}`, no operator overloading, JS truthiness, unbound methods, and no `String.prototype` patching.
4004
+
4005
+ #### Flag reference
4006
+
4007
+ | Flag | What it enables | Effect when disabled |
4008
+ |---|---|---|
4009
+ | `dict_literals` | `{}` creates a Python `ρσ_dict` with `.keys()`, `.values()`, `.items()`, `.get()`, `.pop()`, `.update()`, `fromkeys()`, and `KeyError` on missing key access. | `{}` becomes a plain JS object; no Python dict methods; missing key access returns `undefined`. |
4010
+ | `overload_getitem` | `obj[key]` dispatches to `obj.__getitem__(key)` when defined; `obj[a:b:c]` passes a `slice` object; dict `[]` access raises `KeyError` on missing key. | `[]` compiles to plain JS property access; no `__getitem__` dispatch; no slice dispatch. |
4011
+ | `bound_methods` | Class methods are automatically bound to `self`, so they retain their `self` binding when stored in a variable or passed as a callback. | Detached method references lose `self` (JS default behavior). |
4012
+ | `hash_literals` | When `dict_literals` is off, `{}` creates `Object.create(null)` rather than `{}`, preventing prototype-chain pollution from keys like `toString`. Has no visible effect while `dict_literals` is on. | `{}` becomes a plain `{}` (inherits from `Object.prototype`). Only relevant when `dict_literals` is also disabled. |
4013
+ | `overload_operators` | Arithmetic and bitwise operators (`+`, `-`, `*`, `/`, `//`, `%`, `**`, `&`, `\|`, `^`, `~`, `<<`, `>>`) and ordered-comparison operators (`<`, `>`, `<=`, `>=`) dispatch to dunder methods (`__add__`, `__lt__`, etc.) when defined on the left operand. Also enables `str * n` string repetition, `list * n` / `n * list` list repetition, `dict \| dict` / `dict \|= dict` merge, and Python-style lexicographic list comparison. | All operators compile directly to JS; no dunder dispatch. `str * n` produces `NaN`; list repetition, dict merge, and Python-style list ordering are unavailable. |
4014
+ | `truthiness` | Python truthiness semantics: `[]`, `{}`, `set()`, `''`, `0`, `None` are falsy; objects with `__bool__` are dispatched; `and`/`or` return the deciding operand value (not `True`/`False`); `not`, `if`, `while`, `assert`, and ternary all route through `ρσ_bool()`. Also enables `__call__` dispatch: `obj(args)` invokes `obj.__call__(args)` for callable objects. | Truthiness is JS-native (all objects truthy); `__bool__` is never called; `and`/`or` return booleans; `__call__` is not dispatched. |
4015
+ | `jsx` | JSX syntax (`<Tag attr={expr}>children</Tag>` and `<>...</>` fragments) is recognised as expression syntax and compiled to `React.createElement` calls (or equivalent). | `<` is always a less-than operator; angle-bracket tokens are never parsed as JSX. |
4016
+ | `pythonize_strings` *(output-level option, not a `from __python__` flag)* | `String.prototype` is patched at startup with Python string methods (`strip`, `lstrip`, `rstrip`, `join`, `format`, `capitalize`, `lower`, `upper`, `find`, `rfind`, `index`, `rindex`, `count`, `startswith`, `endswith`, `center`, `ljust`, `rjust`, `zfill`, `partition`, `rpartition`, `splitlines`, `expandtabs`, `swapcase`, `title`, `isspace`, `islower`, `isupper`). Equivalent to calling `from pythonize import strings; strings()` manually. Note: `split()` and `replace()` are intentionally kept as their JS versions. | Python string methods are not available on string instances; call `str.strip(s)` etc., or import and call `strings()` from `pythonize` manually. Disable globally with `--legacy-rapydscript`. |
4017
+
4018
+ ---
4019
+
4020
+ ### Python Features That Are Not Supported
4021
+
4022
+ | Feature | Notes |
4023
+ |---------------------------------------|-----------------------------------------------------------------------------------------|
4024
+ | `__slots__` enforcement | Accepted, but does not restrict attribute assignment |
4025
+ | `locals()` | Returns an empty `dict`. JS has no runtime mechanism for introspecting local variables. |
4026
+ | `input(prompt)` | There is no simple cli input in browser; use `prompt()` |
4027
+ | `compile()` | Python compile/code objects have no JS equivalent |
4028
+ | `memoryview(obj)` | There is no buffer protocol in browser context |
4029
+ | `open(path)` | There is no filesystem access in browser context |
4030
+ | `from module import *` (star imports) | Intentionally unsupported (by design, to prevent namespace pollution) |
4031
+ | `__del__` destructor / finalizer | JS has no guaranteed finalizer |
4032
+
4033
+ ---
4034
+
4035
+ ### Standard Library Modules
4036
+
4037
+ Modules with a `src/lib/` implementation available are marked ✅. All others are absent.
4038
+
4039
+ | Module | Status | Notes |
4040
+ |---------------|-------------|-----------------------------------------------------------------------------------------------|
4041
+ | `math` | ✅ | Full implementation in `src/lib/math.pyj` |
4042
+ | `random` | ✅ | RC4-seeded PRNG in `src/lib/random.pyj` |
4043
+ | `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` |
4046
+ | `functools` | ✅ | `reduce`, `partial`, `wraps`, `lru_cache` |
4047
+ | `itertools` | ✅ | Common iteration tools |
4048
+ | `numpy` | ✅ | Full numpy-like library in `src/lib/numpy.pyj`; `numpy.random` and `numpy.linalg` sub-modules |
4049
+ | `copy` | ✅ | `copy()` shallow copy and `deepcopy()` (circular-ref-safe via memo Map); `__copy__` / `__deepcopy__(memo)` hooks honoured; handles list, set, frozenset, dict, class instances, and plain JS objects |
4050
+ | `typing` | ✅ | `TYPE_CHECKING`, `Any`, `Union`, `Optional`, `ClassVar`, `Final`, `Literal`, `NoReturn`, `List`, `Dict`, `Set`, `FrozenSet`, `Tuple`, `Type`, `Callable`, `Iterator`, `Iterable`, `Generator`, `Sequence`, `MutableSequence`, `Mapping`, `MutableMapping`, `Awaitable`, `Coroutine`, `AsyncGenerator`, `AsyncIterator`, `AsyncIterable`, `IO`, `TextIO`, `BinaryIO`, `Pattern`, `Match`, `TypeVar`, `Generic`, `Protocol`, `cast`, `overload`, `no_type_check`, `no_type_check_decorator`, `runtime_checkable`, `get_type_hints`, `TypedDict`, `NamedTuple`, `AnyStr`, `Text` — all available in `src/lib/typing.pyj` |
4051
+ | `dataclasses` | ✅ | `@dataclass`, `field()`, `asdict()`, `astuple()`, `replace()`, `fields()`, `is_dataclass()`, `MISSING` in `src/lib/dataclasses.pyj`; `frozen=True`, `order=True`, inheritance supported; note: `field()` first positional arg is the default value (JS reserved word `default` cannot be used as a kwarg) |
4052
+ | `enum` | ✅ | `Enum` base class in `src/lib/enum.pyj`; `.name`, `.value`, iteration, `isinstance` checks; `IntEnum`/`Flag` not available |
4053
+ | `abc` | ✅ | `ABC`, `@abstractmethod`, `Protocol`, `@runtime_checkable`, `ABCMeta` (informational), `get_cache_token()` in `src/lib/abc.pyj`; abstract method enforcement via `__init__` guard; `ABC.register()` for virtual subclasses with isinstance support; `Symbol.hasInstance` enables structural isinstance for `@runtime_checkable` protocols; `ABCMeta` metaclass not usable (no metaclass support), use `ABC` base class instead |
4054
+ | `contextlib` | ✅ | `AbstractContextManager`, `@contextmanager`, `closing`, `nullcontext`, `suppress`, `ExitStack` in `src/lib/contextlib.pyj`; generator-based context managers via `@contextmanager`; `asynccontextmanager` not available |
4055
+ | `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
+ | `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
+ | `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 |
4059
+ | `inspect` | ❌ | `signature`, `getmembers`, `isfunction` etc. not available |
4060
+ | `asyncio` | ❌ | Event loop, `gather`, `sleep`, `Queue`, `Task` wrappers not available; use `async`/`await` |
4061
+ | `struct` | ❌ | Binary packing/unpacking not available |
4062
+ | `hashlib` | ❌ | MD5, SHA-256 etc. not available; use Web Crypto API via verbatim JS |
4063
+ | `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
+ | `unittest` | ❌ | Not available; RapydScript uses a custom test runner (`node bin/rapydscript test`) |
4072
+
4073
+ ---
4074
+
4075
+ ### Semantic Differences
4076
+
4077
+ Features that exist in RapydScript but behave differently from standard Python:
4078
+
4079
+ | Feature | Python Behavior | RapydScript Behavior |
4080
+ |---|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
4081
+ | `is` / `is not` | Object identity | Strict equality `===` / `!==`, which is not object/pointer comparison for JS primitives |
4082
+ | `//` 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) |
4084
+ | `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
+ | `Exception.message` | Not standard; use `.args[0]` | `.message` is the standard attribute (JS `Error` style) |
4086
+ | 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. |
4087
+ | Positional-only param enforcement | Passing by keyword raises `TypeError` | Passing by keyword is silently ignored — the named arg is discarded and the parameter gets `undefined` (no error raised) |
4088
+ | Keyword-only param enforcement | Passing positionally raises `TypeError` | Passing positionally raises no error — the extra positional arg is silently discarded and the default value is used |
4089
+ | Default `{}` dict — numeric keys | Integer keys are stored as integers | Numeric keys are auto-coerced to strings by the JS engine: `d[1]` and `d['1']` refer to the same slot |
4090
+ | Default `{}` dict — attribute access | `d.foo` raises `AttributeError` | `d.foo` and `d['foo']` access the same slot; keys are also properties |
4091
+ | String encoding | Unicode strings (full code-point aware) | UTF-16 — non-BMP characters (e.g. emoji) are stored as surrogate pairs. Use `str.uchrs()`, `str.uslice()`, `str.ulen()` for code-point-aware operations. |
4092
+ | Multiple inheritance MRO | C3 linearization (MRO) always deterministic | Built on JS prototype chain; may differ from Python's C3 MRO in complex or diamond-inheritance hierarchies |
4093
+ | 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
+ | `dict` key ordering | Insertion order guaranteed (3.7+) | Depends on JS engine (V8 preserves insertion order in practice) |
4095
+ | 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 |
4097
+
4098
+ ---
4099
+
4100
+ ### Test File
4101
+
4102
+ `test/python_features.pyj` contains runnable assertions for all features surveyed.
4103
+ Features that are not supported have their test code commented out with a `# SKIP:` label
4104
+ and an explanation. Run with:
4105
+
4106
+ ```sh
4107
+ node bin/rapydscript test python_features
4108
+ ```
4109
+
4110
+
3564
4111
  Reasons for the fork
3565
4112
  ----------------------
3566
4113
 
@@ -3574,4 +4121,4 @@ ever resumes, they are welcome to use the code from this fork. All the
3574
4121
  new code is under the same license, to make that possible.
3575
4122
 
3576
4123
  See the [Changelog](https://github.com/ficocelliguy/rapydscript-ns/blob/master/CHANGELOG.md)
3577
- for a list of changes to rapydscript-ns, including this fork at version 8.0
4124
+ for a list of changes to rapydscript-ns, including this fork at version 8.0