rapydscript-ns 0.8.3 → 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 (116) 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 +8 -0
  5. package/HACKING.md +103 -103
  6. package/LICENSE +24 -24
  7. package/PYTHON_DIFFERENCES_REPORT.md +2 -2
  8. package/PYTHON_FEATURE_COVERAGE.md +13 -13
  9. package/README.md +670 -6
  10. package/TODO.md +5 -6
  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 +155 -6
  17. package/package.json +1 -1
  18. package/publish.py +37 -37
  19. package/release/baselib-plain-pretty.js +2006 -229
  20. package/release/baselib-plain-ugly.js +70 -3
  21. package/release/compiler.js +11554 -3870
  22. package/release/signatures.json +31 -29
  23. package/session.vim +4 -4
  24. package/setup.cfg +2 -2
  25. package/src/ast.pyj +93 -1
  26. package/src/baselib-builtins.pyj +22 -1
  27. package/src/baselib-containers.pyj +99 -0
  28. package/src/baselib-errors.pyj +44 -0
  29. package/src/baselib-internal.pyj +94 -4
  30. package/src/baselib-itertools.pyj +97 -97
  31. package/src/baselib-str.pyj +24 -0
  32. package/src/compiler.pyj +36 -36
  33. package/src/errors.pyj +30 -30
  34. package/src/lib/aes.pyj +646 -646
  35. package/src/lib/copy.pyj +120 -0
  36. package/src/lib/elementmaker.pyj +83 -83
  37. package/src/lib/encodings.pyj +126 -126
  38. package/src/lib/gettext.pyj +569 -569
  39. package/src/lib/itertools.pyj +580 -580
  40. package/src/lib/math.pyj +193 -193
  41. package/src/lib/operator.pyj +11 -11
  42. package/src/lib/pythonize.pyj +20 -20
  43. package/src/lib/random.pyj +118 -118
  44. package/src/lib/re.pyj +470 -470
  45. package/src/lib/react.pyj +74 -0
  46. package/src/lib/traceback.pyj +63 -63
  47. package/src/lib/uuid.pyj +77 -77
  48. package/src/monaco-language-service/builtins.js +5 -0
  49. package/src/monaco-language-service/diagnostics.js +25 -3
  50. package/src/monaco-language-service/dts.js +550 -550
  51. package/src/output/classes.pyj +108 -8
  52. package/src/output/codegen.pyj +16 -2
  53. package/src/output/comments.pyj +45 -45
  54. package/src/output/exceptions.pyj +201 -105
  55. package/src/output/functions.pyj +9 -0
  56. package/src/output/jsx.pyj +164 -0
  57. package/src/output/literals.pyj +28 -2
  58. package/src/output/modules.pyj +1 -1
  59. package/src/output/operators.pyj +8 -2
  60. package/src/output/statements.pyj +2 -2
  61. package/src/output/stream.pyj +1 -0
  62. package/src/output/treeshake.pyj +182 -182
  63. package/src/output/utils.pyj +72 -72
  64. package/src/parse.pyj +417 -113
  65. package/src/string_interpolation.pyj +72 -72
  66. package/src/tokenizer.pyj +29 -0
  67. package/src/unicode_aliases.pyj +576 -576
  68. package/src/utils.pyj +192 -192
  69. package/test/_import_one.pyj +37 -37
  70. package/test/_import_two/__init__.pyj +11 -11
  71. package/test/_import_two/level2/deep.pyj +4 -4
  72. package/test/_import_two/other.pyj +6 -6
  73. package/test/_import_two/sub.pyj +13 -13
  74. package/test/aes_vectors.pyj +421 -421
  75. package/test/annotations.pyj +80 -80
  76. package/test/decorators.pyj +77 -77
  77. package/test/docstrings.pyj +39 -39
  78. package/test/elementmaker_test.pyj +45 -45
  79. package/test/functions.pyj +151 -151
  80. package/test/generators.pyj +41 -41
  81. package/test/generic.pyj +370 -370
  82. package/test/imports.pyj +72 -72
  83. package/test/internationalization.pyj +73 -73
  84. package/test/lint.pyj +164 -164
  85. package/test/loops.pyj +85 -85
  86. package/test/numpy.pyj +734 -734
  87. package/test/omit_function_metadata.pyj +20 -20
  88. package/test/python_features.pyj +19 -6
  89. package/test/regexp.pyj +55 -55
  90. package/test/repl.pyj +121 -121
  91. package/test/scoped_flags.pyj +76 -76
  92. package/test/unit/index.js +2177 -64
  93. package/test/unit/language-service-dts.js +543 -543
  94. package/test/unit/language-service-hover.js +455 -455
  95. package/test/unit/language-service.js +590 -4
  96. package/test/unit/web-repl.js +303 -0
  97. package/tools/cli.js +547 -547
  98. package/tools/compile.js +219 -219
  99. package/tools/completer.js +131 -131
  100. package/tools/embedded_compiler.js +251 -251
  101. package/tools/gettext.js +185 -185
  102. package/tools/ini.js +65 -65
  103. package/tools/msgfmt.js +187 -187
  104. package/tools/repl.js +223 -223
  105. package/tools/test.js +118 -118
  106. package/tools/utils.js +128 -128
  107. package/tools/web_repl.js +95 -95
  108. package/try +41 -41
  109. package/web-repl/env.js +196 -74
  110. package/web-repl/index.html +163 -163
  111. package/web-repl/main.js +252 -254
  112. package/web-repl/prism.css +139 -139
  113. package/web-repl/prism.js +113 -113
  114. package/web-repl/rapydscript.js +224 -102
  115. package/web-repl/sha1.js +25 -25
  116. package/hack_demo.pyj +0 -112
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)
@@ -805,9 +806,9 @@ No special flag is required. The `+` operator compiles to a lightweight helper
805
806
 
