rapydscript-ns 0.8.0 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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])