rapydscript-ns 0.8.1 → 0.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/CONTRIBUTORS +3 -2
  3. package/PYTHON_DIFFERENCES_REPORT.md +291 -0
  4. package/PYTHON_FEATURE_COVERAGE.md +200 -0
  5. package/README.md +480 -79
  6. package/TODO.md +6 -318
  7. package/hack_demo.pyj +112 -0
  8. package/language-service/index.js +4474 -0
  9. package/language-service/language-service.d.ts +40 -0
  10. package/package.json +9 -10
  11. package/src/ast.pyj +30 -6
  12. package/src/baselib-builtins.pyj +181 -11
  13. package/src/baselib-containers.pyj +154 -5
  14. package/src/baselib-errors.pyj +3 -0
  15. package/src/baselib-internal.pyj +40 -1
  16. package/src/baselib-str.pyj +42 -1
  17. package/src/lib/collections.pyj +1 -1
  18. package/src/lib/numpy.pyj +10 -10
  19. package/src/monaco-language-service/analyzer.js +132 -22
  20. package/src/monaco-language-service/builtins.js +22 -2
  21. package/src/monaco-language-service/completions.js +224 -3
  22. package/src/monaco-language-service/diagnostics.js +55 -5
  23. package/src/monaco-language-service/index.js +26 -5
  24. package/src/monaco-language-service/scope.js +3 -0
  25. package/src/output/classes.pyj +20 -3
  26. package/src/output/codegen.pyj +38 -3
  27. package/src/output/functions.pyj +35 -25
  28. package/src/output/loops.pyj +64 -11
  29. package/src/output/modules.pyj +1 -4
  30. package/src/output/operators.pyj +67 -1
  31. package/src/output/statements.pyj +7 -3
  32. package/src/output/stream.pyj +6 -13
  33. package/src/parse.pyj +94 -14
  34. package/src/tokenizer.pyj +1 -0
  35. package/test/baselib.pyj +4 -4
  36. package/test/classes.pyj +56 -17
  37. package/test/collections.pyj +5 -5
  38. package/test/python_compat.pyj +326 -0
  39. package/test/python_features.pyj +1271 -0
  40. package/test/slice.pyj +105 -0
  41. package/test/str.pyj +25 -0
  42. package/test/unit/fixtures/fibonacci_expected.js +1 -1
  43. package/test/unit/index.js +119 -7
  44. package/test/unit/language-service-builtins.js +70 -0
  45. package/test/unit/language-service-bundle.js +83 -0
  46. package/test/unit/language-service-completions.js +289 -0
  47. package/test/unit/language-service-index.js +350 -0
  48. package/test/unit/language-service-scope.js +255 -0
  49. package/test/unit/language-service.js +158 -1
  50. package/test/unit/run-language-service.js +2 -0
  51. package/test/unit/web-repl.js +134 -0
  52. package/tools/build-language-service.js +2 -2
  53. package/tools/compiler.js +0 -24
  54. package/tools/export.js +3 -37
  55. package/tools/lint.js +1 -1
  56. package/tools/self.js +1 -9
  57. package/web-repl/rapydscript.js +6 -40
  58. package/web-repl/language-service.js +0 -4084
