rapydscript-ns 0.8.4 → 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 (132) hide show
  1. package/.agignore +1 -1
  2. package/.github/workflows/ci.yml +38 -38
  3. package/=template.pyj +5 -5
  4. package/CHANGELOG.md +18 -0
  5. package/HACKING.md +103 -103
  6. package/LICENSE +24 -24
  7. package/README.md +715 -169
  8. package/TODO.md +9 -2
  9. package/add-toc-to-readme +2 -2
  10. package/bin/export +75 -75
  11. package/bin/rapydscript +70 -70
  12. package/bin/web-repl-export +102 -102
  13. package/build +2 -2
  14. package/language-service/index.js +36 -27
  15. package/package.json +1 -1
  16. package/publish.py +37 -37
  17. package/release/baselib-plain-pretty.js +2358 -168
  18. package/release/baselib-plain-ugly.js +73 -3
  19. package/release/compiler.js +6282 -3092
  20. package/release/signatures.json +31 -30
  21. package/session.vim +4 -4
  22. package/setup.cfg +2 -2
  23. package/src/ast.pyj +1 -0
  24. package/src/baselib-builtins.pyj +340 -2
  25. package/src/baselib-bytes.pyj +664 -0
  26. package/src/baselib-errors.pyj +1 -1
  27. package/src/baselib-internal.pyj +267 -60
  28. package/src/baselib-itertools.pyj +110 -97
  29. package/src/baselib-str.pyj +22 -4
  30. package/src/compiler.pyj +36 -36
  31. package/src/errors.pyj +30 -30
  32. package/src/lib/abc.pyj +317 -0
  33. package/src/lib/aes.pyj +646 -646
  34. package/src/lib/copy.pyj +120 -120
  35. package/src/lib/dataclasses.pyj +532 -0
  36. package/src/lib/elementmaker.pyj +83 -83
  37. package/src/lib/encodings.pyj +126 -126
  38. package/src/lib/enum.pyj +125 -0
  39. package/src/lib/gettext.pyj +569 -569
  40. package/src/lib/itertools.pyj +580 -580
  41. package/src/lib/math.pyj +193 -193
  42. package/src/lib/operator.pyj +11 -11
  43. package/src/lib/pythonize.pyj +20 -20
  44. package/src/lib/random.pyj +118 -118
  45. package/src/lib/re.pyj +504 -470
  46. package/src/lib/react.pyj +74 -74
  47. package/src/lib/traceback.pyj +63 -63
  48. package/src/lib/typing.pyj +577 -0
  49. package/src/lib/uuid.pyj +77 -77
  50. package/src/monaco-language-service/builtins.js +14 -4
  51. package/src/monaco-language-service/diagnostics.js +19 -20
  52. package/src/monaco-language-service/dts.js +550 -550
  53. package/src/output/classes.pyj +62 -26
  54. package/src/output/comments.pyj +45 -45
  55. package/src/output/exceptions.pyj +201 -201
  56. package/src/output/functions.pyj +78 -5
  57. package/src/output/jsx.pyj +164 -164
  58. package/src/output/loops.pyj +5 -2
  59. package/src/output/operators.pyj +100 -34
  60. package/src/output/treeshake.pyj +182 -182
  61. package/src/output/utils.pyj +72 -72
  62. package/src/parse.pyj +80 -16
  63. package/src/string_interpolation.pyj +72 -72
  64. package/src/tokenizer.pyj +9 -4
  65. package/src/unicode_aliases.pyj +576 -576
  66. package/src/utils.pyj +192 -192
  67. package/test/_import_one.pyj +37 -37
  68. package/test/_import_two/__init__.pyj +11 -11
  69. package/test/_import_two/level2/deep.pyj +4 -4
  70. package/test/_import_two/other.pyj +6 -6
  71. package/test/_import_two/sub.pyj +13 -13
  72. package/test/abc.pyj +291 -0
  73. package/test/aes_vectors.pyj +421 -421
  74. package/test/annotations.pyj +80 -80
  75. package/test/arithmetic_nostrict.pyj +88 -0
  76. package/test/arithmetic_types.pyj +169 -0
  77. package/test/baselib.pyj +91 -0
  78. package/test/bytes.pyj +467 -0
  79. package/test/classes.pyj +1 -0
  80. package/test/comparison_ops.pyj +173 -0
  81. package/test/dataclasses.pyj +253 -0
  82. package/test/decorators.pyj +77 -77
  83. package/test/docstrings.pyj +39 -39
  84. package/test/elementmaker_test.pyj +45 -45
  85. package/test/enum.pyj +134 -0
  86. package/test/eval_exec.pyj +56 -0
  87. package/test/format.pyj +148 -0
  88. package/test/functions.pyj +151 -151
  89. package/test/generators.pyj +41 -41
  90. package/test/generic.pyj +370 -370
  91. package/test/imports.pyj +72 -72
  92. package/test/internationalization.pyj +73 -73
  93. package/test/lint.pyj +164 -164
  94. package/test/loops.pyj +85 -85
  95. package/test/numpy.pyj +734 -734
  96. package/test/object.pyj +64 -0
  97. package/test/omit_function_metadata.pyj +20 -20
  98. package/test/python_compat.pyj +17 -15
  99. package/test/python_features.pyj +70 -15
  100. package/test/regexp.pyj +83 -55
  101. package/test/repl.pyj +121 -121
  102. package/test/scoped_flags.pyj +76 -76
  103. package/test/tuples.pyj +96 -0
  104. package/test/typing.pyj +469 -0
  105. package/test/unit/index.js +116 -7
  106. package/test/unit/language-service-dts.js +543 -543
  107. package/test/unit/language-service-hover.js +455 -455
  108. package/test/unit/language-service.js +84 -0
  109. package/test/unit/web-repl.js +804 -1
  110. package/test/vars_locals_globals.pyj +94 -0
  111. package/tools/cli.js +558 -547
  112. package/tools/compile.js +224 -219
  113. package/tools/completer.js +131 -131
  114. package/tools/embedded_compiler.js +262 -251
  115. package/tools/gettext.js +185 -185
  116. package/tools/ini.js +65 -65
  117. package/tools/lint.js +16 -19
  118. package/tools/msgfmt.js +187 -187
  119. package/tools/repl.js +223 -223
  120. package/tools/test.js +118 -118
  121. package/tools/utils.js +128 -128
  122. package/tools/web_repl.js +95 -95
  123. package/try +41 -41
  124. package/web-repl/env.js +196 -196
  125. package/web-repl/index.html +163 -163
  126. package/web-repl/main.js +252 -252
  127. package/web-repl/prism.css +139 -139
  128. package/web-repl/prism.js +113 -113
  129. package/web-repl/rapydscript.js +224 -224
  130. package/web-repl/sha1.js +25 -25
  131. package/PYTHON_DIFFERENCES_REPORT.md +0 -291
  132. package/PYTHON_FEATURE_COVERAGE.md +0 -200
