@kernlang/python 4.0.1-canary.222.1.f06f1a51 → 4.0.1-canary.225.1.f5c8d5fe

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.
@@ -1,3 +1,113 @@
1
+ // Slice S7 — dual-sentinel nullish/equality substrate. `_KERN_UNDEFINED` is a
2
+ // FIRST-CLASS Python value distinct from `None`: `undefined` is nullish with
3
+ // `null` (loose `==` crossing TRUE, `??`/`?.` treat both as nullish) but is NOT
4
+ // strictly equal to `null` (`===` FALSE). The two equality helpers split the JS
5
+ // `==` (loose) and `===` (strict) operators that previously both lowered to
6
+ // Python `==`:
7
+ // _kern_is_nullish(x) -> x is None or x is _KERN_UNDEFINED
8
+ // _kern_strict_equal(a, b) -> SameValue-ish: a nullish operand is equal only
9
+ // to the SAME nullish identity (undefined===undefined,
10
+ // null===null, but undefined!==null); otherwise `==`.
11
+ // _kern_loose_equal(a, b) -> both-nullish crossing is TRUE; else strict.
12
+ // The sentinel is matched by IDENTITY (`is`), never by value, so the undefined
13
+ // `__bool__ = False` override never leaks into the equality semantics. The block
14
+ // self-defines `_KERN_UNDEFINED` via the same idempotent `try/except NameError`
15
+ // guard the fmt/array/js helper blocks use, so it stands alone if registered
16
+ // without them.
17
+ export const KERN_NULLISH_HELPER_PY = [
18
+ 'try:',
19
+ ' _KERN_UNDEFINED',
20
+ 'except NameError:',
21
+ ' class _KernUndefined:',
22
+ ' def __bool__(self): return False',
23
+ " def __repr__(self): return 'undefined'",
24
+ " def __str__(self): return 'undefined'",
25
+ ' _KERN_UNDEFINED = _KernUndefined()',
26
+ '',
27
+ 'def _kern_is_nullish(x):',
28
+ ' return x is None or x is _KERN_UNDEFINED',
29
+ '',
30
+ 'def _kern_strict_equal(a, b):',
31
+ ' if a is _KERN_UNDEFINED or b is _KERN_UNDEFINED:',
32
+ ' return a is b',
33
+ ' if a is None or b is None:',
34
+ ' return a is b',
35
+ // Python `bool` subclasses `int`, so `0 == False` / `1 == True` are True — but
36
+ // JS `0 === false` / `1 === true` are FALSE. Reject a bool-vs-non-bool pair
37
+ // before the value compare so the numeric/boolean type distinction survives.
38
+ ' if (type(a) is bool) != (type(b) is bool):',
39
+ ' return False',
40
+ // Containers must recurse ELEMENT-WISE through `_kern_strict_equal`, not Python
41
+ // `==`: Python list/dict `==` compares elements with `==`, which re-leaks the
42
+ // `0 == False` / `1 == True` bool⊂int conflation one level down (`[0] ==
43
+ // [False]` is True). The KERN core runtime compares structurally with
44
+ // kind-discrimination at EVERY level, so the Python target must too or
45
+ // `[0] === [false]` diverges (True on Python vs False in core). Lists/tuples
46
+ // (array-kind) compare by length + positional recursion; dicts (record-kind)
47
+ // by key set + per-key recursion. Strings stay on `==` (no element kinds).
48
+ ' if isinstance(a, (list, tuple)) and isinstance(b, (list, tuple)):',
49
+ ' if len(a) != len(b):',
50
+ ' return False',
51
+ ' return all(_kern_strict_equal(__k_x, __k_y) for __k_x, __k_y in zip(a, b))',
52
+ ' if isinstance(a, dict) and isinstance(b, dict):',
53
+ ' if a.keys() != b.keys():',
54
+ ' return False',
55
+ ' return all(_kern_strict_equal(a[__k_k], b[__k_k]) for __k_k in a)',
56
+ // A list-vs-dict (or container-vs-scalar) mismatch is unequal — Python `==`
57
+ // already returns False there, but make it explicit so the recursion above is
58
+ // the ONLY container path and a scalar `==` never sees a container pair.
59
+ ' if isinstance(a, (list, tuple, dict)) or isinstance(b, (list, tuple, dict)):',
60
+ ' return False',
61
+ ' return a == b',
62
+ '',
63
+ 'def _kern_loose_equal(a, b):',
64
+ ' if _kern_is_nullish(a) and _kern_is_nullish(b):',
65
+ ' return True',
66
+ ' return _kern_strict_equal(a, b)',
67
+ ].join('\n');
68
+ // Slice S7 — sentinel-aware `Json.stringify` / `JSON.stringify` shim. Raw
69
+ // `json.dumps` cannot model JS `JSON.stringify`'s undefined handling, so the
70
+ // Python target routes through `_kern_json_stringify`:
71
+ // - top-level `_KERN_UNDEFINED` → returns the sentinel itself (host-observed
72
+ // `undefined`), matching JS `JSON.stringify(undefined) === undefined`.
73
+ // - object property whose value is the sentinel → key OMITTED.
74
+ // - array element that is the sentinel → JSON `null`.
75
+ // - rules apply recursively into nested objects/arrays.
76
+ // - `None` stays JSON `null`; compact separators + ensure_ascii=False
77
+ // preserve the existing byte-for-byte parity for sentinel-free inputs.
78
+ // `_kern_json_prepare` returns a sentinel-free structure that `json.dumps` can
79
+ // serialize; the top-level sentinel is short-circuited before dumps runs. The
80
+ // block self-defines `_KERN_UNDEFINED` via the idempotent guard so it stands
81
+ // alone. It references `__k_json`, which the emitter supplies via the stdlib
82
+ // table's `requires.py: 'json'` → `import json as __k_json` (kept, NOT
83
+ // self-imported here, so a body that ALSO calls `Json.parse` shares one import).
84
+ export const KERN_JSON_STRINGIFY_SHIM_PY = [
85
+ 'try:',
86
+ ' _KERN_UNDEFINED',
87
+ 'except NameError:',
88
+ ' class _KernUndefined:',
89
+ ' def __bool__(self): return False',
90
+ " def __repr__(self): return 'undefined'",
91
+ " def __str__(self): return 'undefined'",
92
+ ' _KERN_UNDEFINED = _KernUndefined()',
93
+ '',
94
+ 'def _kern_json_prepare(__k_v):',
95
+ ' if isinstance(__k_v, dict):',
96
+ ' __k_out = {}',
97
+ ' for __k_k, __k_val in __k_v.items():',
98
+ ' if __k_val is _KERN_UNDEFINED:',
99
+ ' continue',
100
+ ' __k_out[__k_k] = _kern_json_prepare(__k_val)',
101
+ ' return __k_out',
102
+ ' if isinstance(__k_v, (list, tuple)):',
103
+ ' return [None if __k_e is _KERN_UNDEFINED else _kern_json_prepare(__k_e) for __k_e in __k_v]',
104
+ ' return __k_v',
105
+ '',
106
+ 'def _kern_json_stringify(__k_v):',
107
+ ' if __k_v is _KERN_UNDEFINED:',
108
+ ' return _KERN_UNDEFINED',
109
+ ' return __k_json.dumps(_kern_json_prepare(__k_v), separators=(",", ":"), ensure_ascii=False)',
110
+ ].join('\n');
1
111
  export const KERN_PAIR_HELPERS_PY = [
2
112
  'def _kern_pairs(__k_v):',
3
113
  ' return __k_v.items() if hasattr(__k_v, "items") else iter(__k_v)',
@@ -10,7 +120,62 @@ export const KERN_PAIR_HELPERS_PY = [
10
120
  ' for __k_item in _kern_pairs(__k_v):',
11
121
  ' yield __k_item',
12
122
  ].join('\n');
13
- export const KERN_FMT_HELPER_PY = [
123
+ /** DECIMAL Slice 1 — KERN-owned canonical Decimal stringifier (Python leg).
124
+ *
125
+ * KERN's parity invariant requires `str(decimalValue)` to render byte-identically
126
+ * on both legs. Python's stdlib `decimal` PRESERVES scale (`Decimal("1.5") +
127
+ * Decimal("2.5")` renders `"4.0"`, and a literal `Decimal("300")` renders `"300"`
128
+ * but `.normalize()` → `"3E+2"`), whereas decimal.js has NO scale concept and
129
+ * renders the significance-free form (`"4"`, `"300"`). To make the Python leg
130
+ * match decimal.js's `toString()` byte-for-byte, this helper re-renders a Python
131
+ * Decimal from its (sign, digits, exponent) tuple under decimal.js's exact
132
+ * notation rule (precision 28, toExpNeg=-7, toExpPos=21):
133
+ * - drop trailing-zero scale (normalize significance)
134
+ * - PLAIN decimal notation iff the adjusted exponent `adj` satisfies
135
+ * `-7 < adj < 21`; else LOWERCASE-`e`, signed sci notation (`1e-8`, `1e+21`)
136
+ * - `-0` / `0.0` / `+0` all render `"0"` (decimal.js drops the sign of zero)
137
+ *
138
+ * Validated byte-for-byte against decimal.js 10.6.0 across 225/226 cross-cases
139
+ * (literals + pairwise sums); the single outlier is a >28-significant-digit input,
140
+ * which is OUTSIDE KERN's certified 28-digit precision envelope. Both engines are
141
+ * configured to precision 28 / ROUND_HALF_EVEN so ARITHMETIC values agree before
142
+ * rendering; this helper only canonicalizes the RENDERING of an agreed value.
143
+ *
144
+ * DECIMAL Slice 2 (Finding B): this block is also EMBEDDED into KERN_FMT_HELPER_PY
145
+ * (below) so `_kern_fmt`'s Decimal branch has `_KernDecimal` + `_kern_decimal_str`
146
+ * in scope intrinsically — no separate registration to miss. */
147
+ export const KERN_DECIMAL_STR_HELPER_PY = [
148
+ 'from decimal import Decimal as _KernDecimal',
149
+ '',
150
+ 'def _kern_decimal_str(__k_d):',
151
+ ' if __k_d.is_zero():',
152
+ ' return "0"',
153
+ ' __k_n = __k_d.normalize()',
154
+ ' __k_sign, __k_digits, __k_exp = __k_n.as_tuple()',
155
+ ' __k_adj = __k_exp + len(__k_digits) - 1',
156
+ ' __k_ds = "".join(map(str, __k_digits))',
157
+ ' __k_neg = "-" if __k_sign else ""',
158
+ ' if -7 < __k_adj < 21:',
159
+ ' if __k_exp >= 0:',
160
+ ' __k_body = __k_ds + ("0" * __k_exp)',
161
+ ' else:',
162
+ ' __k_point = len(__k_ds) + __k_exp',
163
+ ' if __k_point <= 0:',
164
+ ' __k_body = "0." + ("0" * (-__k_point)) + __k_ds',
165
+ ' else:',
166
+ ' __k_body = __k_ds[:__k_point] + "." + __k_ds[__k_point:]',
167
+ ' return __k_neg + __k_body',
168
+ ' __k_mant = __k_ds[0] + ("." + __k_ds[1:] if len(__k_ds) > 1 else "")',
169
+ ' __k_esign = "+" if __k_adj >= 0 else "-"',
170
+ ' return f"{__k_neg}{__k_mant}e{__k_esign}{abs(__k_adj)}"',
171
+ ].join('\n');
172
+ // DECIMAL Slice 2 (Finding B) — `_kern_fmt` EMBEDS the canonical Decimal
173
+ // stringifier so a Decimal renders significance-free (decimal.js form) instead of
174
+ // Python's scale-preserving `str()`. Composing (not co-registering) makes the
175
+ // dependency intrinsic to the single `_kern_fmt` helper block: wherever
176
+ // KERN_FMT_HELPER_PY lands — the 7 IR emit sites OR the route ground prelude —
177
+ // `_KernDecimal`/`_kern_decimal_str` come with it, so it can never be mis-registered.
178
+ const KERN_FMT_BODY_PY = [
14
179
  'try:',
15
180
  ' _KERN_UNDEFINED',
16
181
  'except NameError:',
@@ -33,6 +198,17 @@ export const KERN_FMT_HELPER_PY = [
33
198
  " return 'true' if __k_v else 'false'",
34
199
  ' if isinstance(__k_v, str):',
35
200
  ' return __k_v',
201
+ // DECIMAL Slice 2 (Finding B) — a Decimal must render through KERN's canonical
202
+ // stringifier, NOT Python's scale-preserving `str()`. `str(Decimal("1.5") +
203
+ // Decimal("2.5"))` is "4.0"/"1E+2" (scale kept) → DIVERGES from decimal.js's
204
+ // significance-free "4"/"100". `_kern_decimal_str` re-renders the agreed value in
205
+ // decimal.js's exact form, so String()/template/concat of a Decimal is byte-equal
206
+ // across targets. Placed BEFORE the generic numeric branch. `_KernDecimal` +
207
+ // `_kern_decimal_str` are guaranteed in scope because KERN_FMT_HELPER_PY EMBEDS
208
+ // KERN_DECIMAL_STR_HELPER_PY (composed below) — so the dependency is intrinsic to
209
+ // the `_kern_fmt` block and can never be mis-registered at any emit/prelude site.
210
+ ' if isinstance(__k_v, _KernDecimal):',
211
+ ' return _kern_decimal_str(__k_v)',
36
212
  ' if isinstance(__k_v, float) and __k_v != __k_v:',
37
213
  " return 'NaN'",
38
214
  // JS String(Infinity) is "Infinity"/"-Infinity"; Python str(inf) is "inf".
@@ -76,6 +252,14 @@ export const KERN_FMT_HELPER_PY = [
76
252
  ' except TypeError:',
77
253
  ' return _kern_fmt(left) + _kern_fmt(right)',
78
254
  ].join('\n');
255
+ /** DECIMAL Slice 2 (Finding B) — the production `_kern_fmt` helper block, with the
256
+ * canonical Decimal stringifier EMBEDDED so `_kern_fmt(decimalValue)` renders the
257
+ * significance-free (decimal.js-matching) form. The embed keeps `_kern_decimal_str`
258
+ * single-sourced (KERN_DECIMAL_STR_HELPER_PY) while making the dependency intrinsic
259
+ * to this one block — so the 7 IR emit sites and the route ground prelude that
260
+ * register KERN_FMT_HELPER_PY automatically carry it, with no co-registration to
261
+ * miss. The decimal `import` rides along at the top of the embedded block. */
262
+ export const KERN_FMT_HELPER_PY = [KERN_DECIMAL_STR_HELPER_PY, KERN_FMT_BODY_PY].join('\n\n');
79
263
  export const KERN_I32_HELPER_PY = [
80
264
  'import math',
81
265
  'def _i32(x):',
@@ -92,6 +276,126 @@ export const KERN_I32_HELPER_PY = [
92
276
  ' return 0',
93
277
  ' return ((val & 0xFFFFFFFF) ^ 0x80000000) - 0x80000000',
94
278
  ].join('\n');
279
+ /**
280
+ * ToNumericPrimitive substrate (slice 0.75) — Python twin of the
281
+ * `@kernlang/core` `to-numeric` decision kernel.
282
+ *
283
+ * Single Python-side source of numeric coercion truth for the FROZEN primitive
284
+ * domain (numbers, ECMA numeric strings, booleans, null, undefined sentinel).
285
+ * The emitted Python encodes the ECMA-262 StringNumericLiteral grammar
286
+ * EXPLICITLY rather than delegating to `float(...)`, because `float()` diverges
287
+ * from JS `Number()` on three load-bearing cases: it accepts numeric separators
288
+ * (`float('1_000') == 1000.0`), case-insensitive infinity/NaN words
289
+ * (`float('infinity')`, `float('nan')`), and raises on `0x`/`0b`/`0o` prefixes.
290
+ *
291
+ * Return-type contract (tribunal amendments 1 & 2):
292
+ * - `_kern_to_number(x)` -> Python `float` for EVERY numeric output
293
+ * (bool/null/hex/binary/octal inputs included); `-0.0` sign survives; NaN
294
+ * and ±inf preserved.
295
+ * - `_kern_string_to_number(text)` -> `float` (NaN for any non-grammar string).
296
+ * - `_kern_number_to_int32(n)` -> `int` (signed 32-bit, shift-mask domain).
297
+ * - `_kern_number_to_uint32(n)` -> `int` (unsigned 32-bit).
298
+ * - `_kern_to_int32(x)` -> `int` = int32(to_number(x)).
299
+ * - `_kern_to_uint32(x)` -> `int` = uint32(to_number(x)).
300
+ * - `_kern_to_integer_or_infinity(x)` -> `float` (int-valued, or ±inf).
301
+ *
302
+ * Fail-closed: objects/arrays/functions/symbols/custom-valueOf RAISE
303
+ * `_KernNumericCoercionError` (the caller decides) — full ToPrimitive deferred.
304
+ *
305
+ * Single-source-of-int32 note: this block is the coercion-correct int32 path
306
+ * going forward. The legacy `_i32` (KERN_I32_HELPER_PY) embeds its OWN
307
+ * float()-based coercion and is still wired to production
308
+ * (codegen-body-python.ts ToInt32 lowering). Slice 0.75 is a PURE ADDITION —
309
+ * nothing is rerouted here, so both coexist; the future routing slice retires
310
+ * `_i32` in favor of `_kern_to_int32` once the fallback count hits zero.
311
+ *
312
+ * The ECMA whitespace string below is the exact `StrWhiteSpace` set
313
+ * (`WhiteSpace` + `LineTerminator`) JS trims from a StringNumericLiteral, kept
314
+ * byte-aligned with `ECMA_STR_WHITESPACE` in the TS kernel.
315
+ */
316
+ export const KERN_TO_NUMBER_HELPER_PY = [
317
+ 'import math',
318
+ 'import re',
319
+ '',
320
+ 'try:',
321
+ ' _KERN_UNDEFINED',
322
+ 'except NameError:',
323
+ ' class _KernUndefined:',
324
+ ' def __bool__(self): return False',
325
+ " def __repr__(self): return 'undefined'",
326
+ " def __str__(self): return 'undefined'",
327
+ ' _KERN_UNDEFINED = _KernUndefined()',
328
+ '',
329
+ 'class _KernNumericCoercionError(TypeError):',
330
+ ' pass',
331
+ '',
332
+ // ECMA StrWhiteSpace = WhiteSpace + LineTerminator. Encoded as \u escapes so
333
+ // the emitted source stays ASCII; Python materializes the real code points.
334
+ "_KERN_ECMA_WS = '\\t\\n\\x0b\\x0c\\r \\xa0\\u1680\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u2028\\u2029\\u202f\\u205f\\u3000\\ufeff'",
335
+ "_KERN_DECIMAL_RE = re.compile(r'^[+-]?(?:(?:[0-9]+(?:\\.[0-9]*)?|\\.[0-9]+)(?:[eE][+-]?[0-9]+)?|[0-9]+)$')",
336
+ "_KERN_HEX_RE = re.compile(r'^0[xX][0-9a-fA-F]+$')",
337
+ "_KERN_BIN_RE = re.compile(r'^0[bB][01]+$')",
338
+ "_KERN_OCT_RE = re.compile(r'^0[oO][0-7]+$')",
339
+ '',
340
+ 'def _kern_string_to_number(text):',
341
+ ' s = text.strip(_KERN_ECMA_WS)',
342
+ " if s == '':",
343
+ ' return 0.0',
344
+ " if s == 'Infinity' or s == '+Infinity':",
345
+ " return float('inf')",
346
+ " if s == '-Infinity':",
347
+ " return float('-inf')",
348
+ ' if _KERN_HEX_RE.match(s):',
349
+ ' return float(int(s[2:], 16))',
350
+ ' if _KERN_BIN_RE.match(s):',
351
+ ' return float(int(s[2:], 2))',
352
+ ' if _KERN_OCT_RE.match(s):',
353
+ ' return float(int(s[2:], 8))',
354
+ ' if not _KERN_DECIMAL_RE.match(s):',
355
+ " return float('nan')",
356
+ ' try:',
357
+ ' return float(s)',
358
+ ' except ValueError:',
359
+ " return float('nan')",
360
+ '',
361
+ 'def _kern_to_number(x):',
362
+ ' if x is _KERN_UNDEFINED:',
363
+ " return float('nan')",
364
+ ' if x is None:',
365
+ ' return 0.0',
366
+ ' if isinstance(x, bool):',
367
+ ' return 1.0 if x else 0.0',
368
+ ' if isinstance(x, (int, float)):',
369
+ ' return float(x)',
370
+ ' if isinstance(x, str):',
371
+ ' return _kern_string_to_number(x)',
372
+ " raise _KernNumericCoercionError('KERN ToNumber supports only primitive values (slice-0.75); full ToPrimitive deferred')",
373
+ '',
374
+ 'def _kern_number_to_int32(n):',
375
+ " if n != n or n == 0 or n in (float('inf'), float('-inf')):",
376
+ ' return 0',
377
+ ' i = math.trunc(n)',
378
+ ' return ((i & 0xFFFFFFFF) ^ 0x80000000) - 0x80000000',
379
+ '',
380
+ 'def _kern_number_to_uint32(n):',
381
+ " if n != n or n == 0 or n in (float('inf'), float('-inf')):",
382
+ ' return 0',
383
+ ' return math.trunc(n) & 0xFFFFFFFF',
384
+ '',
385
+ 'def _kern_to_int32(x):',
386
+ ' return _kern_number_to_int32(_kern_to_number(x))',
387
+ '',
388
+ 'def _kern_to_uint32(x):',
389
+ ' return _kern_number_to_uint32(_kern_to_number(x))',
390
+ '',
391
+ 'def _kern_to_integer_or_infinity(x):',
392
+ ' n = _kern_to_number(x)',
393
+ ' if n != n:',
394
+ ' return 0.0',
395
+ " if n in (float('inf'), float('-inf')):",
396
+ ' return n',
397
+ ' return float(math.trunc(n))',
398
+ ].join('\n');
95
399
  export const KERN_TMOD_HELPER_PY = [
96
400
  'import math',
97
401
  'def _tmod(a, b):',
@@ -109,11 +413,36 @@ export const KERN_TMOD_HELPER_PY = [
109
413
  ' return fa - math.trunc(fa / fb) * fb',
110
414
  ].join('\n');
111
415
  export const KERN_JS_HELPER_PY = [
112
- 'def js_truthy(x):',
113
- ' if x is None or x is False: return False',
114
- ' if isinstance(x, (int, float)) and x == 0: return False',
115
- ' if isinstance(x, str) and x == "": return False',
416
+ // Slice S4 — ToBoolean / KERN truthiness substrate. `js_truthy` is an EXPLICIT
417
+ // falsy-set predicate over the KERN value domain: falsy iff x is the undefined
418
+ // sentinel, None, a boolean False, numeric zero (+0/-0.0/0j), NaN, or "".
419
+ // Everything else is truthy INCLUDING [], {}, callables, class instances, and
420
+ // user objects whose Python __bool__/__len__ are falsy. It NEVER delegates to
421
+ // bool(x), len(x), x.__bool__(), x.__len__(), or a ToNumber string conversion:
422
+ // "0", "false", " " are all truthy; only "" is falsy. `bool` is checked BEFORE
423
+ // the numeric branch because Python `bool` subclasses `int`. The undefined
424
+ // sentinel is matched by IDENTITY (`is _KERN_UNDEFINED`) — its __bool__ = False
425
+ // override is for bare-truthiness positions only and must NOT be generalized to
426
+ // user objects. `_kern_truthy` is the canonical name; `js_truthy` is the alias
427
+ // the existing array-predicate lowerings (filter/some/every/find-family) call.
428
+ 'try:',
429
+ ' _KERN_UNDEFINED',
430
+ 'except NameError:',
431
+ ' class _KernUndefined:',
432
+ ' def __bool__(self): return False',
433
+ " def __repr__(self): return 'undefined'",
434
+ " def __str__(self): return 'undefined'",
435
+ ' _KERN_UNDEFINED = _KernUndefined()',
436
+ 'def _kern_truthy(x):',
437
+ ' if x is _KERN_UNDEFINED or x is None or x is False: return False',
438
+ ' if isinstance(x, bool): return x',
439
+ // `x == x` is False only for NaN, so `x != 0 and x == x` rejects both numeric
440
+ // zero (incl. -0.0 and 0j) and NaN without delegating to math.isnan / bool().
441
+ ' if isinstance(x, (int, float, complex)): return x != 0 and x == x',
442
+ ' if isinstance(x, str): return len(x) > 0',
116
443
  ' return True',
444
+ 'def js_truthy(x):',
445
+ ' return _kern_truthy(x)',
117
446
  'def js_equals(a, b):',
118
447
  ' return a == b',
119
448
  ].join('\n');
@@ -169,6 +498,19 @@ export const KERN_JS_ARRAY_HELPERS_PY = [
169
498
  ' return __k_list',
170
499
  ].join('\n');
171
500
  export const KERN_JS_OBJECT_HELPERS_PY = [
501
+ // Slice S7 — `Object.keys/values/entries` must throw TypeError parity for BOTH
502
+ // null and the undefined sentinel (JS `Object.keys(undefined)` throws). The
503
+ // sentinel is defined here via the idempotent guard so the identity check is
504
+ // safe even when this block is registered without the fmt/nullish blocks.
505
+ 'try:',
506
+ ' _KERN_UNDEFINED',
507
+ 'except NameError:',
508
+ ' class _KernUndefined:',
509
+ ' def __bool__(self): return False',
510
+ " def __repr__(self): return 'undefined'",
511
+ " def __str__(self): return 'undefined'",
512
+ ' _KERN_UNDEFINED = _KernUndefined()',
513
+ '',
172
514
  'def _kern_js_is_array_index(__k_key):',
173
515
  ' __k_s = str(__k_key)',
174
516
  ' if not __k_s.isdigit(): return False',
@@ -177,7 +519,7 @@ export const KERN_JS_OBJECT_HELPERS_PY = [
177
519
  ' return 0 <= __k_n < 4294967295 and __k_s == str(__k_n)',
178
520
  '',
179
521
  'def _kern_js_property_items(__k_obj):',
180
- ' if __k_obj is None:',
522
+ ' if __k_obj is None or __k_obj is _KERN_UNDEFINED:',
181
523
  ' raise TypeError("Cannot convert undefined or null to object")',
182
524
  ' if hasattr(__k_obj, "items"):',
183
525
  ' __k_raw = list(__k_obj.items())',
@@ -204,6 +546,196 @@ export const KERN_JS_OBJECT_HELPERS_PY = [
204
546
  '',
205
547
  'def _kern_js_object_entries(__k_obj):',
206
548
  ' return [[__k_k, __k_v] for __k_k, __k_v in _kern_js_property_items(__k_obj)]',
549
+ '',
550
+ 'def _kern_js_object_assign(__k_target, *__k_sources):',
551
+ ' if __k_target is None or __k_target is _KERN_UNDEFINED:',
552
+ ' raise TypeError("Cannot convert undefined or null to object")',
553
+ ' if hasattr(__k_target, "update"):',
554
+ ' __k_out = __k_target',
555
+ ' elif isinstance(__k_target, list):',
556
+ ' __k_out = __k_target',
557
+ ' elif isinstance(__k_target, str):',
558
+ ' __k_out = {str(__k_i): __k_ch for __k_i, __k_ch in enumerate(__k_target)}',
559
+ ' else:',
560
+ ' __k_out = {}',
561
+ ' for __k_src in __k_sources:',
562
+ ' if __k_src is None or __k_src is _KERN_UNDEFINED:',
563
+ ' continue',
564
+ ' for __k_k, __k_v in _kern_js_property_items(__k_src):',
565
+ ' if isinstance(__k_out, list):',
566
+ ' if not _kern_js_is_array_index(__k_k):',
567
+ ' raise TypeError("Object.assign cannot attach non-index properties to Python list target")',
568
+ ' __k_i = int(__k_k)',
569
+ ' while len(__k_out) <= __k_i:',
570
+ ' __k_out.append(_KERN_UNDEFINED)',
571
+ ' __k_out[__k_i] = __k_v',
572
+ ' else:',
573
+ ' __k_out[__k_k] = __k_v',
574
+ ' return __k_out',
575
+ ].join('\n');
576
+ // `Number.isInteger` / `Number.isSafeInteger` do NO coercion — a non-number
577
+ // argument (incl. a boolean) is ALWAYS false (`Number.isInteger("5")` → false,
578
+ // `Number.isInteger(true)` → false). Python's `bool` subclasses `int`, so the
579
+ // integer test MUST reject `bool` explicitly or `Number.isInteger(true)` wrongly
580
+ // returns True. NaN/±Infinity are non-integers. `isSafeInteger` additionally
581
+ // requires `abs(x) <= 2**53 - 1`.
582
+ export const KERN_JS_NUMBER_HELPERS_PY = [
583
+ 'def _kern_number_is_integer(__k_x):',
584
+ ' if isinstance(__k_x, bool):',
585
+ ' return False',
586
+ ' if isinstance(__k_x, int):',
587
+ ' return True',
588
+ ' if isinstance(__k_x, float):',
589
+ ' return __k_x == __k_x and __k_x not in (float("inf"), float("-inf")) and __k_x.is_integer()',
590
+ ' return False',
591
+ '',
592
+ 'def _kern_number_is_safe_integer(__k_x):',
593
+ ' if not _kern_number_is_integer(__k_x):',
594
+ ' return False',
595
+ ' return abs(__k_x) <= 9007199254740991',
596
+ ].join('\n');
597
+ export const KERN_JS_MATH_HELPERS_PY = [
598
+ 'import math',
599
+ '',
600
+ 'def _kern_math_is_negative_zero(__k_n):',
601
+ ' return __k_n == 0 and math.copysign(1.0, __k_n) < 0',
602
+ '',
603
+ 'def _kern_math_nan():',
604
+ ' return float("nan")',
605
+ '',
606
+ 'def _kern_math_round(__k_x):',
607
+ ' __k_n = _kern_to_number(__k_x)',
608
+ ' if __k_n != __k_n or __k_n in (float("inf"), float("-inf")) or __k_n == 0:',
609
+ ' return __k_n',
610
+ ' __k_floor = math.floor(__k_n)',
611
+ ' __k_r = __k_floor + (1 if __k_n - __k_floor >= 0.5 else 0)',
612
+ ' if __k_r == 0 and __k_n < 0:',
613
+ ' return -0.0',
614
+ ' return __k_r',
615
+ '',
616
+ 'def _kern_math_floor(__k_x):',
617
+ ' __k_n = _kern_to_number(__k_x)',
618
+ ' if __k_n != __k_n or __k_n in (float("inf"), float("-inf")) or __k_n == 0:',
619
+ ' return __k_n',
620
+ ' return math.floor(__k_n)',
621
+ '',
622
+ 'def _kern_math_trunc(__k_x):',
623
+ ' __k_n = _kern_to_number(__k_x)',
624
+ ' if __k_n != __k_n or __k_n in (float("inf"), float("-inf")) or __k_n == 0:',
625
+ ' return __k_n',
626
+ ' __k_r = math.trunc(__k_n)',
627
+ ' if __k_r == 0 and __k_n < 0:',
628
+ ' return -0.0',
629
+ ' return __k_r',
630
+ '',
631
+ 'def _kern_math_sign(__k_x):',
632
+ ' __k_n = _kern_to_number(__k_x)',
633
+ ' if __k_n != __k_n or __k_n == 0:',
634
+ ' return __k_n',
635
+ ' return 1 if __k_n > 0 else -1',
636
+ '',
637
+ 'def _kern_math_max(*__k_args):',
638
+ ' if len(__k_args) == 0:',
639
+ ' return float("-inf")',
640
+ ' __k_best = float("-inf")',
641
+ ' for __k_arg in __k_args:',
642
+ ' __k_n = _kern_to_number(__k_arg)',
643
+ ' if __k_n != __k_n:',
644
+ ' return _kern_math_nan()',
645
+ ' if __k_n > __k_best or (__k_n == 0 and __k_best == 0 and not _kern_math_is_negative_zero(__k_n) and _kern_math_is_negative_zero(__k_best)):',
646
+ ' __k_best = __k_n',
647
+ ' return __k_best',
648
+ '',
649
+ 'def _kern_math_min(*__k_args):',
650
+ ' if len(__k_args) == 0:',
651
+ ' return float("inf")',
652
+ ' __k_best = float("inf")',
653
+ ' for __k_arg in __k_args:',
654
+ ' __k_n = _kern_to_number(__k_arg)',
655
+ ' if __k_n != __k_n:',
656
+ ' return _kern_math_nan()',
657
+ ' if __k_n < __k_best or (__k_n == 0 and __k_best == 0 and _kern_math_is_negative_zero(__k_n) and not _kern_math_is_negative_zero(__k_best)):',
658
+ ' __k_best = __k_n',
659
+ ' return __k_best',
660
+ ].join('\n');
661
+ export const KERN_JS_ARRAY_FROM_HELPER_PY = [
662
+ 'import inspect',
663
+ '',
664
+ 'try:',
665
+ ' RangeError',
666
+ 'except NameError:',
667
+ ' class RangeError(ValueError):',
668
+ ' pass',
669
+ '',
670
+ 'try:',
671
+ ' _KERN_UNDEFINED',
672
+ 'except NameError:',
673
+ ' class _KernUndefined:',
674
+ ' def __bool__(self): return False',
675
+ " def __repr__(self): return 'undefined'",
676
+ " def __str__(self): return 'undefined'",
677
+ ' _KERN_UNDEFINED = _KernUndefined()',
678
+ '',
679
+ 'def _kern_array_like_get(__k_source, __k_index):',
680
+ ' if isinstance(__k_source, dict):',
681
+ ' if __k_index in __k_source:',
682
+ ' return __k_source[__k_index]',
683
+ ' __k_key = str(__k_index)',
684
+ ' return __k_source[__k_key] if __k_key in __k_source else _KERN_UNDEFINED',
685
+ ' try:',
686
+ ' return __k_source[__k_index]',
687
+ ' except Exception:',
688
+ ' return _KERN_UNDEFINED',
689
+ '',
690
+ 'def _kern_array_like_length(__k_source):',
691
+ ' if isinstance(__k_source, dict):',
692
+ ' __k_len = __k_source.get("length", 0)',
693
+ ' else:',
694
+ ' __k_len = getattr(__k_source, "length", None)',
695
+ ' if __k_len is None:',
696
+ ' return None',
697
+ ' try:',
698
+ ' __k_num = _kern_to_number(__k_len)',
699
+ ' except Exception:',
700
+ ' return 0',
701
+ ' if __k_num != __k_num or __k_num <= 0:',
702
+ ' return 0',
703
+ ' if __k_num == float("inf"):',
704
+ ' raise RangeError("Invalid array length")',
705
+ ' __k_length = int(__k_num)',
706
+ ' if __k_length > 4294967295:',
707
+ ' raise RangeError("Invalid array length")',
708
+ ' return __k_length',
709
+ '',
710
+ 'def _kern_array_from(__k_source, __k_mapper=None):',
711
+ ' if __k_source is None or __k_source is _KERN_UNDEFINED:',
712
+ ' raise TypeError("Array.from requires an array-like or iterable source")',
713
+ ' __k_len = _kern_array_like_length(__k_source)',
714
+ ' if __k_len is not None:',
715
+ ' __k_values = [_kern_array_like_get(__k_source, __k_i) for __k_i in range(__k_len)]',
716
+ ' elif isinstance(__k_source, str):',
717
+ ' __k_values = list(__k_source)',
718
+ ' else:',
719
+ ' try:',
720
+ ' __k_values = list(__k_source)',
721
+ ' except TypeError:',
722
+ ' __k_values = []',
723
+ ' if __k_mapper is None:',
724
+ ' return list(__k_values)',
725
+ ' return [_kern_array_from_map(__k_mapper, __k_value, __k_index) for __k_index, __k_value in enumerate(__k_values)]',
726
+ '',
727
+ 'def _kern_array_from_map(__k_mapper, __k_value, __k_index):',
728
+ ' try:',
729
+ ' __k_sig = inspect.signature(__k_mapper)',
730
+ ' __k_params = list(__k_sig.parameters.values())',
731
+ ' if any(__k_p.kind == inspect.Parameter.VAR_POSITIONAL for __k_p in __k_params):',
732
+ ' return __k_mapper(__k_value, __k_index)',
733
+ ' __k_positional = [__k_p for __k_p in __k_params if __k_p.kind in (inspect.Parameter.POSITIONAL_ONLY, inspect.Parameter.POSITIONAL_OR_KEYWORD)]',
734
+ ' if len(__k_positional) < 2:',
735
+ ' return __k_mapper(__k_value)',
736
+ ' except (TypeError, ValueError):',
737
+ ' pass',
738
+ ' return __k_mapper(__k_value, __k_index)',
207
739
  ].join('\n');
208
740
  export const KERN_JS_STRING_HELPERS_PY = [
209
741
  'def _kern_js_split_limit(__k_limit):',
@@ -260,4 +792,50 @@ export const KERN_JS_STRING_HELPERS_PY = [
260
792
  ' __k_pos = __k_end',
261
793
  ' return "".join(__k_parts)',
262
794
  ].join('\n');
795
+ // Milestone C, Slice 3 — portable regex MATCH-SET result helpers. JS `RegExp`
796
+ // methods and Python `re` differ in RESULT SHAPE (not just pattern); these two
797
+ // helpers normalize the Python `re.Match` surface into the canonical
798
+ // target-neutral KERN shapes the TS emitter produces natively from a
799
+ // `RegExpMatchArray`. They are byte-for-byte the lowering the Slice-3 oracle
800
+ // (`.agon-goals/regex-slice3/oracle/run_py.py::canon_match_obj` / `lower_matchAll`)
801
+ // certifies against node.
802
+ //
803
+ // _kern_regex_match(pat, s, flags) -> `.match(s)` WITHOUT /g
804
+ // JS `String.match` (non-global) returns a match-array carrying `.index` and
805
+ // `.groups`, or `null` on no match. Python `re.search` returns a `re.Match`
806
+ // OBJECT (a DIFFERENT surface: `m[0]` raises, no `.index` attr) or `None`.
807
+ // We converge both onto `{full, groups, index, named}` | None — so a
808
+ // downstream `m["full"]` / `m["index"]` / `m["named"]` reads identically on
809
+ // each target. (THE load-bearing portability fix — spec §4.1, killer row
810
+ // `match_no_g_groups_KILLER`.)
811
+ //
812
+ // _kern_regex_matchall(pat, s, flags) -> `.matchAll(s)` (/g required)
813
+ // JS `[...s.matchAll(re)]` yields match objects {full, g1.., index}; Python
814
+ // `re.finditer` yields `re.Match`es. We shape both to
815
+ // `[{full, groups, index}, ...]`, INCLUDING zero-width advances (e.g. `/x*/g`
816
+ // over "abc" -> 4 empty matches at 0..3) which `re.finditer` already enumerates
817
+ // identically to JS on modern engines (killer row `matchAll_empty_KILLER`).
818
+ export const KERN_REGEX_MATCH_HELPER_PY = [
819
+ 'def _kern_regex_match(__k_pat, __k_s, __k_flags):',
820
+ ' __k_m = __k_re.search(__k_pat, __k_s, __k_flags)',
821
+ ' if __k_m is None:',
822
+ ' return None',
823
+ ' return {',
824
+ ' "full": __k_m.group(0),',
825
+ ' "groups": [__k_g for __k_g in __k_m.groups()],',
826
+ ' "index": __k_m.start(),',
827
+ ' "named": dict(__k_m.groupdict()),',
828
+ ' }',
829
+ ].join('\n');
830
+ export const KERN_REGEX_MATCHALL_HELPER_PY = [
831
+ 'def _kern_regex_matchall(__k_pat, __k_s, __k_flags):',
832
+ ' return [',
833
+ ' {',
834
+ ' "full": __k_m.group(0),',
835
+ ' "groups": [__k_g for __k_g in __k_m.groups()],',
836
+ ' "index": __k_m.start(),',
837
+ ' }',
838
+ ' for __k_m in __k_re.finditer(__k_pat, __k_s, __k_flags)',
839
+ ' ]',
840
+ ].join('\n');
263
841
  //# sourceMappingURL=helpers.js.map