rapydscript-ns 0.8.2 → 0.8.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agignore +1 -1
- package/.github/workflows/ci.yml +38 -38
- package/=template.pyj +5 -5
- package/CHANGELOG.md +39 -0
- package/HACKING.md +103 -103
- package/LICENSE +24 -24
- package/PYTHON_DIFFERENCES_REPORT.md +291 -0
- package/PYTHON_FEATURE_COVERAGE.md +106 -15
- package/README.md +831 -52
- package/TODO.md +4 -286
- package/add-toc-to-readme +2 -2
- package/bin/export +75 -75
- package/bin/rapydscript +70 -70
- package/bin/web-repl-export +102 -102
- package/build +2 -2
- package/language-service/index.js +4623 -0
- package/language-service/language-service.d.ts +40 -0
- package/package.json +9 -7
- package/publish.py +37 -37
- package/release/baselib-plain-pretty.js +2006 -229
- package/release/baselib-plain-ugly.js +70 -3
- package/release/compiler.js +11554 -3870
- package/release/signatures.json +31 -29
- package/session.vim +4 -4
- package/setup.cfg +2 -2
- package/src/ast.pyj +93 -1
- package/src/baselib-builtins.pyj +99 -2
- package/src/baselib-containers.pyj +107 -4
- package/src/baselib-errors.pyj +44 -0
- package/src/baselib-internal.pyj +124 -5
- package/src/baselib-itertools.pyj +97 -97
- package/src/baselib-str.pyj +32 -1
- package/src/compiler.pyj +36 -36
- package/src/errors.pyj +30 -30
- package/src/lib/aes.pyj +646 -646
- package/src/lib/collections.pyj +1 -1
- package/src/lib/copy.pyj +120 -0
- package/src/lib/elementmaker.pyj +83 -83
- package/src/lib/encodings.pyj +126 -126
- package/src/lib/gettext.pyj +569 -569
- package/src/lib/itertools.pyj +580 -580
- package/src/lib/math.pyj +193 -193
- package/src/lib/numpy.pyj +10 -10
- package/src/lib/operator.pyj +11 -11
- package/src/lib/pythonize.pyj +20 -20
- package/src/lib/random.pyj +118 -118
- package/src/lib/re.pyj +470 -470
- package/src/lib/react.pyj +74 -0
- package/src/lib/traceback.pyj +63 -63
- package/src/lib/uuid.pyj +77 -77
- package/src/monaco-language-service/analyzer.js +131 -9
- package/src/monaco-language-service/builtins.js +17 -2
- package/src/monaco-language-service/completions.js +170 -1
- package/src/monaco-language-service/diagnostics.js +25 -3
- package/src/monaco-language-service/dts.js +550 -550
- package/src/monaco-language-service/index.js +17 -0
- package/src/monaco-language-service/scope.js +3 -0
- package/src/output/classes.pyj +128 -11
- package/src/output/codegen.pyj +17 -3
- package/src/output/comments.pyj +45 -45
- package/src/output/exceptions.pyj +201 -105
- package/src/output/functions.pyj +13 -16
- package/src/output/jsx.pyj +164 -0
- package/src/output/literals.pyj +28 -2
- package/src/output/loops.pyj +0 -9
- package/src/output/modules.pyj +2 -5
- package/src/output/operators.pyj +22 -2
- package/src/output/statements.pyj +2 -2
- package/src/output/stream.pyj +1 -13
- package/src/output/treeshake.pyj +182 -182
- package/src/output/utils.pyj +72 -72
- package/src/parse.pyj +434 -114
- package/src/string_interpolation.pyj +72 -72
- package/src/tokenizer.pyj +29 -0
- package/src/unicode_aliases.pyj +576 -576
- package/src/utils.pyj +192 -192
- package/test/_import_one.pyj +37 -37
- package/test/_import_two/__init__.pyj +11 -11
- package/test/_import_two/level2/deep.pyj +4 -4
- package/test/_import_two/other.pyj +6 -6
- package/test/_import_two/sub.pyj +13 -13
- package/test/aes_vectors.pyj +421 -421
- package/test/annotations.pyj +80 -80
- package/test/baselib.pyj +4 -4
- package/test/classes.pyj +56 -17
- package/test/collections.pyj +5 -5
- package/test/decorators.pyj +77 -77
- package/test/docstrings.pyj +39 -39
- package/test/elementmaker_test.pyj +45 -45
- package/test/functions.pyj +151 -151
- package/test/generators.pyj +41 -41
- package/test/generic.pyj +370 -370
- package/test/imports.pyj +72 -72
- package/test/internationalization.pyj +73 -73
- package/test/lint.pyj +164 -164
- package/test/loops.pyj +85 -85
- package/test/numpy.pyj +734 -734
- package/test/omit_function_metadata.pyj +20 -20
- package/test/python_compat.pyj +326 -0
- package/test/python_features.pyj +129 -29
- package/test/regexp.pyj +55 -55
- package/test/repl.pyj +121 -121
- package/test/scoped_flags.pyj +76 -76
- package/test/slice.pyj +105 -0
- package/test/str.pyj +25 -0
- package/test/unit/fixtures/fibonacci_expected.js +1 -1
- package/test/unit/index.js +2296 -71
- package/test/unit/language-service-builtins.js +70 -0
- package/test/unit/language-service-bundle.js +5 -5
- package/test/unit/language-service-completions.js +180 -0
- package/test/unit/language-service-dts.js +543 -543
- package/test/unit/language-service-hover.js +455 -455
- package/test/unit/language-service-index.js +350 -0
- package/test/unit/language-service-scope.js +255 -0
- package/test/unit/language-service.js +625 -4
- package/test/unit/run-language-service.js +1 -0
- package/test/unit/web-repl.js +437 -0
- package/tools/build-language-service.js +2 -2
- package/tools/cli.js +547 -547
- package/tools/compile.js +219 -219
- package/tools/compiler.js +0 -24
- package/tools/completer.js +131 -131
- package/tools/embedded_compiler.js +251 -251
- package/tools/export.js +3 -37
- package/tools/gettext.js +185 -185
- package/tools/ini.js +65 -65
- package/tools/msgfmt.js +187 -187
- package/tools/repl.js +223 -223
- package/tools/test.js +118 -118
- package/tools/utils.js +128 -128
- package/tools/web_repl.js +95 -95
- package/try +41 -41
- package/web-repl/env.js +196 -74
- package/web-repl/index.html +163 -163
- package/web-repl/main.js +252 -254
- package/web-repl/prism.css +139 -139
- package/web-repl/prism.js +113 -113
- package/web-repl/rapydscript.js +227 -139
- package/web-repl/sha1.js +25 -25
- package/hack_demo.pyj +0 -112
- package/web-repl/language-service.js +0 -4187
package/README.md
CHANGED
|
@@ -41,6 +41,7 @@ backwards compatible) features. For more on the forking, [see the bottom of this
|
|
|
41
41
|
- [Extended Subscript Syntax](#extended-subscript-syntax)
|
|
42
42
|
- [Variable Type Annotations](#variable-type-annotations)
|
|
43
43
|
- [Regular Expressions](#regular-expressions)
|
|
44
|
+
- [JSX Support](#jsx-support)
|
|
44
45
|
- [Creating DOM trees easily](#creating-dom-trees-easily)
|
|
45
46
|
- [Classes](#classes)
|
|
46
47
|
- [External Classes](#external-classes)
|
|
@@ -108,9 +109,6 @@ Here are a few features of RapydScript:
|
|
|
108
109
|
- similar to above, ability to use both, Python's and JavaScript's tutorials (as well as widgets)
|
|
109
110
|
- it's self-hosting, that means the compiler is itself written in RapydScript and compiles into JavaScript
|
|
110
111
|
|
|
111
|
-
Let's not waste any more time with the introductions, however. The best way to
|
|
112
|
-
learn a new language/framework is to dive in.
|
|
113
|
-
|
|
114
112
|
|
|
115
113
|
Installation
|
|
116
114
|
------------
|
|
@@ -357,17 +355,10 @@ math_ops = {
|
|
|
357
355
|
}
|
|
358
356
|
```
|
|
359
357
|
|
|
360
|
-
I'm sure you will agree that the above code is cleaner than declaring 5
|
|
361
|
-
temporary variables first and assigning them to the object literal keys after.
|
|
362
358
|
Note that the example puts the function header (def()) and content on the same
|
|
363
|
-
line
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
good rule of thumb (to keep your code clean) is if your function needs
|
|
367
|
-
semi-colons ask yourself whether you should be inlining, and if it needs more
|
|
368
|
-
than 2 semi-colons, the answer is probably no (note that you can also use
|
|
369
|
-
semi-colons as newline separators within functions that aren't inlined, as in
|
|
370
|
-
the example in the previous section).
|
|
359
|
+
line (function inlining). This is a feature of RapydScript that can be used
|
|
360
|
+
to make the code cleaner in cases like the example above. You can also use it
|
|
361
|
+
in longer functions by chaining statements together using `;`.
|
|
371
362
|
|
|
372
363
|
|
|
373
364
|
Lambda Expressions
|
|
@@ -543,7 +534,7 @@ $(element)\
|
|
|
543
534
|
.show()
|
|
544
535
|
```
|
|
545
536
|
|
|
546
|
-
|
|
537
|
+
This feature handles `do/while` loops as well:
|
|
547
538
|
|
|
548
539
|
```js
|
|
549
540
|
a = 0
|
|
@@ -646,7 +637,9 @@ into the names optional parameters you specified in the function definition.
|
|
|
646
637
|
|
|
647
638
|
Inferred Tuple Packing/Unpacking
|
|
648
639
|
--------------------------------
|
|
649
|
-
Like Python, RapydScript allows inferred tuple packing/unpacking and assignment.
|
|
640
|
+
Like Python, RapydScript allows inferred tuple packing/unpacking and assignment. For
|
|
641
|
+
example, if you wanted to swap two variables, the following is simpler than explicitly
|
|
642
|
+
declaring a temporary variable:
|
|
650
643
|
|
|
651
644
|
```py
|
|
652
645
|
a, b = b, a
|
|
@@ -766,14 +759,19 @@ Containers (lists/sets/dicts)
|
|
|
766
759
|
### Lists
|
|
767
760
|
|
|
768
761
|
Lists in RapydScript are almost identical to lists in Python, but are also
|
|
769
|
-
native JavaScript arrays. The
|
|
770
|
-
``
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
762
|
+
native JavaScript arrays. The ``sort()`` and ``pop()`` methods behave exactly
|
|
763
|
+
as in Python: ``sort()`` performs a numeric sort (in-place, with optional ``key``
|
|
764
|
+
and ``reverse`` arguments) and ``pop()`` performs a bounds-checked pop (raises
|
|
765
|
+
``IndexError`` for out-of-bounds indices). If you need the native JavaScript
|
|
766
|
+
behavior for interop with external JS libraries, use ``jssort()`` (lexicographic
|
|
767
|
+
sort) and ``jspop()`` (no bounds check, always pops the last element). The old
|
|
768
|
+
``pysort()`` and ``pypop()`` names are kept as backward-compatible aliases.
|
|
769
|
+
|
|
770
|
+
Note that even list literals in RapydScript create Python-like list objects,
|
|
771
|
+
and you can also use the builtin ``list()`` function to create lists from other
|
|
772
|
+
iterable objects, just as you would in Python. You can create a RapydScript
|
|
773
|
+
list from a plain native JavaScript array by using the ``list_wrap()`` function,
|
|
774
|
+
like this:
|
|
777
775
|
|
|
778
776
|
```py
|
|
779
777
|
a = v'[1, 2]'
|
|
@@ -781,13 +779,36 @@ pya = list_wrap(a)
|
|
|
781
779
|
# Now pya is a python like list object that satisfies pya === a
|
|
782
780
|
```
|
|
783
781
|
|
|
782
|
+
### List Concatenation
|
|
783
|
+
|
|
784
|
+
The `+` operator concatenates two lists and returns a new list, exactly as in Python:
|
|
785
|
+
|
|
786
|
+
```py
|
|
787
|
+
a = [1, 2]
|
|
788
|
+
b = [3, 4]
|
|
789
|
+
c = a + b # [1, 2, 3, 4] — a and b are unchanged
|
|
790
|
+
```
|
|
791
|
+
|
|
792
|
+
The `+=` operator extends a list in-place (the original list object is mutated):
|
|
793
|
+
|
|
794
|
+
```py
|
|
795
|
+
a = [1, 2]
|
|
796
|
+
ref = a # ref and a point to the same list
|
|
797
|
+
a += [3, 4] # mutates a in-place
|
|
798
|
+
print(ref) # [1, 2, 3, 4] — ref sees the update
|
|
799
|
+
```
|
|
800
|
+
|
|
801
|
+
No special flag is required. The `+` operator compiles to a lightweight helper
|
|
802
|
+
(`ρσ_list_add`) that uses `Array.concat` for lists and falls back to native JS
|
|
803
|
+
`+` for numbers and strings.
|
|
804
|
+
|
|
784
805
|
### Sets
|
|
785
806
|
|
|
786
807
|
Sets in RapydScript are identical to those in python. You can create them using
|
|
787
808
|
set literals or comprehensions and all set operations are supported. You can
|
|
788
|
-
store any object in a set
|
|
789
|
-
|
|
790
|
-
|
|
809
|
+
store any object in a set. For primitive types (strings, numbers) the value is
|
|
810
|
+
used for equality; for class instances, object identity (``is``) is used by
|
|
811
|
+
default unless the class defines a ``__hash__`` method.
|
|
791
812
|
|
|
792
813
|
Note that sets are not a subclass of the ES 6 JavaScript Set object, however,
|
|
793
814
|
they do use this object as a backend, when available. You can create a set from
|
|
@@ -815,8 +836,8 @@ a in s # True
|
|
|
815
836
|
[1, 2] in s # False
|
|
816
837
|
```
|
|
817
838
|
|
|
818
|
-
This is because
|
|
819
|
-
|
|
839
|
+
This is because list identity (not value) determines set membership for mutable
|
|
840
|
+
objects. Define ``__hash__`` on your own classes to control set/dict membership.
|
|
820
841
|
|
|
821
842
|
### Dicts
|
|
822
843
|
|
|
@@ -865,6 +886,63 @@ a = {1:1, 2:2}
|
|
|
865
886
|
isinstance(a, dict) == False # a is a normal JavaScript object
|
|
866
887
|
```
|
|
867
888
|
|
|
889
|
+
### List spread literals
|
|
890
|
+
|
|
891
|
+
RapydScript supports Python's `*expr` spread syntax inside list literals.
|
|
892
|
+
One or more `*expr` items can appear anywhere, interleaved with ordinary
|
|
893
|
+
elements:
|
|
894
|
+
|
|
895
|
+
```py
|
|
896
|
+
a = [1, 2, 3]
|
|
897
|
+
b = [4, 5]
|
|
898
|
+
|
|
899
|
+
# Spread at the end
|
|
900
|
+
result = [0, *a] # [0, 1, 2, 3]
|
|
901
|
+
|
|
902
|
+
# Spread in the middle
|
|
903
|
+
result = [0, *a, *b, 6] # [0, 1, 2, 3, 4, 5, 6]
|
|
904
|
+
|
|
905
|
+
# Copy a list
|
|
906
|
+
copy = [*a] # [1, 2, 3]
|
|
907
|
+
|
|
908
|
+
# Unpack a string
|
|
909
|
+
chars = [*'hello'] # ['h', 'e', 'l', 'l', 'o']
|
|
910
|
+
```
|
|
911
|
+
|
|
912
|
+
Spread works on any iterable (lists, strings, generators, `range()`).
|
|
913
|
+
The result is always a new Python list. Translates to JavaScript's
|
|
914
|
+
`[...expr]` spread syntax.
|
|
915
|
+
|
|
916
|
+
### Set spread literals
|
|
917
|
+
|
|
918
|
+
The same `*expr` syntax works inside set literals `{...}`:
|
|
919
|
+
|
|
920
|
+
```py
|
|
921
|
+
a = [1, 2, 3]
|
|
922
|
+
b = [3, 4, 5]
|
|
923
|
+
|
|
924
|
+
s = {*a, *b} # set([1, 2, 3, 4, 5]) — duplicates removed
|
|
925
|
+
s2 = {*a, 10} # set([1, 2, 3, 10])
|
|
926
|
+
```
|
|
927
|
+
|
|
928
|
+
Translates to `ρσ_set([...a, ...b])`.
|
|
929
|
+
|
|
930
|
+
### `**expr` in function calls
|
|
931
|
+
|
|
932
|
+
`**expr` in a function call now accepts any expression, not just a plain
|
|
933
|
+
variable name:
|
|
934
|
+
|
|
935
|
+
```py
|
|
936
|
+
def f(x=0, y=0):
|
|
937
|
+
return x + y
|
|
938
|
+
|
|
939
|
+
opts = {'x': 10, 'y': 20}
|
|
940
|
+
f(**opts) # 30 (variable — always worked)
|
|
941
|
+
f(**{'x': 1, 'y': 2}) # 3 (dict literal)
|
|
942
|
+
f(**cfg.defaults) # uses attribute access result
|
|
943
|
+
f(**get_opts()) # uses function call result
|
|
944
|
+
```
|
|
945
|
+
|
|
868
946
|
### Dict merge literals
|
|
869
947
|
|
|
870
948
|
RapydScript supports Python's `{**d1, **d2}` dict merge (spread) syntax.
|
|
@@ -1153,7 +1231,8 @@ semantics:
|
|
|
1153
1231
|
| other `float` | derived from the bit pattern |
|
|
1154
1232
|
| `str` | djb2 algorithm — stable within a process |
|
|
1155
1233
|
| object with `__hash__` | dispatches to `__hash__()` |
|
|
1156
|
-
| class instance | stable identity hash (assigned on first call) |
|
|
1234
|
+
| class instance (no `__hash__`) | stable identity hash (assigned on first call) |
|
|
1235
|
+
| class with `__eq__` but no `__hash__` | `TypeError` (unhashable — Python semantics) |
|
|
1157
1236
|
| `list` | `TypeError: unhashable type: 'list'` |
|
|
1158
1237
|
| `set` | `TypeError: unhashable type: 'set'` |
|
|
1159
1238
|
| `dict` | `TypeError: unhashable type: 'dict'` |
|
|
@@ -1173,8 +1252,67 @@ class Point:
|
|
|
1173
1252
|
return self.x * 31 + self.y
|
|
1174
1253
|
|
|
1175
1254
|
hash(Point(1, 2)) # 33
|
|
1255
|
+
|
|
1256
|
+
# Python semantics: __eq__ without __hash__ → unhashable
|
|
1257
|
+
class Bar:
|
|
1258
|
+
def __eq__(self, other):
|
|
1259
|
+
return True
|
|
1260
|
+
hash(Bar()) # TypeError: unhashable type: 'Bar'
|
|
1176
1261
|
```
|
|
1177
1262
|
|
|
1263
|
+
### Attribute-Access Dunders
|
|
1264
|
+
|
|
1265
|
+
RapydScript supports the four Python attribute-interception hooks:
|
|
1266
|
+
`__getattr__`, `__setattr__`, `__delattr__`, and `__getattribute__`.
|
|
1267
|
+
When a class defines any of them, instances are automatically wrapped in a
|
|
1268
|
+
JavaScript `Proxy` that routes attribute access through the hooks — including
|
|
1269
|
+
accesses that occur inside `__init__`.
|
|
1270
|
+
|
|
1271
|
+
| Hook | When called |
|
|
1272
|
+
|---|---|
|
|
1273
|
+
| `__getattr__(self, name)` | Fallback — only called when normal lookup finds nothing |
|
|
1274
|
+
| `__setattr__(self, name, value)` | Every attribute assignment (including `self.x = …` in `__init__`) |
|
|
1275
|
+
| `__delattr__(self, name)` | Every `del obj.attr` |
|
|
1276
|
+
| `__getattribute__(self, name)` | Every attribute read (overrides normal lookup) |
|
|
1277
|
+
|
|
1278
|
+
To bypass the hooks from within the hook itself (avoiding infinite recursion),
|
|
1279
|
+
use the `object.*` bypass functions:
|
|
1280
|
+
|
|
1281
|
+
| Python idiom | Compiled form | Effect |
|
|
1282
|
+
|---|---|---|
|
|
1283
|
+
| `object.__setattr__(self, name, val)` | `ρσ_object_setattr(self, name, val)` | Set attribute directly, bypassing `__setattr__` |
|
|
1284
|
+
| `object.__getattribute__(self, name)` | `ρσ_object_getattr(self, name)` | Read attribute directly, bypassing `__getattribute__` |
|
|
1285
|
+
| `object.__delattr__(self, name)` | `ρσ_object_delattr(self, name)` | Delete attribute directly, bypassing `__delattr__` |
|
|
1286
|
+
|
|
1287
|
+
Subclasses automatically inherit proxy wrapping from their parent class — if
|
|
1288
|
+
`Base` defines `__getattr__`, all `Child(Base)` instances are also Proxy-wrapped.
|
|
1289
|
+
|
|
1290
|
+
```py
|
|
1291
|
+
class Validated:
|
|
1292
|
+
"""Reject negative values at assignment time."""
|
|
1293
|
+
def __setattr__(self, name, value):
|
|
1294
|
+
if jstype(value) is 'number' and value < 0:
|
|
1295
|
+
raise ValueError(name + ' must be non-negative')
|
|
1296
|
+
object.__setattr__(self, name, value)
|
|
1297
|
+
|
|
1298
|
+
v = Validated()
|
|
1299
|
+
v.x = 5 # ok
|
|
1300
|
+
v.x = -1 # ValueError: x must be non-negative
|
|
1301
|
+
|
|
1302
|
+
class AttrProxy:
|
|
1303
|
+
"""Log every attribute read."""
|
|
1304
|
+
def __init__(self):
|
|
1305
|
+
object.__setattr__(self, '_log', [])
|
|
1306
|
+
|
|
1307
|
+
def __getattribute__(self, name):
|
|
1308
|
+
self._log.append(name) # self._log goes through __getattribute__ too!
|
|
1309
|
+
return object.__getattribute__(self, name)
|
|
1310
|
+
```
|
|
1311
|
+
|
|
1312
|
+
> **Proxy support required** — The hooks rely on `Proxy`, which is available
|
|
1313
|
+
> in all modern browsers and Node.js ≥ 6. In environments that lack `Proxy`
|
|
1314
|
+
> the class still works, but the hooks are silently bypassed.
|
|
1315
|
+
|
|
1178
1316
|
Loops
|
|
1179
1317
|
-----
|
|
1180
1318
|
RapydScript's loops work like Python, not JavaScript. You can't, for example
|
|
@@ -1346,6 +1484,20 @@ str.format('{0:02d} {n}', 1, n=2) == '01 2'
|
|
|
1346
1484
|
...
|
|
1347
1485
|
```
|
|
1348
1486
|
|
|
1487
|
+
The `format(value[, spec])` builtin is also supported. It applies the Python
|
|
1488
|
+
format-spec mini-language to a single value — the same mini-language that
|
|
1489
|
+
follows `:` in f-strings and `str.format()` fields:
|
|
1490
|
+
|
|
1491
|
+
```py
|
|
1492
|
+
format(42, '08b') # '00101010' — zero-padded binary
|
|
1493
|
+
format(3.14159, '.2f') # '3.14' — fixed-point
|
|
1494
|
+
format('hi', '>10') # ' hi' — right-aligned in 10-char field
|
|
1495
|
+
format(42) # '42' — no spec: same as str(42)
|
|
1496
|
+
```
|
|
1497
|
+
|
|
1498
|
+
Objects with a `__format__` method are dispatched to it, matching Python's
|
|
1499
|
+
protocol exactly.
|
|
1500
|
+
|
|
1349
1501
|
String predicate methods are also available:
|
|
1350
1502
|
|
|
1351
1503
|
```py
|
|
@@ -1371,6 +1523,16 @@ Case-folding for locale-insensitive lowercase comparison:
|
|
|
1371
1523
|
str.casefold('ÄÖÜ') == str.casefold('äöü') # True (maps to lowercase)
|
|
1372
1524
|
```
|
|
1373
1525
|
|
|
1526
|
+
Tab expansion:
|
|
1527
|
+
|
|
1528
|
+
```py
|
|
1529
|
+
str.expandtabs('a\tb', 4) # 'a b' — expand to next 4-space tab stop
|
|
1530
|
+
str.expandtabs('\t\t', 8) # ' ' — two full tab stops
|
|
1531
|
+
str.expandtabs('ab\tc', 4) # 'ab c' — only 2 spaces needed to reach next stop
|
|
1532
|
+
```
|
|
1533
|
+
|
|
1534
|
+
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.
|
|
1535
|
+
|
|
1374
1536
|
However, if you want to make the python string methods available on string
|
|
1375
1537
|
objects, there is a convenience method in the standard library to do so. Use
|
|
1376
1538
|
the following code:
|
|
@@ -1578,6 +1740,7 @@ can annotate a variable with a type hint, with or without an initial value:
|
|
|
1578
1740
|
x: int = 42
|
|
1579
1741
|
name: str = "Alice"
|
|
1580
1742
|
items: list = [1, 2, 3]
|
|
1743
|
+
coords: tuple = (10, 20)
|
|
1581
1744
|
|
|
1582
1745
|
# Annotation only: declares the type without assigning a value
|
|
1583
1746
|
count: int
|
|
@@ -1650,6 +1813,353 @@ re.match(///
|
|
|
1650
1813
|
///, 'ab')
|
|
1651
1814
|
```
|
|
1652
1815
|
|
|
1816
|
+
JSX Support
|
|
1817
|
+
-----------
|
|
1818
|
+
|
|
1819
|
+
RapydScript supports JSX syntax for building React UI components. JSX elements compile directly to `React.createElement()` calls, so the output is plain JavaScript — no Babel or JSX transform step is needed.
|
|
1820
|
+
|
|
1821
|
+
Enable JSX support with:
|
|
1822
|
+
|
|
1823
|
+
```py
|
|
1824
|
+
from __python__ import jsx
|
|
1825
|
+
```
|
|
1826
|
+
|
|
1827
|
+
### Requirements
|
|
1828
|
+
|
|
1829
|
+
`React` must be in scope at runtime. How you provide it depends on your environment:
|
|
1830
|
+
|
|
1831
|
+
- **Bundler (Vite, webpack, etc.):** `import React from 'react'` at the top of your file (or configure your bundler's global React shim).
|
|
1832
|
+
- **CDN / browser script tag:** load React before your compiled script.
|
|
1833
|
+
- **Bitburner:** React is already available as a global — no import needed.
|
|
1834
|
+
- **RapydScript web REPL:** a minimal React stub is injected automatically so `React.createElement` calls succeed.
|
|
1835
|
+
|
|
1836
|
+
### Output format
|
|
1837
|
+
|
|
1838
|
+
RapydScript compiles JSX to `React.createElement()` calls. For example:
|
|
1839
|
+
|
|
1840
|
+
```py
|
|
1841
|
+
from __python__ import jsx
|
|
1842
|
+
|
|
1843
|
+
def Greeting(props):
|
|
1844
|
+
return <h1>Hello, {props.name}!</h1>
|
|
1845
|
+
```
|
|
1846
|
+
|
|
1847
|
+
Compiles to:
|
|
1848
|
+
|
|
1849
|
+
```js
|
|
1850
|
+
function Greeting(props) {
|
|
1851
|
+
return React.createElement("h1", null, "Hello, ", props.name);
|
|
1852
|
+
}
|
|
1853
|
+
```
|
|
1854
|
+
|
|
1855
|
+
Lowercase tags (`div`, `span`, `h1`, …) become string arguments. Capitalised names and dot-notation (`MyComponent`, `Router.Route`) are passed as references — not strings.
|
|
1856
|
+
|
|
1857
|
+
### Attributes
|
|
1858
|
+
|
|
1859
|
+
String, expression, boolean, and hyphenated attribute names all work:
|
|
1860
|
+
|
|
1861
|
+
```py
|
|
1862
|
+
from __python__ import jsx
|
|
1863
|
+
|
|
1864
|
+
def Form(props):
|
|
1865
|
+
return (
|
|
1866
|
+
<form>
|
|
1867
|
+
<input
|
|
1868
|
+
type="text"
|
|
1869
|
+
aria-label="Name"
|
|
1870
|
+
disabled={props.readonly}
|
|
1871
|
+
onChange={props.onChange}
|
|
1872
|
+
required
|
|
1873
|
+
/>
|
|
1874
|
+
</form>
|
|
1875
|
+
)
|
|
1876
|
+
```
|
|
1877
|
+
|
|
1878
|
+
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`.
|
|
1879
|
+
|
|
1880
|
+
### Nested elements and expressions
|
|
1881
|
+
|
|
1882
|
+
```py
|
|
1883
|
+
from __python__ import jsx
|
|
1884
|
+
|
|
1885
|
+
def UserList(users):
|
|
1886
|
+
return (
|
|
1887
|
+
<ul className="user-list">
|
|
1888
|
+
{[<li key={u.id}>{u.name}</li> for u in users]}
|
|
1889
|
+
</ul>
|
|
1890
|
+
)
|
|
1891
|
+
```
|
|
1892
|
+
|
|
1893
|
+
### Fragments
|
|
1894
|
+
|
|
1895
|
+
Use `<>...</>` to return multiple elements without a wrapper node. Fragments compile to `React.createElement(React.Fragment, null, ...)`:
|
|
1896
|
+
|
|
1897
|
+
```py
|
|
1898
|
+
from __python__ import jsx
|
|
1899
|
+
|
|
1900
|
+
def TwoItems():
|
|
1901
|
+
return (
|
|
1902
|
+
<>
|
|
1903
|
+
<span>First</span>
|
|
1904
|
+
<span>Second</span>
|
|
1905
|
+
</>
|
|
1906
|
+
)
|
|
1907
|
+
```
|
|
1908
|
+
|
|
1909
|
+
### Self-closing elements
|
|
1910
|
+
|
|
1911
|
+
```py
|
|
1912
|
+
from __python__ import jsx
|
|
1913
|
+
|
|
1914
|
+
def Avatar(props):
|
|
1915
|
+
return <img src={props.url} alt={props.name} />
|
|
1916
|
+
```
|
|
1917
|
+
|
|
1918
|
+
### Spread attributes
|
|
1919
|
+
|
|
1920
|
+
```py
|
|
1921
|
+
from __python__ import jsx
|
|
1922
|
+
|
|
1923
|
+
def Button(props):
|
|
1924
|
+
return <button {...props}>Click me</button>
|
|
1925
|
+
```
|
|
1926
|
+
|
|
1927
|
+
Compiles to `React.createElement("button", {...props}, "Click me")`.
|
|
1928
|
+
|
|
1929
|
+
### Component tags
|
|
1930
|
+
|
|
1931
|
+
Capitalised names and dot-notation are treated as component references (not quoted strings):
|
|
1932
|
+
|
|
1933
|
+
```py
|
|
1934
|
+
from __python__ import jsx
|
|
1935
|
+
|
|
1936
|
+
def App():
|
|
1937
|
+
return (
|
|
1938
|
+
<Router.Provider>
|
|
1939
|
+
<MyComponent name="hello" />
|
|
1940
|
+
</Router.Provider>
|
|
1941
|
+
)
|
|
1942
|
+
```
|
|
1943
|
+
|
|
1944
|
+
Compiles to:
|
|
1945
|
+
|
|
1946
|
+
```js
|
|
1947
|
+
React.createElement(Router.Provider, null,
|
|
1948
|
+
React.createElement(MyComponent, {name: "hello"})
|
|
1949
|
+
)
|
|
1950
|
+
```
|
|
1951
|
+
|
|
1952
|
+
### Compiling JSX files
|
|
1953
|
+
|
|
1954
|
+
Since the output is plain JavaScript, compile to a `.js` file as normal:
|
|
1955
|
+
|
|
1956
|
+
```sh
|
|
1957
|
+
rapydscript mycomponent.pyj --output mycomponent.js
|
|
1958
|
+
```
|
|
1959
|
+
|
|
1960
|
+
React Standard Library
|
|
1961
|
+
-----------------------
|
|
1962
|
+
|
|
1963
|
+
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.
|
|
1964
|
+
|
|
1965
|
+
### Importing hooks
|
|
1966
|
+
|
|
1967
|
+
```py
|
|
1968
|
+
from __python__ import jsx
|
|
1969
|
+
from react import useState, useEffect, useCallback, useMemo, useRef
|
|
1970
|
+
|
|
1971
|
+
def Counter():
|
|
1972
|
+
count, setCount = useState(0)
|
|
1973
|
+
|
|
1974
|
+
def increment():
|
|
1975
|
+
setCount(count + 1)
|
|
1976
|
+
|
|
1977
|
+
return <button onClick={increment}>{count}</button>
|
|
1978
|
+
```
|
|
1979
|
+
|
|
1980
|
+
Compiles to:
|
|
1981
|
+
|
|
1982
|
+
```js
|
|
1983
|
+
var useState = React.useState;
|
|
1984
|
+
// ...
|
|
1985
|
+
function Counter() {
|
|
1986
|
+
var [count, setCount] = React.useState(0);
|
|
1987
|
+
function increment() {
|
|
1988
|
+
setCount(count + 1);
|
|
1989
|
+
}
|
|
1990
|
+
return React.createElement("button", {onClick: increment}, count);
|
|
1991
|
+
}
|
|
1992
|
+
```
|
|
1993
|
+
|
|
1994
|
+
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)`.
|
|
1995
|
+
|
|
1996
|
+
### Available exports
|
|
1997
|
+
|
|
1998
|
+
**Hooks (React 16.8+)**
|
|
1999
|
+
|
|
2000
|
+
| Import name | React API |
|
|
2001
|
+
|---|---|
|
|
2002
|
+
| `useState` | `React.useState` |
|
|
2003
|
+
| `useEffect` | `React.useEffect` |
|
|
2004
|
+
| `useContext` | `React.useContext` |
|
|
2005
|
+
| `useReducer` | `React.useReducer` |
|
|
2006
|
+
| `useCallback` | `React.useCallback` |
|
|
2007
|
+
| `useMemo` | `React.useMemo` |
|
|
2008
|
+
| `useRef` | `React.useRef` |
|
|
2009
|
+
| `useImperativeHandle` | `React.useImperativeHandle` |
|
|
2010
|
+
| `useLayoutEffect` | `React.useLayoutEffect` |
|
|
2011
|
+
| `useDebugValue` | `React.useDebugValue` |
|
|
2012
|
+
|
|
2013
|
+
**Hooks (React 18+)**
|
|
2014
|
+
|
|
2015
|
+
| Import name | React API |
|
|
2016
|
+
|---|---|
|
|
2017
|
+
| `useId` | `React.useId` |
|
|
2018
|
+
| `useTransition` | `React.useTransition` |
|
|
2019
|
+
| `useDeferredValue` | `React.useDeferredValue` |
|
|
2020
|
+
| `useSyncExternalStore` | `React.useSyncExternalStore` |
|
|
2021
|
+
| `useInsertionEffect` | `React.useInsertionEffect` |
|
|
2022
|
+
|
|
2023
|
+
**Core classes and elements**
|
|
2024
|
+
|
|
2025
|
+
| Import name | React API |
|
|
2026
|
+
|---|---|
|
|
2027
|
+
| `Component` | `React.Component` |
|
|
2028
|
+
| `PureComponent` | `React.PureComponent` |
|
|
2029
|
+
| `Fragment` | `React.Fragment` |
|
|
2030
|
+
| `StrictMode` | `React.StrictMode` |
|
|
2031
|
+
| `Suspense` | `React.Suspense` |
|
|
2032
|
+
| `Profiler` | `React.Profiler` |
|
|
2033
|
+
|
|
2034
|
+
**Utilities**
|
|
2035
|
+
|
|
2036
|
+
| Import name | React API |
|
|
2037
|
+
|---|---|
|
|
2038
|
+
| `createElement` | `React.createElement` |
|
|
2039
|
+
| `cloneElement` | `React.cloneElement` |
|
|
2040
|
+
| `createContext` | `React.createContext` |
|
|
2041
|
+
| `createRef` | `React.createRef` |
|
|
2042
|
+
| `forwardRef` | `React.forwardRef` |
|
|
2043
|
+
| `isValidElement` | `React.isValidElement` |
|
|
2044
|
+
| `memo` | `React.memo` |
|
|
2045
|
+
| `lazy` | `React.lazy` |
|
|
2046
|
+
|
|
2047
|
+
### Common patterns
|
|
2048
|
+
|
|
2049
|
+
**useEffect with cleanup**
|
|
2050
|
+
|
|
2051
|
+
```py
|
|
2052
|
+
from react import useState, useEffect
|
|
2053
|
+
|
|
2054
|
+
def Timer():
|
|
2055
|
+
count, setCount = useState(0)
|
|
2056
|
+
def setup():
|
|
2057
|
+
interval = setInterval(def(): setCount(count + 1);, 1000)
|
|
2058
|
+
def cleanup():
|
|
2059
|
+
clearInterval(interval)
|
|
2060
|
+
return cleanup
|
|
2061
|
+
useEffect(setup, [count])
|
|
2062
|
+
return count
|
|
2063
|
+
```
|
|
2064
|
+
|
|
2065
|
+
**useReducer**
|
|
2066
|
+
|
|
2067
|
+
```py
|
|
2068
|
+
from react import useReducer
|
|
2069
|
+
|
|
2070
|
+
def reducer(state, action):
|
|
2071
|
+
if action.type == 'increment':
|
|
2072
|
+
return state + 1
|
|
2073
|
+
if action.type == 'decrement':
|
|
2074
|
+
return state - 1
|
|
2075
|
+
return state
|
|
2076
|
+
|
|
2077
|
+
def Counter():
|
|
2078
|
+
state, dispatch = useReducer(reducer, 0)
|
|
2079
|
+
def inc():
|
|
2080
|
+
dispatch({'type': 'increment'})
|
|
2081
|
+
return state
|
|
2082
|
+
```
|
|
2083
|
+
|
|
2084
|
+
**useContext**
|
|
2085
|
+
|
|
2086
|
+
```py
|
|
2087
|
+
from react import createContext, useContext
|
|
2088
|
+
|
|
2089
|
+
ThemeContext = createContext('light')
|
|
2090
|
+
|
|
2091
|
+
def ThemedButton():
|
|
2092
|
+
theme = useContext(ThemeContext)
|
|
2093
|
+
return theme
|
|
2094
|
+
```
|
|
2095
|
+
|
|
2096
|
+
**useRef**
|
|
2097
|
+
|
|
2098
|
+
```py
|
|
2099
|
+
from __python__ import jsx
|
|
2100
|
+
from react import useRef
|
|
2101
|
+
|
|
2102
|
+
def FocusInput():
|
|
2103
|
+
inputRef = useRef(None)
|
|
2104
|
+
def handleClick():
|
|
2105
|
+
inputRef.current.focus()
|
|
2106
|
+
return <input ref={inputRef} />
|
|
2107
|
+
```
|
|
2108
|
+
|
|
2109
|
+
**memo**
|
|
2110
|
+
|
|
2111
|
+
```py
|
|
2112
|
+
from __python__ import jsx
|
|
2113
|
+
from react import memo
|
|
2114
|
+
|
|
2115
|
+
def Row(props):
|
|
2116
|
+
return <li>{props.label}</li>
|
|
2117
|
+
|
|
2118
|
+
MemoRow = memo(Row)
|
|
2119
|
+
```
|
|
2120
|
+
|
|
2121
|
+
**forwardRef**
|
|
2122
|
+
|
|
2123
|
+
```py
|
|
2124
|
+
from __python__ import jsx
|
|
2125
|
+
from react import forwardRef
|
|
2126
|
+
|
|
2127
|
+
def FancyInput(props, ref):
|
|
2128
|
+
return <input ref={ref} placeholder={props.placeholder} />
|
|
2129
|
+
|
|
2130
|
+
FancyInputWithRef = forwardRef(FancyInput)
|
|
2131
|
+
```
|
|
2132
|
+
|
|
2133
|
+
**Class component**
|
|
2134
|
+
|
|
2135
|
+
You can extend `React.Component` directly without importing it, or import `Component` from the `react` module:
|
|
2136
|
+
|
|
2137
|
+
```py
|
|
2138
|
+
from __python__ import jsx
|
|
2139
|
+
from react import Component
|
|
2140
|
+
|
|
2141
|
+
class Greeter(Component):
|
|
2142
|
+
def render(self):
|
|
2143
|
+
return <h1>Hello, {self.props.name}!</h1>
|
|
2144
|
+
```
|
|
2145
|
+
|
|
2146
|
+
**useTransition (React 18)**
|
|
2147
|
+
|
|
2148
|
+
```py
|
|
2149
|
+
from react import useState, useTransition
|
|
2150
|
+
|
|
2151
|
+
def SearchInput():
|
|
2152
|
+
isPending, startTransition = useTransition()
|
|
2153
|
+
query, setQuery = useState('')
|
|
2154
|
+
def handleChange(e):
|
|
2155
|
+
startTransition(def(): setQuery(e.target.value);)
|
|
2156
|
+
return isPending
|
|
2157
|
+
```
|
|
2158
|
+
|
|
2159
|
+
### Requirements
|
|
2160
|
+
|
|
2161
|
+
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.
|
|
2162
|
+
|
|
1653
2163
|
Creating DOM trees easily
|
|
1654
2164
|
---------------------------------
|
|
1655
2165
|
|
|
@@ -1697,7 +2207,7 @@ E.a(onclick=def():
|
|
|
1697
2207
|
|
|
1698
2208
|
Classes
|
|
1699
2209
|
-------
|
|
1700
|
-
|
|
2210
|
+
JavaScript is not known for having excellent class implementation - but RapydScript improves on that. Imagine we want a special text field that takes in a user color string and changes color based on it. Let's create such field via a class:
|
|
1701
2211
|
|
|
1702
2212
|
```js
|
|
1703
2213
|
class ColorfulTextField:
|
|
@@ -1880,6 +2390,176 @@ print(Counter.get_count()) # 2
|
|
|
1880
2390
|
|
|
1881
2391
|
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.
|
|
1882
2392
|
|
|
2393
|
+
### `__new__` Constructor Hook
|
|
2394
|
+
|
|
2395
|
+
RapydScript supports Python's `__new__` method, which runs *before* `__init__` and controls instance creation. Use it to implement patterns like singletons or alternative constructors:
|
|
2396
|
+
|
|
2397
|
+
```py
|
|
2398
|
+
class Singleton:
|
|
2399
|
+
_instance = None
|
|
2400
|
+
def __new__(cls):
|
|
2401
|
+
if cls._instance is None:
|
|
2402
|
+
cls._instance = super().__new__(cls)
|
|
2403
|
+
return cls._instance
|
|
2404
|
+
def __init__(self):
|
|
2405
|
+
pass
|
|
2406
|
+
|
|
2407
|
+
a = Singleton()
|
|
2408
|
+
b = Singleton()
|
|
2409
|
+
assert a is b # same instance
|
|
2410
|
+
```
|
|
2411
|
+
|
|
2412
|
+
`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.
|
|
2413
|
+
|
|
2414
|
+
Class variables accessed via `cls` inside `__new__` are correctly rewritten to `cls.prototype.varname`, matching Python's semantics.
|
|
2415
|
+
|
|
2416
|
+
### `__class_getitem__`
|
|
2417
|
+
|
|
2418
|
+
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]`:
|
|
2419
|
+
|
|
2420
|
+
```py
|
|
2421
|
+
class Box:
|
|
2422
|
+
def __class_getitem__(cls, item):
|
|
2423
|
+
return cls.__name__ + '[' + str(item) + ']'
|
|
2424
|
+
|
|
2425
|
+
print(Box[int]) # Box[<class 'int'>]
|
|
2426
|
+
print(Box['str']) # Box[str]
|
|
2427
|
+
```
|
|
2428
|
+
|
|
2429
|
+
`__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`.
|
|
2430
|
+
|
|
2431
|
+
Subclasses inherit `__class_getitem__` from their parent and receive the subclass as `cls`:
|
|
2432
|
+
|
|
2433
|
+
```py
|
|
2434
|
+
class Base:
|
|
2435
|
+
def __class_getitem__(cls, item):
|
|
2436
|
+
return cls.__name__ + '<' + str(item) + '>'
|
|
2437
|
+
|
|
2438
|
+
class Child(Base):
|
|
2439
|
+
pass
|
|
2440
|
+
|
|
2441
|
+
print(Base[42]) # Base<42>
|
|
2442
|
+
print(Child[42]) # Child<42>
|
|
2443
|
+
```
|
|
2444
|
+
|
|
2445
|
+
Class variables declared in the class body are accessible via `cls.varname` inside `__class_getitem__`, just as with `@classmethod`.
|
|
2446
|
+
|
|
2447
|
+
### `__init_subclass__`
|
|
2448
|
+
|
|
2449
|
+
`__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.
|
|
2450
|
+
|
|
2451
|
+
```py
|
|
2452
|
+
class PluginBase:
|
|
2453
|
+
_plugins = []
|
|
2454
|
+
|
|
2455
|
+
def __init_subclass__(cls, **kwargs):
|
|
2456
|
+
PluginBase._plugins.append(cls)
|
|
2457
|
+
|
|
2458
|
+
class AudioPlugin(PluginBase):
|
|
2459
|
+
pass
|
|
2460
|
+
|
|
2461
|
+
class VideoPlugin(PluginBase):
|
|
2462
|
+
pass
|
|
2463
|
+
|
|
2464
|
+
print(len(PluginBase._plugins)) # 2
|
|
2465
|
+
print(PluginBase._plugins[0].__name__) # AudioPlugin
|
|
2466
|
+
```
|
|
2467
|
+
|
|
2468
|
+
Keyword arguments written in the class header are forwarded to `__init_subclass__`:
|
|
2469
|
+
|
|
2470
|
+
```py
|
|
2471
|
+
class Base:
|
|
2472
|
+
def __init_subclass__(cls, required=False, **kwargs):
|
|
2473
|
+
cls._required = required
|
|
2474
|
+
|
|
2475
|
+
class Strict(Base, required=True):
|
|
2476
|
+
pass
|
|
2477
|
+
|
|
2478
|
+
class Loose(Base):
|
|
2479
|
+
pass
|
|
2480
|
+
|
|
2481
|
+
print(Strict._required) # True
|
|
2482
|
+
print(Loose._required) # False
|
|
2483
|
+
```
|
|
2484
|
+
|
|
2485
|
+
Use `super().__init_subclass__(**kwargs)` to propagate the hook up the hierarchy:
|
|
2486
|
+
|
|
2487
|
+
```py
|
|
2488
|
+
class GrandParent:
|
|
2489
|
+
def __init_subclass__(cls, **kwargs):
|
|
2490
|
+
cls._from_grandparent = True
|
|
2491
|
+
|
|
2492
|
+
class Parent(GrandParent):
|
|
2493
|
+
def __init_subclass__(cls, **kwargs):
|
|
2494
|
+
super().__init_subclass__(**kwargs) # propagates to GrandParent
|
|
2495
|
+
|
|
2496
|
+
class Child(Parent):
|
|
2497
|
+
pass
|
|
2498
|
+
|
|
2499
|
+
print(Child._from_grandparent) # True
|
|
2500
|
+
```
|
|
2501
|
+
|
|
2502
|
+
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.
|
|
2503
|
+
|
|
2504
|
+
### Nested Classes
|
|
2505
|
+
|
|
2506
|
+
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.
|
|
2507
|
+
|
|
2508
|
+
```py
|
|
2509
|
+
class Molecule:
|
|
2510
|
+
class Atom:
|
|
2511
|
+
def __init__(self, element):
|
|
2512
|
+
self.element = element
|
|
2513
|
+
def __repr__(self):
|
|
2514
|
+
return 'Atom(' + self.element + ')'
|
|
2515
|
+
|
|
2516
|
+
def __init__(self, elements):
|
|
2517
|
+
self.structure = []
|
|
2518
|
+
for e in elements:
|
|
2519
|
+
self.structure.push(Molecule.Atom(e))
|
|
2520
|
+
|
|
2521
|
+
water = Molecule(['H', 'H', 'O'])
|
|
2522
|
+
print(len(water.structure)) # 3
|
|
2523
|
+
print(water.structure[0].element) # H
|
|
2524
|
+
print(isinstance(water.structure[0], Molecule.Atom)) # True
|
|
2525
|
+
```
|
|
2526
|
+
|
|
2527
|
+
The nested class is a full class in every respect — it can have its own methods, inherit from other classes, and contain further nested classes:
|
|
2528
|
+
|
|
2529
|
+
```py
|
|
2530
|
+
class Universe:
|
|
2531
|
+
class Galaxy:
|
|
2532
|
+
class Star:
|
|
2533
|
+
def __init__(self, name):
|
|
2534
|
+
self.name = name
|
|
2535
|
+
def __init__(self, star_name):
|
|
2536
|
+
self.star = Universe.Galaxy.Star(star_name)
|
|
2537
|
+
def __init__(self, star_name):
|
|
2538
|
+
self.galaxy = Universe.Galaxy(star_name)
|
|
2539
|
+
|
|
2540
|
+
u = Universe('Sol')
|
|
2541
|
+
print(u.galaxy.star.name) # Sol
|
|
2542
|
+
print(isinstance(u.galaxy.star, Universe.Galaxy.Star)) # True
|
|
2543
|
+
```
|
|
2544
|
+
|
|
2545
|
+
A nested class may also inherit from classes defined in the outer scope:
|
|
2546
|
+
|
|
2547
|
+
```py
|
|
2548
|
+
class Animal:
|
|
2549
|
+
def __init__(self, sound):
|
|
2550
|
+
self.sound = sound
|
|
2551
|
+
|
|
2552
|
+
class Zoo:
|
|
2553
|
+
class Dog(Animal):
|
|
2554
|
+
def __init__(self):
|
|
2555
|
+
Animal.__init__(self, 'woof')
|
|
2556
|
+
|
|
2557
|
+
fido = Zoo.Dog()
|
|
2558
|
+
print(fido.sound) # woof
|
|
2559
|
+
print(isinstance(fido, Animal)) # True
|
|
2560
|
+
print(isinstance(fido, Zoo.Dog)) # True
|
|
2561
|
+
```
|
|
2562
|
+
|
|
1883
2563
|
### External Classes
|
|
1884
2564
|
|
|
1885
2565
|
RapydScript will automatically detect classes declared within the same scope (as long as the declaration occurs before use), as well as classes properly imported into the module (each module making use of a certain class should explicitly import the module containing that class). RapydScript will also properly detect native JavaScript classes (String, Array, Date, etc.). Unfortunately, RapydScript has no way of detecting classes from third-party libraries. In those cases, you could use the `new` keyword every time you create an object from such class. Alternatively, you could mark the class as external.
|
|
@@ -2029,6 +2709,26 @@ next(it) # raises StopIteration
|
|
|
2029
2709
|
When the iterator is exhausted and no default is given, ``StopIteration``
|
|
2030
2710
|
is raised — matching standard Python behaviour.
|
|
2031
2711
|
|
|
2712
|
+
The two-argument form ``iter(callable, sentinel)`` repeatedly calls
|
|
2713
|
+
``callable`` (with no arguments) and yields each return value until it equals
|
|
2714
|
+
``sentinel``, at which point the iterator stops:
|
|
2715
|
+
|
|
2716
|
+
```python
|
|
2717
|
+
count = [0]
|
|
2718
|
+
def next_val():
|
|
2719
|
+
count[0] += 1
|
|
2720
|
+
return count[0]
|
|
2721
|
+
|
|
2722
|
+
list(iter(next_val, 4)) # [1, 2, 3]
|
|
2723
|
+
|
|
2724
|
+
for val in iter(next_val, 7):
|
|
2725
|
+
print(val) # 5, 6
|
|
2726
|
+
```
|
|
2727
|
+
|
|
2728
|
+
The callable may be any function or object with a ``__call__`` method.
|
|
2729
|
+
Sentinel comparison uses strict equality (``===``), matching Python's
|
|
2730
|
+
``is``-then-``==`` semantics for the common case of plain values.
|
|
2731
|
+
|
|
2032
2732
|
Generators
|
|
2033
2733
|
------------
|
|
2034
2734
|
|
|
@@ -2039,18 +2739,19 @@ def f():
|
|
|
2039
2739
|
for i in range(3):
|
|
2040
2740
|
yield i
|
|
2041
2741
|
|
|
2042
|
-
[x for x in f()] == [1, 2
|
|
2742
|
+
[x for x in f()] == [0, 1, 2]
|
|
2043
2743
|
```
|
|
2044
2744
|
|
|
2045
2745
|
There is full support for generators including the Python 3, ```yield from```
|
|
2046
|
-
syntax.
|
|
2746
|
+
syntax.
|
|
2047
2747
|
|
|
2048
2748
|
Generators create JavaScript iterator objects. For differences between python
|
|
2049
|
-
and JavaScript iterators, see the section on iterators above.
|
|
2749
|
+
and JavaScript iterators, see the section on iterators above.
|
|
2050
2750
|
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2751
|
+
By default, generators are down-converted to ES 5 switch statements. Pass
|
|
2752
|
+
``--js-version 6`` to the compiler (or set ``js_version: 6`` in the embedded
|
|
2753
|
+
compiler options) to emit native ES 6 generator functions instead, which are
|
|
2754
|
+
smaller and faster.
|
|
2054
2755
|
|
|
2055
2756
|
Modules
|
|
2056
2757
|
-------
|
|
@@ -2191,7 +2892,7 @@ finally:
|
|
|
2191
2892
|
```
|
|
2192
2893
|
|
|
2193
2894
|
You can create your own Exception classes by inheriting from `Exception`, which
|
|
2194
|
-
is the JavaScript Error class, for more details on this, see
|
|
2895
|
+
is the JavaScript Error class, for more details on this, see the [MDN documentation](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Error).
|
|
2195
2896
|
|
|
2196
2897
|
```py
|
|
2197
2898
|
class MyError(Exception):
|
|
@@ -2236,6 +2937,45 @@ except SomeInternalError:
|
|
|
2236
2937
|
|
|
2237
2938
|
Basically, `try/except/finally` in RapydScript works very similar to the way it does in Python 3.
|
|
2238
2939
|
|
|
2940
|
+
### Exception Groups (`except*`, Python 3.11+)
|
|
2941
|
+
|
|
2942
|
+
RapydScript supports exception groups via the `ExceptionGroup` class and `except*` syntax. An `ExceptionGroup` bundles multiple exceptions under a single message:
|
|
2943
|
+
|
|
2944
|
+
```py
|
|
2945
|
+
eg = ExceptionGroup("network errors", [
|
|
2946
|
+
TimeoutError("host A timed out"),
|
|
2947
|
+
TimeoutError("host B timed out"),
|
|
2948
|
+
ValueError("bad address"),
|
|
2949
|
+
])
|
|
2950
|
+
raise eg
|
|
2951
|
+
```
|
|
2952
|
+
|
|
2953
|
+
Use `except*` to handle exceptions by type — each handler receives a sub-group containing only the matching exceptions:
|
|
2954
|
+
|
|
2955
|
+
```py
|
|
2956
|
+
try:
|
|
2957
|
+
fetch_all()
|
|
2958
|
+
except* TimeoutError as group:
|
|
2959
|
+
for e in group.exceptions:
|
|
2960
|
+
print("timeout:", e)
|
|
2961
|
+
except* ValueError as group:
|
|
2962
|
+
print("bad input:", group.exceptions[0])
|
|
2963
|
+
```
|
|
2964
|
+
|
|
2965
|
+
- Each `except*` clause sees the exceptions not already matched by earlier clauses.
|
|
2966
|
+
- Any unmatched exceptions are automatically re-raised as a new `ExceptionGroup`.
|
|
2967
|
+
- A bare `except*:` (no type) catches all remaining exceptions.
|
|
2968
|
+
- You cannot mix `except` and `except*` in the same `try` block.
|
|
2969
|
+
- Plain (non-group) exceptions can also be caught with `except*`; the variable is bound to the exception itself rather than a sub-group.
|
|
2970
|
+
|
|
2971
|
+
`ExceptionGroup` also provides `subgroup(condition)` and `split(condition)` for programmatic filtering, where `condition` is either an exception class or a predicate function:
|
|
2972
|
+
|
|
2973
|
+
```py
|
|
2974
|
+
eg = ExceptionGroup("mixed", [ValueError("v"), TypeError("t")])
|
|
2975
|
+
ve_group = eg.subgroup(ValueError) # ExceptionGroup of just ValueErrors
|
|
2976
|
+
matched, rest = eg.split(ValueError) # (ve_group, te_group)
|
|
2977
|
+
```
|
|
2978
|
+
|
|
2239
2979
|
Scope Control
|
|
2240
2980
|
-------------
|
|
2241
2981
|
|
|
@@ -2288,30 +3028,32 @@ Shadowing is preferred in most cases, since it can't accidentally damage outside
|
|
|
2288
3028
|
Available Libraries
|
|
2289
3029
|
-------------------
|
|
2290
3030
|
|
|
2291
|
-
One of Python's main strengths is the number of libraries available to the developer.
|
|
3031
|
+
One of Python's main strengths is the number of libraries available to the developer. The large number of readily-available JavaScript libraries will always outnumber community-made Rapydscript libraries. This is why RapydScript was designed with JS and DOM integration in mind from the beginning. For example, plugging in `lodash` in place of RapydScript's `stdlib` will work fine!
|
|
2292
3032
|
|
|
2293
|
-
|
|
3033
|
+
RapydScript's main strength is easy integration with JavaScript and DOM, and easy use of libraries that are already available. That doesn't mean, however, that pythonic libraries can't be written for RapydScript. Rapydscript comes with lightweight clones of several popular Python libraries, which you can find them in `src` directory.
|
|
2294
3034
|
|
|
2295
3035
|
math # replicates almost all of the functionality from Python's math library
|
|
2296
3036
|
re # replicates almost all of the functionality from Python's re library
|
|
2297
3037
|
random # replicates most of the functionality from Python's random library
|
|
3038
|
+
numpy # NumPy-compatible array library (ndarray, ufuncs, numpy.random, numpy.linalg)
|
|
2298
3039
|
elementmaker # easily construct DOM trees
|
|
2299
3040
|
aes # Implement AES symmetric encryption
|
|
2300
3041
|
encodings # Convert to/from UTF-8 bytearrays, base64 strings and native strings
|
|
2301
3042
|
gettext # Support for internationalization of your RapydScript app
|
|
2302
|
-
operator # a subset of
|
|
3043
|
+
operator # a subset of Python's operator module
|
|
2303
3044
|
functools # reduce, partial, wraps, lru_cache, cache, total_ordering, cmp_to_key
|
|
2304
3045
|
collections # namedtuple, deque, Counter, OrderedDict, defaultdict
|
|
3046
|
+
copy # copy (shallow), deepcopy; honours __copy__ / __deepcopy__ hooks
|
|
2305
3047
|
itertools # count, cycle, repeat, accumulate, chain, compress, dropwhile, filterfalse,
|
|
2306
3048
|
# groupby, islice, pairwise, starmap, takewhile, zip_longest,
|
|
2307
3049
|
# product, permutations, combinations, combinations_with_replacement
|
|
2308
3050
|
|
|
2309
|
-
For the most part, the logic implemented in these libraries functions identically to the Python versions.
|
|
3051
|
+
For the most part, the logic implemented in these libraries functions identically to the Python versions. I'd be happy to include more libraries, if other members of the community want them. However, unlike most other Python-to-JavaScript compilers, RapydScript doesn't need them to be complete since there are already tons of available JavaScript libraries that it can use natively.
|
|
2310
3052
|
|
|
2311
3053
|
Linter
|
|
2312
3054
|
---------
|
|
2313
3055
|
|
|
2314
|
-
The RapydScript compiler includes its own, built
|
|
3056
|
+
The RapydScript compiler includes its own, built-in linter. The linter is
|
|
2315
3057
|
modeled on pyflakes, it catches instances of unused/undefined variables,
|
|
2316
3058
|
functions, symbols, etc. While this sounds simple, it is surprisingly effective
|
|
2317
3059
|
in practice. To run the linter:
|
|
@@ -2391,13 +3133,12 @@ RapydScript will pick up any classes you declare yourself as well as native
|
|
|
2391
3133
|
JavaScript classes. It will not, however, pick up class-like objects created by
|
|
2392
3134
|
outside frameworks. There are two approaches for dealing with those. One is via
|
|
2393
3135
|
`@external` decorator, the other is via `new` operator when declaring such
|
|
2394
|
-
object.
|
|
2395
|
-
|
|
2396
|
-
may be more verbose:
|
|
3136
|
+
object. The `@external` decorator is recommended over the `new` operator for
|
|
3137
|
+
several reasons, even if it may be more verbose:
|
|
2397
3138
|
|
|
2398
3139
|
- `@external` decorator makes classes declared externally obvious to anyone looking at your code
|
|
2399
3140
|
- class declaration that uses `@external` decorator can be exported into a reusable module
|
|
2400
|
-
- developers are much more likely to forget a single instance of `new` operator when declaring an object than to forget an import
|
|
3141
|
+
- developers are much more likely to forget a single instance of `new` operator when declaring an object than to forget an import. the errors due to omitted `new` keyword are also likely to be more subtle and devious to debug
|
|
2401
3142
|
|
|
2402
3143
|
#### Embedding the RapydScript compiler in your webpage
|
|
2403
3144
|
|
|
@@ -2417,7 +3158,7 @@ HTML below for an example.
|
|
|
2417
3158
|
<head>
|
|
2418
3159
|
<meta charset="UTF-8">
|
|
2419
3160
|
<title>Test embedded RapydScript</title>
|
|
2420
|
-
<script charset="UTF-8" src="
|
|
3161
|
+
<script charset="UTF-8" src="rapydscript.js"></script>
|
|
2421
3162
|
<script>
|
|
2422
3163
|
var compiler = RapydScript.create_embedded_compiler();
|
|
2423
3164
|
var js = compiler.compile("def hello_world():\n a='RapydScript is cool!'\n print(a)\n alert(a)");
|
|
@@ -2511,9 +3252,10 @@ to an experienced Python developer. The most important such gotchas are listed
|
|
|
2511
3252
|
below:
|
|
2512
3253
|
|
|
2513
3254
|
- Truthiness in JavaScript is very different from Python. Empty lists and dicts
|
|
2514
|
-
are ``False`` in Python but ``True`` in JavaScript.
|
|
2515
|
-
|
|
2516
|
-
|
|
3255
|
+
are ``False`` in Python but ``True`` in JavaScript. You can opt in to full
|
|
3256
|
+
Python truthiness semantics (where empty containers are falsy and ``__bool__``
|
|
3257
|
+
is dispatched) with ``from __python__ import truthiness``. Without that flag,
|
|
3258
|
+
test the length explicitly instead of the container directly.
|
|
2517
3259
|
|
|
2518
3260
|
- Operators in JavaScript are very different from Python. ``1 + '1'`` would be
|
|
2519
3261
|
an error in Python, but results in ``'11'`` in JavaScript. Similarly, ``[1] +
|
|
@@ -2546,6 +3288,31 @@ below:
|
|
|
2546
3288
|
should use a scoped flag. See the section on dictionaries above for details.
|
|
2547
3289
|
|
|
2548
3290
|
|
|
3291
|
+
Python Flags
|
|
3292
|
+
------------
|
|
3293
|
+
|
|
3294
|
+
Python flags are scoped compiler directives that opt in to stricter Python
|
|
3295
|
+
semantics or language features. In source code they are written as:
|
|
3296
|
+
|
|
3297
|
+
```py
|
|
3298
|
+
from __python__ import flag_name
|
|
3299
|
+
```
|
|
3300
|
+
|
|
3301
|
+
At the top level they take effect for the rest of the file; inside a function
|
|
3302
|
+
or class body they apply only to that scope. Prefix a flag with `no_` to turn
|
|
3303
|
+
it off in a nested scope.
|
|
3304
|
+
|
|
3305
|
+
| Flag | Description |
|
|
3306
|
+
|---|---|
|
|
3307
|
+
| `dict_literals` | `{k: v}` literals create Python `dict` objects instead of plain JS objects. |
|
|
3308
|
+
| `overload_getitem` | `obj[key]` dispatches to `__getitem__` / `__setitem__` / `__delitem__` on objects that define them. |
|
|
3309
|
+
| `overload_operators` | Arithmetic and bitwise operators (`+`, `-`, `*`, `/`, `//`, `%`, `**`, `&`, `\|`, `^`, `<<`, `>>`) dispatch to dunder methods (`__add__`, `__sub__`, etc.) and their reflected variants. Unary `-`/`+`/`~` dispatch to `__neg__`/`__pos__`/`__invert__`. |
|
|
3310
|
+
| `truthiness` | Boolean tests and `bool()` dispatch to `__bool__` and treat empty containers as falsy, matching Python semantics. |
|
|
3311
|
+
| `bound_methods` | Method references (`obj.method`) are automatically bound to their object, so they can be passed as callbacks without losing `self`. |
|
|
3312
|
+
| `hash_literals` | `{k: v}` creates a Python `dict` (alias for `dict_literals`; kept for backward compatibility). |
|
|
3313
|
+
| `jsx` | Enable JSX syntax (`<Tag attr={expr}>children</Tag>`). Required for files that contain JSX elements. |
|
|
3314
|
+
|
|
3315
|
+
|
|
2549
3316
|
Monaco Language Service
|
|
2550
3317
|
-----------------------
|
|
2551
3318
|
|
|
@@ -2574,7 +3341,7 @@ npm run build:ls
|
|
|
2574
3341
|
node tools/build-language-service.js --out path/to/language-service.js
|
|
2575
3342
|
```
|
|
2576
3343
|
|
|
2577
|
-
The output is written to `
|
|
3344
|
+
The output is written to `language-service/index.js` by default.
|
|
2578
3345
|
|
|
2579
3346
|
### Basic setup
|
|
2580
3347
|
|
|
@@ -2607,9 +3374,11 @@ when the editor is torn down.
|
|
|
2607
3374
|
| `compiler` | object | — | **Required.** The `window.RapydScript` compiler bundle. |
|
|
2608
3375
|
| `parseDelay` | number | `300` | Debounce delay (ms) before re-checking after an edit. |
|
|
2609
3376
|
| `virtualFiles` | `{name: source}` | `{}` | Virtual modules available to `import` statements. |
|
|
3377
|
+
| `stdlibFiles` | `{name: source}` | `{}` | Like `virtualFiles` but treated as stdlib — always available and never produce bad-import warnings. |
|
|
2610
3378
|
| `dtsFiles` | `[{name, content}]` | `[]` | TypeScript `.d.ts` files loaded at startup. |
|
|
2611
3379
|
| `loadDts` | `(name) => Promise<string>` | — | Async callback for lazy-loading `.d.ts` content on demand. |
|
|
2612
3380
|
| `extraBuiltins` | `{name: true}` | `{}` | Extra global names that suppress undefined-symbol warnings. |
|
|
3381
|
+
| `pythonFlags` | string | — | Comma-separated Python flags to enable globally (e.g. `"dict_literals,overload_getitem"`). See [Python Flags](#python-flags) above. |
|
|
2613
3382
|
|
|
2614
3383
|
### Runtime API
|
|
2615
3384
|
|
|
@@ -2629,6 +3398,9 @@ service.loadDts('lib.dom').then(function () { console.log('DOM types loaded'); }
|
|
|
2629
3398
|
// Suppress undefined-symbol warnings for additional global names
|
|
2630
3399
|
service.addGlobals(['myFrameworkGlobal', '$']);
|
|
2631
3400
|
|
|
3401
|
+
// Get the most recently built scope map for a Monaco model (null if not yet analysed)
|
|
3402
|
+
var scopeMap = service.getScopeMap(editorModel);
|
|
3403
|
+
|
|
2632
3404
|
// Tear down all Monaco providers and event listeners
|
|
2633
3405
|
service.dispose();
|
|
2634
3406
|
```
|
|
@@ -2752,6 +3524,13 @@ original `.py` file with working breakpoints and correct error stack frames.
|
|
|
2752
3524
|
| `keep_docstrings` | bool | `false` | Keep docstrings in the output. |
|
|
2753
3525
|
| `js_version` | number | `6` | Target ECMAScript version (5 or 6). |
|
|
2754
3526
|
| `private_scope` | bool | `false` | Wrap the output in an IIFE. |
|
|
3527
|
+
| `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. |
|
|
3528
|
+
| `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). |
|
|
3529
|
+
| `discard_asserts` | bool | `false` | Strip all `assert` statements from the output. |
|
|
3530
|
+
| `omit_function_metadata` | bool | `false` | Omit per-function metadata (e.g. argument names) from the output for smaller bundles. |
|
|
3531
|
+
| `write_name` | bool | `false` | Emit a `var __name__ = "…"` assignment at the top of the output. |
|
|
3532
|
+
| `tree_shake` | bool | `false` | Remove unused imported names from the output (requires stdlib imports). |
|
|
3533
|
+
| `filename` | string | `'<input>'` | Source filename embedded in the source map and used in error messages. |
|
|
2755
3534
|
|
|
2756
3535
|
**How it works**
|
|
2757
3536
|
|
|
@@ -2769,7 +3548,7 @@ and assembled into the standard `mappings` field. The implementation lives in
|
|
|
2769
3548
|
|
|
2770
3549
|
```bash
|
|
2771
3550
|
node bin/web-repl-export web-repl # rebuilds web-repl/rapydscript.js
|
|
2772
|
-
node tools/build-language-service.js # rebuilds
|
|
3551
|
+
node tools/build-language-service.js # rebuilds language-service/index.js
|
|
2773
3552
|
```
|
|
2774
3553
|
|
|
2775
3554
|
### Running the tests
|