rapydscript-ns 0.8.3 → 0.9.0
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/CHANGELOG.md +26 -0
- package/README.md +1351 -141
- package/TODO.md +12 -6
- package/language-service/index.js +184 -26
- package/package.json +1 -1
- package/release/baselib-plain-pretty.js +5895 -1928
- package/release/baselib-plain-ugly.js +140 -3
- package/release/compiler.js +16282 -5408
- package/release/signatures.json +25 -22
- package/src/ast.pyj +94 -1
- package/src/baselib-builtins.pyj +362 -3
- package/src/baselib-bytes.pyj +664 -0
- package/src/baselib-containers.pyj +99 -0
- package/src/baselib-errors.pyj +45 -1
- package/src/baselib-internal.pyj +346 -49
- package/src/baselib-itertools.pyj +17 -4
- package/src/baselib-str.pyj +46 -4
- package/src/lib/abc.pyj +317 -0
- package/src/lib/copy.pyj +120 -0
- package/src/lib/dataclasses.pyj +532 -0
- package/src/lib/enum.pyj +125 -0
- package/src/lib/pythonize.pyj +1 -1
- package/src/lib/re.pyj +35 -1
- package/src/lib/react.pyj +74 -0
- package/src/lib/typing.pyj +577 -0
- package/src/monaco-language-service/builtins.js +19 -4
- package/src/monaco-language-service/diagnostics.js +40 -19
- package/src/output/classes.pyj +161 -25
- package/src/output/codegen.pyj +16 -2
- package/src/output/exceptions.pyj +97 -1
- package/src/output/functions.pyj +87 -5
- package/src/output/jsx.pyj +164 -0
- package/src/output/literals.pyj +28 -2
- package/src/output/loops.pyj +5 -2
- package/src/output/modules.pyj +1 -1
- package/src/output/operators.pyj +108 -36
- package/src/output/statements.pyj +2 -2
- package/src/output/stream.pyj +1 -0
- package/src/parse.pyj +496 -128
- package/src/tokenizer.pyj +38 -4
- package/test/abc.pyj +291 -0
- 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/dataclasses.pyj +253 -0
- package/test/enum.pyj +134 -0
- package/test/eval_exec.pyj +56 -0
- package/test/format.pyj +148 -0
- package/test/object.pyj +64 -0
- package/test/python_compat.pyj +17 -15
- package/test/python_features.pyj +89 -21
- package/test/regexp.pyj +29 -1
- package/test/tuples.pyj +96 -0
- package/test/typing.pyj +469 -0
- package/test/unit/index.js +2292 -70
- package/test/unit/language-service.js +674 -4
- package/test/unit/web-repl.js +1106 -0
- package/test/vars_locals_globals.pyj +94 -0
- package/tools/cli.js +11 -0
- package/tools/compile.js +5 -0
- package/tools/embedded_compiler.js +15 -4
- package/tools/lint.js +16 -19
- package/tools/repl.js +1 -1
- package/web-repl/env.js +122 -0
- package/web-repl/main.js +1 -3
- package/web-repl/rapydscript.js +125 -3
- package/PYTHON_DIFFERENCES_REPORT.md +0 -291
- package/PYTHON_FEATURE_COVERAGE.md +0 -200
- package/hack_demo.pyj +0 -112
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**
|
|
@@ -41,6 +44,7 @@ backwards compatible) features. For more on the forking, [see the bottom of this
|
|
|
41
44
|
- [Extended Subscript Syntax](#extended-subscript-syntax)
|
|
42
45
|
- [Variable Type Annotations](#variable-type-annotations)
|
|
43
46
|
- [Regular Expressions](#regular-expressions)
|
|
47
|
+
- [JSX Support](#jsx-support)
|
|
44
48
|
- [Creating DOM trees easily](#creating-dom-trees-easily)
|
|
45
49
|
- [Classes](#classes)
|
|
46
50
|
- [External Classes](#external-classes)
|
|
@@ -70,6 +74,7 @@ backwards compatible) features. For more on the forking, [see the bottom of this
|
|
|
70
74
|
- [Virtual modules](#virtual-modules)
|
|
71
75
|
- [Source maps](#source-maps)
|
|
72
76
|
- [Running the tests](#running-the-tests)
|
|
77
|
+
- [Python Feature Coverage](#python-feature-coverage)
|
|
73
78
|
- [Reasons for the fork](#reasons-for-the-fork)
|
|
74
79
|
|
|
75
80
|
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
|
@@ -82,20 +87,17 @@ RapydScript (pronounced 'RapidScript') is a pre-compiler for JavaScript,
|
|
|
82
87
|
similar to CoffeeScript, but with cleaner, more readable syntax. The syntax is
|
|
83
88
|
almost identical to Python, but RapydScript has a focus on performance and
|
|
84
89
|
interoperability with external JavaScript libraries. This means that the
|
|
85
|
-
JavaScript that RapydScript generates is performant and quite close to hand
|
|
90
|
+
JavaScript that RapydScript generates is performant and quite close to hand-
|
|
86
91
|
written JavaScript.
|
|
87
92
|
|
|
88
93
|
RapydScript allows to write your front-end in Python without the overhead that
|
|
89
|
-
other similar frameworks introduce
|
|
90
|
-
JavaScript
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
RapydScript combines the best features of Python as well as JavaScript,
|
|
97
|
-
bringing you features most other Pythonic JavaScript replacements overlook.
|
|
98
|
-
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:
|
|
99
101
|
|
|
100
102
|
- classes that work and feel similar to Python
|
|
101
103
|
- an import system for modules/packages that works just like Python's
|
|
@@ -445,7 +447,6 @@ Class decorators are also supported with the caveat that the class properties
|
|
|
445
447
|
must be accessed via the prototype property. For example:
|
|
446
448
|
|
|
447
449
|
```py
|
|
448
|
-
|
|
449
450
|
def add_x(cls):
|
|
450
451
|
cls.prototype.x = 1
|
|
451
452
|
|
|
@@ -698,6 +699,29 @@ head, *mid, tail = [1, 2, 3, 4, 5] # head=1, mid=[2, 3, 4], tail=5
|
|
|
698
699
|
|
|
699
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.
|
|
700
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
|
+
|
|
701
725
|
Operators and keywords
|
|
702
726
|
------------------------
|
|
703
727
|
|
|
@@ -805,9 +829,9 @@ No special flag is required. The `+` operator compiles to a lightweight helper
|
|
|
805
829
|
|
|
806
830
|
Sets in RapydScript are identical to those in python. You can create them using
|
|
807
831
|
set literals or comprehensions and all set operations are supported. You can
|
|
808
|
-
store any object in a set
|
|
809
|
-
|
|
810
|
-
|
|
832
|
+
store any object in a set. For primitive types (strings, numbers) the value is
|
|
833
|
+
used for equality; for class instances, object identity (``is``) is used by
|
|
834
|
+
default unless the class defines a ``__hash__`` method.
|
|
811
835
|
|
|
812
836
|
Note that sets are not a subclass of the ES 6 JavaScript Set object, however,
|
|
813
837
|
they do use this object as a backend, when available. You can create a set from
|
|
@@ -835,8 +859,8 @@ a in s # True
|
|
|
835
859
|
[1, 2] in s # False
|
|
836
860
|
```
|
|
837
861
|
|
|
838
|
-
This is because
|
|
839
|
-
|
|
862
|
+
This is because list identity (not value) determines set membership for mutable
|
|
863
|
+
objects. Define ``__hash__`` on your own classes to control set/dict membership.
|
|
840
864
|
|
|
841
865
|
### Dicts
|
|
842
866
|
|
|
@@ -855,11 +879,11 @@ differences between RapydScript dicts and Python dicts.
|
|
|
855
879
|
RapydScript dict objects in ```for..in``` loops.
|
|
856
880
|
|
|
857
881
|
Fortunately, there is a builtin ```dict``` type that behaves just like Python's
|
|
858
|
-
```dict``` with all the same methods. The
|
|
859
|
-
|
|
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:
|
|
860
885
|
|
|
861
886
|
```py
|
|
862
|
-
from __python__ import dict_literals, overload_getitem
|
|
863
887
|
a = {1:1, 2:2}
|
|
864
888
|
a[1] # == 1
|
|
865
889
|
a[3] = 3
|
|
@@ -867,17 +891,10 @@ list(a.keys()) == [1, 2, 3]
|
|
|
867
891
|
a['3'] # raises a KeyError as this is a proper python dict, not a JavaScript object
|
|
868
892
|
```
|
|
869
893
|
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
as they are treated in python, not JavaScript.
|
|
873
|
-
|
|
874
|
-
The scoped flags are local to each scope, that means that if you use it in a
|
|
875
|
-
module, it will only affect code in that module, it you use it in a function,
|
|
876
|
-
it will only affect code in that function. In fact, you can even use it to
|
|
877
|
-
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:
|
|
878
896
|
|
|
879
897
|
```py
|
|
880
|
-
from __python__ import dict_literals, overload_getitem
|
|
881
898
|
a = {1:1, 2:2}
|
|
882
899
|
isinstance(a, dict) == True
|
|
883
900
|
from __python__ import no_dict_literals, no_overload_getitem
|
|
@@ -885,6 +902,63 @@ a = {1:1, 2:2}
|
|
|
885
902
|
isinstance(a, dict) == False # a is a normal JavaScript object
|
|
886
903
|
```
|
|
887
904
|
|
|
905
|
+
### List spread literals
|
|
906
|
+
|
|
907
|
+
RapydScript supports Python's `*expr` spread syntax inside list literals.
|
|
908
|
+
One or more `*expr` items can appear anywhere, interleaved with ordinary
|
|
909
|
+
elements:
|
|
910
|
+
|
|
911
|
+
```py
|
|
912
|
+
a = [1, 2, 3]
|
|
913
|
+
b = [4, 5]
|
|
914
|
+
|
|
915
|
+
# Spread at the end
|
|
916
|
+
result = [0, *a] # [0, 1, 2, 3]
|
|
917
|
+
|
|
918
|
+
# Spread in the middle
|
|
919
|
+
result = [0, *a, *b, 6] # [0, 1, 2, 3, 4, 5, 6]
|
|
920
|
+
|
|
921
|
+
# Copy a list
|
|
922
|
+
copy = [*a] # [1, 2, 3]
|
|
923
|
+
|
|
924
|
+
# Unpack a string
|
|
925
|
+
chars = [*'hello'] # ['h', 'e', 'l', 'l', 'o']
|
|
926
|
+
```
|
|
927
|
+
|
|
928
|
+
Spread works on any iterable (lists, strings, generators, `range()`).
|
|
929
|
+
The result is always a new Python list. Translates to JavaScript's
|
|
930
|
+
`[...expr]` spread syntax.
|
|
931
|
+
|
|
932
|
+
### Set spread literals
|
|
933
|
+
|
|
934
|
+
The same `*expr` syntax works inside set literals `{...}`:
|
|
935
|
+
|
|
936
|
+
```py
|
|
937
|
+
a = [1, 2, 3]
|
|
938
|
+
b = [3, 4, 5]
|
|
939
|
+
|
|
940
|
+
s = {*a, *b} # set([1, 2, 3, 4, 5]) — duplicates removed
|
|
941
|
+
s2 = {*a, 10} # set([1, 2, 3, 10])
|
|
942
|
+
```
|
|
943
|
+
|
|
944
|
+
Translates to `ρσ_set([...a, ...b])`.
|
|
945
|
+
|
|
946
|
+
### `**expr` in function calls
|
|
947
|
+
|
|
948
|
+
`**expr` in a function call now accepts any expression, not just a plain
|
|
949
|
+
variable name:
|
|
950
|
+
|
|
951
|
+
```py
|
|
952
|
+
def f(x=0, y=0):
|
|
953
|
+
return x + y
|
|
954
|
+
|
|
955
|
+
opts = {'x': 10, 'y': 20}
|
|
956
|
+
f(**opts) # 30 (variable — always worked)
|
|
957
|
+
f(**{'x': 1, 'y': 2}) # 3 (dict literal)
|
|
958
|
+
f(**cfg.defaults) # uses attribute access result
|
|
959
|
+
f(**get_opts()) # uses function call result
|
|
960
|
+
```
|
|
961
|
+
|
|
888
962
|
### Dict merge literals
|
|
889
963
|
|
|
890
964
|
RapydScript supports Python's `{**d1, **d2}` dict merge (spread) syntax.
|
|
@@ -904,11 +978,10 @@ result = {**defaults, 'weight': 5}
|
|
|
904
978
|
# result == {'color': 'blue', 'size': 10, 'weight': 5}
|
|
905
979
|
```
|
|
906
980
|
|
|
907
|
-
This works for both plain JavaScript-object dicts
|
|
908
|
-
|
|
981
|
+
This works for both plain JavaScript-object dicts and Python `dict` objects
|
|
982
|
+
(``dict_literals`` is on by default):
|
|
909
983
|
|
|
910
984
|
```py
|
|
911
|
-
from __python__ import dict_literals, overload_getitem
|
|
912
985
|
pd1 = {'a': 1}
|
|
913
986
|
pd2 = {'b': 2}
|
|
914
987
|
merged = {**pd1, **pd2} # isinstance(merged, dict) == True
|
|
@@ -919,12 +992,10 @@ and `dict.update()` for Python dicts.
|
|
|
919
992
|
|
|
920
993
|
### Dict merge operator `|` and `|=` (Python 3.9+)
|
|
921
994
|
|
|
922
|
-
|
|
923
|
-
|
|
995
|
+
Python dicts support the `|` (merge) and `|=` (update in-place) operators
|
|
996
|
+
(requires ``overload_operators`` and ``dict_literals``, both on by default):
|
|
924
997
|
|
|
925
998
|
```py
|
|
926
|
-
from __python__ import overload_operators, dict_literals
|
|
927
|
-
|
|
928
999
|
d1 = {'x': 1, 'y': 2}
|
|
929
1000
|
d2 = {'y': 99, 'z': 3}
|
|
930
1001
|
|
|
@@ -939,17 +1010,15 @@ d1 |= d2 # d1 is now {'x': 1, 'y': 99, 'z': 3}
|
|
|
939
1010
|
`d1 |= d2` merges `d2` into `d1` and returns `d1`.
|
|
940
1011
|
|
|
941
1012
|
Without `overload_operators` the `|` symbol is bitwise OR — use
|
|
942
|
-
`{**d1, **d2}` spread syntax as
|
|
1013
|
+
`{**d1, **d2}` spread syntax as an alternative if the flag is disabled.
|
|
943
1014
|
|
|
944
1015
|
|
|
945
1016
|
### Arithmetic operator overloading
|
|
946
1017
|
|
|
947
1018
|
RapydScript supports Python-style arithmetic operator overloading via the
|
|
948
|
-
``overload_operators``
|
|
1019
|
+
``overload_operators`` flag, which is **on by default**:
|
|
949
1020
|
|
|
950
1021
|
```py
|
|
951
|
-
from __python__ import overload_operators
|
|
952
|
-
|
|
953
1022
|
class Vector:
|
|
954
1023
|
def __init__(self, x, y):
|
|
955
1024
|
self.x = x
|
|
@@ -990,31 +1059,52 @@ The supported dunder methods are:
|
|
|
990
1059
|
Augmented assignment (``+=``, ``-=``, etc.) first tries the in-place method
|
|
991
1060
|
(``__iadd__``, ``__isub__``, …) and then falls back to the binary method.
|
|
992
1061
|
|
|
993
|
-
If neither operand defines the relevant dunder method the operation
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
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.
|
|
997
1089
|
|
|
998
1090
|
When `overload_operators` is active, string and list repetition with `*` works just like Python:
|
|
999
1091
|
|
|
1000
1092
|
```py
|
|
1001
|
-
from __python__ import overload_operators
|
|
1002
1093
|
'ha' * 3 # 'hahaha'
|
|
1003
1094
|
3 * 'ha' # 'hahaha'
|
|
1004
1095
|
[0] * 4 # [0, 0, 0, 0]
|
|
1005
1096
|
[1, 2] * 2 # [1, 2, 1, 2]
|
|
1006
1097
|
```
|
|
1007
1098
|
|
|
1008
|
-
Because the dispatch adds one or two property lookups per operation,
|
|
1009
|
-
|
|
1010
|
-
|
|
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``.
|
|
1011
1102
|
|
|
1012
1103
|
The ``collections.Counter`` class defines ``__add__``, ``__sub__``, ``__or__``,
|
|
1013
1104
|
and ``__and__``. With ``overload_operators`` you can use the natural operator
|
|
1014
1105
|
syntax:
|
|
1015
1106
|
|
|
1016
1107
|
```py
|
|
1017
|
-
from __python__ import overload_getitem, overload_operators
|
|
1018
1108
|
from collections import Counter
|
|
1019
1109
|
|
|
1020
1110
|
c1 = Counter('aab')
|
|
@@ -1032,9 +1122,35 @@ RapydScript dicts (but not arbitrary javascript objects). You can also define
|
|
|
1032
1122
|
the ``__eq__(self, other)`` method in your classes to have these operators work
|
|
1033
1123
|
for your own types.
|
|
1034
1124
|
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
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
|
+
```
|
|
1038
1154
|
|
|
1039
1155
|
Chained comparisons work just like Python — each middle operand is evaluated only once:
|
|
1040
1156
|
|
|
@@ -1042,18 +1158,13 @@ Chained comparisons work just like Python — each middle operand is evaluated o
|
|
|
1042
1158
|
# All of these work correctly, including mixed-direction chains
|
|
1043
1159
|
assert 1 < 2 < 3 # True
|
|
1044
1160
|
assert 1 < 2 > 0 # True (1<2 AND 2>0)
|
|
1045
|
-
assert 1 < 2
|
|
1161
|
+
assert [1] < [2] < [3] # True (lexicographic chain)
|
|
1046
1162
|
```
|
|
1047
1163
|
|
|
1048
1164
|
### Python Truthiness and `__bool__`
|
|
1049
1165
|
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
with:
|
|
1053
|
-
|
|
1054
|
-
```py
|
|
1055
|
-
from __python__ import truthiness
|
|
1056
|
-
```
|
|
1166
|
+
RapydScript uses Python truthiness semantics by default (``truthiness`` is
|
|
1167
|
+
**on by default**):
|
|
1057
1168
|
|
|
1058
1169
|
When this flag is active:
|
|
1059
1170
|
|
|
@@ -1064,8 +1175,6 @@ When this flag is active:
|
|
|
1064
1175
|
- **All condition positions** (`if`, `while`, `assert`, `not`, ternary) use Python semantics.
|
|
1065
1176
|
|
|
1066
1177
|
```py
|
|
1067
|
-
from __python__ import truthiness
|
|
1068
|
-
|
|
1069
1178
|
class Empty:
|
|
1070
1179
|
def __bool__(self): return False
|
|
1071
1180
|
|
|
@@ -1079,16 +1188,14 @@ z = [1] and 'ok' # z == 'ok'
|
|
|
1079
1188
|
|
|
1080
1189
|
The flag is **scoped** — it applies until the end of the enclosing
|
|
1081
1190
|
function or class body. Use `from __python__ import no_truthiness` to
|
|
1082
|
-
disable it in a sub-scope.
|
|
1191
|
+
disable it in a sub-scope where JavaScript truthiness is needed.
|
|
1083
1192
|
|
|
1084
1193
|
### Callable Objects (`__call__`)
|
|
1085
1194
|
|
|
1086
1195
|
Any class that defines `__call__` can be invoked directly with `obj(args)`,
|
|
1087
|
-
just like Python callable objects
|
|
1196
|
+
just like Python callable objects:
|
|
1088
1197
|
|
|
1089
1198
|
```python
|
|
1090
|
-
from __python__ import truthiness
|
|
1091
|
-
|
|
1092
1199
|
class Multiplier:
|
|
1093
1200
|
def __init__(self, factor):
|
|
1094
1201
|
self.factor = factor
|
|
@@ -1134,6 +1241,108 @@ Mutation methods (`add`, `remove`, `discard`, `clear`, `update`) are not
|
|
|
1134
1241
|
present on `frozenset` instances, enforcing immutability at the API level.
|
|
1135
1242
|
`frozenset` objects can be iterated and copied with `.copy()`.
|
|
1136
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
|
+
|
|
1137
1346
|
### `issubclass`
|
|
1138
1347
|
|
|
1139
1348
|
`issubclass(cls, classinfo)` checks whether a class is a subclass of another
|
|
@@ -1173,7 +1382,8 @@ semantics:
|
|
|
1173
1382
|
| other `float` | derived from the bit pattern |
|
|
1174
1383
|
| `str` | djb2 algorithm — stable within a process |
|
|
1175
1384
|
| object with `__hash__` | dispatches to `__hash__()` |
|
|
1176
|
-
| class instance | stable identity hash (assigned on first call) |
|
|
1385
|
+
| class instance (no `__hash__`) | stable identity hash (assigned on first call) |
|
|
1386
|
+
| class with `__eq__` but no `__hash__` | `TypeError` (unhashable — Python semantics) |
|
|
1177
1387
|
| `list` | `TypeError: unhashable type: 'list'` |
|
|
1178
1388
|
| `set` | `TypeError: unhashable type: 'set'` |
|
|
1179
1389
|
| `dict` | `TypeError: unhashable type: 'dict'` |
|
|
@@ -1193,8 +1403,213 @@ class Point:
|
|
|
1193
1403
|
return self.x * 31 + self.y
|
|
1194
1404
|
|
|
1195
1405
|
hash(Point(1, 2)) # 33
|
|
1406
|
+
|
|
1407
|
+
# Python semantics: __eq__ without __hash__ → unhashable
|
|
1408
|
+
class Bar:
|
|
1409
|
+
def __eq__(self, other):
|
|
1410
|
+
return True
|
|
1411
|
+
hash(Bar()) # TypeError: unhashable type: 'Bar'
|
|
1196
1412
|
```
|
|
1197
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
|
+
|
|
1560
|
+
### Attribute-Access Dunders
|
|
1561
|
+
|
|
1562
|
+
RapydScript supports the four Python attribute-interception hooks:
|
|
1563
|
+
`__getattr__`, `__setattr__`, `__delattr__`, and `__getattribute__`.
|
|
1564
|
+
When a class defines any of them, instances are automatically wrapped in a
|
|
1565
|
+
JavaScript `Proxy` that routes attribute access through the hooks — including
|
|
1566
|
+
accesses that occur inside `__init__`.
|
|
1567
|
+
|
|
1568
|
+
| Hook | When called |
|
|
1569
|
+
|---|---|
|
|
1570
|
+
| `__getattr__(self, name)` | Fallback — only called when normal lookup finds nothing |
|
|
1571
|
+
| `__setattr__(self, name, value)` | Every attribute assignment (including `self.x = …` in `__init__`) |
|
|
1572
|
+
| `__delattr__(self, name)` | Every `del obj.attr` |
|
|
1573
|
+
| `__getattribute__(self, name)` | Every attribute read (overrides normal lookup) |
|
|
1574
|
+
|
|
1575
|
+
To bypass the hooks from within the hook itself (avoiding infinite recursion),
|
|
1576
|
+
use the `object.*` bypass functions:
|
|
1577
|
+
|
|
1578
|
+
| Python idiom | Compiled form | Effect |
|
|
1579
|
+
|---|---|---|
|
|
1580
|
+
| `object.__setattr__(self, name, val)` | `ρσ_object_setattr(self, name, val)` | Set attribute directly, bypassing `__setattr__` |
|
|
1581
|
+
| `object.__getattribute__(self, name)` | `ρσ_object_getattr(self, name)` | Read attribute directly, bypassing `__getattribute__` |
|
|
1582
|
+
| `object.__delattr__(self, name)` | `ρσ_object_delattr(self, name)` | Delete attribute directly, bypassing `__delattr__` |
|
|
1583
|
+
|
|
1584
|
+
Subclasses automatically inherit proxy wrapping from their parent class — if
|
|
1585
|
+
`Base` defines `__getattr__`, all `Child(Base)` instances are also Proxy-wrapped.
|
|
1586
|
+
|
|
1587
|
+
```py
|
|
1588
|
+
class Validated:
|
|
1589
|
+
"""Reject negative values at assignment time."""
|
|
1590
|
+
def __setattr__(self, name, value):
|
|
1591
|
+
if jstype(value) is 'number' and value < 0:
|
|
1592
|
+
raise ValueError(name + ' must be non-negative')
|
|
1593
|
+
object.__setattr__(self, name, value)
|
|
1594
|
+
|
|
1595
|
+
v = Validated()
|
|
1596
|
+
v.x = 5 # ok
|
|
1597
|
+
v.x = -1 # ValueError: x must be non-negative
|
|
1598
|
+
|
|
1599
|
+
class AttrProxy:
|
|
1600
|
+
"""Log every attribute read."""
|
|
1601
|
+
def __init__(self):
|
|
1602
|
+
object.__setattr__(self, '_log', [])
|
|
1603
|
+
|
|
1604
|
+
def __getattribute__(self, name):
|
|
1605
|
+
self._log.append(name) # self._log goes through __getattribute__ too!
|
|
1606
|
+
return object.__getattribute__(self, name)
|
|
1607
|
+
```
|
|
1608
|
+
|
|
1609
|
+
> **Proxy support required** — The hooks rely on `Proxy`, which is available
|
|
1610
|
+
> in all modern browsers and Node.js ≥ 6. In environments that lack `Proxy`
|
|
1611
|
+
> the class still works, but the hooks are silently bypassed.
|
|
1612
|
+
|
|
1198
1613
|
Loops
|
|
1199
1614
|
-----
|
|
1200
1615
|
RapydScript's loops work like Python, not JavaScript. You can't, for example
|
|
@@ -1377,8 +1792,33 @@ format('hi', '>10') # ' hi' — right-aligned in 10-char field
|
|
|
1377
1792
|
format(42) # '42' — no spec: same as str(42)
|
|
1378
1793
|
```
|
|
1379
1794
|
|
|
1380
|
-
Objects with a `__format__` method are dispatched to it
|
|
1381
|
-
|
|
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).
|
|
1382
1822
|
|
|
1383
1823
|
String predicate methods are also available:
|
|
1384
1824
|
|
|
@@ -1405,6 +1845,16 @@ Case-folding for locale-insensitive lowercase comparison:
|
|
|
1405
1845
|
str.casefold('ÄÖÜ') == str.casefold('äöü') # True (maps to lowercase)
|
|
1406
1846
|
```
|
|
1407
1847
|
|
|
1848
|
+
Tab expansion:
|
|
1849
|
+
|
|
1850
|
+
```py
|
|
1851
|
+
str.expandtabs('a\tb', 4) # 'a b' — expand to next 4-space tab stop
|
|
1852
|
+
str.expandtabs('\t\t', 8) # ' ' — two full tab stops
|
|
1853
|
+
str.expandtabs('ab\tc', 4) # 'ab c' — only 2 spaces needed to reach next stop
|
|
1854
|
+
```
|
|
1855
|
+
|
|
1856
|
+
The optional `tabsize` argument defaults to `8`, matching Python's default. A `tabsize` of `0` removes all tab characters. Newline (`\n`) and carriage-return (`\r`) characters reset the column counter, so each line is expanded independently.
|
|
1857
|
+
|
|
1408
1858
|
However, if you want to make the python string methods available on string
|
|
1409
1859
|
objects, there is a convenience method in the standard library to do so. Use
|
|
1410
1860
|
the following code:
|
|
@@ -1612,6 +2062,7 @@ can annotate a variable with a type hint, with or without an initial value:
|
|
|
1612
2062
|
x: int = 42
|
|
1613
2063
|
name: str = "Alice"
|
|
1614
2064
|
items: list = [1, 2, 3]
|
|
2065
|
+
coords: tuple = (10, 20)
|
|
1615
2066
|
|
|
1616
2067
|
# Annotation only: declares the type without assigning a value
|
|
1617
2068
|
count: int
|
|
@@ -1656,18 +2107,22 @@ Regular Expressions
|
|
|
1656
2107
|
----------------------
|
|
1657
2108
|
|
|
1658
2109
|
RapydScript includes a ```re``` module that mimics the interface of the Python
|
|
1659
|
-
re module.
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
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.
|
|
1671
2126
|
|
|
1672
2127
|
You can use the JavaScript regex literal syntax, including verbose regex
|
|
1673
2128
|
literals, as shown below. In verbose mode, whitespace is ignored and # comments
|
|
@@ -1682,8 +2137,338 @@ re.match(///
|
|
|
1682
2137
|
a # a comment
|
|
1683
2138
|
b # Another comment
|
|
1684
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
|
|
2145
|
+
```
|
|
2146
|
+
|
|
2147
|
+
JSX Support
|
|
2148
|
+
-----------
|
|
2149
|
+
|
|
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.
|
|
2151
|
+
|
|
2152
|
+
JSX support is **on by default**. The ``jsx`` flag can be disabled with
|
|
2153
|
+
``from __python__ import no_jsx`` if needed.
|
|
2154
|
+
|
|
2155
|
+
### Requirements
|
|
2156
|
+
|
|
2157
|
+
`React` must be in scope at runtime. How you provide it depends on your environment:
|
|
2158
|
+
|
|
2159
|
+
- **Bundler (Vite, webpack, etc.):** `import React from 'react'` at the top of your file (or configure your bundler's global React shim).
|
|
2160
|
+
- **CDN / browser script tag:** load React before your compiled script.
|
|
2161
|
+
- **Bitburner:** React is already available as a global — no import needed.
|
|
2162
|
+
- **RapydScript web REPL:** a minimal React stub is injected automatically so `React.createElement` calls succeed.
|
|
2163
|
+
|
|
2164
|
+
### Output format
|
|
2165
|
+
|
|
2166
|
+
RapydScript compiles JSX to `React.createElement()` calls. For example:
|
|
2167
|
+
|
|
2168
|
+
```py
|
|
2169
|
+
def Greeting(props):
|
|
2170
|
+
return <h1>Hello, {props.name}!</h1>
|
|
2171
|
+
```
|
|
2172
|
+
|
|
2173
|
+
Compiles to:
|
|
2174
|
+
|
|
2175
|
+
```js
|
|
2176
|
+
function Greeting(props) {
|
|
2177
|
+
return React.createElement("h1", null, "Hello, ", props.name);
|
|
2178
|
+
}
|
|
1685
2179
|
```
|
|
1686
2180
|
|
|
2181
|
+
Lowercase tags (`div`, `span`, `h1`, …) become string arguments. Capitalised names and dot-notation (`MyComponent`, `Router.Route`) are passed as references — not strings.
|
|
2182
|
+
|
|
2183
|
+
### Attributes
|
|
2184
|
+
|
|
2185
|
+
String, expression, boolean, and hyphenated attribute names all work:
|
|
2186
|
+
|
|
2187
|
+
```py
|
|
2188
|
+
def Form(props):
|
|
2189
|
+
return (
|
|
2190
|
+
<form>
|
|
2191
|
+
<input
|
|
2192
|
+
type="text"
|
|
2193
|
+
aria-label="Name"
|
|
2194
|
+
disabled={props.readonly}
|
|
2195
|
+
onChange={props.onChange}
|
|
2196
|
+
required
|
|
2197
|
+
/>
|
|
2198
|
+
</form>
|
|
2199
|
+
)
|
|
2200
|
+
```
|
|
2201
|
+
|
|
2202
|
+
Hyphenated names (e.g. `aria-label`, `data-id`) are automatically quoted as object keys: `{"aria-label": "Name"}`. Boolean attributes with no value compile to `true`.
|
|
2203
|
+
|
|
2204
|
+
### Nested elements and expressions
|
|
2205
|
+
|
|
2206
|
+
```py
|
|
2207
|
+
def UserList(users):
|
|
2208
|
+
return (
|
|
2209
|
+
<ul className="user-list">
|
|
2210
|
+
{[<li key={u.id}>{u.name}</li> for u in users]}
|
|
2211
|
+
</ul>
|
|
2212
|
+
)
|
|
2213
|
+
```
|
|
2214
|
+
|
|
2215
|
+
### Fragments
|
|
2216
|
+
|
|
2217
|
+
Use `<>...</>` to return multiple elements without a wrapper node. Fragments compile to `React.createElement(React.Fragment, null, ...)`:
|
|
2218
|
+
|
|
2219
|
+
```py
|
|
2220
|
+
def TwoItems():
|
|
2221
|
+
return (
|
|
2222
|
+
<>
|
|
2223
|
+
<span>First</span>
|
|
2224
|
+
<span>Second</span>
|
|
2225
|
+
</>
|
|
2226
|
+
)
|
|
2227
|
+
```
|
|
2228
|
+
|
|
2229
|
+
### Self-closing elements
|
|
2230
|
+
|
|
2231
|
+
```py
|
|
2232
|
+
def Avatar(props):
|
|
2233
|
+
return <img src={props.url} alt={props.name} />
|
|
2234
|
+
```
|
|
2235
|
+
|
|
2236
|
+
### Spread attributes
|
|
2237
|
+
|
|
2238
|
+
```py
|
|
2239
|
+
def Button(props):
|
|
2240
|
+
return <button {...props}>Click me</button>
|
|
2241
|
+
```
|
|
2242
|
+
|
|
2243
|
+
Compiles to `React.createElement("button", {...props}, "Click me")`.
|
|
2244
|
+
|
|
2245
|
+
### Component tags
|
|
2246
|
+
|
|
2247
|
+
Capitalised names and dot-notation are treated as component references (not quoted strings):
|
|
2248
|
+
|
|
2249
|
+
```py
|
|
2250
|
+
def App():
|
|
2251
|
+
return (
|
|
2252
|
+
<Router.Provider>
|
|
2253
|
+
<MyComponent name="hello" />
|
|
2254
|
+
</Router.Provider>
|
|
2255
|
+
)
|
|
2256
|
+
```
|
|
2257
|
+
|
|
2258
|
+
Compiles to:
|
|
2259
|
+
|
|
2260
|
+
```js
|
|
2261
|
+
React.createElement(Router.Provider, null,
|
|
2262
|
+
React.createElement(MyComponent, {name: "hello"})
|
|
2263
|
+
)
|
|
2264
|
+
```
|
|
2265
|
+
|
|
2266
|
+
### Compiling JSX files
|
|
2267
|
+
|
|
2268
|
+
Since the output is plain JavaScript, compile to a `.js` file as normal:
|
|
2269
|
+
|
|
2270
|
+
```sh
|
|
2271
|
+
rapydscript mycomponent.pyj --output mycomponent.js
|
|
2272
|
+
```
|
|
2273
|
+
|
|
2274
|
+
React Standard Library
|
|
2275
|
+
-----------------------
|
|
2276
|
+
|
|
2277
|
+
RapydScript ships a `react` standard library module that re-exports every standard React hook and utility under their familiar Python-friendly names. Import the pieces you need and the compiler will resolve each name to the corresponding `React.*` property at compile time.
|
|
2278
|
+
|
|
2279
|
+
### Importing hooks
|
|
2280
|
+
|
|
2281
|
+
```py
|
|
2282
|
+
from react import useState, useEffect, useCallback, useMemo, useRef
|
|
2283
|
+
|
|
2284
|
+
def Counter():
|
|
2285
|
+
count, setCount = useState(0)
|
|
2286
|
+
|
|
2287
|
+
def increment():
|
|
2288
|
+
setCount(count + 1)
|
|
2289
|
+
|
|
2290
|
+
return <button onClick={increment}>{count}</button>
|
|
2291
|
+
```
|
|
2292
|
+
|
|
2293
|
+
Compiles to:
|
|
2294
|
+
|
|
2295
|
+
```js
|
|
2296
|
+
var useState = React.useState;
|
|
2297
|
+
// ...
|
|
2298
|
+
function Counter() {
|
|
2299
|
+
var [count, setCount] = React.useState(0);
|
|
2300
|
+
function increment() {
|
|
2301
|
+
setCount(count + 1);
|
|
2302
|
+
}
|
|
2303
|
+
return React.createElement("button", {onClick: increment}, count);
|
|
2304
|
+
}
|
|
2305
|
+
```
|
|
2306
|
+
|
|
2307
|
+
Tuple unpacking works naturally because `React.useState` returns a two-element array — `count, setCount = useState(0)` compiles to the ES6 destructuring `var [count, setCount] = React.useState(0)`.
|
|
2308
|
+
|
|
2309
|
+
### Available exports
|
|
2310
|
+
|
|
2311
|
+
**Hooks (React 16.8+)**
|
|
2312
|
+
|
|
2313
|
+
| Import name | React API |
|
|
2314
|
+
|---|---|
|
|
2315
|
+
| `useState` | `React.useState` |
|
|
2316
|
+
| `useEffect` | `React.useEffect` |
|
|
2317
|
+
| `useContext` | `React.useContext` |
|
|
2318
|
+
| `useReducer` | `React.useReducer` |
|
|
2319
|
+
| `useCallback` | `React.useCallback` |
|
|
2320
|
+
| `useMemo` | `React.useMemo` |
|
|
2321
|
+
| `useRef` | `React.useRef` |
|
|
2322
|
+
| `useImperativeHandle` | `React.useImperativeHandle` |
|
|
2323
|
+
| `useLayoutEffect` | `React.useLayoutEffect` |
|
|
2324
|
+
| `useDebugValue` | `React.useDebugValue` |
|
|
2325
|
+
|
|
2326
|
+
**Hooks (React 18+)**
|
|
2327
|
+
|
|
2328
|
+
| Import name | React API |
|
|
2329
|
+
|---|---|
|
|
2330
|
+
| `useId` | `React.useId` |
|
|
2331
|
+
| `useTransition` | `React.useTransition` |
|
|
2332
|
+
| `useDeferredValue` | `React.useDeferredValue` |
|
|
2333
|
+
| `useSyncExternalStore` | `React.useSyncExternalStore` |
|
|
2334
|
+
| `useInsertionEffect` | `React.useInsertionEffect` |
|
|
2335
|
+
|
|
2336
|
+
**Core classes and elements**
|
|
2337
|
+
|
|
2338
|
+
| Import name | React API |
|
|
2339
|
+
|---|---|
|
|
2340
|
+
| `Component` | `React.Component` |
|
|
2341
|
+
| `PureComponent` | `React.PureComponent` |
|
|
2342
|
+
| `Fragment` | `React.Fragment` |
|
|
2343
|
+
| `StrictMode` | `React.StrictMode` |
|
|
2344
|
+
| `Suspense` | `React.Suspense` |
|
|
2345
|
+
| `Profiler` | `React.Profiler` |
|
|
2346
|
+
|
|
2347
|
+
**Utilities**
|
|
2348
|
+
|
|
2349
|
+
| Import name | React API |
|
|
2350
|
+
|---|---|
|
|
2351
|
+
| `createElement` | `React.createElement` |
|
|
2352
|
+
| `cloneElement` | `React.cloneElement` |
|
|
2353
|
+
| `createContext` | `React.createContext` |
|
|
2354
|
+
| `createRef` | `React.createRef` |
|
|
2355
|
+
| `forwardRef` | `React.forwardRef` |
|
|
2356
|
+
| `isValidElement` | `React.isValidElement` |
|
|
2357
|
+
| `memo` | `React.memo` |
|
|
2358
|
+
| `lazy` | `React.lazy` |
|
|
2359
|
+
|
|
2360
|
+
### Common patterns
|
|
2361
|
+
|
|
2362
|
+
**useEffect with cleanup**
|
|
2363
|
+
|
|
2364
|
+
```py
|
|
2365
|
+
from react import useState, useEffect
|
|
2366
|
+
|
|
2367
|
+
def Timer():
|
|
2368
|
+
count, setCount = useState(0)
|
|
2369
|
+
def setup():
|
|
2370
|
+
interval = setInterval(def(): setCount(count + 1);, 1000)
|
|
2371
|
+
def cleanup():
|
|
2372
|
+
clearInterval(interval)
|
|
2373
|
+
return cleanup
|
|
2374
|
+
useEffect(setup, [count])
|
|
2375
|
+
return count
|
|
2376
|
+
```
|
|
2377
|
+
|
|
2378
|
+
**useReducer**
|
|
2379
|
+
|
|
2380
|
+
```py
|
|
2381
|
+
from react import useReducer
|
|
2382
|
+
|
|
2383
|
+
def reducer(state, action):
|
|
2384
|
+
if action.type == 'increment':
|
|
2385
|
+
return state + 1
|
|
2386
|
+
if action.type == 'decrement':
|
|
2387
|
+
return state - 1
|
|
2388
|
+
return state
|
|
2389
|
+
|
|
2390
|
+
def Counter():
|
|
2391
|
+
state, dispatch = useReducer(reducer, 0)
|
|
2392
|
+
def inc():
|
|
2393
|
+
dispatch({'type': 'increment'})
|
|
2394
|
+
return state
|
|
2395
|
+
```
|
|
2396
|
+
|
|
2397
|
+
**useContext**
|
|
2398
|
+
|
|
2399
|
+
```py
|
|
2400
|
+
from react import createContext, useContext
|
|
2401
|
+
|
|
2402
|
+
ThemeContext = createContext('light')
|
|
2403
|
+
|
|
2404
|
+
def ThemedButton():
|
|
2405
|
+
theme = useContext(ThemeContext)
|
|
2406
|
+
return theme
|
|
2407
|
+
```
|
|
2408
|
+
|
|
2409
|
+
**useRef**
|
|
2410
|
+
|
|
2411
|
+
```py
|
|
2412
|
+
from react import useRef
|
|
2413
|
+
|
|
2414
|
+
def FocusInput():
|
|
2415
|
+
inputRef = useRef(None)
|
|
2416
|
+
def handleClick():
|
|
2417
|
+
inputRef.current.focus()
|
|
2418
|
+
return <input ref={inputRef} />
|
|
2419
|
+
```
|
|
2420
|
+
|
|
2421
|
+
**memo**
|
|
2422
|
+
|
|
2423
|
+
```py
|
|
2424
|
+
from react import memo
|
|
2425
|
+
|
|
2426
|
+
def Row(props):
|
|
2427
|
+
return <li>{props.label}</li>
|
|
2428
|
+
|
|
2429
|
+
MemoRow = memo(Row)
|
|
2430
|
+
```
|
|
2431
|
+
|
|
2432
|
+
**forwardRef**
|
|
2433
|
+
|
|
2434
|
+
```py
|
|
2435
|
+
from react import forwardRef
|
|
2436
|
+
|
|
2437
|
+
def FancyInput(props, ref):
|
|
2438
|
+
return <input ref={ref} placeholder={props.placeholder} />
|
|
2439
|
+
|
|
2440
|
+
FancyInputWithRef = forwardRef(FancyInput)
|
|
2441
|
+
```
|
|
2442
|
+
|
|
2443
|
+
**Class component**
|
|
2444
|
+
|
|
2445
|
+
You can extend `React.Component` directly without importing it, or import `Component` from the `react` module:
|
|
2446
|
+
|
|
2447
|
+
```py
|
|
2448
|
+
from react import Component
|
|
2449
|
+
|
|
2450
|
+
class Greeter(Component):
|
|
2451
|
+
def render(self):
|
|
2452
|
+
return <h1>Hello, {self.props.name}!</h1>
|
|
2453
|
+
```
|
|
2454
|
+
|
|
2455
|
+
**useTransition (React 18)**
|
|
2456
|
+
|
|
2457
|
+
```py
|
|
2458
|
+
from react import useState, useTransition
|
|
2459
|
+
|
|
2460
|
+
def SearchInput():
|
|
2461
|
+
isPending, startTransition = useTransition()
|
|
2462
|
+
query, setQuery = useState('')
|
|
2463
|
+
def handleChange(e):
|
|
2464
|
+
startTransition(def(): setQuery(e.target.value);)
|
|
2465
|
+
return isPending
|
|
2466
|
+
```
|
|
2467
|
+
|
|
2468
|
+
### Requirements
|
|
2469
|
+
|
|
2470
|
+
The `react` module does not bundle React itself — it provides compile-time name bindings only. `React` must be available as a global variable at runtime, exactly as described in the [JSX Requirements](#requirements) section above.
|
|
2471
|
+
|
|
1687
2472
|
Creating DOM trees easily
|
|
1688
2473
|
---------------------------------
|
|
1689
2474
|
|
|
@@ -1914,6 +2699,117 @@ print(Counter.get_count()) # 2
|
|
|
1914
2699
|
|
|
1915
2700
|
The `@classmethod` decorator compiles to a method placed directly on the class (not its prototype), with `cls` mapped to `this`. A prototype delegation shim is also generated so instance calls work correctly.
|
|
1916
2701
|
|
|
2702
|
+
### `__new__` Constructor Hook
|
|
2703
|
+
|
|
2704
|
+
RapydScript supports Python's `__new__` method, which runs *before* `__init__` and controls instance creation. Use it to implement patterns like singletons or alternative constructors:
|
|
2705
|
+
|
|
2706
|
+
```py
|
|
2707
|
+
class Singleton:
|
|
2708
|
+
_instance = None
|
|
2709
|
+
def __new__(cls):
|
|
2710
|
+
if cls._instance is None:
|
|
2711
|
+
cls._instance = super().__new__(cls)
|
|
2712
|
+
return cls._instance
|
|
2713
|
+
def __init__(self):
|
|
2714
|
+
pass
|
|
2715
|
+
|
|
2716
|
+
a = Singleton()
|
|
2717
|
+
b = Singleton()
|
|
2718
|
+
assert a is b # same instance
|
|
2719
|
+
```
|
|
2720
|
+
|
|
2721
|
+
`super().__new__(cls)` creates a bare instance of `cls` (equivalent to `Object.create(cls.prototype)` in JavaScript). If `__new__` returns an instance of the class, `__init__` is called on it automatically. If it returns something else, `__init__` is skipped.
|
|
2722
|
+
|
|
2723
|
+
Class variables accessed via `cls` inside `__new__` are correctly rewritten to `cls.prototype.varname`, matching Python's semantics.
|
|
2724
|
+
|
|
2725
|
+
### `__class_getitem__`
|
|
2726
|
+
|
|
2727
|
+
RapydScript supports Python's `__class_getitem__` hook, which enables subscript syntax on a class itself (`MyClass[item]`). Define `__class_getitem__(cls, item)` in a class body to intercept `ClassName[x]`:
|
|
2728
|
+
|
|
2729
|
+
```py
|
|
2730
|
+
class Box:
|
|
2731
|
+
def __class_getitem__(cls, item):
|
|
2732
|
+
return cls.__name__ + '[' + str(item) + ']'
|
|
2733
|
+
|
|
2734
|
+
print(Box[int]) # Box[<class 'int'>]
|
|
2735
|
+
print(Box['str']) # Box[str]
|
|
2736
|
+
```
|
|
2737
|
+
|
|
2738
|
+
`__class_getitem__` is an implicit `@classmethod`: the compiler strips `cls` from the JS parameter list and maps it to `this`, so calling `Box[item]` compiles to `Box.__class_getitem__(item)` with `this = Box`.
|
|
2739
|
+
|
|
2740
|
+
Subclasses inherit `__class_getitem__` from their parent and receive the subclass as `cls`:
|
|
2741
|
+
|
|
2742
|
+
```py
|
|
2743
|
+
class Base:
|
|
2744
|
+
def __class_getitem__(cls, item):
|
|
2745
|
+
return cls.__name__ + '<' + str(item) + '>'
|
|
2746
|
+
|
|
2747
|
+
class Child(Base):
|
|
2748
|
+
pass
|
|
2749
|
+
|
|
2750
|
+
print(Base[42]) # Base<42>
|
|
2751
|
+
print(Child[42]) # Child<42>
|
|
2752
|
+
```
|
|
2753
|
+
|
|
2754
|
+
Class variables declared in the class body are accessible via `cls.varname` inside `__class_getitem__`, just as with `@classmethod`.
|
|
2755
|
+
|
|
2756
|
+
### `__init_subclass__`
|
|
2757
|
+
|
|
2758
|
+
`__init_subclass__` is a hook that is called automatically on a base class whenever a subclass is created. It is an implicit `@classmethod`: the compiler strips `cls` from the JS signature and maps it to `this`, so `cls` receives the newly-created subclass.
|
|
2759
|
+
|
|
2760
|
+
```py
|
|
2761
|
+
class PluginBase:
|
|
2762
|
+
_plugins = []
|
|
2763
|
+
|
|
2764
|
+
def __init_subclass__(cls, **kwargs):
|
|
2765
|
+
PluginBase._plugins.append(cls)
|
|
2766
|
+
|
|
2767
|
+
class AudioPlugin(PluginBase):
|
|
2768
|
+
pass
|
|
2769
|
+
|
|
2770
|
+
class VideoPlugin(PluginBase):
|
|
2771
|
+
pass
|
|
2772
|
+
|
|
2773
|
+
print(len(PluginBase._plugins)) # 2
|
|
2774
|
+
print(PluginBase._plugins[0].__name__) # AudioPlugin
|
|
2775
|
+
```
|
|
2776
|
+
|
|
2777
|
+
Keyword arguments written in the class header are forwarded to `__init_subclass__`:
|
|
2778
|
+
|
|
2779
|
+
```py
|
|
2780
|
+
class Base:
|
|
2781
|
+
def __init_subclass__(cls, required=False, **kwargs):
|
|
2782
|
+
cls._required = required
|
|
2783
|
+
|
|
2784
|
+
class Strict(Base, required=True):
|
|
2785
|
+
pass
|
|
2786
|
+
|
|
2787
|
+
class Loose(Base):
|
|
2788
|
+
pass
|
|
2789
|
+
|
|
2790
|
+
print(Strict._required) # True
|
|
2791
|
+
print(Loose._required) # False
|
|
2792
|
+
```
|
|
2793
|
+
|
|
2794
|
+
Use `super().__init_subclass__(**kwargs)` to propagate the hook up the hierarchy:
|
|
2795
|
+
|
|
2796
|
+
```py
|
|
2797
|
+
class GrandParent:
|
|
2798
|
+
def __init_subclass__(cls, **kwargs):
|
|
2799
|
+
cls._from_grandparent = True
|
|
2800
|
+
|
|
2801
|
+
class Parent(GrandParent):
|
|
2802
|
+
def __init_subclass__(cls, **kwargs):
|
|
2803
|
+
super().__init_subclass__(**kwargs) # propagates to GrandParent
|
|
2804
|
+
|
|
2805
|
+
class Child(Parent):
|
|
2806
|
+
pass
|
|
2807
|
+
|
|
2808
|
+
print(Child._from_grandparent) # True
|
|
2809
|
+
```
|
|
2810
|
+
|
|
2811
|
+
The hook is called after the subclass is fully set up (including `__name__`, `__qualname__`, and `__module__`), so `cls.__name__` is always the correct subclass name inside the hook.
|
|
2812
|
+
|
|
1917
2813
|
### Nested Classes
|
|
1918
2814
|
|
|
1919
2815
|
A class may be defined inside another class. The nested class becomes an attribute of the outer class (accessible as `Outer.Inner`) and is also reachable via instances (`self.Inner` inside methods). This mirrors Python semantics exactly.
|
|
@@ -2014,67 +2910,51 @@ You could also use `external` decorator to bypass improperly imported RapydScrip
|
|
|
2014
2910
|
|
|
2015
2911
|
### Method Binding
|
|
2016
2912
|
|
|
2017
|
-
|
|
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.
|
|
2018
2916
|
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
def __init__(self, name):
|
|
2022
|
-
self.name = name
|
|
2023
|
-
|
|
2024
|
-
def greet(self):
|
|
2025
|
-
if self:
|
|
2026
|
-
print('My name is' + self.name)
|
|
2027
|
-
|
|
2028
|
-
tod = Boy('Tod')
|
|
2029
|
-
tod.greet() # Hello, my name is Tod
|
|
2030
|
-
getattr(tod, 'greet')() # prints nothing
|
|
2031
|
-
```
|
|
2917
|
+
If you need to disable auto-binding in a scope, use
|
|
2918
|
+
``from __python__ import no_bound_methods``.
|
|
2032
2919
|
|
|
2033
|
-
|
|
2034
|
-
automatically bound when the objects of that class are instantiated. In order
|
|
2035
|
-
to do that, use a *scoped flag*, which is a simple instruction to the compiler
|
|
2036
|
-
telling it to auto-bind methods, as shown below:
|
|
2920
|
+
For example:
|
|
2037
2921
|
|
|
2038
2922
|
```py
|
|
2923
|
+
class C:
|
|
2924
|
+
def __init__(self):
|
|
2925
|
+
self.a = 3
|
|
2039
2926
|
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
def __init__(self):
|
|
2044
|
-
self.a = 3
|
|
2045
|
-
|
|
2046
|
-
def val(self):
|
|
2047
|
-
return self.a
|
|
2927
|
+
def val(self):
|
|
2928
|
+
return self.a
|
|
2048
2929
|
|
|
2049
|
-
getattr(
|
|
2930
|
+
getattr(C(), 'val')() == 3 # works because bound_methods is on by default
|
|
2050
2931
|
```
|
|
2051
2932
|
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
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:
|
|
2055
2935
|
|
|
2056
2936
|
```py
|
|
2057
2937
|
class C:
|
|
2058
2938
|
|
|
2059
|
-
def
|
|
2060
|
-
pass #
|
|
2061
|
-
|
|
2062
|
-
from __python__ import bound_methods
|
|
2063
|
-
# Methods below this line will be auto-bound
|
|
2064
|
-
|
|
2065
|
-
def bound(self):
|
|
2066
|
-
pass # This method will be auto-bound
|
|
2939
|
+
def bound1(self):
|
|
2940
|
+
pass # auto-bound (default)
|
|
2067
2941
|
|
|
2068
2942
|
from __python__ import no_bound_methods
|
|
2069
2943
|
# Methods below this line will not be auto-bound
|
|
2070
2944
|
|
|
2071
|
-
def
|
|
2072
|
-
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
|
|
2073
2953
|
```
|
|
2074
2954
|
|
|
2075
2955
|
Scoped flags apply only to the scope they are defined in, so if you define them
|
|
2076
2956
|
inside a class declaration, they only apply to that class. If you define it at
|
|
2077
|
-
the module level, it will
|
|
2957
|
+
the module level, it will apply to all classes in the module that occur
|
|
2078
2958
|
below that line, and so on.
|
|
2079
2959
|
|
|
2080
2960
|
Iterators
|
|
@@ -2350,6 +3230,45 @@ except SomeInternalError:
|
|
|
2350
3230
|
|
|
2351
3231
|
Basically, `try/except/finally` in RapydScript works very similar to the way it does in Python 3.
|
|
2352
3232
|
|
|
3233
|
+
### Exception Groups (`except*`, Python 3.11+)
|
|
3234
|
+
|
|
3235
|
+
RapydScript supports exception groups via the `ExceptionGroup` class and `except*` syntax. An `ExceptionGroup` bundles multiple exceptions under a single message:
|
|
3236
|
+
|
|
3237
|
+
```py
|
|
3238
|
+
eg = ExceptionGroup("network errors", [
|
|
3239
|
+
TimeoutError("host A timed out"),
|
|
3240
|
+
TimeoutError("host B timed out"),
|
|
3241
|
+
ValueError("bad address"),
|
|
3242
|
+
])
|
|
3243
|
+
raise eg
|
|
3244
|
+
```
|
|
3245
|
+
|
|
3246
|
+
Use `except*` to handle exceptions by type — each handler receives a sub-group containing only the matching exceptions:
|
|
3247
|
+
|
|
3248
|
+
```py
|
|
3249
|
+
try:
|
|
3250
|
+
fetch_all()
|
|
3251
|
+
except* TimeoutError as group:
|
|
3252
|
+
for e in group.exceptions:
|
|
3253
|
+
print("timeout:", e)
|
|
3254
|
+
except* ValueError as group:
|
|
3255
|
+
print("bad input:", group.exceptions[0])
|
|
3256
|
+
```
|
|
3257
|
+
|
|
3258
|
+
- Each `except*` clause sees the exceptions not already matched by earlier clauses.
|
|
3259
|
+
- Any unmatched exceptions are automatically re-raised as a new `ExceptionGroup`.
|
|
3260
|
+
- A bare `except*:` (no type) catches all remaining exceptions.
|
|
3261
|
+
- You cannot mix `except` and `except*` in the same `try` block.
|
|
3262
|
+
- Plain (non-group) exceptions can also be caught with `except*`; the variable is bound to the exception itself rather than a sub-group.
|
|
3263
|
+
|
|
3264
|
+
`ExceptionGroup` also provides `subgroup(condition)` and `split(condition)` for programmatic filtering, where `condition` is either an exception class or a predicate function:
|
|
3265
|
+
|
|
3266
|
+
```py
|
|
3267
|
+
eg = ExceptionGroup("mixed", [ValueError("v"), TypeError("t")])
|
|
3268
|
+
ve_group = eg.subgroup(ValueError) # ExceptionGroup of just ValueErrors
|
|
3269
|
+
matched, rest = eg.split(ValueError) # (ve_group, te_group)
|
|
3270
|
+
```
|
|
3271
|
+
|
|
2353
3272
|
Scope Control
|
|
2354
3273
|
-------------
|
|
2355
3274
|
|
|
@@ -2416,7 +3335,16 @@ One of Python's main strengths is the number of libraries available to the devel
|
|
|
2416
3335
|
gettext # Support for internationalization of your RapydScript app
|
|
2417
3336
|
operator # a subset of Python's operator module
|
|
2418
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
|
|
2419
3343
|
collections # namedtuple, deque, Counter, OrderedDict, defaultdict
|
|
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, …
|
|
2420
3348
|
itertools # count, cycle, repeat, accumulate, chain, compress, dropwhile, filterfalse,
|
|
2421
3349
|
# groupby, islice, pairwise, starmap, takewhile, zip_longest,
|
|
2422
3350
|
# product, permutations, combinations, combinations_with_replacement
|
|
@@ -2624,18 +3552,21 @@ As a result, there are some things in RapydScript that might come as surprises
|
|
|
2624
3552
|
to an experienced Python developer. The most important such gotchas are listed
|
|
2625
3553
|
below:
|
|
2626
3554
|
|
|
2627
|
-
-
|
|
2628
|
-
are
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
``
|
|
3555
|
+
- RapydScript uses Python truthiness semantics by default: empty lists and dicts
|
|
3556
|
+
are falsy and ``__bool__`` is dispatched. This is controlled by the
|
|
3557
|
+
``truthiness`` flag, which is on by default. Use
|
|
3558
|
+
``from __python__ import no_truthiness`` to fall back to JavaScript truthiness
|
|
3559
|
+
in a scope.
|
|
3560
|
+
|
|
3561
|
+
- Operator overloading is enabled by default via the ``overload_operators``
|
|
3562
|
+
flag, so ``[1] + [1]`` produces a new list and ``'ha' * 3`` produces
|
|
3563
|
+
``'hahaha'``. Type-checking is controlled by the separate ``strict_arithmetic``
|
|
3564
|
+
flag (also on by default): mixing incompatible types raises ``TypeError``
|
|
3565
|
+
(e.g. ``1 + 'x'`` → ``TypeError: unsupported operand type(s) for +: 'int'
|
|
3566
|
+
and 'str'``). Use ``from __python__ import no_strict_arithmetic`` to keep
|
|
3567
|
+
dunder dispatch but revert to JavaScript's silent coercion for unrecognised
|
|
3568
|
+
type combinations. Use ``from __python__ import no_overload_operators`` to
|
|
3569
|
+
disable operator overloading entirely.
|
|
2639
3570
|
|
|
2640
3571
|
- There are many more keywords than in Python. Because RapydScript compiles
|
|
2641
3572
|
down to JavaScript, the set of keywords is all the keywords of Python + all
|
|
@@ -2656,9 +3587,40 @@ below:
|
|
|
2656
3587
|
yourself. Similarly, the compiler will try to convert SomeClass.method() into
|
|
2657
3588
|
SomeClass.prototype.method() for you, but again, this is not 100% reliable.
|
|
2658
3589
|
|
|
2659
|
-
- The {"a":b} syntax
|
|
2660
|
-
|
|
2661
|
-
|
|
3590
|
+
- The ``{"a":b}`` syntax creates Python ``dict`` objects by default (the
|
|
3591
|
+
``dict_literals`` flag is on by default). Use
|
|
3592
|
+
``from __python__ import no_dict_literals`` to get plain JavaScript objects
|
|
3593
|
+
in a scope. See the section on dictionaries above for details.
|
|
3594
|
+
|
|
3595
|
+
|
|
3596
|
+
Python Flags
|
|
3597
|
+
------------
|
|
3598
|
+
|
|
3599
|
+
Python flags are scoped compiler directives that control Python semantics.
|
|
3600
|
+
All flags are **on by default**. They can be turned off in a scope with
|
|
3601
|
+
the ``no_`` prefix. In source code they are written as:
|
|
3602
|
+
|
|
3603
|
+
```py
|
|
3604
|
+
from __python__ import flag_name
|
|
3605
|
+
```
|
|
3606
|
+
|
|
3607
|
+
At the top level they take effect for the rest of the file; inside a function
|
|
3608
|
+
or class body they apply only to that scope. Prefix a flag with `no_` to turn
|
|
3609
|
+
it off in a scope (e.g. `from __python__ import no_truthiness`).
|
|
3610
|
+
|
|
3611
|
+
All flags are **on by default**. To revert to legacy RapydScript behavior
|
|
3612
|
+
with no flags enabled, pass ``--legacy-rapydscript`` on the command line.
|
|
3613
|
+
|
|
3614
|
+
| Flag | Description |
|
|
3615
|
+
|---|---|
|
|
3616
|
+
| `dict_literals` | `{k: v}` literals create Python `dict` objects instead of plain JS objects. On by default. |
|
|
3617
|
+
| `overload_getitem` | `obj[key]` dispatches to `__getitem__` / `__setitem__` / `__delitem__` on objects that define them. On by default. |
|
|
3618
|
+
| `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. |
|
|
3619
|
+
| `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. |
|
|
3620
|
+
| `truthiness` | Boolean tests and `bool()` dispatch to `__bool__` and treat empty containers as falsy, matching Python semantics. On by default. |
|
|
3621
|
+
| `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. |
|
|
3622
|
+
| `hash_literals` | `{k: v}` creates a Python `dict` (alias for `dict_literals`; kept for backward compatibility). On by default. |
|
|
3623
|
+
| `jsx` | JSX syntax (`<Tag attr={expr}>children</Tag>`) is enabled. On by default. |
|
|
2662
3624
|
|
|
2663
3625
|
|
|
2664
3626
|
Monaco Language Service
|
|
@@ -2722,9 +3684,11 @@ when the editor is torn down.
|
|
|
2722
3684
|
| `compiler` | object | — | **Required.** The `window.RapydScript` compiler bundle. |
|
|
2723
3685
|
| `parseDelay` | number | `300` | Debounce delay (ms) before re-checking after an edit. |
|
|
2724
3686
|
| `virtualFiles` | `{name: source}` | `{}` | Virtual modules available to `import` statements. |
|
|
3687
|
+
| `stdlibFiles` | `{name: source}` | `{}` | Like `virtualFiles` but treated as stdlib — always available and never produce bad-import warnings. |
|
|
2725
3688
|
| `dtsFiles` | `[{name, content}]` | `[]` | TypeScript `.d.ts` files loaded at startup. |
|
|
2726
3689
|
| `loadDts` | `(name) => Promise<string>` | — | Async callback for lazy-loading `.d.ts` content on demand. |
|
|
2727
3690
|
| `extraBuiltins` | `{name: true}` | `{}` | Extra global names that suppress undefined-symbol warnings. |
|
|
3691
|
+
| `pythonFlags` | string | — | Comma-separated Python flags to enable globally (e.g. `"dict_literals,overload_getitem"`). See [Python Flags](#python-flags) above. |
|
|
2728
3692
|
|
|
2729
3693
|
### Runtime API
|
|
2730
3694
|
|
|
@@ -2744,6 +3708,9 @@ service.loadDts('lib.dom').then(function () { console.log('DOM types loaded'); }
|
|
|
2744
3708
|
// Suppress undefined-symbol warnings for additional global names
|
|
2745
3709
|
service.addGlobals(['myFrameworkGlobal', '$']);
|
|
2746
3710
|
|
|
3711
|
+
// Get the most recently built scope map for a Monaco model (null if not yet analysed)
|
|
3712
|
+
var scopeMap = service.getScopeMap(editorModel);
|
|
3713
|
+
|
|
2747
3714
|
// Tear down all Monaco providers and event listeners
|
|
2748
3715
|
service.dispose();
|
|
2749
3716
|
```
|
|
@@ -2867,6 +3834,13 @@ original `.py` file with working breakpoints and correct error stack frames.
|
|
|
2867
3834
|
| `keep_docstrings` | bool | `false` | Keep docstrings in the output. |
|
|
2868
3835
|
| `js_version` | number | `6` | Target ECMAScript version (5 or 6). |
|
|
2869
3836
|
| `private_scope` | bool | `false` | Wrap the output in an IIFE. |
|
|
3837
|
+
| `python_flags` | string | — | Comma-separated Python flags to enable for this compilation (e.g. `"dict_literals,overload_operators"`). See [Python Flags](#python-flags) above. Flags set here override any inherited from a previous `compile()` call on a streaming compiler. |
|
|
3838
|
+
| `virtual_files` | `{name: source}` | — | Map of module-name → RapydScript source for modules importable via `import`. Only used when the underlying streaming compiler was created with a virtual-file context (as `web_repl()` does). |
|
|
3839
|
+
| `discard_asserts` | bool | `false` | Strip all `assert` statements from the output. |
|
|
3840
|
+
| `omit_function_metadata` | bool | `false` | Omit per-function metadata (e.g. argument names) from the output for smaller bundles. |
|
|
3841
|
+
| `write_name` | bool | `false` | Emit a `var __name__ = "…"` assignment at the top of the output. |
|
|
3842
|
+
| `tree_shake` | bool | `false` | Remove unused imported names from the output (requires stdlib imports). |
|
|
3843
|
+
| `filename` | string | `'<input>'` | Source filename embedded in the source map and used in error messages. |
|
|
2870
3844
|
|
|
2871
3845
|
**How it works**
|
|
2872
3846
|
|
|
@@ -2897,6 +3871,242 @@ This runs all seven language-service test suites (diagnostics, scope analysis,
|
|
|
2897
3871
|
completions, signature help, hover, DTS registry, and built-in stubs).
|
|
2898
3872
|
|
|
2899
3873
|
|
|
3874
|
+
Python Feature Coverage
|
|
3875
|
+
-----------------------
|
|
3876
|
+
|
|
3877
|
+
### Fully Supported
|
|
3878
|
+
|
|
3879
|
+
| Feature | Notes |
|
|
3880
|
+
|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---|
|
|
3881
|
+
| `super()` — 0-arg and 2-arg forms | `super().method()` and `super(Cls, self).method()` both work |
|
|
3882
|
+
| `except TypeA, TypeB as e:` | RapydScript comma-separated form; catches multiple exception types |
|
|
3883
|
+
| `except (TypeA, TypeError) as e:` | Tuple form also supported |
|
|
3884
|
+
| `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 |
|
|
3885
|
+
| `try / else` | `else` block runs only when no exception was raised |
|
|
3886
|
+
| `for / else` | `else` block runs when loop completes without `break`; nested break isolation works |
|
|
3887
|
+
| `while / else` | `else` block runs when loop condition becomes `False` without a `break`; nested `break` isolation correct |
|
|
3888
|
+
| `with A() as a, B() as b:` | Multiple context managers in one statement; exits in LIFO order (Python-correct) |
|
|
3889
|
+
| `callable(fn)` | Works for plain functions and objects with `__call__` |
|
|
3890
|
+
| `round(x, ndigits=0)` | Full Python semantics including negative `ndigits` |
|
|
3891
|
+
| `enumerate(iterable, start=0)` | `start` parameter supported |
|
|
3892
|
+
| `str.isspace()`, `str.islower()`, `str.isupper()` | Working string predicates |
|
|
3893
|
+
| `str.isalpha()` | Regex-based; empty string returns `False` |
|
|
3894
|
+
| `str.isdigit()` | Regex-based (`\d+`) |
|
|
3895
|
+
| `str.isalnum()` | Regex-based |
|
|
3896
|
+
| `str.isidentifier()` | Checks `^[a-zA-Z_][a-zA-Z0-9_]*$` |
|
|
3897
|
+
| `str.casefold()` | Maps to `.toLowerCase()` |
|
|
3898
|
+
| `str.removeprefix(prefix)` | Returns unchanged string if prefix not found |
|
|
3899
|
+
| `str.removesuffix(suffix)` | Returns unchanged string if suffix not found |
|
|
3900
|
+
| `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) |
|
|
3901
|
+
| `str * n` string repetition | Works (via `overload_operators`, on by default) |
|
|
3902
|
+
| `list * n` / `n * list` | Works (via `overload_operators`); returns a proper RapydScript list |
|
|
3903
|
+
| `list + list` concatenation | `[1,2] + [3,4]` returns `[1, 2, 3, 4]`; `+=` extends in-place. No flag required. |
|
|
3904
|
+
| `match / case` | Structural pattern matching (Python 3.10) fully supported |
|
|
3905
|
+
| Variable type annotations `x: int = 1` | Parsed and ignored (no runtime enforcement); annotated assignments work normally |
|
|
3906
|
+
| Ellipsis literal `...` as expression | Parsed as a valid expression; evaluates to JS `undefined` at runtime |
|
|
3907
|
+
| Generator `.throw()` | Works via JS generator protocol |
|
|
3908
|
+
| Generator `.send()` | Works via `g.next(value)` |
|
|
3909
|
+
| `yield from` | Works; return value of sub-generator is not accessible |
|
|
3910
|
+
| `+=`, `-=`, `*=`, `/=`, `//=`, `**=`, `%=`, `&=`, `\|=`, `^=`, `<<=`, `>>=` | All augmented assignments work |
|
|
3911
|
+
| `raise X from Y` exception chaining | Sets `__cause__` on the thrown exception; `from None` also supported |
|
|
3912
|
+
| Starred assignment `a, *b, c = ...` | Works |
|
|
3913
|
+
| `[*a, 1, *b]` list spread | Works; any iterable; translates to `[...a, 1, ...b]` |
|
|
3914
|
+
| `{*a, 1, *b}` set spread | Works; translates to `ρσ_set([...a, 1, ...b])` |
|
|
3915
|
+
| `**expr` in function calls | Works with any expression (variable, attr access, call, dict literal), not just plain names |
|
|
3916
|
+
| `@classmethod`, `@staticmethod`, `@property` / `@prop.setter` | All work |
|
|
3917
|
+
| `{**dict1, **dict2}` dict spread | Works as merge replacement for the missing `\|` operator |
|
|
3918
|
+
| `dict.fromkeys()` | Works (via `dict_literals`, on by default) |
|
|
3919
|
+
| 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. |
|
|
3920
|
+
| `for`, `while`, `try/except/finally`, `with`, `match/case` | All control-flow constructs work |
|
|
3921
|
+
| Classes, inheritance, decorators, `__dunder__` methods | Fully supported |
|
|
3922
|
+
| Nested class definitions | Accessible as `Outer.Inner` and via instance (`self.Inner`); arbitrary nesting depth; nested class may inherit from outer-scope classes |
|
|
3923
|
+
| List / dict / set comprehensions, generator expressions | Fully supported |
|
|
3924
|
+
| f-strings, `str.format()`, `format()` builtin, all common `str.*` methods | Fully supported |
|
|
3925
|
+
| `abs()`, `divmod()`, `any()`, `all()`, `sum()`, `min()`, `max()` | All work |
|
|
3926
|
+
| `sorted()`, `reversed()`, `zip()`, `map()`, `filter()` | All work |
|
|
3927
|
+
| `zip(strict=True)` | Raises `ValueError` when iterables have different lengths; equal-length iterables work normally |
|
|
3928
|
+
| `set` with full union/intersection/difference API | Fully supported |
|
|
3929
|
+
| `isinstance()`, `hasattr()`, `getattr()`, `setattr()`, `dir()` | All work |
|
|
3930
|
+
| `bin()`, `hex()`, `oct()`, `chr()`, `ord()` | All work |
|
|
3931
|
+
| `int(x, base)`, `float(x)` with ValueError on bad input | Works |
|
|
3932
|
+
| `lambda` keyword | Full support: args, defaults, `*args`, ternary body, closures, nesting |
|
|
3933
|
+
| 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) |
|
|
3934
|
+
| 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). |
|
|
3935
|
+
| Nested comprehensions (multi-`for` clause) | `[x for row in matrix for x in row if cond]`; works for list, set, and dict comprehensions |
|
|
3936
|
+
| Positional-only parameters `def f(a, b, /):` | Full support — parser enforces placement; runtime passes positional args correctly |
|
|
3937
|
+
| Keyword-only parameters `def f(a, *, b):` | Full support — bare `*` separator enforced; `b` must be passed as keyword |
|
|
3938
|
+
| Walrus operator `:=` | Fully supported: hoisted in `if`/`while` conditions at any scope; comprehension filter assigns to enclosing scope (Python-correct). |
|
|
3939
|
+
| `__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). |
|
|
3940
|
+
| **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()`. |
|
|
3941
|
+
| `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.). |
|
|
3942
|
+
| `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. |
|
|
3943
|
+
| `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`. |
|
|
3944
|
+
| `__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`. |
|
|
3945
|
+
| `__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. |
|
|
3946
|
+
| `__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. |
|
|
3947
|
+
| `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. |
|
|
3948
|
+
| `StopIteration` exception | Defined as a builtin exception class; raised by `next()` when an iterator is exhausted and no default is given. |
|
|
3949
|
+
| `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__`). |
|
|
3950
|
+
| `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). |
|
|
3951
|
+
| `__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 |
|
|
3952
|
+
| `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. |
|
|
3953
|
+
| `__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`. |
|
|
3954
|
+
| `ImportError`, `ModuleNotFoundError` | Both defined as runtime exception classes; `ModuleNotFoundError` is a subclass of `ImportError` (same as Python 3.6+). |
|
|
3955
|
+
| `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. |
|
|
3956
|
+
| `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. |
|
|
3957
|
+
| `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. |
|
|
3958
|
+
| `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. |
|
|
3959
|
+
| 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. |
|
|
3960
|
+
| `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)`. |
|
|
3961
|
+
| `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. |
|
|
3962
|
+
| `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. |
|
|
3963
|
+
| `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. |
|
|
3964
|
+
| `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. |
|
|
3965
|
+
| `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. |
|
|
3966
|
+
|
|
3967
|
+
---
|
|
3968
|
+
|
|
3969
|
+
### Python Compatibility Flags (Default-On)
|
|
3970
|
+
|
|
3971
|
+
All flags below are enabled by default. They can be turned off per-file, per-scope, or globally via the CLI.
|
|
3972
|
+
|
|
3973
|
+
#### Opt-out: per-file or per-scope
|
|
3974
|
+
|
|
3975
|
+
Place at the top of a file to affect the whole file, or inside a function to affect only that scope:
|
|
3976
|
+
|
|
3977
|
+
```python
|
|
3978
|
+
from __python__ import no_truthiness # single flag
|
|
3979
|
+
from __python__ import no_dict_literals, no_overload_operators # multiple flags
|
|
3980
|
+
```
|
|
3981
|
+
|
|
3982
|
+
To re-enable a flag in a nested scope after an outer scope turned it off:
|
|
3983
|
+
|
|
3984
|
+
```python
|
|
3985
|
+
from __python__ import truthiness
|
|
3986
|
+
```
|
|
3987
|
+
|
|
3988
|
+
#### Opt-out: CLI (all files)
|
|
3989
|
+
|
|
3990
|
+
```sh
|
|
3991
|
+
rapydscript compile --python-flags=no_dict_literals,no_truthiness input.pyj
|
|
3992
|
+
```
|
|
3993
|
+
|
|
3994
|
+
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`).
|
|
3995
|
+
|
|
3996
|
+
#### Disable all flags (legacy mode)
|
|
3997
|
+
|
|
3998
|
+
```sh
|
|
3999
|
+
rapydscript compile --legacy-rapydscript input.pyj
|
|
4000
|
+
```
|
|
4001
|
+
|
|
4002
|
+
This restores the original RapydScript behavior: plain JS objects for `{}`, no operator overloading, JS truthiness, unbound methods, and no `String.prototype` patching.
|
|
4003
|
+
|
|
4004
|
+
#### Flag reference
|
|
4005
|
+
|
|
4006
|
+
| Flag | What it enables | Effect when disabled |
|
|
4007
|
+
|---|---|---|
|
|
4008
|
+
| `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`. |
|
|
4009
|
+
| `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. |
|
|
4010
|
+
| `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). |
|
|
4011
|
+
| `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. |
|
|
4012
|
+
| `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. |
|
|
4013
|
+
| `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. |
|
|
4014
|
+
| `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. |
|
|
4015
|
+
| `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`. |
|
|
4016
|
+
|
|
4017
|
+
---
|
|
4018
|
+
|
|
4019
|
+
### Not Supported
|
|
4020
|
+
|
|
4021
|
+
| Feature | Notes |
|
|
4022
|
+
|---------------------------------------|-----------------------------------------------------------------------------------------|
|
|
4023
|
+
| `__slots__` enforcement | Accepted, but does not restrict attribute assignment |
|
|
4024
|
+
| `locals()` | Returns an empty `dict`. JS has no runtime mechanism for introspecting local variables. |
|
|
4025
|
+
| `input(prompt)` | There is no simple cli input in browser; use `prompt()` |
|
|
4026
|
+
| `compile()` | Python compile/code objects have no JS equivalent |
|
|
4027
|
+
| `memoryview(obj)` | There is no buffer protocol in browser context |
|
|
4028
|
+
| `open(path)` | There is no filesystem access in browser context |
|
|
4029
|
+
| `from module import *` (star imports) | Intentionally unsupported (by design, to prevent namespace pollution) |
|
|
4030
|
+
| `__del__` destructor / finalizer | JS has no guaranteed finalizer |
|
|
4031
|
+
|
|
4032
|
+
---
|
|
4033
|
+
|
|
4034
|
+
### Standard Library Modules
|
|
4035
|
+
|
|
4036
|
+
Modules with a `src/lib/` implementation available are marked ✅. All others are absent.
|
|
4037
|
+
|
|
4038
|
+
| Module | Status | Notes |
|
|
4039
|
+
|---------------|-------------|-----------------------------------------------------------------------------------------------|
|
|
4040
|
+
| `math` | ✅ | Full implementation in `src/lib/math.pyj` |
|
|
4041
|
+
| `random` | ✅ | RC4-seeded PRNG in `src/lib/random.pyj` |
|
|
4042
|
+
| `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`. |
|
|
4043
|
+
| `encodings` | ✅ | Base64 and encoding helpers; partial `base64` coverage |
|
|
4044
|
+
| `collections` | ✅ | `defaultdict`, `Counter`, `OrderedDict`, `deque` |
|
|
4045
|
+
| `functools` | ✅ | `reduce`, `partial`, `wraps`, `lru_cache` |
|
|
4046
|
+
| `itertools` | ✅ | Common iteration tools |
|
|
4047
|
+
| `numpy` | ✅ | Full numpy-like library in `src/lib/numpy.pyj`; `numpy.random` and `numpy.linalg` sub-modules |
|
|
4048
|
+
| `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 |
|
|
4049
|
+
| `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` |
|
|
4050
|
+
| `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) |
|
|
4051
|
+
| `enum` | ✅ | `Enum` base class in `src/lib/enum.pyj`; `.name`, `.value`, iteration, `isinstance` checks; `IntEnum`/`Flag` not available |
|
|
4052
|
+
| `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 |
|
|
4053
|
+
| `contextlib` | ❌ | `contextmanager`, `suppress`, `ExitStack`, `asynccontextmanager` not available |
|
|
4054
|
+
| `string` | ❌ | Character constants, `Template`, `Formatter` not available |
|
|
4055
|
+
| `json` | ❌ | No Python wrapper; JS `JSON.parse` / `JSON.stringify` work directly via verbatim JS |
|
|
4056
|
+
| `datetime` | ❌ | `date`, `time`, `datetime`, `timedelta` not available |
|
|
4057
|
+
| `inspect` | ❌ | `signature`, `getmembers`, `isfunction` etc. not available |
|
|
4058
|
+
| `asyncio` | ❌ | Event loop, `gather`, `sleep`, `Queue`, `Task` wrappers not available; use `async`/`await` |
|
|
4059
|
+
| `io` | ❌ | `StringIO`, `BytesIO` not available |
|
|
4060
|
+
| `struct` | ❌ | Binary packing/unpacking not available |
|
|
4061
|
+
| `hashlib` | ❌ | MD5, SHA-256 etc. not available; use Web Crypto API via verbatim JS |
|
|
4062
|
+
| `hmac` | ❌ | Keyed hashing not available |
|
|
4063
|
+
| `base64` | ❌ (partial) | Partial coverage via `encodings` module; no full `base64` module |
|
|
4064
|
+
| `urllib` | ❌ | URL parsing/encoding (`urllib.parse`) not available; use JS `URL` API |
|
|
4065
|
+
| `html` | ❌ | `escape`, `unescape` not available; use JS DOM APIs |
|
|
4066
|
+
| `csv` | ❌ | CSV parsing not available |
|
|
4067
|
+
| `textwrap` | ❌ | `wrap`, `fill`, `dedent`, `indent` not available |
|
|
4068
|
+
| `pprint` | ❌ | Pretty-printing not available |
|
|
4069
|
+
| `logging` | ❌ | Logging framework not available; use `console.*` directly |
|
|
4070
|
+
| `unittest` | ❌ | Not available; RapydScript uses a custom test runner (`node bin/rapydscript test`) |
|
|
4071
|
+
|
|
4072
|
+
---
|
|
4073
|
+
|
|
4074
|
+
### Semantic Differences
|
|
4075
|
+
|
|
4076
|
+
Features that exist in RapydScript but behave differently from standard Python:
|
|
4077
|
+
|
|
4078
|
+
| Feature | Python Behavior | RapydScript Behavior |
|
|
4079
|
+
|---|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
4080
|
+
| `is` / `is not` | Object identity | Strict equality `===` / `!==`, which is not object/pointer comparison for JS primitives |
|
|
4081
|
+
| `//` floor division on floats | `math.floor(a/b)` always | uses `Math.floor` - same result for well-behaved floats |
|
|
4082
|
+
| `%` on negative numbers | Python modulo (always non-negative) | JS remainder (can be negative) |
|
|
4083
|
+
| `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) |
|
|
4084
|
+
| `Exception.message` | Not standard; use `.args[0]` | `.message` is the standard attribute (JS `Error` style) |
|
|
4085
|
+
| 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. |
|
|
4086
|
+
| 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) |
|
|
4087
|
+
| 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 |
|
|
4088
|
+
| 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 |
|
|
4089
|
+
| Default `{}` dict — attribute access | `d.foo` raises `AttributeError` | `d.foo` and `d['foo']` access the same slot; keys are also properties |
|
|
4090
|
+
| 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. |
|
|
4091
|
+
| 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 |
|
|
4092
|
+
| 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) |
|
|
4093
|
+
| `dict` key ordering | Insertion order guaranteed (3.7+) | Depends on JS engine (V8 preserves insertion order in practice) |
|
|
4094
|
+
| 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 |
|
|
4095
|
+
| `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 |
|
|
4096
|
+
|
|
4097
|
+
---
|
|
4098
|
+
|
|
4099
|
+
### Test File
|
|
4100
|
+
|
|
4101
|
+
`test/python_features.pyj` contains runnable assertions for all features surveyed.
|
|
4102
|
+
Features that are not supported have their test code commented out with a `# SKIP:` label
|
|
4103
|
+
and an explanation. Run with:
|
|
4104
|
+
|
|
4105
|
+
```sh
|
|
4106
|
+
node bin/rapydscript test python_features
|
|
4107
|
+
```
|
|
4108
|
+
|
|
4109
|
+
|
|
2900
4110
|
Reasons for the fork
|
|
2901
4111
|
----------------------
|
|
2902
4112
|
|
|
@@ -2910,4 +4120,4 @@ ever resumes, they are welcome to use the code from this fork. All the
|
|
|
2910
4120
|
new code is under the same license, to make that possible.
|
|
2911
4121
|
|
|
2912
4122
|
See the [Changelog](https://github.com/ficocelliguy/rapydscript-ns/blob/master/CHANGELOG.md)
|
|
2913
|
-
for a list of changes to rapydscript-ns, including this fork at version 8.0
|
|
4123
|
+
for a list of changes to rapydscript-ns, including this fork at version 8.0
|