@kernlang/python 4.0.0 → 4.0.1-canary.224.1.1a92ac0a

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,4 +1,5 @@
1
1
  import { generatePythonCoreNode } from '../codegen-python.js';
2
+ import { dedupeGroundPrelude } from '../generators/ground.js';
2
3
  function findServerNode(root) {
3
4
  if (root.type === 'server')
4
5
  return root;
@@ -78,8 +79,11 @@ export function emitModels(root, options) {
78
79
  lines.push('');
79
80
  }
80
81
  }
82
+ // Top-level ground statements inline their helper/import prelude per-statement
83
+ // (a `module` wrapper dedupes in generateModule); collapse repeats across the
84
+ // assembled top-level bodies so each helper block appears once in the module.
81
85
  return {
82
- code: lines.join('\n'),
86
+ code: dedupeGroundPrelude(lines).join('\n'),
83
87
  bodies,
84
88
  };
85
89
  }
@@ -1 +1 @@
1
- {"version":3,"file":"emit-models.js","sourceRoot":"","sources":["../../src/core/emit-models.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"emit-models.js","sourceRoot":"","sources":["../../src/core/emit-models.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,53 @@
1
+ export declare const KERN_NULLISH_HELPER_PY: string;
2
+ export declare const KERN_JSON_STRINGIFY_SHIM_PY: string;
1
3
  export declare const KERN_PAIR_HELPERS_PY: string;
2
4
  export declare const KERN_FMT_HELPER_PY: string;
3
5
  export declare const KERN_I32_HELPER_PY: string;
6
+ /**
7
+ * ToNumericPrimitive substrate (slice 0.75) — Python twin of the
8
+ * `@kernlang/core` `to-numeric` decision kernel.
9
+ *
10
+ * Single Python-side source of numeric coercion truth for the FROZEN primitive
11
+ * domain (numbers, ECMA numeric strings, booleans, null, undefined sentinel).
12
+ * The emitted Python encodes the ECMA-262 StringNumericLiteral grammar
13
+ * EXPLICITLY rather than delegating to `float(...)`, because `float()` diverges
14
+ * from JS `Number()` on three load-bearing cases: it accepts numeric separators
15
+ * (`float('1_000') == 1000.0`), case-insensitive infinity/NaN words
16
+ * (`float('infinity')`, `float('nan')`), and raises on `0x`/`0b`/`0o` prefixes.
17
+ *
18
+ * Return-type contract (tribunal amendments 1 & 2):
19
+ * - `_kern_to_number(x)` -> Python `float` for EVERY numeric output
20
+ * (bool/null/hex/binary/octal inputs included); `-0.0` sign survives; NaN
21
+ * and ±inf preserved.
22
+ * - `_kern_string_to_number(text)` -> `float` (NaN for any non-grammar string).
23
+ * - `_kern_number_to_int32(n)` -> `int` (signed 32-bit, shift-mask domain).
24
+ * - `_kern_number_to_uint32(n)` -> `int` (unsigned 32-bit).
25
+ * - `_kern_to_int32(x)` -> `int` = int32(to_number(x)).
26
+ * - `_kern_to_uint32(x)` -> `int` = uint32(to_number(x)).
27
+ * - `_kern_to_integer_or_infinity(x)` -> `float` (int-valued, or ±inf).
28
+ *
29
+ * Fail-closed: objects/arrays/functions/symbols/custom-valueOf RAISE
30
+ * `_KernNumericCoercionError` (the caller decides) — full ToPrimitive deferred.
31
+ *
32
+ * Single-source-of-int32 note: this block is the coercion-correct int32 path
33
+ * going forward. The legacy `_i32` (KERN_I32_HELPER_PY) embeds its OWN
34
+ * float()-based coercion and is still wired to production
35
+ * (codegen-body-python.ts ToInt32 lowering). Slice 0.75 is a PURE ADDITION —
36
+ * nothing is rerouted here, so both coexist; the future routing slice retires
37
+ * `_i32` in favor of `_kern_to_int32` once the fallback count hits zero.
38
+ *
39
+ * The ECMA whitespace string below is the exact `StrWhiteSpace` set
40
+ * (`WhiteSpace` + `LineTerminator`) JS trims from a StringNumericLiteral, kept
41
+ * byte-aligned with `ECMA_STR_WHITESPACE` in the TS kernel.
42
+ */
43
+ export declare const KERN_TO_NUMBER_HELPER_PY: string;
4
44
  export declare const KERN_TMOD_HELPER_PY: string;
5
45
  export declare const KERN_JS_HELPER_PY: string;
6
46
  export declare const KERN_JS_ARRAY_HELPERS_PY: string;
7
47
  export declare const KERN_JS_OBJECT_HELPERS_PY: string;
48
+ export declare const KERN_JS_NUMBER_HELPERS_PY: string;
49
+ export declare const KERN_JS_MATH_HELPERS_PY: string;
50
+ export declare const KERN_JS_ARRAY_FROM_HELPER_PY: string;
8
51
  export declare const KERN_JS_STRING_HELPERS_PY: string;
52
+ export declare const KERN_REGEX_MATCH_HELPER_PY: string;
53
+ export declare const KERN_REGEX_MATCHALL_HELPER_PY: string;
@@ -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)',
@@ -92,6 +202,126 @@ export const KERN_I32_HELPER_PY = [
92
202
  ' return 0',
93
203
  ' return ((val & 0xFFFFFFFF) ^ 0x80000000) - 0x80000000',
94
204
  ].join('\n');
205
+ /**
206
+ * ToNumericPrimitive substrate (slice 0.75) — Python twin of the
207
+ * `@kernlang/core` `to-numeric` decision kernel.
208
+ *
209
+ * Single Python-side source of numeric coercion truth for the FROZEN primitive
210
+ * domain (numbers, ECMA numeric strings, booleans, null, undefined sentinel).
211
+ * The emitted Python encodes the ECMA-262 StringNumericLiteral grammar
212
+ * EXPLICITLY rather than delegating to `float(...)`, because `float()` diverges
213
+ * from JS `Number()` on three load-bearing cases: it accepts numeric separators
214
+ * (`float('1_000') == 1000.0`), case-insensitive infinity/NaN words
215
+ * (`float('infinity')`, `float('nan')`), and raises on `0x`/`0b`/`0o` prefixes.
216
+ *
217
+ * Return-type contract (tribunal amendments 1 & 2):
218
+ * - `_kern_to_number(x)` -> Python `float` for EVERY numeric output
219
+ * (bool/null/hex/binary/octal inputs included); `-0.0` sign survives; NaN
220
+ * and ±inf preserved.
221
+ * - `_kern_string_to_number(text)` -> `float` (NaN for any non-grammar string).
222
+ * - `_kern_number_to_int32(n)` -> `int` (signed 32-bit, shift-mask domain).
223
+ * - `_kern_number_to_uint32(n)` -> `int` (unsigned 32-bit).
224
+ * - `_kern_to_int32(x)` -> `int` = int32(to_number(x)).
225
+ * - `_kern_to_uint32(x)` -> `int` = uint32(to_number(x)).
226
+ * - `_kern_to_integer_or_infinity(x)` -> `float` (int-valued, or ±inf).
227
+ *
228
+ * Fail-closed: objects/arrays/functions/symbols/custom-valueOf RAISE
229
+ * `_KernNumericCoercionError` (the caller decides) — full ToPrimitive deferred.
230
+ *
231
+ * Single-source-of-int32 note: this block is the coercion-correct int32 path
232
+ * going forward. The legacy `_i32` (KERN_I32_HELPER_PY) embeds its OWN
233
+ * float()-based coercion and is still wired to production
234
+ * (codegen-body-python.ts ToInt32 lowering). Slice 0.75 is a PURE ADDITION —
235
+ * nothing is rerouted here, so both coexist; the future routing slice retires
236
+ * `_i32` in favor of `_kern_to_int32` once the fallback count hits zero.
237
+ *
238
+ * The ECMA whitespace string below is the exact `StrWhiteSpace` set
239
+ * (`WhiteSpace` + `LineTerminator`) JS trims from a StringNumericLiteral, kept
240
+ * byte-aligned with `ECMA_STR_WHITESPACE` in the TS kernel.
241
+ */
242
+ export const KERN_TO_NUMBER_HELPER_PY = [
243
+ 'import math',
244
+ 'import re',
245
+ '',
246
+ 'try:',
247
+ ' _KERN_UNDEFINED',
248
+ 'except NameError:',
249
+ ' class _KernUndefined:',
250
+ ' def __bool__(self): return False',
251
+ " def __repr__(self): return 'undefined'",
252
+ " def __str__(self): return 'undefined'",
253
+ ' _KERN_UNDEFINED = _KernUndefined()',
254
+ '',
255
+ 'class _KernNumericCoercionError(TypeError):',
256
+ ' pass',
257
+ '',
258
+ // ECMA StrWhiteSpace = WhiteSpace + LineTerminator. Encoded as \u escapes so
259
+ // the emitted source stays ASCII; Python materializes the real code points.
260
+ "_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'",
261
+ "_KERN_DECIMAL_RE = re.compile(r'^[+-]?(?:(?:[0-9]+(?:\\.[0-9]*)?|\\.[0-9]+)(?:[eE][+-]?[0-9]+)?|[0-9]+)$')",
262
+ "_KERN_HEX_RE = re.compile(r'^0[xX][0-9a-fA-F]+$')",
263
+ "_KERN_BIN_RE = re.compile(r'^0[bB][01]+$')",
264
+ "_KERN_OCT_RE = re.compile(r'^0[oO][0-7]+$')",
265
+ '',
266
+ 'def _kern_string_to_number(text):',
267
+ ' s = text.strip(_KERN_ECMA_WS)',
268
+ " if s == '':",
269
+ ' return 0.0',
270
+ " if s == 'Infinity' or s == '+Infinity':",
271
+ " return float('inf')",
272
+ " if s == '-Infinity':",
273
+ " return float('-inf')",
274
+ ' if _KERN_HEX_RE.match(s):',
275
+ ' return float(int(s[2:], 16))',
276
+ ' if _KERN_BIN_RE.match(s):',
277
+ ' return float(int(s[2:], 2))',
278
+ ' if _KERN_OCT_RE.match(s):',
279
+ ' return float(int(s[2:], 8))',
280
+ ' if not _KERN_DECIMAL_RE.match(s):',
281
+ " return float('nan')",
282
+ ' try:',
283
+ ' return float(s)',
284
+ ' except ValueError:',
285
+ " return float('nan')",
286
+ '',
287
+ 'def _kern_to_number(x):',
288
+ ' if x is _KERN_UNDEFINED:',
289
+ " return float('nan')",
290
+ ' if x is None:',
291
+ ' return 0.0',
292
+ ' if isinstance(x, bool):',
293
+ ' return 1.0 if x else 0.0',
294
+ ' if isinstance(x, (int, float)):',
295
+ ' return float(x)',
296
+ ' if isinstance(x, str):',
297
+ ' return _kern_string_to_number(x)',
298
+ " raise _KernNumericCoercionError('KERN ToNumber supports only primitive values (slice-0.75); full ToPrimitive deferred')",
299
+ '',
300
+ 'def _kern_number_to_int32(n):',
301
+ " if n != n or n == 0 or n in (float('inf'), float('-inf')):",
302
+ ' return 0',
303
+ ' i = math.trunc(n)',
304
+ ' return ((i & 0xFFFFFFFF) ^ 0x80000000) - 0x80000000',
305
+ '',
306
+ 'def _kern_number_to_uint32(n):',
307
+ " if n != n or n == 0 or n in (float('inf'), float('-inf')):",
308
+ ' return 0',
309
+ ' return math.trunc(n) & 0xFFFFFFFF',
310
+ '',
311
+ 'def _kern_to_int32(x):',
312
+ ' return _kern_number_to_int32(_kern_to_number(x))',
313
+ '',
314
+ 'def _kern_to_uint32(x):',
315
+ ' return _kern_number_to_uint32(_kern_to_number(x))',
316
+ '',
317
+ 'def _kern_to_integer_or_infinity(x):',
318
+ ' n = _kern_to_number(x)',
319
+ ' if n != n:',
320
+ ' return 0.0',
321
+ " if n in (float('inf'), float('-inf')):",
322
+ ' return n',
323
+ ' return float(math.trunc(n))',
324
+ ].join('\n');
95
325
  export const KERN_TMOD_HELPER_PY = [
96
326
  'import math',
97
327
  'def _tmod(a, b):',
@@ -109,11 +339,36 @@ export const KERN_TMOD_HELPER_PY = [
109
339
  ' return fa - math.trunc(fa / fb) * fb',
110
340
  ].join('\n');
111
341
  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',
342
+ // Slice S4 — ToBoolean / KERN truthiness substrate. `js_truthy` is an EXPLICIT
343
+ // falsy-set predicate over the KERN value domain: falsy iff x is the undefined
344
+ // sentinel, None, a boolean False, numeric zero (+0/-0.0/0j), NaN, or "".
345
+ // Everything else is truthy INCLUDING [], {}, callables, class instances, and
346
+ // user objects whose Python __bool__/__len__ are falsy. It NEVER delegates to
347
+ // bool(x), len(x), x.__bool__(), x.__len__(), or a ToNumber string conversion:
348
+ // "0", "false", " " are all truthy; only "" is falsy. `bool` is checked BEFORE
349
+ // the numeric branch because Python `bool` subclasses `int`. The undefined
350
+ // sentinel is matched by IDENTITY (`is _KERN_UNDEFINED`) — its __bool__ = False
351
+ // override is for bare-truthiness positions only and must NOT be generalized to
352
+ // user objects. `_kern_truthy` is the canonical name; `js_truthy` is the alias
353
+ // the existing array-predicate lowerings (filter/some/every/find-family) call.
354
+ 'try:',
355
+ ' _KERN_UNDEFINED',
356
+ 'except NameError:',
357
+ ' class _KernUndefined:',
358
+ ' def __bool__(self): return False',
359
+ " def __repr__(self): return 'undefined'",
360
+ " def __str__(self): return 'undefined'",
361
+ ' _KERN_UNDEFINED = _KernUndefined()',
362
+ 'def _kern_truthy(x):',
363
+ ' if x is _KERN_UNDEFINED or x is None or x is False: return False',
364
+ ' if isinstance(x, bool): return x',
365
+ // `x == x` is False only for NaN, so `x != 0 and x == x` rejects both numeric
366
+ // zero (incl. -0.0 and 0j) and NaN without delegating to math.isnan / bool().
367
+ ' if isinstance(x, (int, float, complex)): return x != 0 and x == x',
368
+ ' if isinstance(x, str): return len(x) > 0',
116
369
  ' return True',
370
+ 'def js_truthy(x):',
371
+ ' return _kern_truthy(x)',
117
372
  'def js_equals(a, b):',
118
373
  ' return a == b',
119
374
  ].join('\n');
@@ -169,6 +424,19 @@ export const KERN_JS_ARRAY_HELPERS_PY = [
169
424
  ' return __k_list',
170
425
  ].join('\n');
171
426
  export const KERN_JS_OBJECT_HELPERS_PY = [
427
+ // Slice S7 — `Object.keys/values/entries` must throw TypeError parity for BOTH
428
+ // null and the undefined sentinel (JS `Object.keys(undefined)` throws). The
429
+ // sentinel is defined here via the idempotent guard so the identity check is
430
+ // safe even when this block is registered without the fmt/nullish blocks.
431
+ 'try:',
432
+ ' _KERN_UNDEFINED',
433
+ 'except NameError:',
434
+ ' class _KernUndefined:',
435
+ ' def __bool__(self): return False',
436
+ " def __repr__(self): return 'undefined'",
437
+ " def __str__(self): return 'undefined'",
438
+ ' _KERN_UNDEFINED = _KernUndefined()',
439
+ '',
172
440
  'def _kern_js_is_array_index(__k_key):',
173
441
  ' __k_s = str(__k_key)',
174
442
  ' if not __k_s.isdigit(): return False',
@@ -177,7 +445,7 @@ export const KERN_JS_OBJECT_HELPERS_PY = [
177
445
  ' return 0 <= __k_n < 4294967295 and __k_s == str(__k_n)',
178
446
  '',
179
447
  'def _kern_js_property_items(__k_obj):',
180
- ' if __k_obj is None:',
448
+ ' if __k_obj is None or __k_obj is _KERN_UNDEFINED:',
181
449
  ' raise TypeError("Cannot convert undefined or null to object")',
182
450
  ' if hasattr(__k_obj, "items"):',
183
451
  ' __k_raw = list(__k_obj.items())',
@@ -204,6 +472,196 @@ export const KERN_JS_OBJECT_HELPERS_PY = [
204
472
  '',
205
473
  'def _kern_js_object_entries(__k_obj):',
206
474
  ' return [[__k_k, __k_v] for __k_k, __k_v in _kern_js_property_items(__k_obj)]',
475
+ '',
476
+ 'def _kern_js_object_assign(__k_target, *__k_sources):',
477
+ ' if __k_target is None or __k_target is _KERN_UNDEFINED:',
478
+ ' raise TypeError("Cannot convert undefined or null to object")',
479
+ ' if hasattr(__k_target, "update"):',
480
+ ' __k_out = __k_target',
481
+ ' elif isinstance(__k_target, list):',
482
+ ' __k_out = __k_target',
483
+ ' elif isinstance(__k_target, str):',
484
+ ' __k_out = {str(__k_i): __k_ch for __k_i, __k_ch in enumerate(__k_target)}',
485
+ ' else:',
486
+ ' __k_out = {}',
487
+ ' for __k_src in __k_sources:',
488
+ ' if __k_src is None or __k_src is _KERN_UNDEFINED:',
489
+ ' continue',
490
+ ' for __k_k, __k_v in _kern_js_property_items(__k_src):',
491
+ ' if isinstance(__k_out, list):',
492
+ ' if not _kern_js_is_array_index(__k_k):',
493
+ ' raise TypeError("Object.assign cannot attach non-index properties to Python list target")',
494
+ ' __k_i = int(__k_k)',
495
+ ' while len(__k_out) <= __k_i:',
496
+ ' __k_out.append(_KERN_UNDEFINED)',
497
+ ' __k_out[__k_i] = __k_v',
498
+ ' else:',
499
+ ' __k_out[__k_k] = __k_v',
500
+ ' return __k_out',
501
+ ].join('\n');
502
+ // `Number.isInteger` / `Number.isSafeInteger` do NO coercion — a non-number
503
+ // argument (incl. a boolean) is ALWAYS false (`Number.isInteger("5")` → false,
504
+ // `Number.isInteger(true)` → false). Python's `bool` subclasses `int`, so the
505
+ // integer test MUST reject `bool` explicitly or `Number.isInteger(true)` wrongly
506
+ // returns True. NaN/±Infinity are non-integers. `isSafeInteger` additionally
507
+ // requires `abs(x) <= 2**53 - 1`.
508
+ export const KERN_JS_NUMBER_HELPERS_PY = [
509
+ 'def _kern_number_is_integer(__k_x):',
510
+ ' if isinstance(__k_x, bool):',
511
+ ' return False',
512
+ ' if isinstance(__k_x, int):',
513
+ ' return True',
514
+ ' if isinstance(__k_x, float):',
515
+ ' return __k_x == __k_x and __k_x not in (float("inf"), float("-inf")) and __k_x.is_integer()',
516
+ ' return False',
517
+ '',
518
+ 'def _kern_number_is_safe_integer(__k_x):',
519
+ ' if not _kern_number_is_integer(__k_x):',
520
+ ' return False',
521
+ ' return abs(__k_x) <= 9007199254740991',
522
+ ].join('\n');
523
+ export const KERN_JS_MATH_HELPERS_PY = [
524
+ 'import math',
525
+ '',
526
+ 'def _kern_math_is_negative_zero(__k_n):',
527
+ ' return __k_n == 0 and math.copysign(1.0, __k_n) < 0',
528
+ '',
529
+ 'def _kern_math_nan():',
530
+ ' return float("nan")',
531
+ '',
532
+ 'def _kern_math_round(__k_x):',
533
+ ' __k_n = _kern_to_number(__k_x)',
534
+ ' if __k_n != __k_n or __k_n in (float("inf"), float("-inf")) or __k_n == 0:',
535
+ ' return __k_n',
536
+ ' __k_floor = math.floor(__k_n)',
537
+ ' __k_r = __k_floor + (1 if __k_n - __k_floor >= 0.5 else 0)',
538
+ ' if __k_r == 0 and __k_n < 0:',
539
+ ' return -0.0',
540
+ ' return __k_r',
541
+ '',
542
+ 'def _kern_math_floor(__k_x):',
543
+ ' __k_n = _kern_to_number(__k_x)',
544
+ ' if __k_n != __k_n or __k_n in (float("inf"), float("-inf")) or __k_n == 0:',
545
+ ' return __k_n',
546
+ ' return math.floor(__k_n)',
547
+ '',
548
+ 'def _kern_math_trunc(__k_x):',
549
+ ' __k_n = _kern_to_number(__k_x)',
550
+ ' if __k_n != __k_n or __k_n in (float("inf"), float("-inf")) or __k_n == 0:',
551
+ ' return __k_n',
552
+ ' __k_r = math.trunc(__k_n)',
553
+ ' if __k_r == 0 and __k_n < 0:',
554
+ ' return -0.0',
555
+ ' return __k_r',
556
+ '',
557
+ 'def _kern_math_sign(__k_x):',
558
+ ' __k_n = _kern_to_number(__k_x)',
559
+ ' if __k_n != __k_n or __k_n == 0:',
560
+ ' return __k_n',
561
+ ' return 1 if __k_n > 0 else -1',
562
+ '',
563
+ 'def _kern_math_max(*__k_args):',
564
+ ' if len(__k_args) == 0:',
565
+ ' return float("-inf")',
566
+ ' __k_best = float("-inf")',
567
+ ' for __k_arg in __k_args:',
568
+ ' __k_n = _kern_to_number(__k_arg)',
569
+ ' if __k_n != __k_n:',
570
+ ' return _kern_math_nan()',
571
+ ' 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)):',
572
+ ' __k_best = __k_n',
573
+ ' return __k_best',
574
+ '',
575
+ 'def _kern_math_min(*__k_args):',
576
+ ' if len(__k_args) == 0:',
577
+ ' return float("inf")',
578
+ ' __k_best = float("inf")',
579
+ ' for __k_arg in __k_args:',
580
+ ' __k_n = _kern_to_number(__k_arg)',
581
+ ' if __k_n != __k_n:',
582
+ ' return _kern_math_nan()',
583
+ ' 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)):',
584
+ ' __k_best = __k_n',
585
+ ' return __k_best',
586
+ ].join('\n');
587
+ export const KERN_JS_ARRAY_FROM_HELPER_PY = [
588
+ 'import inspect',
589
+ '',
590
+ 'try:',
591
+ ' RangeError',
592
+ 'except NameError:',
593
+ ' class RangeError(ValueError):',
594
+ ' pass',
595
+ '',
596
+ 'try:',
597
+ ' _KERN_UNDEFINED',
598
+ 'except NameError:',
599
+ ' class _KernUndefined:',
600
+ ' def __bool__(self): return False',
601
+ " def __repr__(self): return 'undefined'",
602
+ " def __str__(self): return 'undefined'",
603
+ ' _KERN_UNDEFINED = _KernUndefined()',
604
+ '',
605
+ 'def _kern_array_like_get(__k_source, __k_index):',
606
+ ' if isinstance(__k_source, dict):',
607
+ ' if __k_index in __k_source:',
608
+ ' return __k_source[__k_index]',
609
+ ' __k_key = str(__k_index)',
610
+ ' return __k_source[__k_key] if __k_key in __k_source else _KERN_UNDEFINED',
611
+ ' try:',
612
+ ' return __k_source[__k_index]',
613
+ ' except Exception:',
614
+ ' return _KERN_UNDEFINED',
615
+ '',
616
+ 'def _kern_array_like_length(__k_source):',
617
+ ' if isinstance(__k_source, dict):',
618
+ ' __k_len = __k_source.get("length", 0)',
619
+ ' else:',
620
+ ' __k_len = getattr(__k_source, "length", None)',
621
+ ' if __k_len is None:',
622
+ ' return None',
623
+ ' try:',
624
+ ' __k_num = _kern_to_number(__k_len)',
625
+ ' except Exception:',
626
+ ' return 0',
627
+ ' if __k_num != __k_num or __k_num <= 0:',
628
+ ' return 0',
629
+ ' if __k_num == float("inf"):',
630
+ ' raise RangeError("Invalid array length")',
631
+ ' __k_length = int(__k_num)',
632
+ ' if __k_length > 4294967295:',
633
+ ' raise RangeError("Invalid array length")',
634
+ ' return __k_length',
635
+ '',
636
+ 'def _kern_array_from(__k_source, __k_mapper=None):',
637
+ ' if __k_source is None or __k_source is _KERN_UNDEFINED:',
638
+ ' raise TypeError("Array.from requires an array-like or iterable source")',
639
+ ' __k_len = _kern_array_like_length(__k_source)',
640
+ ' if __k_len is not None:',
641
+ ' __k_values = [_kern_array_like_get(__k_source, __k_i) for __k_i in range(__k_len)]',
642
+ ' elif isinstance(__k_source, str):',
643
+ ' __k_values = list(__k_source)',
644
+ ' else:',
645
+ ' try:',
646
+ ' __k_values = list(__k_source)',
647
+ ' except TypeError:',
648
+ ' __k_values = []',
649
+ ' if __k_mapper is None:',
650
+ ' return list(__k_values)',
651
+ ' return [_kern_array_from_map(__k_mapper, __k_value, __k_index) for __k_index, __k_value in enumerate(__k_values)]',
652
+ '',
653
+ 'def _kern_array_from_map(__k_mapper, __k_value, __k_index):',
654
+ ' try:',
655
+ ' __k_sig = inspect.signature(__k_mapper)',
656
+ ' __k_params = list(__k_sig.parameters.values())',
657
+ ' if any(__k_p.kind == inspect.Parameter.VAR_POSITIONAL for __k_p in __k_params):',
658
+ ' return __k_mapper(__k_value, __k_index)',
659
+ ' __k_positional = [__k_p for __k_p in __k_params if __k_p.kind in (inspect.Parameter.POSITIONAL_ONLY, inspect.Parameter.POSITIONAL_OR_KEYWORD)]',
660
+ ' if len(__k_positional) < 2:',
661
+ ' return __k_mapper(__k_value)',
662
+ ' except (TypeError, ValueError):',
663
+ ' pass',
664
+ ' return __k_mapper(__k_value, __k_index)',
207
665
  ].join('\n');
208
666
  export const KERN_JS_STRING_HELPERS_PY = [
209
667
  'def _kern_js_split_limit(__k_limit):',
@@ -260,4 +718,50 @@ export const KERN_JS_STRING_HELPERS_PY = [
260
718
  ' __k_pos = __k_end',
261
719
  ' return "".join(__k_parts)',
262
720
  ].join('\n');
721
+ // Milestone C, Slice 3 — portable regex MATCH-SET result helpers. JS `RegExp`
722
+ // methods and Python `re` differ in RESULT SHAPE (not just pattern); these two
723
+ // helpers normalize the Python `re.Match` surface into the canonical
724
+ // target-neutral KERN shapes the TS emitter produces natively from a
725
+ // `RegExpMatchArray`. They are byte-for-byte the lowering the Slice-3 oracle
726
+ // (`.agon-goals/regex-slice3/oracle/run_py.py::canon_match_obj` / `lower_matchAll`)
727
+ // certifies against node.
728
+ //
729
+ // _kern_regex_match(pat, s, flags) -> `.match(s)` WITHOUT /g
730
+ // JS `String.match` (non-global) returns a match-array carrying `.index` and
731
+ // `.groups`, or `null` on no match. Python `re.search` returns a `re.Match`
732
+ // OBJECT (a DIFFERENT surface: `m[0]` raises, no `.index` attr) or `None`.
733
+ // We converge both onto `{full, groups, index, named}` | None — so a
734
+ // downstream `m["full"]` / `m["index"]` / `m["named"]` reads identically on
735
+ // each target. (THE load-bearing portability fix — spec §4.1, killer row
736
+ // `match_no_g_groups_KILLER`.)
737
+ //
738
+ // _kern_regex_matchall(pat, s, flags) -> `.matchAll(s)` (/g required)
739
+ // JS `[...s.matchAll(re)]` yields match objects {full, g1.., index}; Python
740
+ // `re.finditer` yields `re.Match`es. We shape both to
741
+ // `[{full, groups, index}, ...]`, INCLUDING zero-width advances (e.g. `/x*/g`
742
+ // over "abc" -> 4 empty matches at 0..3) which `re.finditer` already enumerates
743
+ // identically to JS on modern engines (killer row `matchAll_empty_KILLER`).
744
+ export const KERN_REGEX_MATCH_HELPER_PY = [
745
+ 'def _kern_regex_match(__k_pat, __k_s, __k_flags):',
746
+ ' __k_m = __k_re.search(__k_pat, __k_s, __k_flags)',
747
+ ' if __k_m is None:',
748
+ ' return None',
749
+ ' return {',
750
+ ' "full": __k_m.group(0),',
751
+ ' "groups": [__k_g for __k_g in __k_m.groups()],',
752
+ ' "index": __k_m.start(),',
753
+ ' "named": dict(__k_m.groupdict()),',
754
+ ' }',
755
+ ].join('\n');
756
+ export const KERN_REGEX_MATCHALL_HELPER_PY = [
757
+ 'def _kern_regex_matchall(__k_pat, __k_s, __k_flags):',
758
+ ' return [',
759
+ ' {',
760
+ ' "full": __k_m.group(0),',
761
+ ' "groups": [__k_g for __k_g in __k_m.groups()],',
762
+ ' "index": __k_m.start(),',
763
+ ' }',
764
+ ' for __k_m in __k_re.finditer(__k_pat, __k_s, __k_flags)',
765
+ ' ]',
766
+ ].join('\n');
263
767
  //# sourceMappingURL=helpers.js.map