rapydscript-ns 0.8.1 → 0.8.3
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/CHANGELOG.md +31 -0
- package/CONTRIBUTORS +3 -2
- package/PYTHON_DIFFERENCES_REPORT.md +291 -0
- package/PYTHON_FEATURE_COVERAGE.md +200 -0
- package/README.md +480 -79
- package/TODO.md +6 -318
- package/hack_demo.pyj +112 -0
- package/language-service/index.js +4474 -0
- package/language-service/language-service.d.ts +40 -0
- package/package.json +9 -10
- package/src/ast.pyj +30 -6
- package/src/baselib-builtins.pyj +181 -11
- package/src/baselib-containers.pyj +154 -5
- package/src/baselib-errors.pyj +3 -0
- package/src/baselib-internal.pyj +40 -1
- package/src/baselib-str.pyj +42 -1
- package/src/lib/collections.pyj +1 -1
- package/src/lib/numpy.pyj +10 -10
- package/src/monaco-language-service/analyzer.js +132 -22
- package/src/monaco-language-service/builtins.js +22 -2
- package/src/monaco-language-service/completions.js +224 -3
- package/src/monaco-language-service/diagnostics.js +55 -5
- package/src/monaco-language-service/index.js +26 -5
- package/src/monaco-language-service/scope.js +3 -0
- package/src/output/classes.pyj +20 -3
- package/src/output/codegen.pyj +38 -3
- package/src/output/functions.pyj +35 -25
- package/src/output/loops.pyj +64 -11
- package/src/output/modules.pyj +1 -4
- package/src/output/operators.pyj +67 -1
- package/src/output/statements.pyj +7 -3
- package/src/output/stream.pyj +6 -13
- package/src/parse.pyj +94 -14
- package/src/tokenizer.pyj +1 -0
- package/test/baselib.pyj +4 -4
- package/test/classes.pyj +56 -17
- package/test/collections.pyj +5 -5
- package/test/python_compat.pyj +326 -0
- package/test/python_features.pyj +1271 -0
- package/test/slice.pyj +105 -0
- package/test/str.pyj +25 -0
- package/test/unit/fixtures/fibonacci_expected.js +1 -1
- package/test/unit/index.js +119 -7
- package/test/unit/language-service-builtins.js +70 -0
- package/test/unit/language-service-bundle.js +83 -0
- package/test/unit/language-service-completions.js +289 -0
- package/test/unit/language-service-index.js +350 -0
- package/test/unit/language-service-scope.js +255 -0
- package/test/unit/language-service.js +158 -1
- package/test/unit/run-language-service.js +2 -0
- package/test/unit/web-repl.js +134 -0
- package/tools/build-language-service.js +2 -2
- package/tools/compiler.js +0 -24
- package/tools/export.js +3 -37
- package/tools/lint.js +1 -1
- package/tools/self.js +1 -9
- package/web-repl/rapydscript.js +6 -40
- package/web-repl/language-service.js +0 -4084
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,34 @@
|
|
|
1
|
+
version 0.8.0 - 0.8.3
|
|
2
|
+
=======================
|
|
3
|
+
|
|
4
|
+
* Add language service with tab-completion, return type inference, hover tips, and typing exports
|
|
5
|
+
* Add source maps support for the transpiled js
|
|
6
|
+
* Add `itertools` standard library module
|
|
7
|
+
* Add `collections` standard library module
|
|
8
|
+
* Add `functools` standard library module
|
|
9
|
+
* Add `numpy` standard library module
|
|
10
|
+
* Add `async`/`await` support
|
|
11
|
+
* Add lambda expression support
|
|
12
|
+
* Add structural pattern matching (`match`/`case`) support
|
|
13
|
+
* Add starred assignment support (e.g. `a, *b = [1, 2, 3]`)
|
|
14
|
+
* Add ellipsis operator and subscript tuple support
|
|
15
|
+
* Add walrus operator (`:=`) support
|
|
16
|
+
* Add dict merge literal support (`{**a, **b}`)
|
|
17
|
+
* Add positional-only (`/`) and keyword-only (`*`) parameter separators
|
|
18
|
+
* Add `@classmethod` decorator support with improved class property access
|
|
19
|
+
* Add nested comprehension support
|
|
20
|
+
* Add overloaded operator support
|
|
21
|
+
* Add Python-style truthiness for empty lists and dicts
|
|
22
|
+
* Add `super()` support
|
|
23
|
+
* Add compiler flags to enable optional pythonic behaviors
|
|
24
|
+
* Add `any()` and `all()` builtins
|
|
25
|
+
* Add tree shaking support for reduced output size
|
|
26
|
+
* Add module mode for exporting root-level functions
|
|
27
|
+
* Add support for virtual imports
|
|
28
|
+
* Add support for bundling pythonized strings into compiled output
|
|
29
|
+
* `print` now maps to `console.log`; `window.print` maps to the JS printer dialog
|
|
30
|
+
* Add custom class mapping support in `dict()`
|
|
31
|
+
|
|
1
32
|
version 0.7.22
|
|
2
33
|
=======================
|
|
3
34
|
|
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,291 @@
|
|
|
1
|
+
# RapydScript vs Python: Differences Report
|
|
2
|
+
|
|
3
|
+
This report catalogs every place in the README where RapydScript behavior
|
|
4
|
+
differs from standard Python, assesses whether each claim is still accurate,
|
|
5
|
+
and notes which items are covered by tests.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Summary Table
|
|
10
|
+
|
|
11
|
+
| # | Topic | README Claim | Accurate? | Tested? |
|
|
12
|
+
|---|-------|-------------|-----------|---------|
|
|
13
|
+
| 1 | Function argument leniency | Too few args → `undefined` instead of `TypeError` | ✓ Yes | Added (python_compat.pyj) |
|
|
14
|
+
| 2 | `*args` + optional arg duplication | Duplicate kwarg silently ignored | ✓ Yes | Added (python_compat.pyj) |
|
|
15
|
+
| 3 | Positional-only parameter enforcement | Passing by keyword silently ignored | ✓ Yes | Added (python_compat.pyj) |
|
|
16
|
+
| 4 | Keyword-only parameter enforcement | Passing positionally raises no error | ✓ Yes | Added (python_compat.pyj) |
|
|
17
|
+
| 5 | `is` operator | Maps to `===` (strict equality, not Python identity) | ✓ Yes | generic.pyj |
|
|
18
|
+
| 6 | Arithmetic: `1 + '1'` | Results in `'11'` (JS string coercion) | ✓ Yes | Added (python_compat.pyj) |
|
|
19
|
+
| 7 | List concatenation without `overload_operators` | `[1] + [1]` results in a string, not a list | ✓ Yes | Added (python_compat.pyj) |
|
|
20
|
+
| 8 | Ordering operators on containers | `<`, `>`, `<=`, `>=` use JS coercion (not element-wise) | ✓ Yes | Added (python_compat.pyj) |
|
|
21
|
+
| 9 | List `sort()` | `sort()` = Python numeric sort; JS sort available as `jssort()` | ✓ Fixed | baselib.pyj, collections.pyj, python_compat.pyj |
|
|
22
|
+
| 10 | List `pop()` | `pop()` = Python bounds-checked pop; JS pop available as `jspop()` | ✓ Fixed | baselib.pyj, collections.pyj, python_compat.pyj |
|
|
23
|
+
| 11 | Sets: no `__hash__` for custom objects | Object equality via `is` (identity); `__hash__` not dispatched for set membership | ✓ Yes | Noted in python_features.pyj #47 (skipped) |
|
|
24
|
+
| 12 | Dicts: JS object by default | Non-existent key returns `undefined` (not `KeyError`) | ✓ Yes | Added (python_compat.pyj) |
|
|
25
|
+
| 13 | Dicts: numeric keys auto-converted | Number keys become strings in JS object dicts | ✓ Yes | Added (python_compat.pyj) |
|
|
26
|
+
| 14 | Dicts: keys as attributes | Dict keys accessible as object properties | ✓ Yes | Added (python_compat.pyj) |
|
|
27
|
+
| 15 | Dicts: no `.keys()/.values()/.items()` etc. by default | JS objects lack Python dict methods without `dict_literals` flag | ✓ Yes | scoped_flags.pyj |
|
|
28
|
+
| 16 | Strings: not Python strings by default | Python string methods on `str` object, not on string instances | ✓ Yes | str.pyj |
|
|
29
|
+
| 17 | `strings()` leaves `split()`/`replace()` as JS versions | After `strings()`, `split()` and `replace()` remain JS implementations | ✓ Yes | Added (python_compat.pyj) |
|
|
30
|
+
| 18 | Strings: UTF-16 semantics | Non-BMP characters are surrogate pairs | ✓ Yes | Documented; `str.uchrs/uslice/ulen` helpers exist |
|
|
31
|
+
| 19 | Truthiness: JS semantics by default | Empty `[]` and `{}` are truthy without `truthiness` flag | ✓ Yes | python_features.pyj #40 |
|
|
32
|
+
| 20 | Method binding: not automatic | `f = obj.method; f()` loses `self` | ✓ Yes | scoped_flags.pyj |
|
|
33
|
+
| 21 | Nested classes | Fully supported — full tests in `test/classes.pyj` | ✓ Yes | classes.pyj |
|
|
34
|
+
| 22 | Multiple inheritance MRO | May differ from Python's C3 MRO in complex hierarchies | ✓ Yes | Documented |
|
|
35
|
+
| 23 | `global` keyword | Prefers outer-scope variable over module-global when both exist | ✓ Yes | Added (python_compat.pyj) |
|
|
36
|
+
| 24 | `__slots__` | Accepted syntactically but does not restrict attribute creation | ✓ Yes | Noted in python_features.pyj #23 (skipped) |
|
|
37
|
+
| 25 | Star imports | Not supported (by design) | ✓ Yes | Documented |
|
|
38
|
+
| 26 | Regex: no lookbehind | JavaScript regex engine has no lookbehind support | ✓ Yes (ES5); ES2018+ does have lookbehind, so ES6 mode may vary | regexp.pyj |
|
|
39
|
+
| 27 | Regex: MatchObject `start()`/`end()` | May return incorrect values for nested capture groups | ✓ Yes | regexp.pyj |
|
|
40
|
+
| 28 | Generators: ES5 default | Down-compiled to ES5 switch statements by default | ✓ Yes | generators.pyj |
|
|
41
|
+
| 29 | Extra keywords | All JavaScript keywords are also reserved | ✓ Yes | generic.pyj (throws tests) |
|
|
42
|
+
| 30 | `slice()` builtin | Fully supported — `slice(start, stop, step)`, `.indices()`, equality, `isinstance` all work | ✓ Yes | python_features.pyj #63, slice.pyj |
|
|
43
|
+
| 31 | `int.bit_length()` | Not supported | ✓ Yes | Noted in python_features.pyj #17 (skipped) |
|
|
44
|
+
| 32 | `float.is_integer()` | Not supported | ✓ Yes | Noted in python_features.pyj #18 (skipped) |
|
|
45
|
+
| 33 | `zip(strict=True)` | Not supported | ✓ Yes | Noted in python_features.pyj #27 (skipped) |
|
|
46
|
+
| 34 | `vars()`/`locals()`/`globals()` | Not implemented | ✓ Yes | Noted in python_features.pyj #60 (skipped) |
|
|
47
|
+
| 35 | `complex()` / `j` suffix | No complex number type | ✓ Yes | Noted in python_features.pyj #42, #59 (skipped) |
|
|
48
|
+
| 36 | `b'...'` bytes literals | No bytes type; use `encodings` module | ✓ Yes | Noted in python_features.pyj #43 (skipped) |
|
|
49
|
+
| 37 | `except*` (exception groups) | Not supported (Python 3.11+) | ✓ Yes | Noted in python_features.pyj #44 (skipped) |
|
|
50
|
+
| 38 | `__new__` constructor hook | Not supported | ✓ Yes | Noted in python_features.pyj #45 (skipped) |
|
|
51
|
+
| 39 | `__del__` destructor | Not supported | ✓ Yes | Noted in python_features.pyj #46 (skipped) |
|
|
52
|
+
| 40 | `__hash__` for set/dict membership | Not dispatched; uses JS identity (`===`) | ✓ Yes | Noted in python_features.pyj #47 (skipped) |
|
|
53
|
+
| 41 | `__getattr__`/`__setattr__`/`__delattr__` | Not supported | ✓ Yes | Noted in python_features.pyj #48 (skipped) |
|
|
54
|
+
| 42 | `__getattribute__` | Not supported | ✓ Yes | Noted in python_features.pyj #49 |
|
|
55
|
+
| 43 | `__class_getitem__` | Not supported | ✓ Yes | Noted in python_features.pyj #51 (skipped) |
|
|
56
|
+
| 44 | `__init_subclass__` | Not supported | ✓ Yes | Noted in python_features.pyj #52 (skipped) |
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Detailed Notes on Key Differences
|
|
61
|
+
|
|
62
|
+
### Function Argument Validation
|
|
63
|
+
|
|
64
|
+
RapydScript intentionally does **not** raise `TypeError` for:
|
|
65
|
+
- Calling a function with too few arguments (extra params are `undefined`)
|
|
66
|
+
- Calling a function with too many arguments (extras silently discarded)
|
|
67
|
+
- Passing a positional-only parameter by keyword (named kwarg silently ignored)
|
|
68
|
+
- Passing a keyword-only parameter positionally (no error raised)
|
|
69
|
+
- Specifying an optional argument twice when `*args` is present
|
|
70
|
+
|
|
71
|
+
**Rationale:** Performance and interoperability with JavaScript libraries that
|
|
72
|
+
frequently call functions with varying argument patterns.
|
|
73
|
+
|
|
74
|
+
### Dict Defaults (JavaScript Object Semantics)
|
|
75
|
+
|
|
76
|
+
Without `from __python__ import dict_literals, overload_getitem`:
|
|
77
|
+
- `{...}` creates a plain JS object, not a Python dict
|
|
78
|
+
- Missing keys return `undefined`, not `KeyError`
|
|
79
|
+
- Numeric keys are silently coerced to strings: `d[1]` and `d['1']` are the same key
|
|
80
|
+
- Dict keys are also accessible as properties: `d.foo == d['foo']`
|
|
81
|
+
- No `.keys()`, `.values()`, `.items()`, `.get()`, `.pop()` methods
|
|
82
|
+
|
|
83
|
+
Use `from __python__ import dict_literals, overload_getitem` for full Python dict semantics.
|
|
84
|
+
|
|
85
|
+
### List Method Name Conflicts (Resolved)
|
|
86
|
+
|
|
87
|
+
RapydScript lists are native JavaScript arrays. The method naming has been
|
|
88
|
+
restored to match Python conventions:
|
|
89
|
+
- `list.sort()` — Python numeric sort (in-place, supports `key` and `reverse`)
|
|
90
|
+
- `list.pop()` — Python bounds-checked pop (raises `IndexError` for out-of-bounds)
|
|
91
|
+
- `list.jssort()` — native JS lexicographic sort (for JS interop)
|
|
92
|
+
- `list.jspop()` — native JS pop (no bounds check, ignores arguments)
|
|
93
|
+
- `list.pysort()` / `list.pypop()` — backward-compat aliases for `sort()`/`pop()`
|
|
94
|
+
|
|
95
|
+
### Truthiness
|
|
96
|
+
|
|
97
|
+
Default truthiness follows JavaScript rules:
|
|
98
|
+
- `[]`, `{}`, any non-null object → **truthy** (unlike Python)
|
|
99
|
+
- `0`, `''`, `null`, `undefined`, `NaN` → falsy
|
|
100
|
+
|
|
101
|
+
Enable Python truthiness with `from __python__ import truthiness`:
|
|
102
|
+
- Empty containers (`[]`, `{}`, `set()`, `''`) → falsy
|
|
103
|
+
- `__bool__` and `__len__` dunders are dispatched
|
|
104
|
+
|
|
105
|
+
### String Method Availability
|
|
106
|
+
|
|
107
|
+
Python string methods are available via the `str` object:
|
|
108
|
+
```python
|
|
109
|
+
str.strip(' hello ') # 'hello'
|
|
110
|
+
str.split('a b') # ['a', 'b']
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
To make them available on string instances, call `from pythonize import strings; strings()`.
|
|
114
|
+
However, `split()` and `replace()` are **always** the JS versions — Python semantics
|
|
115
|
+
for these two methods must be accessed via `str.split(s)` / `str.replace(s, ...)`.
|
|
116
|
+
|
|
117
|
+
Notable JS-vs-Python difference for `split()`:
|
|
118
|
+
- Python `'a b'.split()` → `['a', 'b']` (splits on any whitespace, strips leading/trailing)
|
|
119
|
+
- JS `'a b'.split()` → `['a b']` (no-arg form returns single-element array)
|
|
120
|
+
|
|
121
|
+
### Method Binding
|
|
122
|
+
|
|
123
|
+
RapydScript **does not** auto-bind methods to instances. This matches
|
|
124
|
+
JavaScript semantics but differs from Python:
|
|
125
|
+
```python
|
|
126
|
+
obj = MyClass()
|
|
127
|
+
obj.method() # ✓ works — 'self' is correctly set
|
|
128
|
+
f = obj.method
|
|
129
|
+
f() # ✗ 'self' is undefined/window (unbound)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Use `from __python__ import bound_methods` inside a class to enable auto-binding.
|
|
133
|
+
|
|
134
|
+
### `global` Keyword Scope
|
|
135
|
+
|
|
136
|
+
RapydScript's `global` keyword slightly differs from Python's: if a variable
|
|
137
|
+
exists in an intermediate outer function scope **and** in the module-level
|
|
138
|
+
(global) scope, the outer function scope variable takes precedence.
|
|
139
|
+
In Python, `global` always refers to the module-level scope.
|
|
140
|
+
|
|
141
|
+
### `is` Operator
|
|
142
|
+
|
|
143
|
+
`is` compiles to `===` (strict equality), not Python's object identity check.
|
|
144
|
+
This means:
|
|
145
|
+
- `1 is 1` is `True` (same as Python)
|
|
146
|
+
- `'a' is 'a'` is `True` (same as Python for interned strings)
|
|
147
|
+
- `[1] is [1]` is `False` (same as Python — different objects)
|
|
148
|
+
- But `NaN is NaN` is `False` in JS (`NaN !== NaN`), which differs from Python
|
|
149
|
+
|
|
150
|
+
### Ordering Operators on Containers
|
|
151
|
+
|
|
152
|
+
RapydScript does **not** overload `<`, `>`, `<=`, `>=` for lists or other
|
|
153
|
+
containers. These fall through to JS comparison which coerces to strings:
|
|
154
|
+
```python
|
|
155
|
+
[10] < [9] # True in RapydScript (JS: '10' < '9' string compare)
|
|
156
|
+
# False in Python (numeric element comparison)
|
|
157
|
+
```
|
|
158
|
+
Use explicit element comparisons or define `__lt__` etc. and call them directly.
|
|
159
|
+
|
|
160
|
+
### Regular Expressions
|
|
161
|
+
|
|
162
|
+
The `re` module wraps JavaScript's regex engine. Key limitations:
|
|
163
|
+
1. **Lookbehind** — not supported in ES5; may work in ES6+ environments
|
|
164
|
+
2. **Unicode** — non-BMP characters may require ES6 `u` flag
|
|
165
|
+
3. **`MatchObject.start()`/`.end()`** for sub-groups — incorrect for some
|
|
166
|
+
nested-capture patterns (JS doesn't expose sub-group positions directly)
|
|
167
|
+
|
|
168
|
+
### Generators
|
|
169
|
+
|
|
170
|
+
By default, generators compile to ES5 state-machine switch statements (larger
|
|
171
|
+
but widely compatible). Pass `--js-version 6` to emit native ES6 generators.
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Features Completely Absent (not in Python either, but noteworthy)
|
|
176
|
+
|
|
177
|
+
| Feature | Notes |
|
|
178
|
+
|---------|-------|
|
|
179
|
+
| `v'...'` verbatim JS | RapydScript-only; embeds raw JS |
|
|
180
|
+
| Existential operator `x?.y` | Borrowed from CoffeeScript |
|
|
181
|
+
| `?` null-coalescing shorthand `a = b ? c` | RapydScript-specific |
|
|
182
|
+
| `def` as expression | Assign anonymous multi-line functions |
|
|
183
|
+
| `.call(this)` chaining syntax | Invoke anonymous functions immediately |
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
*Report generated 2026-03-21. Test coverage additions in `test/python_compat.pyj`.*
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Recommendations for Improving Python Compatibility
|
|
192
|
+
|
|
193
|
+
The following improvements are prioritized by how frequently they surprise Python users and how disruptive the gap is in practice. Items marked 🔥 are silent footguns — they produce wrong behavior with no error, making them especially harmful.
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
### 1. 🔥 Fix List Concatenation (`+`) Without `overload_operators`
|
|
198
|
+
|
|
199
|
+
**Current:** `[1, 2] + [3, 4]` silently produces the string `'[1, 2][3, 4]'` — a completely wrong result with no warning.
|
|
200
|
+
|
|
201
|
+
**Recommendation:** Make basic list `+` concatenation work correctly by default, without requiring `overload_operators`. This could be done at the compiler level by emitting a `.concat()` call whenever the `+` operator is applied between two list literals or list-typed variables. Alternatively, emit a runtime check: if both operands are arrays, use `.concat()`.
|
|
202
|
+
|
|
203
|
+
**Why:** This is one of the worst silent footguns in the language. Any Python programmer who writes `result = list_a + list_b` will get corrupted output that looks like a string. The fact that `overload_operators` fixes it is obscure and adds overhead for an operation Python users expect to always work.
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
### 2. ✅ Rename `pysort()` / `pypop()` Back to `sort()` / `pop()` — **IMPLEMENTED**
|
|
208
|
+
|
|
209
|
+
**Implemented:** `list.sort()` now performs Python-style numeric sort (in-place, supports `key` and `reverse`). `list.pop()` now performs Python-style bounds-checked pop (raises `IndexError` for out-of-bounds). The native JS versions are available as `list.jssort()` and `list.jspop()`. The old `pysort()` / `pypop()` names are retained as backward-compat aliases.
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
### 3. 🔥 Enable Python Truthiness by Default (or Warn on Empty Container Tests)
|
|
214
|
+
|
|
215
|
+
**Current:** `if []:` evaluates to `True` (JS semantics). Python semantics require `from __python__ import truthiness`.
|
|
216
|
+
|
|
217
|
+
**Recommendation:** Either (a) make `truthiness` the default behavior and provide a `from __js__ import truthiness` escape hatch for legacy code, or (b) emit a compiler warning when a list/dict/set literal is used directly as an `if` condition without the `truthiness` flag.
|
|
218
|
+
|
|
219
|
+
**Why:** The truthiness of empty containers is one of the most fundamental Python idioms. `if items:`, `while queue:`, and `if not result:` are idiomatic Python in virtually every codebase. Getting them silently wrong without the `truthiness` flag is a trap that is very hard to debug.
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
### 4. Make Python Dict the Default (or Promote `dict_literals` to a Global Default)
|
|
224
|
+
|
|
225
|
+
**Current:** `{}` creates a plain JS object. Missing keys return `undefined`, numeric keys are coerced to strings, and `.keys()` / `.values()` / `.items()` don't exist. Full Python dict semantics require `from __python__ import dict_literals, overload_getitem`.
|
|
226
|
+
|
|
227
|
+
**Recommendation:** Promote `dict_literals` to an opt-out default via a compiler flag (`--python-dicts` or similar). Alternatively, provide a project-level config option to enable it globally so users don't have to add the import to every file.
|
|
228
|
+
|
|
229
|
+
**Why:** Python dicts are used everywhere. The behavior gap (missing key → `undefined` instead of `KeyError`, numeric keys aliasing to string keys, no `.items()`) causes subtle bugs that are extremely hard to trace. The current model forces users to remember a file-level import for behavior they'd expect to be the baseline.
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
### 5. Auto-Apply `strings()` or Make Python String Methods Available on Instances
|
|
234
|
+
|
|
235
|
+
**Current:** Python string methods like `.strip()`, `.split()`, `.upper()` live on the `str` module object (`str.strip(s)`) rather than on string instances. To use instance-style calls, users must call `from pythonize import strings; strings()`. Even then, `.split()` and `.replace()` remain JS versions.
|
|
236
|
+
|
|
237
|
+
**Recommendation:** Two improvements:
|
|
238
|
+
1. Make `strings()` apply automatically in Python-compatibility mode, or provide a cleaner `from __python__ import strings` import.
|
|
239
|
+
2. Overwrite the JS `.split()` and `.replace()` with Python-correct implementations inside `strings()`. The current doc explicitly says these two are **intentionally** left as JS versions — this should be reconsidered, as it makes `strings()` provide incomplete Python compatibility.
|
|
240
|
+
|
|
241
|
+
**Why:** Method calls on string instances (`s.split()`, `s.strip()`, `s.startswith('x')`) are the default mental model for every Python user. Having to write `str.split(s)` instead of `s.split()` is jarring and prevents Python code from running without modification.
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
### 6. Add `__getattr__` / `__setattr__` Support via ES6 Proxy
|
|
246
|
+
|
|
247
|
+
**Current:** `__getattr__` and `__setattr__` are not supported. Attribute access interception is impossible.
|
|
248
|
+
|
|
249
|
+
**Recommendation:** In ES6 mode (`--js-version 6`), implement `__getattr__` and `__setattr__` by wrapping class instances in a `Proxy` when those dunders are defined. The `get` and `set` traps can delegate to the user's `__getattr__` / `__setattr__` methods with a fallback to the underlying object.
|
|
250
|
+
|
|
251
|
+
**Why:** `__getattr__` is used extensively for lazy loading, dynamic APIs, mock objects, ORMs, and proxy patterns. It's the basis for many Python libraries (e.g. `dataclasses`-style field access, `unittest.mock`, `attrs`). Without it, an entire class of Python patterns is unavailable. ES6 Proxy support is well-established in all modern JS environments.
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
### 7. Add Stub Modules for `typing`, `dataclasses`, and `enum`
|
|
256
|
+
|
|
257
|
+
**Current:** `typing`, `dataclasses`, and `enum` are completely absent.
|
|
258
|
+
|
|
259
|
+
**Recommendation:**
|
|
260
|
+
- **`typing`**: Add a stub `src/lib/typing.pyj` that exports `List`, `Dict`, `Optional`, `Union`, `Tuple`, `Any`, `TypeVar`, `Generic`, `Callable` — all as no-ops or identity functions. This lets users write `from typing import Optional, List` without import errors, and allows type-annotated Python code to run unchanged.
|
|
261
|
+
- **`enum`**: Add a basic `Enum` base class where `class Color(Enum): RED = 1` creates an object with `.name` and `.value` attributes, iteration over members, and `Color.RED` access. This is well-defined behavior with no JS-fundamental blockers.
|
|
262
|
+
- **`dataclasses`**: Add a `@dataclass` decorator that introspects class-level annotations and generates `__init__`, `__repr__`, and `__eq__`. This is a high-value feature — dataclasses are ubiquitous in modern Python and could be implemented in ~100 lines of RapydScript using the existing annotation support.
|
|
263
|
+
|
|
264
|
+
**Why:** These three modules are among the most-imported in modern Python codebases. Their absence means entire categories of modern Python patterns — type hints, structured data classes, enumerated constants — fail at import time with no workaround other than rewriting the code.
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
### 8. Add a `copy` Module (`copy.copy` / `copy.deepcopy`)
|
|
269
|
+
|
|
270
|
+
**Current:** No `copy` module. Shallow and deep copy must be done via JS idioms (`Object.assign`, `JSON.parse(JSON.stringify(...))`) or verbatim JS.
|
|
271
|
+
|
|
272
|
+
**Recommendation:** Add `src/lib/copy.pyj` with:
|
|
273
|
+
- `copy(obj)` — shallow clone: spread for plain objects, `.slice()` for arrays, class instance duplication via `Object.assign(Object.create(proto), obj)`.
|
|
274
|
+
- `deepcopy(obj)` — deep clone: recursive traversal for plain objects/arrays; use `structuredClone()` (ES2022) when available with a recursive fallback.
|
|
275
|
+
|
|
276
|
+
**Why:** `copy.deepcopy` is one of the most commonly needed utilities when porting Python code. Many algorithms depend on working with independent copies of mutable data structures. Without it, users either introduce subtle aliasing bugs or write verbose verbatim-JS workarounds.
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
### Summary: Priority Order
|
|
281
|
+
|
|
282
|
+
| Priority | Item | Effort | Impact |
|
|
283
|
+
|---|---|---|---|
|
|
284
|
+
| 1 | Fix list `+` concatenation (silent footgun) | Low — compiler emit change | 🔥 High |
|
|
285
|
+
| 2 | ~~Rename `pysort`/`pypop` back to `sort`/`pop`~~ | ✅ Done | 🔥 High |
|
|
286
|
+
| 3 | Default Python truthiness | Medium — flag change + regression test | 🔥 High |
|
|
287
|
+
| 4 | Default Python dicts | Medium — compiler flag or config | High |
|
|
288
|
+
| 5 | Python string methods on instances | Low — fix `strings()` split/replace | High |
|
|
289
|
+
| 6 | `typing`, `dataclasses`, `enum` stubs | Medium — ~200 lines per module | High |
|
|
290
|
+
| 7 | `copy` module | Low — ~80 lines | Medium |
|
|
291
|
+
| 8 | `__getattr__`/`__setattr__` via Proxy | High — ES6 only, code-gen complexity | Medium |
|
|
@@ -0,0 +1,200 @@
|
|
|
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
|
+
| `list + list` concatenation | `[1,2] + [3,4]` returns `[1, 2, 3, 4]`; `+=` extends in-place. No flag required. |
|
|
28
|
+
| `match / case` | Structural pattern matching (Python 3.10) fully supported |
|
|
29
|
+
| Variable type annotations `x: int = 1` | Parsed and ignored (no runtime enforcement); annotated assignments work normally |
|
|
30
|
+
| Ellipsis literal `...` as expression | Parsed as a valid expression; evaluates to JS `undefined` at runtime |
|
|
31
|
+
| Generator `.throw()` | Works via JS generator protocol |
|
|
32
|
+
| Generator `.send()` | Works via `g.next(value)` |
|
|
33
|
+
| `yield from` | Works; return value of sub-generator is not accessible |
|
|
34
|
+
| `+=`, `-=`, `*=`, `/=`, `//=`, `**=`, `%=`, `&=`, `\|=`, `^=`, `<<=`, `>>=` | All augmented assignments work |
|
|
35
|
+
| `raise X from Y` exception chaining | Sets `__cause__` on the thrown exception; `from None` also supported |
|
|
36
|
+
| Starred assignment `a, *b, c = ...` | Works |
|
|
37
|
+
| `@classmethod`, `@staticmethod`, `@property` / `@prop.setter` | All work |
|
|
38
|
+
| `{**dict1, **dict2}` dict spread | Works as merge replacement for the missing `\|` operator |
|
|
39
|
+
| `dict.fromkeys()` | Works with `dict_literals` flag |
|
|
40
|
+
| Chained comparisons `a < b < c` and `a < b > c` | Same-direction and mixed-direction chains both work; middle operand evaluated once |
|
|
41
|
+
| `for`, `while`, `try/except/finally`, `with`, `match/case` | All control-flow constructs work |
|
|
42
|
+
| Classes, inheritance, decorators, `__dunder__` methods | Fully supported |
|
|
43
|
+
| Nested class definitions | Accessible as `Outer.Inner` and via instance (`self.Inner`); arbitrary nesting depth; nested class may inherit from outer-scope classes |
|
|
44
|
+
| List / dict / set comprehensions, generator expressions | Fully supported |
|
|
45
|
+
| f-strings, `str.format()`, `format()` builtin, all common `str.*` methods | Fully supported |
|
|
46
|
+
| `abs()`, `divmod()`, `any()`, `all()`, `sum()`, `min()`, `max()` | All work |
|
|
47
|
+
| `sorted()`, `reversed()`, `zip()`, `map()`, `filter()` | All work |
|
|
48
|
+
| `set` with full union/intersection/difference API | Fully supported |
|
|
49
|
+
| `isinstance()`, `hasattr()`, `getattr()`, `setattr()`, `dir()` | All work |
|
|
50
|
+
| `bin()`, `hex()`, `oct()`, `chr()`, `ord()` | All work |
|
|
51
|
+
| `int(x, base)`, `float(x)` with ValueError on bad input | Works |
|
|
52
|
+
| `lambda` keyword | Full support: args, defaults, `*args`, ternary body, closures, nesting |
|
|
53
|
+
| 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 |
|
|
54
|
+
| Nested comprehensions (multi-`for` clause) | `[x for row in matrix for x in row if cond]`; works for list, set, and dict comprehensions |
|
|
55
|
+
| Positional-only parameters `def f(a, b, /):` | Full support — parser enforces placement; runtime passes positional args correctly |
|
|
56
|
+
| Keyword-only parameters `def f(a, *, b):` | Full support — bare `*` separator enforced; `b` must be passed as keyword |
|
|
57
|
+
| Walrus operator `:=` | Fully supported: hoisted in `if`/`while` conditions at any scope; comprehension filter assigns to enclosing scope (Python-correct). |
|
|
58
|
+
| `__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`. |
|
|
59
|
+
| **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()`. |
|
|
60
|
+
| `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.). |
|
|
61
|
+
| `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. |
|
|
62
|
+
| `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`. |
|
|
63
|
+
| `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. |
|
|
64
|
+
| `StopIteration` exception | Defined as a builtin exception class; raised by `next()` when an iterator is exhausted and no default is given. |
|
|
65
|
+
| `iter(callable, sentinel)` | Two-argument form calls `callable` (no args) repeatedly until the return value equals `sentinel` (strict `===`). Returns a lazy iterator compatible with `for` loops, `next()`, `list()`, and all iterator consumers. Works with plain functions and callable objects (`__call__`). |
|
|
66
|
+
| `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`. |
|
|
67
|
+
| `slice(start, stop[, step])` | Full Python `slice` class: 1-, 2-, and 3-argument forms; `.start`, `.stop`, `.step` attributes; `.indices(length)` → `(start, stop, step)`; `str()` / `repr()`; `isinstance(s, slice)`; equality `==`; use as subscript `lst[s]` (read, write, `del`) all work. |
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## ❌ Not Supported — Missing from Baselib (runtime)
|
|
72
|
+
|
|
73
|
+
| Feature | Priority |
|
|
74
|
+
|-------------------------------------|------------------------------------------------------------------------|
|
|
75
|
+
| `complex(real, imag)` | 🟢 Low — no complex number type |
|
|
76
|
+
| `vars()` / `locals()` / `globals()` | 🟢 Low — not defined; use direct attribute access |
|
|
77
|
+
| `str.expandtabs(tabsize)` | 🟢 Low |
|
|
78
|
+
| `int.bit_length()` | 🟢 Low — useful for bit manipulation |
|
|
79
|
+
| `float.is_integer()` | 🟢 Low |
|
|
80
|
+
| `exec(code)` | 🟢 Low — use `v'eval(...)'` for inline JS evaluation |
|
|
81
|
+
| `eval(expr)` | 🟢 Low — use `v'eval(...)'` for inline JS evaluation |
|
|
82
|
+
| `__import__(name)` | 🟢 Low — not supported; use `import` statement |
|
|
83
|
+
| `input(prompt)` | 🟢 Low — not built in; use `prompt()` via `v'prompt(...)'` |
|
|
84
|
+
| `bytes(x)` / `bytearray(x)` | 🟢 Low — no built-in bytes type; use `Uint8Array` via verbatim JS |
|
|
85
|
+
| `object()` | 🟢 Low — base `object` type not exposed; use plain `{}` or a class |
|
|
86
|
+
| `abc` module — `ABC`, `@abstractmethod`, `Protocol` | 🟢 Low — no abc module; no enforcement of abstract methods |
|
|
87
|
+
| `compile()` | 🔴 N/A — Python compile/code objects have no JS equivalent |
|
|
88
|
+
| `memoryview(obj)` | 🔴 N/A — no buffer protocol in browser context |
|
|
89
|
+
| `open(path)` | 🔴 N/A — no filesystem access in browser context |
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## ❌ Not Supported — Parser / Syntax Level
|
|
94
|
+
|
|
95
|
+
| Feature | Priority |
|
|
96
|
+
|-----------------------------------------------|----------------------------------------------------------------------|
|
|
97
|
+
| `from module import *` (star imports) | 🟢 Low — intentionally unsupported (by design, to prevent namespace pollution) |
|
|
98
|
+
| `zip(strict=True)` | 🟢 Low |
|
|
99
|
+
| `__slots__` enforcement | 🟢 Low — accepted but does not restrict attribute assignment |
|
|
100
|
+
| Complex number literals `3+4j` | 🟢 Low — no `j` suffix; no complex type |
|
|
101
|
+
| `b'...'` bytes literals | 🟢 Low — no `b` prefix; use the `encodings` module for encoding work |
|
|
102
|
+
| `except*` (exception groups, Python 3.11+) | 🟢 Low — no parser support |
|
|
103
|
+
| `__new__` constructor hook | 🟢 Low — no alternative constructor support |
|
|
104
|
+
| `__del__` destructor / finalizer | 🟢 Low — JS has no guaranteed finalizer |
|
|
105
|
+
| `__hash__` dunder | 🟢 Low — not dispatched; set/dict use `===` object identity |
|
|
106
|
+
| `__getattr__` / `__setattr__` / `__delattr__` | 🟢 Low — no attribute-access interception |
|
|
107
|
+
| `__getattribute__` | 🟢 Low — no attribute-lookup override |
|
|
108
|
+
| `__format__` dunder | 🟢 Low — `format()` builtin not defined; `__format__` not dispatched |
|
|
109
|
+
| `__class_getitem__` | 🟢 Low — no `MyClass[T]` generic subscript syntax |
|
|
110
|
+
| `__init_subclass__` hook | 🟢 Low |
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Standard Library Modules
|
|
115
|
+
|
|
116
|
+
Modules with a `src/lib/` implementation available are marked ✅. All others are absent.
|
|
117
|
+
|
|
118
|
+
| Module | Status | Notes |
|
|
119
|
+
|---------------|-------------|-----------------------------------------------------------------------------------------------|
|
|
120
|
+
| `math` | ✅ | Full implementation in `src/lib/math.pyj` |
|
|
121
|
+
| `random` | ✅ | RC4-seeded PRNG in `src/lib/random.pyj` |
|
|
122
|
+
| `re` | ✅ | Regex wrapper in `src/lib/re.pyj`; limited vs full PCRE (no lookbehind, limited unicode, no conditional groups); `MatchObject.start()`/`.end()` may return incorrect values for sub-groups in nested-capture patterns (JS regex API does not expose sub-group positions) |
|
|
123
|
+
| `encodings` | ✅ | Base64 and encoding helpers; partial `base64` coverage |
|
|
124
|
+
| `collections` | ✅ | `defaultdict`, `Counter`, `OrderedDict`, `deque` |
|
|
125
|
+
| `functools` | ✅ | `reduce`, `partial`, `wraps`, `lru_cache` |
|
|
126
|
+
| `itertools` | ✅ | Common iteration tools |
|
|
127
|
+
| `numpy` | ✅ | Full numpy-like library in `src/lib/numpy.pyj`; `numpy.random` and `numpy.linalg` sub-modules |
|
|
128
|
+
| `typing` | ❌ | `List`, `Dict`, `Optional`, `Union`, `Tuple`, `Generic`, `TypeVar` — none available |
|
|
129
|
+
| `dataclasses` | ❌ | `@dataclass`, `field()`, `asdict()`, `astuple()` not available |
|
|
130
|
+
| `contextlib` | ❌ | `contextmanager`, `suppress`, `ExitStack`, `asynccontextmanager` not available |
|
|
131
|
+
| `copy` | ❌ | `copy()` / `deepcopy()` not available |
|
|
132
|
+
| `string` | ❌ | Character constants, `Template`, `Formatter` not available |
|
|
133
|
+
| `json` | ❌ | No Python wrapper; JS `JSON.parse` / `JSON.stringify` work directly via verbatim JS |
|
|
134
|
+
| `datetime` | ❌ | `date`, `time`, `datetime`, `timedelta` not available |
|
|
135
|
+
| `inspect` | ❌ | `signature`, `getmembers`, `isfunction` etc. not available |
|
|
136
|
+
| `asyncio` | ❌ | Event loop, `gather`, `sleep`, `Queue`, `Task` wrappers not available; use `async`/`await` |
|
|
137
|
+
| `enum` | ❌ | `Enum`, `IntEnum`, `Flag` not available |
|
|
138
|
+
| `abc` | ❌ | `ABC`, `abstractmethod` not available |
|
|
139
|
+
| `io` | ❌ | `StringIO`, `BytesIO` not available |
|
|
140
|
+
| `struct` | ❌ | Binary packing/unpacking not available |
|
|
141
|
+
| `hashlib` | ❌ | MD5, SHA-256 etc. not available; use Web Crypto API via verbatim JS |
|
|
142
|
+
| `hmac` | ❌ | Keyed hashing not available |
|
|
143
|
+
| `base64` | ❌ (partial) | Partial coverage via `encodings` module; no full `base64` module |
|
|
144
|
+
| `urllib` | ❌ | URL parsing/encoding (`urllib.parse`) not available; use JS `URL` API |
|
|
145
|
+
| `html` | ❌ | `escape`, `unescape` not available; use JS DOM APIs |
|
|
146
|
+
| `csv` | ❌ | CSV parsing not available |
|
|
147
|
+
| `textwrap` | ❌ | `wrap`, `fill`, `dedent`, `indent` not available |
|
|
148
|
+
| `pprint` | ❌ | Pretty-printing not available |
|
|
149
|
+
| `logging` | ❌ | Logging framework not available; use `console.*` directly |
|
|
150
|
+
| `unittest` | ❌ | Not available; RapydScript uses a custom test runner (`node bin/rapydscript test`) |
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Semantic Differences
|
|
155
|
+
|
|
156
|
+
Features that exist in RapydScript but behave differently from standard Python:
|
|
157
|
+
|
|
158
|
+
| Feature | Python Behavior | RapydScript Behavior |
|
|
159
|
+
|---|---|---|
|
|
160
|
+
| Truthiness of `[]` / `{}` | `False` | `True` (JS semantics) unless `from __python__ import truthiness` is active |
|
|
161
|
+
| `is` / `is not` | Object identity | Strict equality `===` / `!==` |
|
|
162
|
+
| `//` floor division on floats | `math.floor(a/b)` always | Correct for integers; uses `Math.floor` (same result for well-behaved floats) |
|
|
163
|
+
| `%` on negative numbers | Python modulo (always non-negative) | JS remainder (can be negative) |
|
|
164
|
+
| `int` / `float` distinction | Separate types | Both are JS `number`; `isinstance(x, int)` and `isinstance(x, float)` use heuristics |
|
|
165
|
+
| `str * n` repetition | Always works | Requires `from __python__ import overload_operators` |
|
|
166
|
+
| Unbound method references | Methods are unbound by default | Same — but storing a method in a variable without the `bound_methods` compiler flag loses `self` binding |
|
|
167
|
+
| `dict` key ordering | Insertion order guaranteed (3.7+) | Depends on JS engine (V8 preserves insertion order in practice) |
|
|
168
|
+
| `global` / `nonlocal` scoping | Full cross-scope declaration | `global` works for module-level; if a variable exists in both an intermediate outer scope **and** the module-level scope, the outer scope takes precedence (differs from Python where `global` always forces module-level) |
|
|
169
|
+
| `Exception.message` | Not standard; use `.args[0]` | `.message` is the standard attribute (JS `Error` style) |
|
|
170
|
+
| `re` module | Full PCRE (lookbehind, full unicode, conditional groups) | No lookbehind; limited unicode property escapes; no `(?(1)...)` conditional groups |
|
|
171
|
+
| `parenthesized with (A() as a, B() as b):` | Multiple context managers in parenthesized form (3.10+) | Not meaningful in a browser/event-driven context; multi-context `with` without parens works |
|
|
172
|
+
| Function call argument count | Too few args → `TypeError`; too many → `TypeError` | Too few args → extra params are `undefined`; too many → extras silently discarded. No `TypeError` is raised in either case. |
|
|
173
|
+
| Positional-only param enforcement | Passing by keyword raises `TypeError` | Passing by keyword is silently ignored — the named arg is discarded and the parameter gets `undefined` (no error raised) |
|
|
174
|
+
| Keyword-only param enforcement | Passing positionally raises `TypeError` | Passing positionally raises no error — the extra positional arg is silently discarded and the default value is used |
|
|
175
|
+
| `is` / `is not` with `NaN` | `math.nan is math.nan` → `True` (same object) | `x is NaN` compiles to `isNaN(x)` (not `x === NaN`), making NaN checks work correctly |
|
|
176
|
+
| Arithmetic type coercion | `1 + '1'` raises `TypeError` | `1 + '1'` → `'11'`; JS coerces the number to a string |
|
|
177
|
+
| `list + list` | Concatenation returns a new list | Now matches Python: `[1,2] + [3,4]` returns `[1, 2, 3, 4]`; `+=` extends the list in-place. No `overload_operators` flag required. (Pre-existing JS `+` coercion is replaced by a lightweight `ρσ_list_add` helper.) |
|
|
178
|
+
| `<`, `>`, `<=`, `>=` on lists / containers | Element-wise lexicographic comparison | Falls through to JS coercion — operands are stringified first (e.g. `[10] < [9]` is `True` because `'[10]' < '[9]'`). Comparison dunders (`__lt__` etc.) can be defined and called directly but are not auto-dispatched by these operators. |
|
|
179
|
+
| Default `{}` dict — missing key | `KeyError` raised | Returns `undefined`; use `from __python__ import dict_literals, overload_getitem` to get `KeyError` |
|
|
180
|
+
| Default `{}` dict — numeric keys | Integer keys are stored as integers | Numeric keys are auto-coerced to strings by the JS engine: `d[1]` and `d['1']` refer to the same slot |
|
|
181
|
+
| Default `{}` dict — attribute access | `d.foo` raises `AttributeError` | `d.foo` and `d['foo']` access the same slot; keys are also properties |
|
|
182
|
+
| Default `{}` dict — Python dict methods | `.keys()`, `.values()`, `.items()`, `.get()`, `.pop()`, `.update()` all available | None of these methods exist on a plain JS object dict; use `from __python__ import dict_literals` for full Python dict semantics |
|
|
183
|
+
| String methods on instances | `'hello'.strip()` works directly | Python string methods live on the `str` module object (`str.strip(s)`), not on string instances. JS native methods (`.toUpperCase()`, `.trim()`, etc.) work on instances. Call `from pythonize import strings; strings()` to copy Python methods onto string instances. |
|
|
184
|
+
| `strings()` — `split()` / `replace()` | `'a b'.split()` splits on whitespace; `'aaa'.replace('a','b')` replaces all | After `strings()`, `split()` and `replace()` are **intentionally left** as JS versions: no-arg `.split()` returns a one-element array; `.replace(str, str)` replaces only the first occurrence. Use `str.split(s)` / `str.replace(s, old, new)` for Python semantics. |
|
|
185
|
+
| String encoding | Unicode strings (full code-point aware) | UTF-16 — non-BMP characters (e.g. emoji) are stored as surrogate pairs. Use `str.uchrs()`, `str.uslice()`, `str.ulen()` for code-point-aware operations. |
|
|
186
|
+
| Multiple inheritance MRO | C3 linearization (MRO) always deterministic | Built on JS prototype chain; may differ from Python's C3 MRO in complex or diamond-inheritance hierarchies |
|
|
187
|
+
| Generators — output format | Native Python generator objects | Down-compiled to ES5 state-machine switch statements by default; pass `--js-version 6` for native ES6 generators (smaller and faster) |
|
|
188
|
+
| Reserved keywords | Python keywords only | All JavaScript reserved words (`default`, `switch`, `delete`, `void`, `typeof`, etc.) are also reserved in RapydScript, since it compiles to JS |
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Test File
|
|
193
|
+
|
|
194
|
+
`test/python_features.pyj` contains runnable assertions for all features surveyed.
|
|
195
|
+
Features that are not supported have their test code commented out with a `# SKIP:` label
|
|
196
|
+
and an explanation. Run with:
|
|
197
|
+
|
|
198
|
+
```sh
|
|
199
|
+
node bin/rapydscript test python_features
|
|
200
|
+
```
|