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.
- package/CHANGELOG.md +28 -0
- package/PYTHON_GAPS.md +352 -0
- package/README.md +176 -32
- package/TODO.md +1 -128
- package/bin/rapydscript +70 -70
- package/language-service/index.js +242 -11
- package/memory/project_string_impl.md +43 -0
- package/package.json +1 -1
- package/release/baselib-plain-pretty.js +248 -38
- package/release/baselib-plain-ugly.js +8 -8
- package/release/compiler.js +778 -277
- package/release/signatures.json +30 -30
- package/src/ast.pyj +10 -1
- package/src/baselib-builtins.pyj +56 -2
- package/src/baselib-containers.pyj +25 -1
- package/src/baselib-errors.pyj +7 -3
- package/src/baselib-internal.pyj +51 -6
- package/src/baselib-str.pyj +18 -5
- package/src/lib/asyncio.pyj +534 -0
- package/src/lib/base64.pyj +399 -0
- package/src/lib/bisect.pyj +73 -0
- package/src/lib/collections.pyj +228 -4
- package/src/lib/csv.pyj +494 -0
- package/src/lib/heapq.pyj +98 -0
- package/src/lib/html.pyj +382 -0
- package/src/lib/http/__init__.pyj +98 -0
- package/src/lib/http/client.pyj +304 -0
- package/src/lib/http/cookies.pyj +236 -0
- package/src/lib/logging.pyj +672 -0
- package/src/lib/pprint.pyj +455 -0
- package/src/lib/pythonize.pyj +20 -20
- package/src/lib/statistics.pyj +0 -0
- package/src/lib/string.pyj +357 -0
- package/src/lib/textwrap.pyj +329 -0
- package/src/lib/urllib/__init__.pyj +14 -0
- package/src/lib/urllib/error.pyj +66 -0
- package/src/lib/urllib/parse.pyj +475 -0
- package/src/lib/urllib/request.pyj +86 -0
- package/src/monaco-language-service/analyzer.js +5 -2
- package/src/monaco-language-service/completions.js +26 -0
- package/src/monaco-language-service/diagnostics.js +203 -4
- package/src/monaco-language-service/scope.js +1 -0
- package/src/output/codegen.pyj +4 -1
- package/src/output/functions.pyj +152 -6
- package/src/output/loops.pyj +17 -2
- package/src/output/modules.pyj +1 -1
- package/src/output/operators.pyj +15 -0
- package/src/output/stream.pyj +0 -1
- package/src/parse.pyj +108 -24
- package/src/tokenizer.pyj +19 -3
- package/test/async_generators.pyj +144 -0
- package/test/asyncio.pyj +307 -0
- package/test/base64.pyj +202 -0
- package/test/baselib.pyj +23 -0
- package/test/bisect.pyj +178 -0
- package/test/chainmap.pyj +185 -0
- package/test/csv.pyj +405 -0
- package/test/float_special.pyj +64 -0
- package/test/heapq.pyj +174 -0
- package/test/html.pyj +212 -0
- package/test/http.pyj +259 -0
- package/test/imports.pyj +79 -72
- package/test/logging.pyj +356 -0
- package/test/long.pyj +130 -0
- package/test/parenthesized_with.pyj +141 -0
- package/test/pprint.pyj +232 -0
- package/test/python_compat.pyj +3 -5
- package/test/python_modulo.pyj +76 -0
- package/test/python_modulo_off.pyj +21 -0
- package/test/statistics.pyj +224 -0
- package/test/str.pyj +14 -0
- package/test/string.pyj +245 -0
- package/test/textwrap.pyj +172 -0
- package/test/type_display.pyj +48 -0
- package/test/type_enforcement.pyj +164 -0
- package/test/unit/index.js +94 -6
- package/test/unit/language-service-completions.js +121 -0
- package/test/unit/language-service-scope.js +32 -0
- package/test/unit/language-service.js +190 -5
- package/test/unit/run-language-service.js +17 -3
- package/test/unit/web-repl.js +2401 -13
- package/test/urllib.pyj +193 -0
- package/tools/compile.js +1 -1
- package/tools/embedded_compiler.js +7 -7
- package/tools/export.js +4 -2
- package/web-repl/main.js +1 -1
- package/web-repl/rapydscript.js +7 -5
- package/test/omit_function_metadata.pyj +0 -20
package/test/pprint.pyj
ADDED
|
@@ -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')
|
package/test/python_compat.pyj
CHANGED
|
@@ -235,7 +235,7 @@ strings()
|
|
|
235
235
|
ae(' hello '.strip(), 'hello')
|
|
236
236
|
ae('hello world'.upper(), 'HELLO WORLD')
|
|
237
237
|
|
|
238
|
-
# ── 15. strings()
|
|
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
|
-
#
|
|
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'), '
|
|
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))
|