rapydscript-ns 0.8.0 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CONTRIBUTORS CHANGED
@@ -1,11 +1,12 @@
1
- This project is officially supported by Kovid Goyal.
1
+ This project is officially supported by Michael Ficocelli.
2
2
 
3
3
  Main Developers
4
4
  ---------------
5
- Kovid Goyal
5
+ Michael Ficocelli
6
6
 
7
7
  Other Contributors
8
8
  ------------------
9
+ Kovid Goyal (Previous main developer, developer of rapydscript-ng)
9
10
  Alexander Tsepkov (main developer of the original RapydScript)
10
11
  Charles Law
11
12
  Tobias Weber
@@ -0,0 +1,109 @@
1
+ # Python Feature Coverage Report — RapydScript-NS
2
+
3
+ ## ✅ Fully Supported
4
+
5
+ | Feature | Notes |
6
+ |---|---|
7
+ | `super()` — 0-arg and 2-arg forms | `super().method()` and `super(Cls, self).method()` both work |
8
+ | `except TypeA, TypeB as e:` | RapydScript comma-separated form; catches multiple exception types |
9
+ | `except (TypeA, TypeError) as e:` | Tuple form also supported |
10
+ | `try / else` | `else` block runs only when no exception was raised |
11
+ | `for / else` | `else` block runs when loop completes without `break`; nested break isolation works |
12
+ | `while / else` | `else` block runs when loop condition becomes `False` without a `break`; nested `break` isolation correct |
13
+ | `with A() as a, B() as b:` | Multiple context managers in one statement; exits in LIFO order (Python-correct) |
14
+ | `callable(fn)` | Works for plain functions and objects with `__call__` |
15
+ | `round(x, ndigits=0)` | Full Python semantics including negative `ndigits` |
16
+ | `enumerate(iterable, start=0)` | `start` parameter supported |
17
+ | `str.isspace()`, `str.islower()`, `str.isupper()` | Working string predicates |
18
+ | `str.isalpha()` | Regex-based; empty string returns `False` |
19
+ | `str.isdigit()` | Regex-based (`\d+`) |
20
+ | `str.isalnum()` | Regex-based |
21
+ | `str.isidentifier()` | Checks `^[a-zA-Z_][a-zA-Z0-9_]*$` |
22
+ | `str.casefold()` | Maps to `.toLowerCase()` |
23
+ | `str.removeprefix(prefix)` | Returns unchanged string if prefix not found |
24
+ | `str.removesuffix(suffix)` | Returns unchanged string if suffix not found |
25
+ | `str * n` string repetition | Works when `from __python__ import overload_operators` is active |
26
+ | `list * n` / `n * list` | Works with `overload_operators`; returns a proper RapydScript list |
27
+ | `match / case` | Structural pattern matching (Python 3.10) fully supported |
28
+ | Generator `.throw()` | Works via JS generator protocol |
29
+ | Generator `.send()` | Works via `g.next(value)` |
30
+ | `yield from` | Works; return value of sub-generator is not accessible |
31
+ | `+=`, `-=`, `*=`, `/=`, `//=`, `**=`, `%=`, `&=`, `\|=`, `^=`, `<<=`, `>>=` | All augmented assignments work |
32
+ | `raise X from Y` exception chaining | Sets `__cause__` on the thrown exception; `from None` also supported |
33
+ | Starred assignment `a, *b, c = ...` | Works |
34
+ | `@classmethod`, `@staticmethod`, `@property` / `@prop.setter` | All work |
35
+ | `{**dict1, **dict2}` dict spread | Works as merge replacement for the missing `\|` operator |
36
+ | `dict.fromkeys()` | Works with `dict_literals` flag |
37
+ | Chained comparisons `a < b < c` and `a < b > c` | Same-direction and mixed-direction chains both work; middle operand evaluated once |
38
+ | `for`, `while`, `try/except/finally`, `with`, `match/case` | All control-flow constructs work |
39
+ | Classes, inheritance, decorators, `__dunder__` methods | Fully supported |
40
+ | List / dict / set comprehensions, generator expressions | Fully supported |
41
+ | f-strings, `str.format()`, all common `str.*` methods | Fully supported |
42
+ | `abs()`, `divmod()`, `any()`, `all()`, `sum()`, `min()`, `max()` | All work |
43
+ | `sorted()`, `reversed()`, `zip()`, `map()`, `filter()` | All work |
44
+ | `set` with full union/intersection/difference API | Fully supported |
45
+ | `isinstance()`, `hasattr()`, `getattr()`, `setattr()`, `dir()` | All work |
46
+ | `bin()`, `hex()`, `oct()`, `chr()`, `ord()` | All work |
47
+ | `int(x, base)`, `float(x)` with ValueError on bad input | Works |
48
+ | `lambda` keyword | Full support: args, defaults, `*args`, ternary body, closures, nesting |
49
+ | Arithmetic operator overloading — `__add__`, `__sub__`, `__mul__`, `__truediv__`, `__floordiv__`, `__mod__`, `__pow__`, `__neg__`, `__pos__`, `__abs__`, `__invert__`, `__lshift__`, `__rshift__`, `__and__`, `__or__`, `__xor__`, `__radd__`, `__iadd__` etc. | Dispatched when `from __python__ import overload_operators` is active; comparison operators (`<`, `>`, `<=`, `>=`) are **not** re-dispatched — call `obj.__lt__(other)` directly |
50
+ | Nested comprehensions (multi-`for` clause) | `[x for row in matrix for x in row if cond]`; works for list, set, and dict comprehensions |
51
+ | Positional-only parameters `def f(a, b, /):` | Full support — parser enforces placement; runtime passes positional args correctly |
52
+ | Keyword-only parameters `def f(a, *, b):` | Full support — bare `*` separator enforced; `b` must be passed as keyword |
53
+ | Walrus operator `:=` | Fully supported: hoisted in `if`/`while` conditions at any scope; comprehension filter assigns to enclosing scope (Python-correct). |
54
+ | `__call__` dunder dispatch | `obj()` dispatches to `obj.__call__(args)` for callable objects; `callable(obj)` also returns `True`; both forms work. Requires `from __python__ import truthiness`. |
55
+ | **Truthiness / `__bool__`** | Full Python truthiness via `from __python__ import truthiness`: empty `[]`, `{}`, `set()`, `''` are falsy; `__bool__` is dispatched; `and`/`or` return operand values; `not`, `if`, `while`, `assert`, ternary all use `ρσ_bool()`. |
56
+ | `frozenset(iterable)` | Immutable set: construction from list/set/iterable; `in`, `len()`, iteration, `copy()`, `union()`, `intersection()`, `difference()`, `symmetric_difference()`, `issubset()`, `issuperset()`, `isdisjoint()` — all return `frozenset`. `isinstance(x, frozenset)` works. Compares equal to a `set` with the same elements via `__eq__`. No mutation methods (`add`, `remove`, etc.). |
57
+ | `issubclass(cls, classinfo)` | Checks prototype chain; `classinfo` may be a class or tuple of classes; every class is a subclass of itself; raises `TypeError` for non-class arguments. |
58
+ | `hash(obj)` | Numbers hash by value (int identity, float → int form if whole); strings use djb2; `None` → 0; booleans → 0/1; objects with `__hash__` dispatch to it; class instances get a stable identity hash; `list`, `set`, `dict` raise `TypeError`. |
59
+ | `next(iterator[, default])` | Advances a JS-protocol iterator (`{done, value}`); returns `default` when exhausted if provided, otherwise raises `StopIteration`. Works with `iter()`, `range()`, `enumerate()`, generators, and any object with a `.next()` or `__next__()` method. |
60
+ | `StopIteration` exception | Defined as a builtin exception class; raised by `next()` when an iterator is exhausted and no default is given. |
61
+ | `dict \| dict` and `dict \|= dict` (Python 3.9+) | Dict merge via `\|` creates a new merged dict (right-side values win); `\|=` updates in-place. Requires `from __python__ import overload_operators, dict_literals`. |
62
+
63
+ ---
64
+
65
+ ## ❌ Not Supported — Missing from Baselib (runtime)
66
+
67
+ | Feature | Priority |
68
+ |-------------------------------------|------------------------------------------------------------------------|
69
+ | `format(value[, spec])` | 🟢 Low — not a builtin; `str.format()` and f-strings work |
70
+ | `iter(callable, sentinel)` | 🟢 Low — two-arg form not supported; single-arg `iter(iterable)` works |
71
+ | `slice(start, stop[, step])` | 🟢 Low — not a builtin object; list slicing syntax `a[1:5:2]` works |
72
+ | `complex(real, imag)` | 🟢 Low — no complex number type |
73
+ | `vars()` / `locals()` / `globals()` | 🟢 Low — not defined; use direct attribute access |
74
+ | `str.expandtabs(tabsize)` | 🟢 Low |
75
+ | `int.bit_length()` | 🟢 Low — useful for bit manipulation |
76
+ | `float.is_integer()` | 🟢 Low |
77
+
78
+ ---
79
+
80
+ ## ❌ Not Supported — Parser / Syntax Level
81
+
82
+ | Feature | Priority |
83
+ |-----------------------------------------------|----------------------------------------------------------------------|
84
+ | `zip(strict=True)` | 🟢 Low |
85
+ | Nested class definitions | 🟢 Low — noted as not yet fully implemented |
86
+ | `__slots__` enforcement | 🟢 Low — accepted but does not restrict attribute assignment |
87
+ | Complex number literals `3+4j` | 🟢 Low — no `j` suffix; no complex type |
88
+ | `b'...'` bytes literals | 🟢 Low — no `b` prefix; use the `encodings` module for encoding work |
89
+ | `except*` (exception groups, Python 3.11+) | 🟢 Low — no parser support |
90
+ | `__new__` constructor hook | 🟢 Low — no alternative constructor support |
91
+ | `__del__` destructor / finalizer | 🟢 Low — JS has no guaranteed finalizer |
92
+ | `__hash__` dunder | 🟢 Low — not dispatched; set/dict use `===` object identity |
93
+ | `__getattr__` / `__setattr__` / `__delattr__` | 🟢 Low — no attribute-access interception |
94
+ | `__getattribute__` | 🟢 Low — no attribute-lookup override |
95
+ | `__format__` dunder | 🟢 Low — `format()` builtin not defined; `__format__` not dispatched |
96
+ | `__class_getitem__` | 🟢 Low — no `MyClass[T]` generic subscript syntax |
97
+ | `__init_subclass__` hook | 🟢 Low |
98
+
99
+ ---
100
+
101
+ ## Test File
102
+
103
+ `test/python_features.pyj` contains runnable assertions for all features surveyed.
104
+ Features that are not supported have their test code commented out with a `# SKIP:` label
105
+ and an explanation. Run with:
106
+
107
+ ```sh
108
+ node bin/rapydscript test python_features
109
+ ```
package/README.md CHANGED
@@ -2,15 +2,14 @@ RapydScript
2
2
  ===========
