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.
Files changed (72) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +1351 -141
  3. package/TODO.md +12 -6
  4. package/language-service/index.js +184 -26
  5. package/package.json +1 -1
  6. package/release/baselib-plain-pretty.js +5895 -1928
  7. package/release/baselib-plain-ugly.js +140 -3
  8. package/release/compiler.js +16282 -5408
  9. package/release/signatures.json +25 -22
  10. package/src/ast.pyj +94 -1
  11. package/src/baselib-builtins.pyj +362 -3
  12. package/src/baselib-bytes.pyj +664 -0
  13. package/src/baselib-containers.pyj +99 -0
  14. package/src/baselib-errors.pyj +45 -1
  15. package/src/baselib-internal.pyj +346 -49
  16. package/src/baselib-itertools.pyj +17 -4
  17. package/src/baselib-str.pyj +46 -4
  18. package/src/lib/abc.pyj +317 -0
  19. package/src/lib/copy.pyj +120 -0
  20. package/src/lib/dataclasses.pyj +532 -0
  21. package/src/lib/enum.pyj +125 -0
  22. package/src/lib/pythonize.pyj +1 -1
  23. package/src/lib/re.pyj +35 -1
  24. package/src/lib/react.pyj +74 -0
  25. package/src/lib/typing.pyj +577 -0
  26. package/src/monaco-language-service/builtins.js +19 -4
  27. package/src/monaco-language-service/diagnostics.js +40 -19
  28. package/src/output/classes.pyj +161 -25
  29. package/src/output/codegen.pyj +16 -2
  30. package/src/output/exceptions.pyj +97 -1
  31. package/src/output/functions.pyj +87 -5
  32. package/src/output/jsx.pyj +164 -0
  33. package/src/output/literals.pyj +28 -2
  34. package/src/output/loops.pyj +5 -2
  35. package/src/output/modules.pyj +1 -1
  36. package/src/output/operators.pyj +108 -36
  37. package/src/output/statements.pyj +2 -2
  38. package/src/output/stream.pyj +1 -0
  39. package/src/parse.pyj +496 -128
  40. package/src/tokenizer.pyj +38 -4
  41. package/test/abc.pyj +291 -0
  42. package/test/arithmetic_nostrict.pyj +88 -0
  43. package/test/arithmetic_types.pyj +169 -0
  44. package/test/baselib.pyj +91 -0
  45. package/test/bytes.pyj +467 -0
  46. package/test/classes.pyj +1 -0
  47. package/test/comparison_ops.pyj +173 -0
  48. package/test/dataclasses.pyj +253 -0
  49. package/test/enum.pyj +134 -0
  50. package/test/eval_exec.pyj +56 -0
  51. package/test/format.pyj +148 -0
  52. package/test/object.pyj +64 -0
  53. package/test/python_compat.pyj +17 -15
  54. package/test/python_features.pyj +89 -21
  55. package/test/regexp.pyj +29 -1
  56. package/test/tuples.pyj +96 -0
  57. package/test/typing.pyj +469 -0
  58. package/test/unit/index.js +2292 -70
  59. package/test/unit/language-service.js +674 -4
  60. package/test/unit/web-repl.js +1106 -0
  61. package/test/vars_locals_globals.pyj +94 -0
  62. package/tools/cli.js +11 -0
  63. package/tools/compile.js +5 -0
  64. package/tools/embedded_compiler.js +15 -4
  65. package/tools/lint.js +16 -19
  66. package/tools/repl.js +1 -1
  67. package/web-repl/env.js +122 -0
  68. package/web-repl/main.js +1 -3
  69. package/web-repl/rapydscript.js +125 -3
  70. package/PYTHON_DIFFERENCES_REPORT.md +0 -291
  71. package/PYTHON_FEATURE_COVERAGE.md +0 -200
  72. package/hack_demo.pyj +0 -112
@@ -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')
@@ -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')
@@ -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 — JS string coercion ──────────────────────
118
+ # ── 8. Ordering operators on lists — behaviour depends on overload_operators ──
119
119
  # Python: [10] < [9] compares element-wise → False (10 > 9).
120
- # RapydScript: < coerces to string → '10' < '9' is True (char comparison).
121
- # This is a common gotcha: single-element lists with multi-digit numbers.
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' → [10] < [9] is True.
125
- ok([10] < [9]) # True in RapydScript (string: '[10]' < '[9]')
126
- ae([9] < [10], False) # False (string: '[9]' > '[10]')
127
-
128
- # Compare: Python uses numeric element-wise ordering, so [10] > [9] (10 > 9).
129
- # RapydScript gives the opposite result for these specific values.
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
- # For correct numeric comparisons, compare elements explicitly:
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 split() and replace() as JS versions ─────────────────
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
- # JS String.prototype.split() with no args: returns the whole string in a 1-element array.
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
- # After strings(), .split() is STILL the JS version (by design):
246
- ade('a b'.split(), ['a b']) # JS: no-arg split = one-element array
247
- ade('a b'.split(' '), ['a', 'b']) # JS split with separator still works
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.
@@ -366,14 +366,41 @@ def _test_frozenset():
366
366
  _test_frozenset()
367
367
 
368
368
  # ── 17. int.bit_length() ─────────────────────────────────────────────────────
369
- # STATUS: NOT SUPPORTED — Number prototype does not have bit_length.
370
- #
371
- # ae((255).bit_length(), 8)
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: NOT SUPPORTED — Number prototype does not have is_integer.
375
- #
376
- # ae((1.0).is_integer(), True)
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: NOT SUPPORTED zip() silently truncates to shortest.
556
+ # STATUS: WORKSequal-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: NOT SUPPORTED — no j suffix; no complex type.
878
- # SKIP:
879
- # c = 3+4j
880
- # ae(c.real, 3)
881
- # ae(c.imag, 4)
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(obj, spec) dispatches to obj.__format__(spec).
944
- # Tested fully in test/str.pyj.
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: 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)
1001
+ # STATUS: WORKSClass[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
- # Tested fully in test/str.pyj.
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'))
@@ -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')