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.
Files changed (141) hide show
  1. package/.agignore +1 -1
  2. package/.github/workflows/ci.yml +38 -38
  3. package/=template.pyj +5 -5
  4. package/CHANGELOG.md +39 -0
  5. package/HACKING.md +103 -103
  6. package/LICENSE +24 -24
  7. package/PYTHON_DIFFERENCES_REPORT.md +291 -0
  8. package/PYTHON_FEATURE_COVERAGE.md +106 -15
  9. package/README.md +831 -52
  10. package/TODO.md +4 -286
  11. package/add-toc-to-readme +2 -2
  12. package/bin/export +75 -75
  13. package/bin/rapydscript +70 -70
  14. package/bin/web-repl-export +102 -102
  15. package/build +2 -2
  16. package/language-service/index.js +4623 -0
  17. package/language-service/language-service.d.ts +40 -0
  18. package/package.json +9 -7
  19. package/publish.py +37 -37
  20. package/release/baselib-plain-pretty.js +2006 -229
  21. package/release/baselib-plain-ugly.js +70 -3
  22. package/release/compiler.js +11554 -3870
  23. package/release/signatures.json +31 -29
  24. package/session.vim +4 -4
  25. package/setup.cfg +2 -2
  26. package/src/ast.pyj +93 -1
  27. package/src/baselib-builtins.pyj +99 -2
  28. package/src/baselib-containers.pyj +107 -4
  29. package/src/baselib-errors.pyj +44 -0
  30. package/src/baselib-internal.pyj +124 -5
  31. package/src/baselib-itertools.pyj +97 -97
  32. package/src/baselib-str.pyj +32 -1
  33. package/src/compiler.pyj +36 -36
  34. package/src/errors.pyj +30 -30
  35. package/src/lib/aes.pyj +646 -646
  36. package/src/lib/collections.pyj +1 -1
  37. package/src/lib/copy.pyj +120 -0
  38. package/src/lib/elementmaker.pyj +83 -83
  39. package/src/lib/encodings.pyj +126 -126
  40. package/src/lib/gettext.pyj +569 -569
  41. package/src/lib/itertools.pyj +580 -580
  42. package/src/lib/math.pyj +193 -193
  43. package/src/lib/numpy.pyj +10 -10
  44. package/src/lib/operator.pyj +11 -11
  45. package/src/lib/pythonize.pyj +20 -20
  46. package/src/lib/random.pyj +118 -118
  47. package/src/lib/re.pyj +470 -470
  48. package/src/lib/react.pyj +74 -0
  49. package/src/lib/traceback.pyj +63 -63
  50. package/src/lib/uuid.pyj +77 -77
  51. package/src/monaco-language-service/analyzer.js +131 -9
  52. package/src/monaco-language-service/builtins.js +17 -2
  53. package/src/monaco-language-service/completions.js +170 -1
  54. package/src/monaco-language-service/diagnostics.js +25 -3
  55. package/src/monaco-language-service/dts.js +550 -550
  56. package/src/monaco-language-service/index.js +17 -0
  57. package/src/monaco-language-service/scope.js +3 -0
  58. package/src/output/classes.pyj +128 -11
  59. package/src/output/codegen.pyj +17 -3
  60. package/src/output/comments.pyj +45 -45
  61. package/src/output/exceptions.pyj +201 -105
  62. package/src/output/functions.pyj +13 -16
  63. package/src/output/jsx.pyj +164 -0
  64. package/src/output/literals.pyj +28 -2
  65. package/src/output/loops.pyj +0 -9
  66. package/src/output/modules.pyj +2 -5
  67. package/src/output/operators.pyj +22 -2
  68. package/src/output/statements.pyj +2 -2
  69. package/src/output/stream.pyj +1 -13
  70. package/src/output/treeshake.pyj +182 -182
  71. package/src/output/utils.pyj +72 -72
  72. package/src/parse.pyj +434 -114
  73. package/src/string_interpolation.pyj +72 -72
  74. package/src/tokenizer.pyj +29 -0
  75. package/src/unicode_aliases.pyj +576 -576
  76. package/src/utils.pyj +192 -192
  77. package/test/_import_one.pyj +37 -37
  78. package/test/_import_two/__init__.pyj +11 -11
  79. package/test/_import_two/level2/deep.pyj +4 -4
  80. package/test/_import_two/other.pyj +6 -6
  81. package/test/_import_two/sub.pyj +13 -13
  82. package/test/aes_vectors.pyj +421 -421
  83. package/test/annotations.pyj +80 -80
  84. package/test/baselib.pyj +4 -4
  85. package/test/classes.pyj +56 -17
  86. package/test/collections.pyj +5 -5
  87. package/test/decorators.pyj +77 -77
  88. package/test/docstrings.pyj +39 -39
  89. package/test/elementmaker_test.pyj +45 -45
  90. package/test/functions.pyj +151 -151
  91. package/test/generators.pyj +41 -41
  92. package/test/generic.pyj +370 -370
  93. package/test/imports.pyj +72 -72
  94. package/test/internationalization.pyj +73 -73
  95. package/test/lint.pyj +164 -164
  96. package/test/loops.pyj +85 -85
  97. package/test/numpy.pyj +734 -734
  98. package/test/omit_function_metadata.pyj +20 -20
  99. package/test/python_compat.pyj +326 -0
  100. package/test/python_features.pyj +129 -29
  101. package/test/regexp.pyj +55 -55
  102. package/test/repl.pyj +121 -121
  103. package/test/scoped_flags.pyj +76 -76
  104. package/test/slice.pyj +105 -0
  105. package/test/str.pyj +25 -0
  106. package/test/unit/fixtures/fibonacci_expected.js +1 -1
  107. package/test/unit/index.js +2296 -71
  108. package/test/unit/language-service-builtins.js +70 -0
  109. package/test/unit/language-service-bundle.js +5 -5
  110. package/test/unit/language-service-completions.js +180 -0
  111. package/test/unit/language-service-dts.js +543 -543
  112. package/test/unit/language-service-hover.js +455 -455
  113. package/test/unit/language-service-index.js +350 -0
  114. package/test/unit/language-service-scope.js +255 -0
  115. package/test/unit/language-service.js +625 -4
  116. package/test/unit/run-language-service.js +1 -0
  117. package/test/unit/web-repl.js +437 -0
  118. package/tools/build-language-service.js +2 -2
  119. package/tools/cli.js +547 -547
  120. package/tools/compile.js +219 -219
  121. package/tools/compiler.js +0 -24
  122. package/tools/completer.js +131 -131
  123. package/tools/embedded_compiler.js +251 -251
  124. package/tools/export.js +3 -37
  125. package/tools/gettext.js +185 -185
  126. package/tools/ini.js +65 -65
  127. package/tools/msgfmt.js +187 -187
  128. package/tools/repl.js +223 -223
  129. package/tools/test.js +118 -118
  130. package/tools/utils.js +128 -128
  131. package/tools/web_repl.js +95 -95
  132. package/try +41 -41
  133. package/web-repl/env.js +196 -74
  134. package/web-repl/index.html +163 -163
  135. package/web-repl/main.js +252 -254
  136. package/web-repl/prism.css +139 -139
  137. package/web-repl/prism.js +113 -113
  138. package/web-repl/rapydscript.js +227 -139
  139. package/web-repl/sha1.js +25 -25
  140. package/hack_demo.pyj +0 -112
  141. 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. I'll refer to it as function inlining. This is meant as a feature of
