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.
- package/.agignore +1 -1
- package/.github/workflows/ci.yml +38 -38
- package/=template.pyj +5 -5
- package/CHANGELOG.md +26 -0
- package/HACKING.md +103 -103
- package/LICENSE +24 -24
- package/README.md +716 -169
- package/TODO.md +7 -2
- package/add-toc-to-readme +2 -2
- package/bin/export +75 -75
- package/bin/rapydscript +70 -70
- package/bin/web-repl-export +102 -102
- package/build +2 -2
- package/language-service/index.js +36 -27
- package/package.json +1 -1
- package/publish.py +37 -37
- package/release/baselib-plain-pretty.js +2358 -168
- package/release/baselib-plain-ugly.js +73 -3
- package/release/compiler.js +6283 -3093
- package/release/signatures.json +31 -30
- package/session.vim +4 -4
- package/setup.cfg +2 -2
- package/src/ast.pyj +1 -0
- package/src/baselib-builtins.pyj +340 -2
- package/src/baselib-bytes.pyj +664 -0
- package/src/baselib-errors.pyj +1 -1
- package/src/baselib-internal.pyj +267 -60
- package/src/baselib-itertools.pyj +110 -97
- package/src/baselib-str.pyj +22 -4
- package/src/compiler.pyj +36 -36
- package/src/errors.pyj +30 -30
- package/src/lib/abc.pyj +317 -0
- package/src/lib/aes.pyj +646 -646
- package/src/lib/contextlib.pyj +379 -0
- package/src/lib/copy.pyj +120 -120
- package/src/lib/dataclasses.pyj +532 -0
- package/src/lib/datetime.pyj +712 -0
- package/src/lib/elementmaker.pyj +83 -83
- package/src/lib/encodings.pyj +126 -126
- package/src/lib/enum.pyj +125 -0
- package/src/lib/gettext.pyj +569 -569
- package/src/lib/io.pyj +500 -0
- package/src/lib/itertools.pyj +580 -580
- package/src/lib/json.pyj +227 -0
- package/src/lib/math.pyj +193 -193
- package/src/lib/operator.pyj +11 -11
- package/src/lib/pythonize.pyj +20 -20
- package/src/lib/random.pyj +118 -118
- package/src/lib/re.pyj +504 -470
- package/src/lib/react.pyj +74 -74
- package/src/lib/traceback.pyj +63 -63
- package/src/lib/typing.pyj +577 -0
- package/src/lib/uuid.pyj +77 -77
- package/src/monaco-language-service/builtins.js +14 -4
- package/src/monaco-language-service/diagnostics.js +19 -20
- package/src/monaco-language-service/dts.js +550 -550
- package/src/output/classes.pyj +62 -26
- package/src/output/comments.pyj +45 -45
- package/src/output/exceptions.pyj +201 -201
- package/src/output/functions.pyj +78 -5
- package/src/output/jsx.pyj +164 -164
- package/src/output/loops.pyj +5 -2
- package/src/output/operators.pyj +100 -34
- package/src/output/treeshake.pyj +182 -182
- package/src/output/utils.pyj +72 -72
- package/src/parse.pyj +80 -16
- package/src/string_interpolation.pyj +72 -72
- package/src/tokenizer.pyj +10 -5
- package/src/unicode_aliases.pyj +576 -576
- package/src/utils.pyj +192 -192
- package/test/_import_one.pyj +37 -37
- package/test/_import_two/__init__.pyj +11 -11
- package/test/_import_two/level2/deep.pyj +4 -4
- package/test/_import_two/other.pyj +6 -6
- package/test/_import_two/sub.pyj +13 -13
- package/test/abc.pyj +291 -0
- package/test/aes_vectors.pyj +421 -421
- package/test/annotations.pyj +80 -80
- package/test/arithmetic_nostrict.pyj +88 -0
- package/test/arithmetic_types.pyj +169 -0
- package/test/baselib.pyj +91 -0
- package/test/bytes.pyj +467 -0
- package/test/classes.pyj +1 -0
- package/test/comparison_ops.pyj +173 -0
- package/test/contextlib.pyj +362 -0
- package/test/dataclasses.pyj +253 -0
- package/test/datetime.pyj +500 -0
- package/test/debugger_stmt.pyj +41 -0
- package/test/decorators.pyj +77 -77
- package/test/docstrings.pyj +39 -39
- package/test/elementmaker_test.pyj +45 -45
- package/test/enum.pyj +134 -0
- package/test/eval_exec.pyj +56 -0
- package/test/format.pyj +148 -0
- package/test/functions.pyj +151 -151
- package/test/generators.pyj +41 -41
- package/test/generic.pyj +370 -370
- package/test/imports.pyj +72 -72
- package/test/internationalization.pyj +73 -73
- package/test/io.pyj +316 -0
- package/test/json.pyj +196 -0
- package/test/lint.pyj +164 -164
- package/test/loops.pyj +85 -85
- package/test/numpy.pyj +734 -734
- package/test/object.pyj +64 -0
- package/test/omit_function_metadata.pyj +20 -20
- package/test/python_compat.pyj +17 -15
- package/test/python_features.pyj +70 -15
- package/test/regexp.pyj +83 -55
- package/test/repl.pyj +121 -121
- package/test/scoped_flags.pyj +76 -76
- package/test/tuples.pyj +96 -0
- package/test/typing.pyj +469 -0
- package/test/unit/index.js +116 -7
- package/test/unit/language-service-dts.js +543 -543
- package/test/unit/language-service-hover.js +455 -455
- package/test/unit/language-service.js +84 -0
- package/test/unit/web-repl.js +1337 -1
- package/test/vars_locals_globals.pyj +94 -0
- package/tools/cli.js +558 -547
- package/tools/compile.js +224 -219
- package/tools/completer.js +131 -131
- package/tools/embedded_compiler.js +262 -251
- package/tools/gettext.js +185 -185
- package/tools/ini.js +65 -65
- package/tools/lint.js +16 -19
- package/tools/msgfmt.js +187 -187
- package/tools/repl.js +223 -223
- package/tools/test.js +118 -118
- package/tools/utils.js +128 -128
- package/tools/web_repl.js +95 -95
- package/try +41 -41
- package/web-repl/env.js +196 -196
- package/web-repl/index.html +163 -163
- package/web-repl/main.js +252 -252
- package/web-repl/prism.css +139 -139
- package/web-repl/prism.js +113 -113
- package/web-repl/rapydscript.js +224 -224
- package/web-repl/sha1.js +25 -25
- package/PYTHON_DIFFERENCES_REPORT.md +0 -291
- package/PYTHON_FEATURE_COVERAGE.md +0 -200
package/README.md
CHANGED
|
@@ -6,11 +6,14 @@ RapydScript
|
|
|
6
6
|
[](https://www.npmjs.com/package/rapydscript-ns)
|
|
7
7
|
[](https://snyk.io/test/github/ficocelliguy/rapydscript-ns)
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
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
|
|
91
|
-
JavaScript
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
|
860
|
-
|
|
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
|
-
|
|
872
|
-
|
|
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
|
|
966
|
-
|
|
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
|
-
|
|
981
|
-
|
|
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
|
|
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``
|
|
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
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
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,
|
|
1067
|
-
|
|
1068
|
-
|
|
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
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
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
|
|
1161
|
+
assert [1] < [2] < [3] # True (lexicographic chain)
|
|
1104
1162
|
```
|
|
1105
1163
|
|
|
1106
1164
|
### Python Truthiness and `__bool__`
|
|
1107
1165
|
|
|
1108
|
-
|
|
1109
|
-
|
|
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
|
|
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
|
|
1499
|
-
|
|
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.
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
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
|
-
|
|
2616
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2628
|
-
|
|
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(
|
|
2930
|
+
getattr(C(), 'val')() == 3 # works because bound_methods is on by default
|
|
2637
2931
|
```
|
|
2638
2932
|
|
|
2639
|
-
|
|
2640
|
-
|
|
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
|
|
2647
|
-
pass #
|
|
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
|
|
2659
|
-
pass #
|
|
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
|
|
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
|
-
-
|
|
3255
|
-
are
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
``
|
|
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
|
|
3287
|
-
|
|
3288
|
-
|
|
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
|
|
3295
|
-
|
|
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
|
|
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
|
-
| `
|
|
3311
|
-
| `
|
|
3312
|
-
| `
|
|
3313
|
-
| `
|
|
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
|