rapydscript-ns 0.9.2 → 0.9.4

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 (88) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/PYTHON_GAPS.md +352 -0
  3. package/README.md +176 -32
  4. package/TODO.md +1 -128
  5. package/bin/rapydscript +70 -70
  6. package/language-service/index.js +242 -11
  7. package/memory/project_string_impl.md +43 -0
  8. package/package.json +1 -1
  9. package/release/baselib-plain-pretty.js +248 -38
  10. package/release/baselib-plain-ugly.js +8 -8
  11. package/release/compiler.js +778 -277
  12. package/release/signatures.json +30 -30
  13. package/src/ast.pyj +10 -1
  14. package/src/baselib-builtins.pyj +56 -2
  15. package/src/baselib-containers.pyj +25 -1
  16. package/src/baselib-errors.pyj +7 -3
  17. package/src/baselib-internal.pyj +51 -6
  18. package/src/baselib-str.pyj +18 -5
  19. package/src/lib/asyncio.pyj +534 -0
  20. package/src/lib/base64.pyj +399 -0
  21. package/src/lib/bisect.pyj +73 -0
  22. package/src/lib/collections.pyj +228 -4
  23. package/src/lib/csv.pyj +494 -0
  24. package/src/lib/heapq.pyj +98 -0
  25. package/src/lib/html.pyj +382 -0
  26. package/src/lib/http/__init__.pyj +98 -0
  27. package/src/lib/http/client.pyj +304 -0
  28. package/src/lib/http/cookies.pyj +236 -0
  29. package/src/lib/logging.pyj +672 -0
  30. package/src/lib/pprint.pyj +455 -0
  31. package/src/lib/pythonize.pyj +20 -20
  32. package/src/lib/statistics.pyj +0 -0
  33. package/src/lib/string.pyj +357 -0
  34. package/src/lib/textwrap.pyj +329 -0
  35. package/src/lib/urllib/__init__.pyj +14 -0
  36. package/src/lib/urllib/error.pyj +66 -0
  37. package/src/lib/urllib/parse.pyj +475 -0
  38. package/src/lib/urllib/request.pyj +86 -0
  39. package/src/monaco-language-service/analyzer.js +5 -2
  40. package/src/monaco-language-service/completions.js +26 -0
  41. package/src/monaco-language-service/diagnostics.js +203 -4
  42. package/src/monaco-language-service/scope.js +1 -0
  43. package/src/output/codegen.pyj +4 -1
  44. package/src/output/functions.pyj +152 -6
  45. package/src/output/loops.pyj +17 -2
  46. package/src/output/modules.pyj +1 -1
  47. package/src/output/operators.pyj +15 -0
  48. package/src/output/stream.pyj +0 -1
  49. package/src/parse.pyj +108 -24
  50. package/src/tokenizer.pyj +19 -3
  51. package/test/async_generators.pyj +144 -0
  52. package/test/asyncio.pyj +307 -0
  53. package/test/base64.pyj +202 -0
  54. package/test/baselib.pyj +23 -0
  55. package/test/bisect.pyj +178 -0
  56. package/test/chainmap.pyj +185 -0
  57. package/test/csv.pyj +405 -0
  58. package/test/float_special.pyj +64 -0
  59. package/test/heapq.pyj +174 -0
  60. package/test/html.pyj +212 -0
  61. package/test/http.pyj +259 -0
  62. package/test/imports.pyj +79 -72
  63. package/test/logging.pyj +356 -0
  64. package/test/long.pyj +130 -0
  65. package/test/parenthesized_with.pyj +141 -0
  66. package/test/pprint.pyj +232 -0
  67. package/test/python_compat.pyj +3 -5
  68. package/test/python_modulo.pyj +76 -0
  69. package/test/python_modulo_off.pyj +21 -0
  70. package/test/statistics.pyj +224 -0
  71. package/test/str.pyj +14 -0
  72. package/test/string.pyj +245 -0
  73. package/test/textwrap.pyj +172 -0
  74. package/test/type_display.pyj +48 -0
  75. package/test/type_enforcement.pyj +164 -0
  76. package/test/unit/index.js +94 -6
  77. package/test/unit/language-service-completions.js +121 -0
  78. package/test/unit/language-service-scope.js +32 -0
  79. package/test/unit/language-service.js +190 -5
  80. package/test/unit/run-language-service.js +17 -3
  81. package/test/unit/web-repl.js +2401 -13
  82. package/test/urllib.pyj +193 -0
  83. package/tools/compile.js +1 -1
  84. package/tools/embedded_compiler.js +7 -7
  85. package/tools/export.js +4 -2
  86. package/web-repl/main.js +1 -1
  87. package/web-repl/rapydscript.js +7 -5
  88. package/test/omit_function_metadata.pyj +0 -20
