rapydscript-ns 0.8.3 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/README.md +1351 -141
- package/TODO.md +12 -6
- package/language-service/index.js +184 -26
- package/package.json +1 -1
- package/release/baselib-plain-pretty.js +5895 -1928
- package/release/baselib-plain-ugly.js +140 -3
- package/release/compiler.js +16282 -5408
- package/release/signatures.json +25 -22
- package/src/ast.pyj +94 -1
- package/src/baselib-builtins.pyj +362 -3
- package/src/baselib-bytes.pyj +664 -0
- package/src/baselib-containers.pyj +99 -0
- package/src/baselib-errors.pyj +45 -1
- package/src/baselib-internal.pyj +346 -49
- package/src/baselib-itertools.pyj +17 -4
- package/src/baselib-str.pyj +46 -4
- package/src/lib/abc.pyj +317 -0
- package/src/lib/copy.pyj +120 -0
- package/src/lib/dataclasses.pyj +532 -0
- package/src/lib/enum.pyj +125 -0
- package/src/lib/pythonize.pyj +1 -1
- package/src/lib/re.pyj +35 -1
- package/src/lib/react.pyj +74 -0
- package/src/lib/typing.pyj +577 -0
- package/src/monaco-language-service/builtins.js +19 -4
- package/src/monaco-language-service/diagnostics.js +40 -19
- package/src/output/classes.pyj +161 -25
- package/src/output/codegen.pyj +16 -2
- package/src/output/exceptions.pyj +97 -1
- package/src/output/functions.pyj +87 -5
- package/src/output/jsx.pyj +164 -0
- package/src/output/literals.pyj +28 -2
- package/src/output/loops.pyj +5 -2
- package/src/output/modules.pyj +1 -1
- package/src/output/operators.pyj +108 -36
- package/src/output/statements.pyj +2 -2
- package/src/output/stream.pyj +1 -0
- package/src/parse.pyj +496 -128
- package/src/tokenizer.pyj +38 -4
- package/test/abc.pyj +291 -0
- package/test/arithmetic_nostrict.pyj +88 -0
- package/test/arithmetic_types.pyj +169 -0
- package/test/baselib.pyj +91 -0
- package/test/bytes.pyj +467 -0
- package/test/classes.pyj +1 -0
- package/test/comparison_ops.pyj +173 -0
- package/test/dataclasses.pyj +253 -0
- package/test/enum.pyj +134 -0
- package/test/eval_exec.pyj +56 -0
- package/test/format.pyj +148 -0
- package/test/object.pyj +64 -0
- package/test/python_compat.pyj +17 -15
- package/test/python_features.pyj +89 -21
- package/test/regexp.pyj +29 -1
- package/test/tuples.pyj +96 -0
- package/test/typing.pyj +469 -0
- package/test/unit/index.js +2292 -70
- package/test/unit/language-service.js +674 -4
- package/test/unit/web-repl.js +1106 -0
- package/test/vars_locals_globals.pyj +94 -0
- package/tools/cli.js +11 -0
- package/tools/compile.js +5 -0
- package/tools/embedded_compiler.js +15 -4
- package/tools/lint.js +16 -19
- package/tools/repl.js +1 -1
- package/web-repl/env.js +122 -0
- package/web-repl/main.js +1 -3
- package/web-repl/rapydscript.js +125 -3
- package/PYTHON_DIFFERENCES_REPORT.md +0 -291
- package/PYTHON_FEATURE_COVERAGE.md +0 -200
- package/hack_demo.pyj +0 -112
package/test/format.pyj
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# vim:fileencoding=utf-8
|
|
2
|
+
# License: BSD
|
|
3
|
+
# Tests for __format__ dunder dispatch in format(), str.format(), and f-strings
|
|
4
|
+
# globals: assrt
|
|
5
|
+
|
|
6
|
+
ae = assrt.equal
|
|
7
|
+
|
|
8
|
+
# ── Basic custom __format__ ───────────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
class Currency:
|
|
11
|
+
def __init__(self, amount):
|
|
12
|
+
self.amount = amount
|
|
13
|
+
def __str__(self):
|
|
14
|
+
return str(self.amount)
|
|
15
|
+
def __format__(self, spec):
|
|
16
|
+
if spec == 'usd':
|
|
17
|
+
return '$' + str(self.amount)
|
|
18
|
+
if spec == 'eur':
|
|
19
|
+
return '€' + str(self.amount)
|
|
20
|
+
return format(self.amount, spec)
|
|
21
|
+
|
|
22
|
+
c = Currency(42)
|
|
23
|
+
|
|
24
|
+
# format() builtin dispatches to __format__
|
|
25
|
+
ae(format(c, 'usd'), '$42')
|
|
26
|
+
ae(format(c, 'eur'), '€42')
|
|
27
|
+
ae(format(c, '.2f'), '42.00')
|
|
28
|
+
|
|
29
|
+
# str.format() dispatches to __format__
|
|
30
|
+
ae(str.format('{:usd}', c), '$42')
|
|
31
|
+
ae(str.format('{:eur}', c), '€42')
|
|
32
|
+
ae(str.format('{:.2f}', c), '42.00')
|
|
33
|
+
|
|
34
|
+
# f-strings dispatch to __format__
|
|
35
|
+
ae(f'{c:usd}', '$42')
|
|
36
|
+
ae(f'{c:eur}', '€42')
|
|
37
|
+
ae(f'{c:.2f}', '42.00')
|
|
38
|
+
|
|
39
|
+
# ── format() with empty spec calls __format__('') ────────────────────────────
|
|
40
|
+
|
|
41
|
+
class Tagged:
|
|
42
|
+
def __init__(self, v):
|
|
43
|
+
self.v = v
|
|
44
|
+
def __str__(self):
|
|
45
|
+
return 'str:' + str(self.v)
|
|
46
|
+
def __format__(self, spec):
|
|
47
|
+
if not spec:
|
|
48
|
+
return 'fmt:' + str(self.v)
|
|
49
|
+
return 'fmt:' + format(self.v, spec)
|
|
50
|
+
|
|
51
|
+
t = Tagged(7)
|
|
52
|
+
ae(format(t), 'fmt:7')
|
|
53
|
+
ae(format(t, ''), 'fmt:7')
|
|
54
|
+
ae(str.format('{}', t), 'fmt:7')
|
|
55
|
+
ae(f'{t}', 'fmt:7')
|
|
56
|
+
|
|
57
|
+
# ── Default __format__ delegates to __str__ when spec is empty ────────────────
|
|
58
|
+
|
|
59
|
+
class Simple:
|
|
60
|
+
def __str__(self):
|
|
61
|
+
return 'simple!'
|
|
62
|
+
|
|
63
|
+
s = Simple()
|
|
64
|
+
ae(format(s), 'simple!')
|
|
65
|
+
ae(format(s, ''), 'simple!')
|
|
66
|
+
ae(str.format('{}', s), 'simple!')
|
|
67
|
+
ae(f'{s}', 'simple!')
|
|
68
|
+
|
|
69
|
+
# ── Default __format__ raises TypeError for non-empty spec ───────────────────
|
|
70
|
+
|
|
71
|
+
class Plain:
|
|
72
|
+
def __str__(self):
|
|
73
|
+
return 'plain'
|
|
74
|
+
|
|
75
|
+
p = Plain()
|
|
76
|
+
raised = False
|
|
77
|
+
try:
|
|
78
|
+
format(p, 'd')
|
|
79
|
+
except TypeError:
|
|
80
|
+
raised = True
|
|
81
|
+
ae(raised, True)
|
|
82
|
+
|
|
83
|
+
raised = False
|
|
84
|
+
try:
|
|
85
|
+
str.format('{:d}', p)
|
|
86
|
+
except TypeError:
|
|
87
|
+
raised = True
|
|
88
|
+
ae(raised, True)
|
|
89
|
+
|
|
90
|
+
# ── Inheritance: subclass inherits parent __format__ ─────────────────────────
|
|
91
|
+
|
|
92
|
+
class Base:
|
|
93
|
+
def __format__(self, spec):
|
|
94
|
+
return 'base:' + spec
|
|
95
|
+
|
|
96
|
+
class Child(Base):
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
child = Child()
|
|
100
|
+
ae(format(child, 'x'), 'base:x')
|
|
101
|
+
ae(str.format('{:x}', child), 'base:x')
|
|
102
|
+
ae(f'{child:x}', 'base:x')
|
|
103
|
+
|
|
104
|
+
# ── Subclass can override __format__ ─────────────────────────────────────────
|
|
105
|
+
|
|
106
|
+
class Override(Base):
|
|
107
|
+
def __format__(self, spec):
|
|
108
|
+
return 'override:' + spec
|
|
109
|
+
|
|
110
|
+
o = Override()
|
|
111
|
+
ae(format(o, 'y'), 'override:y')
|
|
112
|
+
ae(str.format('{:y}', o), 'override:y')
|
|
113
|
+
|
|
114
|
+
# ── !r and !s transformers bypass __format__ ─────────────────────────────────
|
|
115
|
+
|
|
116
|
+
class WithRepr:
|
|
117
|
+
def __repr__(self):
|
|
118
|
+
return 'MyRepr'
|
|
119
|
+
def __str__(self):
|
|
120
|
+
return 'MyStr'
|
|
121
|
+
def __format__(self, spec):
|
|
122
|
+
return 'MyFmt'
|
|
123
|
+
|
|
124
|
+
wr = WithRepr()
|
|
125
|
+
# With no transformer, __format__ is called
|
|
126
|
+
ae(str.format('{}', wr), 'MyFmt')
|
|
127
|
+
# !s applies str(), result is a string (no __format__)
|
|
128
|
+
ae(str.format('{!s}', wr), 'MyStr')
|
|
129
|
+
# !r applies repr()
|
|
130
|
+
ae(str.format('{!r}', wr), 'MyRepr')
|
|
131
|
+
|
|
132
|
+
# ── Multiple fields with __format__ ──────────────────────────────────────────
|
|
133
|
+
|
|
134
|
+
class Qty:
|
|
135
|
+
def __init__(self, n):
|
|
136
|
+
self.n = n
|
|
137
|
+
def __str__(self):
|
|
138
|
+
return str(self.n)
|
|
139
|
+
def __format__(self, spec):
|
|
140
|
+
if spec == 'w':
|
|
141
|
+
words = ['zero', 'one', 'two', 'three', 'four', 'five']
|
|
142
|
+
return words[self.n] if self.n < len(words) else str(self.n)
|
|
143
|
+
return format(self.n, spec)
|
|
144
|
+
|
|
145
|
+
a, b = Qty(1), Qty(2)
|
|
146
|
+
ae(str.format('{:w} and {:w}', a, b), 'one and two')
|
|
147
|
+
ae(f'{a:w} + {b:w}', 'one + two')
|
|
148
|
+
ae(str.format('{0:d} + {1:d}', a, b), '1 + 2')
|
package/test/object.pyj
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# globals: assrt
|
|
2
|
+
|
|
3
|
+
ae = assrt.equal
|
|
4
|
+
ade = assrt.deepEqual
|
|
5
|
+
ok = assrt.ok
|
|
6
|
+
|
|
7
|
+
# ── Basic instantiation ───────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
o = object()
|
|
10
|
+
ok(o is not None)
|
|
11
|
+
ok(o is not undefined)
|
|
12
|
+
|
|
13
|
+
# ── Sentinel pattern: each call returns a distinct instance ───────────────────
|
|
14
|
+
|
|
15
|
+
s1 = object()
|
|
16
|
+
s2 = object()
|
|
17
|
+
ok(s1 is not s2)
|
|
18
|
+
|
|
19
|
+
# ── isinstance ────────────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
ok(isinstance(o, object))
|
|
22
|
+
ok(isinstance(s1, object))
|
|
23
|
+
ok(isinstance(s2, object))
|
|
24
|
+
|
|
25
|
+
# ── repr / str ────────────────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
r = repr(o)
|
|
28
|
+
ok(r.startsWith('<object object'))
|
|
29
|
+
|
|
30
|
+
# ── hash: stable and distinct between instances ───────────────────────────────
|
|
31
|
+
|
|
32
|
+
h1 = hash(s1)
|
|
33
|
+
h2 = hash(s2)
|
|
34
|
+
ok(jstype(h1) is 'number')
|
|
35
|
+
ok(h1 is not h2)
|
|
36
|
+
ae(hash(s1), h1) # hash is stable across calls
|
|
37
|
+
|
|
38
|
+
# ── Subclassing: class Foo(object) ────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
class Widget(object):
|
|
41
|
+
def __init__(self, name):
|
|
42
|
+
self.name = name
|
|
43
|
+
|
|
44
|
+
def greet(self):
|
|
45
|
+
return 'Hello, ' + self.name
|
|
46
|
+
|
|
47
|
+
w = Widget('world')
|
|
48
|
+
ok(isinstance(w, Widget))
|
|
49
|
+
ok(isinstance(w, object))
|
|
50
|
+
ae(w.greet(), 'Hello, world')
|
|
51
|
+
|
|
52
|
+
# ── Multiple inheritance with object as one parent ────────────────────────────
|
|
53
|
+
|
|
54
|
+
class Mixin:
|
|
55
|
+
def tag(self):
|
|
56
|
+
return 'mixin'
|
|
57
|
+
|
|
58
|
+
class Combined(Mixin, object):
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
c = Combined()
|
|
62
|
+
ok(isinstance(c, Combined))
|
|
63
|
+
ok(isinstance(c, object))
|
|
64
|
+
ae(c.tag(), 'mixin')
|
package/test/python_compat.pyj
CHANGED
|
@@ -115,20 +115,22 @@ _a8 += [3, 4]
|
|
|
115
115
|
ade(_a8, [1, 2, 3, 4])
|
|
116
116
|
ade(_b8, [1, 2, 3, 4]) # _b8 still points to same object, now extended
|
|
117
117
|
|
|
118
|
-
# ── 8. Ordering operators on lists —
|
|
118
|
+
# ── 8. Ordering operators on lists — behaviour depends on overload_operators ──
|
|
119
119
|
# Python: [10] < [9] compares element-wise → False (10 > 9).
|
|
120
|
-
#
|
|
121
|
-
#
|
|
120
|
+
# Without overload_operators (tested here): < falls through to JS string
|
|
121
|
+
# coercion → '[10]' < '[9]' → True (char comparison on the first differing
|
|
122
|
+
# digit: '1' < '9'). This is a common gotcha with multi-digit numbers.
|
|
123
|
+
#
|
|
124
|
+
# With overload_operators (the default when using rapydscript compile or the
|
|
125
|
+
# web-repl), < dispatches through ρσ_op_lt which does lexicographic comparison
|
|
126
|
+
# of list elements — matching Python semantics ([10] < [9] → False).
|
|
122
127
|
|
|
123
128
|
# RapydScript lists have a Python-style __str__, so [10].toString() = '[10]'.
|
|
124
|
-
# String compare of '[10]' vs '[9]': '[' = '[', then '1' < '9' →
|
|
125
|
-
ok([10] < [9]) # True
|
|
126
|
-
ae([9] < [10], False) # False
|
|
127
|
-
|
|
128
|
-
# Compare: Python uses numeric element-wise ordering, so [10] > [9] (10 > 9).
|
|
129
|
-
# RapydScript gives the opposite result for these specific values.
|
|
129
|
+
# String compare of '[10]' vs '[9]': '[' = '[', then '1' < '9' → True.
|
|
130
|
+
ok([10] < [9]) # True (string: '[10]' < '[9]') — no overload_operators
|
|
131
|
+
ae([9] < [10], False) # False (string: '[9]' > '[10]')
|
|
130
132
|
|
|
131
|
-
#
|
|
133
|
+
# Direct numeric comparison is always unambiguous:
|
|
132
134
|
ok(10 > 9) # direct numeric comparison always works
|
|
133
135
|
|
|
134
136
|
# ── 9. List sort() — Python numeric sort (jssort() for JS lexicographic) ──────
|
|
@@ -233,18 +235,18 @@ strings()
|
|
|
233
235
|
ae(' hello '.strip(), 'hello')
|
|
234
236
|
ae('hello world'.upper(), 'HELLO WORLD')
|
|
235
237
|
|
|
236
|
-
# ── 15. strings() leaves
|
|
238
|
+
# ── 15. strings() leaves replace() as JS version; split() defaults to ' ' ─────
|
|
237
239
|
# (strings() is active from section 14 above)
|
|
238
240
|
# Python str.split() with no args: splits on any whitespace, strips leading/trailing.
|
|
239
|
-
#
|
|
241
|
+
# RapydScript .split() with no args: transpiles to .split(" ") — splits on single space.
|
|
240
242
|
|
|
241
243
|
# Python semantics via str module:
|
|
242
244
|
ade(str.split('a b'), ['a', 'b']) # Python split on any whitespace
|
|
243
245
|
ade(str.split('a b c'), ['a', 'b', 'c'])
|
|
244
246
|
|
|
245
|
-
#
|
|
246
|
-
ade('a b'.split(), ['a
|
|
247
|
-
ade('a b'.split(' '), ['a', 'b']) #
|
|
247
|
+
# .split() with no args now defaults to splitting on ' ':
|
|
248
|
+
ade('a b'.split(), ['a', '', 'b']) # double space → empty token between
|
|
249
|
+
ade('a b'.split(' '), ['a', 'b']) # explicit ' ' separator still works
|
|
248
250
|
|
|
249
251
|
# Python str.replace() vs JS String.prototype.replace():
|
|
250
252
|
# Python replaces ALL occurrences by default.
|
package/test/python_features.pyj
CHANGED
|
@@ -366,14 +366,41 @@ def _test_frozenset():
|
|
|
366
366
|
_test_frozenset()
|
|
367
367
|
|
|
368
368
|
# ── 17. int.bit_length() ─────────────────────────────────────────────────────
|
|
369
|
-
# STATUS:
|
|
370
|
-
|
|
371
|
-
|
|
369
|
+
# STATUS: ✓ SUPPORTED — Number.prototype.bit_length added to baselib.
|
|
370
|
+
|
|
371
|
+
def _test_int_bit_length():
|
|
372
|
+
ae((0).bit_length(), 0)
|
|
373
|
+
ae((1).bit_length(), 1)
|
|
374
|
+
ae((2).bit_length(), 2)
|
|
375
|
+
ae((3).bit_length(), 2)
|
|
376
|
+
ae((4).bit_length(), 3)
|
|
377
|
+
ae((255).bit_length(), 8)
|
|
378
|
+
ae((256).bit_length(), 9)
|
|
379
|
+
ae((1023).bit_length(), 10)
|
|
380
|
+
ae((1024).bit_length(), 11)
|
|
381
|
+
# Negative numbers: bit_length ignores sign (Python semantics)
|
|
382
|
+
ae((-1).bit_length(), 1)
|
|
383
|
+
ae((-5).bit_length(), 3)
|
|
384
|
+
ae((-255).bit_length(), 8)
|
|
385
|
+
|
|
386
|
+
_test_int_bit_length()
|
|
372
387
|
|
|
373
388
|
# ── 18. float.is_integer() ───────────────────────────────────────────────────
|
|
374
|
-
# STATUS:
|
|
375
|
-
|
|
376
|
-
|
|
389
|
+
# STATUS: ✓ SUPPORTED — Number.prototype.is_integer added to baselib.
|
|
390
|
+
|
|
391
|
+
def _test_float_is_integer():
|
|
392
|
+
ae((1.0).is_integer(), True)
|
|
393
|
+
ae((1.5).is_integer(), False)
|
|
394
|
+
ae((0.0).is_integer(), True)
|
|
395
|
+
ae((-2.0).is_integer(), True)
|
|
396
|
+
ae((-2.5).is_integer(), False)
|
|
397
|
+
ae((1e10).is_integer(), True)
|
|
398
|
+
ae((1.0000000001).is_integer(), False)
|
|
399
|
+
# NaN and Infinity are not integers (JS literals)
|
|
400
|
+
ae(v'Infinity'.is_integer(), False)
|
|
401
|
+
ae(v'NaN'.is_integer(), False)
|
|
402
|
+
|
|
403
|
+
_test_float_is_integer()
|
|
377
404
|
|
|
378
405
|
# ── 19. dict | merge operator (Python 3.9+) ──────────────────────────────────
|
|
379
406
|
# STATUS: ✓ WORKS — requires `from __python__ import overload_operators, dict_literals`
|
|
@@ -526,9 +553,20 @@ ae(g.next().value, 1)
|
|
|
526
553
|
ae(g.next('hello').value, 'hello')
|
|
527
554
|
|
|
528
555
|
# ── 27. zip(strict=True) ─────────────────────────────────────────────────────
|
|
529
|
-
# STATUS:
|
|
556
|
+
# STATUS: ✓ WORKS — equal-length iterables succeed; mismatched raise ValueError.
|
|
530
557
|
|
|
531
558
|
ade(list(zip([1, 2], [3, 4])), [[1, 3], [2, 4]]) # basic zip still works
|
|
559
|
+
ade(list(zip([1, 2], [3, 4], strict=True)), [[1, 3], [2, 4]]) # strict, equal lengths OK
|
|
560
|
+
try:
|
|
561
|
+
list(zip([1, 2], [3], strict=True))
|
|
562
|
+
ae(True, False) # should not reach here
|
|
563
|
+
except ValueError:
|
|
564
|
+
pass # expected: first iterable longer
|
|
565
|
+
try:
|
|
566
|
+
list(zip([1], [3, 4], strict=True))
|
|
567
|
+
ae(True, False) # should not reach here
|
|
568
|
+
except ValueError:
|
|
569
|
+
pass # expected: second iterable longer
|
|
532
570
|
|
|
533
571
|
# ── 28. Walrus operator := ────────────────────────────────────────────────────
|
|
534
572
|
# STATUS: ✓ WORKS — hoisting in if/while conditions; comprehension filter scope
|
|
@@ -874,11 +912,25 @@ ae(_c(), 2)
|
|
|
874
912
|
ae(_c(), 3)
|
|
875
913
|
|
|
876
914
|
# ── 42. Complex number literals 3+4j ─────────────────────────────────────────
|
|
877
|
-
# STATUS:
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
915
|
+
# STATUS: ✓ SUPPORTED — j suffix, complex() builtin, arithmetic, abs(), conjugate()
|
|
916
|
+
def _test_complex():
|
|
917
|
+
from __python__ import overload_operators
|
|
918
|
+
c = 3+4j
|
|
919
|
+
ae(c.real, 3)
|
|
920
|
+
ae(c.imag, 4)
|
|
921
|
+
ae(abs(c), 5.0)
|
|
922
|
+
ade(c.conjugate(), complex(3, -4))
|
|
923
|
+
ade(c + complex(1, 2), complex(4, 6))
|
|
924
|
+
ade(c - complex(1, 1), complex(2, 3))
|
|
925
|
+
ade(c * complex(0, 1), complex(-4, 3))
|
|
926
|
+
ade(c / complex(1, 0), complex(3, 4))
|
|
927
|
+
ade(complex(5), complex(5, 0))
|
|
928
|
+
ade(complex(), complex(0, 0))
|
|
929
|
+
ae(repr(1j), '1j')
|
|
930
|
+
ae(repr(complex(3, -4)), '(3-4j)')
|
|
931
|
+
ade(1 + 2j, complex(1, 2))
|
|
932
|
+
ade(-1j, complex(0, -1))
|
|
933
|
+
_test_complex()
|
|
882
934
|
|
|
883
935
|
# ── 43. b'...' bytes literals ────────────────────────────────────────────────
|
|
884
936
|
# STATUS: ✗ NOT SUPPORTED — no b prefix; no native bytes type.
|
|
@@ -940,16 +992,31 @@ ae(_c(), 3)
|
|
|
940
992
|
# STATUS: ✗ NOT SUPPORTED — no attribute-lookup override.
|
|
941
993
|
|
|
942
994
|
# ── 50. __format__ dunder ────────────────────────────────────────────────────
|
|
943
|
-
# STATUS: ✓ WORKS — format(
|
|
944
|
-
#
|
|
995
|
+
# STATUS: ✓ WORKS — format(), str.format(), and f-strings all dispatch to
|
|
996
|
+
# obj.__format__(spec). Default __format__ auto-generated for classes.
|
|
997
|
+
# !r/!s/!a transformers bypass __format__ correctly.
|
|
998
|
+
# Tested fully in test/format.pyj.
|
|
945
999
|
|
|
946
1000
|
# ── 51. __class_getitem__ ────────────────────────────────────────────────────
|
|
947
|
-
# STATUS:
|
|
948
|
-
#
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
1001
|
+
# STATUS: ✓ WORKS — Class[item] dispatches to __class_getitem__(cls, item).
|
|
1002
|
+
# Subclasses inherit __class_getitem__; cls is set to the calling class.
|
|
1003
|
+
|
|
1004
|
+
class _Box51:
|
|
1005
|
+
def __class_getitem__(cls, item):
|
|
1006
|
+
return cls
|
|
1007
|
+
|
|
1008
|
+
ok(_Box51[int] is _Box51)
|
|
1009
|
+
|
|
1010
|
+
class _BoxNamed51:
|
|
1011
|
+
def __class_getitem__(cls, item):
|
|
1012
|
+
return cls.__name__ + '[' + str(item) + ']'
|
|
1013
|
+
|
|
1014
|
+
ok(_BoxNamed51[42] == '_BoxNamed51[42]')
|
|
1015
|
+
|
|
1016
|
+
class _BoxChild51(_BoxNamed51):
|
|
1017
|
+
pass
|
|
1018
|
+
|
|
1019
|
+
ok(_BoxChild51[99] == '_BoxChild51[99]')
|
|
953
1020
|
|
|
954
1021
|
# ── 52. __init_subclass__ ────────────────────────────────────────────────────
|
|
955
1022
|
# STATUS: ✗ NOT SUPPORTED — no subclass hook.
|
|
@@ -1086,7 +1153,8 @@ _check_unhashable({'a': 1})
|
|
|
1086
1153
|
|
|
1087
1154
|
# ── 55. format(value, spec) builtin ──────────────────────────────────────────
|
|
1088
1155
|
# STATUS: ✓ WORKS — format(value, spec) is defined and dispatches to __format__.
|
|
1089
|
-
#
|
|
1156
|
+
# str.format() and f-strings also dispatch to __format__.
|
|
1157
|
+
# Tested fully in test/format.pyj and test/str.pyj.
|
|
1090
1158
|
#
|
|
1091
1159
|
# ae(format(3.14159, '.2f'), '3.14')
|
|
1092
1160
|
|
package/test/regexp.pyj
CHANGED
|
@@ -38,9 +38,37 @@ assrt.equal(']', re.match('[]]', ']').group())
|
|
|
38
38
|
|
|
39
39
|
assrt.throws(def():re.search(r'(?(1)a|b)b', 'ab');, re.error)
|
|
40
40
|
|
|
41
|
-
# Test lookbehind assertions
|
|
41
|
+
# Test positive lookbehind assertions
|
|
42
42
|
assrt.equal('acdb', re.sub(r'(?<=a)b', 'c', 'abdb'))
|
|
43
43
|
|
|
44
|
+
# Test negative lookbehind assertions
|
|
45
|
+
assrt.equal('accd', re.sub(r'(?<!a)b', 'c', 'acbd'))
|
|
46
|
+
|
|
47
|
+
# Test variable-width lookbehind (ES2018+)
|
|
48
|
+
assrt.equal('c', re.search(r'(?<=ab+)c', 'abbc').group())
|
|
49
|
+
|
|
50
|
+
# Test fullmatch
|
|
51
|
+
assrt.ok(re.fullmatch(r'\w+', 'hello') is not None)
|
|
52
|
+
assrt.ok(re.fullmatch(r'\w+', 'hello world') is None)
|
|
53
|
+
assrt.ok(re.fullmatch(r'\w+', '') is None)
|
|
54
|
+
assrt.equal(re.fullmatch(r'(\w+)', 'hello').group(1), 'hello')
|
|
55
|
+
assrt.ok(re.fullmatch(r'[a-z]+', 'hello') is not None)
|
|
56
|
+
assrt.ok(re.fullmatch(r'[a-z]+', 'hello!') is None)
|
|
57
|
+
|
|
58
|
+
# Test accurate group start/end positions (ES2022 'd' flag; falls back to heuristic)
|
|
59
|
+
m = re.match(r'(a)(b)(c)', 'abc')
|
|
60
|
+
assrt.equal(m.start(1), 0)
|
|
61
|
+
assrt.equal(m.end(1), 1)
|
|
62
|
+
assrt.equal(m.start(2), 1)
|
|
63
|
+
assrt.equal(m.end(2), 2)
|
|
64
|
+
assrt.equal(m.start(3), 2)
|
|
65
|
+
assrt.equal(m.end(3), 3)
|
|
66
|
+
|
|
67
|
+
# Test NOFLAG and S (DOTALL) aliases
|
|
68
|
+
assrt.equal(re.NOFLAG, 0)
|
|
69
|
+
assrt.equal(re.S, re.DOTALL)
|
|
70
|
+
assrt.equal(re.search('.+', 'a\nb', flags=re.S).group(), 'a\nb')
|
|
71
|
+
|
|
44
72
|
# Test named groups
|
|
45
73
|
assrt.equal('aa', re.sub(r'(?P<a>a)b', r'\g<a>\1', 'ab'))
|
|
46
74
|
assrt.equal('bb', re.sub(r'(?P<a>a)(?P=a)', r'bb', 'aa'))
|
package/test/tuples.pyj
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# globals: assrt
|
|
2
|
+
# vim:fileencoding=utf-8
|
|
3
|
+
#
|
|
4
|
+
# tuples.pyj
|
|
5
|
+
# Tests for tuple literal syntax: (a, b), (a,), and ().
|
|
6
|
+
|
|
7
|
+
ae = assrt.equal
|
|
8
|
+
ade = assrt.deepEqual
|
|
9
|
+
ok = assrt.ok
|
|
10
|
+
|
|
11
|
+
# ── 1. Empty tuple ────────────────────────────────────────────────────────────
|
|
12
|
+
t = ()
|
|
13
|
+
ok(Array.isArray(t))
|
|
14
|
+
ae(t.length, 0)
|
|
15
|
+
|
|
16
|
+
# ── 2. Single-element tuple with trailing comma ───────────────────────────────
|
|
17
|
+
t1 = (42,)
|
|
18
|
+
ok(Array.isArray(t1))
|
|
19
|
+
ae(t1.length, 1)
|
|
20
|
+
ae(t1[0], 42)
|
|
21
|
+
|
|
22
|
+
t_str = ('hello',)
|
|
23
|
+
ae(t_str[0], 'hello')
|
|
24
|
+
|
|
25
|
+
# ── 3. Two-element tuple ──────────────────────────────────────────────────────
|
|
26
|
+
t2 = (1, 2)
|
|
27
|
+
ok(Array.isArray(t2))
|
|
28
|
+
ae(t2.length, 2)
|
|
29
|
+
ae(t2[0], 1)
|
|
30
|
+
ae(t2[1], 2)
|
|
31
|
+
|
|
32
|
+
# ── 4. Three-element tuple ────────────────────────────────────────────────────
|
|
33
|
+
t3 = ('a', 'b', 'c')
|
|
34
|
+
ae(t3.length, 3)
|
|
35
|
+
ae(t3[0], 'a')
|
|
36
|
+
ae(t3[1], 'b')
|
|
37
|
+
ae(t3[2], 'c')
|
|
38
|
+
|
|
39
|
+
# ── 5. Trailing comma on multi-element tuple ──────────────────────────────────
|
|
40
|
+
t_trail = (10, 20, 30,)
|
|
41
|
+
ae(t_trail.length, 3)
|
|
42
|
+
ae(t_trail[0], 10)
|
|
43
|
+
ae(t_trail[2], 30)
|
|
44
|
+
|
|
45
|
+
# ── 6. Tuple in an expression context (return, assignment) ────────────────────
|
|
46
|
+
def make_pair():
|
|
47
|
+
return (99, 100)
|
|
48
|
+
|
|
49
|
+
p = make_pair()
|
|
50
|
+
ae(p[0], 99)
|
|
51
|
+
ae(p[1], 100)
|
|
52
|
+
|
|
53
|
+
# ── 7. Tuple unpacking works the same as array unpacking ─────────────────────
|
|
54
|
+
a, b = (10, 20)
|
|
55
|
+
ae(a, 10)
|
|
56
|
+
ae(b, 20)
|
|
57
|
+
|
|
58
|
+
# ── 8. Nested tuples ─────────────────────────────────────────────────────────
|
|
59
|
+
nested = ((1, 2), (3, 4))
|
|
60
|
+
ae(nested.length, 2)
|
|
61
|
+
ae(nested[0][0], 1)
|
|
62
|
+
ae(nested[0][1], 2)
|
|
63
|
+
ae(nested[1][0], 3)
|
|
64
|
+
ae(nested[1][1], 4)
|
|
65
|
+
|
|
66
|
+
# ── 9. Tuple as function argument ─────────────────────────────────────────────
|
|
67
|
+
def first(t):
|
|
68
|
+
return t[0]
|
|
69
|
+
|
|
70
|
+
ae(first((7, 8, 9)), 7)
|
|
71
|
+
|
|
72
|
+
# ── 10. isinstance with tuple of types ────────────────────────────────────────
|
|
73
|
+
ok(isinstance(42, (int, str)))
|
|
74
|
+
ok(isinstance('hi', (int, str)))
|
|
75
|
+
ok(not isinstance(3.14, (int, str)))
|
|
76
|
+
|
|
77
|
+
# ── 11. Tuple expressions with complex elements ───────────────────────────────
|
|
78
|
+
x = 5
|
|
79
|
+
tup = (x + 1, x * 2, x if x > 0 else 0)
|
|
80
|
+
ae(tup[0], 6)
|
|
81
|
+
ae(tup[1], 10)
|
|
82
|
+
ae(tup[2], 5)
|
|
83
|
+
|
|
84
|
+
# ── 12. Parenthesised single expression is NOT a tuple ───────────────────────
|
|
85
|
+
plain = (42)
|
|
86
|
+
ae(plain, 42)
|
|
87
|
+
ok(not Array.isArray(plain))
|
|
88
|
+
|
|
89
|
+
# ── 13. Tuple used in for-loop unpacking ─────────────────────────────────────
|
|
90
|
+
pairs = [(1, 'one'), (2, 'two'), (3, 'three')]
|
|
91
|
+
result = []
|
|
92
|
+
for num, name in pairs:
|
|
93
|
+
result.push(str(num) + ':' + name)
|
|
94
|
+
ae(result[0], '1:one')
|
|
95
|
+
ae(result[1], '2:two')
|
|
96
|
+
ae(result[2], '3:three')
|