rapydscript-ns 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agignore +1 -0
- package/.gitattributes +4 -0
- package/.github/workflows/ci.yml +38 -0
- package/.github/workflows/web-repl-page-deploy.yml +42 -0
- package/=template.pyj +5 -0
- package/CHANGELOG.md +456 -0
- package/CONTRIBUTORS +13 -0
- package/HACKING.md +103 -0
- package/LICENSE +24 -0
- package/README.md +2512 -0
- package/TODO.md +327 -0
- package/add-toc-to-readme +2 -0
- package/bin/export +75 -0
- package/bin/rapydscript +70 -0
- package/bin/web-repl-export +102 -0
- package/build +3 -0
- package/package.json +46 -0
- package/publish.py +37 -0
- package/release/baselib-plain-pretty.js +4370 -0
- package/release/baselib-plain-ugly.js +3 -0
- package/release/compiler.js +18394 -0
- package/release/signatures.json +31 -0
- package/session.vim +4 -0
- package/setup.cfg +2 -0
- package/src/ast.pyj +1356 -0
- package/src/baselib-builtins.pyj +279 -0
- package/src/baselib-containers.pyj +723 -0
- package/src/baselib-errors.pyj +37 -0
- package/src/baselib-internal.pyj +421 -0
- package/src/baselib-itertools.pyj +97 -0
- package/src/baselib-str.pyj +798 -0
- package/src/compiler.pyj +36 -0
- package/src/errors.pyj +30 -0
- package/src/lib/aes.pyj +646 -0
- package/src/lib/collections.pyj +695 -0
- package/src/lib/elementmaker.pyj +83 -0
- package/src/lib/encodings.pyj +126 -0
- package/src/lib/functools.pyj +148 -0
- package/src/lib/gettext.pyj +569 -0
- package/src/lib/itertools.pyj +580 -0
- package/src/lib/math.pyj +193 -0
- package/src/lib/numpy.pyj +2101 -0
- package/src/lib/operator.pyj +11 -0
- package/src/lib/pythonize.pyj +20 -0
- package/src/lib/random.pyj +118 -0
- package/src/lib/re.pyj +470 -0
- package/src/lib/traceback.pyj +63 -0
- package/src/lib/uuid.pyj +77 -0
- package/src/monaco-language-service/analyzer.js +526 -0
- package/src/monaco-language-service/builtins.js +543 -0
- package/src/monaco-language-service/completions.js +498 -0
- package/src/monaco-language-service/diagnostics.js +643 -0
- package/src/monaco-language-service/dts.js +550 -0
- package/src/monaco-language-service/hover.js +121 -0
- package/src/monaco-language-service/index.js +386 -0
- package/src/monaco-language-service/scope.js +162 -0
- package/src/monaco-language-service/signature.js +144 -0
- package/src/output/__init__.pyj +0 -0
- package/src/output/classes.pyj +296 -0
- package/src/output/codegen.pyj +492 -0
- package/src/output/comments.pyj +45 -0
- package/src/output/exceptions.pyj +105 -0
- package/src/output/functions.pyj +491 -0
- package/src/output/literals.pyj +109 -0
- package/src/output/loops.pyj +444 -0
- package/src/output/modules.pyj +329 -0
- package/src/output/operators.pyj +429 -0
- package/src/output/statements.pyj +463 -0
- package/src/output/stream.pyj +309 -0
- package/src/output/treeshake.pyj +182 -0
- package/src/output/utils.pyj +72 -0
- package/src/parse.pyj +3106 -0
- package/src/string_interpolation.pyj +72 -0
- package/src/tokenizer.pyj +702 -0
- package/src/unicode_aliases.pyj +576 -0
- package/src/utils.pyj +192 -0
- package/test/_import_one.pyj +37 -0
- package/test/_import_two/__init__.pyj +11 -0
- package/test/_import_two/level2/__init__.pyj +0 -0
- package/test/_import_two/level2/deep.pyj +4 -0
- package/test/_import_two/other.pyj +6 -0
- package/test/_import_two/sub.pyj +13 -0
- package/test/aes_vectors.pyj +421 -0
- package/test/annotations.pyj +80 -0
- package/test/baselib.pyj +319 -0
- package/test/classes.pyj +452 -0
- package/test/collections.pyj +152 -0
- package/test/decorators.pyj +77 -0
- package/test/dict_spread.pyj +76 -0
- package/test/docstrings.pyj +39 -0
- package/test/elementmaker_test.pyj +45 -0
- package/test/ellipsis.pyj +49 -0
- package/test/functions.pyj +151 -0
- package/test/generators.pyj +41 -0
- package/test/generic.pyj +370 -0
- package/test/imports.pyj +72 -0
- package/test/internationalization.pyj +73 -0
- package/test/lint.pyj +164 -0
- package/test/loops.pyj +85 -0
- package/test/numpy.pyj +734 -0
- package/test/omit_function_metadata.pyj +20 -0
- package/test/regexp.pyj +55 -0
- package/test/repl.pyj +121 -0
- package/test/scoped_flags.pyj +76 -0
- package/test/starargs.pyj +506 -0
- package/test/starred_assign.pyj +104 -0
- package/test/str.pyj +198 -0
- package/test/subscript_tuple.pyj +53 -0
- package/test/unit/fixtures/fibonacci_expected.js +46 -0
- package/test/unit/index.js +2989 -0
- package/test/unit/language-service-builtins.js +815 -0
- package/test/unit/language-service-completions.js +1067 -0
- package/test/unit/language-service-dts.js +543 -0
- package/test/unit/language-service-hover.js +455 -0
- package/test/unit/language-service-scope.js +833 -0
- package/test/unit/language-service-signature.js +458 -0
- package/test/unit/language-service.js +705 -0
- package/test/unit/run-language-service.js +41 -0
- package/test/unit/web-repl.js +484 -0
- package/tools/build-language-service.js +190 -0
- package/tools/cli.js +547 -0
- package/tools/compile.js +219 -0
- package/tools/compiler.js +108 -0
- package/tools/completer.js +131 -0
- package/tools/embedded_compiler.js +251 -0
- package/tools/export.js +316 -0
- package/tools/gettext.js +185 -0
- package/tools/ini.js +65 -0
- package/tools/lint.js +705 -0
- package/tools/msgfmt.js +187 -0
- package/tools/repl.js +223 -0
- package/tools/self.js +162 -0
- package/tools/test.js +118 -0
- package/tools/utils.js +128 -0
- package/tools/web_repl.js +95 -0
- package/try +41 -0
- package/web-repl/env.js +74 -0
- package/web-repl/index.html +163 -0
- package/web-repl/language-service.js +4084 -0
- package/web-repl/main.js +254 -0
- package/web-repl/prism.css +139 -0
- package/web-repl/prism.js +113 -0
- package/web-repl/rapydscript.js +435 -0
- package/web-repl/sha1.js +25 -0
|
@@ -0,0 +1,2989 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* test/unit/index.js
|
|
3
|
+
*
|
|
4
|
+
* Snapshot-style unit tests: compile RapydScript → JS, verify emitted code
|
|
5
|
+
* patterns, run the JS, and assert expected values via embedded `assrt` calls.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* node test/unit/index.js # run all tests
|
|
9
|
+
* node test/unit/index.js <test-name> # run a single test by name
|
|
10
|
+
*/
|
|
11
|
+
"use strict";
|
|
12
|
+
|
|
13
|
+
var path = require("path");
|
|
14
|
+
var fs = require("fs");
|
|
15
|
+
var vm = require("vm");
|
|
16
|
+
var assert = require("assert");
|
|
17
|
+
var utils = require("../../tools/utils");
|
|
18
|
+
var colored = utils.safe_colored;
|
|
19
|
+
|
|
20
|
+
// ── Compiler setup ───────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
var BASE_PATH = path.resolve(__dirname, "../..");
|
|
23
|
+
var LIB_PATH = path.join(BASE_PATH, "src", "lib");
|
|
24
|
+
|
|
25
|
+
var compiler_dir = path.join(BASE_PATH, "dev");
|
|
26
|
+
if (!utils.path_exists(path.join(compiler_dir, "compiler.js"))) {
|
|
27
|
+
compiler_dir = path.join(BASE_PATH, "release");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
var compiler_module = require("../../tools/compiler");
|
|
31
|
+
var RapydScript = compiler_module.create_compiler();
|
|
32
|
+
var baselib = fs.readFileSync(
|
|
33
|
+
path.join(compiler_dir, "baselib-plain-pretty.js"), "utf-8"
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
// ── assert.deepEqual patch ───────────────────────────────────────────────────
|
|
37
|
+
// RapydScript arrays carry extra properties; compare them as plain arrays,
|
|
38
|
+
// same as tools/test.js does.
|
|
39
|
+
|
|
40
|
+
var _deepEqual = assert.deepEqual;
|
|
41
|
+
assert.deepEqual = function (a, b, message) {
|
|
42
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
43
|
+
if (a === b) return;
|
|
44
|
+
if (a.length !== b.length)
|
|
45
|
+
throw new assert.AssertionError({
|
|
46
|
+
actual: a, expected: b,
|
|
47
|
+
operator: "deepEqual",
|
|
48
|
+
stackStartFunction: assert.deepEqual,
|
|
49
|
+
});
|
|
50
|
+
for (var i = 0; i < a.length; i++) assert.deepEqual(a[i], b[i], message);
|
|
51
|
+
} else if (a !== undefined && a !== null && typeof a.__eq__ === "function") {
|
|
52
|
+
if (!a.__eq__(b))
|
|
53
|
+
throw new assert.AssertionError({
|
|
54
|
+
actual: a, expected: b,
|
|
55
|
+
operator: "deepEqual",
|
|
56
|
+
stackStartFunction: assert.deepEqual,
|
|
57
|
+
});
|
|
58
|
+
} else {
|
|
59
|
+
return _deepEqual(a, b, message);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
function compile(src) {
|
|
66
|
+
var ast = RapydScript.parse(src, {
|
|
67
|
+
filename : "<unit-test>",
|
|
68
|
+
toplevel : null,
|
|
69
|
+
basedir : BASE_PATH,
|
|
70
|
+
libdir : LIB_PATH,
|
|
71
|
+
});
|
|
72
|
+
var output = new RapydScript.OutputStream({
|
|
73
|
+
baselib_plain : baselib,
|
|
74
|
+
beautify : true,
|
|
75
|
+
js_version : 6,
|
|
76
|
+
private_scope : false,
|
|
77
|
+
});
|
|
78
|
+
ast.print(output);
|
|
79
|
+
var js = output.toString();
|
|
80
|
+
return js;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function compile_with_flags(src, flags_obj) {
|
|
84
|
+
var ast = RapydScript.parse(src, {
|
|
85
|
+
filename : "<unit-test>",
|
|
86
|
+
toplevel : null,
|
|
87
|
+
basedir : BASE_PATH,
|
|
88
|
+
libdir : LIB_PATH,
|
|
89
|
+
scoped_flags: flags_obj || {},
|
|
90
|
+
});
|
|
91
|
+
var output = new RapydScript.OutputStream({
|
|
92
|
+
baselib_plain : baselib,
|
|
93
|
+
beautify : true,
|
|
94
|
+
js_version : 6,
|
|
95
|
+
private_scope : false,
|
|
96
|
+
});
|
|
97
|
+
ast.print(output);
|
|
98
|
+
return output.toString();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function compile_virtual(src, virtual_files) {
|
|
102
|
+
compiler_module.set_virtual_files(virtual_files);
|
|
103
|
+
try {
|
|
104
|
+
var ast = RapydScript.parse(src, {
|
|
105
|
+
filename : "<unit-test>",
|
|
106
|
+
toplevel : null,
|
|
107
|
+
basedir : BASE_PATH,
|
|
108
|
+
libdir : LIB_PATH,
|
|
109
|
+
import_dirs : ['__virtual__'],
|
|
110
|
+
});
|
|
111
|
+
var output = new RapydScript.OutputStream({
|
|
112
|
+
baselib_plain : baselib,
|
|
113
|
+
beautify : true,
|
|
114
|
+
js_version : 6,
|
|
115
|
+
private_scope : false,
|
|
116
|
+
});
|
|
117
|
+
ast.print(output);
|
|
118
|
+
return output.toString();
|
|
119
|
+
} finally {
|
|
120
|
+
compiler_module.clear_virtual_files();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function run_js(js) {
|
|
125
|
+
return vm.runInNewContext(js, {
|
|
126
|
+
__name__ : "<unit-test>",
|
|
127
|
+
console : console,
|
|
128
|
+
assrt : assert,
|
|
129
|
+
// ρσ_last_exception must be in the global scope for try/except to work
|
|
130
|
+
ρσ_last_exception : undefined,
|
|
131
|
+
}, {filename: "<unit-test>"});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Verify every pattern in `checks` appears in `js`.
|
|
135
|
+
// Each entry may be a plain string (substring match) or a RegExp.
|
|
136
|
+
function check_js_patterns(test_name, js, checks) {
|
|
137
|
+
(checks || []).forEach(function (pat) {
|
|
138
|
+
var ok = (pat instanceof RegExp) ? pat.test(js) : js.indexOf(pat) !== -1;
|
|
139
|
+
if (!ok) {
|
|
140
|
+
var desc = (pat instanceof RegExp) ? String(pat) : JSON.stringify(pat);
|
|
141
|
+
throw new Error(
|
|
142
|
+
"compiled JS missing expected pattern " + desc +
|
|
143
|
+
"\n in test: " + test_name
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ── Test definitions ─────────────────────────────────────────────────────────
|
|
150
|
+
//
|
|
151
|
+
// Each test object:
|
|
152
|
+
// name {string} – unique identifier (used for filtering)
|
|
153
|
+
// description {string} – what this test exercises
|
|
154
|
+
// src {string} – RapydScript source; embed assertions with `assrt`
|
|
155
|
+
// js_checks {Array} – (optional) strings/RegExps to find in compiled JS
|
|
156
|
+
//
|
|
157
|
+
// The RapydScript source may use `assrt.equal`, `assrt.deepEqual`, `assrt.ok`,
|
|
158
|
+
// and `assrt.throws`. A failing assertion or thrown exception fails the test.
|
|
159
|
+
|
|
160
|
+
var TESTS = [
|
|
161
|
+
|
|
162
|
+
// ── Operators ─────────────────────────────────────────────────────────
|
|
163
|
+
|
|
164
|
+
{
|
|
165
|
+
name: "floor_division",
|
|
166
|
+
description: "// operator compiles to Math.floor(x / y)",
|
|
167
|
+
src: [
|
|
168
|
+
"# globals: assrt",
|
|
169
|
+
"assrt.equal(7 // 2, 3)",
|
|
170
|
+
"assrt.equal(-7 // 2, -4)",
|
|
171
|
+
"assrt.equal(10 // 3, 3)",
|
|
172
|
+
].join("\n"),
|
|
173
|
+
js_checks: ["Math.floor(7 / 2)", "Math.floor(-7 / 2)"],
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
{
|
|
177
|
+
name: "exponentiation",
|
|
178
|
+
description: "** operator compiles to Math.pow(x, y)",
|
|
179
|
+
src: [
|
|
180
|
+
"# globals: assrt",
|
|
181
|
+
"assrt.equal(2 ** 10, 1024)",
|
|
182
|
+
"assrt.equal(3 ** 3, 27)",
|
|
183
|
+
"assrt.equal(10 ** 0, 1)",
|
|
184
|
+
].join("\n"),
|
|
185
|
+
js_checks: ["Math.pow(2, 10)", "Math.pow(3, 3)", "Math.pow(10, 0)"],
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
{
|
|
189
|
+
name: "not_operator",
|
|
190
|
+
description: '"not" compiles to "!"',
|
|
191
|
+
src: [
|
|
192
|
+
"# globals: assrt",
|
|
193
|
+
"assrt.equal(not True, False)",
|
|
194
|
+
"assrt.equal(not False, True)",
|
|
195
|
+
"assrt.equal(not (1 is 2), True)",
|
|
196
|
+
].join("\n"),
|
|
197
|
+
js_checks: ["!true", "!false"],
|
|
198
|
+
},
|
|
199
|
+
|
|
200
|
+
{
|
|
201
|
+
name: "is_and_is_not",
|
|
202
|
+
description: '"is" compiles to "==="; "is not" compiles to "!=="',
|
|
203
|
+
src: [
|
|
204
|
+
"# globals: assrt",
|
|
205
|
+
"assrt.equal(1 is 1, True)",
|
|
206
|
+
"assrt.equal(1 is not 2, True)",
|
|
207
|
+
'assrt.equal("a" is not "b", True)',
|
|
208
|
+
"assrt.equal(None is None, True)",
|
|
209
|
+
].join("\n"),
|
|
210
|
+
js_checks: ["1 === 1", "1 !== 2", '"a" !== "b"', "null === null"],
|
|
211
|
+
},
|
|
212
|
+
|
|
213
|
+
{
|
|
214
|
+
name: "and_or_operators",
|
|
215
|
+
description: '"and" / "or" compile to "&&" / "||"',
|
|
216
|
+
src: [
|
|
217
|
+
"# globals: assrt",
|
|
218
|
+
"assrt.equal(True and False, False)",
|
|
219
|
+
"assrt.equal(False or True, True)",
|
|
220
|
+
"assrt.equal(True and True, True)",
|
|
221
|
+
"assrt.equal(False or False, False)",
|
|
222
|
+
].join("\n"),
|
|
223
|
+
js_checks: ["true && false", "false || true"],
|
|
224
|
+
},
|
|
225
|
+
|
|
226
|
+
{
|
|
227
|
+
name: "ternary_expression",
|
|
228
|
+
description: '"x if cond else y" compiles to "(cond) ? x : y"',
|
|
229
|
+
src: [
|
|
230
|
+
"# globals: assrt",
|
|
231
|
+
"assrt.equal(1 if True else 2, 1)",
|
|
232
|
+
'assrt.equal("yes" if 5 > 3 else "no", "yes")',
|
|
233
|
+
"assrt.equal(0 if False else 99, 99)",
|
|
234
|
+
].join("\n"),
|
|
235
|
+
js_checks: ["? 1 : 2", '? "yes" : "no"'],
|
|
236
|
+
},
|
|
237
|
+
|
|
238
|
+
{
|
|
239
|
+
name: "chained_comparison",
|
|
240
|
+
description: "Chained comparisons (a < b < c) lower to && in JS",
|
|
241
|
+
src: [
|
|
242
|
+
"# globals: assrt",
|
|
243
|
+
"x = 5",
|
|
244
|
+
"assrt.equal(1 < x < 10, True)",
|
|
245
|
+
"assrt.equal(0 < x < 4, False)",
|
|
246
|
+
"assrt.equal(5 <= x <= 5, True)",
|
|
247
|
+
].join("\n"),
|
|
248
|
+
// Chained comparisons always produce at least one &&
|
|
249
|
+
js_checks: ["&&"],
|
|
250
|
+
},
|
|
251
|
+
|
|
252
|
+
// ── Strings ───────────────────────────────────────────────────────────
|
|
253
|
+
|
|
254
|
+
{
|
|
255
|
+
name: "f_string",
|
|
256
|
+
description: "f-strings compile to concatenation via ρσ_str.format",
|
|
257
|
+
src: [
|
|
258
|
+
'# globals: assrt',
|
|
259
|
+
'name = "World"',
|
|
260
|
+
'assrt.equal(f"Hello {name}", "Hello World")',
|
|
261
|
+
'n = 42',
|
|
262
|
+
'assrt.equal(f"n={n}", "n=42")',
|
|
263
|
+
'assrt.equal(f"{1 + 1} is two", "2 is two")',
|
|
264
|
+
].join("\n"),
|
|
265
|
+
js_checks: ["ρσ_str.format"],
|
|
266
|
+
},
|
|
267
|
+
|
|
268
|
+
{
|
|
269
|
+
name: "string_methods",
|
|
270
|
+
description: "str.upper/lower/strip/split work via the str global; native JS methods also available",
|
|
271
|
+
src: [
|
|
272
|
+
'# globals: assrt',
|
|
273
|
+
// str.* form (ρσ_str methods)
|
|
274
|
+
'assrt.equal(str.upper("hello"), "HELLO")',
|
|
275
|
+
'assrt.equal(str.lower("WORLD"), "world")',
|
|
276
|
+
'assrt.equal(str.strip(" hi "), "hi")',
|
|
277
|
+
// native JS String methods still work
|
|
278
|
+
'parts = "a,b,c".split(",")',
|
|
279
|
+
'assrt.equal(parts[0], "a")',
|
|
280
|
+
'assrt.equal(parts.length, 3)',
|
|
281
|
+
'assrt.equal("hello world".indexOf("world"), 6)',
|
|
282
|
+
].join("\n"),
|
|
283
|
+
js_checks: [],
|
|
284
|
+
},
|
|
285
|
+
|
|
286
|
+
// ── Collections ───────────────────────────────────────────────────────
|
|
287
|
+
|
|
288
|
+
{
|
|
289
|
+
name: "list_comprehension",
|
|
290
|
+
description: "List comprehensions with filter compile to a ρσ_Result push loop",
|
|
291
|
+
src: [
|
|
292
|
+
"# globals: assrt",
|
|
293
|
+
"evens = [x for x in range(10) if x % 2 is 0]",
|
|
294
|
+
"assrt.deepEqual(evens, [0, 2, 4, 6, 8])",
|
|
295
|
+
"squares = [x**2 for x in [1, 2, 3]]",
|
|
296
|
+
"assrt.deepEqual(squares, [1, 4, 9])",
|
|
297
|
+
].join("\n"),
|
|
298
|
+
js_checks: ["ρσ_Result.push"],
|
|
299
|
+
},
|
|
300
|
+
|
|
301
|
+
{
|
|
302
|
+
name: "dict_comprehension",
|
|
303
|
+
description: "Dict comprehensions build objects via a ρσ_Result accumulator",
|
|
304
|
+
src: [
|
|
305
|
+
"# globals: assrt",
|
|
306
|
+
"doubled = {k: k*2 for k in [1, 2, 3]}",
|
|
307
|
+
"assrt.equal(doubled[1], 2)",
|
|
308
|
+
"assrt.equal(doubled[2], 4)",
|
|
309
|
+
"assrt.equal(doubled[3], 6)",
|
|
310
|
+
].join("\n"),
|
|
311
|
+
js_checks: ["ρσ_Result", "ρσ_Index"],
|
|
312
|
+
},
|
|
313
|
+
|
|
314
|
+
// ── Control flow ──────────────────────────────────────────────────────
|
|
315
|
+
|
|
316
|
+
{
|
|
317
|
+
name: "range_for_loop",
|
|
318
|
+
description: "range() for-loops compile to C-style indexed for loops",
|
|
319
|
+
src: [
|
|
320
|
+
"# globals: assrt",
|
|
321
|
+
"total = 0",
|
|
322
|
+
"for i in range(5):",
|
|
323
|
+
" total += i",
|
|
324
|
+
"assrt.equal(total, 10)",
|
|
325
|
+
"items = []",
|
|
326
|
+
"for j in range(2, 8, 2):",
|
|
327
|
+
" items.push(j)",
|
|
328
|
+
"assrt.deepEqual(items, [2, 4, 6])",
|
|
329
|
+
].join("\n"),
|
|
330
|
+
js_checks: [
|
|
331
|
+
// range(5) → for (var ρσ_IndexN = 0; ρσ_IndexN < 5; ρσ_IndexN++)
|
|
332
|
+
/for \(var ρσ_Index\d+ = 0; ρσ_Index\d+ < 5; ρσ_Index\d+\+\+\)/,
|
|
333
|
+
],
|
|
334
|
+
},
|
|
335
|
+
|
|
336
|
+
{
|
|
337
|
+
name: "while_loop",
|
|
338
|
+
description: "while loops compile to JS while statements",
|
|
339
|
+
src: [
|
|
340
|
+
"# globals: assrt",
|
|
341
|
+
"n = 1",
|
|
342
|
+
"while n < 32:",
|
|
343
|
+
" n *= 2",
|
|
344
|
+
"assrt.equal(n, 32)",
|
|
345
|
+
].join("\n"),
|
|
346
|
+
js_checks: ["while (n < 32)"],
|
|
347
|
+
},
|
|
348
|
+
|
|
349
|
+
{
|
|
350
|
+
name: "try_except",
|
|
351
|
+
description: "try/except compiles to try/catch with an instanceof type guard",
|
|
352
|
+
src: [
|
|
353
|
+
"# globals: assrt, ρσ_last_exception",
|
|
354
|
+
"caught = False",
|
|
355
|
+
"msg = ''",
|
|
356
|
+
"try:",
|
|
357
|
+
' raise ValueError("oops")',
|
|
358
|
+
"except ValueError as e:",
|
|
359
|
+
" caught = True",
|
|
360
|
+
" msg = str(e)",
|
|
361
|
+
"assrt.equal(caught, True)",
|
|
362
|
+
'assrt.ok(msg.indexOf("oops") >= 0)',
|
|
363
|
+
].join("\n"),
|
|
364
|
+
js_checks: ["catch (ρσ_Exception)", "instanceof ValueError"],
|
|
365
|
+
},
|
|
366
|
+
|
|
367
|
+
// ── Functions ─────────────────────────────────────────────────────────
|
|
368
|
+
|
|
369
|
+
{
|
|
370
|
+
name: "default_arguments",
|
|
371
|
+
description: "Default argument values are applied when args are omitted",
|
|
372
|
+
src: [
|
|
373
|
+
"# globals: assrt",
|
|
374
|
+
'def greet(name, greeting="Hello"):',
|
|
375
|
+
' return greeting + ", " + name',
|
|
376
|
+
'assrt.equal(greet("World"), "Hello, World")',
|
|
377
|
+
'assrt.equal(greet("Alice", "Hi"), "Hi, Alice")',
|
|
378
|
+
].join("\n"),
|
|
379
|
+
js_checks: [],
|
|
380
|
+
},
|
|
381
|
+
|
|
382
|
+
{
|
|
383
|
+
name: "variadic_star_args",
|
|
384
|
+
description: "*args functions are tagged __handles_kwarg_interpolation__",
|
|
385
|
+
src: [
|
|
386
|
+
"# globals: assrt",
|
|
387
|
+
"def add(*args):",
|
|
388
|
+
" total = 0",
|
|
389
|
+
" for n in args:",
|
|
390
|
+
" total += n",
|
|
391
|
+
" return total",
|
|
392
|
+
"assrt.equal(add(1, 2, 3), 6)",
|
|
393
|
+
"assrt.equal(add(10, 20), 30)",
|
|
394
|
+
"assrt.equal(add(), 0)",
|
|
395
|
+
].join("\n"),
|
|
396
|
+
js_checks: ["__handles_kwarg_interpolation__"],
|
|
397
|
+
},
|
|
398
|
+
|
|
399
|
+
{
|
|
400
|
+
name: "anonymous_function",
|
|
401
|
+
description: "Anonymous def expressions compile to JS function expressions",
|
|
402
|
+
src: [
|
|
403
|
+
"# globals: assrt",
|
|
404
|
+
"double = def(x): return x * 2;",
|
|
405
|
+
"assrt.equal(double(3), 6)",
|
|
406
|
+
"assrt.equal(double(7), 14)",
|
|
407
|
+
"apply_fn = def(f, val): return f(val);",
|
|
408
|
+
"assrt.equal(apply_fn(double, 5), 10)",
|
|
409
|
+
].join("\n"),
|
|
410
|
+
js_checks: [/function\s*\(x\)\s*\{/],
|
|
411
|
+
},
|
|
412
|
+
|
|
413
|
+
{
|
|
414
|
+
name: "nonlocal_closure",
|
|
415
|
+
description: '"nonlocal" lets an inner function mutate an outer variable',
|
|
416
|
+
src: [
|
|
417
|
+
"# globals: assrt",
|
|
418
|
+
"counter = 0",
|
|
419
|
+
"def increment():",
|
|
420
|
+
" nonlocal counter",
|
|
421
|
+
" counter += 1",
|
|
422
|
+
"increment()",
|
|
423
|
+
"increment()",
|
|
424
|
+
"increment()",
|
|
425
|
+
"assrt.equal(counter, 3)",
|
|
426
|
+
].join("\n"),
|
|
427
|
+
// nonlocal → the outer variable is accessed/modified directly
|
|
428
|
+
js_checks: ["counter += 1"],
|
|
429
|
+
},
|
|
430
|
+
|
|
431
|
+
// ── Classes ───────────────────────────────────────────────────────────
|
|
432
|
+
|
|
433
|
+
{
|
|
434
|
+
name: "class_with_methods",
|
|
435
|
+
description: "Classes compile to prototype objects; isinstance works",
|
|
436
|
+
src: [
|
|
437
|
+
"# globals: assrt",
|
|
438
|
+
"class Greeter:",
|
|
439
|
+
" def __init__(self, name):",
|
|
440
|
+
" self.name = name",
|
|
441
|
+
" def greet(self):",
|
|
442
|
+
' return "Hello, " + self.name',
|
|
443
|
+
'g = Greeter("Alice")',
|
|
444
|
+
'assrt.equal(g.greet(), "Hello, Alice")',
|
|
445
|
+
"assrt.ok(isinstance(g, Greeter))",
|
|
446
|
+
].join("\n"),
|
|
447
|
+
js_checks: ["Greeter.prototype", "__init__"],
|
|
448
|
+
},
|
|
449
|
+
|
|
450
|
+
{
|
|
451
|
+
name: "class_inheritance",
|
|
452
|
+
description: "Subclasses use __extends__; isinstance sees the whole chain",
|
|
453
|
+
src: [
|
|
454
|
+
"# globals: assrt",
|
|
455
|
+
"class Animal:",
|
|
456
|
+
" def __init__(self, name):",
|
|
457
|
+
" self.name = name",
|
|
458
|
+
" def speak(self):",
|
|
459
|
+
' return self.name + " speaks"',
|
|
460
|
+
"class Dog(Animal):",
|
|
461
|
+
" def speak(self):",
|
|
462
|
+
' return self.name + " barks"',
|
|
463
|
+
'a = Animal("Cat")',
|
|
464
|
+
'd = Dog("Rex")',
|
|
465
|
+
'assrt.equal(a.speak(), "Cat speaks")',
|
|
466
|
+
'assrt.equal(d.speak(), "Rex barks")',
|
|
467
|
+
"assrt.ok(isinstance(d, Dog))",
|
|
468
|
+
"assrt.ok(isinstance(d, Animal))",
|
|
469
|
+
"assrt.ok(not isinstance(a, Dog))",
|
|
470
|
+
].join("\n"),
|
|
471
|
+
js_checks: ["ρσ_extends"],
|
|
472
|
+
},
|
|
473
|
+
|
|
474
|
+
{
|
|
475
|
+
name: "class_dunder_properties",
|
|
476
|
+
description: "Classes get __name__, __qualname__, __module__; instances get __class__",
|
|
477
|
+
src: [
|
|
478
|
+
"# globals: assrt",
|
|
479
|
+
"class Animal:",
|
|
480
|
+
" def __init__(self, name):",
|
|
481
|
+
" self.name = name",
|
|
482
|
+
"assrt.equal(Animal.__name__, 'Animal')",
|
|
483
|
+
"assrt.equal(Animal.__qualname__, 'Animal')",
|
|
484
|
+
"assrt.equal(jstype(Animal.__module__), 'string')",
|
|
485
|
+
"a = Animal('Rex')",
|
|
486
|
+
"assrt.ok(a.__class__ is Animal)",
|
|
487
|
+
"assrt.equal(type(a).__name__, 'Animal')",
|
|
488
|
+
"class Dog(Animal):",
|
|
489
|
+
" pass",
|
|
490
|
+
"d = Dog('Buddy')",
|
|
491
|
+
"assrt.equal(Dog.__name__, 'Dog')",
|
|
492
|
+
"assrt.ok(d.__class__ is Dog)",
|
|
493
|
+
"assrt.equal(type(d).__name__, 'Dog')",
|
|
494
|
+
].join("\n"),
|
|
495
|
+
js_checks: [
|
|
496
|
+
"Animal.__name__ = \"Animal\"",
|
|
497
|
+
"Animal.__qualname__ = \"Animal\"",
|
|
498
|
+
"Animal.__module__ =",
|
|
499
|
+
'Object.defineProperty(Animal.prototype, "__class__"',
|
|
500
|
+
],
|
|
501
|
+
},
|
|
502
|
+
|
|
503
|
+
{
|
|
504
|
+
name: "classmethod",
|
|
505
|
+
description: "@classmethod decorator: cls is the class; factory pattern; instance delegation",
|
|
506
|
+
src: [
|
|
507
|
+
"# globals: assrt",
|
|
508
|
+
"class Shape:",
|
|
509
|
+
" def __init__(self, kind, size):",
|
|
510
|
+
" self.kind = kind",
|
|
511
|
+
" self.size = size",
|
|
512
|
+
" @classmethod",
|
|
513
|
+
" def circle(cls, size):",
|
|
514
|
+
" return cls('circle', size)",
|
|
515
|
+
" @classmethod",
|
|
516
|
+
" def square(cls, size):",
|
|
517
|
+
" return cls('square', size)",
|
|
518
|
+
"s1 = Shape.circle(10)",
|
|
519
|
+
"assrt.equal(s1.kind, 'circle')",
|
|
520
|
+
"assrt.equal(s1.size, 10)",
|
|
521
|
+
"assrt.ok(isinstance(s1, Shape))",
|
|
522
|
+
"s2 = Shape.square(5)",
|
|
523
|
+
"assrt.equal(s2.kind, 'square')",
|
|
524
|
+
"assrt.equal(s2.size, 5)",
|
|
525
|
+
"# Call classmethod on an instance (delegates to class)",
|
|
526
|
+
"s3 = s1.circle(7)",
|
|
527
|
+
"assrt.equal(s3.kind, 'circle')",
|
|
528
|
+
"assrt.equal(s3.size, 7)",
|
|
529
|
+
].join("\n"),
|
|
530
|
+
js_checks: [
|
|
531
|
+
// classmethod defined on the class itself
|
|
532
|
+
"Shape.circle = function",
|
|
533
|
+
// prototype delegation exists
|
|
534
|
+
"Shape.prototype.circle",
|
|
535
|
+
// cls is `this` in body
|
|
536
|
+
"var cls = this",
|
|
537
|
+
],
|
|
538
|
+
},
|
|
539
|
+
|
|
540
|
+
{
|
|
541
|
+
name: "classmethod_classvar_access",
|
|
542
|
+
description: "@classmethod: cls.classvar accesses class variable without .prototype; subclass inherits classmethod",
|
|
543
|
+
src: [
|
|
544
|
+
"# globals: assrt",
|
|
545
|
+
"class Counter:",
|
|
546
|
+
" count = 0",
|
|
547
|
+
" @classmethod",
|
|
548
|
+
" def increment(cls):",
|
|
549
|
+
" cls.count += 1",
|
|
550
|
+
" @classmethod",
|
|
551
|
+
" def get_count(cls):",
|
|
552
|
+
" return cls.count",
|
|
553
|
+
" @classmethod",
|
|
554
|
+
" def reset(cls):",
|
|
555
|
+
" cls.count = 0",
|
|
556
|
+
"Counter.increment()",
|
|
557
|
+
"Counter.increment()",
|
|
558
|
+
"Counter.increment()",
|
|
559
|
+
"assrt.equal(Counter.get_count(), 3)",
|
|
560
|
+
"Counter.reset()",
|
|
561
|
+
"assrt.equal(Counter.get_count(), 0)",
|
|
562
|
+
"# Subclass inherits classmethod; cls is the subclass",
|
|
563
|
+
"class SpecialCounter(Counter):",
|
|
564
|
+
" count = 0",
|
|
565
|
+
"SpecialCounter.increment()",
|
|
566
|
+
"SpecialCounter.increment()",
|
|
567
|
+
"assrt.equal(SpecialCounter.get_count(), 2)",
|
|
568
|
+
"assrt.equal(Counter.get_count(), 0)",
|
|
569
|
+
].join("\n"),
|
|
570
|
+
js_checks: [
|
|
571
|
+
// cls.count should compile to cls.prototype.count
|
|
572
|
+
"cls.prototype.count",
|
|
573
|
+
],
|
|
574
|
+
},
|
|
575
|
+
|
|
576
|
+
{
|
|
577
|
+
name: "classmethod_subclass_factory",
|
|
578
|
+
description: "@classmethod factory on subclass: cls is subclass, type(instance).__name__ is correct",
|
|
579
|
+
src: [
|
|
580
|
+
"# globals: assrt",
|
|
581
|
+
"class Animal:",
|
|
582
|
+
" count = 0",
|
|
583
|
+
" def __init__(self, name):",
|
|
584
|
+
" Animal.count += 1",
|
|
585
|
+
" self.name = name",
|
|
586
|
+
" @classmethod",
|
|
587
|
+
" def get_count(cls):",
|
|
588
|
+
" return cls.count",
|
|
589
|
+
" @classmethod",
|
|
590
|
+
" def create(cls, name):",
|
|
591
|
+
" return cls(name)",
|
|
592
|
+
"dog = Animal.create('Rex')",
|
|
593
|
+
"cat = Animal.create('Whiskers')",
|
|
594
|
+
"assrt.equal(Animal.get_count(), 2)",
|
|
595
|
+
"assrt.equal(dog.get_count(), 2)",
|
|
596
|
+
"class Dog(Animal):",
|
|
597
|
+
" def speak(self):",
|
|
598
|
+
" return 'Woof'",
|
|
599
|
+
"puppy = Dog.create('Buddy')",
|
|
600
|
+
"assrt.equal(type(puppy).__name__, 'Dog')",
|
|
601
|
+
"assrt.ok(isinstance(puppy, Dog))",
|
|
602
|
+
"assrt.ok(isinstance(puppy, Animal))",
|
|
603
|
+
"assrt.equal(Animal.get_count(), 3)",
|
|
604
|
+
].join("\n"),
|
|
605
|
+
},
|
|
606
|
+
|
|
607
|
+
// ── Verbatim JS ───────────────────────────────────────────────────────
|
|
608
|
+
|
|
609
|
+
{
|
|
610
|
+
name: "verbatim_js",
|
|
611
|
+
description: 'v"..." expressions are emitted as-is in the JS output',
|
|
612
|
+
src: [
|
|
613
|
+
"# globals: assrt",
|
|
614
|
+
'result = v"typeof undefined"',
|
|
615
|
+
'assrt.equal(result, "undefined")',
|
|
616
|
+
"arr = [1, 2, 3]",
|
|
617
|
+
'len = v"arr.length"',
|
|
618
|
+
"assrt.equal(len, 3)",
|
|
619
|
+
'assrt.equal(v"Math.max(4, 7)", 7)',
|
|
620
|
+
].join("\n"),
|
|
621
|
+
js_checks: ["typeof undefined", "arr.length", "Math.max(4, 7)"],
|
|
622
|
+
},
|
|
623
|
+
|
|
624
|
+
// ── Example scripts ─────────────────────────────────────────────
|
|
625
|
+
|
|
626
|
+
{
|
|
627
|
+
name: "fibonacci",
|
|
628
|
+
description: "Fibonacci function with recursion and memoization",
|
|
629
|
+
src: `# globals: assrt
|
|
630
|
+
def memoize(f):
|
|
631
|
+
memo = {}
|
|
632
|
+
return def(x):
|
|
633
|
+
if x not in memo: memo[x] = f(x)
|
|
634
|
+
return memo[x]
|
|
635
|
+
|
|
636
|
+
@memoize
|
|
637
|
+
def fib(n):
|
|
638
|
+
if n == 0: return 0
|
|
639
|
+
elif n == 1: return 1
|
|
640
|
+
else: return fib(n-1) + fib(n-2)
|
|
641
|
+
|
|
642
|
+
assrt.equal(fib(0), 0)
|
|
643
|
+
assrt.equal(fib(1), 1)
|
|
644
|
+
assrt.equal(fib(10), 55)
|
|
645
|
+
assrt.equal(fib(15), 610)
|
|
646
|
+
`,
|
|
647
|
+
// Full exact expected JS for the user-code section, stored in a fixture
|
|
648
|
+
// file to preserve trailing whitespace on the blank line between the two
|
|
649
|
+
// function definitions (line with 8 spaces that a template literal would drop).
|
|
650
|
+
js_checks: [fs.readFileSync(path.join(__dirname, "fixtures", "fibonacci_expected.js"), "utf-8").replace(/\r\n/g, "\n")],
|
|
651
|
+
},
|
|
652
|
+
|
|
653
|
+
// ── Virtual file system ───────────────────────────────────────────────
|
|
654
|
+
|
|
655
|
+
{
|
|
656
|
+
name: "virtual_fs_import",
|
|
657
|
+
description: "from mymodule import square works when mymodule is a virtual file",
|
|
658
|
+
src: [
|
|
659
|
+
"# globals: assrt",
|
|
660
|
+
"from mymodule import square",
|
|
661
|
+
"assrt.equal(square(4), 16)",
|
|
662
|
+
"assrt.equal(square(7), 49)",
|
|
663
|
+
].join("\n"),
|
|
664
|
+
virtual_files: {
|
|
665
|
+
mymodule: [
|
|
666
|
+
"def square(n):",
|
|
667
|
+
" return n * n",
|
|
668
|
+
].join("\n"),
|
|
669
|
+
},
|
|
670
|
+
js_checks: ["function square(n)"],
|
|
671
|
+
},
|
|
672
|
+
|
|
673
|
+
{
|
|
674
|
+
name: "virtual_fs_multi_import",
|
|
675
|
+
description: "imports from two different virtual modules both work",
|
|
676
|
+
src: [
|
|
677
|
+
"# globals: assrt",
|
|
678
|
+
"from shapes import Circle",
|
|
679
|
+
"from mathutils import double",
|
|
680
|
+
"c = Circle(5)",
|
|
681
|
+
"assrt.equal(c.area(), 78)",
|
|
682
|
+
"assrt.equal(double(6), 12)",
|
|
683
|
+
].join("\n"),
|
|
684
|
+
virtual_files: {
|
|
685
|
+
shapes: [
|
|
686
|
+
"class Circle:",
|
|
687
|
+
" def __init__(self, r):",
|
|
688
|
+
" self.r = r",
|
|
689
|
+
" def area(self):",
|
|
690
|
+
" return int(3.14159 * self.r * self.r)",
|
|
691
|
+
].join("\n"),
|
|
692
|
+
mathutils: [
|
|
693
|
+
"def double(x):",
|
|
694
|
+
" return x * 2",
|
|
695
|
+
].join("\n"),
|
|
696
|
+
},
|
|
697
|
+
js_checks: ["Circle.prototype", "function double(x)"],
|
|
698
|
+
},
|
|
699
|
+
|
|
700
|
+
// ── Walrus operator (:=) ──────────────────────────────────────────────
|
|
701
|
+
|
|
702
|
+
{
|
|
703
|
+
name: "walrus_basic",
|
|
704
|
+
description: "name := expr compiles to (name = expr) and returns the value",
|
|
705
|
+
src: [
|
|
706
|
+
"# globals: assrt",
|
|
707
|
+
"x = (y := 42)",
|
|
708
|
+
"assrt.equal(y, 42)",
|
|
709
|
+
"assrt.equal(x, 42)",
|
|
710
|
+
].join("\n"),
|
|
711
|
+
js_checks: ["(y = 42)"],
|
|
712
|
+
},
|
|
713
|
+
|
|
714
|
+
{
|
|
715
|
+
name: "walrus_in_if",
|
|
716
|
+
description: "walrus in if condition assigns and tests the value",
|
|
717
|
+
src: [
|
|
718
|
+
"# globals: assrt",
|
|
719
|
+
"data = [1, 2, 3]",
|
|
720
|
+
"if (n := len(data)) > 0:",
|
|
721
|
+
" assrt.equal(n, 3)",
|
|
722
|
+
"else:",
|
|
723
|
+
" assrt.fail('should not reach')",
|
|
724
|
+
].join("\n"),
|
|
725
|
+
js_checks: ["(n ="],
|
|
726
|
+
},
|
|
727
|
+
|
|
728
|
+
{
|
|
729
|
+
name: "walrus_in_while",
|
|
730
|
+
description: "walrus in while loop condition accumulates values",
|
|
731
|
+
src: [
|
|
732
|
+
"# globals: assrt",
|
|
733
|
+
"items = [10, 20, 30]",
|
|
734
|
+
"idx = 0",
|
|
735
|
+
"total = 0",
|
|
736
|
+
"while (idx := idx + 1) <= 3:",
|
|
737
|
+
" total += items[idx - 1]",
|
|
738
|
+
"assrt.equal(total, 60)",
|
|
739
|
+
].join("\n"),
|
|
740
|
+
js_checks: ["(idx ="],
|
|
741
|
+
},
|
|
742
|
+
|
|
743
|
+
{
|
|
744
|
+
name: "walrus_in_list_comprehension",
|
|
745
|
+
description: "walrus in list comprehension assigns in the enclosing scope",
|
|
746
|
+
src: [
|
|
747
|
+
"# globals: assrt",
|
|
748
|
+
"def get_last_even(nums):",
|
|
749
|
+
" evens = [y for x in nums if (y := x) % 2 == 0]",
|
|
750
|
+
" return evens",
|
|
751
|
+
"result = get_last_even([1, 2, 3, 4, 5, 6])",
|
|
752
|
+
"assrt.equal(result.length, 3)",
|
|
753
|
+
"assrt.equal(result[0], 2)",
|
|
754
|
+
"assrt.equal(result[2], 6)",
|
|
755
|
+
].join("\n"),
|
|
756
|
+
js_checks: ["(y ="],
|
|
757
|
+
},
|
|
758
|
+
|
|
759
|
+
{
|
|
760
|
+
name: "walrus_value_returned",
|
|
761
|
+
description: "walrus expression evaluates to the assigned value",
|
|
762
|
+
src: [
|
|
763
|
+
"# globals: assrt",
|
|
764
|
+
"def double(x):",
|
|
765
|
+
" return x * 2",
|
|
766
|
+
"result = (z := double(7))",
|
|
767
|
+
"assrt.equal(z, 14)",
|
|
768
|
+
"assrt.equal(result, 14)",
|
|
769
|
+
].join("\n"),
|
|
770
|
+
js_checks: ["(z ="],
|
|
771
|
+
},
|
|
772
|
+
|
|
773
|
+
// ── match/case (structural pattern matching) ──────────────────────────
|
|
774
|
+
|
|
775
|
+
{
|
|
776
|
+
name: "match_literal_patterns",
|
|
777
|
+
description: "match/case with literal patterns compiles to do-while with === checks",
|
|
778
|
+
src: [
|
|
779
|
+
"# globals: assrt",
|
|
780
|
+
"def describe(x):",
|
|
781
|
+
" result = 'unknown'",
|
|
782
|
+
" match x:",
|
|
783
|
+
" case 1:",
|
|
784
|
+
" result = 'one'",
|
|
785
|
+
" case 2:",
|
|
786
|
+
" result = 'two'",
|
|
787
|
+
" case 'hello':",
|
|
788
|
+
" result = 'greeting'",
|
|
789
|
+
" case True:",
|
|
790
|
+
" result = 'true-val'",
|
|
791
|
+
" case None:",
|
|
792
|
+
" result = 'none-val'",
|
|
793
|
+
" return result",
|
|
794
|
+
"assrt.equal(describe(1), 'one')",
|
|
795
|
+
"assrt.equal(describe(2), 'two')",
|
|
796
|
+
"assrt.equal(describe('hello'), 'greeting')",
|
|
797
|
+
"assrt.equal(describe(True), 'true-val')",
|
|
798
|
+
"assrt.equal(describe(None), 'none-val')",
|
|
799
|
+
"assrt.equal(describe(99), 'unknown')",
|
|
800
|
+
].join("\n"),
|
|
801
|
+
js_checks: ["=== 1", "=== 2", "=== \"hello\"", "=== true", "=== null", "do {"],
|
|
802
|
+
},
|
|
803
|
+
|
|
804
|
+
{
|
|
805
|
+
name: "match_wildcard",
|
|
806
|
+
description: "case _: always matches and provides a default",
|
|
807
|
+
src: [
|
|
808
|
+
"# globals: assrt",
|
|
809
|
+
"def categorize(x):",
|
|
810
|
+
" match x:",
|
|
811
|
+
" case 0:",
|
|
812
|
+
" return 'zero'",
|
|
813
|
+
" case _:",
|
|
814
|
+
" return 'nonzero'",
|
|
815
|
+
"assrt.equal(categorize(0), 'zero')",
|
|
816
|
+
"assrt.equal(categorize(5), 'nonzero')",
|
|
817
|
+
"assrt.equal(categorize(-1), 'nonzero')",
|
|
818
|
+
].join("\n"),
|
|
819
|
+
js_checks: ["=== 0", "true"],
|
|
820
|
+
},
|
|
821
|
+
|
|
822
|
+
{
|
|
823
|
+
name: "match_capture",
|
|
824
|
+
description: "case x: captures and binds the matched value",
|
|
825
|
+
src: [
|
|
826
|
+
"# globals: assrt",
|
|
827
|
+
"def double_it(val):",
|
|
828
|
+
" match val:",
|
|
829
|
+
" case 0:",
|
|
830
|
+
" return 0",
|
|
831
|
+
" case n:",
|
|
832
|
+
" return n * 2",
|
|
833
|
+
"assrt.equal(double_it(0), 0)",
|
|
834
|
+
"assrt.equal(double_it(5), 10)",
|
|
835
|
+
"assrt.equal(double_it(-3), -6)",
|
|
836
|
+
].join("\n"),
|
|
837
|
+
js_checks: [],
|
|
838
|
+
},
|
|
839
|
+
|
|
840
|
+
{
|
|
841
|
+
name: "match_or_patterns",
|
|
842
|
+
description: "case a | b: matches if any pattern matches",
|
|
843
|
+
src: [
|
|
844
|
+
"# globals: assrt",
|
|
845
|
+
"def weekend(day):",
|
|
846
|
+
" match day:",
|
|
847
|
+
" case 'Saturday' | 'Sunday':",
|
|
848
|
+
" return True",
|
|
849
|
+
" case _:",
|
|
850
|
+
" return False",
|
|
851
|
+
"assrt.equal(weekend('Saturday'), True)",
|
|
852
|
+
"assrt.equal(weekend('Sunday'), True)",
|
|
853
|
+
"assrt.equal(weekend('Monday'), False)",
|
|
854
|
+
"assrt.equal(weekend('Friday'), False)",
|
|
855
|
+
].join("\n"),
|
|
856
|
+
js_checks: ["||"],
|
|
857
|
+
},
|
|
858
|
+
|
|
859
|
+
{
|
|
860
|
+
name: "match_guard",
|
|
861
|
+
description: "case pattern if guard: only matches when guard is True",
|
|
862
|
+
src: [
|
|
863
|
+
"# globals: assrt",
|
|
864
|
+
"def sign(x):",
|
|
865
|
+
" match x:",
|
|
866
|
+
" case n if n > 0:",
|
|
867
|
+
" return 'positive'",
|
|
868
|
+
" case n if n < 0:",
|
|
869
|
+
" return 'negative'",
|
|
870
|
+
" case _:",
|
|
871
|
+
" return 'zero'",
|
|
872
|
+
"assrt.equal(sign(5), 'positive')",
|
|
873
|
+
"assrt.equal(sign(-3), 'negative')",
|
|
874
|
+
"assrt.equal(sign(0), 'zero')",
|
|
875
|
+
].join("\n"),
|
|
876
|
+
js_checks: ["if ("],
|
|
877
|
+
},
|
|
878
|
+
|
|
879
|
+
{
|
|
880
|
+
name: "match_sequence",
|
|
881
|
+
description: "case [a, b]: matches a 2-element array and binds elements",
|
|
882
|
+
src: [
|
|
883
|
+
"# globals: assrt",
|
|
884
|
+
"def first_two(lst):",
|
|
885
|
+
" match lst:",
|
|
886
|
+
" case []:",
|
|
887
|
+
" return 'empty'",
|
|
888
|
+
" case [x]:",
|
|
889
|
+
" return 'one:' + str(x)",
|
|
890
|
+
" case [x, y]:",
|
|
891
|
+
" return 'two:' + str(x) + ',' + str(y)",
|
|
892
|
+
" case _:",
|
|
893
|
+
" return 'many'",
|
|
894
|
+
"assrt.equal(first_two([]), 'empty')",
|
|
895
|
+
"assrt.equal(first_two([1]), 'one:1')",
|
|
896
|
+
"assrt.equal(first_two([1, 2]), 'two:1,2')",
|
|
897
|
+
"assrt.equal(first_two([1, 2, 3]), 'many')",
|
|
898
|
+
].join("\n"),
|
|
899
|
+
js_checks: ["Array.isArray"],
|
|
900
|
+
},
|
|
901
|
+
|
|
902
|
+
{
|
|
903
|
+
name: "match_star_sequence",
|
|
904
|
+
description: "case [head, *tail]: captures the rest of a sequence",
|
|
905
|
+
src: [
|
|
906
|
+
"# globals: assrt",
|
|
907
|
+
"def parse_list(lst):",
|
|
908
|
+
" match lst:",
|
|
909
|
+
" case []:",
|
|
910
|
+
" return 'empty'",
|
|
911
|
+
" case [first, *rest]:",
|
|
912
|
+
" return str(first) + '+' + str(rest.length)",
|
|
913
|
+
"assrt.equal(parse_list([]), 'empty')",
|
|
914
|
+
"assrt.equal(parse_list([1]), '1+0')",
|
|
915
|
+
"assrt.equal(parse_list([1, 2, 3]), '1+2')",
|
|
916
|
+
].join("\n"),
|
|
917
|
+
js_checks: ["Array.isArray", ".slice("],
|
|
918
|
+
},
|
|
919
|
+
|
|
920
|
+
{
|
|
921
|
+
name: "match_mapping",
|
|
922
|
+
description: "case {'key': value}: matches dict-like objects",
|
|
923
|
+
src: [
|
|
924
|
+
"# globals: assrt",
|
|
925
|
+
"def get_name(obj):",
|
|
926
|
+
" match obj:",
|
|
927
|
+
" case {'name': n, 'age': a}:",
|
|
928
|
+
" return n + ' is ' + str(a)",
|
|
929
|
+
" case {'name': n}:",
|
|
930
|
+
" return n",
|
|
931
|
+
" case _:",
|
|
932
|
+
" return 'unknown'",
|
|
933
|
+
"assrt.equal(get_name({'name': 'Alice', 'age': 30}), 'Alice is 30')",
|
|
934
|
+
"assrt.equal(get_name({'name': 'Bob'}), 'Bob')",
|
|
935
|
+
"assrt.equal(get_name({}), 'unknown')",
|
|
936
|
+
].join("\n"),
|
|
937
|
+
js_checks: ["typeof", "in "],
|
|
938
|
+
},
|
|
939
|
+
|
|
940
|
+
{
|
|
941
|
+
name: "match_class_pattern",
|
|
942
|
+
description: "case ClassName(kw=pat): matches class instances by keyword attributes",
|
|
943
|
+
src: [
|
|
944
|
+
"# globals: assrt",
|
|
945
|
+
"class Point:",
|
|
946
|
+
" def __init__(self, x, y):",
|
|
947
|
+
" self.x = x",
|
|
948
|
+
" self.y = y",
|
|
949
|
+
"def describe_point(p):",
|
|
950
|
+
" match p:",
|
|
951
|
+
" case Point(x=0, y=0):",
|
|
952
|
+
" return 'origin'",
|
|
953
|
+
" case Point(x=0, y=py):",
|
|
954
|
+
" return 'y-axis:' + str(py)",
|
|
955
|
+
" case Point(x=px, y=0):",
|
|
956
|
+
" return 'x-axis:' + str(px)",
|
|
957
|
+
" case Point(x=px, y=py):",
|
|
958
|
+
" return str(px) + ',' + str(py)",
|
|
959
|
+
"assrt.equal(describe_point(Point(0, 0)), 'origin')",
|
|
960
|
+
"assrt.equal(describe_point(Point(0, 5)), 'y-axis:5')",
|
|
961
|
+
"assrt.equal(describe_point(Point(3, 0)), 'x-axis:3')",
|
|
962
|
+
"assrt.equal(describe_point(Point(3, 4)), '3,4')",
|
|
963
|
+
].join("\n"),
|
|
964
|
+
js_checks: ["instanceof Point"],
|
|
965
|
+
},
|
|
966
|
+
|
|
967
|
+
{
|
|
968
|
+
name: "match_as_pattern",
|
|
969
|
+
description: "case pattern as name: binds the whole match to name",
|
|
970
|
+
src: [
|
|
971
|
+
"# globals: assrt",
|
|
972
|
+
"def inspect(val):",
|
|
973
|
+
" match val:",
|
|
974
|
+
" case [x, y] as pair:",
|
|
975
|
+
" return str(pair.length) + ':' + str(x)",
|
|
976
|
+
" case v as other:",
|
|
977
|
+
" return 'other:' + str(other)",
|
|
978
|
+
"assrt.equal(inspect([1, 2]), '2:1')",
|
|
979
|
+
"assrt.equal(inspect(42), 'other:42')",
|
|
980
|
+
].join("\n"),
|
|
981
|
+
js_checks: [],
|
|
982
|
+
},
|
|
983
|
+
|
|
984
|
+
{
|
|
985
|
+
name: "match_negative_literal",
|
|
986
|
+
description: "Negative number literals work in patterns",
|
|
987
|
+
src: [
|
|
988
|
+
"# globals: assrt",
|
|
989
|
+
"def sign(x):",
|
|
990
|
+
" match x:",
|
|
991
|
+
" case -1:",
|
|
992
|
+
" return 'neg one'",
|
|
993
|
+
" case 0:",
|
|
994
|
+
" return 'zero'",
|
|
995
|
+
" case 1:",
|
|
996
|
+
" return 'one'",
|
|
997
|
+
" case _:",
|
|
998
|
+
" return 'other'",
|
|
999
|
+
"assrt.equal(sign(-1), 'neg one')",
|
|
1000
|
+
"assrt.equal(sign(0), 'zero')",
|
|
1001
|
+
"assrt.equal(sign(1), 'one')",
|
|
1002
|
+
"assrt.equal(sign(2), 'other')",
|
|
1003
|
+
].join("\n"),
|
|
1004
|
+
js_checks: ["=== -1"],
|
|
1005
|
+
},
|
|
1006
|
+
|
|
1007
|
+
{
|
|
1008
|
+
name: "match_guard_fallthrough",
|
|
1009
|
+
description: "When a guard fails, the next case is tried",
|
|
1010
|
+
src: [
|
|
1011
|
+
"# globals: assrt",
|
|
1012
|
+
"def categorize(x):",
|
|
1013
|
+
" match x:",
|
|
1014
|
+
" case n if n > 100:",
|
|
1015
|
+
" return 'big'",
|
|
1016
|
+
" case n if n > 10:",
|
|
1017
|
+
" return 'medium'",
|
|
1018
|
+
" case n if n > 0:",
|
|
1019
|
+
" return 'small'",
|
|
1020
|
+
" case _:",
|
|
1021
|
+
" return 'nonpositive'",
|
|
1022
|
+
"assrt.equal(categorize(200), 'big')",
|
|
1023
|
+
"assrt.equal(categorize(50), 'medium')",
|
|
1024
|
+
"assrt.equal(categorize(5), 'small')",
|
|
1025
|
+
"assrt.equal(categorize(0), 'nonpositive')",
|
|
1026
|
+
"assrt.equal(categorize(-1), 'nonpositive')",
|
|
1027
|
+
].join("\n"),
|
|
1028
|
+
js_checks: [],
|
|
1029
|
+
},
|
|
1030
|
+
|
|
1031
|
+
{
|
|
1032
|
+
name: "match_nested_sequence",
|
|
1033
|
+
description: "Nested sequence patterns match nested arrays",
|
|
1034
|
+
src: [
|
|
1035
|
+
"# globals: assrt",
|
|
1036
|
+
"def matrix_type(m):",
|
|
1037
|
+
" match m:",
|
|
1038
|
+
" case [[a, b], [c, d]]:",
|
|
1039
|
+
" return '2x2:' + str(a+d)",
|
|
1040
|
+
" case _:",
|
|
1041
|
+
" return 'other'",
|
|
1042
|
+
"assrt.equal(matrix_type([[1, 2], [3, 4]]), '2x2:5')",
|
|
1043
|
+
"assrt.equal(matrix_type([[1, 2, 3]]), 'other')",
|
|
1044
|
+
].join("\n"),
|
|
1045
|
+
js_checks: ["Array.isArray"],
|
|
1046
|
+
},
|
|
1047
|
+
|
|
1048
|
+
{
|
|
1049
|
+
name: "match_is_soft_keyword",
|
|
1050
|
+
description: "'match' can still be used as a variable name",
|
|
1051
|
+
src: [
|
|
1052
|
+
"# globals: assrt",
|
|
1053
|
+
"match = 42",
|
|
1054
|
+
"assrt.equal(match, 42)",
|
|
1055
|
+
"match += 1",
|
|
1056
|
+
"assrt.equal(match, 43)",
|
|
1057
|
+
].join("\n"),
|
|
1058
|
+
js_checks: [],
|
|
1059
|
+
},
|
|
1060
|
+
|
|
1061
|
+
// ── Lambda ────────────────────────────────────────────────────────────
|
|
1062
|
+
|
|
1063
|
+
{
|
|
1064
|
+
name: "lambda_basic",
|
|
1065
|
+
description: "lambda compiles to an anonymous JS function expression",
|
|
1066
|
+
src: [
|
|
1067
|
+
"# globals: assrt",
|
|
1068
|
+
"double = lambda x: x * 2",
|
|
1069
|
+
"assrt.equal(double(5), 10)",
|
|
1070
|
+
"add = lambda a, b: a + b",
|
|
1071
|
+
"assrt.equal(add(3, 4), 7)",
|
|
1072
|
+
].join("\n"),
|
|
1073
|
+
js_checks: ["function"],
|
|
1074
|
+
},
|
|
1075
|
+
|
|
1076
|
+
{
|
|
1077
|
+
name: "lambda_no_args",
|
|
1078
|
+
description: "lambda with no arguments works",
|
|
1079
|
+
src: [
|
|
1080
|
+
"# globals: assrt",
|
|
1081
|
+
"forty_two = lambda: 42",
|
|
1082
|
+
"assrt.equal(forty_two(), 42)",
|
|
1083
|
+
].join("\n"),
|
|
1084
|
+
js_checks: [],
|
|
1085
|
+
},
|
|
1086
|
+
|
|
1087
|
+
{
|
|
1088
|
+
name: "lambda_inline",
|
|
1089
|
+
description: "lambda used inline in a function call",
|
|
1090
|
+
src: [
|
|
1091
|
+
"# globals: assrt",
|
|
1092
|
+
"def apply(fn, x):",
|
|
1093
|
+
" return fn(x)",
|
|
1094
|
+
"assrt.equal(apply(lambda x: x * x, 5), 25)",
|
|
1095
|
+
"nums = [3, 1, 2]",
|
|
1096
|
+
"nums.sort(lambda a, b: a - b)",
|
|
1097
|
+
"assrt.deepEqual(nums, [1, 2, 3])",
|
|
1098
|
+
].join("\n"),
|
|
1099
|
+
js_checks: [],
|
|
1100
|
+
},
|
|
1101
|
+
|
|
1102
|
+
{
|
|
1103
|
+
name: "lambda_ternary_body",
|
|
1104
|
+
description: "lambda body can be a ternary (if/else) expression",
|
|
1105
|
+
src: [
|
|
1106
|
+
"# globals: assrt",
|
|
1107
|
+
"abs_val = lambda x: x if x >= 0 else -x",
|
|
1108
|
+
"assrt.equal(abs_val(5), 5)",
|
|
1109
|
+
"assrt.equal(abs_val(-3), 3)",
|
|
1110
|
+
"clamp = lambda x, lo, hi: lo if x < lo else (hi if x > hi else x)",
|
|
1111
|
+
"assrt.equal(clamp(5, 0, 10), 5)",
|
|
1112
|
+
"assrt.equal(clamp(-1, 0, 10), 0)",
|
|
1113
|
+
"assrt.equal(clamp(15, 0, 10), 10)",
|
|
1114
|
+
].join("\n"),
|
|
1115
|
+
js_checks: [],
|
|
1116
|
+
},
|
|
1117
|
+
|
|
1118
|
+
{
|
|
1119
|
+
name: "lambda_default_args",
|
|
1120
|
+
description: "lambda supports default argument values",
|
|
1121
|
+
src: [
|
|
1122
|
+
"# globals: assrt",
|
|
1123
|
+
"greet = lambda name='world': 'hello ' + name",
|
|
1124
|
+
"assrt.equal(greet(), 'hello world')",
|
|
1125
|
+
"assrt.equal(greet('alice'), 'hello alice')",
|
|
1126
|
+
].join("\n"),
|
|
1127
|
+
js_checks: [],
|
|
1128
|
+
},
|
|
1129
|
+
|
|
1130
|
+
{
|
|
1131
|
+
name: "lambda_closure",
|
|
1132
|
+
description: "lambda captures variables from enclosing scope",
|
|
1133
|
+
src: [
|
|
1134
|
+
"# globals: assrt",
|
|
1135
|
+
"def make_adder(n):",
|
|
1136
|
+
" return lambda x: x + n",
|
|
1137
|
+
"add5 = make_adder(5)",
|
|
1138
|
+
"assrt.equal(add5(3), 8)",
|
|
1139
|
+
"assrt.equal(add5(10), 15)",
|
|
1140
|
+
].join("\n"),
|
|
1141
|
+
js_checks: [],
|
|
1142
|
+
},
|
|
1143
|
+
|
|
1144
|
+
{
|
|
1145
|
+
name: "lambda_in_list",
|
|
1146
|
+
description: "lambda can be stored in a list and called",
|
|
1147
|
+
src: [
|
|
1148
|
+
"# globals: assrt",
|
|
1149
|
+
"ops = [lambda x: x + 1, lambda x: x * 2, lambda x: x - 3]",
|
|
1150
|
+
"assrt.equal(ops[0](5), 6)",
|
|
1151
|
+
"assrt.equal(ops[1](5), 10)",
|
|
1152
|
+
"assrt.equal(ops[2](5), 2)",
|
|
1153
|
+
].join("\n"),
|
|
1154
|
+
js_checks: [],
|
|
1155
|
+
},
|
|
1156
|
+
|
|
1157
|
+
{
|
|
1158
|
+
name: "lambda_nested",
|
|
1159
|
+
description: "nested lambdas work (lambda returning lambda)",
|
|
1160
|
+
src: [
|
|
1161
|
+
"# globals: assrt",
|
|
1162
|
+
"mult = lambda x: lambda y: x * y",
|
|
1163
|
+
"triple = mult(3)",
|
|
1164
|
+
"assrt.equal(triple(4), 12)",
|
|
1165
|
+
"assrt.equal(mult(5)(6), 30)",
|
|
1166
|
+
].join("\n"),
|
|
1167
|
+
js_checks: [],
|
|
1168
|
+
},
|
|
1169
|
+
|
|
1170
|
+
{
|
|
1171
|
+
name: "lambda_star_args",
|
|
1172
|
+
description: "lambda supports *args",
|
|
1173
|
+
src: [
|
|
1174
|
+
"# globals: assrt",
|
|
1175
|
+
"total = lambda *args: sum(args)",
|
|
1176
|
+
"assrt.equal(total(1, 2, 3), 6)",
|
|
1177
|
+
"assrt.equal(total(10, 20), 30)",
|
|
1178
|
+
].join("\n"),
|
|
1179
|
+
js_checks: [],
|
|
1180
|
+
},
|
|
1181
|
+
|
|
1182
|
+
// ── Variable type annotations ──────────────────────────────────────────
|
|
1183
|
+
|
|
1184
|
+
{
|
|
1185
|
+
name: "var_annotation_with_value",
|
|
1186
|
+
description: "variable type annotations with assignment compile and run correctly",
|
|
1187
|
+
src: [
|
|
1188
|
+
"# globals: assrt",
|
|
1189
|
+
"x: int = 42",
|
|
1190
|
+
"assrt.equal(x, 42)",
|
|
1191
|
+
"y: str = 'hello'",
|
|
1192
|
+
"assrt.equal(y, 'hello')",
|
|
1193
|
+
"z: float = 3.14",
|
|
1194
|
+
"assrt.equal(z, 3.14)",
|
|
1195
|
+
].join("\n"),
|
|
1196
|
+
js_checks: ["x = 42", "y = \"hello\"", "z = 3.14"],
|
|
1197
|
+
},
|
|
1198
|
+
|
|
1199
|
+
{
|
|
1200
|
+
name: "var_annotation_only",
|
|
1201
|
+
description: "annotation-only (no value) compiles and runs without error",
|
|
1202
|
+
src: [
|
|
1203
|
+
"# globals: assrt",
|
|
1204
|
+
"x: int",
|
|
1205
|
+
"assrt.ok(True)",
|
|
1206
|
+
].join("\n"),
|
|
1207
|
+
// annotation-only produces no assignment
|
|
1208
|
+
js_checks: [],
|
|
1209
|
+
},
|
|
1210
|
+
|
|
1211
|
+
{
|
|
1212
|
+
name: "var_annotation_complex_type",
|
|
1213
|
+
description: "variable annotations with complex type expressions work correctly",
|
|
1214
|
+
src: [
|
|
1215
|
+
"# globals: assrt",
|
|
1216
|
+
"xs: list = [1, 2, 3]",
|
|
1217
|
+
"assrt.deepEqual(xs, [1, 2, 3])",
|
|
1218
|
+
"d: dict = {'a': 1}",
|
|
1219
|
+
"assrt.equal(d['a'], 1)",
|
|
1220
|
+
"n: int = 2 + 3",
|
|
1221
|
+
"assrt.equal(n, 5)",
|
|
1222
|
+
].join("\n"),
|
|
1223
|
+
js_checks: [],
|
|
1224
|
+
},
|
|
1225
|
+
|
|
1226
|
+
{
|
|
1227
|
+
name: "var_annotation_in_function",
|
|
1228
|
+
description: "variable annotations inside functions work correctly",
|
|
1229
|
+
src: [
|
|
1230
|
+
"# globals: assrt",
|
|
1231
|
+
"def f():",
|
|
1232
|
+
" x: int = 10",
|
|
1233
|
+
" y: str = 'hi'",
|
|
1234
|
+
" return x",
|
|
1235
|
+
"assrt.equal(f(), 10)",
|
|
1236
|
+
].join("\n"),
|
|
1237
|
+
js_checks: ["x = 10"],
|
|
1238
|
+
},
|
|
1239
|
+
|
|
1240
|
+
{
|
|
1241
|
+
name: "var_annotation_in_class",
|
|
1242
|
+
description: "variable annotations as class attributes work correctly",
|
|
1243
|
+
src: [
|
|
1244
|
+
"# globals: assrt",
|
|
1245
|
+
"class Counter:",
|
|
1246
|
+
" count: int = 0",
|
|
1247
|
+
" def increment(self):",
|
|
1248
|
+
" self.count += 1",
|
|
1249
|
+
"c = Counter()",
|
|
1250
|
+
"c.increment()",
|
|
1251
|
+
"assrt.equal(c.count, 1)",
|
|
1252
|
+
].join("\n"),
|
|
1253
|
+
js_checks: [],
|
|
1254
|
+
},
|
|
1255
|
+
|
|
1256
|
+
{
|
|
1257
|
+
name: "var_annotation_expr_rhs",
|
|
1258
|
+
description: "annotation with complex RHS expression evaluates correctly",
|
|
1259
|
+
src: [
|
|
1260
|
+
"# globals: assrt",
|
|
1261
|
+
"def compute():",
|
|
1262
|
+
" result: int = 0",
|
|
1263
|
+
" for i in range(5):",
|
|
1264
|
+
" result += i",
|
|
1265
|
+
" return result",
|
|
1266
|
+
"assrt.equal(compute(), 10)",
|
|
1267
|
+
].join("\n"),
|
|
1268
|
+
js_checks: [],
|
|
1269
|
+
},
|
|
1270
|
+
|
|
1271
|
+
{
|
|
1272
|
+
name: "var_annotation_multiple",
|
|
1273
|
+
description: "multiple annotated assignments in sequence all execute",
|
|
1274
|
+
src: [
|
|
1275
|
+
"# globals: assrt",
|
|
1276
|
+
"a: int = 1",
|
|
1277
|
+
"b: int = 2",
|
|
1278
|
+
"c: int = a + b",
|
|
1279
|
+
"assrt.equal(c, 3)",
|
|
1280
|
+
].join("\n"),
|
|
1281
|
+
js_checks: ["a = 1", "b = 2"],
|
|
1282
|
+
},
|
|
1283
|
+
|
|
1284
|
+
// ── super() ───────────────────────────────────────────────────────────
|
|
1285
|
+
|
|
1286
|
+
{
|
|
1287
|
+
name: "super_init",
|
|
1288
|
+
description: "super().__init__() calls parent constructor",
|
|
1289
|
+
src: [
|
|
1290
|
+
"# globals: assrt",
|
|
1291
|
+
"class Animal:",
|
|
1292
|
+
" def __init__(self, name):",
|
|
1293
|
+
" self.name = name",
|
|
1294
|
+
"class Dog(Animal):",
|
|
1295
|
+
" def __init__(self, name, breed):",
|
|
1296
|
+
" super().__init__(name)",
|
|
1297
|
+
" self.breed = breed",
|
|
1298
|
+
"d = Dog('Rex', 'Labrador')",
|
|
1299
|
+
"assrt.equal(d.name, 'Rex')",
|
|
1300
|
+
"assrt.equal(d.breed, 'Labrador')",
|
|
1301
|
+
].join("\n"),
|
|
1302
|
+
js_checks: [/Animal\.prototype\.__init__\.call\(this/],
|
|
1303
|
+
},
|
|
1304
|
+
|
|
1305
|
+
{
|
|
1306
|
+
name: "super_method",
|
|
1307
|
+
description: "super().method() calls parent method",
|
|
1308
|
+
src: [
|
|
1309
|
+
"# globals: assrt",
|
|
1310
|
+
"class Base:",
|
|
1311
|
+
" def greet(self):",
|
|
1312
|
+
" return 'Hello'",
|
|
1313
|
+
"class Child(Base):",
|
|
1314
|
+
" def greet(self):",
|
|
1315
|
+
" return super().greet() + ' World'",
|
|
1316
|
+
"c = Child()",
|
|
1317
|
+
"assrt.equal(c.greet(), 'Hello World')",
|
|
1318
|
+
].join("\n"),
|
|
1319
|
+
js_checks: [/Base\.prototype\.greet\.call\(this/],
|
|
1320
|
+
},
|
|
1321
|
+
|
|
1322
|
+
{
|
|
1323
|
+
name: "super_with_args",
|
|
1324
|
+
description: "super().method() passes arguments to parent",
|
|
1325
|
+
src: [
|
|
1326
|
+
"# globals: assrt",
|
|
1327
|
+
"class Adder:",
|
|
1328
|
+
" def add(self, a, b):",
|
|
1329
|
+
" return a + b",
|
|
1330
|
+
"class LoggingAdder(Adder):",
|
|
1331
|
+
" def add(self, a, b):",
|
|
1332
|
+
" return super().add(a, b)",
|
|
1333
|
+
"la = LoggingAdder()",
|
|
1334
|
+
"assrt.equal(la.add(3, 4), 7)",
|
|
1335
|
+
].join("\n"),
|
|
1336
|
+
js_checks: [/Adder\.prototype\.add\.call\(this/],
|
|
1337
|
+
},
|
|
1338
|
+
|
|
1339
|
+
{
|
|
1340
|
+
name: "super_two_arg_form",
|
|
1341
|
+
description: "super(ClassName, self).method() two-argument form works",
|
|
1342
|
+
src: [
|
|
1343
|
+
"# globals: assrt",
|
|
1344
|
+
"class A:",
|
|
1345
|
+
" def val(self):",
|
|
1346
|
+
" return 'A'",
|
|
1347
|
+
"class B(A):",
|
|
1348
|
+
" def val(self):",
|
|
1349
|
+
" return super(B, self).val() + 'B'",
|
|
1350
|
+
"b = B()",
|
|
1351
|
+
"assrt.equal(b.val(), 'AB')",
|
|
1352
|
+
].join("\n"),
|
|
1353
|
+
js_checks: [/A\.prototype\.val\.call\(this/],
|
|
1354
|
+
},
|
|
1355
|
+
|
|
1356
|
+
{
|
|
1357
|
+
name: "super_multi_level",
|
|
1358
|
+
description: "super() works across multiple levels of inheritance",
|
|
1359
|
+
src: [
|
|
1360
|
+
"# globals: assrt",
|
|
1361
|
+
"class A:",
|
|
1362
|
+
" def name(self):",
|
|
1363
|
+
" return 'A'",
|
|
1364
|
+
"class B(A):",
|
|
1365
|
+
" def name(self):",
|
|
1366
|
+
" return super().name() + 'B'",
|
|
1367
|
+
"class C(B):",
|
|
1368
|
+
" def name(self):",
|
|
1369
|
+
" return super().name() + 'C'",
|
|
1370
|
+
"assrt.equal(C().name(), 'ABC')",
|
|
1371
|
+
].join("\n"),
|
|
1372
|
+
},
|
|
1373
|
+
|
|
1374
|
+
{
|
|
1375
|
+
name: "super_in_nested_method",
|
|
1376
|
+
description: "super() resolves correctly inside nested function in method",
|
|
1377
|
+
src: [
|
|
1378
|
+
"# globals: assrt",
|
|
1379
|
+
"class Base:",
|
|
1380
|
+
" def make_greeting(self, name):",
|
|
1381
|
+
" return 'Hi ' + name",
|
|
1382
|
+
"class Child(Base):",
|
|
1383
|
+
" def make_greeting(self, name):",
|
|
1384
|
+
" result = super().make_greeting(name)",
|
|
1385
|
+
" return result + '!'",
|
|
1386
|
+
"assrt.equal(Child().make_greeting('world'), 'Hi world!')",
|
|
1387
|
+
].join("\n"),
|
|
1388
|
+
},
|
|
1389
|
+
|
|
1390
|
+
// ── any() ─────────────────────────────────────────────────────────────
|
|
1391
|
+
|
|
1392
|
+
{
|
|
1393
|
+
name: "any_true_on_truthy_element",
|
|
1394
|
+
description: "any() returns True when at least one element is truthy",
|
|
1395
|
+
src: [
|
|
1396
|
+
"# globals: assrt",
|
|
1397
|
+
"assrt.equal(any([False, 0, '', 1]), True)",
|
|
1398
|
+
"assrt.equal(any([1, 2, 3]), True)",
|
|
1399
|
+
"assrt.equal(any(['hello']), True)",
|
|
1400
|
+
].join("\n"),
|
|
1401
|
+
},
|
|
1402
|
+
|
|
1403
|
+
{
|
|
1404
|
+
name: "any_false_on_all_falsy",
|
|
1405
|
+
description: "any() returns False when all elements are falsy",
|
|
1406
|
+
src: [
|
|
1407
|
+
"# globals: assrt",
|
|
1408
|
+
"assrt.equal(any([False, 0, '', None]), False)",
|
|
1409
|
+
"assrt.equal(any([0, 0, 0]), False)",
|
|
1410
|
+
].join("\n"),
|
|
1411
|
+
},
|
|
1412
|
+
|
|
1413
|
+
{
|
|
1414
|
+
name: "any_empty_iterable",
|
|
1415
|
+
description: "any() returns False for an empty iterable",
|
|
1416
|
+
src: [
|
|
1417
|
+
"# globals: assrt",
|
|
1418
|
+
"assrt.equal(any([]), False)",
|
|
1419
|
+
"assrt.equal(any(iter([])), False)",
|
|
1420
|
+
].join("\n"),
|
|
1421
|
+
},
|
|
1422
|
+
|
|
1423
|
+
{
|
|
1424
|
+
name: "any_with_iterator",
|
|
1425
|
+
description: "any() works with an iterator (not just arrays)",
|
|
1426
|
+
src: [
|
|
1427
|
+
"# globals: assrt",
|
|
1428
|
+
"assrt.equal(any(iter([0, 0, 3])), True)",
|
|
1429
|
+
"assrt.equal(any(iter([0, 0, 0])), False)",
|
|
1430
|
+
"assrt.equal(any(iter([True])), True)",
|
|
1431
|
+
].join("\n"),
|
|
1432
|
+
},
|
|
1433
|
+
|
|
1434
|
+
{
|
|
1435
|
+
name: "any_with_range",
|
|
1436
|
+
description: "any() works with range()",
|
|
1437
|
+
src: [
|
|
1438
|
+
"# globals: assrt",
|
|
1439
|
+
"assrt.equal(any(range(3)), True)",
|
|
1440
|
+
"assrt.equal(any(range(0)), False)",
|
|
1441
|
+
].join("\n"),
|
|
1442
|
+
},
|
|
1443
|
+
|
|
1444
|
+
{
|
|
1445
|
+
name: "any_compiles_to_function_call",
|
|
1446
|
+
description: "any(x) compiles to a function call to any",
|
|
1447
|
+
src: [
|
|
1448
|
+
"# globals: assrt",
|
|
1449
|
+
"result = any([True, False])",
|
|
1450
|
+
"assrt.equal(result, True)",
|
|
1451
|
+
].join("\n"),
|
|
1452
|
+
js_checks: ["any("],
|
|
1453
|
+
},
|
|
1454
|
+
|
|
1455
|
+
// ── all() ─────────────────────────────────────────────────────────────
|
|
1456
|
+
|
|
1457
|
+
{
|
|
1458
|
+
name: "all_true_on_all_truthy",
|
|
1459
|
+
description: "all() returns True when every element is truthy",
|
|
1460
|
+
src: [
|
|
1461
|
+
"# globals: assrt",
|
|
1462
|
+
"assrt.equal(all([1, 2, 3]), True)",
|
|
1463
|
+
"assrt.equal(all(['a', 'b']), True)",
|
|
1464
|
+
"assrt.equal(all([True, True]), True)",
|
|
1465
|
+
].join("\n"),
|
|
1466
|
+
},
|
|
1467
|
+
|
|
1468
|
+
{
|
|
1469
|
+
name: "all_false_on_any_falsy",
|
|
1470
|
+
description: "all() returns False when any element is falsy",
|
|
1471
|
+
src: [
|
|
1472
|
+
"# globals: assrt",
|
|
1473
|
+
"assrt.equal(all([1, 0, 3]), False)",
|
|
1474
|
+
"assrt.equal(all([True, False, True]), False)",
|
|
1475
|
+
"assrt.equal(all([1, None]), False)",
|
|
1476
|
+
].join("\n"),
|
|
1477
|
+
},
|
|
1478
|
+
|
|
1479
|
+
{
|
|
1480
|
+
name: "all_empty_iterable",
|
|
1481
|
+
description: "all() returns True for an empty iterable",
|
|
1482
|
+
src: [
|
|
1483
|
+
"# globals: assrt",
|
|
1484
|
+
"assrt.equal(all([]), True)",
|
|
1485
|
+
"assrt.equal(all(iter([])), True)",
|
|
1486
|
+
].join("\n"),
|
|
1487
|
+
},
|
|
1488
|
+
|
|
1489
|
+
{
|
|
1490
|
+
name: "all_with_iterator",
|
|
1491
|
+
description: "all() works with an iterator (not just arrays)",
|
|
1492
|
+
src: [
|
|
1493
|
+
"# globals: assrt",
|
|
1494
|
+
"assrt.equal(all(iter([1, 2, 3])), True)",
|
|
1495
|
+
"assrt.equal(all(iter([1, 0, 3])), False)",
|
|
1496
|
+
].join("\n"),
|
|
1497
|
+
},
|
|
1498
|
+
|
|
1499
|
+
{
|
|
1500
|
+
name: "all_with_range",
|
|
1501
|
+
description: "all() works with range()",
|
|
1502
|
+
src: [
|
|
1503
|
+
"# globals: assrt",
|
|
1504
|
+
"assrt.equal(all(range(1, 4)), True)",
|
|
1505
|
+
"assrt.equal(all(range(0, 3)), False)",
|
|
1506
|
+
].join("\n"),
|
|
1507
|
+
},
|
|
1508
|
+
|
|
1509
|
+
{
|
|
1510
|
+
name: "all_compiles_to_function_call",
|
|
1511
|
+
description: "all(x) compiles to a function call to all",
|
|
1512
|
+
src: [
|
|
1513
|
+
"# globals: assrt",
|
|
1514
|
+
"result = all([True, True])",
|
|
1515
|
+
"assrt.equal(result, True)",
|
|
1516
|
+
].join("\n"),
|
|
1517
|
+
js_checks: ["all("],
|
|
1518
|
+
},
|
|
1519
|
+
|
|
1520
|
+
{
|
|
1521
|
+
name: "any_all_combined",
|
|
1522
|
+
description: "any() and all() can be composed and used together",
|
|
1523
|
+
src: [
|
|
1524
|
+
"# globals: assrt",
|
|
1525
|
+
"nums = [2, 4, 6, 8]",
|
|
1526
|
+
"assrt.equal(all([x > 0 for x in nums]), True)",
|
|
1527
|
+
"assrt.equal(any([x > 5 for x in nums]), True)",
|
|
1528
|
+
"assrt.equal(all([x > 5 for x in nums]), False)",
|
|
1529
|
+
"assrt.equal(any([x > 10 for x in nums]), False)",
|
|
1530
|
+
].join("\n"),
|
|
1531
|
+
},
|
|
1532
|
+
|
|
1533
|
+
// ── print() ───────────────────────────────────────────────────────────
|
|
1534
|
+
|
|
1535
|
+
{
|
|
1536
|
+
name: "print_compiles_to_console_log",
|
|
1537
|
+
description: "print(x) compiles directly to console.log(x)",
|
|
1538
|
+
src: [
|
|
1539
|
+
"# globals: assrt",
|
|
1540
|
+
"print('hello')",
|
|
1541
|
+
"print(1, 2, 3)",
|
|
1542
|
+
].join("\n"),
|
|
1543
|
+
js_checks: ["console.log(\"hello\")", "console.log(1, 2, 3)"],
|
|
1544
|
+
},
|
|
1545
|
+
|
|
1546
|
+
{
|
|
1547
|
+
name: "print_outputs_to_console",
|
|
1548
|
+
description: "print() captures output via console.log",
|
|
1549
|
+
src: [
|
|
1550
|
+
"# globals: assrt, captured",
|
|
1551
|
+
"captured = []",
|
|
1552
|
+
"def mock_log(*args):",
|
|
1553
|
+
" captured.push(args.join(' '))",
|
|
1554
|
+
"orig = console.log",
|
|
1555
|
+
"console.log = mock_log",
|
|
1556
|
+
"print('hello')",
|
|
1557
|
+
"print('a', 'b', 'c')",
|
|
1558
|
+
"console.log = orig",
|
|
1559
|
+
"assrt.equal(captured[0], 'hello')",
|
|
1560
|
+
"assrt.equal(captured[1], 'a b c')",
|
|
1561
|
+
].join("\n"),
|
|
1562
|
+
},
|
|
1563
|
+
|
|
1564
|
+
{
|
|
1565
|
+
name: "print_sep_kwarg",
|
|
1566
|
+
description: "print(a, b, sep=x) joins args with sep",
|
|
1567
|
+
src: [
|
|
1568
|
+
"# globals: assrt, captured",
|
|
1569
|
+
"captured = []",
|
|
1570
|
+
"def mock_log(*args):",
|
|
1571
|
+
" captured.push(args.join(' '))",
|
|
1572
|
+
"orig = console.log",
|
|
1573
|
+
"console.log = mock_log",
|
|
1574
|
+
"print('x', 'y', 'z', sep='-')",
|
|
1575
|
+
"console.log = orig",
|
|
1576
|
+
"assrt.equal(captured[0], 'x-y-z')",
|
|
1577
|
+
].join("\n"),
|
|
1578
|
+
js_checks: ["ρσ_print"],
|
|
1579
|
+
},
|
|
1580
|
+
|
|
1581
|
+
{
|
|
1582
|
+
name: "print_does_not_clobber_window_print",
|
|
1583
|
+
description: "print() transpilation does not overwrite window.print",
|
|
1584
|
+
src: [
|
|
1585
|
+
"# globals: assrt",
|
|
1586
|
+
"print('test')",
|
|
1587
|
+
].join("\n"),
|
|
1588
|
+
// The compiled JS must NOT contain 'var print = ' (which would overwrite window.print)
|
|
1589
|
+
js_checks: [/console\.log\("test"\)/],
|
|
1590
|
+
},
|
|
1591
|
+
|
|
1592
|
+
{
|
|
1593
|
+
name: "window_print_preserved",
|
|
1594
|
+
description: "window.print() compiles as window.print() for browser print dialog",
|
|
1595
|
+
src: [
|
|
1596
|
+
"# globals: assrt",
|
|
1597
|
+
"state = {'called': False}",
|
|
1598
|
+
"def on_print():",
|
|
1599
|
+
" state['called'] = True",
|
|
1600
|
+
"window_mock = {'print': on_print}",
|
|
1601
|
+
"window_mock.print()",
|
|
1602
|
+
"assrt.equal(state['called'], True)",
|
|
1603
|
+
].join("\n"),
|
|
1604
|
+
js_checks: ["window_mock.print()"],
|
|
1605
|
+
},
|
|
1606
|
+
|
|
1607
|
+
// ── functools ─────────────────────────────────────────────────────────
|
|
1608
|
+
|
|
1609
|
+
{
|
|
1610
|
+
name: "functools_reduce",
|
|
1611
|
+
description: "functools.reduce applies a function cumulatively over an iterable",
|
|
1612
|
+
src: [
|
|
1613
|
+
"# globals: assrt",
|
|
1614
|
+
"from functools import reduce",
|
|
1615
|
+
"assrt.equal(reduce(lambda a, b: a + b, [1, 2, 3, 4]), 10)",
|
|
1616
|
+
"assrt.equal(reduce(lambda a, b: a * b, [1, 2, 3, 4]), 24)",
|
|
1617
|
+
"assrt.equal(reduce(lambda a, b: a + b, [1, 2, 3], 10), 16)",
|
|
1618
|
+
"assrt.equal(reduce(lambda a, b: a + b, [], 0), 0)",
|
|
1619
|
+
"assrt.equal(reduce(lambda a, b: a + b, [42]), 42)",
|
|
1620
|
+
"assrt.throws(def():",
|
|
1621
|
+
" reduce(lambda a, b: a + b, [])",
|
|
1622
|
+
", /empty/)",
|
|
1623
|
+
].join("\n"),
|
|
1624
|
+
js_checks: [],
|
|
1625
|
+
},
|
|
1626
|
+
|
|
1627
|
+
{
|
|
1628
|
+
name: "functools_partial",
|
|
1629
|
+
description: "functools.partial creates a partial application of a function",
|
|
1630
|
+
src: [
|
|
1631
|
+
"# globals: assrt",
|
|
1632
|
+
"from functools import partial",
|
|
1633
|
+
"def add(a, b): return a + b",
|
|
1634
|
+
"add5 = partial(add, 5)",
|
|
1635
|
+
"assrt.equal(add5(3), 8)",
|
|
1636
|
+
"assrt.equal(add5(10), 15)",
|
|
1637
|
+
"def power(base, exp): return base ** exp",
|
|
1638
|
+
"square = partial(power, exp=2)",
|
|
1639
|
+
"assrt.equal(square(4), 16)",
|
|
1640
|
+
"assrt.equal(square(5), 25)",
|
|
1641
|
+
"assrt.equal(partial(add, 1, 2)(), 3)",
|
|
1642
|
+
"p = partial(add, 5)",
|
|
1643
|
+
"assrt.equal(p.func, add)",
|
|
1644
|
+
"assrt.deepEqual(p.args, [5])",
|
|
1645
|
+
].join("\n"),
|
|
1646
|
+
js_checks: [],
|
|
1647
|
+
},
|
|
1648
|
+
|
|
1649
|
+
{
|
|
1650
|
+
name: "functools_wraps",
|
|
1651
|
+
description: "functools.wraps copies __name__ and __doc__ to wrapper",
|
|
1652
|
+
src: [
|
|
1653
|
+
"# globals: assrt",
|
|
1654
|
+
"from functools import wraps",
|
|
1655
|
+
"def my_decorator(f):",
|
|
1656
|
+
" @wraps(f)",
|
|
1657
|
+
" def wrapper(*args):",
|
|
1658
|
+
" return f(*args)",
|
|
1659
|
+
" return wrapper",
|
|
1660
|
+
"@my_decorator",
|
|
1661
|
+
"def add(a, b):",
|
|
1662
|
+
" return a + b",
|
|
1663
|
+
"assrt.equal(add.__name__, 'add')",
|
|
1664
|
+
"assrt.equal(add.__wrapped__.name, 'add')",
|
|
1665
|
+
"assrt.equal(add(2, 3), 5)",
|
|
1666
|
+
].join("\n"),
|
|
1667
|
+
js_checks: [],
|
|
1668
|
+
},
|
|
1669
|
+
|
|
1670
|
+
{
|
|
1671
|
+
name: "functools_lru_cache",
|
|
1672
|
+
description: "functools.lru_cache memoizes function results",
|
|
1673
|
+
src: [
|
|
1674
|
+
"# globals: assrt",
|
|
1675
|
+
"from functools import lru_cache",
|
|
1676
|
+
"call_count = 0",
|
|
1677
|
+
"@lru_cache(maxsize=4)",
|
|
1678
|
+
"def fib(n):",
|
|
1679
|
+
" nonlocal call_count",
|
|
1680
|
+
" call_count += 1",
|
|
1681
|
+
" if n < 2: return n",
|
|
1682
|
+
" return fib(n - 1) + fib(n - 2)",
|
|
1683
|
+
"assrt.equal(fib(5), 5)",
|
|
1684
|
+
"assrt.equal(fib(6), 8)",
|
|
1685
|
+
"first_count = call_count",
|
|
1686
|
+
"fib(5)",
|
|
1687
|
+
"assrt.equal(call_count, first_count)",
|
|
1688
|
+
"info = fib.cache_info()",
|
|
1689
|
+
"assrt.ok(info['hits'] > 0)",
|
|
1690
|
+
"assrt.ok(info['maxsize'] is 4)",
|
|
1691
|
+
"fib.cache_clear()",
|
|
1692
|
+
"call_count = 0",
|
|
1693
|
+
"fib(3)",
|
|
1694
|
+
"assrt.ok(call_count > 0)",
|
|
1695
|
+
].join("\n"),
|
|
1696
|
+
js_checks: [],
|
|
1697
|
+
},
|
|
1698
|
+
|
|
1699
|
+
{
|
|
1700
|
+
name: "functools_lru_cache_no_parens",
|
|
1701
|
+
description: "@lru_cache without parens uses default maxsize=128",
|
|
1702
|
+
src: [
|
|
1703
|
+
"# globals: assrt",
|
|
1704
|
+
"from functools import lru_cache",
|
|
1705
|
+
"call_count = 0",
|
|
1706
|
+
"@lru_cache",
|
|
1707
|
+
"def double(n):",
|
|
1708
|
+
" nonlocal call_count",
|
|
1709
|
+
" call_count += 1",
|
|
1710
|
+
" return n * 2",
|
|
1711
|
+
"assrt.equal(double(5), 10)",
|
|
1712
|
+
"assrt.equal(double(5), 10)",
|
|
1713
|
+
"assrt.equal(call_count, 1)",
|
|
1714
|
+
"assrt.equal(double.cache_info()['maxsize'], 128)",
|
|
1715
|
+
].join("\n"),
|
|
1716
|
+
js_checks: [],
|
|
1717
|
+
},
|
|
1718
|
+
|
|
1719
|
+
{
|
|
1720
|
+
name: "functools_cache",
|
|
1721
|
+
description: "functools.cache is an unbounded memoizing decorator",
|
|
1722
|
+
src: [
|
|
1723
|
+
"# globals: assrt",
|
|
1724
|
+
"from functools import cache",
|
|
1725
|
+
"call_count = 0",
|
|
1726
|
+
"@cache",
|
|
1727
|
+
"def expensive(n):",
|
|
1728
|
+
" nonlocal call_count",
|
|
1729
|
+
" call_count += 1",
|
|
1730
|
+
" return n * n",
|
|
1731
|
+
"assrt.equal(expensive(3), 9)",
|
|
1732
|
+
"assrt.equal(expensive(3), 9)",
|
|
1733
|
+
"assrt.equal(call_count, 1)",
|
|
1734
|
+
"assrt.ok(expensive.cache_info()['maxsize'] is None)",
|
|
1735
|
+
].join("\n"),
|
|
1736
|
+
js_checks: [],
|
|
1737
|
+
},
|
|
1738
|
+
|
|
1739
|
+
{
|
|
1740
|
+
name: "functools_total_ordering",
|
|
1741
|
+
description: "functools.total_ordering fills in missing comparison methods",
|
|
1742
|
+
src: [
|
|
1743
|
+
"# globals: assrt",
|
|
1744
|
+
"from functools import total_ordering",
|
|
1745
|
+
"@total_ordering",
|
|
1746
|
+
"class Version:",
|
|
1747
|
+
" def __init__(self, n):",
|
|
1748
|
+
" self.n = n",
|
|
1749
|
+
" def __eq__(self, other):",
|
|
1750
|
+
" return self.n == other.n",
|
|
1751
|
+
" def __lt__(self, other):",
|
|
1752
|
+
" return self.n < other.n",
|
|
1753
|
+
"v1 = Version(1)",
|
|
1754
|
+
"v2 = Version(2)",
|
|
1755
|
+
"v3 = Version(1)",
|
|
1756
|
+
"assrt.equal(v1.__lt__(v2), True)",
|
|
1757
|
+
"assrt.equal(v2.__gt__(v1), True)",
|
|
1758
|
+
"assrt.equal(v1.__le__(v3), True)",
|
|
1759
|
+
"assrt.equal(v2.__ge__(v1), True)",
|
|
1760
|
+
"assrt.equal(v1.__eq__(v3), True)",
|
|
1761
|
+
"assrt.equal(v1.__gt__(v2), False)",
|
|
1762
|
+
].join("\n"),
|
|
1763
|
+
js_checks: [],
|
|
1764
|
+
},
|
|
1765
|
+
|
|
1766
|
+
{
|
|
1767
|
+
name: "functools_cmp_to_key",
|
|
1768
|
+
description: "functools.cmp_to_key converts a comparison function to a key class",
|
|
1769
|
+
src: [
|
|
1770
|
+
"# globals: assrt",
|
|
1771
|
+
"from functools import cmp_to_key",
|
|
1772
|
+
"def cmp_len(a, b): return len(a) - len(b)",
|
|
1773
|
+
"Key = cmp_to_key(cmp_len)",
|
|
1774
|
+
"k1 = Key('hi')",
|
|
1775
|
+
"k2 = Key('hello')",
|
|
1776
|
+
"k3 = Key('hi')",
|
|
1777
|
+
"assrt.equal(k1.__lt__(k2), True)",
|
|
1778
|
+
"assrt.equal(k2.__gt__(k1), True)",
|
|
1779
|
+
"assrt.equal(k1.__eq__(k3), True)",
|
|
1780
|
+
"assrt.equal(k1.__le__(k3), True)",
|
|
1781
|
+
"assrt.equal(k2.__ge__(k1), True)",
|
|
1782
|
+
"assrt.equal(k1.__gt__(k2), False)",
|
|
1783
|
+
].join("\n"),
|
|
1784
|
+
js_checks: [],
|
|
1785
|
+
},
|
|
1786
|
+
|
|
1787
|
+
// ── collections ───────────────────────────────────────────────────────
|
|
1788
|
+
|
|
1789
|
+
{
|
|
1790
|
+
name: "collections_namedtuple_basic",
|
|
1791
|
+
description: "namedtuple creates a class with named field access and numeric index access",
|
|
1792
|
+
src: [
|
|
1793
|
+
"# globals: assrt",
|
|
1794
|
+
"from collections import namedtuple",
|
|
1795
|
+
"Point = namedtuple('Point', ['x', 'y'])",
|
|
1796
|
+
"p = Point(3, 4)",
|
|
1797
|
+
"assrt.equal(p.x, 3)",
|
|
1798
|
+
"assrt.equal(p.y, 4)",
|
|
1799
|
+
"assrt.equal(p[0], 3)",
|
|
1800
|
+
"assrt.equal(p[1], 4)",
|
|
1801
|
+
"assrt.equal(p[-1], 4)",
|
|
1802
|
+
"assrt.equal(p.length, 2)",
|
|
1803
|
+
"assrt.deepEqual(list(p), [3, 4])",
|
|
1804
|
+
].join("\n"),
|
|
1805
|
+
js_checks: [],
|
|
1806
|
+
},
|
|
1807
|
+
|
|
1808
|
+
{
|
|
1809
|
+
name: "collections_namedtuple_string_fields",
|
|
1810
|
+
description: "namedtuple accepts a space-separated or comma-separated field string",
|
|
1811
|
+
src: [
|
|
1812
|
+
"# globals: assrt",
|
|
1813
|
+
"from collections import namedtuple",
|
|
1814
|
+
"Point = namedtuple('Point', 'x y')",
|
|
1815
|
+
"p = Point(1, 2)",
|
|
1816
|
+
"assrt.equal(p.x, 1)",
|
|
1817
|
+
"assrt.equal(p.y, 2)",
|
|
1818
|
+
"Color = namedtuple('Color', 'r, g, b')",
|
|
1819
|
+
"c = Color(255, 128, 0)",
|
|
1820
|
+
"assrt.equal(c.r, 255)",
|
|
1821
|
+
"assrt.equal(c.g, 128)",
|
|
1822
|
+
"assrt.equal(c.b, 0)",
|
|
1823
|
+
].join("\n"),
|
|
1824
|
+
js_checks: [],
|
|
1825
|
+
},
|
|
1826
|
+
|
|
1827
|
+
{
|
|
1828
|
+
name: "collections_namedtuple_methods",
|
|
1829
|
+
description: "namedtuple _asdict, _replace, _make, _fields work correctly",
|
|
1830
|
+
src: [
|
|
1831
|
+
"# globals: assrt",
|
|
1832
|
+
"from collections import namedtuple",
|
|
1833
|
+
"Point = namedtuple('Point', ['x', 'y'])",
|
|
1834
|
+
"p = Point(3, 4)",
|
|
1835
|
+
"d = p._asdict()",
|
|
1836
|
+
"assrt.equal(d['x'], 3)",
|
|
1837
|
+
"assrt.equal(d['y'], 4)",
|
|
1838
|
+
"p2 = p._replace(x=10)",
|
|
1839
|
+
"assrt.equal(p2.x, 10)",
|
|
1840
|
+
"assrt.equal(p2.y, 4)",
|
|
1841
|
+
"assrt.equal(p.x, 3)",
|
|
1842
|
+
"p3 = Point._make([7, 8])",
|
|
1843
|
+
"assrt.equal(p3.x, 7)",
|
|
1844
|
+
"assrt.equal(p3.y, 8)",
|
|
1845
|
+
"assrt.deepEqual(Point._fields, ['x', 'y'])",
|
|
1846
|
+
].join("\n"),
|
|
1847
|
+
js_checks: [],
|
|
1848
|
+
},
|
|
1849
|
+
|
|
1850
|
+
{
|
|
1851
|
+
name: "collections_namedtuple_repr_eq",
|
|
1852
|
+
description: "namedtuple __repr__ and __eq__ work correctly",
|
|
1853
|
+
src: [
|
|
1854
|
+
"# globals: assrt",
|
|
1855
|
+
"from collections import namedtuple",
|
|
1856
|
+
"Point = namedtuple('Point', ['x', 'y'])",
|
|
1857
|
+
"p1 = Point(1, 2)",
|
|
1858
|
+
"p2 = Point(1, 2)",
|
|
1859
|
+
"p3 = Point(1, 3)",
|
|
1860
|
+
"assrt.equal(p1.__repr__(), 'Point(x=1, y=2)')",
|
|
1861
|
+
"assrt.equal(p1.__eq__(p2), True)",
|
|
1862
|
+
"assrt.equal(p1.__eq__(p3), False)",
|
|
1863
|
+
].join("\n"),
|
|
1864
|
+
js_checks: [],
|
|
1865
|
+
},
|
|
1866
|
+
|
|
1867
|
+
{
|
|
1868
|
+
name: "collections_namedtuple_wrong_args",
|
|
1869
|
+
description: "namedtuple raises TypeError on wrong number of arguments",
|
|
1870
|
+
src: [
|
|
1871
|
+
"# globals: assrt",
|
|
1872
|
+
"from collections import namedtuple",
|
|
1873
|
+
"Point = namedtuple('Point', ['x', 'y'])",
|
|
1874
|
+
"assrt.throws(def():",
|
|
1875
|
+
" Point(1)",
|
|
1876
|
+
", /TypeError/)",
|
|
1877
|
+
"assrt.throws(def():",
|
|
1878
|
+
" Point(1, 2, 3)",
|
|
1879
|
+
", /TypeError/)",
|
|
1880
|
+
].join("\n"),
|
|
1881
|
+
js_checks: [],
|
|
1882
|
+
},
|
|
1883
|
+
|
|
1884
|
+
{
|
|
1885
|
+
name: "collections_deque_basic",
|
|
1886
|
+
description: "deque append, appendleft, pop, popleft work correctly",
|
|
1887
|
+
src: [
|
|
1888
|
+
"# globals: assrt",
|
|
1889
|
+
"from collections import deque",
|
|
1890
|
+
"d = deque([1, 2, 3])",
|
|
1891
|
+
"assrt.equal(len(d), 3)",
|
|
1892
|
+
"d.append(4)",
|
|
1893
|
+
"assrt.equal(len(d), 4)",
|
|
1894
|
+
"assrt.equal(d.pop(), 4)",
|
|
1895
|
+
"d.appendleft(0)",
|
|
1896
|
+
"assrt.equal(d.popleft(), 0)",
|
|
1897
|
+
"assrt.equal(len(d), 3)",
|
|
1898
|
+
].join("\n"),
|
|
1899
|
+
js_checks: [],
|
|
1900
|
+
},
|
|
1901
|
+
|
|
1902
|
+
{
|
|
1903
|
+
name: "collections_deque_maxlen",
|
|
1904
|
+
description: "deque with maxlen discards items from opposite end",
|
|
1905
|
+
src: [
|
|
1906
|
+
"# globals: assrt",
|
|
1907
|
+
"from collections import deque",
|
|
1908
|
+
"d = deque([1, 2, 3], maxlen=3)",
|
|
1909
|
+
"assrt.equal(d.maxlen, 3)",
|
|
1910
|
+
"d.append(4)",
|
|
1911
|
+
"assrt.equal(len(d), 3)",
|
|
1912
|
+
"assrt.equal(list(d)[0], 2)",
|
|
1913
|
+
"d.appendleft(0)",
|
|
1914
|
+
"assrt.equal(len(d), 3)",
|
|
1915
|
+
"assrt.equal(list(d)[2], 3)",
|
|
1916
|
+
].join("\n"),
|
|
1917
|
+
js_checks: [],
|
|
1918
|
+
},
|
|
1919
|
+
|
|
1920
|
+
{
|
|
1921
|
+
name: "collections_deque_rotate",
|
|
1922
|
+
description: "deque rotate and reverse work correctly",
|
|
1923
|
+
src: [
|
|
1924
|
+
"# globals: assrt",
|
|
1925
|
+
"from collections import deque",
|
|
1926
|
+
"d = deque([1, 2, 3, 4, 5])",
|
|
1927
|
+
"d.rotate(2)",
|
|
1928
|
+
"assrt.deepEqual(list(d), [4, 5, 1, 2, 3])",
|
|
1929
|
+
"d.rotate(-1)",
|
|
1930
|
+
"assrt.deepEqual(list(d), [5, 1, 2, 3, 4])",
|
|
1931
|
+
"d.reverse()",
|
|
1932
|
+
"assrt.deepEqual(list(d), [4, 3, 2, 1, 5])",
|
|
1933
|
+
].join("\n"),
|
|
1934
|
+
js_checks: [],
|
|
1935
|
+
},
|
|
1936
|
+
|
|
1937
|
+
{
|
|
1938
|
+
name: "collections_deque_contains_iter",
|
|
1939
|
+
description: "deque __contains__, __iter__, count, remove work",
|
|
1940
|
+
src: [
|
|
1941
|
+
"# globals: assrt",
|
|
1942
|
+
"from collections import deque",
|
|
1943
|
+
"d = deque([1, 2, 3, 2, 1])",
|
|
1944
|
+
"assrt.equal(2 in d, True)",
|
|
1945
|
+
"assrt.equal(5 in d, False)",
|
|
1946
|
+
"assrt.equal(d.count(2), 2)",
|
|
1947
|
+
"assrt.equal(d.count(1), 2)",
|
|
1948
|
+
"d.remove(2)",
|
|
1949
|
+
"assrt.equal(d.count(2), 1)",
|
|
1950
|
+
].join("\n"),
|
|
1951
|
+
js_checks: [],
|
|
1952
|
+
},
|
|
1953
|
+
|
|
1954
|
+
{
|
|
1955
|
+
name: "collections_counter_basic",
|
|
1956
|
+
description: "Counter counts items from a string or list",
|
|
1957
|
+
src: [
|
|
1958
|
+
"# globals: assrt",
|
|
1959
|
+
"from __python__ import overload_getitem",
|
|
1960
|
+
"from collections import Counter",
|
|
1961
|
+
"c = Counter('aabbcc')",
|
|
1962
|
+
"assrt.equal(c['a'], 2)",
|
|
1963
|
+
"assrt.equal(c['b'], 2)",
|
|
1964
|
+
"assrt.equal(c['c'], 2)",
|
|
1965
|
+
"assrt.equal(c['z'], 0)",
|
|
1966
|
+
"c2 = Counter([1, 2, 2, 3, 3, 3])",
|
|
1967
|
+
"assrt.equal(c2[1], 1)",
|
|
1968
|
+
"assrt.equal(c2[2], 2)",
|
|
1969
|
+
"assrt.equal(c2[3], 3)",
|
|
1970
|
+
].join("\n"),
|
|
1971
|
+
js_checks: [],
|
|
1972
|
+
},
|
|
1973
|
+
|
|
1974
|
+
{
|
|
1975
|
+
name: "collections_counter_most_common",
|
|
1976
|
+
description: "Counter.most_common returns items sorted by count",
|
|
1977
|
+
src: [
|
|
1978
|
+
"# globals: assrt",
|
|
1979
|
+
"from collections import Counter",
|
|
1980
|
+
"c = Counter('aabbbcc')",
|
|
1981
|
+
"mc = c.most_common(2)",
|
|
1982
|
+
"assrt.equal(mc[0][0], 'b')",
|
|
1983
|
+
"assrt.equal(mc[0][1], 3)",
|
|
1984
|
+
"assrt.equal(mc[1][1], 2)",
|
|
1985
|
+
"assrt.equal(len(c.most_common()), 3)",
|
|
1986
|
+
].join("\n"),
|
|
1987
|
+
js_checks: [],
|
|
1988
|
+
},
|
|
1989
|
+
|
|
1990
|
+
{
|
|
1991
|
+
name: "collections_counter_update_subtract",
|
|
1992
|
+
description: "Counter update and subtract modify counts in-place",
|
|
1993
|
+
src: [
|
|
1994
|
+
"# globals: assrt",
|
|
1995
|
+
"from __python__ import overload_getitem",
|
|
1996
|
+
"from collections import Counter",
|
|
1997
|
+
"c = Counter('aab')",
|
|
1998
|
+
"c.update('ab')",
|
|
1999
|
+
"assrt.equal(c['a'], 3)",
|
|
2000
|
+
"assrt.equal(c['b'], 2)",
|
|
2001
|
+
"c.subtract('ab')",
|
|
2002
|
+
"assrt.equal(c['a'], 2)",
|
|
2003
|
+
"assrt.equal(c['b'], 1)",
|
|
2004
|
+
].join("\n"),
|
|
2005
|
+
js_checks: [],
|
|
2006
|
+
},
|
|
2007
|
+
|
|
2008
|
+
{
|
|
2009
|
+
name: "collections_counter_arithmetic",
|
|
2010
|
+
description: "Counter __add__, __sub__, __or__, __and__ via method calls",
|
|
2011
|
+
src: [
|
|
2012
|
+
"# globals: assrt",
|
|
2013
|
+
"from __python__ import overload_getitem",
|
|
2014
|
+
"from collections import Counter",
|
|
2015
|
+
"c1 = Counter('aab')",
|
|
2016
|
+
"c2 = Counter('ab')",
|
|
2017
|
+
"c3 = c1.__add__(c2)",
|
|
2018
|
+
"assrt.equal(c3['a'], 3)",
|
|
2019
|
+
"assrt.equal(c3['b'], 2)",
|
|
2020
|
+
"c4 = c1.__sub__(c2)",
|
|
2021
|
+
"assrt.equal(c4['a'], 1)",
|
|
2022
|
+
"assrt.equal(c4.get('b', 0), 0)",
|
|
2023
|
+
"c5 = c1.__or__(c2)",
|
|
2024
|
+
"assrt.equal(c5['a'], 2)",
|
|
2025
|
+
"assrt.equal(c5['b'], 1)",
|
|
2026
|
+
"c6 = c1.__and__(c2)",
|
|
2027
|
+
"assrt.equal(c6['a'], 1)",
|
|
2028
|
+
"assrt.equal(c6['b'], 1)",
|
|
2029
|
+
].join("\n"),
|
|
2030
|
+
js_checks: [],
|
|
2031
|
+
},
|
|
2032
|
+
|
|
2033
|
+
{
|
|
2034
|
+
name: "collections_counter_elements",
|
|
2035
|
+
description: "Counter.elements yields each element repeated by count",
|
|
2036
|
+
src: [
|
|
2037
|
+
"# globals: assrt",
|
|
2038
|
+
"from collections import Counter",
|
|
2039
|
+
"c = Counter({'a': 3, 'b': 1})",
|
|
2040
|
+
"elems = list(c.elements())",
|
|
2041
|
+
"elems.sort()",
|
|
2042
|
+
"assrt.deepEqual(elems, ['a', 'a', 'a', 'b'])",
|
|
2043
|
+
].join("\n"),
|
|
2044
|
+
js_checks: [],
|
|
2045
|
+
},
|
|
2046
|
+
|
|
2047
|
+
{
|
|
2048
|
+
name: "collections_ordereddict_basic",
|
|
2049
|
+
description: "OrderedDict preserves insertion order and supports item access",
|
|
2050
|
+
src: [
|
|
2051
|
+
"# globals: assrt",
|
|
2052
|
+
"from __python__ import overload_getitem",
|
|
2053
|
+
"from collections import OrderedDict",
|
|
2054
|
+
"od = OrderedDict()",
|
|
2055
|
+
"od['a'] = 1",
|
|
2056
|
+
"od['b'] = 2",
|
|
2057
|
+
"od['c'] = 3",
|
|
2058
|
+
"assrt.equal(od['a'], 1)",
|
|
2059
|
+
"assrt.equal(od['b'], 2)",
|
|
2060
|
+
"assrt.deepEqual(od.keys(), ['a', 'b', 'c'])",
|
|
2061
|
+
"assrt.deepEqual(od.values(), [1, 2, 3])",
|
|
2062
|
+
].join("\n"),
|
|
2063
|
+
js_checks: [],
|
|
2064
|
+
},
|
|
2065
|
+
|
|
2066
|
+
{
|
|
2067
|
+
name: "collections_ordereddict_popitem_move",
|
|
2068
|
+
description: "OrderedDict popitem and move_to_end work correctly",
|
|
2069
|
+
src: [
|
|
2070
|
+
"# globals: assrt",
|
|
2071
|
+
"from __python__ import overload_getitem",
|
|
2072
|
+
"from collections import OrderedDict",
|
|
2073
|
+
"od = OrderedDict()",
|
|
2074
|
+
"od['a'] = 1",
|
|
2075
|
+
"od['b'] = 2",
|
|
2076
|
+
"od['c'] = 3",
|
|
2077
|
+
"item = od.popitem()",
|
|
2078
|
+
"assrt.deepEqual(item, ['c', 3])",
|
|
2079
|
+
"assrt.equal(len(od), 2)",
|
|
2080
|
+
"item2 = od.popitem(last=False)",
|
|
2081
|
+
"assrt.deepEqual(item2, ['a', 1])",
|
|
2082
|
+
"od2 = OrderedDict()",
|
|
2083
|
+
"od2['x'] = 10",
|
|
2084
|
+
"od2['y'] = 20",
|
|
2085
|
+
"od2['z'] = 30",
|
|
2086
|
+
"od2.move_to_end('x')",
|
|
2087
|
+
"assrt.deepEqual(od2.keys(), ['y', 'z', 'x'])",
|
|
2088
|
+
"od2.move_to_end('x', last=False)",
|
|
2089
|
+
"assrt.deepEqual(od2.keys(), ['x', 'y', 'z'])",
|
|
2090
|
+
].join("\n"),
|
|
2091
|
+
js_checks: [],
|
|
2092
|
+
},
|
|
2093
|
+
|
|
2094
|
+
{
|
|
2095
|
+
name: "collections_ordereddict_eq",
|
|
2096
|
+
description: "OrderedDict equality considers insertion order",
|
|
2097
|
+
src: [
|
|
2098
|
+
"# globals: assrt",
|
|
2099
|
+
"from __python__ import overload_getitem",
|
|
2100
|
+
"from collections import OrderedDict",
|
|
2101
|
+
"od1 = OrderedDict()",
|
|
2102
|
+
"od1['a'] = 1",
|
|
2103
|
+
"od1['b'] = 2",
|
|
2104
|
+
"od2 = OrderedDict()",
|
|
2105
|
+
"od2['a'] = 1",
|
|
2106
|
+
"od2['b'] = 2",
|
|
2107
|
+
"od3 = OrderedDict()",
|
|
2108
|
+
"od3['b'] = 2",
|
|
2109
|
+
"od3['a'] = 1",
|
|
2110
|
+
"assrt.equal(od1.__eq__(od2), True)",
|
|
2111
|
+
"assrt.equal(od1.__eq__(od3), False)",
|
|
2112
|
+
].join("\n"),
|
|
2113
|
+
js_checks: [],
|
|
2114
|
+
},
|
|
2115
|
+
|
|
2116
|
+
{
|
|
2117
|
+
name: "collections_defaultdict_basic",
|
|
2118
|
+
description: "defaultdict calls factory for missing keys",
|
|
2119
|
+
src: [
|
|
2120
|
+
"# globals: assrt",
|
|
2121
|
+
"from __python__ import overload_getitem",
|
|
2122
|
+
"from collections import defaultdict",
|
|
2123
|
+
"d = defaultdict(list)",
|
|
2124
|
+
"d['a'].append(1)",
|
|
2125
|
+
"d['a'].append(2)",
|
|
2126
|
+
"d['b'].append(3)",
|
|
2127
|
+
"assrt.deepEqual(d['a'], [1, 2])",
|
|
2128
|
+
"assrt.deepEqual(d['b'], [3])",
|
|
2129
|
+
"assrt.equal(len(d), 2)",
|
|
2130
|
+
].join("\n"),
|
|
2131
|
+
js_checks: [],
|
|
2132
|
+
},
|
|
2133
|
+
|
|
2134
|
+
{
|
|
2135
|
+
name: "collections_defaultdict_int",
|
|
2136
|
+
description: "defaultdict with int-returning factory works as a counter",
|
|
2137
|
+
src: [
|
|
2138
|
+
"# globals: assrt",
|
|
2139
|
+
"from __python__ import overload_getitem",
|
|
2140
|
+
"from collections import defaultdict",
|
|
2141
|
+
"d = defaultdict(lambda: 0)",
|
|
2142
|
+
"for ch in 'hello':",
|
|
2143
|
+
" d[ch] += 1",
|
|
2144
|
+
"assrt.equal(d['l'], 2)",
|
|
2145
|
+
"assrt.equal(d['h'], 1)",
|
|
2146
|
+
"assrt.equal(d['z'], 0)",
|
|
2147
|
+
].join("\n"),
|
|
2148
|
+
js_checks: [],
|
|
2149
|
+
},
|
|
2150
|
+
|
|
2151
|
+
{
|
|
2152
|
+
name: "collections_dict_conversion",
|
|
2153
|
+
description: "dict() converts Counter, OrderedDict, defaultdict to a plain dict",
|
|
2154
|
+
src: [
|
|
2155
|
+
"# globals: assrt",
|
|
2156
|
+
"from __python__ import overload_getitem",
|
|
2157
|
+
"from collections import Counter, OrderedDict, defaultdict",
|
|
2158
|
+
"c = Counter('aab')",
|
|
2159
|
+
"d = dict(c)",
|
|
2160
|
+
"assrt.equal(d['a'], 2)",
|
|
2161
|
+
"assrt.equal(d['b'], 1)",
|
|
2162
|
+
"od = OrderedDict()",
|
|
2163
|
+
"od['x'] = 10",
|
|
2164
|
+
"od['y'] = 20",
|
|
2165
|
+
"d2 = dict(od)",
|
|
2166
|
+
"assrt.equal(d2['x'], 10)",
|
|
2167
|
+
"assrt.equal(d2['y'], 20)",
|
|
2168
|
+
"dd = defaultdict(list)",
|
|
2169
|
+
"dd['k'].append(1)",
|
|
2170
|
+
"dd['k'].append(2)",
|
|
2171
|
+
"d3 = dict(dd)",
|
|
2172
|
+
"assrt.deepEqual(d3['k'], [1, 2])",
|
|
2173
|
+
].join("\n"),
|
|
2174
|
+
js_checks: [],
|
|
2175
|
+
},
|
|
2176
|
+
|
|
2177
|
+
{
|
|
2178
|
+
name: "collections_defaultdict_no_factory",
|
|
2179
|
+
description: "defaultdict with no factory raises KeyError on missing key",
|
|
2180
|
+
src: [
|
|
2181
|
+
"# globals: assrt",
|
|
2182
|
+
"from __python__ import overload_getitem",
|
|
2183
|
+
"from collections import defaultdict",
|
|
2184
|
+
"d = defaultdict()",
|
|
2185
|
+
"assrt.throws(def():",
|
|
2186
|
+
" x = d['missing']",
|
|
2187
|
+
", /KeyError/)",
|
|
2188
|
+
].join("\n"),
|
|
2189
|
+
js_checks: [],
|
|
2190
|
+
},
|
|
2191
|
+
|
|
2192
|
+
// ── itertools ─────────────────────────────────────────────────────────
|
|
2193
|
+
|
|
2194
|
+
{
|
|
2195
|
+
name: "itertools_count",
|
|
2196
|
+
description: "itertools.count counts from start by step",
|
|
2197
|
+
src: [
|
|
2198
|
+
"# globals: assrt",
|
|
2199
|
+
"from itertools import count, islice",
|
|
2200
|
+
"assrt.deepEqual(list(islice(count(), 5)), [0, 1, 2, 3, 4])",
|
|
2201
|
+
"assrt.deepEqual(list(islice(count(10, 2), 4)), [10, 12, 14, 16])",
|
|
2202
|
+
"assrt.deepEqual(list(islice(count(-1, -1), 4)), [-1, -2, -3, -4])",
|
|
2203
|
+
].join("\n"),
|
|
2204
|
+
js_checks: [],
|
|
2205
|
+
},
|
|
2206
|
+
|
|
2207
|
+
{
|
|
2208
|
+
name: "itertools_cycle",
|
|
2209
|
+
description: "itertools.cycle cycles through an iterable indefinitely",
|
|
2210
|
+
src: [
|
|
2211
|
+
"# globals: assrt",
|
|
2212
|
+
"from itertools import cycle, islice",
|
|
2213
|
+
"assrt.deepEqual(list(islice(cycle([1, 2, 3]), 7)), [1, 2, 3, 1, 2, 3, 1])",
|
|
2214
|
+
"assrt.deepEqual(list(islice(cycle('ab'), 5)), ['a', 'b', 'a', 'b', 'a'])",
|
|
2215
|
+
"assrt.deepEqual(list(islice(cycle([42]), 3)), [42, 42, 42])",
|
|
2216
|
+
].join("\n"),
|
|
2217
|
+
js_checks: [],
|
|
2218
|
+
},
|
|
2219
|
+
|
|
2220
|
+
{
|
|
2221
|
+
name: "itertools_repeat",
|
|
2222
|
+
description: "itertools.repeat repeats an element n times or forever",
|
|
2223
|
+
src: [
|
|
2224
|
+
"# globals: assrt",
|
|
2225
|
+
"from itertools import repeat, islice",
|
|
2226
|
+
"assrt.deepEqual(list(repeat(5, 3)), [5, 5, 5])",
|
|
2227
|
+
"assrt.deepEqual(list(repeat('x', 4)), ['x', 'x', 'x', 'x'])",
|
|
2228
|
+
"assrt.deepEqual(list(repeat(0, 0)), [])",
|
|
2229
|
+
"assrt.deepEqual(list(islice(repeat(7), 3)), [7, 7, 7])",
|
|
2230
|
+
].join("\n"),
|
|
2231
|
+
js_checks: [],
|
|
2232
|
+
},
|
|
2233
|
+
|
|
2234
|
+
{
|
|
2235
|
+
name: "itertools_accumulate",
|
|
2236
|
+
description: "itertools.accumulate returns running totals",
|
|
2237
|
+
src: [
|
|
2238
|
+
"# globals: assrt",
|
|
2239
|
+
"from itertools import accumulate",
|
|
2240
|
+
"assrt.deepEqual(list(accumulate([1, 2, 3, 4])), [1, 3, 6, 10])",
|
|
2241
|
+
"assrt.deepEqual(list(accumulate([1, 2, 3], lambda a, b: a * b)), [1, 2, 6])",
|
|
2242
|
+
"assrt.deepEqual(list(accumulate([1, 2, 3], initial=10)), [10, 11, 13, 16])",
|
|
2243
|
+
"assrt.deepEqual(list(accumulate([])), [])",
|
|
2244
|
+
"assrt.deepEqual(list(accumulate([], initial=5)), [5])",
|
|
2245
|
+
].join("\n"),
|
|
2246
|
+
js_checks: [],
|
|
2247
|
+
},
|
|
2248
|
+
|
|
2249
|
+
{
|
|
2250
|
+
name: "itertools_chain",
|
|
2251
|
+
description: "itertools.chain chains multiple iterables together",
|
|
2252
|
+
src: [
|
|
2253
|
+
"# globals: assrt",
|
|
2254
|
+
"from itertools import chain",
|
|
2255
|
+
"assrt.deepEqual(list(chain([1, 2], [3, 4])), [1, 2, 3, 4])",
|
|
2256
|
+
"assrt.deepEqual(list(chain([1], [], [2, 3])), [1, 2, 3])",
|
|
2257
|
+
"assrt.deepEqual(list(chain()), [])",
|
|
2258
|
+
"assrt.deepEqual(list(chain([1, 2])), [1, 2])",
|
|
2259
|
+
].join("\n"),
|
|
2260
|
+
js_checks: [],
|
|
2261
|
+
},
|
|
2262
|
+
|
|
2263
|
+
{
|
|
2264
|
+
name: "itertools_chain_from_iterable",
|
|
2265
|
+
description: "itertools.chain.from_iterable chains from a single iterable of iterables",
|
|
2266
|
+
src: [
|
|
2267
|
+
"# globals: assrt",
|
|
2268
|
+
"from itertools import chain",
|
|
2269
|
+
"assrt.deepEqual(list(chain.from_iterable([[1, 2], [3, 4]])), [1, 2, 3, 4])",
|
|
2270
|
+
"assrt.deepEqual(list(chain.from_iterable([[1], [], [2, 3]])), [1, 2, 3])",
|
|
2271
|
+
"assrt.deepEqual(list(chain.from_iterable([])), [])",
|
|
2272
|
+
].join("\n"),
|
|
2273
|
+
js_checks: [],
|
|
2274
|
+
},
|
|
2275
|
+
|
|
2276
|
+
{
|
|
2277
|
+
name: "itertools_compress",
|
|
2278
|
+
description: "itertools.compress filters data by boolean selectors",
|
|
2279
|
+
src: [
|
|
2280
|
+
"# globals: assrt",
|
|
2281
|
+
"from itertools import compress",
|
|
2282
|
+
"assrt.deepEqual(list(compress([1, 2, 3, 4, 5], [1, 0, 1, 0, 1])), [1, 3, 5])",
|
|
2283
|
+
"assrt.deepEqual(list(compress('ABCDE', [True, False, True, False, True])), ['A', 'C', 'E'])",
|
|
2284
|
+
"assrt.deepEqual(list(compress([1, 2, 3], [0, 0, 0])), [])",
|
|
2285
|
+
].join("\n"),
|
|
2286
|
+
js_checks: [],
|
|
2287
|
+
},
|
|
2288
|
+
|
|
2289
|
+
{
|
|
2290
|
+
name: "itertools_dropwhile",
|
|
2291
|
+
description: "itertools.dropwhile drops elements while predicate is true",
|
|
2292
|
+
src: [
|
|
2293
|
+
"# globals: assrt",
|
|
2294
|
+
"from itertools import dropwhile",
|
|
2295
|
+
"assrt.deepEqual(list(dropwhile(lambda x: x < 5, [1, 4, 6, 4, 1])), [6, 4, 1])",
|
|
2296
|
+
"assrt.deepEqual(list(dropwhile(lambda x: True, [1, 2, 3])), [])",
|
|
2297
|
+
"assrt.deepEqual(list(dropwhile(lambda x: False, [1, 2, 3])), [1, 2, 3])",
|
|
2298
|
+
].join("\n"),
|
|
2299
|
+
js_checks: [],
|
|
2300
|
+
},
|
|
2301
|
+
|
|
2302
|
+
{
|
|
2303
|
+
name: "itertools_filterfalse",
|
|
2304
|
+
description: "itertools.filterfalse yields elements where predicate is falsy",
|
|
2305
|
+
src: [
|
|
2306
|
+
"# globals: assrt",
|
|
2307
|
+
"from itertools import filterfalse",
|
|
2308
|
+
"assrt.deepEqual(list(filterfalse(lambda x: x % 2, range(10))), [0, 2, 4, 6, 8])",
|
|
2309
|
+
"assrt.deepEqual(list(filterfalse(lambda x: x, [0, 1, 0, 2, 0])), [0, 0, 0])",
|
|
2310
|
+
].join("\n"),
|
|
2311
|
+
js_checks: [],
|
|
2312
|
+
},
|
|
2313
|
+
|
|
2314
|
+
{
|
|
2315
|
+
name: "itertools_groupby",
|
|
2316
|
+
description: "itertools.groupby groups consecutive equal elements",
|
|
2317
|
+
src: [
|
|
2318
|
+
"# globals: assrt",
|
|
2319
|
+
"from itertools import groupby",
|
|
2320
|
+
"result = list(groupby([1, 1, 2, 3, 3]))",
|
|
2321
|
+
"assrt.equal(result[0][0], 1)",
|
|
2322
|
+
"assrt.equal(result[1][0], 2)",
|
|
2323
|
+
"assrt.equal(result[2][0], 3)",
|
|
2324
|
+
"assrt.deepEqual(list(result[0][1]), [1, 1])",
|
|
2325
|
+
"assrt.deepEqual(list(result[1][1]), [2])",
|
|
2326
|
+
"assrt.deepEqual(list(result[2][1]), [3, 3])",
|
|
2327
|
+
"# With key function",
|
|
2328
|
+
"words = ['apple', 'ant', 'bat', 'bee', 'cat']",
|
|
2329
|
+
"keyed = [(k, list(g)) for k, g in groupby(words, lambda w: w[0])]",
|
|
2330
|
+
"assrt.equal(keyed[0][0], 'a')",
|
|
2331
|
+
"assrt.equal(keyed[1][0], 'b')",
|
|
2332
|
+
"assrt.equal(keyed[2][0], 'c')",
|
|
2333
|
+
].join("\n"),
|
|
2334
|
+
js_checks: [],
|
|
2335
|
+
},
|
|
2336
|
+
|
|
2337
|
+
{
|
|
2338
|
+
name: "itertools_islice",
|
|
2339
|
+
description: "itertools.islice slices an iterator",
|
|
2340
|
+
src: [
|
|
2341
|
+
"# globals: assrt",
|
|
2342
|
+
"from itertools import islice",
|
|
2343
|
+
"assrt.deepEqual(list(islice(range(10), 5)), [0, 1, 2, 3, 4])",
|
|
2344
|
+
"assrt.deepEqual(list(islice(range(10), 2, 7)), [2, 3, 4, 5, 6])",
|
|
2345
|
+
"assrt.deepEqual(list(islice(range(10), 2, 8, 2)), [2, 4, 6])",
|
|
2346
|
+
"assrt.deepEqual(list(islice(range(10), 0)), [])",
|
|
2347
|
+
"assrt.deepEqual(list(islice(range(10), None)), [0,1,2,3,4,5,6,7,8,9])",
|
|
2348
|
+
].join("\n"),
|
|
2349
|
+
js_checks: [],
|
|
2350
|
+
},
|
|
2351
|
+
|
|
2352
|
+
{
|
|
2353
|
+
name: "itertools_pairwise",
|
|
2354
|
+
description: "itertools.pairwise yields overlapping consecutive pairs",
|
|
2355
|
+
src: [
|
|
2356
|
+
"# globals: assrt",
|
|
2357
|
+
"from itertools import pairwise",
|
|
2358
|
+
"assrt.deepEqual(list(pairwise([1, 2, 3, 4])), [[1, 2], [2, 3], [3, 4]])",
|
|
2359
|
+
"assrt.deepEqual(list(pairwise([1])), [])",
|
|
2360
|
+
"assrt.deepEqual(list(pairwise([])), [])",
|
|
2361
|
+
"assrt.deepEqual(list(pairwise('ABCD')), [['A','B'],['B','C'],['C','D']])",
|
|
2362
|
+
].join("\n"),
|
|
2363
|
+
js_checks: [],
|
|
2364
|
+
},
|
|
2365
|
+
|
|
2366
|
+
{
|
|
2367
|
+
name: "itertools_starmap",
|
|
2368
|
+
description: "itertools.starmap maps a function with argument unpacking",
|
|
2369
|
+
src: [
|
|
2370
|
+
"# globals: assrt",
|
|
2371
|
+
"from itertools import starmap",
|
|
2372
|
+
"assrt.deepEqual(list(starmap(lambda x, y: x * y, [[2, 3], [4, 5], [1, 6]])), [6, 20, 6])",
|
|
2373
|
+
"assrt.deepEqual(list(starmap(lambda a, b, c: a + b + c, [[1,2,3],[4,5,6]])), [6, 15])",
|
|
2374
|
+
"assrt.deepEqual(list(starmap(pow, [[2, 3], [3, 2]])), [8, 9])",
|
|
2375
|
+
].join("\n"),
|
|
2376
|
+
js_checks: [],
|
|
2377
|
+
},
|
|
2378
|
+
|
|
2379
|
+
{
|
|
2380
|
+
name: "itertools_takewhile",
|
|
2381
|
+
description: "itertools.takewhile yields elements while predicate is true",
|
|
2382
|
+
src: [
|
|
2383
|
+
"# globals: assrt",
|
|
2384
|
+
"from itertools import takewhile",
|
|
2385
|
+
"assrt.deepEqual(list(takewhile(lambda x: x < 5, [1, 4, 6, 4, 1])), [1, 4])",
|
|
2386
|
+
"assrt.deepEqual(list(takewhile(lambda x: True, [1, 2, 3])), [1, 2, 3])",
|
|
2387
|
+
"assrt.deepEqual(list(takewhile(lambda x: False, [1, 2, 3])), [])",
|
|
2388
|
+
].join("\n"),
|
|
2389
|
+
js_checks: [],
|
|
2390
|
+
},
|
|
2391
|
+
|
|
2392
|
+
{
|
|
2393
|
+
name: "itertools_zip_longest",
|
|
2394
|
+
description: "itertools.zip_longest zips iterables using a fill value for shorter ones",
|
|
2395
|
+
src: [
|
|
2396
|
+
"# globals: assrt",
|
|
2397
|
+
"from itertools import zip_longest",
|
|
2398
|
+
"assrt.deepEqual(list(zip_longest([1, 2, 3], [4, 5])), [[1,4],[2,5],[3,None]])",
|
|
2399
|
+
"assrt.deepEqual(list(zip_longest([1, 2], [3, 4, 5], fillvalue=0)), [[1,3],[2,4],[0,5]])",
|
|
2400
|
+
"assrt.deepEqual(list(zip_longest([], [1, 2], fillvalue='x')), [['x',1],['x',2]])",
|
|
2401
|
+
"assrt.deepEqual(list(zip_longest()), [])",
|
|
2402
|
+
].join("\n"),
|
|
2403
|
+
js_checks: [],
|
|
2404
|
+
},
|
|
2405
|
+
|
|
2406
|
+
{
|
|
2407
|
+
name: "itertools_product",
|
|
2408
|
+
description: "itertools.product returns the cartesian product of iterables",
|
|
2409
|
+
src: [
|
|
2410
|
+
"# globals: assrt",
|
|
2411
|
+
"from itertools import product",
|
|
2412
|
+
"assrt.deepEqual(list(product([1,2],[3,4])), [[1,3],[1,4],[2,3],[2,4]])",
|
|
2413
|
+
"assrt.deepEqual(list(product([0,1], repeat=2)), [[0,0],[0,1],[1,0],[1,1]])",
|
|
2414
|
+
"assrt.deepEqual(list(product([1,2],[3,4],[5,6])), [[1,3,5],[1,3,6],[1,4,5],[1,4,6],[2,3,5],[2,3,6],[2,4,5],[2,4,6]])",
|
|
2415
|
+
"assrt.deepEqual(list(product([], [1,2])), [])",
|
|
2416
|
+
].join("\n"),
|
|
2417
|
+
js_checks: [],
|
|
2418
|
+
},
|
|
2419
|
+
|
|
2420
|
+
{
|
|
2421
|
+
name: "itertools_permutations",
|
|
2422
|
+
description: "itertools.permutations returns all r-length permutations",
|
|
2423
|
+
src: [
|
|
2424
|
+
"# globals: assrt",
|
|
2425
|
+
"from itertools import permutations",
|
|
2426
|
+
"assrt.deepEqual(list(permutations([1,2,3])), [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]])",
|
|
2427
|
+
"assrt.deepEqual(list(permutations([1,2,3], 2)), [[1,2],[1,3],[2,1],[2,3],[3,1],[3,2]])",
|
|
2428
|
+
"assrt.deepEqual(list(permutations([1,2], 0)), [[]])",
|
|
2429
|
+
"assrt.deepEqual(list(permutations([1], 2)), [])",
|
|
2430
|
+
].join("\n"),
|
|
2431
|
+
js_checks: [],
|
|
2432
|
+
},
|
|
2433
|
+
|
|
2434
|
+
{
|
|
2435
|
+
name: "itertools_combinations",
|
|
2436
|
+
description: "itertools.combinations returns r-length combinations without repeats",
|
|
2437
|
+
src: [
|
|
2438
|
+
"# globals: assrt",
|
|
2439
|
+
"from itertools import combinations",
|
|
2440
|
+
"assrt.deepEqual(list(combinations([1,2,3,4], 2)), [[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]])",
|
|
2441
|
+
"assrt.deepEqual(list(combinations([1,2,3], 3)), [[1,2,3]])",
|
|
2442
|
+
"assrt.deepEqual(list(combinations([1,2,3], 0)), [[]])",
|
|
2443
|
+
"assrt.deepEqual(list(combinations([1,2], 3)), [])",
|
|
2444
|
+
].join("\n"),
|
|
2445
|
+
js_checks: [],
|
|
2446
|
+
},
|
|
2447
|
+
|
|
2448
|
+
{
|
|
2449
|
+
name: "itertools_combinations_with_replacement",
|
|
2450
|
+
description: "itertools.combinations_with_replacement returns r-length combinations allowing repeats",
|
|
2451
|
+
src: [
|
|
2452
|
+
"# globals: assrt",
|
|
2453
|
+
"from itertools import combinations_with_replacement",
|
|
2454
|
+
"assrt.deepEqual(list(combinations_with_replacement([1,2,3], 2)), [[1,1],[1,2],[1,3],[2,2],[2,3],[3,3]])",
|
|
2455
|
+
"assrt.deepEqual(list(combinations_with_replacement([1,2], 3)), [[1,1,1],[1,1,2],[1,2,2],[2,2,2]])",
|
|
2456
|
+
].join("\n"),
|
|
2457
|
+
js_checks: [],
|
|
2458
|
+
},
|
|
2459
|
+
|
|
2460
|
+
// ── operator overloading ───────────────────────────────────────────────
|
|
2461
|
+
|
|
2462
|
+
{
|
|
2463
|
+
name: "operator_overloading_basic",
|
|
2464
|
+
description: "Binary +, -, *, / dispatch to __add__, __sub__, __mul__, __truediv__",
|
|
2465
|
+
src: [
|
|
2466
|
+
"# globals: assrt",
|
|
2467
|
+
"from __python__ import overload_operators",
|
|
2468
|
+
"class Vec:",
|
|
2469
|
+
" def __init__(self, x, y):",
|
|
2470
|
+
" self.x = x",
|
|
2471
|
+
" self.y = y",
|
|
2472
|
+
" def __add__(self, other):",
|
|
2473
|
+
" return Vec(self.x + other.x, self.y + other.y)",
|
|
2474
|
+
" def __sub__(self, other):",
|
|
2475
|
+
" return Vec(self.x - other.x, self.y - other.y)",
|
|
2476
|
+
" def __mul__(self, scalar):",
|
|
2477
|
+
" return Vec(self.x * scalar, self.y * scalar)",
|
|
2478
|
+
" def __truediv__(self, scalar):",
|
|
2479
|
+
" return Vec(self.x / scalar, self.y / scalar)",
|
|
2480
|
+
"a = Vec(3, 4)",
|
|
2481
|
+
"b = Vec(1, 2)",
|
|
2482
|
+
"c = a + b",
|
|
2483
|
+
"assrt.equal(c.x, 4)",
|
|
2484
|
+
"assrt.equal(c.y, 6)",
|
|
2485
|
+
"d = a - b",
|
|
2486
|
+
"assrt.equal(d.x, 2)",
|
|
2487
|
+
"assrt.equal(d.y, 2)",
|
|
2488
|
+
"e = a * 2",
|
|
2489
|
+
"assrt.equal(e.x, 6)",
|
|
2490
|
+
"assrt.equal(e.y, 8)",
|
|
2491
|
+
"f = a / 2",
|
|
2492
|
+
"assrt.equal(f.x, 1.5)",
|
|
2493
|
+
"assrt.equal(f.y, 2)",
|
|
2494
|
+
].join("\n"),
|
|
2495
|
+
js_checks: [
|
|
2496
|
+
"ρσ_op_add(",
|
|
2497
|
+
"ρσ_op_sub(",
|
|
2498
|
+
"ρσ_op_mul(",
|
|
2499
|
+
"ρσ_op_truediv(",
|
|
2500
|
+
],
|
|
2501
|
+
},
|
|
2502
|
+
|
|
2503
|
+
{
|
|
2504
|
+
name: "operator_overloading_floor_mod_pow",
|
|
2505
|
+
description: "// % ** dispatch to __floordiv__, __mod__, __pow__",
|
|
2506
|
+
src: [
|
|
2507
|
+
"# globals: assrt",
|
|
2508
|
+
"from __python__ import overload_operators",
|
|
2509
|
+
"class Num:",
|
|
2510
|
+
" def __init__(self, v):",
|
|
2511
|
+
" self.v = v",
|
|
2512
|
+
" def __floordiv__(self, other):",
|
|
2513
|
+
" return Num(self.v // other.v)",
|
|
2514
|
+
" def __mod__(self, other):",
|
|
2515
|
+
" return Num(self.v % other.v)",
|
|
2516
|
+
" def __pow__(self, other):",
|
|
2517
|
+
" return Num(self.v ** other.v)",
|
|
2518
|
+
"a = Num(10)",
|
|
2519
|
+
"b = Num(3)",
|
|
2520
|
+
"assrt.equal((a // b).v, 3)",
|
|
2521
|
+
"assrt.equal((a % b).v, 1)",
|
|
2522
|
+
"assrt.equal((a ** b).v, 1000)",
|
|
2523
|
+
].join("\n"),
|
|
2524
|
+
js_checks: [
|
|
2525
|
+
"ρσ_op_floordiv(",
|
|
2526
|
+
"ρσ_op_mod(",
|
|
2527
|
+
"ρσ_op_pow(",
|
|
2528
|
+
],
|
|
2529
|
+
},
|
|
2530
|
+
|
|
2531
|
+
{
|
|
2532
|
+
name: "operator_overloading_bitwise",
|
|
2533
|
+
description: "&, |, ^, <<, >> dispatch to bitwise dunder methods",
|
|
2534
|
+
src: [
|
|
2535
|
+
"# globals: assrt",
|
|
2536
|
+
"from __python__ import overload_operators",
|
|
2537
|
+
"class Bits:",
|
|
2538
|
+
" def __init__(self, v):",
|
|
2539
|
+
" self.v = v",
|
|
2540
|
+
" def __and__(self, other):",
|
|
2541
|
+
" return Bits(self.v & other.v)",
|
|
2542
|
+
" def __or__(self, other):",
|
|
2543
|
+
" return Bits(self.v | other.v)",
|
|
2544
|
+
" def __xor__(self, other):",
|
|
2545
|
+
" return Bits(self.v ^ other.v)",
|
|
2546
|
+
" def __lshift__(self, n):",
|
|
2547
|
+
" return Bits(self.v << n)",
|
|
2548
|
+
" def __rshift__(self, n):",
|
|
2549
|
+
" return Bits(self.v >> n)",
|
|
2550
|
+
"a = Bits(0b1010)",
|
|
2551
|
+
"b = Bits(0b1100)",
|
|
2552
|
+
"assrt.equal((a & b).v, 0b1000)",
|
|
2553
|
+
"assrt.equal((a | b).v, 0b1110)",
|
|
2554
|
+
"assrt.equal((a ^ b).v, 0b0110)",
|
|
2555
|
+
"assrt.equal((a << 1).v, 0b10100)",
|
|
2556
|
+
"assrt.equal((a >> 1).v, 0b101)",
|
|
2557
|
+
].join("\n"),
|
|
2558
|
+
js_checks: [
|
|
2559
|
+
"ρσ_op_and(",
|
|
2560
|
+
"ρσ_op_or(",
|
|
2561
|
+
"ρσ_op_xor(",
|
|
2562
|
+
"ρσ_op_lshift(",
|
|
2563
|
+
"ρσ_op_rshift(",
|
|
2564
|
+
],
|
|
2565
|
+
},
|
|
2566
|
+
|
|
2567
|
+
{
|
|
2568
|
+
name: "operator_overloading_unary",
|
|
2569
|
+
description: "Unary - + ~ dispatch to __neg__, __pos__, __invert__",
|
|
2570
|
+
src: [
|
|
2571
|
+
"# globals: assrt",
|
|
2572
|
+
"from __python__ import overload_operators",
|
|
2573
|
+
"class MyNum:",
|
|
2574
|
+
" def __init__(self, v):",
|
|
2575
|
+
" self.v = v",
|
|
2576
|
+
" def __neg__(self):",
|
|
2577
|
+
" return MyNum(-self.v)",
|
|
2578
|
+
" def __pos__(self):",
|
|
2579
|
+
" return MyNum(abs(self.v))",
|
|
2580
|
+
" def __invert__(self):",
|
|
2581
|
+
" return MyNum(~self.v)",
|
|
2582
|
+
"a = MyNum(5)",
|
|
2583
|
+
"assrt.equal((-a).v, -5)",
|
|
2584
|
+
"assrt.equal((+a).v, 5)",
|
|
2585
|
+
"assrt.equal((~a).v, ~5)",
|
|
2586
|
+
].join("\n"),
|
|
2587
|
+
js_checks: [
|
|
2588
|
+
"ρσ_op_neg(",
|
|
2589
|
+
"ρσ_op_pos(",
|
|
2590
|
+
"ρσ_op_invert(",
|
|
2591
|
+
],
|
|
2592
|
+
},
|
|
2593
|
+
|
|
2594
|
+
{
|
|
2595
|
+
name: "operator_overloading_augmented",
|
|
2596
|
+
description: "+= -= *= dispatch to __iadd__, __isub__, __imul__ (or fall back to binary)",
|
|
2597
|
+
src: [
|
|
2598
|
+
"# globals: assrt",
|
|
2599
|
+
"from __python__ import overload_operators",
|
|
2600
|
+
"class Counter:",
|
|
2601
|
+
" def __init__(self, n):",
|
|
2602
|
+
" self.n = n",
|
|
2603
|
+
" def __iadd__(self, other):",
|
|
2604
|
+
" self.n = self.n + other",
|
|
2605
|
+
" return self",
|
|
2606
|
+
" def __isub__(self, other):",
|
|
2607
|
+
" self.n = self.n - other",
|
|
2608
|
+
" return self",
|
|
2609
|
+
"c = Counter(10)",
|
|
2610
|
+
"c += 3",
|
|
2611
|
+
"assrt.equal(c.n, 13)",
|
|
2612
|
+
"c -= 4",
|
|
2613
|
+
"assrt.equal(c.n, 9)",
|
|
2614
|
+
// fallback: plain ints have no __iadd__, use native +=
|
|
2615
|
+
"x = 5",
|
|
2616
|
+
"x += 2",
|
|
2617
|
+
"assrt.equal(x, 7)",
|
|
2618
|
+
].join("\n"),
|
|
2619
|
+
js_checks: [
|
|
2620
|
+
"ρσ_op_iadd(",
|
|
2621
|
+
"ρσ_op_isub(",
|
|
2622
|
+
],
|
|
2623
|
+
},
|
|
2624
|
+
|
|
2625
|
+
{
|
|
2626
|
+
name: "operator_overloading_reflected",
|
|
2627
|
+
description: "__radd__ is called when left side has no __add__",
|
|
2628
|
+
src: [
|
|
2629
|
+
"# globals: assrt",
|
|
2630
|
+
"from __python__ import overload_operators",
|
|
2631
|
+
"class MyNum:",
|
|
2632
|
+
" def __init__(self, v):",
|
|
2633
|
+
" self.v = v",
|
|
2634
|
+
" def __radd__(self, other):",
|
|
2635
|
+
" return MyNum(other + self.v)",
|
|
2636
|
+
"n = MyNum(10)",
|
|
2637
|
+
"result = 5 + n",
|
|
2638
|
+
"assrt.equal(result.v, 15)",
|
|
2639
|
+
].join("\n"),
|
|
2640
|
+
js_checks: ["ρσ_op_add("],
|
|
2641
|
+
},
|
|
2642
|
+
|
|
2643
|
+
{
|
|
2644
|
+
name: "operator_overloading_fallback",
|
|
2645
|
+
description: "Without dunder methods, operators fall back to native JS behavior",
|
|
2646
|
+
src: [
|
|
2647
|
+
"# globals: assrt",
|
|
2648
|
+
"from __python__ import overload_operators",
|
|
2649
|
+
"assrt.equal(2 + 3, 5)",
|
|
2650
|
+
"assrt.equal(10 - 4, 6)",
|
|
2651
|
+
"assrt.equal(3 * 4, 12)",
|
|
2652
|
+
"assrt.equal(10 / 4, 2.5)",
|
|
2653
|
+
"assrt.equal(10 // 4, 2)",
|
|
2654
|
+
"assrt.equal(10 % 3, 1)",
|
|
2655
|
+
"assrt.equal(2 ** 8, 256)",
|
|
2656
|
+
"assrt.equal(6 & 3, 2)",
|
|
2657
|
+
"assrt.equal(6 | 3, 7)",
|
|
2658
|
+
"assrt.equal(6 ^ 3, 5)",
|
|
2659
|
+
"assrt.equal(1 << 4, 16)",
|
|
2660
|
+
"assrt.equal(32 >> 2, 8)",
|
|
2661
|
+
].join("\n"),
|
|
2662
|
+
js_checks: ["ρσ_op_add("],
|
|
2663
|
+
},
|
|
2664
|
+
|
|
2665
|
+
{
|
|
2666
|
+
name: "operator_overloading_no_flag",
|
|
2667
|
+
description: "Without overload_operators flag operators emit native JS (no helpers in user code)",
|
|
2668
|
+
src: [
|
|
2669
|
+
"# globals: assrt",
|
|
2670
|
+
"assrt.equal(2 + 3, 5)",
|
|
2671
|
+
"assrt.equal(3 * 4, 12)",
|
|
2672
|
+
].join("\n"),
|
|
2673
|
+
// Verify user-code expressions compile to native operators, not helper calls
|
|
2674
|
+
js_checks: [/assrt\.equal\(2 \+ 3, 5\)/, /assrt\.equal\(3 \* 4, 12\)/],
|
|
2675
|
+
js_not_checks: [],
|
|
2676
|
+
},
|
|
2677
|
+
|
|
2678
|
+
{
|
|
2679
|
+
name: "collections_counter_operator_syntax",
|
|
2680
|
+
description: "Counter +, -, |, & work via operator syntax with overload_operators",
|
|
2681
|
+
src: [
|
|
2682
|
+
"# globals: assrt",
|
|
2683
|
+
"from __python__ import overload_getitem, overload_operators",
|
|
2684
|
+
"from collections import Counter",
|
|
2685
|
+
"c1 = Counter('aab')",
|
|
2686
|
+
"c2 = Counter('ab')",
|
|
2687
|
+
"c3 = c1 + c2",
|
|
2688
|
+
"assrt.equal(c3['a'], 3)",
|
|
2689
|
+
"assrt.equal(c3['b'], 2)",
|
|
2690
|
+
"c4 = c1 - c2",
|
|
2691
|
+
"assrt.equal(c4['a'], 1)",
|
|
2692
|
+
"assrt.equal(c4.get('b', 0), 0)",
|
|
2693
|
+
"c5 = c1 | c2",
|
|
2694
|
+
"assrt.equal(c5['a'], 2)",
|
|
2695
|
+
"assrt.equal(c5['b'], 1)",
|
|
2696
|
+
"c6 = c1 & c2",
|
|
2697
|
+
"assrt.equal(c6['a'], 1)",
|
|
2698
|
+
"assrt.equal(c6['b'], 1)",
|
|
2699
|
+
].join("\n"),
|
|
2700
|
+
js_checks: ["ρσ_op_add(", "ρσ_op_sub(", "ρσ_op_or(", "ρσ_op_and("],
|
|
2701
|
+
},
|
|
2702
|
+
|
|
2703
|
+
// ── nested comprehensions ──────────────────────────────────────────────
|
|
2704
|
+
|
|
2705
|
+
{
|
|
2706
|
+
name: "nested_comprehension_list_flatten",
|
|
2707
|
+
description: "list comprehension with two for-clauses flattens a 2-D list",
|
|
2708
|
+
src: [
|
|
2709
|
+
"# globals: assrt",
|
|
2710
|
+
"matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]",
|
|
2711
|
+
"flat = [x for row in matrix for x in row]",
|
|
2712
|
+
"assrt.deepEqual(flat, [1, 2, 3, 4, 5, 6, 7, 8, 9])",
|
|
2713
|
+
].join("\n"),
|
|
2714
|
+
js_checks: [],
|
|
2715
|
+
},
|
|
2716
|
+
|
|
2717
|
+
{
|
|
2718
|
+
name: "nested_comprehension_list_filter",
|
|
2719
|
+
description: "list comprehension with two for-clauses and if-filter",
|
|
2720
|
+
src: [
|
|
2721
|
+
"# globals: assrt",
|
|
2722
|
+
"pairs = [x for i in range(4) for x in range(i) if x % 2 == 0]",
|
|
2723
|
+
"assrt.deepEqual(pairs, [0, 0, 0, 2])",
|
|
2724
|
+
].join("\n"),
|
|
2725
|
+
js_checks: [],
|
|
2726
|
+
},
|
|
2727
|
+
|
|
2728
|
+
{
|
|
2729
|
+
name: "nested_comprehension_outer_filter",
|
|
2730
|
+
description: "list comprehension with if-filter on the outer for-clause",
|
|
2731
|
+
src: [
|
|
2732
|
+
"# globals: assrt",
|
|
2733
|
+
"result = [x for row in [[1,2],[3,4],[5,6]] if row[0] > 2 for x in row]",
|
|
2734
|
+
"assrt.deepEqual(result, [3, 4, 5, 6])",
|
|
2735
|
+
].join("\n"),
|
|
2736
|
+
js_checks: [],
|
|
2737
|
+
},
|
|
2738
|
+
|
|
2739
|
+
{
|
|
2740
|
+
name: "nested_comprehension_three_levels",
|
|
2741
|
+
description: "list comprehension with three for-clauses (triple nested)",
|
|
2742
|
+
src: [
|
|
2743
|
+
"# globals: assrt",
|
|
2744
|
+
"result = [x for a in [[1,2],[3,4]] for b in [a, a] for x in b if x > 2]",
|
|
2745
|
+
"assrt.deepEqual(result, [3, 4, 3, 4])",
|
|
2746
|
+
].join("\n"),
|
|
2747
|
+
js_checks: [],
|
|
2748
|
+
},
|
|
2749
|
+
|
|
2750
|
+
{
|
|
2751
|
+
name: "nested_comprehension_set",
|
|
2752
|
+
description: "set comprehension with two for-clauses",
|
|
2753
|
+
src: [
|
|
2754
|
+
"# globals: assrt",
|
|
2755
|
+
"s = {x + y for x in range(3) for y in range(3)}",
|
|
2756
|
+
"assrt.equal(sorted(list(s)).toString(), [0,1,2,3,4].toString())",
|
|
2757
|
+
].join("\n"),
|
|
2758
|
+
js_checks: [],
|
|
2759
|
+
},
|
|
2760
|
+
|
|
2761
|
+
{
|
|
2762
|
+
name: "nested_comprehension_dict",
|
|
2763
|
+
description: "dict comprehension with two for-clauses",
|
|
2764
|
+
src: [
|
|
2765
|
+
"# globals: assrt",
|
|
2766
|
+
"d = {(i * 10 + j): i * j for i in range(3) for j in range(3) if i != j}",
|
|
2767
|
+
"assrt.equal(d[1], 0)",
|
|
2768
|
+
"assrt.equal(d[2], 0)",
|
|
2769
|
+
"assrt.equal(d[10], 0)",
|
|
2770
|
+
"assrt.equal(d[12], 2)",
|
|
2771
|
+
"assrt.equal(d[20], 0)",
|
|
2772
|
+
"assrt.equal(d[21], 2)",
|
|
2773
|
+
].join("\n"),
|
|
2774
|
+
js_checks: [],
|
|
2775
|
+
},
|
|
2776
|
+
|
|
2777
|
+
{
|
|
2778
|
+
name: "nested_comprehension_range_product",
|
|
2779
|
+
description: "list comprehension that builds coordinate pairs (equivalent to itertools.product)",
|
|
2780
|
+
src: [
|
|
2781
|
+
"# globals: assrt",
|
|
2782
|
+
"coords = [[i, j] for i in range(2) for j in range(2)]",
|
|
2783
|
+
"assrt.deepEqual(coords, [[0,0],[0,1],[1,0],[1,1]])",
|
|
2784
|
+
].join("\n"),
|
|
2785
|
+
js_checks: [],
|
|
2786
|
+
},
|
|
2787
|
+
|
|
2788
|
+
// ── python_flags compiler option ──────────────────────────────────────
|
|
2789
|
+
|
|
2790
|
+
{
|
|
2791
|
+
name: "python_flag_dict_literals_via_scoped_flags",
|
|
2792
|
+
description: "dict_literals flag via scoped_flags produces same JS as from __python__ import",
|
|
2793
|
+
run: function() {
|
|
2794
|
+
var src_inline = [
|
|
2795
|
+
"from __python__ import dict_literals",
|
|
2796
|
+
"x = {}",
|
|
2797
|
+
].join("\n");
|
|
2798
|
+
var src_flagged = "x = {}";
|
|
2799
|
+
var js_inline = compile(src_inline);
|
|
2800
|
+
var js_flagged = compile_with_flags(src_flagged, { dict_literals: true });
|
|
2801
|
+
// Both should produce the same dict() call pattern
|
|
2802
|
+
assert.ok(js_flagged.indexOf("dict_literal") !== -1 || js_flagged.indexOf("ρσ_dict") !== -1 || js_inline === js_flagged,
|
|
2803
|
+
"dict_literals flag should produce same output as inline import; got:\n" + js_flagged);
|
|
2804
|
+
},
|
|
2805
|
+
},
|
|
2806
|
+
|
|
2807
|
+
{
|
|
2808
|
+
name: "python_flag_overload_operators_via_scoped_flags",
|
|
2809
|
+
description: "overload_operators flag via scoped_flags produces same JS as from __python__ import",
|
|
2810
|
+
run: function() {
|
|
2811
|
+
var src_inline = "from __python__ import overload_operators\nx = a + b";
|
|
2812
|
+
var src_flagged = "x = a + b";
|
|
2813
|
+
var js_inline = compile(src_inline);
|
|
2814
|
+
var js_flagged = compile_with_flags(src_flagged, { overload_operators: true });
|
|
2815
|
+
assert.equal(js_inline.indexOf("ρσ_op_add") !== -1, true,
|
|
2816
|
+
"inline import: expected ρσ_op_add in: " + js_inline);
|
|
2817
|
+
assert.equal(js_flagged.indexOf("ρσ_op_add") !== -1, true,
|
|
2818
|
+
"scoped_flags: expected ρσ_op_add in: " + js_flagged);
|
|
2819
|
+
},
|
|
2820
|
+
},
|
|
2821
|
+
|
|
2822
|
+
{
|
|
2823
|
+
name: "python_flag_overload_getitem_via_scoped_flags",
|
|
2824
|
+
description: "overload_getitem flag via scoped_flags produces same JS as from __python__ import",
|
|
2825
|
+
run: function() {
|
|
2826
|
+
var src_inline = "from __python__ import overload_getitem\ny = obj[key]";
|
|
2827
|
+
var src_flagged = "y = obj[key]";
|
|
2828
|
+
var js_inline = compile(src_inline);
|
|
2829
|
+
var js_flagged = compile_with_flags(src_flagged, { overload_getitem: true });
|
|
2830
|
+
assert.equal(js_inline.indexOf("__getitem__") !== -1, true,
|
|
2831
|
+
"inline import: expected __getitem__ in: " + js_inline);
|
|
2832
|
+
assert.equal(js_flagged.indexOf("__getitem__") !== -1, true,
|
|
2833
|
+
"scoped_flags: expected __getitem__ in: " + js_flagged);
|
|
2834
|
+
},
|
|
2835
|
+
},
|
|
2836
|
+
|
|
2837
|
+
{
|
|
2838
|
+
name: "python_flag_bound_methods_via_scoped_flags",
|
|
2839
|
+
description: "bound_methods flag via scoped_flags produces same JS as from __python__ import",
|
|
2840
|
+
run: function() {
|
|
2841
|
+
var src_inline = [
|
|
2842
|
+
"from __python__ import bound_methods",
|
|
2843
|
+
"class Foo:",
|
|
2844
|
+
" def bar(self):",
|
|
2845
|
+
" return 1",
|
|
2846
|
+
].join("\n");
|
|
2847
|
+
var src_flagged = [
|
|
2848
|
+
"class Foo:",
|
|
2849
|
+
" def bar(self):",
|
|
2850
|
+
" return 1",
|
|
2851
|
+
].join("\n");
|
|
2852
|
+
var js_inline = compile(src_inline);
|
|
2853
|
+
var js_flagged = compile_with_flags(src_flagged, { bound_methods: true });
|
|
2854
|
+
assert.equal(js_inline.indexOf("bind") !== -1, true,
|
|
2855
|
+
"inline import: expected bind in: " + js_inline);
|
|
2856
|
+
assert.equal(js_flagged.indexOf("bind") !== -1, true,
|
|
2857
|
+
"scoped_flags: expected bind in: " + js_flagged);
|
|
2858
|
+
},
|
|
2859
|
+
},
|
|
2860
|
+
|
|
2861
|
+
{
|
|
2862
|
+
name: "python_flag_hash_literals_via_scoped_flags",
|
|
2863
|
+
description: "hash_literals flag via scoped_flags produces same JS as from __python__ import",
|
|
2864
|
+
run: function() {
|
|
2865
|
+
var src_inline = "from __python__ import hash_literals\ns = {1, 2, 3}";
|
|
2866
|
+
var src_flagged = "s = {1, 2, 3}";
|
|
2867
|
+
var js_inline = compile(src_inline);
|
|
2868
|
+
var js_flagged = compile_with_flags(src_flagged, { hash_literals: true });
|
|
2869
|
+
// Both should produce a set() call
|
|
2870
|
+
assert.equal(js_inline.indexOf("set") !== -1, true,
|
|
2871
|
+
"inline import: expected set() in: " + js_inline);
|
|
2872
|
+
assert.equal(js_flagged.indexOf("set") !== -1, true,
|
|
2873
|
+
"scoped_flags: expected set() in: " + js_flagged);
|
|
2874
|
+
},
|
|
2875
|
+
},
|
|
2876
|
+
|
|
2877
|
+
{
|
|
2878
|
+
name: "python_flag_all_flags_runtime",
|
|
2879
|
+
description: "overload_operators + overload_getitem work correctly at runtime via scoped_flags",
|
|
2880
|
+
src: [
|
|
2881
|
+
"# globals: assrt",
|
|
2882
|
+
"from __python__ import overload_operators, overload_getitem",
|
|
2883
|
+
"class Vec:",
|
|
2884
|
+
" def __init__(self, x):",
|
|
2885
|
+
" self.x = x",
|
|
2886
|
+
" def __add__(self, other):",
|
|
2887
|
+
" return Vec(self.x + other.x)",
|
|
2888
|
+
" def __getitem__(self, key):",
|
|
2889
|
+
" return self.x + key",
|
|
2890
|
+
"a = Vec(10)",
|
|
2891
|
+
"b = Vec(5)",
|
|
2892
|
+
"c = a + b",
|
|
2893
|
+
"assrt.equal(c.x, 15)",
|
|
2894
|
+
"assrt.equal(a[3], 13)",
|
|
2895
|
+
].join("\n"),
|
|
2896
|
+
js_checks: ["ρσ_op_add", "__getitem__"],
|
|
2897
|
+
},
|
|
2898
|
+
|
|
2899
|
+
];
|
|
2900
|
+
|
|
2901
|
+
// ── Runner ───────────────────────────────────────────────────────────────────
|
|
2902
|
+
|
|
2903
|
+
function run_tests(filter) {
|
|
2904
|
+
var tests = filter
|
|
2905
|
+
? TESTS.filter(function (t) { return t.name === filter; })
|
|
2906
|
+
: TESTS;
|
|
2907
|
+
|
|
2908
|
+
if (tests.length === 0) {
|
|
2909
|
+
console.error(colored("No test found: " + filter, "red"));
|
|
2910
|
+
process.exit(1);
|
|
2911
|
+
}
|
|
2912
|
+
|
|
2913
|
+
var failures = [];
|
|
2914
|
+
|
|
2915
|
+
tests.forEach(function (test) {
|
|
2916
|
+
|
|
2917
|
+
// Custom run function (for tests that need direct JS-level control)
|
|
2918
|
+
if (typeof test.run === "function") {
|
|
2919
|
+
try {
|
|
2920
|
+
test.run();
|
|
2921
|
+
} catch (e) {
|
|
2922
|
+
failures.push(test.name);
|
|
2923
|
+
var msg = e.stack || String(e);
|
|
2924
|
+
console.log(colored("FAIL " + test.name, "red") +
|
|
2925
|
+
" [run]\n " + msg + "\n");
|
|
2926
|
+
return;
|
|
2927
|
+
}
|
|
2928
|
+
console.log(colored("PASS " + test.name, "green") +
|
|
2929
|
+
" – " + test.description);
|
|
2930
|
+
return;
|
|
2931
|
+
}
|
|
2932
|
+
|
|
2933
|
+
var js;
|
|
2934
|
+
|
|
2935
|
+
// 1 – compile RapydScript → JS
|
|
2936
|
+
try {
|
|
2937
|
+
js = test.virtual_files ? compile_virtual(test.src, test.virtual_files) : compile(test.src);
|
|
2938
|
+
} catch (e) {
|
|
2939
|
+
failures.push(test.name);
|
|
2940
|
+
console.log(colored("FAIL " + test.name, "red") +
|
|
2941
|
+
" [compile error]\n " + e + "\n");
|
|
2942
|
+
return;
|
|
2943
|
+
}
|
|
2944
|
+
|
|
2945
|
+
// 2 – verify expected patterns appear in the JS output
|
|
2946
|
+
try {
|
|
2947
|
+
check_js_patterns(test.name, js, test.js_checks);
|
|
2948
|
+
// also check patterns that must NOT appear
|
|
2949
|
+
(test.js_not_checks || []).forEach(function (pat) {
|
|
2950
|
+
var found = (pat instanceof RegExp) ? pat.test(js) : js.indexOf(pat) !== -1;
|
|
2951
|
+
if (found) {
|
|
2952
|
+
var desc = (pat instanceof RegExp) ? String(pat) : JSON.stringify(pat);
|
|
2953
|
+
throw new Error("compiled JS unexpectedly contains " + desc + "\n in test: " + test.name);
|
|
2954
|
+
}
|
|
2955
|
+
});
|
|
2956
|
+
} catch (e) {
|
|
2957
|
+
failures.push(test.name);
|
|
2958
|
+
console.debug("Emitted JS:\n" + js + "\n");
|
|
2959
|
+
console.log(colored("FAIL " + test.name, "red") +
|
|
2960
|
+
" [JS pattern mismatch]\n " + e.message + "\n");
|
|
2961
|
+
return;
|
|
2962
|
+
}
|
|
2963
|
+
|
|
2964
|
+
// 3 – run the JS; assertions embedded in src catch wrong values
|
|
2965
|
+
try {
|
|
2966
|
+
run_js(js);
|
|
2967
|
+
} catch (e) {
|
|
2968
|
+
failures.push(test.name);
|
|
2969
|
+
var msg = e.stack || String(e);
|
|
2970
|
+
console.log(colored("FAIL " + test.name, "red") +
|
|
2971
|
+
" [runtime]\n " + msg + "\n");
|
|
2972
|
+
return;
|
|
2973
|
+
}
|
|
2974
|
+
|
|
2975
|
+
console.log(colored("PASS " + test.name, "green") +
|
|
2976
|
+
" – " + test.description);
|
|
2977
|
+
});
|
|
2978
|
+
|
|
2979
|
+
console.log("");
|
|
2980
|
+
if (failures.length) {
|
|
2981
|
+
console.log(colored(failures.length + " test(s) failed.", "red"));
|
|
2982
|
+
} else {
|
|
2983
|
+
console.log(colored("All " + tests.length + " unit tests passed!", "green"));
|
|
2984
|
+
}
|
|
2985
|
+
process.exit(failures.length ? 1 : 0);
|
|
2986
|
+
}
|
|
2987
|
+
|
|
2988
|
+
var filter = process.argv[2] || null;
|
|
2989
|
+
run_tests(filter);
|