@@ -0,0 +1,232 @@
1
+ # globals: assrt
2
+ # vim:fileencoding=utf-8
3
+ #
4
+ # pprint.pyj
5
+ # Tests for the pprint standard library module.
6
+
7
+ from pprint import (
8
+ pformat,
9
+ pprint,
10
+ pp,
11
+ saferepr,
12
+ isreadable,
13
+ isrecursive,
14
+ PrettyPrinter,
15
+ )
16
+
17
+ ae = assrt.equal
18
+ ade = assrt.deepEqual
19
+ ok = assrt.ok
20
+
21
+ # ── 1. Atoms — pformat returns repr() for primitives ────────────────────────
22
+
23
+ ae(pformat(None), 'None')
24
+ ae(pformat(True), 'True')
25
+ ae(pformat(False), 'False')
26
+ ae(pformat(42), '42')
27
+ ae(pformat(3.14), '3.14')
28
+ ae(pformat('hello'), '"hello"')
29
+ ae(pformat(''), '""')
30
+
31
+ # ── 2. Empty containers ─────────────────────────────────────────────────────
32
+
33
+ ae(pformat([]), '[]')
34
+ ae(pformat({}), '{}')
35
+ ae(pformat(set()), 'set()')
36
+ ae(pformat(frozenset()), 'frozenset()')
37
+
38
+ # ── 3. Short containers fit on a single line ────────────────────────────────
39
+
40
+ ae(pformat([1, 2, 3]), '[1, 2, 3]')
41
+ ae(pformat({'a': 1, 'b': 2}), '{"a": 1, "b": 2}')
42
+ ae(pformat({1, 2, 3}), '{1, 2, 3}')
43
+ ae(pformat(frozenset([1, 2, 3])), 'frozenset({1, 2, 3})')
44
+
45
+ # ── 4. Wide list breaks across lines ────────────────────────────────────────
46
+
47
+ _wide = pformat([1, 2, 3, 4, 5], width=8)
48
+ ae(_wide, '[1,\n 2,\n 3,\n 4,\n 5]')
49
+
50
+ # ── 5. Wide dict breaks across lines, keys sorted by default ────────────────
51
+
52
+ _wd = pformat({'name': 'Alice', 'age': 30}, width=15)
53
+ ae(_wd, '{"age": 30,\n "name": "Alice"}')
54
+
55
+ # ── 6. sort_dicts=False preserves insertion order ───────────────────────────
56
+
57
+ # Use ρσ_dict-backed dict() to guarantee Map ordering
58
+ _d = dict()
59
+ _d.set('c', 1)
60
+ _d.set('a', 2)
61
+ _d.set('b', 3)
62
+ ae(pformat(_d, sort_dicts=False), '{"c": 1, "a": 2, "b": 3}')
63
+ ae(pformat(_d, sort_dicts=True), '{"a": 2, "b": 3, "c": 1}')
64
+
65
+ # ── 7. pp() defaults sort_dicts=False, has same parameters as pprint ────────
66
+
67
+ # pp prints rather than returning; we just confirm it runs without throwing.
68
+ pp({'c': 1, 'a': 2})
69
+
70
+ # ── 8. depth parameter limits expansion ─────────────────────────────────────
71
+
72
+ ae(pformat([1, [2, [3, [4]]]], depth=1), '[1, [...]]')
73
+ ae(pformat([1, [2, [3, [4]]]], depth=2), '[1, [2, [...]]]')
74
+ ae(pformat({'a': {'b': {'c': 1}}}, depth=1), '{"a": {...}}')
75
+
76
+ # ── 9. Nested containers — multi-line indentation ───────────────────────────
77
+
78
+ # After "[" at col 0, items align at col 1 (default indent=1).
79
+ ae(pformat([[1, 2], [3, 4]], width=20),
80
+ '[[1, 2], [3, 4]]')
81
+
82
+ # With smaller width, both inner lists must break to their own lines.
83
+ _nested = pformat([[1, 2], [3, 4], [5, 6], [7, 8]], width=12)
84
+ ade(_nested.split('\n'), ['[[1, 2],', ' [3, 4],', ' [5, 6],', ' [7, 8]]'])
85
+
86
+ # ── 10. indent parameter adjusts inner indentation ──────────────────────────
87
+
88
+ ae(pformat([1, 2, 3], width=5, indent=4),
89
+ '[ 1,\n 2,\n 3]')
90
+
91
+ # ── 11. compact=True packs multiple items per line ──────────────────────────
92
+
93
+ _c = pformat([1, 2, 3, 4, 5, 6, 7, 8], compact=True, width=15)
94
+ ade(_c.split('\n'), ['[1, 2, 3, 4, 5,', ' 6, 7, 8]'])
95
+
96
+ # ── 12. saferepr — single-line repr; recursive marker for cycles ────────────
97
+
98
+ ae(saferepr([1, 2, 3]), '[1, 2, 3]')
99
+ ae(saferepr({'a': 1}), '{"a": 1}')
100
+ ae(saferepr('text'), '"text"')
101
+
102
+ # Self-referential list -> recursive marker, not infinite recursion
103
+ _self_list = [1, 2]
104
+ _self_list.push(_self_list)
105
+ _rep = saferepr(_self_list)
106
+ ok(_rep.indexOf('<Recursion on') >= 0,
107
+ 'saferepr should emit <Recursion on ...> for cycles')
108
+
109
+ # ── 13. isreadable — True only when no recursion and only readable types ────
110
+
111
+ ok(isreadable([1, 2, 3]))
112
+ ok(isreadable({'a': 1, 'b': 2}))
113
+ ok(isreadable('hi'))
114
+ ok(isreadable(42))
115
+
116
+ # Sets are not readable in Python's pprint (set literal != set()).
117
+ ok(not isreadable(set())) # explicitly empty set -> 'set()' is *not* a readable literal
118
+
119
+ # A self-referential structure is never readable
120
+ ok(not isreadable(_self_list))
121
+
122
+ # ── 14. isrecursive — True only for recursive structures ────────────────────
123
+
124
+ ok(not isrecursive([1, 2, 3]))
125
+ ok(not isrecursive({'a': 1}))
126
+ ok(isrecursive(_self_list))
127
+
128
+ _self_dict = {}
129
+ _self_dict['k'] = _self_dict
130
+ ok(isrecursive(_self_dict))
131
+
132
+ # ── 15. PrettyPrinter — class-based use ─────────────────────────────────────
133
+
134
+ _pp_class = PrettyPrinter(indent=2, width=10)
135
+ ae(_pp_class.pformat([1, 2, 3]), '[1, 2, 3]')
136
+
137
+ _pp_wide = PrettyPrinter(indent=2, width=8)
138
+ ae(_pp_wide.pformat([10, 20, 30]),
139
+ '[ 10,\n 20,\n 30]')
140
+ # `[` at col 0, then indent_per_level-1 == 1 space pad, then "10" at col 2
141
+ # subsequent lines indented to col 2
142
+
143
+ # ── 16. PrettyPrinter.isreadable / isrecursive instance methods ─────────────
144
+
145
+ _pp_ir = PrettyPrinter()
146
+ ok(_pp_ir.isreadable([1, 2, 3]))
147
+ ok(not _pp_ir.isrecursive([1, 2, 3]))
148
+ ok(_pp_ir.isrecursive(_self_list))
149
+
150
+ # ── 17. Validation — ValueError for invalid PrettyPrinter args ──────────────
151
+
152
+ _caught = False
153
+ try:
154
+ PrettyPrinter(indent=-1)
155
+ except ValueError:
156
+ _caught = True
157
+ ok(_caught, 'PrettyPrinter should reject indent < 0')
158
+
159
+ _caught2 = False
160
+ try:
161
+ PrettyPrinter(depth=0)
162
+ except ValueError:
163
+ _caught2 = True
164
+ ok(_caught2, 'PrettyPrinter should reject depth <= 0')
165
+
166
+ # ── 18. Mixed nested structure formatting ───────────────────────────────────
167
+
168
+ _data = {
169
+ 'count': 2,
170
+ 'users': [
171
+ {'age': 30, 'name': 'Alice'},
172
+ {'age': 25, 'name': 'Bob'},
173
+ ],
174
+ }
175
+
176
+ # Width permits inline lists.
177
+ ae(pformat(_data, width=120),
178
+ '{"count": 2, "users": [{"age": 30, "name": "Alice"}, {"age": 25, "name": "Bob"}]}')
179
+
180
+ # Narrow width forces multi-line expansion.
181
+ _narrow = pformat(_data, width=30)
182
+ ok(_narrow.indexOf('\n') > 0, 'narrow width should produce multi-line output')
183
+
184
+ # ── 19. Stream output (PrettyPrinter with custom stream) ────────────────────
185
+
186
+ class _Collector:
187
+ def __init__(self):
188
+ self.parts = v'[]'
189
+
190
+ def write(self, s):
191
+ self.parts.push(s)
192
+
193
+ def getvalue(self):
194
+ return self.parts.join('')
195
+
196
+ _col = _Collector()
197
+ _pp_s = PrettyPrinter(stream=_col)
198
+ _pp_s.pprint([1, 2, 3])
199
+ ae(_col.getvalue(), '[1, 2, 3]\n')
200
+
201
+ # ── 20. pprint() module-level function with custom stream ───────────────────
202
+
203
+ _col2 = _Collector()
204
+ pprint({'a': 1}, stream=_col2)
205
+ ae(_col2.getvalue(), '{"a": 1}\n')
206
+
207
+ # ── 21. Empty / single-element containers do not break ──────────────────────
208
+
209
+ ae(pformat([42]), '[42]')
210
+ ae(pformat({'k': 'v'}), '{"k": "v"}')
211
+ ae(pformat([], width=1), '[]')
212
+ ae(pformat({}, width=1), '{}')
213
+
214
+ # ── 22. Numbers and booleans preserved as repr ──────────────────────────────
215
+
216
+ ae(pformat([True, False, None, 0]), '[True, False, None, 0]')
217
+
218
+ # ── 23. Strings with special characters get repr-escaped ────────────────────
219
+
220
+ ae(pformat('a\nb'), '"a\\nb"')
221
+ ae(pformat('"quoted"'), '"\\"quoted\\""')
222
+
223
+ # ── 24. underscore_numbers parameter is accepted (currently ignored) ────────
224
+
225
+ ae(pformat(1000000, underscore_numbers=True), '1000000')
226
+
227
+ # ── 25. pp() with kwargs forwarded ──────────────────────────────────────────
228
+
229
+ _col3 = _Collector()
230
+ pp({'b': 2, 'a': 1}, stream=_col3, width=80)
231
+ # pp defaults sort_dicts=False, so 'b' comes before 'a' (insertion order)
232
+ ae(_col3.getvalue(), '{"b": 2, "a": 1}\n')
@@ -235,7 +235,7 @@ strings()
235
235
  ae(' hello '.strip(), 'hello')