@@ -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')
@@ -1,20 +1,20 @@
1
- # globals: RapydScript
2
-
3
- # Test that omit_function_metadata flag suppresses __module__ and __argnames__
4
- code = 'def foo(a, b):\n return a + b'
5
- ast = RapydScript.parse(code, {'filename': '<test>'})
6
-
7
- # Without flag: output should contain __module__ and __argnames__
8
- output_with = v'new RapydScript.OutputStream({"beautify": true, "omit_baselib": true})'
9
- ast.print(output_with)
10
- result_with = output_with.get()
11
- assrt.ok(result_with.indexOf('__module__') >= 0, '__module__ should appear in default output')
12
- assrt.ok(result_with.indexOf('__argnames__') >= 0, '__argnames__ should appear in default output')
13
-
14
- # With flag: output should not contain __module__, __argnames__, or Object.defineProperties
15
- output_without = v'new RapydScript.OutputStream({"beautify": true, "omit_baselib": true, "omit_function_metadata": true})'
16
- ast.print(output_without)
17
- result_without = output_without.get()
18
- assrt.ok(result_without.indexOf('__module__') is -1, '__module__ should not appear when omit_function_metadata is set')
19
- assrt.ok(result_without.indexOf('__argnames__') is -1, '__argnames__ should not appear when omit_function_metadata is set')
20
- assrt.ok(result_without.indexOf('Object.defineProperties') is -1, 'Object.defineProperties should not appear when omit_function_metadata is set')
1
+ # globals: RapydScript
2
+
3
+ # Test that omit_function_metadata flag suppresses __module__ and __argnames__
4
+ code = 'def foo(a, b):\n return a + b'
5
+ ast = RapydScript.parse(code, {'filename': '<test>'})
6
+
7
+ # Without flag: output should contain __module__ and __argnames__
8
+ output_with = v'new RapydScript.OutputStream({"beautify": true, "omit_baselib": true})'
9
+ ast.print(output_with)
10
+ result_with = output_with.get()
11
+ assrt.ok(result_with.indexOf('__module__') >= 0, '__module__ should appear in default output')
12
+ assrt.ok(result_with.indexOf('__argnames__') >= 0, '__argnames__ should appear in default output')
13
+
14
+ # With flag: output should not contain __module__, __argnames__, or Object.defineProperties
15
+ output_without = v'new RapydScript.OutputStream({"beautify": true, "omit_baselib": true, "omit_function_metadata": true})'
16
+ ast.print(output_without)
17
+ result_without = output_without.get()
18
+ assrt.ok(result_without.indexOf('__module__') is -1, '__module__ should not appear when omit_function_metadata is set')
19
+ assrt.ok(result_without.indexOf('__argnames__') is -1, '__argnames__ should not appear when omit_function_metadata is set')
20
+ assrt.ok(result_without.indexOf('Object.defineProperties') is -1, 'Object.defineProperties should not appear when omit_function_metadata is set')
@@ -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,8 +992,10 @@ 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
1001
  # STATUS: ✓ WORKS — Class[item] dispatches to __class_getitem__(cls, item).
