rapydscript-ns 0.8.2 → 0.8.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agignore +1 -1
- package/.github/workflows/ci.yml +38 -38
- package/=template.pyj +5 -5
- package/CHANGELOG.md +39 -0
- package/HACKING.md +103 -103
- package/LICENSE +24 -24
- package/PYTHON_DIFFERENCES_REPORT.md +291 -0
- package/PYTHON_FEATURE_COVERAGE.md +106 -15
- package/README.md +831 -52
- package/TODO.md +4 -286
- package/add-toc-to-readme +2 -2
- package/bin/export +75 -75
- package/bin/rapydscript +70 -70
- package/bin/web-repl-export +102 -102
- package/build +2 -2
- package/language-service/index.js +4623 -0
- package/language-service/language-service.d.ts +40 -0
- package/package.json +9 -7
- package/publish.py +37 -37
- package/release/baselib-plain-pretty.js +2006 -229
- package/release/baselib-plain-ugly.js +70 -3
- package/release/compiler.js +11554 -3870
- package/release/signatures.json +31 -29
- package/session.vim +4 -4
- package/setup.cfg +2 -2
- package/src/ast.pyj +93 -1
- package/src/baselib-builtins.pyj +99 -2
- package/src/baselib-containers.pyj +107 -4
- package/src/baselib-errors.pyj +44 -0
- package/src/baselib-internal.pyj +124 -5
- package/src/baselib-itertools.pyj +97 -97
- package/src/baselib-str.pyj +32 -1
- package/src/compiler.pyj +36 -36
- package/src/errors.pyj +30 -30
- package/src/lib/aes.pyj +646 -646
- package/src/lib/collections.pyj +1 -1
- package/src/lib/copy.pyj +120 -0
- package/src/lib/elementmaker.pyj +83 -83
- package/src/lib/encodings.pyj +126 -126
- package/src/lib/gettext.pyj +569 -569
- package/src/lib/itertools.pyj +580 -580
- package/src/lib/math.pyj +193 -193
- package/src/lib/numpy.pyj +10 -10
- package/src/lib/operator.pyj +11 -11
- package/src/lib/pythonize.pyj +20 -20
- package/src/lib/random.pyj +118 -118
- package/src/lib/re.pyj +470 -470
- package/src/lib/react.pyj +74 -0
- package/src/lib/traceback.pyj +63 -63
- package/src/lib/uuid.pyj +77 -77
- package/src/monaco-language-service/analyzer.js +131 -9
- package/src/monaco-language-service/builtins.js +17 -2
- package/src/monaco-language-service/completions.js +170 -1
- package/src/monaco-language-service/diagnostics.js +25 -3
- package/src/monaco-language-service/dts.js +550 -550
- package/src/monaco-language-service/index.js +17 -0
- package/src/monaco-language-service/scope.js +3 -0
- package/src/output/classes.pyj +128 -11
- package/src/output/codegen.pyj +17 -3
- package/src/output/comments.pyj +45 -45
- package/src/output/exceptions.pyj +201 -105
- package/src/output/functions.pyj +13 -16
- package/src/output/jsx.pyj +164 -0
- package/src/output/literals.pyj +28 -2
- package/src/output/loops.pyj +0 -9
- package/src/output/modules.pyj +2 -5
- package/src/output/operators.pyj +22 -2
- package/src/output/statements.pyj +2 -2
- package/src/output/stream.pyj +1 -13
- package/src/output/treeshake.pyj +182 -182
- package/src/output/utils.pyj +72 -72
- package/src/parse.pyj +434 -114
- package/src/string_interpolation.pyj +72 -72
- package/src/tokenizer.pyj +29 -0
- package/src/unicode_aliases.pyj +576 -576
- package/src/utils.pyj +192 -192
- package/test/_import_one.pyj +37 -37
- package/test/_import_two/__init__.pyj +11 -11
- package/test/_import_two/level2/deep.pyj +4 -4
- package/test/_import_two/other.pyj +6 -6
- package/test/_import_two/sub.pyj +13 -13
- package/test/aes_vectors.pyj +421 -421
- package/test/annotations.pyj +80 -80
- package/test/baselib.pyj +4 -4
- package/test/classes.pyj +56 -17
- package/test/collections.pyj +5 -5
- package/test/decorators.pyj +77 -77
- package/test/docstrings.pyj +39 -39
- package/test/elementmaker_test.pyj +45 -45
- package/test/functions.pyj +151 -151
- package/test/generators.pyj +41 -41
- package/test/generic.pyj +370 -370
- package/test/imports.pyj +72 -72
- package/test/internationalization.pyj +73 -73
- package/test/lint.pyj +164 -164
- package/test/loops.pyj +85 -85
- package/test/numpy.pyj +734 -734
- package/test/omit_function_metadata.pyj +20 -20
- package/test/python_compat.pyj +326 -0
- package/test/python_features.pyj +129 -29
- package/test/regexp.pyj +55 -55
- package/test/repl.pyj +121 -121
- package/test/scoped_flags.pyj +76 -76
- 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 +2296 -71
- package/test/unit/language-service-builtins.js +70 -0
- package/test/unit/language-service-bundle.js +5 -5
- package/test/unit/language-service-completions.js +180 -0
- package/test/unit/language-service-dts.js +543 -543
- package/test/unit/language-service-hover.js +455 -455
- package/test/unit/language-service-index.js +350 -0
- package/test/unit/language-service-scope.js +255 -0
- package/test/unit/language-service.js +625 -4
- package/test/unit/run-language-service.js +1 -0
- package/test/unit/web-repl.js +437 -0
- package/tools/build-language-service.js +2 -2
- package/tools/cli.js +547 -547
- package/tools/compile.js +219 -219
- package/tools/compiler.js +0 -24
- package/tools/completer.js +131 -131
- package/tools/embedded_compiler.js +251 -251
- package/tools/export.js +3 -37
- package/tools/gettext.js +185 -185
- package/tools/ini.js +65 -65
- package/tools/msgfmt.js +187 -187
- package/tools/repl.js +223 -223
- package/tools/test.js +118 -118
- package/tools/utils.js +128 -128
- package/tools/web_repl.js +95 -95
- package/try +41 -41
- package/web-repl/env.js +196 -74
- package/web-repl/index.html +163 -163
- package/web-repl/main.js +252 -254
- package/web-repl/prism.css +139 -139
- package/web-repl/prism.js +113 -113
- package/web-repl/rapydscript.js +227 -139
- package/web-repl/sha1.js +25 -25
- package/hack_demo.pyj +0 -112
- package/web-repl/language-service.js +0 -4187
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
# globals: RapydScript
|
|
2
|
-
|
|
3
|
-
# Test that omit_function_metadata flag suppresses __module__ and __argnames__
|
|
4
|
-
code = 'def foo(a, b):\n return a + b'
|
|
5
|
-
ast = RapydScript.parse(code, {'filename': '<test>'})
|
|
6
|
-
|
|
7
|
-
# Without flag: output should contain __module__ and __argnames__
|
|
8
|
-
output_with = v'new RapydScript.OutputStream({"beautify": true, "omit_baselib": true})'
|
|
9
|
-
ast.print(output_with)
|
|
10
|
-
result_with = output_with.get()
|
|
11
|
-
assrt.ok(result_with.indexOf('__module__') >= 0, '__module__ should appear in default output')
|
|
12
|
-
assrt.ok(result_with.indexOf('__argnames__') >= 0, '__argnames__ should appear in default output')
|
|
13
|
-
|
|
14
|
-
# With flag: output should not contain __module__, __argnames__, or Object.defineProperties
|
|
15
|
-
output_without = v'new RapydScript.OutputStream({"beautify": true, "omit_baselib": true, "omit_function_metadata": true})'
|
|
16
|
-
ast.print(output_without)
|
|
17
|
-
result_without = output_without.get()
|
|
18
|
-
assrt.ok(result_without.indexOf('__module__') is -1, '__module__ should not appear when omit_function_metadata is set')
|
|
19
|
-
assrt.ok(result_without.indexOf('__argnames__') is -1, '__argnames__ should not appear when omit_function_metadata is set')
|
|
20
|
-
assrt.ok(result_without.indexOf('Object.defineProperties') is -1, 'Object.defineProperties should not appear when omit_function_metadata is set')
|
|
1
|
+
# globals: RapydScript
|
|
2
|
+
|
|
3
|
+
# Test that omit_function_metadata flag suppresses __module__ and __argnames__
|
|
4
|
+
code = 'def foo(a, b):\n return a + b'
|
|
5
|
+
ast = RapydScript.parse(code, {'filename': '<test>'})
|
|
6
|
+
|
|
7
|
+
# Without flag: output should contain __module__ and __argnames__
|
|
8
|
+
output_with = v'new RapydScript.OutputStream({"beautify": true, "omit_baselib": true})'
|
|
9
|
+
ast.print(output_with)
|
|
10
|
+
result_with = output_with.get()
|
|
11
|
+
assrt.ok(result_with.indexOf('__module__') >= 0, '__module__ should appear in default output')
|
|
12
|
+
assrt.ok(result_with.indexOf('__argnames__') >= 0, '__argnames__ should appear in default output')
|
|
13
|
+
|
|
14
|
+
# With flag: output should not contain __module__, __argnames__, or Object.defineProperties
|
|
15
|
+
output_without = v'new RapydScript.OutputStream({"beautify": true, "omit_baselib": true, "omit_function_metadata": true})'
|
|
16
|
+
ast.print(output_without)
|
|
17
|
+
result_without = output_without.get()
|
|
18
|
+
assrt.ok(result_without.indexOf('__module__') is -1, '__module__ should not appear when omit_function_metadata is set')
|
|
19
|
+
assrt.ok(result_without.indexOf('__argnames__') is -1, '__argnames__ should not appear when omit_function_metadata is set')
|
|
20
|
+
assrt.ok(result_without.indexOf('Object.defineProperties') is -1, 'Object.defineProperties should not appear when omit_function_metadata is set')
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
# globals: assrt
|
|
2
|
+
# vim:fileencoding=utf-8
|
|
3
|
+
#
|
|
4
|
+
# python_compat.pyj
|
|
5
|
+
# Tests for places where RapydScript intentionally differs from Python.
|
|
6
|
+
# Each section documents the Python behavior, the RapydScript behavior, and
|
|
7
|
+
# asserts that RapydScript does what the README says it does.
|
|
8
|
+
|
|
9
|
+
ae = assrt.equal
|
|
10
|
+
ade = assrt.deepEqual
|
|
11
|
+
ok = assrt.ok
|
|
12
|
+
|
|
13
|
+
# ── 1. Function argument leniency — too few arguments ────────────────────────
|
|
14
|
+
# Python: raises TypeError("f() missing 1 required positional argument: 'b'")
|
|
15
|
+
# RapydScript: extra parameters are undefined instead of raising TypeError.
|
|
16
|
+
|
|
17
|
+
def _f_two_args(a, b):
|
|
18
|
+
return b
|
|
19
|
+
|
|
20
|
+
ae(_f_two_args(1), undefined) # b is undefined
|
|
21
|
+
ae(_f_two_args(1, 2), 2) # normal call still works
|
|
22
|
+
|
|
23
|
+
def _f_default(a, b=99):
|
|
24
|
+
return b
|
|
25
|
+
|
|
26
|
+
ae(_f_default(1), 99) # default value used when b not passed
|
|
27
|
+
ae(_f_default(1, 7), 7)
|
|
28
|
+
|
|
29
|
+
# ── 2. Function argument leniency — too many arguments ───────────────────────
|
|
30
|
+
# Python: raises TypeError("f() takes 1 positional argument but 3 were given")
|
|
31
|
+
# RapydScript: extra positional arguments are silently discarded.
|
|
32
|
+
|
|
33
|
+
def _f_one_arg(a):
|
|
34
|
+
return a
|
|
35
|
+
|
|
36
|
+
ae(_f_one_arg(42, 99, 'extra'), 42) # extras silently ignored
|
|
37
|
+
|
|
38
|
+
# ── 3. Positional-only parameter enforcement — lenient ───────────────────────
|
|
39
|
+
# Python: greet(name="Alice") raises TypeError when name is positional-only.
|
|
40
|
+
# RapydScript: the named keyword arg is silently ignored; the parameter gets
|
|
41
|
+
# its positional value or undefined if not provided positionally.
|
|
42
|
+
|
|
43
|
+
def _po_only(a, b, /):
|
|
44
|
+
return [a, b]
|
|
45
|
+
|
|
46
|
+
ade(_po_only(1, 2), [1, 2]) # normal positional call works
|
|
47
|
+
|
|
48
|
+
# Passing 'b' by keyword: keyword is silently ignored, b gets undefined.
|
|
49
|
+
result3 = _po_only(1, b=2)
|
|
50
|
+
ae(result3[0], 1)
|
|
51
|
+
ae(result3[1], undefined) # named value was silently ignored
|
|
52
|
+
|
|
53
|
+
# ── 4. Keyword-only parameter enforcement — lenient ──────────────────────────
|
|
54
|
+
# Python: f(1, 2) raises TypeError when b is keyword-only (after bare *).
|
|
55
|
+
# RapydScript: positional value is accepted without error.
|
|
56
|
+
|
|
57
|
+
def _kw_only(a, *, b=10):
|
|
58
|
+
return [a, b]
|
|
59
|
+
|
|
60
|
+
ade(_kw_only(1, b=99), [1, 99]) # normal keyword call works
|
|
61
|
+
ade(_kw_only(1), [1, 10]) # default value used
|
|
62
|
+
|
|
63
|
+
# Passing keyword-only param positionally: no TypeError in RapydScript.
|
|
64
|
+
# The extra positional arg is silently discarded; b gets its default value.
|
|
65
|
+
# (Python would raise TypeError here.)
|
|
66
|
+
_kw_result = None
|
|
67
|
+
_kw_error = None
|
|
68
|
+
try:
|
|
69
|
+
_kw_result = _kw_only(1, 99)
|
|
70
|
+
except TypeError as e:
|
|
71
|
+
_kw_error = e
|
|
72
|
+
ok(_kw_error is None) # no TypeError raised
|
|
73
|
+
ae(_kw_result[0], 1)
|
|
74
|
+
ae(_kw_result[1], 10) # default used; positional 99 was discarded
|
|
75
|
+
|
|
76
|
+
# ── 5. `is` operator — maps to JavaScript === ────────────────────────────────
|
|
77
|
+
# Python: `is` checks object identity (id(a) == id(b)).
|
|
78
|
+
# RapydScript: `is` compiles to ===, which is strict value equality for primitives.
|
|
79
|
+
# Most cases behave identically, but NaN is a notable divergence.
|
|
80
|
+
|
|
81
|
+
ae(1 is 1, True)
|
|
82
|
+
ae('hello' is 'hello', True)
|
|
83
|
+
ae(None is None, True)
|
|
84
|
+
|
|
85
|
+
# NaN: RapydScript special-cases `x is NaN` → compiles to isNaN(x).
|
|
86
|
+
# This is a deliberate deviation from `===` to make NaN checks useful.
|
|
87
|
+
# (Plain `NaN === NaN` is always false in JS, so `is NaN` would be useless.)
|
|
88
|
+
ok(42 / 'x' is NaN) # compiles to: isNaN(42 / 'x') → true
|
|
89
|
+
ok(42 is not NaN) # compiles to: !isNaN(42) → true
|
|
90
|
+
|
|
91
|
+
# ── 6. Arithmetic coercion: number + string ───────────────────────────────────
|
|
92
|
+
# Python: 1 + '1' raises TypeError.
|
|
93
|
+
# RapydScript: delegates to JavaScript which coerces 1 to '1', giving '11'.
|
|
94
|
+
|
|
95
|
+
ae(1 + '1', '11')
|
|
96
|
+
ae('prefix_' + 42, 'prefix_42')
|
|
97
|
+
|
|
98
|
+
# ── 7. List + list concatenation ─────────────────────────────────────────────
|
|
99
|
+
# Python: [1] + [2] returns [1, 2].
|
|
100
|
+
# RapydScript: list + list returns a new concatenated list (Python semantics).
|
|
101
|
+
# The + operator compiles to ρσ_list_add which uses Array.concat when both sides are arrays.
|
|
102
|
+
|
|
103
|
+
ade([1] + [2], [1, 2])
|
|
104
|
+
ade([1, 2] + [3, 4], [1, 2, 3, 4])
|
|
105
|
+
|
|
106
|
+
# Variables holding lists also work:
|
|
107
|
+
_a7 = [1, 2]
|
|
108
|
+
_b7 = [3, 4]
|
|
109
|
+
ade(_a7 + _b7, [1, 2, 3, 4])
|
|
110
|
+
|
|
111
|
+
# += extends the list in-place (Python semantics — same object is mutated):
|
|
112
|
+
_a8 = [1, 2]
|
|
113
|
+
_b8 = _a8
|
|
114
|
+
_a8 += [3, 4]
|
|
115
|
+
ade(_a8, [1, 2, 3, 4])
|
|
116
|
+
ade(_b8, [1, 2, 3, 4]) # _b8 still points to same object, now extended
|
|
117
|
+
|
|
118
|
+
# ── 8. Ordering operators on lists — JS string coercion ──────────────────────
|
|
119
|
+
# Python: [10] < [9] compares element-wise → False (10 > 9).
|
|
120
|
+
# RapydScript: < coerces to string → '10' < '9' is True (char comparison).
|
|
121
|
+
# This is a common gotcha: single-element lists with multi-digit numbers.
|
|
122
|
+
|
|
123
|
+
# RapydScript lists have a Python-style __str__, so [10].toString() = '[10]'.
|
|
124
|
+
# String compare of '[10]' vs '[9]': '[' = '[', then '1' < '9' → [10] < [9] is True.
|
|
125
|
+
ok([10] < [9]) # True in RapydScript (string: '[10]' < '[9]')
|
|
126
|
+
ae([9] < [10], False) # False (string: '[9]' > '[10]')
|
|
127
|
+
|
|
128
|
+
# Compare: Python uses numeric element-wise ordering, so [10] > [9] (10 > 9).
|
|
129
|
+
# RapydScript gives the opposite result for these specific values.
|
|
130
|
+
|
|
131
|
+
# For correct numeric comparisons, compare elements explicitly:
|
|
132
|
+
ok(10 > 9) # direct numeric comparison always works
|
|
133
|
+
|
|
134
|
+
# ── 9. List sort() — Python numeric sort (jssort() for JS lexicographic) ──────
|
|
135
|
+
# list.sort() now does Python-style numeric ordering by default (in-place).
|
|
136
|
+
# list.jssort() gives the native JS sort (lexicographic, for JS interop).
|
|
137
|
+
|
|
138
|
+
lst_py = [10, 9, 2]
|
|
139
|
+
lst_py.sort() # Python-style numeric sort
|
|
140
|
+
ade(lst_py, [2, 9, 10])
|
|
141
|
+
|
|
142
|
+
# sort with key and reverse also work:
|
|
143
|
+
lst_rev = [3, 1, 2]
|
|
144
|
+
lst_rev.sort(reverse=True)
|
|
145
|
+
ade(lst_rev, [3, 2, 1])
|
|
146
|
+
|
|
147
|
+
# jssort() is the native JS lexicographic sort (for JS interop):
|
|
148
|
+
lst_js = [10, 9, 2]
|
|
149
|
+
lst_js.jssort() # JS sort: compares as strings '10', '9', '2'
|
|
150
|
+
ade(lst_js, [10, 2, 9]) # lexicographic: '1' < '2' < '9'
|
|
151
|
+
|
|
152
|
+
# ── 10. List pop() — Python bounds-checked pop (jspop() for JS native) ────────
|
|
153
|
+
# list.pop(i) now behaves like Python: raises IndexError for out-of-bounds index.
|
|
154
|
+
# list.jspop() is the native JS pop — no bounds check, always pops the last element.
|
|
155
|
+
|
|
156
|
+
lst10 = [1, 2, 3]
|
|
157
|
+
ae(lst10.pop(), 3) # Python pop: removes and returns last element
|
|
158
|
+
ade(lst10, [1, 2])
|
|
159
|
+
|
|
160
|
+
lst10b = [1, 2, 3]
|
|
161
|
+
ae(lst10b.pop(0), 1) # Python pop with index: removes and returns element at 0
|
|
162
|
+
ade(lst10b, [2, 3])
|
|
163
|
+
|
|
164
|
+
# pop raises IndexError for out-of-bounds:
|
|
165
|
+
_caught10 = False
|
|
166
|
+
try:
|
|
167
|
+
[1].pop(99)
|
|
168
|
+
except IndexError:
|
|
169
|
+
_caught10 = True
|
|
170
|
+
ok(_caught10)
|
|
171
|
+
|
|
172
|
+
# jspop() is the native JS pop — ignores arguments, always pops the last element:
|
|
173
|
+
lst10c = [1, 2, 3]
|
|
174
|
+
ae(lst10c.jspop(), 3) # JS pop: always removes and returns the last element
|
|
175
|
+
ade(lst10c, [1, 2])
|
|
176
|
+
|
|
177
|
+
# ── 11. Default dict (JS object) — missing key returns undefined ──────────────
|
|
178
|
+
# Python: d['missing'] raises KeyError.
|
|
179
|
+
# RapydScript (no dict_literals): missing key returns undefined.
|
|
180
|
+
|
|
181
|
+
d11 = {'a': 1, 'b': 2}
|
|
182
|
+
ae(d11['a'], 1)
|
|
183
|
+
ae(d11['missing'], undefined) # no KeyError
|
|
184
|
+
|
|
185
|
+
# With dict_literals, Python behavior is enabled:
|
|
186
|
+
def _test_keyerror():
|
|
187
|
+
from __python__ import dict_literals, overload_getitem
|
|
188
|
+
d = {'a': 1}
|
|
189
|
+
caught = False
|
|
190
|
+
try:
|
|
191
|
+
x = d['missing']
|
|
192
|
+
except KeyError:
|
|
193
|
+
caught = True
|
|
194
|
+
ok(caught)
|
|
195
|
+
_test_keyerror()
|
|
196
|
+
|
|
197
|
+
# ── 12. Default dict — numeric keys auto-converted to strings ─────────────────
|
|
198
|
+
# Python: {1: 'one'} keeps integer key 1.
|
|
199
|
+
# RapydScript JS object: numeric keys become strings. d[1] and d['1'] are the same.
|
|
200
|
+
|
|
201
|
+
d12 = {}
|
|
202
|
+
d12[1] = 'one'
|
|
203
|
+
ae(d12[1], 'one')
|
|
204
|
+
ae(d12['1'], 'one') # same slot — number keys coerced to string
|
|
205
|
+
|
|
206
|
+
# ── 13. Default dict — keys accessible as attributes ─────────────────────────
|
|
207
|
+
# Python dicts: d.foo is always an AttributeError (use d['foo'] or d.get('foo')).
|
|
208
|
+
# RapydScript JS object: d.foo and d['foo'] access the same slot.
|
|
209
|
+
|
|
210
|
+
d13 = {'hello': 42, 'world': 99}
|
|
211
|
+
ae(d13.hello, 42)
|
|
212
|
+
ae(d13['hello'], 42)
|
|
213
|
+
d13.newkey = 'set via attr'
|
|
214
|
+
ae(d13['newkey'], 'set via attr')
|
|
215
|
+
|
|
216
|
+
# ── 14. String methods not on string instances by default ─────────────────────
|
|
217
|
+
# Python: 'hello'.strip() works directly on the string.
|
|
218
|
+
# RapydScript (default): Python string methods live on the `str` object,
|
|
219
|
+
# not on string instances. Native JS methods (upper, toLowerCase, etc.) do work.
|
|
220
|
+
|
|
221
|
+
s14 = ' hello '
|
|
222
|
+
ae(str.strip(s14), 'hello') # Python method via str object
|
|
223
|
+
ae(s14.trim(), 'hello') # JS native trim() works on instances
|
|
224
|
+
|
|
225
|
+
s14b = 'hello world'
|
|
226
|
+
ae(str.upper(s14b), 'HELLO WORLD') # Python via str
|
|
227
|
+
ae(s14b.toUpperCase(), 'HELLO WORLD') # JS native
|
|
228
|
+
|
|
229
|
+
# After strings(), Python methods work on instances:
|
|
230
|
+
from pythonize import strings
|
|
231
|
+
strings()
|
|
232
|
+
|
|
233
|
+
ae(' hello '.strip(), 'hello')
|
|
234
|
+
ae('hello world'.upper(), 'HELLO WORLD')
|
|
235
|
+
|
|
236
|
+
# ── 15. strings() leaves split() and replace() as JS versions ─────────────────
|
|
237
|
+
# (strings() is active from section 14 above)
|
|
238
|
+
# Python str.split() with no args: splits on any whitespace, strips leading/trailing.
|
|
239
|
+
# JS String.prototype.split() with no args: returns the whole string in a 1-element array.
|
|
240
|
+
|
|
241
|
+
# Python semantics via str module:
|
|
242
|
+
ade(str.split('a b'), ['a', 'b']) # Python split on any whitespace
|
|
243
|
+
ade(str.split('a b c'), ['a', 'b', 'c'])
|
|
244
|
+
|
|
245
|
+
# After strings(), .split() is STILL the JS version (by design):
|
|
246
|
+
ade('a b'.split(), ['a b']) # JS: no-arg split = one-element array
|
|
247
|
+
ade('a b'.split(' '), ['a', 'b']) # JS split with separator still works
|
|
248
|
+
|
|
249
|
+
# Python str.replace() vs JS String.prototype.replace():
|
|
250
|
+
# Python replaces ALL occurrences by default.
|
|
251
|
+
# JS .replace() with a string pattern only replaces the FIRST occurrence.
|
|
252
|
+
ae(str.replace('aaa', 'a', 'b'), 'bbb') # Python: replaces all
|
|
253
|
+
ae('aaa'.replace('a', 'b'), 'baa') # JS: replaces only first
|
|
254
|
+
|
|
255
|
+
# ── 16. Method binding — not automatic ──────────────────────────────────────
|
|
256
|
+
# Python: bound methods carry a reference to self.
|
|
257
|
+
# RapydScript (default): methods are unbound; extracting a method loses self.
|
|
258
|
+
|
|
259
|
+
class _Bindable:
|
|
260
|
+
def __init__(self, value):
|
|
261
|
+
self.value = value
|
|
262
|
+
def get_value(self):
|
|
263
|
+
return self.value if self else None # guard: self may be falsy if unbound
|
|
264
|
+
|
|
265
|
+
obj16 = _Bindable(55)
|
|
266
|
+
ae(obj16.get_value(), 55) # direct call works
|
|
267
|
+
|
|
268
|
+
unbound = obj16.get_value
|
|
269
|
+
ae(unbound(), None) # self is not obj16; guard returns None
|
|
270
|
+
|
|
271
|
+
# With bound_methods flag, extraction works correctly:
|
|
272
|
+
class _AutoBound:
|
|
273
|
+
from __python__ import bound_methods
|
|
274
|
+
def __init__(self, value):
|
|
275
|
+
self.value = value
|
|
276
|
+
def get_value(self):
|
|
277
|
+
return self.value
|
|
278
|
+
|
|
279
|
+
obj16b = _AutoBound(77)
|
|
280
|
+
bound_fn = obj16b.get_value
|
|
281
|
+
ae(bound_fn(), 77) # self is correctly bound to obj16b
|
|
282
|
+
|
|
283
|
+
# ── 17. `global` keyword — prefers outer scope over module global ─────────────
|
|
284
|
+
# Python: `global x` inside a nested function always refers to the module-level x.
|
|
285
|
+
# RapydScript: if x also exists in an intermediate outer scope, that outer scope
|
|
286
|
+
# variable is used instead of the module-level one.
|
|
287
|
+
#
|
|
288
|
+
# NOTE: This is a documented incompatibility. The README says:
|
|
289
|
+
# "if a variable is present both in the outer scope and the global scope,
|
|
290
|
+
# the variable from the outer scope will be used"
|
|
291
|
+
#
|
|
292
|
+
# SKIP: This behavior is inherently tied to how JavaScript closures work and is
|
|
293
|
+
# difficult to exercise meaningfully in a compiled-to-JS test environment,
|
|
294
|
+
# because the "global" and "outer" scopes collapse to different things
|
|
295
|
+
# depending on context. The behavior is documented but not testable with a
|
|
296
|
+
# simple assertion here without knowing the exact generated JS scope.
|
|
297
|
+
#
|
|
298
|
+
# Example (would be run in a context where x is BOTH a global and a local):
|
|
299
|
+
# _x17 = 'global_value'
|
|
300
|
+
# def _outer17():
|
|
301
|
+
# _x17 = 'outer_value'
|
|
302
|
+
# def _inner17():
|
|
303
|
+
# global _x17
|
|
304
|
+
# return _x17
|
|
305
|
+
# return _inner17()
|
|
306
|
+
# # Python: _outer17() == 'global_value' (global keyword forces module level)
|
|
307
|
+
# # RapydScript: _outer17() == 'outer_value' (outer scope wins)
|
|
308
|
+
|
|
309
|
+
# ── 18. Truthiness — JS defaults ─────────────────────────────────────────────
|
|
310
|
+
# Tested more fully in python_features.pyj #40, but demonstrating JS defaults here.
|
|
311
|
+
# Without `from __python__ import truthiness`:
|
|
312
|
+
|
|
313
|
+
_empty_list = []
|
|
314
|
+
_empty_dict = {}
|
|
315
|
+
|
|
316
|
+
# In JS, empty arrays and objects are truthy:
|
|
317
|
+
ok(_empty_list) # True (JS truthiness: any object is truthy)
|
|
318
|
+
ok(_empty_dict) # True (JS truthiness: any object is truthy)
|
|
319
|
+
|
|
320
|
+
# Enable Python truthiness:
|
|
321
|
+
from __python__ import truthiness
|
|
322
|
+
|
|
323
|
+
ok(not []) # False (Python semantics)
|
|
324
|
+
ok(not {}) # False (Python semantics)
|
|
325
|
+
ok([1]) # True
|
|
326
|
+
ok({'a': 1}) # True
|
package/test/python_features.pyj
CHANGED
|
@@ -228,6 +228,9 @@ ae(str.isspace('a '), False)
|
|
|
228
228
|
ae(str.casefold('Hello World'), 'hello world')
|
|
229
229
|
ae(str.casefold('ABC'), 'abc')
|
|
230
230
|
ae(str.casefold('already'), 'already')
|
|
231
|
+
# Unicode: Latin capital letters with diacritics fold to lowercase equivalents
|
|
232
|
+
ae(str.casefold('ÄÖÜ'), 'äöü')
|
|
233
|
+
ae(str.casefold('ÄÖÜ'), str.casefold('äöü'))
|
|
231
234
|
|
|
232
235
|
# ── 12. str.removeprefix() / str.removesuffix() ──────────────────────────────
|
|
233
236
|
# STATUS: ✓ WORKS (added in this release)
|
|
@@ -496,7 +499,7 @@ _test_raise_from_none()
|
|
|
496
499
|
# STATUS: ✗ NOT ENFORCED — __slots__ is accepted but does not restrict attrs.
|
|
497
500
|
|
|
498
501
|
# ── 24. Nested classes ───────────────────────────────────────────────────────
|
|
499
|
-
# STATUS:
|
|
502
|
+
# STATUS: ✓ WORKS — added in commit 44c9802; tested fully in test/classes.pyj
|
|
500
503
|
|
|
501
504
|
# ── 25. Generator .throw() ───────────────────────────────────────────────────
|
|
502
505
|
# STATUS: ✓ WORKS
|
|
@@ -937,21 +940,29 @@ ae(_c(), 3)
|
|
|
937
940
|
# STATUS: ✗ NOT SUPPORTED — no attribute-lookup override.
|
|
938
941
|
|
|
939
942
|
# ── 50. __format__ dunder ────────────────────────────────────────────────────
|
|
940
|
-
# STATUS:
|
|
941
|
-
#
|
|
942
|
-
# SKIP:
|
|
943
|
-
# class _Fmt:
|
|
944
|
-
# def __format__(self, spec):
|
|
945
|
-
# return '<' + spec + '>'
|
|
946
|
-
# ae(format(_Fmt(), 'test'), '<test>')
|
|
943
|
+
# STATUS: ✓ WORKS — format(obj, spec) dispatches to obj.__format__(spec).
|
|
944
|
+
# Tested fully in test/str.pyj.
|
|
947
945
|
|
|
948
946
|
# ── 51. __class_getitem__ ────────────────────────────────────────────────────
|
|
949
|
-
# STATUS:
|
|
950
|
-
#
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
947
|
+
# STATUS: ✓ WORKS — Class[item] dispatches to __class_getitem__(cls, item).
|
|
948
|
+
# Subclasses inherit __class_getitem__; cls is set to the calling class.
|
|
949
|
+
|
|
950
|
+
class _Box51:
|
|
951
|
+
def __class_getitem__(cls, item):
|
|
952
|
+
return cls
|
|
953
|
+
|
|
954
|
+
ok(_Box51[int] is _Box51)
|
|
955
|
+
|
|
956
|
+
class _BoxNamed51:
|
|
957
|
+
def __class_getitem__(cls, item):
|
|
958
|
+
return cls.__name__ + '[' + str(item) + ']'
|
|
959
|
+
|
|
960
|
+
ok(_BoxNamed51[42] == '_BoxNamed51[42]')
|
|
961
|
+
|
|
962
|
+
class _BoxChild51(_BoxNamed51):
|
|
963
|
+
pass
|
|
964
|
+
|
|
965
|
+
ok(_BoxChild51[99] == '_BoxChild51[99]')
|
|
955
966
|
|
|
956
967
|
# ── 52. __init_subclass__ ────────────────────────────────────────────────────
|
|
957
968
|
# STATUS: ✗ NOT SUPPORTED — no subclass hook.
|
|
@@ -1087,9 +1098,9 @@ _check_unhashable({1, 2})
|
|
|
1087
1098
|
_check_unhashable({'a': 1})
|
|
1088
1099
|
|
|
1089
1100
|
# ── 55. format(value, spec) builtin ──────────────────────────────────────────
|
|
1090
|
-
# STATUS:
|
|
1091
|
-
#
|
|
1092
|
-
#
|
|
1101
|
+
# STATUS: ✓ WORKS — format(value, spec) is defined and dispatches to __format__.
|
|
1102
|
+
# Tested fully in test/str.pyj.
|
|
1103
|
+
#
|
|
1093
1104
|
# ae(format(3.14159, '.2f'), '3.14')
|
|
1094
1105
|
|
|
1095
1106
|
# ── 56. next(iterator[, default]) builtin ────────────────────────────────────
|
|
@@ -1132,20 +1143,60 @@ ae(next(g), 200)
|
|
|
1132
1143
|
ae(next(g, 'stop'), 'stop')
|
|
1133
1144
|
|
|
1134
1145
|
# ── 57. iter(callable, sentinel) two-arg form ────────────────────────────────
|
|
1135
|
-
#
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1146
|
+
# Basic usage: collect values until sentinel
|
|
1147
|
+
_count57 = [0]
|
|
1148
|
+
def _counter57():
|
|
1149
|
+
_count57[0] += 1
|
|
1150
|
+
return _count57[0]
|
|
1151
|
+
ade(list(iter(_counter57, 3)), [1, 2])
|
|
1152
|
+
# Sentinel never reached: stops at exact match
|
|
1153
|
+
_vals57 = ['a', 'b', 'c', None]
|
|
1154
|
+
_idx57 = [0]
|
|
1155
|
+
def _src57():
|
|
1156
|
+
v = _vals57[_idx57[0]]
|
|
1157
|
+
_idx57[0] += 1
|
|
1158
|
+
return v
|
|
1159
|
+
ade(list(iter(_src57, None)), ['a', 'b', 'c'])
|
|
1160
|
+
# Sentinel on first call returns empty iterator
|
|
1161
|
+
_first57 = [0]
|
|
1162
|
+
def _fn57():
|
|
1163
|
+
_first57[0] += 1
|
|
1164
|
+
return 'stop'
|
|
1165
|
+
ade(list(iter(_fn57, 'stop')), [])
|
|
1166
|
+
# Works with for-loop
|
|
1167
|
+
_out57 = []
|
|
1168
|
+
_i57 = [0]
|
|
1169
|
+
def _seq57():
|
|
1170
|
+
_i57[0] += 1
|
|
1171
|
+
return _i57[0]
|
|
1172
|
+
for _v57 in iter(_seq57, 4):
|
|
1173
|
+
_out57.append(_v57)
|
|
1174
|
+
ade(_out57, [1, 2, 3])
|
|
1175
|
+
# Works with next()
|
|
1176
|
+
_j57 = [0]
|
|
1177
|
+
def _f57():
|
|
1178
|
+
_j57[0] += 1
|
|
1179
|
+
return _j57[0]
|
|
1180
|
+
_it57 = iter(_f57, 5)
|
|
1181
|
+
ae(next(_it57), 1)
|
|
1182
|
+
ae(next(_it57), 2)
|
|
1183
|
+
ae(next(_it57), 3)
|
|
1184
|
+
ae(next(_it57), 4)
|
|
1185
|
+
ae(next(_it57, 'done'), 'done')
|
|
1186
|
+
# Callable object with __call__
|
|
1187
|
+
class _Caller57:
|
|
1188
|
+
def __init__(self):
|
|
1189
|
+
self.n = 0
|
|
1190
|
+
def __call__(self):
|
|
1191
|
+
self.n += 1
|
|
1192
|
+
return self.n
|
|
1193
|
+
_caller57 = _Caller57()
|
|
1194
|
+
ade(list(iter(_caller57, 3)), [1, 2])
|
|
1141
1195
|
|
|
1142
1196
|
# ── 58. slice() as builtin object ────────────────────────────────────────────
|
|
1143
|
-
# STATUS:
|
|
1144
|
-
#
|
|
1145
|
-
#
|
|
1146
|
-
# ae(s.start, 1)
|
|
1147
|
-
# ae(s.stop, 5)
|
|
1148
|
-
# ae(s.step, 2)
|
|
1197
|
+
# STATUS: ✓ WORKS — implemented (see item #63 below for full tests).
|
|
1198
|
+
# NOTE: This item was previously marked NOT IMPLEMENTED but was added in a
|
|
1199
|
+
# recent release. Full tests live in item #63 and test/slice.pyj.
|
|
1149
1200
|
|
|
1150
1201
|
# ── 59. complex() builtin ────────────────────────────────────────────────────
|
|
1151
1202
|
# STATUS: ✗ NOT SUPPORTED — no complex number type.
|
|
@@ -1182,3 +1233,52 @@ def _ko(a, *, b):
|
|
|
1182
1233
|
|
|
1183
1234
|
ade(_ko(1, b=2), [1, 2])
|
|
1184
1235
|
ade(_ko(b=2, a=1), [1, 2])
|
|
1236
|
+
|
|
1237
|
+
# ── 63. slice() builtin ───────────────────────────────────────────────────────
|
|
1238
|
+
# STATUS: ✓ WORKS
|
|
1239
|
+
# Subscript read/write tests (lst[s], lst[s]=val) are in test/slice.pyj
|
|
1240
|
+
# because they require `from __python__ import overload_getitem`.
|
|
1241
|
+
|
|
1242
|
+
# Construction forms
|
|
1243
|
+
_s1 = slice(5)
|
|
1244
|
+
ae(_s1.start, None)
|
|
1245
|
+
ae(_s1.stop, 5)
|
|
1246
|
+
ae(_s1.step, None)
|
|
1247
|
+
|
|
1248
|
+
_s2 = slice(2, 8)
|
|
1249
|
+
ae(_s2.start, 2)
|
|
1250
|
+
ae(_s2.stop, 8)
|
|
1251
|
+
ae(_s2.step, None)
|
|
1252
|
+
|
|
1253
|
+
_s3 = slice(1, 9, 2)
|
|
1254
|
+
ae(_s3.start, 1)
|
|
1255
|
+
ae(_s3.stop, 9)
|
|
1256
|
+
ae(_s3.step, 2)
|
|
1257
|
+
|
|
1258
|
+
# None values preserved
|
|
1259
|
+
_sn = slice(None, None, -1)
|
|
1260
|
+
ae(_sn.start, None)
|
|
1261
|
+
ae(_sn.stop, None)
|
|
1262
|
+
ae(_sn.step, -1)
|
|
1263
|
+
|
|
1264
|
+
# indices() method
|
|
1265
|
+
ade(_s2.indices(10), [2, 8, 1])
|
|
1266
|
+
ade(slice(None, None, 1).indices(5), [0, 5, 1])
|
|
1267
|
+
ade(slice(-3, -1).indices(10), [7, 9, 1])
|
|
1268
|
+
ade(slice(None, None, -1).indices(5), [4, -1, -1])
|
|
1269
|
+
|
|
1270
|
+
# repr / str
|
|
1271
|
+
ae(str(slice(1, 5, None)), 'slice(1, 5, None)')
|
|
1272
|
+
ae(str(slice(None, 5, 2)), 'slice(None, 5, 2)')
|
|
1273
|
+
|
|
1274
|
+
# Equality
|
|
1275
|
+
ok(slice(1, 5) == slice(1, 5))
|
|
1276
|
+
ok(not (slice(1, 5) == slice(1, 6)))
|
|
1277
|
+
|
|
1278
|
+
# isinstance
|
|
1279
|
+
ok(isinstance(slice(1, 5), slice))
|
|
1280
|
+
|
|
1281
|
+
# Delete with slice object uses ρσ_delitem (no overload_getitem needed)
|
|
1282
|
+
_b = [0, 1, 2, 3, 4]
|
|
1283
|
+
del _b[slice(1, 3)]
|
|
1284
|
+
ade(_b, [0, 3, 4])
|