rapydscript-ns 0.8.1 → 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 +1 -4
- 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
|
@@ -0,0 +1,1184 @@
|
|
|
1
|
+
# globals: assrt
|
|
2
|
+
# vim:fileencoding=utf-8
|
|
3
|
+
#
|
|
4
|
+
# python_features.pyj
|
|
5
|
+
# Survey of Python syntax features and their support status in RapydScript.
|
|
6
|
+
# Sections marked SKIP are known parse-time or runtime unsupported features.
|
|
7
|
+
# All other tests exercise features that were uncertain before this survey.
|
|
8
|
+
|
|
9
|
+
ae = assrt.equal
|
|
10
|
+
ade = assrt.deepEqual
|
|
11
|
+
ok = assrt.ok
|
|
12
|
+
|
|
13
|
+
# ── 1. super() with zero arguments ──────────────────────────────────────────
|
|
14
|
+
# STATUS: ✓ WORKS
|
|
15
|
+
|
|
16
|
+
class _Base:
|
|
17
|
+
def greet(self):
|
|
18
|
+
return 'base'
|
|
19
|
+
|
|
20
|
+
class _Child(_Base):
|
|
21
|
+
def greet(self):
|
|
22
|
+
return super().greet() + '+child'
|
|
23
|
+
|
|
24
|
+
ae(_Child().greet(), 'base+child')
|
|
25
|
+
|
|
26
|
+
class _Child2(_Base):
|
|
27
|
+
def greet(self):
|
|
28
|
+
return super(_Child2, self).greet() + '+child2'
|
|
29
|
+
|
|
30
|
+
ae(_Child2().greet(), 'base+child2')
|
|
31
|
+
|
|
32
|
+
# ── 2. Multiple exception types — comma form ─────────────────────────────────
|
|
33
|
+
# STATUS: ✓ WORKS
|
|
34
|
+
|
|
35
|
+
def _multi_except():
|
|
36
|
+
for exc_cls in [TypeError, ValueError]:
|
|
37
|
+
caught_type = None
|
|
38
|
+
try:
|
|
39
|
+
raise exc_cls('test')
|
|
40
|
+
except TypeError, ValueError as e:
|
|
41
|
+
caught_type = type(e)
|
|
42
|
+
ae(caught_type, exc_cls)
|
|
43
|
+
|
|
44
|
+
_multi_except()
|
|
45
|
+
|
|
46
|
+
# ── 3. Multiple exception types — tuple form ─────────────────────────────────
|
|
47
|
+
# STATUS: ✓ WORKS (added in this release)
|
|
48
|
+
|
|
49
|
+
def _multi_except_tuple():
|
|
50
|
+
caught = False
|
|
51
|
+
try:
|
|
52
|
+
raise ValueError('boom')
|
|
53
|
+
except (TypeError, ValueError) as e:
|
|
54
|
+
caught = True
|
|
55
|
+
ae(caught, True)
|
|
56
|
+
|
|
57
|
+
_multi_except_tuple()
|
|
58
|
+
|
|
59
|
+
# ── 4. try / else ────────────────────────────────────────────────────────────
|
|
60
|
+
# STATUS: ✓ WORKS
|
|
61
|
+
|
|
62
|
+
def _try_else():
|
|
63
|
+
ran = []
|
|
64
|
+
try:
|
|
65
|
+
x = 1
|
|
66
|
+
except:
|
|
67
|
+
ran.push('except')
|
|
68
|
+
else:
|
|
69
|
+
ran.push('else')
|
|
70
|
+
ade(ran, ['else'])
|
|
71
|
+
|
|
72
|
+
ran = []
|
|
73
|
+
try:
|
|
74
|
+
raise ValueError('oops')
|
|
75
|
+
except ValueError:
|
|
76
|
+
ran.push('except')
|
|
77
|
+
else:
|
|
78
|
+
ran.push('else')
|
|
79
|
+
ade(ran, ['except'])
|
|
80
|
+
|
|
81
|
+
_try_else()
|
|
82
|
+
|
|
83
|
+
# ── 5. for / else ────────────────────────────────────────────────────────────
|
|
84
|
+
# STATUS: ✓ WORKS (added in this release)
|
|
85
|
+
|
|
86
|
+
def _for_else():
|
|
87
|
+
# else runs when loop finishes without break
|
|
88
|
+
ran_else = False
|
|
89
|
+
for x in [1, 2, 3]:
|
|
90
|
+
pass
|
|
91
|
+
else:
|
|
92
|
+
ran_else = True
|
|
93
|
+
ae(ran_else, True)
|
|
94
|
+
|
|
95
|
+
# else is skipped when break occurs
|
|
96
|
+
ran_else = False
|
|
97
|
+
for x in [1, 2, 3]:
|
|
98
|
+
if x == 2:
|
|
99
|
+
break
|
|
100
|
+
else:
|
|
101
|
+
ran_else = True
|
|
102
|
+
ae(ran_else, False)
|
|
103
|
+
|
|
104
|
+
# else runs for empty iterable (no break possible)
|
|
105
|
+
ran_else = False
|
|
106
|
+
for x in []:
|
|
107
|
+
pass
|
|
108
|
+
else:
|
|
109
|
+
ran_else = True
|
|
110
|
+
ae(ran_else, True)
|
|
111
|
+
|
|
112
|
+
# typical search pattern: find item or report not found
|
|
113
|
+
def find_item(lst, target):
|
|
114
|
+
for item in lst:
|
|
115
|
+
if item == target:
|
|
116
|
+
return 'found'
|
|
117
|
+
else:
|
|
118
|
+
return 'not found'
|
|
119
|
+
|
|
120
|
+
ae(find_item([1, 2, 3], 2), 'found')
|
|
121
|
+
ae(find_item([1, 2, 3], 9), 'not found')
|
|
122
|
+
|
|
123
|
+
# nested loops: break in inner should NOT affect outer for/else
|
|
124
|
+
outer_else = False
|
|
125
|
+
for i in [1, 2]:
|
|
126
|
+
for j in [10, 20]:
|
|
127
|
+
if j == 10:
|
|
128
|
+
break # breaks inner only
|
|
129
|
+
else:
|
|
130
|
+
outer_else = True
|
|
131
|
+
ae(outer_else, True)
|
|
132
|
+
|
|
133
|
+
_for_else()
|
|
134
|
+
|
|
135
|
+
# ── 6. Multiple context managers in a single with statement ─────────────────
|
|
136
|
+
# STATUS: ✓ WORKS — enters L→R, exits R→L (LIFO, Python-correct)
|
|
137
|
+
|
|
138
|
+
class _CM:
|
|
139
|
+
def __init__(self, name, log):
|
|
140
|
+
self.name = name
|
|
141
|
+
self.log = log
|
|
142
|
+
def __enter__(self):
|
|
143
|
+
self.log.push('enter:' + self.name)
|
|
144
|
+
return self
|
|
145
|
+
def __exit__(self):
|
|
146
|
+
self.log.push('exit:' + self.name)
|
|
147
|
+
return False
|
|
148
|
+
|
|
149
|
+
def _test_multi_with():
|
|
150
|
+
log = []
|
|
151
|
+
with _CM('a', log) as a, _CM('b', log) as b:
|
|
152
|
+
log.push('body')
|
|
153
|
+
ae(log[0], 'enter:a')
|
|
154
|
+
ae(log[1], 'enter:b')
|
|
155
|
+
ae(log[2], 'body')
|
|
156
|
+
# LIFO exit order: b exits before a (Python-correct)
|
|
157
|
+
ae(log[3], 'exit:b')
|
|
158
|
+
ae(log[4], 'exit:a')
|
|
159
|
+
|
|
160
|
+
_test_multi_with()
|
|
161
|
+
|
|
162
|
+
# ── 7. callable() ────────────────────────────────────────────────────────────
|
|
163
|
+
# STATUS: ✓ WORKS — checks typeof function AND __call__ method
|
|
164
|
+
|
|
165
|
+
ae(callable(len), True)
|
|
166
|
+
ae(callable(42), False)
|
|
167
|
+
ae(callable(None), False)
|
|
168
|
+
ae(callable(def(): pass;), True)
|
|
169
|
+
|
|
170
|
+
class _WithCall:
|
|
171
|
+
def __call__(self):
|
|
172
|
+
return 42
|
|
173
|
+
|
|
174
|
+
class _WithoutCall:
|
|
175
|
+
pass
|
|
176
|
+
|
|
177
|
+
ae(callable(_WithCall()), True)
|
|
178
|
+
ae(callable(_WithoutCall()), False)
|
|
179
|
+
|
|
180
|
+
# ── 8. round(x, ndigits) ─────────────────────────────────────────────────────
|
|
181
|
+
# STATUS: ✓ WORKS (added in this release)
|
|
182
|
+
|
|
183
|
+
ae(round(3), 3)
|
|
184
|
+
ae(round(3.7), 4)
|
|
185
|
+
ae(round(3.14159, 2), 3.14)
|
|
186
|
+
ae(round(3.145, 2), 3.15)
|
|
187
|
+
ae(round(1234, -2), 1200)
|
|
188
|
+
ae(round(0.5), 1)
|
|
189
|
+
|
|
190
|
+
# ── 9. enumerate(iterable, start) ────────────────────────────────────────────
|
|
191
|
+
# STATUS: ✓ WORKS (added in this release)
|
|
192
|
+
|
|
193
|
+
ade(list(enumerate(['a', 'b', 'c'], 1)), [[1, 'a'], [2, 'b'], [3, 'c']])
|
|
194
|
+
ade(list(enumerate(['x', 'y'], 5)), [[5, 'x'], [6, 'y']])
|
|
195
|
+
ade(list(enumerate(['a', 'b'])), [[0, 'a'], [1, 'b']]) # default still works
|
|
196
|
+
|
|
197
|
+
# ── 10. String predicates ─────────────────────────────────────────────────────
|
|
198
|
+
# STATUS: ✓ WORKS (added in this release)
|
|
199
|
+
|
|
200
|
+
# isalpha
|
|
201
|
+
ae(str.isalpha('abc'), True)
|
|
202
|
+
ae(str.isalpha('ab3'), False)
|
|
203
|
+
ae(str.isalpha(''), False)
|
|
204
|
+
|
|
205
|
+
# isdigit
|
|
206
|
+
ae(str.isdigit('123'), True)
|
|
207
|
+
ae(str.isdigit('12a'), False)
|
|
208
|
+
ae(str.isdigit(''), False)
|
|
209
|
+
|
|
210
|
+
# isalnum
|
|
211
|
+
ae(str.isalnum('abc123'), True)
|
|
212
|
+
ae(str.isalnum('abc!'), False)
|
|
213
|
+
ae(str.isalnum(''), False)
|
|
214
|
+
|
|
215
|
+
# isidentifier
|
|
216
|
+
ae(str.isidentifier('hello'), True)
|
|
217
|
+
ae(str.isidentifier('hello_world'), True)
|
|
218
|
+
ae(str.isidentifier('123abc'), False)
|
|
219
|
+
ae(str.isidentifier(''), False)
|
|
220
|
+
|
|
221
|
+
# isspace — was already supported; smoke test
|
|
222
|
+
ae(str.isspace(' '), True)
|
|
223
|
+
ae(str.isspace('a '), False)
|
|
224
|
+
|
|
225
|
+
# ── 11. str.casefold() ───────────────────────────────────────────────────────
|
|
226
|
+
# STATUS: ✓ WORKS (added in this release)
|
|
227
|
+
|
|
228
|
+
ae(str.casefold('Hello World'), 'hello world')
|
|
229
|
+
ae(str.casefold('ABC'), 'abc')
|
|
230
|
+
ae(str.casefold('already'), 'already')
|
|
231
|
+
|
|
232
|
+
# ── 12. str.removeprefix() / str.removesuffix() ──────────────────────────────
|
|
233
|
+
# STATUS: ✓ WORKS (added in this release)
|
|
234
|
+
|
|
235
|
+
ae(str.removeprefix('HelloWorld', 'Hello'), 'World')
|
|
236
|
+
ae(str.removeprefix('HelloWorld', 'World'), 'HelloWorld') # no match → unchanged
|
|
237
|
+
ae(str.removeprefix('Hello', ''), 'Hello')
|
|
238
|
+
|
|
239
|
+
ae(str.removesuffix('HelloWorld', 'World'), 'Hello')
|
|
240
|
+
ae(str.removesuffix('HelloWorld', 'Hello'), 'HelloWorld') # no match → unchanged
|
|
241
|
+
ae(str.removesuffix('Hello', ''), 'Hello')
|
|
242
|
+
|
|
243
|
+
# ── 13. String repetition: str * n ───────────────────────────────────────────
|
|
244
|
+
# STATUS: ✓ WORKS with overload_operators flag
|
|
245
|
+
|
|
246
|
+
def _test_str_multiply():
|
|
247
|
+
from __python__ import overload_operators
|
|
248
|
+
ae('ha' * 3, 'hahaha')
|
|
249
|
+
ae(3 * 'ha', 'hahaha')
|
|
250
|
+
ae('x' * 0, '')
|
|
251
|
+
|
|
252
|
+
_test_str_multiply()
|
|
253
|
+
|
|
254
|
+
# ── 14. List repetition: [x] * n ─────────────────────────────────────────────
|
|
255
|
+
# STATUS: ✓ WORKS with overload_operators flag (added in this release)
|
|
256
|
+
|
|
257
|
+
def _test_list_multiply():
|
|
258
|
+
from __python__ import overload_operators
|
|
259
|
+
ade([0] * 3, [0, 0, 0])
|
|
260
|
+
ade([1, 2] * 2, [1, 2, 1, 2])
|
|
261
|
+
ade(3 * [0], [0, 0, 0])
|
|
262
|
+
ade([1] * 0, [])
|
|
263
|
+
|
|
264
|
+
_test_list_multiply()
|
|
265
|
+
|
|
266
|
+
# ── 15. except (Type,) tuple form ────────────────────────────────────────────
|
|
267
|
+
# STATUS: ✓ WORKS (added in this release) — see §3 above
|
|
268
|
+
|
|
269
|
+
# ── 16. frozenset ─────────────────────────────────────────────────────────────
|
|
270
|
+
# STATUS: ✓ WORKS (added in this release)
|
|
271
|
+
|
|
272
|
+
def _test_frozenset():
|
|
273
|
+
# Construction from list
|
|
274
|
+
fs = frozenset([1, 2, 3])
|
|
275
|
+
ae(len(fs), 3)
|
|
276
|
+
ok(1 in fs)
|
|
277
|
+
ok(2 in fs)
|
|
278
|
+
ok(not (4 in fs))
|
|
279
|
+
|
|
280
|
+
# Construction from set
|
|
281
|
+
fs2 = frozenset({4, 5})
|
|
282
|
+
ae(len(fs2), 2)
|
|
283
|
+
ok(4 in fs2)
|
|
284
|
+
|
|
285
|
+
# Empty frozenset
|
|
286
|
+
empty = frozenset()
|
|
287
|
+
ae(len(empty), 0)
|
|
288
|
+
ok(not (1 in empty))
|
|
289
|
+
|
|
290
|
+
# Iteration
|
|
291
|
+
total = 0
|
|
292
|
+
for x in frozenset([10, 20, 30]):
|
|
293
|
+
total += x
|
|
294
|
+
ae(total, 60)
|
|
295
|
+
|
|
296
|
+
# isinstance
|
|
297
|
+
ok(isinstance(fs, frozenset))
|
|
298
|
+
ok(not isinstance([1, 2], frozenset))
|
|
299
|
+
|
|
300
|
+
# copy returns a frozenset with same elements
|
|
301
|
+
fc = fs.copy()
|
|
302
|
+
ok(isinstance(fc, frozenset))
|
|
303
|
+
ae(len(fc), len(fs))
|
|
304
|
+
ok(1 in fc)
|
|
305
|
+
ok(3 in fc)
|
|
306
|
+
|
|
307
|
+
# union — returns frozenset
|
|
308
|
+
u = frozenset([1, 2]).union(frozenset([2, 3]))
|
|
309
|
+
ok(isinstance(u, frozenset))
|
|
310
|
+
ae(len(u), 3)
|
|
311
|
+
ok(1 in u and 2 in u and 3 in u)
|
|
312
|
+
|
|
313
|
+
# union with multiple args
|
|
314
|
+
u2 = frozenset([1]).union(frozenset([2]), frozenset([3]))
|
|
315
|
+
ae(len(u2), 3)
|
|
316
|
+
|
|
317
|
+
# intersection — returns frozenset
|
|
318
|
+
inter = frozenset([1, 2, 3]).intersection(frozenset([2, 3, 4]))
|
|
319
|
+
ok(isinstance(inter, frozenset))
|
|
320
|
+
ae(len(inter), 2)
|
|
321
|
+
ok(2 in inter and 3 in inter)
|
|
322
|
+
ok(not (1 in inter))
|
|
323
|
+
|
|
324
|
+
# difference — returns frozenset
|
|
325
|
+
diff = frozenset([1, 2, 3]).difference(frozenset([2]))
|
|
326
|
+
ok(isinstance(diff, frozenset))
|
|
327
|
+
ae(len(diff), 2)
|
|
328
|
+
ok(1 in diff and 3 in diff)
|
|
329
|
+
ok(not (2 in diff))
|
|
330
|
+
|
|
331
|
+
# symmetric_difference — returns frozenset
|
|
332
|
+
sd = frozenset([1, 2, 3]).symmetric_difference(frozenset([2, 3, 4]))
|
|
333
|
+
ok(isinstance(sd, frozenset))
|
|
334
|
+
ae(len(sd), 2)
|
|
335
|
+
ok(1 in sd and 4 in sd)
|
|
336
|
+
|
|
337
|
+
# issubset / issuperset
|
|
338
|
+
ok(frozenset([1, 2]).issubset(frozenset([1, 2, 3])))
|
|
339
|
+
ok(not frozenset([1, 4]).issubset(frozenset([1, 2, 3])))
|
|
340
|
+
ok(frozenset([1, 2, 3]).issuperset(frozenset([2, 3])))
|
|
341
|
+
ok(not frozenset([1, 2]).issuperset(frozenset([1, 2, 3])))
|
|
342
|
+
|
|
343
|
+
# isdisjoint
|
|
344
|
+
ok(frozenset([1, 2]).isdisjoint(frozenset([3, 4])))
|
|
345
|
+
ok(not frozenset([1, 2]).isdisjoint(frozenset([2, 3])))
|
|
346
|
+
|
|
347
|
+
# __eq__ with another frozenset
|
|
348
|
+
ok(frozenset([1, 2, 3]).__eq__(frozenset([1, 2, 3])))
|
|
349
|
+
ok(not frozenset([1, 2]).__eq__(frozenset([1, 2, 3])))
|
|
350
|
+
|
|
351
|
+
# frozenset and set compare equal when same elements
|
|
352
|
+
ok(frozenset([1, 2, 3]).__eq__({1, 2, 3}))
|
|
353
|
+
ok({1, 2, 3}.__eq__(frozenset([1, 2, 3])))
|
|
354
|
+
|
|
355
|
+
# Mutation raises — these would throw TypeError in Python.
|
|
356
|
+
# frozenset has no add/remove/clear methods (immutable by design).
|
|
357
|
+
ok(not hasattr(fs, 'add'))
|
|
358
|
+
ok(not hasattr(fs, 'remove'))
|
|
359
|
+
ok(not hasattr(fs, 'discard'))
|
|
360
|
+
ok(not hasattr(fs, 'clear'))
|
|
361
|
+
ok(not hasattr(fs, 'update'))
|
|
362
|
+
|
|
363
|
+
_test_frozenset()
|
|
364
|
+
|
|
365
|
+
# ── 17. int.bit_length() ─────────────────────────────────────────────────────
|
|
366
|
+
# STATUS: ✗ NOT SUPPORTED — Number prototype does not have bit_length.
|
|
367
|
+
#
|
|
368
|
+
# ae((255).bit_length(), 8)
|
|
369
|
+
|
|
370
|
+
# ── 18. float.is_integer() ───────────────────────────────────────────────────
|
|
371
|
+
# STATUS: ✗ NOT SUPPORTED — Number prototype does not have is_integer.
|
|
372
|
+
#
|
|
373
|
+
# ae((1.0).is_integer(), True)
|
|
374
|
+
|
|
375
|
+
# ── 19. dict | merge operator (Python 3.9+) ──────────────────────────────────
|
|
376
|
+
# STATUS: ✓ WORKS — requires `from __python__ import overload_operators, dict_literals`
|
|
377
|
+
|
|
378
|
+
def _test_dict_merge_operator():
|
|
379
|
+
from __python__ import overload_operators, dict_literals
|
|
380
|
+
d1 = {'x': 1, 'y': 2}
|
|
381
|
+
d2 = {'y': 99, 'z': 3}
|
|
382
|
+
|
|
383
|
+
# d1 | d2 creates a new merged dict; d2 values win on conflict
|
|
384
|
+
merged = d1 | d2
|
|
385
|
+
ade(merged, {'x': 1, 'y': 99, 'z': 3})
|
|
386
|
+
|
|
387
|
+
# original dicts are unchanged
|
|
388
|
+
ade(d1, {'x': 1, 'y': 2})
|
|
389
|
+
ade(d2, {'y': 99, 'z': 3})
|
|
390
|
+
|
|
391
|
+
# empty dict merge
|
|
392
|
+
ade(d1 | {}, {'x': 1, 'y': 2})
|
|
393
|
+
ade({} | d1, {'x': 1, 'y': 2})
|
|
394
|
+
|
|
395
|
+
# |= updates in place
|
|
396
|
+
d3 = {'a': 1, 'b': 2}
|
|
397
|
+
d3 |= {'b': 20, 'c': 3}
|
|
398
|
+
ade(d3, {'a': 1, 'b': 20, 'c': 3})
|
|
399
|
+
|
|
400
|
+
# chained merges
|
|
401
|
+
base = {'k': 0}
|
|
402
|
+
r = base | {'k': 1} | {'k': 2}
|
|
403
|
+
ade(r, {'k': 2})
|
|
404
|
+
|
|
405
|
+
_test_dict_merge_operator()
|
|
406
|
+
|
|
407
|
+
# ── 20. for / else ───────────────────────────────────────────────────────────
|
|
408
|
+
# STATUS: ✓ WORKS — see §5 above
|
|
409
|
+
|
|
410
|
+
# ── 21. while / else ─────────────────────────────────────────────────────────
|
|
411
|
+
# STATUS: ✓ WORKS (added in this release)
|
|
412
|
+
|
|
413
|
+
def _while_else():
|
|
414
|
+
# else runs when loop condition becomes False (no break)
|
|
415
|
+
ran_else = False
|
|
416
|
+
i = 0
|
|
417
|
+
while i < 3:
|
|
418
|
+
i += 1
|
|
419
|
+
else:
|
|
420
|
+
ran_else = True
|
|
421
|
+
ae(ran_else, True)
|
|
422
|
+
ae(i, 3)
|
|
423
|
+
|
|
424
|
+
# else is skipped when break occurs
|
|
425
|
+
ran_else = False
|
|
426
|
+
i = 0
|
|
427
|
+
while i < 5:
|
|
428
|
+
if i == 2:
|
|
429
|
+
break
|
|
430
|
+
i += 1
|
|
431
|
+
else:
|
|
432
|
+
ran_else = True
|
|
433
|
+
ae(ran_else, False)
|
|
434
|
+
|
|
435
|
+
# else runs when condition is False from the start (zero iterations)
|
|
436
|
+
ran_else = False
|
|
437
|
+
i = 10
|
|
438
|
+
while i < 3:
|
|
439
|
+
i += 1
|
|
440
|
+
else:
|
|
441
|
+
ran_else = True
|
|
442
|
+
ae(ran_else, True)
|
|
443
|
+
|
|
444
|
+
# nested loops: break in inner should NOT affect outer while/else
|
|
445
|
+
outer_else = False
|
|
446
|
+
i = 0
|
|
447
|
+
while i < 2:
|
|
448
|
+
j = 0
|
|
449
|
+
while j < 2:
|
|
450
|
+
if j == 0:
|
|
451
|
+
break # breaks inner only
|
|
452
|
+
j += 1
|
|
453
|
+
i += 1
|
|
454
|
+
else:
|
|
455
|
+
outer_else = True
|
|
456
|
+
ae(outer_else, True)
|
|
457
|
+
|
|
458
|
+
_while_else()
|
|
459
|
+
|
|
460
|
+
# ── 22. raise X from Y (exception chaining) ──────────────────────────────────
|
|
461
|
+
# STATUS: ✓ WORKS (added in this release)
|
|
462
|
+
|
|
463
|
+
def _test_raise_from():
|
|
464
|
+
cause_set = False
|
|
465
|
+
cause_val = None
|
|
466
|
+
try:
|
|
467
|
+
try:
|
|
468
|
+
raise ValueError('original')
|
|
469
|
+
except ValueError as orig:
|
|
470
|
+
raise TypeError('chained') from orig
|
|
471
|
+
except TypeError as e:
|
|
472
|
+
cause_set = hasattr(e, '__cause__')
|
|
473
|
+
cause_val = e.__cause__
|
|
474
|
+
ae(cause_set, True)
|
|
475
|
+
ok(cause_val is not None)
|
|
476
|
+
ae(cause_val.message, 'original')
|
|
477
|
+
|
|
478
|
+
_test_raise_from()
|
|
479
|
+
|
|
480
|
+
def _test_raise_from_none():
|
|
481
|
+
# raise X from None: __cause__ is explicitly set to None
|
|
482
|
+
caught = None
|
|
483
|
+
try:
|
|
484
|
+
try:
|
|
485
|
+
raise ValueError('inner')
|
|
486
|
+
except ValueError:
|
|
487
|
+
raise TypeError('outer') from None
|
|
488
|
+
except TypeError as e:
|
|
489
|
+
caught = e
|
|
490
|
+
ok(caught is not None)
|
|
491
|
+
ae(caught.__cause__, None)
|
|
492
|
+
|
|
493
|
+
_test_raise_from_none()
|
|
494
|
+
|
|
495
|
+
# ── 23. __slots__ ────────────────────────────────────────────────────────────
|
|
496
|
+
# STATUS: ✗ NOT ENFORCED — __slots__ is accepted but does not restrict attrs.
|
|
497
|
+
|
|
498
|
+
# ── 24. Nested classes ───────────────────────────────────────────────────────
|
|
499
|
+
# STATUS: ✗ NOT SUPPORTED — see comment in test/classes.pyj
|
|
500
|
+
|
|
501
|
+
# ── 25. Generator .throw() ───────────────────────────────────────────────────
|
|
502
|
+
# STATUS: ✓ WORKS
|
|
503
|
+
|
|
504
|
+
def _gen_throw():
|
|
505
|
+
try:
|
|
506
|
+
yield 1
|
|
507
|
+
except ValueError as e:
|
|
508
|
+
yield 'caught'
|
|
509
|
+
|
|
510
|
+
g = _gen_throw()
|
|
511
|
+
ae(g.next().value, 1)
|
|
512
|
+
ae(g.throw(ValueError('err')).value, 'caught')
|
|
513
|
+
|
|
514
|
+
# ── 26. Generator .send() ────────────────────────────────────────────────────
|
|
515
|
+
# STATUS: ✓ WORKS
|
|
516
|
+
|
|
517
|
+
def _gen_send():
|
|
518
|
+
data = yield 1
|
|
519
|
+
yield data
|
|
520
|
+
|
|
521
|
+
g = _gen_send()
|
|
522
|
+
ae(g.next().value, 1)
|
|
523
|
+
ae(g.next('hello').value, 'hello')
|
|
524
|
+
|
|
525
|
+
# ── 27. zip(strict=True) ─────────────────────────────────────────────────────
|
|
526
|
+
# STATUS: ✗ NOT SUPPORTED — zip() silently truncates to shortest.
|
|
527
|
+
|
|
528
|
+
ade(list(zip([1, 2], [3, 4])), [[1, 3], [2, 4]]) # basic zip still works
|
|
529
|
+
|
|
530
|
+
# ── 28. Walrus operator := ────────────────────────────────────────────────────
|
|
531
|
+
# STATUS: ✓ WORKS — hoisting in if/while conditions; comprehension filter scope
|
|
532
|
+
|
|
533
|
+
_wn = 10
|
|
534
|
+
if (_wm := _wn * 2) > 15:
|
|
535
|
+
ae(_wm, 20)
|
|
536
|
+
|
|
537
|
+
# walrus in a nested binary condition
|
|
538
|
+
_wa = 3
|
|
539
|
+
if (_wb := _wa + 7) >= 10:
|
|
540
|
+
ae(_wb, 10)
|
|
541
|
+
|
|
542
|
+
# walrus available after the if-block (variable persists in enclosing scope)
|
|
543
|
+
ae(_wb, 10)
|
|
544
|
+
|
|
545
|
+
# comprehension filter with walrus — y assigned in enclosing scope
|
|
546
|
+
_wdata = [1, 2, 3, 4, 5]
|
|
547
|
+
_wresult = [_wy for _wx in _wdata if (_wy := _wx * 2) > 6]
|
|
548
|
+
ade(_wresult, [8, 10])
|
|
549
|
+
ae(_wy, 10) # last walrus-assigned value is visible in enclosing scope
|
|
550
|
+
|
|
551
|
+
# ── 29. Chained comparisons ──────────────────────────────────────────────────
|
|
552
|
+
# STATUS: ✓ WORKS — same-direction and mixed-direction chains both correct
|
|
553
|
+
|
|
554
|
+
ae(1 < 2 < 3, True)
|
|
555
|
+
ae(3 > 2 > 1, True)
|
|
556
|
+
ae(1 < 3 < 2, False)
|
|
557
|
+
ae(1 < 2 > 3, False) # mixed-direction: 1<2 AND 2>3 = True AND False = False
|
|
558
|
+
ae(1 < 2 > 0, True) # mixed-direction: 1<2 AND 2>0 = True AND True = True
|
|
559
|
+
ae(5 > 3 < 4, True) # mixed-direction: 5>3 AND 3<4 = True AND True = True
|
|
560
|
+
ae(5 > 3 < 1, False) # mixed-direction: 5>3 AND 3<1 = True AND False = False
|
|
561
|
+
|
|
562
|
+
# ── 30. **= power-assign ─────────────────────────────────────────────────────
|
|
563
|
+
# STATUS: ✓ WORKS (added in this release)
|
|
564
|
+
|
|
565
|
+
n = 3
|
|
566
|
+
n **= 2
|
|
567
|
+
ae(n, 9)
|
|
568
|
+
|
|
569
|
+
n = 2
|
|
570
|
+
n **= 10
|
|
571
|
+
ae(n, 1024)
|
|
572
|
+
|
|
573
|
+
n = 5
|
|
574
|
+
n **= 0
|
|
575
|
+
ae(n, 1)
|
|
576
|
+
|
|
577
|
+
n = 4
|
|
578
|
+
n **= 0.5
|
|
579
|
+
ae(n, 2)
|
|
580
|
+
|
|
581
|
+
# ── 31. Augmented assignment operators ───────────────────────────────────────
|
|
582
|
+
# STATUS: ✓ WORKS — +=, -=, *=, /=, //=, %=, &=, |=, ^=, <<=, >>=
|
|
583
|
+
|
|
584
|
+
n = 10
|
|
585
|
+
n += 5; ae(n, 15)
|
|
586
|
+
n -= 3; ae(n, 12)
|
|
587
|
+
n *= 2; ae(n, 24)
|
|
588
|
+
n //= 5; ae(n, 4)
|
|
589
|
+
n %= 3; ae(n, 1)
|
|
590
|
+
n |= 6; ae(n, 7) # 1 | 6 = 7
|
|
591
|
+
n &= 5; ae(n, 5) # 7 & 5 = 5
|
|
592
|
+
n ^= 3; ae(n, 6) # 5 ^ 3 = 6
|
|
593
|
+
n <<= 2; ae(n, 24) # 6 << 2 = 24
|
|
594
|
+
n >>= 1; ae(n, 12) # 24 >> 1 = 12
|
|
595
|
+
|
|
596
|
+
# ── 32. Starred assignment ───────────────────────────────────────────────────
|
|
597
|
+
# STATUS: ✓ WORKS
|
|
598
|
+
|
|
599
|
+
a, *b, c = [1, 2, 3, 4, 5]
|
|
600
|
+
ae(a, 1)
|
|
601
|
+
ade(b, [2, 3, 4])
|
|
602
|
+
ae(c, 5)
|
|
603
|
+
|
|
604
|
+
# ── 33. match / case ─────────────────────────────────────────────────────────
|
|
605
|
+
# STATUS: ✓ WORKS
|
|
606
|
+
|
|
607
|
+
def _classify(x):
|
|
608
|
+
match x:
|
|
609
|
+
case 0:
|
|
610
|
+
return 'zero'
|
|
611
|
+
case 1:
|
|
612
|
+
return 'one'
|
|
613
|
+
case _:
|
|
614
|
+
return 'other'
|
|
615
|
+
|
|
616
|
+
ae(_classify(0), 'zero')
|
|
617
|
+
ae(_classify(1), 'one')
|
|
618
|
+
ae(_classify(5), 'other')
|
|
619
|
+
|
|
620
|
+
# ── 34. dict.fromkeys() ──────────────────────────────────────────────────────
|
|
621
|
+
# STATUS: ✓ WORKS
|
|
622
|
+
|
|
623
|
+
from __python__ import dict_literals
|
|
624
|
+
ade(dict.fromkeys(['a', 'b'], 0), {'a': 0, 'b': 0})
|
|
625
|
+
|
|
626
|
+
# ── 35. @classmethod / @staticmethod ─────────────────────────────────────────
|
|
627
|
+
# STATUS: ✓ WORKS
|
|
628
|
+
|
|
629
|
+
class _Counted:
|
|
630
|
+
_n = 0
|
|
631
|
+
@classmethod
|
|
632
|
+
def bump(cls):
|
|
633
|
+
cls._n += 1
|
|
634
|
+
@classmethod
|
|
635
|
+
def count(cls):
|
|
636
|
+
return cls._n
|
|
637
|
+
|
|
638
|
+
_Counted.bump()
|
|
639
|
+
_Counted.bump()
|
|
640
|
+
ae(_Counted.count(), 2)
|
|
641
|
+
|
|
642
|
+
class _Util:
|
|
643
|
+
@staticmethod
|
|
644
|
+
def add(a, b):
|
|
645
|
+
return a + b
|
|
646
|
+
|
|
647
|
+
ae(_Util.add(3, 4), 7)
|
|
648
|
+
|
|
649
|
+
# ── 36. yield from ───────────────────────────────────────────────────────────
|
|
650
|
+
# STATUS: ✓ WORKS
|
|
651
|
+
|
|
652
|
+
def _sub():
|
|
653
|
+
yield 10
|
|
654
|
+
yield 20
|
|
655
|
+
|
|
656
|
+
def _outer():
|
|
657
|
+
yield from _sub()
|
|
658
|
+
yield 30
|
|
659
|
+
|
|
660
|
+
ade(list(_outer()), [10, 20, 30])
|
|
661
|
+
|
|
662
|
+
# ── 37. lambda keyword ───────────────────────────────────────────────────────
|
|
663
|
+
# STATUS: ✓ WORKS — compiles to anonymous JS function expression
|
|
664
|
+
|
|
665
|
+
double = lambda x: x * 2
|
|
666
|
+
ae(double(3), 6)
|
|
667
|
+
ae(double(0), 0)
|
|
668
|
+
|
|
669
|
+
add = lambda a, b: a + b
|
|
670
|
+
ae(add(2, 3), 5)
|
|
671
|
+
|
|
672
|
+
ae((lambda: 42)(), 42)
|
|
673
|
+
|
|
674
|
+
abs_val = lambda x: x if x >= 0 else -x
|
|
675
|
+
ae(abs_val(-5), 5)
|
|
676
|
+
ae(abs_val(3), 3)
|
|
677
|
+
|
|
678
|
+
greet = lambda name='world': 'hello ' + name
|
|
679
|
+
ae(greet(), 'hello world')
|
|
680
|
+
ae(greet('there'), 'hello there')
|
|
681
|
+
|
|
682
|
+
# lambda captures enclosing scope
|
|
683
|
+
def _make_adder(n):
|
|
684
|
+
return lambda x: x + n
|
|
685
|
+
|
|
686
|
+
add10 = _make_adder(10)
|
|
687
|
+
ae(add10(5), 15)
|
|
688
|
+
|
|
689
|
+
# ── 38. Operator overloading (__add__, __sub__, etc.) ────────────────────────
|
|
690
|
+
# STATUS: ✓ WORKS — requires `from __python__ import overload_operators`
|
|
691
|
+
|
|
692
|
+
def _test_operator_overloading():
|
|
693
|
+
from __python__ import overload_operators
|
|
694
|
+
|
|
695
|
+
class Vec:
|
|
696
|
+
def __init__(self, x, y):
|
|
697
|
+
self.x = x
|
|
698
|
+
self.y = y
|
|
699
|
+
def __add__(self, other):
|
|
700
|
+
return Vec(self.x + other.x, self.y + other.y)
|
|
701
|
+
def __sub__(self, other):
|
|
702
|
+
return Vec(self.x - other.x, self.y - other.y)
|
|
703
|
+
def __mul__(self, n):
|
|
704
|
+
return Vec(self.x * n, self.y * n)
|
|
705
|
+
def __eq__(self, other):
|
|
706
|
+
return self.x == other.x and self.y == other.y
|
|
707
|
+
|
|
708
|
+
v1 = Vec(1, 2)
|
|
709
|
+
v2 = Vec(3, 4)
|
|
710
|
+
ok((v1 + v2) == Vec(4, 6))
|
|
711
|
+
ok((v2 - v1) == Vec(2, 2))
|
|
712
|
+
ok((v1 * 3) == Vec(3, 6))
|
|
713
|
+
|
|
714
|
+
class Num:
|
|
715
|
+
def __init__(self, n):
|
|
716
|
+
self.n = n
|
|
717
|
+
def __neg__(self):
|
|
718
|
+
return Num(-self.n)
|
|
719
|
+
def __eq__(self, other):
|
|
720
|
+
return self.n == other.n
|
|
721
|
+
|
|
722
|
+
ok(-Num(5) == Num(-5))
|
|
723
|
+
ok(-Num(-3) == Num(3))
|
|
724
|
+
|
|
725
|
+
# Comparison dunders defined — called directly (< > <= >= are NOT re-dispatched)
|
|
726
|
+
class Version:
|
|
727
|
+
def __init__(self, n):
|
|
728
|
+
self.n = n
|
|
729
|
+
def __lt__(self, other):
|
|
730
|
+
return self.n < other.n
|
|
731
|
+
def __le__(self, other):
|
|
732
|
+
return self.n <= other.n
|
|
733
|
+
def __gt__(self, other):
|
|
734
|
+
return self.n > other.n
|
|
735
|
+
def __ge__(self, other):
|
|
736
|
+
return self.n >= other.n
|
|
737
|
+
|
|
738
|
+
v1 = Version(1)
|
|
739
|
+
v2 = Version(2)
|
|
740
|
+
ok(v1.__lt__(v2))
|
|
741
|
+
ok(v2.__gt__(v1))
|
|
742
|
+
ok(v1.__le__(Version(1)))
|
|
743
|
+
ok(v2.__ge__(Version(2)))
|
|
744
|
+
|
|
745
|
+
# __floordiv__ and __mod__
|
|
746
|
+
class Int:
|
|
747
|
+
def __init__(self, n):
|
|
748
|
+
self.n = n
|
|
749
|
+
def __floordiv__(self, other):
|
|
750
|
+
return Int(Math.floor(self.n / other.n))
|
|
751
|
+
def __mod__(self, other):
|
|
752
|
+
return Int(self.n % other.n)
|
|
753
|
+
def __eq__(self, other):
|
|
754
|
+
return self.n == other.n
|
|
755
|
+
|
|
756
|
+
ok(Int(7) // Int(2) == Int(3))
|
|
757
|
+
ok(Int(7) % Int(3) == Int(1))
|
|
758
|
+
|
|
759
|
+
_test_operator_overloading()
|
|
760
|
+
|
|
761
|
+
# ── 39. Nested comprehensions (multi-for) ────────────────────────────────────
|
|
762
|
+
# STATUS: ✓ WORKS
|
|
763
|
+
|
|
764
|
+
# Flatten nested list
|
|
765
|
+
ade([x for row in [[1,2],[3,4]] for x in row], [1,2,3,4])
|
|
766
|
+
|
|
767
|
+
# Filter within nested comprehension
|
|
768
|
+
ade([x for row in [[1,2,3],[4,5,6]] for x in row if x % 2 == 0], [2,4,6])
|
|
769
|
+
|
|
770
|
+
# Nested list comprehension — column extraction
|
|
771
|
+
matrix = [[1,2,3],[4,5,6],[7,8,9]]
|
|
772
|
+
ade([row[0] for row in matrix], [1,4,7])
|
|
773
|
+
|
|
774
|
+
# Three-level nesting
|
|
775
|
+
ade([i+j+k for i in [1] for j in [10] for k in [100]], [111])
|
|
776
|
+
|
|
777
|
+
# Dict comprehension over pairs
|
|
778
|
+
ade({k: v for k, v in [['a',1],['b',2]]}, {'a':1, 'b':2})
|
|
779
|
+
|
|
780
|
+
# Set comprehension with two for-clauses
|
|
781
|
+
ok({x+y for x in [1,2] for y in [10,20]} == {11, 12, 21, 22})
|
|
782
|
+
|
|
783
|
+
from __python__ import truthiness
|
|
784
|
+
|
|
785
|
+
# ── 40. __bool__ / Python truthiness ─────────────────────────────────────────
|
|
786
|
+
# STATUS: ✓ WORKS — requires `from __python__ import truthiness`
|
|
787
|
+
# Empty list/dict/set are falsy; __bool__ dunder is dispatched.
|
|
788
|
+
|
|
789
|
+
# Empty containers are falsy (Python semantics)
|
|
790
|
+
ok(not [])
|
|
791
|
+
ok(not {})
|
|
792
|
+
ok(not set())
|
|
793
|
+
ok(not '')
|
|
794
|
+
|
|
795
|
+
# Non-empty containers are truthy
|
|
796
|
+
ok([0])
|
|
797
|
+
ok([1, 2])
|
|
798
|
+
ok({'a': 1})
|
|
799
|
+
ok({1})
|
|
800
|
+
ok('x')
|
|
801
|
+
|
|
802
|
+
# numbers: 0 is falsy, nonzero is truthy
|
|
803
|
+
ok(not 0)
|
|
804
|
+
ok(1)
|
|
805
|
+
ok(-1)
|
|
806
|
+
|
|
807
|
+
# None/undefined are falsy
|
|
808
|
+
ok(not None)
|
|
809
|
+
|
|
810
|
+
# __bool__ dunder is dispatched by bool()
|
|
811
|
+
class _AlwaysFalse:
|
|
812
|
+
def __bool__(self):
|
|
813
|
+
return False
|
|
814
|
+
|
|
815
|
+
class _AlwaysTrue:
|
|
816
|
+
def __bool__(self):
|
|
817
|
+
return True
|
|
818
|
+
|
|
819
|
+
ok(not bool(_AlwaysFalse()))
|
|
820
|
+
ok(bool(_AlwaysTrue()))
|
|
821
|
+
|
|
822
|
+
# bool() on containers
|
|
823
|
+
ok(not bool([]))
|
|
824
|
+
ok(not bool({}))
|
|
825
|
+
ok(bool([1]))
|
|
826
|
+
ok(bool({'a': 1}))
|
|
827
|
+
|
|
828
|
+
# `not` operator uses Python truthiness
|
|
829
|
+
ok(not [])
|
|
830
|
+
ok(not not [1])
|
|
831
|
+
|
|
832
|
+
# `and` / `or` return values (not just booleans), use Python truthiness
|
|
833
|
+
ok([] or 'fallback') # [] is falsy → return 'fallback'
|
|
834
|
+
ae([] or 'fallback', 'fallback')
|
|
835
|
+
_t12 = [1, 2]
|
|
836
|
+
ae(_t12 or 'fallback', _t12) # [1,2] is truthy → return [1,2]
|
|
837
|
+
_t0 = []
|
|
838
|
+
ae(_t0 and 'nope', _t0) # [] is falsy → return []
|
|
839
|
+
ae([1] and 'yes', 'yes') # [1] is truthy → return 'yes'
|
|
840
|
+
|
|
841
|
+
# `x if cond else y` uses Python truthiness for cond
|
|
842
|
+
ae('yes' if [1] else 'no', 'yes')
|
|
843
|
+
ae('yes' if [] else 'no', 'no')
|
|
844
|
+
|
|
845
|
+
# ── 41. __call__ as callable dispatch ────────────────────────────────────────
|
|
846
|
+
# STATUS: ✓ WORKS — requires `from __python__ import truthiness`
|
|
847
|
+
# obj() dispatches to obj.__call__() for callable objects.
|
|
848
|
+
|
|
849
|
+
class _Adder:
|
|
850
|
+
def __init__(self, n):
|
|
851
|
+
self.n = n
|
|
852
|
+
def __call__(self, x):
|
|
853
|
+
return self.n + x
|
|
854
|
+
|
|
855
|
+
add5 = _Adder(5)
|
|
856
|
+
ae(add5(3), 8) # obj() dispatches to __call__
|
|
857
|
+
ae(add5.__call__(3), 8) # explicit __call__ still works
|
|
858
|
+
ae(callable(add5), True) # callable() detects __call__ (§7 above)
|
|
859
|
+
|
|
860
|
+
# callable object with no args
|
|
861
|
+
class _Counter:
|
|
862
|
+
def __init__(self):
|
|
863
|
+
self.count = 0
|
|
864
|
+
def __call__(self):
|
|
865
|
+
self.count += 1
|
|
866
|
+
return self.count
|
|
867
|
+
|
|
868
|
+
_c = _Counter()
|
|
869
|
+
ae(_c(), 1)
|
|
870
|
+
ae(_c(), 2)
|
|
871
|
+
ae(_c(), 3)
|
|
872
|
+
|
|
873
|
+
# ── 42. Complex number literals 3+4j ─────────────────────────────────────────
|
|
874
|
+
# STATUS: ✗ NOT SUPPORTED — no j suffix; no complex type.
|
|
875
|
+
# SKIP:
|
|
876
|
+
# c = 3+4j
|
|
877
|
+
# ae(c.real, 3)
|
|
878
|
+
# ae(c.imag, 4)
|
|
879
|
+
|
|
880
|
+
# ── 43. b'...' bytes literals ────────────────────────────────────────────────
|
|
881
|
+
# STATUS: ✗ NOT SUPPORTED — no b prefix; no native bytes type.
|
|
882
|
+
# Use the encodings module for encoding work.
|
|
883
|
+
# SKIP:
|
|
884
|
+
# data = b'hello'
|
|
885
|
+
# ae(data[0], 104)
|
|
886
|
+
|
|
887
|
+
# ── 44. except* (exception groups) ───────────────────────────────────────────
|
|
888
|
+
# STATUS: ✗ NOT SUPPORTED — Python 3.11+ syntax; no parser support.
|
|
889
|
+
# SKIP:
|
|
890
|
+
# try:
|
|
891
|
+
# raise ExceptionGroup('eg', [ValueError('v'), TypeError('t')])
|
|
892
|
+
# except* ValueError as eg:
|
|
893
|
+
# pass
|
|
894
|
+
|
|
895
|
+
# ── 45. __new__ constructor ───────────────────────────────────────────────────
|
|
896
|
+
# STATUS: ✗ NOT SUPPORTED — no alternative constructor hook.
|
|
897
|
+
# SKIP:
|
|
898
|
+
# class _Singleton:
|
|
899
|
+
# _instance = None
|
|
900
|
+
# def __new__(cls):
|
|
901
|
+
# if cls._instance is None:
|
|
902
|
+
# cls._instance = super().__new__(cls)
|
|
903
|
+
# return cls._instance
|
|
904
|
+
# ae(_Singleton() is _Singleton(), True)
|
|
905
|
+
|
|
906
|
+
# ── 46. __del__ destructor ────────────────────────────────────────────────────
|
|
907
|
+
# STATUS: ✗ NOT SUPPORTED — no destructor/finalizer.
|
|
908
|
+
# SKIP:
|
|
909
|
+
# class _Tracked:
|
|
910
|
+
# def __del__(self):
|
|
911
|
+
# pass # never called
|
|
912
|
+
|
|
913
|
+
# ── 47. __hash__ dunder ───────────────────────────────────────────────────────
|
|
914
|
+
# STATUS: ✗ NOT SUPPORTED — set/dict use object identity (=== semantics).
|
|
915
|
+
# Two instances with equal fields are different keys even with __hash__.
|
|
916
|
+
# SKIP:
|
|
917
|
+
# class _Point:
|
|
918
|
+
# def __init__(self, x, y):
|
|
919
|
+
# self.x = x
|
|
920
|
+
# self.y = y
|
|
921
|
+
# def __hash__(self):
|
|
922
|
+
# return self.x * 31 + self.y
|
|
923
|
+
# def __eq__(self, other):
|
|
924
|
+
# return self.x == other.x and self.y == other.y
|
|
925
|
+
# s = {_Point(1, 2)}
|
|
926
|
+
# ok(_Point(1, 2) in s) # FAILS — __hash__ not dispatched
|
|
927
|
+
|
|
928
|
+
# ── 48. __getattr__ / __setattr__ / __delattr__ ───────────────────────────────
|
|
929
|
+
# STATUS: ✗ NOT SUPPORTED — no attribute-access interception.
|
|
930
|
+
# SKIP:
|
|
931
|
+
# class _Dynamic:
|
|
932
|
+
# def __getattr__(self, name):
|
|
933
|
+
# return 'default'
|
|
934
|
+
# ae(_Dynamic().anything, 'default') # FAILS
|
|
935
|
+
|
|
936
|
+
# ── 49. __getattribute__ ─────────────────────────────────────────────────────
|
|
937
|
+
# STATUS: ✗ NOT SUPPORTED — no attribute-lookup override.
|
|
938
|
+
|
|
939
|
+
# ── 50. __format__ dunder ────────────────────────────────────────────────────
|
|
940
|
+
# STATUS: ✗ NOT SUPPORTED — format(obj, spec) is not a defined builtin;
|
|
941
|
+
# __format__ is not dispatched.
|
|
942
|
+
# SKIP:
|
|
943
|
+
# class _Fmt:
|
|
944
|
+
# def __format__(self, spec):
|
|
945
|
+
# return '<' + spec + '>'
|
|
946
|
+
# ae(format(_Fmt(), 'test'), '<test>')
|
|
947
|
+
|
|
948
|
+
# ── 51. __class_getitem__ ────────────────────────────────────────────────────
|
|
949
|
+
# STATUS: ✗ NOT SUPPORTED — no generic subscript MyClass[T] syntax.
|
|
950
|
+
# SKIP:
|
|
951
|
+
# class _Box:
|
|
952
|
+
# def __class_getitem__(cls, item):
|
|
953
|
+
# return cls
|
|
954
|
+
# ok(_Box[int] is _Box)
|
|
955
|
+
|
|
956
|
+
# ── 52. __init_subclass__ ────────────────────────────────────────────────────
|
|
957
|
+
# STATUS: ✗ NOT SUPPORTED — no subclass hook.
|
|
958
|
+
# SKIP:
|
|
959
|
+
# class _Plugin:
|
|
960
|
+
# registry = []
|
|
961
|
+
# def __init_subclass__(cls, **kwargs):
|
|
962
|
+
# _Plugin.registry.push(cls)
|
|
963
|
+
# class _MyPlugin(_Plugin): pass
|
|
964
|
+
# ok(_MyPlugin in _Plugin.registry)
|
|
965
|
+
|
|
966
|
+
# ── 53. issubclass() ─────────────────────────────────────────────────────────
|
|
967
|
+
# STATUS: ✓ WORKS
|
|
968
|
+
|
|
969
|
+
class _ISC_Animal:
|
|
970
|
+
pass
|
|
971
|
+
|
|
972
|
+
class _ISC_Dog(_ISC_Animal):
|
|
973
|
+
pass
|
|
974
|
+
|
|
975
|
+
class _ISC_Poodle(_ISC_Dog):
|
|
976
|
+
pass
|
|
977
|
+
|
|
978
|
+
class _ISC_Cat(_ISC_Animal):
|
|
979
|
+
pass
|
|
980
|
+
|
|
981
|
+
class _ISC_Unrelated:
|
|
982
|
+
pass
|
|
983
|
+
|
|
984
|
+
# every class is a subclass of itself
|
|
985
|
+
ok(issubclass(_ISC_Dog, _ISC_Dog))
|
|
986
|
+
ok(issubclass(_ISC_Animal, _ISC_Animal))
|
|
987
|
+
|
|
988
|
+
# direct inheritance
|
|
989
|
+
ok(issubclass(_ISC_Dog, _ISC_Animal))
|
|
990
|
+
|
|
991
|
+
# transitive inheritance (grandchild)
|
|
992
|
+
ok(issubclass(_ISC_Poodle, _ISC_Animal))
|
|
993
|
+
ok(issubclass(_ISC_Poodle, _ISC_Dog))
|
|
994
|
+
|
|
995
|
+
# sibling is not a subclass
|
|
996
|
+
ok(not issubclass(_ISC_Cat, _ISC_Dog))
|
|
997
|
+
ok(not issubclass(_ISC_Dog, _ISC_Cat))
|
|
998
|
+
|
|
999
|
+
# parent is not a subclass of child
|
|
1000
|
+
ok(not issubclass(_ISC_Animal, _ISC_Dog))
|
|
1001
|
+
|
|
1002
|
+
# unrelated class
|
|
1003
|
+
ok(not issubclass(_ISC_Unrelated, _ISC_Animal))
|
|
1004
|
+
|
|
1005
|
+
# tuple form — True if subclass of any
|
|
1006
|
+
ok(issubclass(_ISC_Dog, (_ISC_Cat, _ISC_Animal)))
|
|
1007
|
+
ok(issubclass(_ISC_Poodle, (_ISC_Cat, _ISC_Dog)))
|
|
1008
|
+
ok(not issubclass(_ISC_Unrelated, (_ISC_Cat, _ISC_Dog)))
|
|
1009
|
+
|
|
1010
|
+
# TypeError for non-class first arg
|
|
1011
|
+
_isc_err = None
|
|
1012
|
+
try:
|
|
1013
|
+
issubclass(42, _ISC_Animal)
|
|
1014
|
+
except TypeError as e:
|
|
1015
|
+
_isc_err = str(e)
|
|
1016
|
+
ok(_isc_err is not None)
|
|
1017
|
+
|
|
1018
|
+
# TypeError for non-class second arg
|
|
1019
|
+
_isc_err2 = None
|
|
1020
|
+
try:
|
|
1021
|
+
issubclass(_ISC_Dog, 'not_a_class')
|
|
1022
|
+
except TypeError as e:
|
|
1023
|
+
_isc_err2 = str(e)
|
|
1024
|
+
ok(_isc_err2 is not None)
|
|
1025
|
+
|
|
1026
|
+
# ── 54. hash() builtin ───────────────────────────────────────────────────────
|
|
1027
|
+
# STATUS: ✓ WORKS
|
|
1028
|
+
|
|
1029
|
+
# None → 0
|
|
1030
|
+
ae(hash(None), 0)
|
|
1031
|
+
|
|
1032
|
+
# booleans
|
|
1033
|
+
ae(hash(True), 1)
|
|
1034
|
+
ae(hash(False), 0)
|
|
1035
|
+
|
|
1036
|
+
# integers — hash is the integer itself
|
|
1037
|
+
ae(hash(0), 0)
|
|
1038
|
+
ae(hash(42), 42)
|
|
1039
|
+
ae(hash(-1), -1)
|
|
1040
|
+
|
|
1041
|
+
# floats that are whole numbers hash like ints
|
|
1042
|
+
ae(hash(3.0), 3)
|
|
1043
|
+
ae(hash(-7.0), -7)
|
|
1044
|
+
|
|
1045
|
+
# strings — same string always gives same hash
|
|
1046
|
+
ae(hash('hello'), hash('hello'))
|
|
1047
|
+
ae(hash(''), hash(''))
|
|
1048
|
+
ok(hash('abc') != hash('xyz'))
|
|
1049
|
+
|
|
1050
|
+
# result is always an integer
|
|
1051
|
+
ok(jstype(hash(42)) is 'number')
|
|
1052
|
+
ok(jstype(hash('hi')) is 'number')
|
|
1053
|
+
|
|
1054
|
+
# objects with __hash__ — dispatched
|
|
1055
|
+
class _Hashable:
|
|
1056
|
+
def __init__(self, val):
|
|
1057
|
+
self.val = val
|
|
1058
|
+
def __hash__(self):
|
|
1059
|
+
return self.val * 7
|
|
1060
|
+
|
|
1061
|
+
ae(hash(_Hashable(3)), 21)
|
|
1062
|
+
ae(hash(_Hashable(0)), 0)
|
|
1063
|
+
|
|
1064
|
+
# class instances without __hash__ — stable identity hash
|
|
1065
|
+
_h1 = _Hashable(99)
|
|
1066
|
+
ae(hash(_h1), hash(_h1)) # consistent
|
|
1067
|
+
|
|
1068
|
+
# distinct instances have different hashes (identity-based)
|
|
1069
|
+
class _Plain:
|
|
1070
|
+
pass
|
|
1071
|
+
|
|
1072
|
+
_p1 = _Plain()
|
|
1073
|
+
_p2 = _Plain()
|
|
1074
|
+
ok(hash(_p1) != hash(_p2))
|
|
1075
|
+
|
|
1076
|
+
# unhashable types raise TypeError
|
|
1077
|
+
def _check_unhashable(val):
|
|
1078
|
+
_caught = False
|
|
1079
|
+
try:
|
|
1080
|
+
hash(val)
|
|
1081
|
+
except TypeError:
|
|
1082
|
+
_caught = True
|
|
1083
|
+
ok(_caught)
|
|
1084
|
+
|
|
1085
|
+
_check_unhashable([1, 2, 3])
|
|
1086
|
+
_check_unhashable({1, 2})
|
|
1087
|
+
_check_unhashable({'a': 1})
|
|
1088
|
+
|
|
1089
|
+
# ── 55. format(value, spec) builtin ──────────────────────────────────────────
|
|
1090
|
+
# STATUS: ✗ NOT IMPLEMENTED — format(value, spec) builtin is not defined.
|
|
1091
|
+
# str.format() and f-strings work fine.
|
|
1092
|
+
# SKIP:
|
|
1093
|
+
# ae(format(3.14159, '.2f'), '3.14')
|
|
1094
|
+
|
|
1095
|
+
# ── 56. next(iterator[, default]) builtin ────────────────────────────────────
|
|
1096
|
+
# Basic list iteration
|
|
1097
|
+
it = iter([10, 20, 30])
|
|
1098
|
+
ae(next(it), 10)
|
|
1099
|
+
ae(next(it), 20)
|
|
1100
|
+
ae(next(it), 30)
|
|
1101
|
+
# Default value when exhausted
|
|
1102
|
+
ae(next(it, 'done'), 'done')
|
|
1103
|
+
ae(next(it, None), None)
|
|
1104
|
+
# StopIteration raised when exhausted and no default
|
|
1105
|
+
it2 = iter([])
|
|
1106
|
+
raised = False
|
|
1107
|
+
try:
|
|
1108
|
+
next(it2)
|
|
1109
|
+
except StopIteration:
|
|
1110
|
+
raised = True
|
|
1111
|
+
ae(raised, True)
|
|
1112
|
+
# Works with range iterator
|
|
1113
|
+
it3 = iter(range(3))
|
|
1114
|
+
ae(next(it3), 0)
|
|
1115
|
+
ae(next(it3), 1)
|
|
1116
|
+
ae(next(it3), 2)
|
|
1117
|
+
ae(next(it3, -1), -1)
|
|
1118
|
+
# Works with enumerate iterator
|
|
1119
|
+
it4 = iter(enumerate(['a', 'b']))
|
|
1120
|
+
_n4a = next(it4)
|
|
1121
|
+
ae(_n4a[0], 0); ae(_n4a[1], 'a')
|
|
1122
|
+
_n4b = next(it4)
|
|
1123
|
+
ae(_n4b[0], 1); ae(_n4b[1], 'b')
|
|
1124
|
+
ae(next(it4, 'end'), 'end')
|
|
1125
|
+
# Works with generator
|
|
1126
|
+
def _next_gen():
|
|
1127
|
+
yield 100
|
|
1128
|
+
yield 200
|
|
1129
|
+
g = _next_gen()
|
|
1130
|
+
ae(next(g), 100)
|
|
1131
|
+
ae(next(g), 200)
|
|
1132
|
+
ae(next(g, 'stop'), 'stop')
|
|
1133
|
+
|
|
1134
|
+
# ── 57. iter(callable, sentinel) two-arg form ────────────────────────────────
|
|
1135
|
+
# STATUS: ✗ NOT IMPLEMENTED — two-argument iter(callable, sentinel) not supported.
|
|
1136
|
+
# SKIP:
|
|
1137
|
+
# count = [0]
|
|
1138
|
+
# def counter():
|
|
1139
|
+
# count[0] += 1; return count[0]
|
|
1140
|
+
# ae(list(iter(counter, 3)), [1, 2])
|
|
1141
|
+
|
|
1142
|
+
# ── 58. slice() as builtin object ────────────────────────────────────────────
|
|
1143
|
+
# STATUS: ✗ NOT IMPLEMENTED — slice() constructor is not a builtin.
|
|
1144
|
+
# SKIP:
|
|
1145
|
+
# s = slice(1, 5, 2)
|
|
1146
|
+
# ae(s.start, 1)
|
|
1147
|
+
# ae(s.stop, 5)
|
|
1148
|
+
# ae(s.step, 2)
|
|
1149
|
+
|
|
1150
|
+
# ── 59. complex() builtin ────────────────────────────────────────────────────
|
|
1151
|
+
# STATUS: ✗ NOT SUPPORTED — no complex number type.
|
|
1152
|
+
# SKIP:
|
|
1153
|
+
# c = complex(3, 4)
|
|
1154
|
+
# ae(c.real, 3)
|
|
1155
|
+
# ae(c.imag, 4)
|
|
1156
|
+
|
|
1157
|
+
# ── 60. vars() / locals() / globals() ────────────────────────────────────────
|
|
1158
|
+
# STATUS: ✗ NOT IMPLEMENTED — these Python builtins are not defined.
|
|
1159
|
+
# SKIP:
|
|
1160
|
+
# g = globals()
|
|
1161
|
+
# ok(isinstance(g, dict))
|
|
1162
|
+
|
|
1163
|
+
# ── 61. Positional-only parameters def f(a, /, b): ────────────────────────────
|
|
1164
|
+
# STATUS: ✓ WORKS — tested in test/starargs.pyj
|
|
1165
|
+
|
|
1166
|
+
def _po(a, b, /):
|
|
1167
|
+
return [a, b]
|
|
1168
|
+
|
|
1169
|
+
ade(_po(1, 2), [1, 2])
|
|
1170
|
+
|
|
1171
|
+
def _po_mix(a, /, b):
|
|
1172
|
+
return [a, b]
|
|
1173
|
+
|
|
1174
|
+
ade(_po_mix(1, 2), [1, 2])
|
|
1175
|
+
ade(_po_mix(1, b=2), [1, 2])
|
|
1176
|
+
|
|
1177
|
+
# ── 62. Keyword-only parameters def f(a, *, b): ───────────────────────────────
|
|
1178
|
+
# STATUS: ✓ WORKS — tested in test/starargs.pyj
|
|
1179
|
+
|
|
1180
|
+
def _ko(a, *, b):
|
|
1181
|
+
return [a, b]
|
|
1182
|
+
|
|
1183
|
+
ade(_ko(1, b=2), [1, 2])
|
|
1184
|
+
ade(_ko(b=2, a=1), [1, 2])
|