236
236
  ae('hello world'.upper(), 'HELLO WORLD')
237
237
 
238
- # ── 15. strings() leaves replace() as JS version; split() defaults to ' ' ─────
238
+ # ── 15. strings() patches replace(); split() defaults to ' ' ──────────────────
239
239
  # (strings() is active from section 14 above)
240
240
  # Python str.split() with no args: splits on any whitespace, strips leading/trailing.
241
241
  # RapydScript .split() with no args: transpiles to .split(" ") — splits on single space.
@@ -248,11 +248,9 @@ ade(str.split('a b c'), ['a', 'b', 'c'])
248
248
  ade('a b'.split(), ['a', '', 'b']) # double space → empty token between
249
249
  ade('a b'.split(' '), ['a', 'b']) # explicit ' ' separator still works
250
250
 
251
- # Python str.replace() vs JS String.prototype.replace():
252
- # Python replaces ALL occurrences by default.
253
- # JS .replace() with a string pattern only replaces the FIRST occurrence.
251
+ # str.replace() and String.prototype.replace() both use Python semantics (replace all).
254
252
  ae(str.replace('aaa', 'a', 'b'), 'bbb') # Python: replaces all
255
- ae('aaa'.replace('a', 'b'), 'baa') # JS: replaces only first
253
+ ae('aaa'.replace('a', 'b'), 'bbb') # also replaces all (pythonized)
256
254
 