@@ -0,0 +1,1271 @@
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
+ # Unicode: Latin capital letters with diacritics fold to lowercase equivalents
232
+ ae(str.casefold('ÄÖÜ'), 'äöü')
233
+ ae(str.casefold('ÄÖÜ'), str.casefold('äöü'))
234
+
235
+ # ── 12. str.removeprefix() / str.removesuffix() ──────────────────────────────
236
+ # STATUS: ✓ WORKS (added in this release)
237
+
238
+ ae(str.removeprefix('HelloWorld', 'Hello'), 'World')
239
+ ae(str.removeprefix('HelloWorld', 'World'), 'HelloWorld') # no match → unchanged
240
+ ae(str.removeprefix('Hello', ''), 'Hello')
241
+
242
+ ae(str.removesuffix('HelloWorld', 'World'), 'Hello')
243
+ ae(str.removesuffix('HelloWorld', 'Hello'), 'HelloWorld') # no match → unchanged
244
+ ae(str.removesuffix('Hello', ''), 'Hello')
245
+
246
+ # ── 13. String repetition: str * n ───────────────────────────────────────────
247
+ # STATUS: ✓ WORKS with overload_operators flag
248
+
249
+ def _test_str_multiply():
250
+ from __python__ import overload_operators
251
+ ae('ha' * 3, 'hahaha')
252
+ ae(3 * 'ha', 'hahaha')
253
+ ae('x' * 0, '')
254
+
255
+ _test_str_multiply()
256
+
257
+ # ── 14. List repetition: [x] * n ─────────────────────────────────────────────
258
+ # STATUS: ✓ WORKS with overload_operators flag (added in this release)
259
+
260
+ def _test_list_multiply():
261
+ from __python__ import overload_operators
262
+ ade([0] * 3, [0, 0, 0])
263
+ ade([1, 2] * 2, [1, 2, 1, 2])
264
+ ade(3 * [0], [0, 0, 0])
265
+ ade([1] * 0, [])
266
+
267
+ _test_list_multiply()
268
+
269
+ # ── 15. except (Type,) tuple form ────────────────────────────────────────────
270
+ # STATUS: ✓ WORKS (added in this release) — see §3 above
271
+
272
+ # ── 16. frozenset ─────────────────────────────────────────────────────────────
273
+ # STATUS: ✓ WORKS (added in this release)
274
+
275
+ def _test_frozenset():
276
+ # Construction from list
277
+ fs = frozenset([1, 2, 3])
278
+ ae(len(fs), 3)
279
+ ok(1 in fs)
280
+ ok(2 in fs)
281
+ ok(not (4 in fs))
282
+
283
+ # Construction from set
284
+ fs2 = frozenset({4, 5})
285
+ ae(len(fs2), 2)
286
+ ok(4 in fs2)
287
+
288
+ # Empty frozenset
289
+ empty = frozenset()
290
+ ae(len(empty), 0)
291
+ ok(not (1 in empty))
292
+
293
+ # Iteration
294
+ total = 0
295
+ for x in frozenset([10, 20, 30]):
296
+ total += x
297
+ ae(total, 60)
298
+
299
+ # isinstance
300
+ ok(isinstance(fs, frozenset))
301
+ ok(not isinstance([1, 2], frozenset))
302
+
303
+ # copy returns a frozenset with same elements
304
+ fc = fs.copy()
305
+ ok(isinstance(fc, frozenset))
306
+ ae(len(fc), len(fs))
307
+ ok(1 in fc)
308
+ ok(3 in fc)
309
+
310
+ # union — returns frozenset
311
+ u = frozenset([1, 2]).union(frozenset([2, 3]))
312
+ ok(isinstance(u, frozenset))
313
+ ae(len(u), 3)
314
+ ok(1 in u and 2 in u and 3 in u)
315
+
316
+ # union with multiple args
317
+ u2 = frozenset([1]).union(frozenset([2]), frozenset([3]))
318
+ ae(len(u2), 3)
319
+
320
+ # intersection — returns frozenset
321
+ inter = frozenset([1, 2, 3]).intersection(frozenset([2, 3, 4]))
322
+ ok(isinstance(inter, frozenset))
323
+ ae(len(inter), 2)
324
+ ok(2 in inter and 3 in inter)
325
+ ok(not (1 in inter))
326
+
327
+ # difference — returns frozenset
328
+ diff = frozenset([1, 2, 3]).difference(frozenset([2]))
329
+ ok(isinstance(diff, frozenset))
330
+ ae(len(diff), 2)
331
+ ok(1 in diff and 3 in diff)
332
+ ok(not (2 in diff))
333
+
334
+ # symmetric_difference — returns frozenset
335
+ sd = frozenset([1, 2, 3]).symmetric_difference(frozenset([2, 3, 4]))
336
+ ok(isinstance(sd, frozenset))
337
+ ae(len(sd), 2)
338
+ ok(1 in sd and 4 in sd)
339
+
340
+ # issubset / issuperset
341
+ ok(frozenset([1, 2]).issubset(frozenset([1, 2, 3])))
342
+ ok(not frozenset([1, 4]).issubset(frozenset([1, 2, 3])))
343
+ ok(frozenset([1, 2, 3]).issuperset(frozenset([2, 3])))
344
+ ok(not frozenset([1, 2]).issuperset(frozenset([1, 2, 3])))
345
+
346
+ # isdisjoint
347
+ ok(frozenset([1, 2]).isdisjoint(frozenset([3, 4])))
348
+ ok(not frozenset([1, 2]).isdisjoint(frozenset([2, 3])))
349
+
350
+ # __eq__ with another frozenset
351
+ ok(frozenset([1, 2, 3]).__eq__(frozenset([1, 2, 3])))
352
+ ok(not frozenset([1, 2]).__eq__(frozenset([1, 2, 3])))
353
+
354
+ # frozenset and set compare equal when same elements
355
+ ok(frozenset([1, 2, 3]).__eq__({1, 2, 3}))
356
+ ok({1, 2, 3}.__eq__(frozenset([1, 2, 3])))
357
+
358
+ # Mutation raises — these would throw TypeError in Python.
359
+ # frozenset has no add/remove/clear methods (immutable by design).
360
+ ok(not hasattr(fs, 'add'))
361
+ ok(not hasattr(fs, 'remove'))
362
+ ok(not hasattr(fs, 'discard'))
363
+ ok(not hasattr(fs, 'clear'))
364
+ ok(not hasattr(fs, 'update'))
365
+
366
+ _test_frozenset()
367
+
368
+ # ── 17. int.bit_length() ─────────────────────────────────────────────────────
369
+ # STATUS: ✗ NOT SUPPORTED — Number prototype does not have bit_length.
370
+ #
371
+ # ae((255).bit_length(), 8)
372
+
373
+ # ── 18. float.is_integer() ───────────────────────────────────────────────────
374
+ # STATUS: ✗ NOT SUPPORTED — Number prototype does not have is_integer.
375
+ #
376
+ # ae((1.0).is_integer(), True)
377
+
378
+ # ── 19. dict | merge operator (Python 3.9+) ──────────────────────────────────
379
+ # STATUS: ✓ WORKS — requires `from __python__ import overload_operators, dict_literals`
380
+
381
+ def _test_dict_merge_operator():
382
+ from __python__ import overload_operators, dict_literals
383
+ d1 = {'x': 1, 'y': 2}
384
+ d2 = {'y': 99, 'z': 3}
385
+
386
+ # d1 | d2 creates a new merged dict; d2 values win on conflict
387
+ merged = d1 | d2
388
+ ade(merged, {'x': 1, 'y': 99, 'z': 3})
389
+
390
+ # original dicts are unchanged
391
+ ade(d1, {'x': 1, 'y': 2})
392
+ ade(d2, {'y': 99, 'z': 3})
393
+
394
+ # empty dict merge
395
+ ade(d1 | {}, {'x': 1, 'y': 2})
396
+ ade({} | d1, {'x': 1, 'y': 2})
397
+
398
+ # |= updates in place
399
+ d3 = {'a': 1, 'b': 2}
400
+ d3 |= {'b': 20, 'c': 3}
401
+ ade(d3, {'a': 1, 'b': 20, 'c': 3})
402
+
403
+ # chained merges
404
+ base = {'k': 0}
405
+ r = base | {'k': 1} | {'k': 2}
406
+ ade(r, {'k': 2})
407
+
408
+ _test_dict_merge_operator()
409
+
410
+ # ── 20. for / else ───────────────────────────────────────────────────────────
411
+ # STATUS: ✓ WORKS — see §5 above
412
+
413
+ # ── 21. while / else ─────────────────────────────────────────────────────────
414
+ # STATUS: ✓ WORKS (added in this release)
415
+
416
+ def _while_else():
417
+ # else runs when loop condition becomes False (no break)
418
+ ran_else = False
419
+ i = 0
420
+ while i < 3:
421
+ i += 1
422
+ else:
423
+ ran_else = True
424
+ ae(ran_else, True)
425
+ ae(i, 3)
426
+
427
+ # else is skipped when break occurs
428
+ ran_else = False
429
+ i = 0
430
+ while i < 5:
431
+ if i == 2:
432
+ break
433
+ i += 1
434
+ else:
435
+ ran_else = True
436
+ ae(ran_else, False)
437
+
438
+ # else runs when condition is False from the start (zero iterations)
439
+ ran_else = False
440
+ i = 10
441
+ while i < 3:
442
+ i += 1
443
+ else:
444
+ ran_else = True
445
+ ae(ran_else, True)
446
+
447
+ # nested loops: break in inner should NOT affect outer while/else
448
+ outer_else = False
449
+ i = 0
450
+ while i < 2:
451
+ j = 0
452
+ while j < 2:
453
+ if j == 0:
454
+ break # breaks inner only
455
+ j += 1
456
+ i += 1
457
+ else:
458
+ outer_else = True
459
+ ae(outer_else, True)
460
+
461
+ _while_else()
462
+
463
+ # ── 22. raise X from Y (exception chaining) ──────────────────────────────────
464
+ # STATUS: ✓ WORKS (added in this release)
465
+
466
+ def _test_raise_from():
467
+ cause_set = False
468
+ cause_val = None
469
+ try:
470
+ try:
471
+ raise ValueError('original')
472
+ except ValueError as orig:
473
+ raise TypeError('chained') from orig
474
+ except TypeError as e:
475
+ cause_set = hasattr(e, '__cause__')
476
+ cause_val = e.__cause__
477
+ ae(cause_set, True)
478
+ ok(cause_val is not None)
479
+ ae(cause_val.message, 'original')
480
+
481
+ _test_raise_from()
482
+
483
+ def _test_raise_from_none():
484
+ # raise X from None: __cause__ is explicitly set to None
485
+ caught = None
486
+ try:
487
+ try:
488
+ raise ValueError('inner')
489
+ except ValueError:
490
+ raise TypeError('outer') from None
491
+ except TypeError as e:
492
+ caught = e
493
+ ok(caught is not None)
494
+ ae(caught.__cause__, None)
495
+
496
+ _test_raise_from_none()
497
+
498
+ # ── 23. __slots__ ────────────────────────────────────────────────────────────
499
+ # STATUS: ✗ NOT ENFORCED — __slots__ is accepted but does not restrict attrs.
500
+
501
+ # ── 24. Nested classes ───────────────────────────────────────────────────────
502
+ # STATUS: ✓ WORKS — added in commit 44c9802; tested fully in test/classes.pyj
503
+
504
+ # ── 25. Generator .throw() ───────────────────────────────────────────────────
505
+ # STATUS: ✓ WORKS
506
+
507
+ def _gen_throw():
508
+ try:
509
+ yield 1
510
+ except ValueError as e:
511
+ yield 'caught'
512
+
513
+ g = _gen_throw()
514
+ ae(g.next().value, 1)
515
+ ae(g.throw(ValueError('err')).value, 'caught')
516
+
517
+ # ── 26. Generator .send() ────────────────────────────────────────────────────
518
+ # STATUS: ✓ WORKS
519
+
520
+ def _gen_send():
521
+ data = yield 1
522
+ yield data
523
+
524
+ g = _gen_send()
525
+ ae(g.next().value, 1)
526
+ ae(g.next('hello').value, 'hello')
527
+
528
+ # ── 27. zip(strict=True) ─────────────────────────────────────────────────────
529
+ # STATUS: ✗ NOT SUPPORTED — zip() silently truncates to shortest.
530
+
531
+ ade(list(zip([1, 2], [3, 4])), [[1, 3], [2, 4]]) # basic zip still works
532
+
533
+ # ── 28. Walrus operator := ────────────────────────────────────────────────────
534
+ # STATUS: ✓ WORKS — hoisting in if/while conditions; comprehension filter scope
535
+
536
+ _wn = 10
537
+ if (_wm := _wn * 2) > 15:
538
+ ae(_wm, 20)
539
+
540
+ # walrus in a nested binary condition
541
+ _wa = 3
542
+ if (_wb := _wa + 7) >= 10:
543
+ ae(_wb, 10)
544
+
545
+ # walrus available after the if-block (variable persists in enclosing scope)
546
+ ae(_wb, 10)
547
+
548
+ # comprehension filter with walrus — y assigned in enclosing scope
549
+ _wdata = [1, 2, 3, 4, 5]
550
+ _wresult = [_wy for _wx in _wdata if (_wy := _wx * 2) > 6]
551
+ ade(_wresult, [8, 10])
552
+ ae(_wy, 10) # last walrus-assigned value is visible in enclosing scope
553
+
554
+ # ── 29. Chained comparisons ──────────────────────────────────────────────────
555
+ # STATUS: ✓ WORKS — same-direction and mixed-direction chains both correct
556
+
557
+ ae(1 < 2 < 3, True)
558
+ ae(3 > 2 > 1, True)
559
+ ae(1 < 3 < 2, False)
560
+ ae(1 < 2 > 3, False) # mixed-direction: 1<2 AND 2>3 = True AND False = False
561
+ ae(1 < 2 > 0, True) # mixed-direction: 1<2 AND 2>0 = True AND True = True
562
+ ae(5 > 3 < 4, True) # mixed-direction: 5>3 AND 3<4 = True AND True = True
563
+ ae(5 > 3 < 1, False) # mixed-direction: 5>3 AND 3<1 = True AND False = False
564
+
565
+ # ── 30. **= power-assign ─────────────────────────────────────────────────────
566
+ # STATUS: ✓ WORKS (added in this release)
567
+
568
+ n = 3
569
+ n **= 2
570
+ ae(n, 9)
571
+
572
+ n = 2
573
+ n **= 10
574
+ ae(n, 1024)
575
+
576
+ n = 5
577
+ n **= 0
578
+ ae(n, 1)
579
+
580
+ n = 4
581
+ n **= 0.5
582
+ ae(n, 2)
583
+
584
+ # ── 31. Augmented assignment operators ───────────────────────────────────────
585
+ # STATUS: ✓ WORKS — +=, -=, *=, /=, //=, %=, &=, |=, ^=, <<=, >>=
586
+
587
+ n = 10
588
+ n += 5; ae(n, 15)
589
+ n -= 3; ae(n, 12)
590
+ n *= 2; ae(n, 24)
591
+ n //= 5; ae(n, 4)
592
+ n %= 3; ae(n, 1)
593
+ n |= 6; ae(n, 7) # 1 | 6 = 7
594
+ n &= 5; ae(n, 5) # 7 & 5 = 5
595
+ n ^= 3; ae(n, 6) # 5 ^ 3 = 6
596
+ n <<= 2; ae(n, 24) # 6 << 2 = 24
597
+ n >>= 1; ae(n, 12) # 24 >> 1 = 12
598
+
599
+ # ── 32. Starred assignment ───────────────────────────────────────────────────
600
+ # STATUS: ✓ WORKS
601
+
602
+ a, *b, c = [1, 2, 3, 4, 5]
603
+ ae(a, 1)
604
+ ade(b, [2, 3, 4])
605
+ ae(c, 5)
606
+
607
+ # ── 33. match / case ─────────────────────────────────────────────────────────
608
+ # STATUS: ✓ WORKS
609
+
610
+ def _classify(x):
611
+ match x:
612
+ case 0:
613
+ return 'zero'
614
+ case 1:
615
+ return 'one'
616
+ case _:
617
+ return 'other'
618
+
619
+ ae(_classify(0), 'zero')
620
+ ae(_classify(1), 'one')
621
+ ae(_classify(5), 'other')
622
+
623
+ # ── 34. dict.fromkeys() ──────────────────────────────────────────────────────
624
+ # STATUS: ✓ WORKS
625
+
626
+ from __python__ import dict_literals
627
+ ade(dict.fromkeys(['a', 'b'], 0), {'a': 0, 'b': 0})
628
+
629
+ # ── 35. @classmethod / @staticmethod ─────────────────────────────────────────
630
+ # STATUS: ✓ WORKS
631
+
632
+ class _Counted:
633
+ _n = 0
634
+ @classmethod
635
+ def bump(cls):
636
+ cls._n += 1
637
+ @classmethod
638
+ def count(cls):
639
+ return cls._n
640
+
641
+ _Counted.bump()
642
+ _Counted.bump()
643
+ ae(_Counted.count(), 2)
644
+
645
+ class _Util:
646
+ @staticmethod
647
+ def add(a, b):
648
+ return a + b
649
+
650
+ ae(_Util.add(3, 4), 7)
651
+
652
+ # ── 36. yield from ───────────────────────────────────────────────────────────
653
+ # STATUS: ✓ WORKS
654
+
655
+ def _sub():
656
+ yield 10
657
+ yield 20
658
+
659
+ def _outer():
660
+ yield from _sub()
661
+ yield 30
662
+
663
+ ade(list(_outer()), [10, 20, 30])
664
+
665
+ # ── 37. lambda keyword ───────────────────────────────────────────────────────
666
+ # STATUS: ✓ WORKS — compiles to anonymous JS function expression
667
+
668
+ double = lambda x: x * 2
669
+ ae(double(3), 6)
670
+ ae(double(0), 0)
671
+
672
+ add = lambda a, b: a + b
673
+ ae(add(2, 3), 5)
674
+
675
+ ae((lambda: 42)(), 42)
676
+
677
+ abs_val = lambda x: x if x >= 0 else -x
678
+ ae(abs_val(-5), 5)
679
+ ae(abs_val(3), 3)
680
+
681
+ greet = lambda name='world': 'hello ' + name
682
+ ae(greet(), 'hello world')
683
+ ae(greet('there'), 'hello there')
684
+
685
+ # lambda captures enclosing scope
686
+ def _make_adder(n):
687
+ return lambda x: x + n
688
+
689
+ add10 = _make_adder(10)
690
+ ae(add10(5), 15)
691
+
692
+ # ── 38. Operator overloading (__add__, __sub__, etc.) ────────────────────────
693
+ # STATUS: ✓ WORKS — requires `from __python__ import overload_operators`
694
+
695
+ def _test_operator_overloading():
696
+ from __python__ import overload_operators
697
+
698
+ class Vec:
699
+ def __init__(self, x, y):
700
+ self.x = x
701
+ self.y = y
702
+ def __add__(self, other):
703
+ return Vec(self.x + other.x, self.y + other.y)
704
+ def __sub__(self, other):
705
+ return Vec(self.x - other.x, self.y - other.y)
706
+ def __mul__(self, n):
707
+ return Vec(self.x * n, self.y * n)
708
+ def __eq__(self, other):
709
+ return self.x == other.x and self.y == other.y
710
+
711
+ v1 = Vec(1, 2)
712
+ v2 = Vec(3, 4)
713
+ ok((v1 + v2) == Vec(4, 6))
714
+ ok((v2 - v1) == Vec(2, 2))
715
+ ok((v1 * 3) == Vec(3, 6))
716
+
717
+ class Num:
718
+ def __init__(self, n):
719
+ self.n = n
720
+ def __neg__(self):
721
+ return Num(-self.n)
722
+ def __eq__(self, other):
723
+ return self.n == other.n
724
+
725
+ ok(-Num(5) == Num(-5))
726
+ ok(-Num(-3) == Num(3))
727
+
728
+ # Comparison dunders defined — called directly (< > <= >= are NOT re-dispatched)
729
+ class Version:
730
+ def __init__(self, n):
731
+ self.n = n
732
+ def __lt__(self, other):
733
+ return self.n < other.n
734
+ def __le__(self, other):
735
+ return self.n <= other.n
736
+ def __gt__(self, other):
737
+ return self.n > other.n
738
+ def __ge__(self, other):
739
+ return self.n >= other.n
740
+
741
+ v1 = Version(1)
742
+ v2 = Version(2)
743
+ ok(v1.__lt__(v2))
744
+ ok(v2.__gt__(v1))
745
+ ok(v1.__le__(Version(1)))
746
+ ok(v2.__ge__(Version(2)))
747
+
748
+ # __floordiv__ and __mod__
749
+ class Int:
750
+ def __init__(self, n):
751
+ self.n = n
752
+ def __floordiv__(self, other):
753
+ return Int(Math.floor(self.n / other.n))
754
+ def __mod__(self, other):
755
+ return Int(self.n % other.n)
756
+ def __eq__(self, other):
757
+ return self.n == other.n
758
+
759
+ ok(Int(7) // Int(2) == Int(3))
760
+ ok(Int(7) % Int(3) == Int(1))
761
+
762
+ _test_operator_overloading()
763
+
764
+ # ── 39. Nested comprehensions (multi-for) ────────────────────────────────────
765
+ # STATUS: ✓ WORKS
766
+
767
+ # Flatten nested list
768
+ ade([x for row in [[1,2],[3,4]] for x in row], [1,2,3,4])
769
+
770
+ # Filter within nested comprehension
771
+ ade([x for row in [[1,2,3],[4,5,6]] for x in row if x % 2 == 0], [2,4,6])
772
+
773
+ # Nested list comprehension — column extraction
774
+ matrix = [[1,2,3],[4,5,6],[7,8,9]]
775
+ ade([row[0] for row in matrix], [1,4,7])
776
+
777
+ # Three-level nesting
778
+ ade([i+j+k for i in [1] for j in [10] for k in [100]], [111])
779
+
780
+ # Dict comprehension over pairs
781
+ ade({k: v for k, v in [['a',1],['b',2]]}, {'a':1, 'b':2})
782
+
783
+ # Set comprehension with two for-clauses
784
+ ok({x+y for x in [1,2] for y in [10,20]} == {11, 12, 21, 22})
785
+
786
+ from __python__ import truthiness
787
+
788
+ # ── 40. __bool__ / Python truthiness ─────────────────────────────────────────
789
+ # STATUS: ✓ WORKS — requires `from __python__ import truthiness`
790
+ # Empty list/dict/set are falsy; __bool__ dunder is dispatched.
791
+
792
+ # Empty containers are falsy (Python semantics)
793
+ ok(not [])
794
+ ok(not {})
795
+ ok(not set())
796
+ ok(not '')
797
+
798
+ # Non-empty containers are truthy
799
+ ok([0])
800
+ ok([1, 2])
801
+ ok({'a': 1})
802
+ ok({1})
803
+ ok('x')
804
+
805
+ # numbers: 0 is falsy, nonzero is truthy
806
+ ok(not 0)
807
+ ok(1)
808
+ ok(-1)
809
+
810
+ # None/undefined are falsy
811
+ ok(not None)
812
+
813
+ # __bool__ dunder is dispatched by bool()
814
+ class _AlwaysFalse:
815
+ def __bool__(self):
816
+ return False
817
+
818
+ class _AlwaysTrue:
819
+ def __bool__(self):
820
+ return True
821
+
822
+ ok(not bool(_AlwaysFalse()))
823
+ ok(bool(_AlwaysTrue()))
824
+
825
+ # bool() on containers
826
+ ok(not bool([]))
827
+ ok(not bool({}))
828
+ ok(bool([1]))
829
+ ok(bool({'a': 1}))
830
+
831
+ # `not` operator uses Python truthiness
832
+ ok(not [])
833
+ ok(not not [1])
834
+
835
+ # `and` / `or` return values (not just booleans), use Python truthiness
836
+ ok([] or 'fallback') # [] is falsy → return 'fallback'
837
+ ae([] or 'fallback', 'fallback')
838
+ _t12 = [1, 2]
839
+ ae(_t12 or 'fallback', _t12) # [1,2] is truthy → return [1,2]
840
+ _t0 = []
841
+ ae(_t0 and 'nope', _t0) # [] is falsy → return []
842
+ ae([1] and 'yes', 'yes') # [1] is truthy → return 'yes'
843
+
844
+ # `x if cond else y` uses Python truthiness for cond
845
+ ae('yes' if [1] else 'no', 'yes')
846
+ ae('yes' if [] else 'no', 'no')
847
+
848
+ # ── 41. __call__ as callable dispatch ────────────────────────────────────────
849
+ # STATUS: ✓ WORKS — requires `from __python__ import truthiness`
850
+ # obj() dispatches to obj.__call__() for callable objects.
851
+
852
+ class _Adder:
853
+ def __init__(self, n):
854
+ self.n = n
855
+ def __call__(self, x):
856
+ return self.n + x
857
+
858
+ add5 = _Adder(5)
859
+ ae(add5(3), 8) # obj() dispatches to __call__
860
+ ae(add5.__call__(3), 8) # explicit __call__ still works
861
+ ae(callable(add5), True) # callable() detects __call__ (§7 above)
862
+
863
+ # callable object with no args
864
+ class _Counter:
865
+ def __init__(self):
866
+ self.count = 0
867
+ def __call__(self):
868
+ self.count += 1
869
+ return self.count
870
+
871
+ _c = _Counter()
872
+ ae(_c(), 1)
873
+ ae(_c(), 2)
874
+ ae(_c(), 3)
875
+
876
+ # ── 42. Complex number literals 3+4j ─────────────────────────────────────────
877
+ # STATUS: ✗ NOT SUPPORTED — no j suffix; no complex type.
878
+ # SKIP:
879
+ # c = 3+4j
880
+ # ae(c.real, 3)
881
+ # ae(c.imag, 4)
882
+
883
+ # ── 43. b'...' bytes literals ────────────────────────────────────────────────
884
+ # STATUS: ✗ NOT SUPPORTED — no b prefix; no native bytes type.
885
+ # Use the encodings module for encoding work.
886
+ # SKIP:
887
+ # data = b'hello'
888
+ # ae(data[0], 104)
889
+
890
+ # ── 44. except* (exception groups) ───────────────────────────────────────────
891
+ # STATUS: ✗ NOT SUPPORTED — Python 3.11+ syntax; no parser support.
892
+ # SKIP:
893
+ # try:
894
+ # raise ExceptionGroup('eg', [ValueError('v'), TypeError('t')])
895
+ # except* ValueError as eg:
896
+ # pass
897
+
898
+ # ── 45. __new__ constructor ───────────────────────────────────────────────────
899
+ # STATUS: ✗ NOT SUPPORTED — no alternative constructor hook.
900
+ # SKIP:
901
+ # class _Singleton:
902
+ # _instance = None
903
+ # def __new__(cls):
904
+ # if cls._instance is None:
905
+ # cls._instance = super().__new__(cls)
906
+ # return cls._instance
907
+ # ae(_Singleton() is _Singleton(), True)
908
+
909
+ # ── 46. __del__ destructor ────────────────────────────────────────────────────
910
+ # STATUS: ✗ NOT SUPPORTED — no destructor/finalizer.
911
+ # SKIP:
912
+ # class _Tracked:
913
+ # def __del__(self):
914
+ # pass # never called
915
+
916
+ # ── 47. __hash__ dunder ───────────────────────────────────────────────────────
917
+ # STATUS: ✗ NOT SUPPORTED — set/dict use object identity (=== semantics).
918
+ # Two instances with equal fields are different keys even with __hash__.
919
+ # SKIP:
920
+ # class _Point:
921
+ # def __init__(self, x, y):
922
+ # self.x = x
923
+ # self.y = y
924
+ # def __hash__(self):
925
+ # return self.x * 31 + self.y
926
+ # def __eq__(self, other):
927
+ # return self.x == other.x and self.y == other.y
928
+ # s = {_Point(1, 2)}
929
+ # ok(_Point(1, 2) in s) # FAILS — __hash__ not dispatched
930
+
931
+ # ── 48. __getattr__ / __setattr__ / __delattr__ ───────────────────────────────
932
+ # STATUS: ✗ NOT SUPPORTED — no attribute-access interception.
933
+ # SKIP:
934
+ # class _Dynamic:
935
+ # def __getattr__(self, name):
936
+ # return 'default'
937
+ # ae(_Dynamic().anything, 'default') # FAILS
938
+
939
+ # ── 49. __getattribute__ ─────────────────────────────────────────────────────
940
+ # STATUS: ✗ NOT SUPPORTED — no attribute-lookup override.
941
+
942
+ # ── 50. __format__ dunder ────────────────────────────────────────────────────
943
+ # STATUS: ✓ WORKS — format(obj, spec) dispatches to obj.__format__(spec).
944
+ # Tested fully in test/str.pyj.
945
+
946
+ # ── 51. __class_getitem__ ────────────────────────────────────────────────────
947
+ # STATUS: ✗ NOT SUPPORTED — no generic subscript MyClass[T] syntax.
948
+ # SKIP:
949
+ # class _Box:
950
+ # def __class_getitem__(cls, item):
951
+ # return cls
952
+ # ok(_Box[int] is _Box)
953
+
954
+ # ── 52. __init_subclass__ ────────────────────────────────────────────────────
955
+ # STATUS: ✗ NOT SUPPORTED — no subclass hook.
956
+ # SKIP:
957
+ # class _Plugin:
958
+ # registry = []
959
+ # def __init_subclass__(cls, **kwargs):
960
+ # _Plugin.registry.push(cls)
961
+ # class _MyPlugin(_Plugin): pass
962
+ # ok(_MyPlugin in _Plugin.registry)
963
+
964
+ # ── 53. issubclass() ─────────────────────────────────────────────────────────
965
+ # STATUS: ✓ WORKS
966
+
967
+ class _ISC_Animal:
968
+ pass
969
+
970
+ class _ISC_Dog(_ISC_Animal):
971
+ pass
972
+
973
+ class _ISC_Poodle(_ISC_Dog):
974
+ pass
975
+
976
+ class _ISC_Cat(_ISC_Animal):
977
+ pass
978
+
979
+ class _ISC_Unrelated:
980
+ pass
981
+
982
+ # every class is a subclass of itself
983
+ ok(issubclass(_ISC_Dog, _ISC_Dog))
984
+ ok(issubclass(_ISC_Animal, _ISC_Animal))
985
+
986
+ # direct inheritance
987
+ ok(issubclass(_ISC_Dog, _ISC_Animal))
988
+
989
+ # transitive inheritance (grandchild)
990
+ ok(issubclass(_ISC_Poodle, _ISC_Animal))
991
+ ok(issubclass(_ISC_Poodle, _ISC_Dog))
992
+
993
+ # sibling is not a subclass
994
+ ok(not issubclass(_ISC_Cat, _ISC_Dog))
995
+ ok(not issubclass(_ISC_Dog, _ISC_Cat))
996
+
997
+ # parent is not a subclass of child
998
+ ok(not issubclass(_ISC_Animal, _ISC_Dog))
999
+
1000
+ # unrelated class
1001
+ ok(not issubclass(_ISC_Unrelated, _ISC_Animal))
1002
+
1003
+ # tuple form — True if subclass of any
1004
+ ok(issubclass(_ISC_Dog, (_ISC_Cat, _ISC_Animal)))
1005
+ ok(issubclass(_ISC_Poodle, (_ISC_Cat, _ISC_Dog)))
1006
+ ok(not issubclass(_ISC_Unrelated, (_ISC_Cat, _ISC_Dog)))
1007
+
1008
+ # TypeError for non-class first arg
1009
+ _isc_err = None
1010
+ try:
1011
+ issubclass(42, _ISC_Animal)
1012
+ except TypeError as e:
1013
+ _isc_err = str(e)
1014
+ ok(_isc_err is not None)
1015
+
1016
+ # TypeError for non-class second arg
1017
+ _isc_err2 = None
1018
+ try:
1019
+ issubclass(_ISC_Dog, 'not_a_class')
1020
+ except TypeError as e:
1021
+ _isc_err2 = str(e)
1022
+ ok(_isc_err2 is not None)
1023
+
1024
+ # ── 54. hash() builtin ───────────────────────────────────────────────────────
1025
+ # STATUS: ✓ WORKS
1026
+
1027
+ # None → 0
1028
+ ae(hash(None), 0)
1029
+
1030
+ # booleans
1031
+ ae(hash(True), 1)
1032
+ ae(hash(False), 0)
1033
+
1034
+ # integers — hash is the integer itself
1035
+ ae(hash(0), 0)
1036
+ ae(hash(42), 42)
1037
+ ae(hash(-1), -1)
1038
+
1039
+ # floats that are whole numbers hash like ints
1040
+ ae(hash(3.0), 3)
1041
+ ae(hash(-7.0), -7)
1042
+
1043
+ # strings — same string always gives same hash
1044
+ ae(hash('hello'), hash('hello'))
1045
+ ae(hash(''), hash(''))
1046
+ ok(hash('abc') != hash('xyz'))
1047
+
1048
+ # result is always an integer
1049
+ ok(jstype(hash(42)) is 'number')
1050
+ ok(jstype(hash('hi')) is 'number')
1051
+
1052
+ # objects with __hash__ — dispatched
1053
+ class _Hashable:
1054
+ def __init__(self, val):
1055
+ self.val = val
1056
+ def __hash__(self):
1057
+ return self.val * 7
1058
+
1059
+ ae(hash(_Hashable(3)), 21)
1060
+ ae(hash(_Hashable(0)), 0)
1061
+
1062
+ # class instances without __hash__ — stable identity hash
1063
+ _h1 = _Hashable(99)
1064
+ ae(hash(_h1), hash(_h1)) # consistent
1065
+
1066
+ # distinct instances have different hashes (identity-based)
1067
+ class _Plain:
1068
+ pass
1069
+
1070
+ _p1 = _Plain()
1071
+ _p2 = _Plain()
1072
+ ok(hash(_p1) != hash(_p2))
1073
+
1074
+ # unhashable types raise TypeError
1075
+ def _check_unhashable(val):
1076
+ _caught = False
1077
+ try:
1078
+ hash(val)
1079
+ except TypeError:
1080
+ _caught = True
1081
+ ok(_caught)
1082
+
1083
+ _check_unhashable([1, 2, 3])
1084
+ _check_unhashable({1, 2})
1085
+ _check_unhashable({'a': 1})
1086
+
1087
+ # ── 55. format(value, spec) builtin ──────────────────────────────────────────
1088
+ # STATUS: ✓ WORKS — format(value, spec) is defined and dispatches to __format__.
1089
+ # Tested fully in test/str.pyj.
1090
+ #
1091
+ # ae(format(3.14159, '.2f'), '3.14')
1092
+
1093
+ # ── 56. next(iterator[, default]) builtin ────────────────────────────────────
1094
+ # Basic list iteration
1095
+ it = iter([10, 20, 30])
1096
+ ae(next(it), 10)
1097
+ ae(next(it), 20)
1098
+ ae(next(it), 30)
1099
+ # Default value when exhausted
1100
+ ae(next(it, 'done'), 'done')
1101
+ ae(next(it, None), None)
1102
+ # StopIteration raised when exhausted and no default
1103
+ it2 = iter([])
1104
+ raised = False
1105
+ try:
1106
+ next(it2)
1107
+ except StopIteration:
1108
+ raised = True
1109
+ ae(raised, True)
1110
+ # Works with range iterator
1111
+ it3 = iter(range(3))
1112
+ ae(next(it3), 0)
1113
+ ae(next(it3), 1)
1114
+ ae(next(it3), 2)
1115
+ ae(next(it3, -1), -1)
1116
+ # Works with enumerate iterator
1117
+ it4 = iter(enumerate(['a', 'b']))
1118
+ _n4a = next(it4)
1119
+ ae(_n4a[0], 0); ae(_n4a[1], 'a')
1120
+ _n4b = next(it4)
1121
+ ae(_n4b[0], 1); ae(_n4b[1], 'b')
1122
+ ae(next(it4, 'end'), 'end')
1123
+ # Works with generator
1124
+ def _next_gen():
1125
+ yield 100
1126
+ yield 200
1127
+ g = _next_gen()
1128
+ ae(next(g), 100)
1129
+ ae(next(g), 200)
1130
+ ae(next(g, 'stop'), 'stop')
1131
+
1132
+ # ── 57. iter(callable, sentinel) two-arg form ────────────────────────────────
1133
+ # Basic usage: collect values until sentinel
1134
+ _count57 = [0]
1135
+ def _counter57():
1136
+ _count57[0] += 1
1137
+ return _count57[0]
1138
+ ade(list(iter(_counter57, 3)), [1, 2])
1139
+ # Sentinel never reached: stops at exact match
1140
+ _vals57 = ['a', 'b', 'c', None]
1141
+ _idx57 = [0]
1142
+ def _src57():
1143
+ v = _vals57[_idx57[0]]
1144
+ _idx57[0] += 1
1145
+ return v
1146
+ ade(list(iter(_src57, None)), ['a', 'b', 'c'])
1147
+ # Sentinel on first call returns empty iterator
1148
+ _first57 = [0]
1149
+ def _fn57():
1150
+ _first57[0] += 1
1151
+ return 'stop'
1152
+ ade(list(iter(_fn57, 'stop')), [])
1153
+ # Works with for-loop
1154
+ _out57 = []
1155
+ _i57 = [0]
1156
+ def _seq57():
1157
+ _i57[0] += 1
1158
+ return _i57[0]
1159
+ for _v57 in iter(_seq57, 4):
1160
+ _out57.append(_v57)
1161
+ ade(_out57, [1, 2, 3])
1162
+ # Works with next()
1163
+ _j57 = [0]
1164
+ def _f57():
1165
+ _j57[0] += 1
1166
+ return _j57[0]
1167
+ _it57 = iter(_f57, 5)
1168
+ ae(next(_it57), 1)
1169
+ ae(next(_it57), 2)
1170
+ ae(next(_it57), 3)
1171
+ ae(next(_it57), 4)
1172
+ ae(next(_it57, 'done'), 'done')
1173
+ # Callable object with __call__
1174
+ class _Caller57:
1175
+ def __init__(self):
1176
+ self.n = 0
1177
+ def __call__(self):
1178
+ self.n += 1
1179
+ return self.n
1180
+ _caller57 = _Caller57()
1181
+ ade(list(iter(_caller57, 3)), [1, 2])
1182
+
1183
+ # ── 58. slice() as builtin object ────────────────────────────────────────────
1184
+ # STATUS: ✓ WORKS — implemented (see item #63 below for full tests).
1185
+ # NOTE: This item was previously marked NOT IMPLEMENTED but was added in a
1186
+ # recent release. Full tests live in item #63 and test/slice.pyj.
1187
+
1188
+ # ── 59. complex() builtin ────────────────────────────────────────────────────
1189
+ # STATUS: ✗ NOT SUPPORTED — no complex number type.
1190
+ # SKIP:
1191
+ # c = complex(3, 4)
1192
+ # ae(c.real, 3)
1193
+ # ae(c.imag, 4)
1194
+
1195
+ # ── 60. vars() / locals() / globals() ────────────────────────────────────────
1196
+ # STATUS: ✗ NOT IMPLEMENTED — these Python builtins are not defined.
1197
+ # SKIP:
1198
+ # g = globals()
1199
+ # ok(isinstance(g, dict))
1200
+
1201
+ # ── 61. Positional-only parameters def f(a, /, b): ────────────────────────────
1202
+ # STATUS: ✓ WORKS — tested in test/starargs.pyj
1203
+
1204
+ def _po(a, b, /):
1205
+ return [a, b]
1206
+
1207
+ ade(_po(1, 2), [1, 2])
1208
+
1209
+ def _po_mix(a, /, b):
1210
+ return [a, b]
1211
+
1212
+ ade(_po_mix(1, 2), [1, 2])
1213
+ ade(_po_mix(1, b=2), [1, 2])
1214
+
1215
+ # ── 62. Keyword-only parameters def f(a, *, b): ───────────────────────────────
1216
+ # STATUS: ✓ WORKS — tested in test/starargs.pyj
1217
+
1218
+ def _ko(a, *, b):
1219
+ return [a, b]
1220
+
1221
+ ade(_ko(1, b=2), [1, 2])
1222
+ ade(_ko(b=2, a=1), [1, 2])
1223
+
1224
+ # ── 63. slice() builtin ───────────────────────────────────────────────────────
1225
+ # STATUS: ✓ WORKS
1226
+ # Subscript read/write tests (lst[s], lst[s]=val) are in test/slice.pyj
1227
+ # because they require `from __python__ import overload_getitem`.
1228
+
1229
+ # Construction forms
1230
+ _s1 = slice(5)
1231
+ ae(_s1.start, None)
1232
+ ae(_s1.stop, 5)
1233
+ ae(_s1.step, None)
1234
+
1235
+ _s2 = slice(2, 8)
1236
+ ae(_s2.start, 2)
1237
+ ae(_s2.stop, 8)
1238
+ ae(_s2.step, None)
1239
+
1240
+ _s3 = slice(1, 9, 2)
1241
+ ae(_s3.start, 1)
1242
+ ae(_s3.stop, 9)
1243
+ ae(_s3.step, 2)
1244
+
1245
+ # None values preserved
1246
+ _sn = slice(None, None, -1)
1247
+ ae(_sn.start, None)
1248
+ ae(_sn.stop, None)
1249
+ ae(_sn.step, -1)
1250
+
1251
+ # indices() method
1252
+ ade(_s2.indices(10), [2, 8, 1])
1253
+ ade(slice(None, None, 1).indices(5), [0, 5, 1])
1254
+ ade(slice(-3, -1).indices(10), [7, 9, 1])
1255
+ ade(slice(None, None, -1).indices(5), [4, -1, -1])
1256
+
1257
+ # repr / str
1258
+ ae(str(slice(1, 5, None)), 'slice(1, 5, None)')
1259
+ ae(str(slice(None, 5, 2)), 'slice(None, 5, 2)')
1260
+
1261
+ # Equality
1262
+ ok(slice(1, 5) == slice(1, 5))
1263
+ ok(not (slice(1, 5) == slice(1, 6)))
1264
+
1265
+ # isinstance
1266
+ ok(isinstance(slice(1, 5), slice))
1267
+
1268
+ # Delete with slice object uses ρσ_delitem (no overload_getitem needed)
1269
+ _b = [0, 1, 2, 3, 4]
1270
+ del _b[slice(1, 3)]
1271
+ ade(_b, [0, 3, 4])