806
807
  Sets in RapydScript are identical to those in python. You can create them using
807
808
  set literals or comprehensions and all set operations are supported. You can
808
- store any object in a set, the only caveat is that RapydScript does not support
809
- the ``__hash__()`` method, so if you store an arbitrary object as opposed to a
810
- 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.
811
812
 
812
813
  Note that sets are not a subclass of the ES 6 JavaScript Set object, however,
813
814
  they do use this object as a backend, when available. You can create a set from
@@ -835,8 +836,8 @@ a in s # True
835
836
  [1, 2] in s # False
836
837
  ```
837
838
 
838
- This is because, as noted above, object equality is via the ```is```
839
- 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.
840
841
 
841
842
  ### Dicts
842
843
 
@@ -885,6 +886,63 @@ a = {1:1, 2:2}
885
886
  isinstance(a, dict) == False # a is a normal JavaScript object
886
887
  ```
887
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
+
888
946
  ### Dict merge literals
889
947
 
890
948
  RapydScript supports Python's `{**d1, **d2}` dict merge (spread) syntax.
@@ -1173,7 +1231,8 @@ semantics:
1173
1231
  | other `float` | derived from the bit pattern |
1174
1232
  | `str` | djb2 algorithm — stable within a process |
1175
1233
  | object with `__hash__` | dispatches to `__hash__()` |
1176
- | 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) |
1177
1236
  | `list` | `TypeError: unhashable type: 'list'` |
1178
1237
  | `set` | `TypeError: unhashable type: 'set'` |
1179
1238
  | `dict` | `TypeError: unhashable type: 'dict'` |
@@ -1193,8 +1252,67 @@ class Point:
1193
1252
  return self.x * 31 + self.y
1194
1253
 
1195
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'
1196
1261
  ```
1197
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
+
1198
1316
  Loops
1199
1317
  -----
1200
1318
  RapydScript's loops work like Python, not JavaScript. You can't, for example
@@ -1405,6 +1523,16 @@ Case-folding for locale-insensitive lowercase comparison:
1405
1523
  str.casefold('ÄÖÜ') == str.casefold('äöü') # True (maps to lowercase)
1406
1524
  ```