3
3
 
4
4
 
5
- [![Build Status](https://github.com/ebook-utils/kovidgoyal/rapydscript-ng/CI/badge.svg)](https://github.com/kovidgoyal/rapydscript-ng/actions?query=workflow%3ACI)
6
- [![Downloads](https://img.shields.io/npm/dm/rapydscript-ng.svg)](https://www.npmjs.com/package/rapydscript-ng)
7
- [![Current Release](https://img.shields.io/npm/v/rapydscript-ng.svg)](https://www.npmjs.com/package/rapydscript-ng)
8
- [![Known Vulnerabilities](https://snyk.io/test/github/kovidgoyal/rapydscript-ng/badge.svg)](https://snyk.io/test/github/kovidgoyal/rapydscript-ng)
5
+ [![Build Status](https://github.com/ficocelliguy/rapydscript-ns/actions/workflows/ci.yml/badge.svg)](https://github.com/ficocelliguy/rapydscript-ns/actions?query=workflow%3ACI)
6
+ [![Current Release](https://img.shields.io/npm/v/rapydscript-ns)](https://www.npmjs.com/package/rapydscript-ns)
7
+ [![Known Vulnerabilities](https://snyk.io/test/github/ficocelliguy/rapydscript-ns/badge.svg)](https://snyk.io/test/github/ficocelliguy/rapydscript-ns)
9
8
 
10
9
  This is a fork of the original RapydScript that adds many new (not always
11
10
  backwards compatible) features. For more on the forking, [see the bottom of this file](#reasons-for-the-fork)
12
11
 
13
- [Try RapydScript-ng live via an in-browser REPL!](https://sw.kovidgoyal.net/rapydscript/repl/)
12
+ [Try RapydScript-ns live via an in-browser REPL!](https://ficocelliguy.github.io/rapydscript-ns/)
14
13
 
15
14
  <!-- START doctoc generated TOC please keep comment here to allow auto update -->
16
15
  <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
@@ -116,22 +115,22 @@ learn a new language/framework is to dive in.
116
115
  Installation
117
116
  ------------
118
117
 
119
- [Try RapydScript-ng live via an in-browser REPL!](https://sw.kovidgoyal.net/rapydscript/repl/)
118
+ [Try RapydScript-ns live via an in-browser REPL!](https://ficocelliguy.github.io/rapydscript-ns/)
120
119
 
121
120
  First make sure you have installed the latest version of [node.js](https://nodejs.org/) (You may need to restart your computer after this step).
122
121
 
123
122
  From NPM for use as a command line app:
124
123
 
125
- npm install rapydscript-ng -g
124
+ npm install rapydscript-ns -g
126
125
 
127
126
  From NPM for use in your own node project:
128
127
 
129
- npm install rapydscript-ng
128
+ npm install rapydscript-ns
130
129
 
131
130
  From Git:
132
131
 
133
- git clone https://github.com/kovidgoyal/rapydscript-ng.git
134
- cd rapydscript-ng
132
+ git clone https://github.com/ficocelliguy/rapydscript-ns.git
133
+ cd rapydscript-ns
135
134
  sudo npm link .
136
135
  npm install # This will automatically install the dependencies for RapydScript
137
136
 
@@ -727,7 +726,7 @@ Keywords:
727
726
  Operators:
728
727
 
729
728
  RapydScript JavaScript
730
-
729
+
731
730
  and &&
732
731
  or ||
733
732
  not !
@@ -736,7 +735,10 @@ Operators:
736
735
  +=1 ++
737
736
  -=1 --
738
737
  ** Math.pow()
739
-
738
+ **= x = Math.pow(x, y)
739
+
740
+ All Python augmented assignment operators are supported: `+=`, `-=`, `*=`, `/=`, `//=`, `**=`, `%=`, `>>=`, `<<=`, `|=`, `^=`, `&=`.
741
+
740
742
  Admittedly, `is` is not exactly the same thing in Python as `===` in JavaScript, but JavaScript is quirky when it comes to comparing objects anyway.
741
743
 
742
744
 
@@ -895,6 +897,30 @@ merged = {**pd1, **pd2} # isinstance(merged, dict) == True
895
897
  The spread items are translated using `Object.assign` for plain JS objects
896
898
  and `dict.update()` for Python dicts.
897
899
 
900
+ ### Dict merge operator `|` and `|=` (Python 3.9+)
901
+
902
+ When `from __python__ import overload_operators, dict_literals` is active,
903
+ Python dicts support the `|` (merge) and `|=` (update in-place) operators:
904
+
905
+ ```py
906
+ from __python__ import overload_operators, dict_literals
907
+
908
+ d1 = {'x': 1, 'y': 2}
909
+ d2 = {'y': 99, 'z': 3}
910
+
911
+ # Create a new merged dict — right-side values win on key conflict
912
+ merged = d1 | d2 # {'x': 1, 'y': 99, 'z': 3}
913
+
914
+ # Update d1 in place
915
+ d1 |= d2 # d1 is now {'x': 1, 'y': 99, 'z': 3}
916
+ ```
917
+
918
+ `d1 | d2` creates a new dict (neither operand is mutated).
919
+ `d1 |= d2` merges `d2` into `d1` and returns `d1`.
920
+
921
+ Without `overload_operators` the `|` symbol is bitwise OR — use
922
+ `{**d1, **d2}` spread syntax as a flag-free alternative.
923
+
898
924
 
899
925
  ### Arithmetic operator overloading
900
926
 
@@ -949,6 +975,16 @@ to the native JavaScript operator, so plain numbers, strings, and booleans
949
975
  continue to work as expected with no performance penalty when no dunder method
950
976
  is defined.
951
977
 
978
+ When `overload_operators` is active, string and list repetition with `*` works just like Python:
979
+
980
+ ```py
981
+ from __python__ import overload_operators
982
+ 'ha' * 3 # 'hahaha'
983
+ 3 * 'ha' # 'hahaha'
984
+ [0] * 4 # [0, 0, 0, 0]
985
+ [1, 2] * 2 # [1, 2, 1, 2]
986
+ ```
987
+
952
988
  Because the dispatch adds one or two property lookups per operation, the flag
953
989
  is **opt-in** rather than always-on. Enable it only in the files or scopes
954
990
  where you need it.
@@ -978,7 +1014,166 @@ for your own types.
978
1014
 
979
1015
  RapydScript does not overload the ordering operators ```(>, <, >=,
980
1016
  <=)``` as doing so would be a big performance impact (function calls in
981
- JavaScript are very slow). So using them on containers is useless.
1017
+ JavaScript are very slow). So using them on containers is useless.
1018
+
1019
+ Chained comparisons work just like Python — each middle operand is evaluated only once:
1020
+
1021
+ ```py
1022
+ # All of these work correctly, including mixed-direction chains
1023
+ assert 1 < 2 < 3 # True
1024
+ assert 1 < 2 > 0 # True (1<2 AND 2>0)
1025
+ assert 1 < 2 > 3 == False # 1<2 AND 2>3 = True AND False = False
1026
+ ```
1027
+
1028
+ ### Python Truthiness and `__bool__`
1029
+
1030
+ By default RapydScript uses JavaScript truthiness, where empty arrays `[]` and
1031
+ empty objects `{}` are **truthy**. Activate full Python truthiness semantics
1032
+ with:
1033
+
1034
+ ```py
1035
+ from __python__ import truthiness
1036
+ ```
1037
+
1038
+ When this flag is active:
1039
+
1040
+ - **Empty containers are falsy**: `[]`, `{}`, `set()`, `''`, `0`, `None` are all falsy.
1041
+ - **`__bool__` is dispatched**: objects with a `__bool__` method control their truthiness.
1042
+ - **`__len__` is used**: objects with `__len__` are falsy when `len(obj) == 0`.
1043
+ - **`and`/`or` return operand values** (not booleans), just like Python.
1044
+ - **All condition positions** (`if`, `while`, `assert`, `not`, ternary) use Python semantics.
1045
+
1046
+ ```py
1047
+ from __python__ import truthiness
1048
+
1049
+ class Empty:
1050
+ def __bool__(self): return False
1051
+
1052
+ if not []: # True — [] is falsy
1053
+ print('empty')
1054
+
1055
+ x = [] or 'default' # x == 'default'
1056
+ y = [1] or 'default' # y == [1]
1057
+ z = [1] and 'ok' # z == 'ok'
1058
+ ```
1059
+
1060
+ The flag is **scoped** — it applies until the end of the enclosing
1061
+ function or class body. Use `from __python__ import no_truthiness` to
1062
+ disable it in a sub-scope.
1063
+
1064
+ ### Callable Objects (`__call__`)
1065
+
1066
+ Any class that defines `__call__` can be invoked directly with `obj(args)`,
1067
+ just like Python callable objects. This requires `from __python__ import truthiness`:
1068
+
1069
+ ```python
1070
+ from __python__ import truthiness
1071
+
1072
+ class Multiplier:
1073
+ def __init__(self, factor):
1074
+ self.factor = factor
1075
+ def __call__(self, x):
1076
+ return self.factor * x
1077
+
1078
+ triple = Multiplier(3)
1079
+ triple(7) # 21 — dispatches to triple.__call__(7)
1080
+ ```
1081
+
1082
+ `callable(obj)` returns `True` when `__call__` is defined. The dispatch is
1083
+ automatic for all direct function-call expressions that are simple names
1084
+ (i.e. not method accesses like `obj.method()`).
1085
+
1086
+ ### `frozenset`
1087
+
1088
+ RapydScript provides a full `frozenset` builtin — an immutable, unordered
1089
+ collection of unique elements, identical to Python's `frozenset`.
1090
+
1091
+ ```python
1092
+ fs = frozenset([1, 2, 3])
1093
+ len(fs) # 3
1094
+ 2 in fs # True
1095
+ isinstance(fs, frozenset) # True
1096
+
1097
+ # Set operations return new frozensets
1098
+ a = frozenset([1, 2, 3])
1099
+ b = frozenset([2, 3, 4])
1100
+ a.union(b) # frozenset({1, 2, 3, 4})
1101
+ a.intersection(b) # frozenset({2, 3})
1102
+ a.difference(b) # frozenset({1})
1103
+ a.symmetric_difference(b) # frozenset({1, 4})
1104
+
1105
+ a.issubset(frozenset([1, 2, 3, 4])) # True
1106
+ a.issuperset(frozenset([1, 2])) # True
1107
+ a.isdisjoint(frozenset([5, 6])) # True
1108
+
1109
+ # Compares equal to a set with the same elements
1110
+ frozenset([1, 2]).__eq__({1, 2}) # True
1111
+ ```
1112
+
1113
+ Mutation methods (`add`, `remove`, `discard`, `clear`, `update`) are not
1114
+ present on `frozenset` instances, enforcing immutability at the API level.
1115
+ `frozenset` objects can be iterated and copied with `.copy()`.
1116
+
1117
+ ### `issubclass`
1118
+
1119
+ `issubclass(cls, classinfo)` checks whether a class is a subclass of another
1120
+ class (or any class in a tuple of classes). Every class is considered a
1121
+ subclass of itself.
1122
+
1123
+ ```python
1124
+ class Animal: pass
1125
+ class Dog(Animal): pass
1126
+ class Poodle(Dog): pass
1127
+ class Cat(Animal): pass
1128
+
1129
+ issubclass(Dog, Animal) # True
1130
+ issubclass(Poodle, Animal) # True — transitive
1131
+ issubclass(Poodle, Dog) # True
1132
+ issubclass(Dog, Dog) # True — a class is its own subclass
1133
+ issubclass(Cat, Dog) # False
1134
+ issubclass(Animal, Dog) # False — parent is not a subclass of child
1135
+
1136
+ # tuple form — True if cls is a subclass of any entry
1137
+ issubclass(Dog, (Cat, Animal)) # True
1138
+ issubclass(Poodle, (Cat, Dog)) # True
1139
+ ```
1140
+
1141
+ `TypeError` is raised when either argument is not a class.
1142
+
1143
+ ### `hash`
1144
+
1145
+ `hash(obj)` returns an integer hash value for an object, following Python
1146
+ semantics:
1147
+
1148
+ | Type | Hash rule |
1149
+ |---|---|
1150
+ | `None` | `0` |
1151
+ | `bool` | `1` for `True`, `0` for `False` |
1152
+ | `int` / whole `float` | the integer value itself |
1153
+ | other `float` | derived from the bit pattern |
1154
+ | `str` | djb2 algorithm — stable within a process |
1155
+ | object with `__hash__` | dispatches to `__hash__()` |
1156
+ | class instance | stable identity hash (assigned on first call) |
1157
+ | `list` | `TypeError: unhashable type: 'list'` |
1158
+ | `set` | `TypeError: unhashable type: 'set'` |
1159
+ | `dict` | `TypeError: unhashable type: 'dict'` |
1160
+
1161
+ ```python
1162
+ hash(None) # 0
1163
+ hash(True) # 1
1164
+ hash(42) # 42
1165
+ hash(3.0) # 3 (whole float → same as int)
1166
+ hash('hello') # stable integer
1167
+
1168
+ class Point:
1169
+ def __init__(self, x, y):
1170
+ self.x = x
1171
+ self.y = y
1172
+ def __hash__(self):
1173
+ return self.x * 31 + self.y
1174
+
1175
+ hash(Point(1, 2)) # 33
1176
+ ```
982
1177
 
983
1178
  Loops
984
1179
  -----
@@ -1000,6 +1195,39 @@ for index, animal in enumerate(animals):
1000
1195
  print("index:"+index, "animal:"+animal)
1001
1196
  ```
1002
1197
 
1198
+ `enumerate()` supports an optional `start` argument (default 0):
1199
+
1200
+ ```py
1201
+ for index, animal in enumerate(animals, 1):
1202
+ print(str(index) + '. ' + animal) # 1-based numbering
1203
+ ```
1204
+
1205
+ Like in Python, `for` loops support an `else` clause that runs only when the loop completes without hitting a `break`:
1206
+
1207
+ ```py
1208
+ for animal in animals:
1209
+ if animal == 'cat':
1210
+ print('found a cat')
1211
+ break
1212
+ else:
1213
+ print('no cat found')
1214
+ ```
1215
+
1216
+ This is useful for search patterns where you want to take an action only if the searched item was not found.
1217
+
1218
+ `while` loops also support an `else` clause, which runs when the condition becomes `False` (i.e. no `break` was executed):
1219
+
1220
+ ```py
1221
+ i = 0
1222
+ while i < len(items):
1223
+ if items[i] == target:
1224
+ print('found at', i)
1225
+ break
1226
+ i += 1
1227
+ else:
1228
+ print('not found')
1229
+ ```
1230
+
1003
1231
  Like in Python, if you just want the index, you can use range:
1004
1232
 
1005
1233
  ```py
@@ -1118,6 +1346,31 @@ str.format('{0:02d} {n}', 1, n=2) == '01 2'
1118
1346
  ...
1119
1347
  ```
1120
1348
 
1349
+ String predicate methods are also available:
1350
+
1351
+ ```py
1352
+ str.isalpha('abc') # True — all alphabetic
1353
+ str.isdigit('123') # True — all digits
1354
+ str.isalnum('abc123') # True — alphanumeric
1355
+ str.isspace(' ') # True — all whitespace
1356
+ str.isupper('ABC') # True
1357
+ str.islower('abc') # True
1358
+ str.isidentifier('my_var') # True — valid Python identifier
1359
+ ```
1360
+
1361
+ Python 3.9 prefix/suffix removal:
1362
+
1363
+ ```py
1364
+ str.removeprefix('HelloWorld', 'Hello') # 'World'
1365
+ str.removesuffix('HelloWorld', 'World') # 'Hello'
1366
+ ```
1367
+
1368
+ Case-folding for locale-insensitive lowercase comparison:
1369
+
1370
+ ```py
1371
+ str.casefold('ÄÖÜ') == str.casefold('äöü') # True (maps to lowercase)
1372
+ ```
1373
+
1121
1374
  However, if you want to make the python string methods available on string
1122
1375
  objects, there is a convenience method in the standard library to do so. Use
1123
1376
  the following code:
@@ -1756,11 +2009,25 @@ an ``__iter__`` method, just as you would in python. For example:
1756
2009
  print (x) # Will print 1, 2, 3
1757
2010
  ```
1758
2011
 
1759
- Note that unlike python, an iterators ``next()`` method does not return
1760
- the next value, but instead an object with two properties: ``done and value``.
1761
- ``value`` is the next value and done will be ``True`` when the iterator is
1762
- exhausted. No ``StopIteration`` exception is raised. These choices were
1763
- made so that the iterator works with other JavaScript code.
2012
+ Internally, an iterator's ``.next()`` method returns a JavaScript object with
2013
+ two properties: ``done`` and ``value``. ``value`` is the next value and
2014
+ ``done`` is ``True`` when the iterator is exhausted. This matches the
2015
+ JavaScript iterator protocol and allows interoperability with vanilla JS code.
2016
+
2017
+ RapydScript also provides the Python-style ``next()`` builtin, which wraps
2018
+ this protocol transparently:
2019
+
2020
+ ```python
2021
+ it = iter([1, 2, 3])
2022
+ next(it) # 1
2023
+ next(it) # 2
2024
+ next(it, 'end') # 3
2025
+ next(it, 'end') # 'end' (iterator exhausted, default returned)
2026
+ next(it) # raises StopIteration
2027
+ ```
2028
+
2029
+ When the iterator is exhausted and no default is given, ``StopIteration``
2030
+ is raised — matching standard Python behaviour.
1764
2031
 
1765
2032
  Generators
1766
2033
  ------------
@@ -1935,7 +2202,7 @@ class MyError(Exception):
1935
2202
  raise MyError('This is a custom error!')
1936
2203
  ```
1937
2204
 
1938
- You can lump multiple errors in the same except block as well:
2205
+ You can catch multiple exception types in one `except` clause. Both the comma form and the tuple form are supported:
1939
2206
 
1940
2207
  ```py
1941
2208
  try:
@@ -1943,9 +2210,31 @@ try:
1943
2210
  except ReferenceError, TypeError as e:
1944
2211
  print(e.name + ':' + e.message)
1945
2212
  raise # re-raise the exception
2213
+
2214
+ # Equivalent tuple form (Python 3 style):
2215
+ try:
2216
+ risky()
2217
+ except (ReferenceError, TypeError) as e:
2218
+ handle(e)
2219
+ ```
2220
+
2221
+ Exception chaining with `raise X from Y` is supported. The cause is stored on the raised exception's `__cause__` attribute:
2222
+
2223
+ ```py
2224
+ try:
2225
+ open_file('data.txt')
2226
+ except OSError as exc:
2227
+ raise ValueError('Could not read config') from exc
1946
2228
  ```
1947
2229
 
1948
- Basically, `try/except/finally` in RapydScript works very similar to the way it does in Python 3.
2230
+ Use `raise X from None` to explicitly suppress the chained context:
2231
+
2232
+ ```py
2233
+ except SomeInternalError:
2234
+ raise PublicError('something went wrong') from None
2235
+ ```
2236
+
2237
+ Basically, `try/except/finally` in RapydScript works very similar to the way it does in Python 3.
1949
2238
 
1950
2239
  Scope Control
1951
2240
  -------------
@@ -2114,7 +2403,7 @@ may be more verbose:
2114
2403
 
2115
2404
  You can embed the RapydScript compiler in your webpage so that you can have
2116
2405
  your webapp directly compile user supplied RapydScript code into JavaScript.
2117
- To do so, simply include the [embeddable rapydscript compiler](https://sw.kovidgoyal.net/rapydscript/repl/rapydscript.js)
2406
+ To do so, simply include the [embeddable rapydscript compiler](https://github.com/ficocelliguy/rapydscript-ns/blob/master/web-repl/rapydscript.js)
2118
2407
  in your page, and use it to compile arbitrary RapydScript code.
2119
2408
 
2120
2409
  You create the compiler by calling: `RapydScript.create_embedded_compiler()` and compile
@@ -2128,7 +2417,7 @@ HTML below for an example.
2128
2417
  <head>
2129
2418
  <meta charset="UTF-8">
2130
2419
  <title>Test embedded RapydScript</title>
2131
- <script charset="UTF-8" src="https://sw.kovidgoyal.net/rapydscript/repl/rapydscript.js"></script>
2420
+ <script charset="UTF-8" src="https://github.com/ficocelliguy/rapydscript-ns/blob/master/web-repl/rapydscript.js"></script>
2132
2421
  <script>
2133
2422
  var compiler = RapydScript.create_embedded_compiler();
2134
2423
  var js = compiler.compile("def hello_world():\n a='RapydScript is cool!'\n print(a)\n alert(a)");
@@ -2496,17 +2785,14 @@ completions, signature help, hover, DTS registry, and built-in stubs).
2496
2785
  Reasons for the fork
2497
2786
  ----------------------
2498
2787
 
2499
- The fork was initially created because the original developer of RapydScript
2500
- did not have the time to keep up with the pace of development. Since then,
2501
- development on the original RapydScript seems to have stalled completely.
2502
- Also, there are certain disagreements on the future direction of RapydScript.
2503
-
2504
- Regardless, this fork is not a hostile fork, if development on the original
2505
- ever resumes, they are welcome to use the code from this fork. I have kept all
2506
- new code under the same license, to make that possible.
2788
+ The fork was created because both the original developer of RapydScript
2789
+ and the developer of the prior fork rapydscript-ng both did not have
2790
+ the time to keep up with the pace of development. Rapydscript has not had
2791
+ any npm updates since 2020, and rapydscript-ng since 2022.
2507
2792
 
2508
- See the [Changelog](https://github.com/kovidgoyal/rapydscript-ng/blob/master/CHANGELOG.md)
2509
- for a list of changes to rapydscript-ng since the fork.
2793
+ This fork is not a hostile fork - if development on the prior versions
2794
+ ever resumes, they are welcome to use the code from this fork. All the
2795
+ new code is under the same license, to make that possible.
2510
2796
 
2511
- For some discussion surrounding the fork, see
2512
- [this bug report](https://github.com/kovidgoyal/rapydscript-ng/issues/15)
2797
+ See the [Changelog](https://github.com/ficocelliguy/rapydscript-ns/blob/master/CHANGELOG.md)
2798
+ for a list of changes to rapydscript-ns, including this fork at version 8.0