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 +3 -2
- package/PYTHON_FEATURE_COVERAGE.md +109 -0
- package/README.md +320 -34
- package/TODO.md +17 -48
- package/hack_demo.pyj +112 -0
- package/package.json +7 -5
- package/src/ast.pyj +30 -6
- package/src/baselib-builtins.pyj +104 -10
- package/src/baselib-containers.pyj +146 -1
- package/src/baselib-errors.pyj +3 -0
- package/src/baselib-internal.pyj +10 -0
- package/src/baselib-str.pyj +34 -0
- package/src/monaco-language-service/analyzer.js +1 -13
- package/src/monaco-language-service/builtins.js +10 -0
- package/src/monaco-language-service/completions.js +54 -2
- package/src/monaco-language-service/diagnostics.js +54 -4
- package/src/monaco-language-service/index.js +9 -5
- package/src/output/codegen.pyj +37 -2
- package/src/output/functions.pyj +31 -9
- package/src/output/loops.pyj +64 -2
- package/src/output/operators.pyj +53 -1
- package/src/output/statements.pyj +7 -3
- package/src/output/stream.pyj +6 -0
- package/src/parse.pyj +77 -13
- package/src/tokenizer.pyj +1 -0
- package/test/python_features.pyj +1184 -0
- package/test/unit/language-service-bundle.js +83 -0
- package/test/unit/language-service-completions.js +109 -0
- package/test/unit/language-service.js +123 -1
- package/test/unit/run-language-service.js +1 -0
- package/tools/lint.js +1 -1
- package/tools/self.js +1 -9
- package/web-repl/language-service.js +129 -26
- package/web-repl/rapydscript.js +3 -3
package/CONTRIBUTORS
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
This project is officially supported by
|
|
1
|
+
This project is officially supported by Michael Ficocelli.
|
|
2
2
|
|
|
3
3
|
Main Developers
|
|
4
4
|
---------------
|
|
5
|
-
|
|
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
|
-
[](https://snyk.io/test/github/kovidgoyal/rapydscript-ng)
|
|
5
|
+
[](https://github.com/ficocelliguy/rapydscript-ns/actions?query=workflow%3ACI)
|
|
6
|
+
[](https://www.npmjs.com/package/rapydscript-ns)
|
|
7
|
+
[](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-
|
|
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-
|
|
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-
|
|
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-
|
|
128
|
+
npm install rapydscript-ns
|
|
130
129
|
|
|
131
130
|
From Git:
|
|
132
131
|
|
|
133
|
-
git clone https://github.com/
|
|
134
|
-
cd rapydscript-
|
|
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
|
-
|
|
1760
|
-
|
|
1761
|
-
``
|
|
1762
|
-
|
|
1763
|
-
|
|
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
|
|
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
|
-
|
|
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://
|
|
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://
|
|
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
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
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
|
-
|
|
2509
|
-
|
|
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
|
-
|
|
2512
|
-
|
|
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
|