364
- RapydScript to make the code cleaner in cases like the example above. While you
365
- can use it in longer functions by chaining statements together using `;`, a
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
- Some of you might welcome this feature, some of you might not. RapydScript always aims to make its unique features unobtrusive to regular Python, which means that you don't have to use them if you disagree with them. Recently, we have enhanced this feature to handle `do/while` loops as well:
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. While inferred/implicit logic is usually bad, it can sometimes make the code cleaner, and based on the order of statements in the Zen of Python, 'beautiful' takes priority over 'explicit'. For example, if you wanted to swap two variables, the following looks cleaner than explicitly declaring a temporary variable:
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 only small caveats are that the ``sort()`` and
770
- ``pop()`` methods are renamed to ``pysort()`` and ``pypop()``. This is so that
771
- you can pass RapydScript lists to external JavaScript libraries without any
772
- conflicts. Note that even list literals in RapydScript create python like list
773
- objects, and you can also use the builtin ``list()`` function to create lists
774
- from other iterable objects, just as you would in python. You can create a
775
- RapydScript list from a plain native JavaScript array by using the ``list_wrap()``
776
- function, like this:
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, the only caveat is that RapydScript does not support
789
- the ``__hash__()`` method, so if you store an arbitrary object as opposed to a
790
- primitive type, object equality will be via the ``is`` operator.
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, as noted above, object equality is via the ```is```
819
- operator, not hashes.
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
- This is where RapydScript really starts to shine. JavaScript is known for having really crappy class implementation (it's basically a hack on top of a normal function, most experienced users suggest using external libraries for creating those instead of creating them in pure JavaScript). Luckily RapydScript fixes that. Let's 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.
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, 3]
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
- Currently, generators are down-converted to ES 5 switch statements. In the
2052
- future, when ES 6 support is widespread, they will be converted to native
2053
- JavaScript ES 6 generators.
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 (the MDN documentation)[https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Error].
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. This is something very few other `Python-in-a-browser` frameworks understand. In the browser JavaScript is king, and no matter how many libraries the community for the given project will write, the readily-available JavaScript libraries will always outnumber them. This is why RapydScript was designed with JavaScript and DOM integration in mind from the beginning. Indeed, plugging `underscore.js` in place of RapydScript's `stdlib` will work just as well, and some developers may choose to do so, after all, `underscore.js` is very Pythonic and very complete.
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
- It is for that reason that I try to keep RapydScript bells and whistles to a minimum. RapydScript's main strength is easy integration with JavaScript and DOM, which allows me to stay sane and not rewrite my own versions of the libraries that are already available. That doesn't mean, however, that pythonic libraries can't be written for RapydScript. To prove that, I have implemented lightweight clones of several popular Python libraries and bundled them into RapydScript, you can find them in `src` directory. The following libraries are included:
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 python;s operator module
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. I'd be happy to include more libraries, if other members of the community want to implement them (it's fun to do, `re.pyj` is a good example), but I want to reemphasize that 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.
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 in linter. The linter is
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. To keep code legible and consistent, I strongly prefer the use of
2395
- `@external` decorator over the `new` operator for several reasons, even if it
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, the errors due to omitted `new` keyword are also likely to be more subtle and devious to debug
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="https://github.com/ficocelliguy/rapydscript-ns/blob/master/web-repl/rapydscript.js"></script>
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. The compiler could work
2515
- around that, but not without a significant performance cost, so it is best to
2516
- just get used to checking the length instead of the object directly.
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 `web-repl/language-service.js` by default.
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 web-repl/language-service.js
3551
+ node tools/build-language-service.js # rebuilds language-service/index.js
2773
3552
  ```
2774
3553
 
2775
3554
  ### Running the tests