@@ -1099,7 +1153,8 @@ _check_unhashable({'a': 1})
1099
1153
 
1100
1154
  # ── 55. format(value, spec) builtin ──────────────────────────────────────────
1101
1155
  # STATUS: ✓ WORKS — format(value, spec) is defined and dispatches to __format__.
1102
- # 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.
1103
1158
  #
1104
1159
  # ae(format(3.14159, '.2f'), '3.14')
1105
1160
 
package/test/regexp.pyj CHANGED
@@ -1,55 +1,83 @@
1
- # vim:fileencoding=utf-8
2
- # License: BSD
3
- # Copyright: 2015, Kovid Goyal <kovid at kovidgoyal.net>
4
-
5
- # Test the re module
6
- import re
7
- s = "Isaac Newton, physicist"
8
- c = re.search("(\\w+) (\\w+)", s)
9
- assrt.equal(c.group(1), 'Isaac')
10
- assrt.equal(c.group(2), 'Newton')
11
- c = re.match("(\\w+) (\\w+)", s)
12
- assrt.equal(c.group(1), 'Isaac')
13
- assrt.equal(c.start(1), 0)
14
- assrt.equal(c.end(1), c.group(1).length)
15
- assrt.equal(c.start(2), c.group(1).length + 1)
16
- assrt.equal(c.group(2), 'Newton')
17
- m = re.search('a(b)cd', 'abc abcd')
18
- assrt.equal(m.group(1), 'b')
19
- assrt.equal(m.start(1), m.string.lastIndexOf('b'))
20
- assrt.deepEqual(re.split('\\s', s), ['Isaac', 'Newton,', 'physicist'])
21
- assrt.deepEqual(re.findall('s[a-z]', s), ['sa', 'si', 'st'])
22
- assrt.deepEqual([m.group() for m in re.finditer('s[a-z]', s)], ['sa', 'si', 'st'])
23
- assrt.deepEqual(re.findall(/s[a-z]/, s), ['sa', 'si', 'st'])
24
- assrt.equal(re.sub('[A-Z]', '_', s), '_saac _ewton, physicist')
25
- assrt.equal(re.sub('[A-Z]', '_', s, count=1), '_saac Newton, physicist')
26
- assrt.equal(re.search('a[.]b', 'axb'), None)
27
- assrt.equal(re.search(r'a\.b', 'axb'), None)
28
- assrt.equal(re.search('a[.]b', 'axb', flags=re.D), None)
29
- assrt.equal(re.search('.+', 'a\nb').group(), 'a')
30
- assrt.equal(re.search('.+', 'a\nb', flags=re.D).group(), 'a\nb')
31
- assrt.equal(re.search('(?s).+', 'a\nb').group(), 'a\nb')
32
- assrt.equal(re.sub('a(b)', r'xx', 'ab'), r'xx')
33
- assrt.equal(re.sub('a(b)', r'\\1', 'ab'), r'\1')
34
- assrt.equal(re.sub('a(b)', r'\\\1', 'ab'), r'\b')
35
- assrt.equal(re.sub('a(b)', r'\g<1>', 'ab'), r'b')
36
- assrt.equal(re.sub('a(b)', def(m):return m.group(1);, 'ab'), r'b')
37
- assrt.equal(']', re.match('[]]', ']').group())
38
-
39
- assrt.throws(def():re.search(r'(?(1)a|b)b', 'ab');, re.error)
40
-
41
- # Test lookbehind assertions
42
- assrt.equal('acdb', re.sub(r'(?<=a)b', 'c', 'abdb'))
43
-
44
- # Test named groups
45
- assrt.equal('aa', re.sub(r'(?P<a>a)b', r'\g<a>\1', 'ab'))
46
- assrt.equal('bb', re.sub(r'(?P<a>a)(?P=a)', r'bb', 'aa'))
47
- assrt.equal('ab', re.sub(r'(.)(?P<a>a)', r'\g<a>\1', 'ba'))
48
- assrt.deepEqual({'a':'a', 'b':'b'}, re.search(r'(?P<a>a)(?P<b>b)', 'ab').groupdict())
49
-
50
- # Test verbose mode literals
51
- assrt.equal(re.search(///
52
- a
53
- . # anything
54
- b
55
- ///, ' axb').group(), 'axb')
1
+ # vim:fileencoding=utf-8
2
+ # License: BSD
3
+ # Copyright: 2015, Kovid Goyal <kovid at kovidgoyal.net>
4
+
5
+ # Test the re module
6
+ import re
7
+ s = "Isaac Newton, physicist"
8
+ c = re.search("(\\w+) (\\w+)", s)
9
+ assrt.equal(c.group(1), 'Isaac')
10
+ assrt.equal(c.group(2), 'Newton')
11
+ c = re.match("(\\w+) (\\w+)", s)
12
+ assrt.equal(c.group(1), 'Isaac')
13
+ assrt.equal(c.start(1), 0)
14
+ assrt.equal(c.end(1), c.group(1).length)
15
+ assrt.equal(c.start(2), c.group(1).length + 1)
16
+ assrt.equal(c.group(2), 'Newton')
17
+ m = re.search('a(b)cd', 'abc abcd')
18
+ assrt.equal(m.group(1), 'b')
19
+ assrt.equal(m.start(1), m.string.lastIndexOf('b'))
20
+ assrt.deepEqual(re.split('\\s', s), ['Isaac', 'Newton,', 'physicist'])
21
+ assrt.deepEqual(re.findall('s[a-z]', s), ['sa', 'si', 'st'])
22
+ assrt.deepEqual([m.group() for m in re.finditer('s[a-z]', s)], ['sa', 'si', 'st'])
23
+ assrt.deepEqual(re.findall(/s[a-z]/, s), ['sa', 'si', 'st'])
24
+ assrt.equal(re.sub('[A-Z]', '_', s), '_saac _ewton, physicist')
25
+ assrt.equal(re.sub('[A-Z]', '_', s, count=1), '_saac Newton, physicist')
26
+ assrt.equal(re.search('a[.]b', 'axb'), None)
27
+ assrt.equal(re.search(r'a\.b', 'axb'), None)
28
+ assrt.equal(re.search('a[.]b', 'axb', flags=re.D), None)
29
+ assrt.equal(re.search('.+', 'a\nb').group(), 'a')
30
+ assrt.equal(re.search('.+', 'a\nb', flags=re.D).group(), 'a\nb')
31
+ assrt.equal(re.search('(?s).+', 'a\nb').group(), 'a\nb')
32
+ assrt.equal(re.sub('a(b)', r'xx', 'ab'), r'xx')
33
+ assrt.equal(re.sub('a(b)', r'\\1', 'ab'), r'\1')
34
+ assrt.equal(re.sub('a(b)', r'\\\1', 'ab'), r'\b')
35
+ assrt.equal(re.sub('a(b)', r'\g<1>', 'ab'), r'b')
36
+ assrt.equal(re.sub('a(b)', def(m):return m.group(1);, 'ab'), r'b')
37
+ assrt.equal(']', re.match('[]]', ']').group())
38
+
39
+ assrt.throws(def():re.search(r'(?(1)a|b)b', 'ab');, re.error)
40
+
41
+ # Test positive lookbehind assertions
42
+ assrt.equal('acdb', re.sub(r'(?<=a)b', 'c', 'abdb'))
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
+
72
+ # Test named groups
73
+ assrt.equal('aa', re.sub(r'(?P<a>a)b', r'\g<a>\1', 'ab'))
74
+ assrt.equal('bb', re.sub(r'(?P<a>a)(?P=a)', r'bb', 'aa'))
75
+ assrt.equal('ab', re.sub(r'(.)(?P<a>a)', r'\g<a>\1', 'ba'))
76
+ assrt.deepEqual({'a':'a', 'b':'b'}, re.search(r'(?P<a>a)(?P<b>b)', 'ab').groupdict())
77
+
78
+ # Test verbose mode literals
79
+ assrt.equal(re.search(///
80
+ a
81
+ . # anything
82
+ b
83
+ ///, ' axb').group(), 'axb')