1407
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
+
1408
1536
  However, if you want to make the python string methods available on string
1409
1537
  objects, there is a convenience method in the standard library to do so. Use
1410
1538
  the following code:
@@ -1612,6 +1740,7 @@ can annotate a variable with a type hint, with or without an initial value:
1612
1740
  x: int = 42
1613
1741
  name: str = "Alice"
1614
1742
  items: list = [1, 2, 3]
1743
+ coords: tuple = (10, 20)
1615
1744
 
1616
1745
  # Annotation only: declares the type without assigning a value
1617
1746
  count: int
@@ -1684,6 +1813,353 @@ re.match(///
1684
1813
  ///, 'ab')
1685
1814
  ```
1686
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
+
1687
2163
  Creating DOM trees easily
1688
2164
  ---------------------------------
1689
2165
 
@@ -1914,6 +2390,117 @@ print(Counter.get_count()) # 2
1914
2390
 
1915
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.
1916
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
+
1917
2504
  ### Nested Classes
1918
2505
 
1919
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.
@@ -2350,6 +2937,45 @@ except SomeInternalError:
2350
2937
 
2351
2938
  Basically, `try/except/finally` in RapydScript works very similar to the way it does in Python 3.
2352
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
+
2353
2979
  Scope Control
2354
2980
  -------------
2355
2981
 
@@ -2417,6 +3043,7 @@ One of Python's main strengths is the number of libraries available to the devel
2417
3043
  operator # a subset of Python's operator module
2418
3044
  functools # reduce, partial, wraps, lru_cache, cache, total_ordering, cmp_to_key
2419
3045
  collections # namedtuple, deque, Counter, OrderedDict, defaultdict
3046
+ copy # copy (shallow), deepcopy; honours __copy__ / __deepcopy__ hooks
2420
3047
  itertools # count, cycle, repeat, accumulate, chain, compress, dropwhile, filterfalse,
2421
3048
  # groupby, islice, pairwise, starmap, takewhile, zip_longest,
2422
3049
  # product, permutations, combinations, combinations_with_replacement
@@ -2661,6 +3288,31 @@ below:
2661
3288
  should use a scoped flag. See the section on dictionaries above for details.
2662
3289
 
2663
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
+
2664
3316
  Monaco Language Service
2665
3317
  -----------------------
2666
3318
 
@@ -2722,9 +3374,11 @@ when the editor is torn down.
2722
3374
  | `compiler` | object | — | **Required.** The `window.RapydScript` compiler bundle. |
2723
3375
  | `parseDelay` | number | `300` | Debounce delay (ms) before re-checking after an edit. |
2724
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. |
2725
3378
  | `dtsFiles` | `[{name, content}]` | `[]` | TypeScript `.d.ts` files loaded at startup. |
2726
3379
  | `loadDts` | `(name) => Promise<string>` | — | Async callback for lazy-loading `.d.ts` content on demand. |
2727
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. |
2728
3382
 
2729
3383
  ### Runtime API
2730
3384
 
@@ -2744,6 +3398,9 @@ service.loadDts('lib.dom').then(function () { console.log('DOM types loaded'); }
2744
3398
  // Suppress undefined-symbol warnings for additional global names
2745
3399
  service.addGlobals(['myFrameworkGlobal', '$']);
2746
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
+
2747
3404
  // Tear down all Monaco providers and event listeners
2748
3405
  service.dispose();
2749
3406
  ```
@@ -2867,6 +3524,13 @@ original `.py` file with working breakpoints and correct error stack frames.
2867
3524
  | `keep_docstrings` | bool | `false` | Keep docstrings in the output. |
2868
3525
  | `js_version` | number | `6` | Target ECMAScript version (5 or 6). |
2869
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. |
2870
3534
 
2871
3535
  **How it works**
2872
3536