257
255
  # ── 16. Method binding — not automatic ──────────────────────────────────────
258
256
  # Python: bound methods carry a reference to self.
@@ -0,0 +1,76 @@
1
+ # globals: assrt
2
+ # vim:fileencoding=utf-8
3
+ #
4
+ # python_modulo.pyj
5
+ # Tests for Python-style modulo: result takes the sign of the divisor.
6
+ # python_modulo defaults to True; no __python__ import needed.
7
+
8
+ ae = assrt.equal
9
+ ok = assrt.ok
10
+
11
+ # ── Positive operands ────────────────────────────────────────────────────────
12
+ ae(7 % 3, 1)
13
+ ae(0 % 5, 0)
14
+ ae(6 % 3, 0)
15
+
16
+ # ── Negative dividend ────────────────────────────────────────────────────────
17
+ ae(-7 % 3, 2)
18
+ ae(-1 % 3, 2)
19
+ ae(-6 % 3, 0)
20
+
21
+ # ── Negative divisor ─────────────────────────────────────────────────────────
22
+ ae(7 % -3, -2)
23
+ ae(1 % -3, -2)
24
+ ae(6 % -3, 0)
25
+
26
+ # ── Both negative ────────────────────────────────────────────────────────────
27
+ ae(-7 % -3, -1)
28
+ ae(-1 % -3, -1)
29
+ ae(-6 % -3, 0)
30
+
31
+ # ── Floats ───────────────────────────────────────────────────────────────────
32
+ ae(-7.5 % 2, 0.5)
33
+ ae(7.5 % -2, -0.5)
34
+ ae(2.5 % 1, 0.5)
35
+
36
+ # ── Booleans (treated as numbers) ────────────────────────────────────────────
37
+ ae(5 % True, 0)
38
+ ae(False % 3, 0)
39
+
40
+ # ── Augmented `%=` ───────────────────────────────────────────────────────────
41
+ x = -7
42
+ x %= 3
43
+ ae(x, 2)
44
+
45
+ y = 7
46
+ y %= -3
47
+ ae(y, -2)
48
+
49
+ z = -7
50
+ z %= -3
51
+ ae(z, -1)
52
+
53
+ # ── Inside expressions ───────────────────────────────────────────────────────
54
+ ae((-7 % 3) + 10, 12)
55
+ ae(((-10) % 3) * 2, 4)
56
+
57
+ # ── Variables (not just literals) ────────────────────────────────────────────
58
+ a = -7
59
+ b = 3
60
+ ae(a % b, 2)
61
+
62
+ # ── BigInt (Python semantics already worked; regression guard) ───────────────
63
+ ae(BigInt(-7) % BigInt(3), BigInt(2))
64
+ ae(BigInt(7) % BigInt(-3), BigInt(-2))
65
+
66
+ # ── Custom __mod__ wins over numeric path ────────────────────────────────────
67
+ # python_modulo routes through ρσ_op_mod_ns which dispatches __mod__/__rmod__
68
+ # even without overload_operators.
69
+ class Mod:
70
+ def __init__(self, v):
71
+ self.v = v
72
+ def __mod__(self, other):
73
+ return 'mod-' + self.v + '-' + other
74
+
75
+ m = Mod('a')
76
+ ae(m % 'b', 'mod-a-b')
@@ -0,0 +1,21 @@
1
+ # globals: assrt
2
+ # vim:fileencoding=utf-8
3
+ #
4
+ # python_modulo_off.pyj
5
+ # Tests for opting out of Python-style modulo via no_python_modulo.
6
+ # With the flag off, `%` emits raw JS modulo (sign of dividend).
7
+
8
+ from __python__ import no_python_modulo
9
+
10
+ ae = assrt.equal
11
+
12
+ # JS modulo: result takes the sign of the dividend.
13
+ ae(-7 % 3, -1) # Python would give 2
14
+ ae(7 % -3, 1) # Python would give -2
15
+ ae(-7 % -3, -1) # same as Python here
16
+ ae(7 % 3, 1) # positive case identical
17
+
18
+ # Augmented `%=` follows the same flag.
19
+ x = -7
20
+ x %= 3
21
+ ae(x, -1) # Python would give 2
@@ -0,0 +1,224 @@
1
+ # globals: assrt
2
+ # vim:fileencoding=utf-8
3
+ #
4
+ # statistics.pyj
5
+ # Tests for the statistics standard library module.
6
+
7
+ from statistics import (
8
+ StatisticsError,
9
+ mean, fmean, geometric_mean, harmonic_mean,
10
+ median, median_low, median_high, median_grouped, mode, multimode,
11
+ variance, pvariance, stdev, pstdev,
12
+ quantiles,
13
+ covariance, correlation, linear_regression,
14
+ NormalDist,
15
+ )
16
+
17
+ ae = assrt.equal
18
+ ade = assrt.deepEqual
19
+ ok = assrt.ok
20
+
21
+
22
+ def close(a, b, tol=1e-9):
23
+ return Math.abs(a - b) < tol
24
+
25
+
26
+ # ── 1. mean ───────────────────────────────────────────────────────────────────
27
+
28
+ ae(mean([1, 2, 3, 4]), 2.5)
29
+ ae(mean([1, 2, 3]), 2)
30
+ ae(mean([5]), 5)
31
+ ae(mean([-1, 1]), 0)
32
+ ok(close(mean([1, 2, 4]), 7 / 3))
33
+ # any iterable works
34
+ ae(mean(x for x in [2, 4, 6]), 4)
35
+
36
+ # ── 2. fmean ──────────────────────────────────────────────────────────────────
37
+
38
+ ae(fmean([1, 2, 3, 4]), 2.5)
39
+ ok(close(fmean([3.5, 4.0, 5.25]), 4.25))
40
+ # weighted
41
+ ok(close(fmean([10, 20, 30], [1, 1, 1]), 20))
42
+ ok(close(fmean([1, 2, 3, 4], [4, 3, 2, 1]), 2.0))
43
+
44
+ # ── 3. geometric_mean ─────────────────────────────────────────────────────────
45
+
46
+ ok(close(geometric_mean([4, 1, 1 / 32]), 0.5))
47
+ ok(close(geometric_mean([2, 8]), 4.0))
48
+ ok(close(geometric_mean([10]), 10.0))
49
+
50
+ # ── 4. harmonic_mean ──────────────────────────────────────────────────────────
51
+
52
+ ok(close(harmonic_mean([40, 60]), 48.0))
53
+ ok(close(harmonic_mean([1, 2, 4]), 12 / 7))
54
+ ae(harmonic_mean([0, 5, 10]), 0) # a zero forces the result to zero
55
+ # weighted
56
+ ok(close(harmonic_mean([40, 60], [5, 30]), 56.0))
57
+
58
+ # ── 5. median family ──────────────────────────────────────────────────────────
59
+
60
+ ae(median([1, 3, 5]), 3)
61
+ ae(median([1, 3, 5, 7]), 4)
62
+ ae(median([5, 1, 3]), 3) # unsorted input
63
+ ae(median_low([1, 3, 5, 7]), 3)
64
+ ae(median_low([1, 3, 5]), 3)
65
+ ae(median_high([1, 3, 5, 7]), 5)
66
+ ae(median_high([1, 3, 5]), 3)
67
+ ae(median_grouped([52, 52, 53, 54]), 52.5)
68
+ ok(close(median_grouped([1, 2, 2, 3, 4, 4, 4, 4, 4, 5]), 3.7))
69
+ ok(close(median_grouped([1, 3, 3, 5, 7], 2), 3.5))
70
+
71
+ # ── 6. mode / multimode ───────────────────────────────────────────────────────
72
+
73
+ ae(mode([1, 1, 2, 3, 3, 3, 4]), 3)
74
+ ae(mode([1, 1, 1, 2, 2]), 1)
75
+ ae(mode(['red', 'blue', 'red', 'green']), 'red')
76
+ ae(mode([7]), 7)
77
+ # ties: first mode encountered wins
78
+ ae(mode([4, 4, 5, 5]), 4)
79
+ ade(multimode([1, 1, 2, 2, 3]), [1, 2])
80
+ ade(multimode([1, 2, 3]), [1, 2, 3])
81
+ ade(multimode([]), [])
82
+ ade(multimode(['a', 'a', 'b', 'c', 'c']), ['a', 'c'])
83
+
84
+ # ── 7. variance / stdev ───────────────────────────────────────────────────────
85
+
86
+ ae(variance([1, 2, 3, 4, 5]), 2.5)
87
+ ae(pvariance([1, 2, 3, 4, 5]), 2.0)
88
+ ok(close(stdev([1, 2, 3, 4, 5]), Math.sqrt(2.5)))
89
+ ok(close(pstdev([1, 2, 3, 4, 5]), Math.sqrt(2.0)))
90
+ ok(close(variance([2.75, 1.75, 1.25, 0.25, 0.5, 1.25, 3.5]), 1.3720238095238095))
91
+ ok(close(pvariance([0.0, 0.25, 0.25, 1.25, 1.5, 1.75, 2.75, 3.25]), 1.25))
92
+ # explicit centre argument
93
+ ae(variance([1, 2, 3, 4, 5], 3), 2.5)
94
+ ae(pvariance([1, 2, 3, 4, 5], 3), 2.0)
95
+
96
+ # ── 8. quantiles ──────────────────────────────────────────────────────────────
97
+
98
+ ade(quantiles([1, 2, 3, 4]), [1.25, 2.5, 3.75])
99
+ _q = quantiles([1, 2, 3, 4, 5, 6, 7, 8, 9])
100
+ ae(_q.length, 3)
101
+ _qi = quantiles([1, 2, 3, 4, 5], n=4, method='inclusive')
102
+ ae(_qi.length, 3)
103
+ ade(quantiles([0, 100], n=2, method='inclusive'), [50])
104
+ # deciles
105
+ ae(quantiles([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], n=10).length, 9)
106
+
107
+ # ── 9. covariance / correlation / linear_regression ──────────────────────────
108
+
109
+ _cx = [1, 2, 3, 4, 5, 6, 7, 8, 9]
110
+ _cy = [1, 2, 3, 1, 2, 3, 1, 2, 3]
111
+ ok(close(covariance(_cx, _cy), 0.75))
112
+
113
+ _px = [1, 2, 3, 4, 5, 6, 7, 8, 9]
114
+ _py = [9, 8, 7, 6, 5, 4, 3, 2, 1]
115
+ ok(close(correlation(_px, _px), 1.0))
116
+ ok(close(correlation(_px, _py), -1.0))
117
+ # ranked (Spearman) correlation handles a monotonic non-linear relation
118
+ ok(close(correlation([1, 2, 3, 4, 5], [1, 4, 9, 16, 25], method='ranked'), 1.0))
119
+
120
+ _lr = linear_regression([1, 2, 3, 4, 5], [2, 4, 6, 8, 10])
121
+ ok(close(_lr.slope, 2.0))
122
+ ok(close(_lr.intercept, 0.0))
123
+ _lr2 = linear_regression([0, 1, 2, 3], [1, 3, 5, 7])
124
+ ok(close(_lr2.slope, 2.0))
125
+ ok(close(_lr2.intercept, 1.0))
126
+ _lrp = linear_regression([1, 2, 3], [2, 4, 6], proportional=True)
127
+ ok(close(_lrp.slope, 2.0))
128
+ ae(_lrp.intercept, 0.0)
129
+
130
+ # ── 10. NormalDist — basics ───────────────────────────────────────────────────
131
+
132
+ _nd = NormalDist(100, 15)
133
+ ae(_nd.mean, 100)
134
+ ae(_nd.median, 100)
135
+ ae(_nd.mode, 100)
136
+ ae(_nd.stdev, 15)
137
+ ae(_nd.variance, 225)
138
+ ae(NormalDist().mean, 0)
139
+ ae(NormalDist().stdev, 1)
140
+
141
+ # ── 11. NormalDist — pdf / cdf / inv_cdf ─────────────────────────────────────
142
+
143
+ ok(close(_nd.cdf(100), 0.5, 1e-6))
144
+ ok(close(_nd.cdf(115), 0.8413447460685429, 1e-5))
145
+ ok(close(_nd.cdf(85), 0.15865525393145707, 1e-5))
146
+ ae(_nd.inv_cdf(0.5), 100) # exact: q = 0 ⇒ x = mu
147
+ ok(close(_nd.inv_cdf(0.8413447460685429), 115, 1e-4))
148
+ ok(close(_nd.pdf(100), 1 / Math.sqrt(2 * Math.PI * 225)))
149
+ # cdf and inv_cdf are inverses
150
+ ok(close(_nd.cdf(_nd.inv_cdf(0.25)), 0.25, 1e-5))
151
+
152
+ # ── 12. NormalDist — zscore / quantiles / from_samples ───────────────────────
153
+
154
+ ae(_nd.zscore(115), 1.0)
155
+ ae(_nd.zscore(70), -2.0)
156
+ _ndq = _nd.quantiles(4)
157
+ ae(_ndq.length, 3)
158
+ ok(close(_ndq[1], 100)) # the median cut point
159
+
160
+ _fs = NormalDist.from_samples([1, 2, 3, 4, 5])
161
+ ok(close(_fs.mean, 3.0))
162
+ ok(close(_fs.stdev, Math.sqrt(2.5)))
163
+
164
+ # ── 13. NormalDist — overlap / samples / arithmetic ──────────────────────────
165
+
166
+ ok(close(NormalDist(0, 1).overlap(NormalDist(0, 1)), 1.0, 1e-5))
167
+ _ov = NormalDist(0, 1).overlap(NormalDist(1, 1))
168
+ ok(_ov > 0.5 and _ov < 0.7) # ~0.6171
169
+
170
+ _samp = _nd.samples(50, seed=42)
171
+ ae(_samp.length, 50)
172
+ # deterministic with a fixed seed
173
+ _samp2 = _nd.samples(50, seed=42)
174
+ ade(_samp, _samp2)
175
+
176
+ # arithmetic combines distributions / scalars
177
+ _sum = NormalDist(2, 3).__add__(NormalDist(4, 4))
178
+ ae(_sum.mean, 6)
179
+ ae(_sum.stdev, 5) # hypot(3, 4)
180
+ _shift = NormalDist(10, 2).__add__(5)
181
+ ae(_shift.mean, 15)
182
+ ae(_shift.stdev, 2)
183
+ _scaled = NormalDist(10, 2).__mul__(3)
184
+ ae(_scaled.mean, 30)
185
+ ae(_scaled.stdev, 6)
186
+
187
+ ok(NormalDist(1, 2).__eq__(NormalDist(1, 2)))
188
+ ok(not NormalDist(1, 2).__eq__(NormalDist(1, 3)))
189
+
190
+ # ── 14. error handling ────────────────────────────────────────────────────────
191
+
192
+ def raises(fn):
193
+ try:
194
+ fn()
195
+ except StatisticsError:
196
+ return True
197
+ return False
198
+
199
+ ok(raises(def(): return mean([]);))
200
+ ok(raises(def(): return median([]);))
201
+ ok(raises(def(): return mode([]);))
202
+ ok(raises(def(): return variance([1]);))
203
+ ok(raises(def(): return pvariance([]);))
204
+ ok(raises(def(): return geometric_mean([1, -2, 3]);))
205
+ ok(raises(def(): return harmonic_mean([1, -2]);))
206
+ ok(raises(def(): return covariance([1, 2], [1, 2, 3]);))
207
+ ok(raises(def(): return correlation([1, 1, 1], [2, 3, 4]);))
208
+ ok(raises(def(): return linear_regression([1, 1, 1], [2, 3, 4]);))
209
+
210
+ # inv_cdf rejects out-of-range probabilities
211
+ ok(raises(def(): return NormalDist(0, 1).inv_cdf(0);))
212
+ ok(raises(def(): return NormalDist(0, 1).inv_cdf(1.5);))
213
+ # negative sigma is rejected
214
+ ok(raises(def(): return NormalDist(0, -1);))
215
+
216
+ # StatisticsError is a subclass of ValueError
217
+ _is_value_error = False
218
+ try:
219
+ mean([])
220
+ except ValueError:
221
+ _is_value_error = True
222
+ ok(_is_value_error, 'StatisticsError should be catchable as ValueError')
223
+
224
+ print('statistics: all tests passed')
package/test/str.pyj CHANGED
@@ -221,3 +221,17 @@ ae('{{}}'.format(), '{}')
221
221
  ae('{x}}}'.format(x=1), '1}')
222
222
  a = 1
223
223
  ae(f'{{ {a} }}', '{ 1 }')
224
+
225
+ # str.replace — Python semantics (replace all by default)
226
+ ae('bbb', 'aaa'.replace('a', 'b'))
227
+ ae('hello world', 'hello earth'.replace('earth', 'world'))
228
+ ae('baa', 'aaa'.replace('a', 'b', 1))
229
+ ae('bba', 'aaa'.replace('a', 'b', 2))
230
+ ae('aaa', 'aaa'.replace('a', 'b', 0))
231
+ ae('aaa', 'aaa'.replace('x', 'y'))
232
+ ae('', 'aaa'.replace('a', ''))
233
+ ae('xbxbxb', 'ababab'.replace('a', 'x'))
234
+ ae('a$b$c', 'a-b-c'.replace('-', '$'))
235
+ # negative count replaces all (same as omitting count)
236
+ ae('bbb', 'aaa'.replace('a', 'b', -1))
237
+ ae('bbb', 'aaa'.replace('a', 'b', -99))