@kernlang/python 3.5.8-canary.207.1.43495cde → 3.5.8-canary.209.1.8afcd7e7
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/dist/codegen-body-python.d.ts +2 -0
- package/dist/codegen-body-python.js +131 -1
- package/dist/codegen-body-python.js.map +1 -1
- package/dist/codegen-python.d.ts +1 -1
- package/dist/codegen-python.js +6 -2
- package/dist/codegen-python.js.map +1 -1
- package/dist/core/expr/helpers.d.ts +2 -0
- package/dist/core/expr/helpers.js +92 -0
- package/dist/core/expr/helpers.js.map +1 -1
- package/dist/core/expr/index.d.ts +1 -1
- package/dist/core/expr/index.js +267 -21
- package/dist/core/expr/index.js.map +1 -1
- package/dist/generators/ground.d.ts +2 -0
- package/dist/generators/ground.js +91 -1
- package/dist/generators/ground.js.map +1 -1
- package/package.json +2 -2
|
@@ -59,4 +59,96 @@ export const KERN_JS_HELPER_PY = [
|
|
|
59
59
|
'def js_equals(a, b):',
|
|
60
60
|
' return a == b',
|
|
61
61
|
].join('\n');
|
|
62
|
+
export const KERN_JS_OBJECT_HELPERS_PY = [
|
|
63
|
+
'def _kern_js_is_array_index(__k_key):',
|
|
64
|
+
' __k_s = str(__k_key)',
|
|
65
|
+
' if not __k_s.isdigit(): return False',
|
|
66
|
+
' if len(__k_s) > 1 and __k_s[0] == "0": return False',
|
|
67
|
+
' __k_n = int(__k_s)',
|
|
68
|
+
' return 0 <= __k_n < 4294967295 and __k_s == str(__k_n)',
|
|
69
|
+
'',
|
|
70
|
+
'def _kern_js_property_items(__k_obj):',
|
|
71
|
+
' if __k_obj is None:',
|
|
72
|
+
' raise TypeError("Cannot convert undefined or null to object")',
|
|
73
|
+
' if hasattr(__k_obj, "items"):',
|
|
74
|
+
' __k_raw = list(__k_obj.items())',
|
|
75
|
+
' else:',
|
|
76
|
+
' try:',
|
|
77
|
+
' __k_raw = [(str(__k_i), __k_v) for __k_i, __k_v in enumerate(__k_obj)]',
|
|
78
|
+
' except TypeError:',
|
|
79
|
+
' __k_raw = []',
|
|
80
|
+
' __k_indexed = []',
|
|
81
|
+
' __k_rest = []',
|
|
82
|
+
' for __k_pos, (__k_key, __k_val) in enumerate(__k_raw):',
|
|
83
|
+
' __k_s = str(__k_key)',
|
|
84
|
+
' if _kern_js_is_array_index(__k_s):',
|
|
85
|
+
' __k_indexed.append((int(__k_s), __k_s, __k_val))',
|
|
86
|
+
' else:',
|
|
87
|
+
' __k_rest.append((__k_pos, __k_s, __k_val))',
|
|
88
|
+
' return [(__k_s, __k_v) for _, __k_s, __k_v in sorted(__k_indexed, key=lambda __k_item: __k_item[0])] + [(__k_s, __k_v) for _, __k_s, __k_v in __k_rest]',
|
|
89
|
+
'',
|
|
90
|
+
'def _kern_js_object_keys(__k_obj):',
|
|
91
|
+
' return [__k_k for __k_k, _ in _kern_js_property_items(__k_obj)]',
|
|
92
|
+
'',
|
|
93
|
+
'def _kern_js_object_values(__k_obj):',
|
|
94
|
+
' return [__k_v for _, __k_v in _kern_js_property_items(__k_obj)]',
|
|
95
|
+
'',
|
|
96
|
+
'def _kern_js_object_entries(__k_obj):',
|
|
97
|
+
' return [[__k_k, __k_v] for __k_k, __k_v in _kern_js_property_items(__k_obj)]',
|
|
98
|
+
].join('\n');
|
|
99
|
+
export const KERN_JS_STRING_HELPERS_PY = [
|
|
100
|
+
'def _kern_js_split_limit(__k_limit):',
|
|
101
|
+
' if __k_limit is None:',
|
|
102
|
+
' return None',
|
|
103
|
+
' try:',
|
|
104
|
+
' __k_n = float(__k_limit)',
|
|
105
|
+
' except Exception:',
|
|
106
|
+
' return 0',
|
|
107
|
+
' if __k_n != __k_n or __k_n in (float("inf"), float("-inf")):',
|
|
108
|
+
' return 0',
|
|
109
|
+
' return int(__k_n) % 4294967296',
|
|
110
|
+
'',
|
|
111
|
+
'def _kern_js_replacement(__k_repl, __k_match, __k_prefix, __k_suffix):',
|
|
112
|
+
' __k_repl = str(__k_repl)',
|
|
113
|
+
' __k_out = []',
|
|
114
|
+
' __k_i = 0',
|
|
115
|
+
' while __k_i < len(__k_repl):',
|
|
116
|
+
' __k_c = __k_repl[__k_i]',
|
|
117
|
+
' if __k_c == "$" and __k_i + 1 < len(__k_repl):',
|
|
118
|
+
' __k_n = __k_repl[__k_i + 1]',
|
|
119
|
+
' if __k_n == "$":',
|
|
120
|
+
' __k_out.append("$"); __k_i += 2; continue',
|
|
121
|
+
' if __k_n == "&":',
|
|
122
|
+
' __k_out.append(__k_match); __k_i += 2; continue',
|
|
123
|
+
' if __k_n == "`":',
|
|
124
|
+
' __k_out.append(__k_prefix); __k_i += 2; continue',
|
|
125
|
+
' if __k_n == "\'":',
|
|
126
|
+
' __k_out.append(__k_suffix); __k_i += 2; continue',
|
|
127
|
+
' __k_out.append(__k_c)',
|
|
128
|
+
' __k_i += 1',
|
|
129
|
+
' return "".join(__k_out)',
|
|
130
|
+
'',
|
|
131
|
+
'def _kern_js_replace(__k_s, __k_search, __k_repl, __k_all=False):',
|
|
132
|
+
' __k_s = str(__k_s)',
|
|
133
|
+
' __k_search = str(__k_search)',
|
|
134
|
+
' if not __k_all:',
|
|
135
|
+
' __k_idx = __k_s.find(__k_search)',
|
|
136
|
+
' if __k_idx < 0: return __k_s',
|
|
137
|
+
' __k_end = __k_idx + len(__k_search)',
|
|
138
|
+
' return __k_s[:__k_idx] + _kern_js_replacement(__k_repl, __k_search, __k_s[:__k_idx], __k_s[__k_end:]) + __k_s[__k_end:]',
|
|
139
|
+
' if __k_search == "":',
|
|
140
|
+
' return "".join(_kern_js_replacement(__k_repl, "", __k_s[:__k_i], __k_s[__k_i:]) + (__k_s[__k_i] if __k_i < len(__k_s) else "") for __k_i in range(len(__k_s) + 1))',
|
|
141
|
+
' __k_parts = []',
|
|
142
|
+
' __k_pos = 0',
|
|
143
|
+
' while True:',
|
|
144
|
+
' __k_idx = __k_s.find(__k_search, __k_pos)',
|
|
145
|
+
' if __k_idx < 0:',
|
|
146
|
+
' __k_parts.append(__k_s[__k_pos:])',
|
|
147
|
+
' break',
|
|
148
|
+
' __k_end = __k_idx + len(__k_search)',
|
|
149
|
+
' __k_parts.append(__k_s[__k_pos:__k_idx])',
|
|
150
|
+
' __k_parts.append(_kern_js_replacement(__k_repl, __k_search, __k_s[:__k_idx], __k_s[__k_end:]))',
|
|
151
|
+
' __k_pos = __k_end',
|
|
152
|
+
' return "".join(__k_parts)',
|
|
153
|
+
].join('\n');
|
|
62
154
|
//# sourceMappingURL=helpers.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.js","sourceRoot":"","sources":["../../../src/core/expr/helpers.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,yBAAyB;IACzB,sEAAsE;IACtE,EAAE;IACF,qCAAqC;IACrC,qCAAqC;IACrC,sCAAsC;IACtC,4BAA4B;IAC5B,WAAW;IACX,6CAA6C;IAC7C,4BAA4B;CAC7B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEb,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,uBAAuB;IACvB,iCAAiC;IACjC,6CAA6C;IAC7C,uBAAuB;IACvB,uBAAuB;IACvB,uBAAuB;CACxB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEb,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,aAAa;IACb,cAAc;IACd,4BAA4B;IAC5B,UAAU;IACV,2CAA2C;IAC3C,kCAAkC;IAClC,uBAAuB;IACvB,cAAc;IACd,4BAA4B;IAC5B,iDAAiD;IACjD,wCAAwC;IACxC,2BAA2B;IAC3B,sBAAsB;IACtB,2DAA2D;CAC5D,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEb,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,aAAa;IACb,kBAAkB;IAClB,yBAAyB;IACzB,yBAAyB;IACzB,UAAU;IACV,uBAAuB;IACvB,uBAAuB;IACvB,uBAAuB;IACvB,6BAA6B;IAC7B,8DAA8D;IAC9D,4CAA4C;IAC5C,qCAAqC;IACrC,kCAAkC;IAClC,0CAA0C;CAC3C,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEb,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,mBAAmB;IACnB,8CAA8C;IAC9C,6DAA6D;IAC7D,qDAAqD;IACrD,iBAAiB;IACjB,sBAAsB;IACtB,mBAAmB;CACpB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"helpers.js","sourceRoot":"","sources":["../../../src/core/expr/helpers.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared Python expression lowering — framework-agnostic.
|
|
3
3
|
*/
|
|
4
|
-
export { KERN_FMT_HELPER_PY, KERN_I32_HELPER_PY, KERN_JS_HELPER_PY, KERN_PAIR_HELPERS_PY, KERN_TMOD_HELPER_PY, } from './helpers.js';
|
|
4
|
+
export { KERN_FMT_HELPER_PY, KERN_I32_HELPER_PY, KERN_JS_HELPER_PY, KERN_JS_OBJECT_HELPERS_PY, KERN_JS_STRING_HELPERS_PY, KERN_PAIR_HELPERS_PY, KERN_TMOD_HELPER_PY, } from './helpers.js';
|
|
5
5
|
export declare function quoteObjectKeysOutsideStrings(expr: string): string;
|
|
6
6
|
export declare function rewriteExpr(expr: string, pathParams: string[], bodyFields?: Set<string>, authUser?: boolean, imports?: Set<string>, hoistedDefs?: string[], closureSeq?: {
|
|
7
7
|
n: number;
|
package/dist/core/expr/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared Python expression lowering — framework-agnostic.
|
|
3
3
|
*/
|
|
4
|
-
import { lowerJsClosureBodyToPython } from '@kernlang/core';
|
|
4
|
+
import { lowerJsClosureBodyToPython, PORTABLE_LOGIC_PRIMITIVES } from '@kernlang/core';
|
|
5
5
|
import { toSnakeCase } from '../../type-map.js';
|
|
6
|
-
import { KERN_I32_HELPER_PY, KERN_JS_HELPER_PY, KERN_TMOD_HELPER_PY } from './helpers.js';
|
|
7
|
-
export { KERN_FMT_HELPER_PY, KERN_I32_HELPER_PY, KERN_JS_HELPER_PY, KERN_PAIR_HELPERS_PY, KERN_TMOD_HELPER_PY, } from './helpers.js';
|
|
6
|
+
import { KERN_I32_HELPER_PY, KERN_JS_HELPER_PY, KERN_JS_OBJECT_HELPERS_PY, KERN_JS_STRING_HELPERS_PY, KERN_TMOD_HELPER_PY, } from './helpers.js';
|
|
7
|
+
export { KERN_FMT_HELPER_PY, KERN_I32_HELPER_PY, KERN_JS_HELPER_PY, KERN_JS_OBJECT_HELPERS_PY, KERN_JS_STRING_HELPERS_PY, KERN_PAIR_HELPERS_PY, KERN_TMOD_HELPER_PY, } from './helpers.js';
|
|
8
8
|
// Quoted strings absorbed by the alternation; only literal `===`/`!==`
|
|
9
9
|
// outside strings get rewritten. Both single and double quotes AND
|
|
10
10
|
// backtick template literals are covered so a message like
|
|
@@ -520,6 +520,201 @@ function splitTopLevelArgs(inner) {
|
|
|
520
520
|
args.push(inner.slice(start).trim());
|
|
521
521
|
return args;
|
|
522
522
|
}
|
|
523
|
+
// ── Host-builtin lowering: Set.has / Date.getTime / logical-not ─────────────
|
|
524
|
+
// Portable-pure constructs lifted from the fitvt/job-central R1 audit. Math/
|
|
525
|
+
// Number/String/Array builtins are handled by the lower*BuiltinCalls passes
|
|
526
|
+
// above; these three were the residual gap (Set membership, epoch-ms dates,
|
|
527
|
+
// `!`). Same balanced-scan approach as the sibling passes.
|
|
528
|
+
function isCustomReceiverChar(c) {
|
|
529
|
+
return !!c && /[\w.]/.test(c);
|
|
530
|
+
}
|
|
531
|
+
function lowerSetOperandMemberRead(expr) {
|
|
532
|
+
const simple = expr.match(/^([A-Za-z_]\w*)\.([A-Za-z_]\w*)$/);
|
|
533
|
+
if (simple) {
|
|
534
|
+
const [, obj, field] = simple;
|
|
535
|
+
return `(${obj}.get("${field}") if isinstance(${obj}, dict) else ${obj}.${field})`;
|
|
536
|
+
}
|
|
537
|
+
const projection = expr.match(/^\[([A-Za-z_]\w*)\.([A-Za-z_]\w*) for \1 in ([\s\S]+)\]$/);
|
|
538
|
+
if (projection) {
|
|
539
|
+
const [, obj, field, source] = projection;
|
|
540
|
+
return `[${obj}.get("${field}") if isinstance(${obj}, dict) else ${obj}.${field} for ${obj} in ${source}]`;
|
|
541
|
+
}
|
|
542
|
+
return expr;
|
|
543
|
+
}
|
|
544
|
+
// new Set(arr).has(x) → (x) in set(arr). Runs AFTER array-method lowering so a
|
|
545
|
+
// `.map(...)` arg is already a comprehension.
|
|
546
|
+
function lowerSetHasCalls(expr, _imports) {
|
|
547
|
+
let out = '';
|
|
548
|
+
let i = 0;
|
|
549
|
+
let quote = null;
|
|
550
|
+
while (i < expr.length) {
|
|
551
|
+
const c = expr[i];
|
|
552
|
+
if (quote) {
|
|
553
|
+
out += c;
|
|
554
|
+
if (c === '\\') {
|
|
555
|
+
out += expr[i + 1] ?? '';
|
|
556
|
+
i += 2;
|
|
557
|
+
continue;
|
|
558
|
+
}
|
|
559
|
+
if (c === quote)
|
|
560
|
+
quote = null;
|
|
561
|
+
i += 1;
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
564
|
+
if (c === '"' || c === "'" || c === '`') {
|
|
565
|
+
quote = c;
|
|
566
|
+
out += c;
|
|
567
|
+
i += 1;
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
const m = expr.slice(i).match(/^new\s+Set\s*\(/);
|
|
571
|
+
if (m && !isCustomReceiverChar(expr[i - 1])) {
|
|
572
|
+
const setOpen = i + m[0].length - 1;
|
|
573
|
+
const setClose = matchBalancedParen(expr, setOpen);
|
|
574
|
+
const afterSet = setClose === -1 ? '' : expr.slice(setClose + 1);
|
|
575
|
+
const hasMatch = afterSet.match(/^\s*\.has\s*\(/);
|
|
576
|
+
if (setClose !== -1 && hasMatch) {
|
|
577
|
+
const hasOpen = setClose + 1 + hasMatch[0].length - 1;
|
|
578
|
+
const hasClose = matchBalancedParen(expr, hasOpen);
|
|
579
|
+
if (hasClose !== -1) {
|
|
580
|
+
// Recurse so nested `new Set(...)` inside the args lowers too.
|
|
581
|
+
const setArg = lowerSetOperandMemberRead(lowerSetHasCalls(expr.slice(setOpen + 1, setClose).trim()));
|
|
582
|
+
const hasArg = lowerSetOperandMemberRead(lowerSetHasCalls(expr.slice(hasOpen + 1, hasClose).trim()));
|
|
583
|
+
out += `(${hasArg}) in set(${setArg})`;
|
|
584
|
+
i = hasClose + 1;
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
out += c;
|
|
590
|
+
i += 1;
|
|
591
|
+
}
|
|
592
|
+
return out;
|
|
593
|
+
}
|
|
594
|
+
// new Date(arg).getTime() → epoch milliseconds. Runs BEFORE Math builtins so a
|
|
595
|
+
// surrounding Math.round sees an integer.
|
|
596
|
+
function lowerDateGetTimeCalls(expr, imports) {
|
|
597
|
+
let out = '';
|
|
598
|
+
let i = 0;
|
|
599
|
+
let quote = null;
|
|
600
|
+
while (i < expr.length) {
|
|
601
|
+
const c = expr[i];
|
|
602
|
+
if (quote) {
|
|
603
|
+
out += c;
|
|
604
|
+
if (c === '\\') {
|
|
605
|
+
out += expr[i + 1] ?? '';
|
|
606
|
+
i += 2;
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
if (c === quote)
|
|
610
|
+
quote = null;
|
|
611
|
+
i += 1;
|
|
612
|
+
continue;
|
|
613
|
+
}
|
|
614
|
+
if (c === '"' || c === "'" || c === '`') {
|
|
615
|
+
quote = c;
|
|
616
|
+
out += c;
|
|
617
|
+
i += 1;
|
|
618
|
+
continue;
|
|
619
|
+
}
|
|
620
|
+
const m = expr.slice(i).match(/^new\s+Date\s*\(/);
|
|
621
|
+
if (m && !isCustomReceiverChar(expr[i - 1])) {
|
|
622
|
+
const openIdx = i + m[0].length - 1;
|
|
623
|
+
const closeIdx = matchBalancedParen(expr, openIdx);
|
|
624
|
+
if (closeIdx !== -1 && expr.slice(closeIdx + 1).match(/^\s*\.getTime\s*\(\s*\)/)) {
|
|
625
|
+
const tail = expr.slice(closeIdx + 1).match(/^\s*\.getTime\s*\(\s*\)/)[0];
|
|
626
|
+
// Recurse so nested new Date(...).getTime() inside the arg lowers too.
|
|
627
|
+
const arg = lowerDateGetTimeCalls(expr.slice(openIdx + 1, closeIdx).trim(), imports);
|
|
628
|
+
imports?.add('from datetime import datetime, timezone');
|
|
629
|
+
// Branch on the runtime value: JS `new Date(n)` accepts epoch-ms numbers
|
|
630
|
+
// (getTime() returns n), else parse an ISO string. Case-insensitive Z.
|
|
631
|
+
// KNOWN LIMITATIONS (tracked follow-ups, beyond the R1 surface): a
|
|
632
|
+
// date-only string carrying a TZ offset ("2026-06-03Z") and non-ISO
|
|
633
|
+
// formats still raise in fromisoformat.
|
|
634
|
+
out +=
|
|
635
|
+
`(lambda __k_v: int(__k_v) if isinstance(__k_v, (int, float)) ` +
|
|
636
|
+
`else int((lambda __k_dt: (__k_dt if __k_dt.tzinfo is not None else __k_dt.replace(tzinfo=timezone.utc)).timestamp() * 1000)` +
|
|
637
|
+
`(datetime.fromisoformat(str(__k_v).replace("Z", "+00:00").replace("z", "+00:00")))))(${arg})`;
|
|
638
|
+
i = closeIdx + 1 + tail.length;
|
|
639
|
+
continue;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
out += c;
|
|
643
|
+
i += 1;
|
|
644
|
+
}
|
|
645
|
+
return out;
|
|
646
|
+
}
|
|
647
|
+
// `!` → Python `not `. Skips `!=`/`!==`. Runs after the operator/Set passes.
|
|
648
|
+
function lowerLogicalNot(expr, _imports) {
|
|
649
|
+
let out = '';
|
|
650
|
+
let i = 0;
|
|
651
|
+
let quote = null;
|
|
652
|
+
while (i < expr.length) {
|
|
653
|
+
const c = expr[i];
|
|
654
|
+
if (quote) {
|
|
655
|
+
out += c;
|
|
656
|
+
if (c === '\\') {
|
|
657
|
+
out += expr[i + 1] ?? '';
|
|
658
|
+
i += 2;
|
|
659
|
+
continue;
|
|
660
|
+
}
|
|
661
|
+
if (c === quote)
|
|
662
|
+
quote = null;
|
|
663
|
+
i += 1;
|
|
664
|
+
continue;
|
|
665
|
+
}
|
|
666
|
+
if (c === '"' || c === "'" || c === '`') {
|
|
667
|
+
quote = c;
|
|
668
|
+
out += c;
|
|
669
|
+
i += 1;
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
// KNOWN LIMITATION (tracked follow-up): `not` binds looser than comparison
|
|
673
|
+
// in Python, so `!a < b` (JS: `(!a) < b`) lowers to `not a < b` (Python:
|
|
674
|
+
// `not (a < b)`). Safe for the boolean-connective uses in the R1 surface.
|
|
675
|
+
if (c === '!' && expr[i + 1] !== '=') {
|
|
676
|
+
out += 'not ';
|
|
677
|
+
i += 1;
|
|
678
|
+
while (i < expr.length && /\s/.test(expr[i]))
|
|
679
|
+
i += 1;
|
|
680
|
+
continue;
|
|
681
|
+
}
|
|
682
|
+
out += c;
|
|
683
|
+
i += 1;
|
|
684
|
+
}
|
|
685
|
+
return out;
|
|
686
|
+
}
|
|
687
|
+
const PYTHON_PORTABLE_LOGIC_LOWERINGS = [
|
|
688
|
+
{
|
|
689
|
+
primitive: 'time.epochMs',
|
|
690
|
+
phase: 'beforeMath',
|
|
691
|
+
lower: lowerDateGetTimeCalls,
|
|
692
|
+
},
|
|
693
|
+
{
|
|
694
|
+
primitive: 'collection.has',
|
|
695
|
+
phase: 'afterArrayMethods',
|
|
696
|
+
lower: lowerSetHasCalls,
|
|
697
|
+
},
|
|
698
|
+
{
|
|
699
|
+
primitive: 'logic.not',
|
|
700
|
+
phase: 'final',
|
|
701
|
+
lower: lowerLogicalNot,
|
|
702
|
+
},
|
|
703
|
+
];
|
|
704
|
+
for (const entry of PYTHON_PORTABLE_LOGIC_LOWERINGS) {
|
|
705
|
+
if (PORTABLE_LOGIC_PRIMITIVES[entry.primitive].targets.python !== 'stable') {
|
|
706
|
+
throw new Error(`Portable logic primitive '${entry.primitive}' is not stable on the Python target.`);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
function lowerPortableLogicPrimitives(expr, imports, phase) {
|
|
710
|
+
let result = expr;
|
|
711
|
+
for (const entry of PYTHON_PORTABLE_LOGIC_LOWERINGS) {
|
|
712
|
+
if (entry.phase !== phase)
|
|
713
|
+
continue;
|
|
714
|
+
result = entry.lower(result, imports);
|
|
715
|
+
}
|
|
716
|
+
return result;
|
|
717
|
+
}
|
|
523
718
|
// Lower JSON.stringify(...) / JSON.parse(...) to json.dumps/loads. Uses a
|
|
524
719
|
// balanced, string-aware scan because the single argument can itself contain
|
|
525
720
|
// commas, nested parens, brackets, braces, or string literals.
|
|
@@ -920,7 +1115,7 @@ function lowerStringBuiltinCalls(expr) {
|
|
|
920
1115
|
});
|
|
921
1116
|
}
|
|
922
1117
|
// Lower the argument-taking JS String methods.
|
|
923
|
-
function lowerStringArgMethods(expr) {
|
|
1118
|
+
function lowerStringArgMethods(expr, imports) {
|
|
924
1119
|
let out = '';
|
|
925
1120
|
let i = 0;
|
|
926
1121
|
let quote = null;
|
|
@@ -950,9 +1145,18 @@ function lowerStringArgMethods(expr) {
|
|
|
950
1145
|
if (closeIdx !== -1) {
|
|
951
1146
|
const args = splitTopLevelArgs(expr.slice(openIdx + 1, closeIdx));
|
|
952
1147
|
if (args.length === 2 && !args[0].trim().startsWith('/')) {
|
|
953
|
-
const a0 = lowerStringArgMethods(args[0]).trim();
|
|
954
|
-
const a1 = lowerStringArgMethods(args[1]).trim();
|
|
955
|
-
|
|
1148
|
+
const a0 = lowerStringArgMethods(args[0], imports).trim();
|
|
1149
|
+
const a1 = lowerStringArgMethods(args[1], imports).trim();
|
|
1150
|
+
const receiverStart = findReceiverStart(out);
|
|
1151
|
+
if (receiverStart !== -1 && !isStringLiteralWithoutDollar(a1)) {
|
|
1152
|
+
imports?.add(KERN_JS_STRING_HELPERS_PY);
|
|
1153
|
+
const receiver = out.slice(receiverStart);
|
|
1154
|
+
const pre = out.slice(0, receiverStart);
|
|
1155
|
+
out = `${pre}_kern_js_replace(${receiver}, ${a0}, ${a1}, True)`;
|
|
1156
|
+
}
|
|
1157
|
+
else {
|
|
1158
|
+
out += `.replace(${a0}, ${a1})`;
|
|
1159
|
+
}
|
|
956
1160
|
i = closeIdx + 1;
|
|
957
1161
|
continue;
|
|
958
1162
|
}
|
|
@@ -964,9 +1168,18 @@ function lowerStringArgMethods(expr) {
|
|
|
964
1168
|
if (closeIdx !== -1) {
|
|
965
1169
|
const args = splitTopLevelArgs(expr.slice(openIdx + 1, closeIdx));
|
|
966
1170
|
if (args.length === 2 && !args[0].trim().startsWith('/')) {
|
|
967
|
-
const a0 = lowerStringArgMethods(args[0]).trim();
|
|
968
|
-
const a1 = lowerStringArgMethods(args[1]).trim();
|
|
969
|
-
|
|
1171
|
+
const a0 = lowerStringArgMethods(args[0], imports).trim();
|
|
1172
|
+
const a1 = lowerStringArgMethods(args[1], imports).trim();
|
|
1173
|
+
const receiverStart = findReceiverStart(out);
|
|
1174
|
+
if (receiverStart !== -1 && !isStringLiteralWithoutDollar(a1)) {
|
|
1175
|
+
imports?.add(KERN_JS_STRING_HELPERS_PY);
|
|
1176
|
+
const receiver = out.slice(receiverStart);
|
|
1177
|
+
const pre = out.slice(0, receiverStart);
|
|
1178
|
+
out = `${pre}_kern_js_replace(${receiver}, ${a0}, ${a1}, False)`;
|
|
1179
|
+
}
|
|
1180
|
+
else {
|
|
1181
|
+
out += `.replace(${a0}, ${a1}, 1)`;
|
|
1182
|
+
}
|
|
970
1183
|
i = closeIdx + 1;
|
|
971
1184
|
continue;
|
|
972
1185
|
}
|
|
@@ -1031,7 +1244,7 @@ function lowerStringArgMethods(expr) {
|
|
|
1031
1244
|
const openIdx = i + '.repeat('.length - 1;
|
|
1032
1245
|
const closeIdx = matchBalancedParen(expr, openIdx);
|
|
1033
1246
|
if (closeIdx !== -1) {
|
|
1034
|
-
const args = splitTopLevelArgs(expr.slice(openIdx + 1, closeIdx)).map((a) => lowerStringArgMethods(a).trim());
|
|
1247
|
+
const args = splitTopLevelArgs(expr.slice(openIdx + 1, closeIdx)).map((a) => lowerStringArgMethods(a, imports).trim());
|
|
1035
1248
|
const n = args[0] ?? '0';
|
|
1036
1249
|
const receiverStart = findReceiverStart(out);
|
|
1037
1250
|
if (receiverStart !== -1) {
|
|
@@ -1047,9 +1260,19 @@ function lowerStringArgMethods(expr) {
|
|
|
1047
1260
|
const openIdx = i + '.split('.length - 1;
|
|
1048
1261
|
const closeIdx = matchBalancedParen(expr, openIdx);
|
|
1049
1262
|
if (closeIdx !== -1) {
|
|
1050
|
-
const args = splitTopLevelArgs(expr.slice(openIdx + 1, closeIdx)).map((a) => lowerStringArgMethods(a).trim());
|
|
1263
|
+
const args = splitTopLevelArgs(expr.slice(openIdx + 1, closeIdx)).map((a) => lowerStringArgMethods(a, imports).trim());
|
|
1264
|
+
if (args.length >= 1 && isEmptyStringLiteral(args[0])) {
|
|
1265
|
+
const receiverStart = findReceiverStart(out);
|
|
1266
|
+
if (receiverStart !== -1) {
|
|
1267
|
+
const receiver = out.slice(receiverStart);
|
|
1268
|
+
const pre = out.slice(0, receiverStart);
|
|
1269
|
+
out = `${pre}list(${receiver})${args.length === 2 ? `[:${lowerSplitLimit(args[1], imports)}]` : ''}`;
|
|
1270
|
+
i = closeIdx + 1;
|
|
1271
|
+
continue;
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1051
1274
|
if (args.length === 2) {
|
|
1052
|
-
out += `.split(${args[0]})[:${args[1]}]`;
|
|
1275
|
+
out += `.split(${args[0]})[:${lowerSplitLimit(args[1], imports)}]`;
|
|
1053
1276
|
i = closeIdx + 1;
|
|
1054
1277
|
continue;
|
|
1055
1278
|
}
|
|
@@ -1060,6 +1283,20 @@ function lowerStringArgMethods(expr) {
|
|
|
1060
1283
|
}
|
|
1061
1284
|
return out;
|
|
1062
1285
|
}
|
|
1286
|
+
function isEmptyStringLiteral(expr) {
|
|
1287
|
+
const t = expr.trim();
|
|
1288
|
+
return t === '""' || t === "''" || t === '``';
|
|
1289
|
+
}
|
|
1290
|
+
function isStringLiteralWithoutDollar(expr) {
|
|
1291
|
+
const t = expr.trim();
|
|
1292
|
+
if (t.includes('$') || t.includes('\\'))
|
|
1293
|
+
return false;
|
|
1294
|
+
return /^(?:"[^"]*"|'[^']*'|`[^`]*`)$/.test(t);
|
|
1295
|
+
}
|
|
1296
|
+
function lowerSplitLimit(limit, imports) {
|
|
1297
|
+
imports?.add(KERN_JS_STRING_HELPERS_PY);
|
|
1298
|
+
return `_kern_js_split_limit(${limit})`;
|
|
1299
|
+
}
|
|
1063
1300
|
// Lower selected Object/Array/Date host builtins in portable expressions.
|
|
1064
1301
|
function lowerObjectArrayDateBuiltinCalls(expr, imports) {
|
|
1065
1302
|
let out = '';
|
|
@@ -1129,12 +1366,18 @@ function lowerObjectArrayDateBuiltinCalls(expr, imports) {
|
|
|
1129
1366
|
}
|
|
1130
1367
|
else {
|
|
1131
1368
|
const arg = lowerObjectArrayDateBuiltinCalls(rawArgs, imports).trim();
|
|
1132
|
-
if (method === 'Object.keys')
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
else if (method === 'Object.
|
|
1137
|
-
|
|
1369
|
+
if (method === 'Object.keys') {
|
|
1370
|
+
imports?.add(KERN_JS_OBJECT_HELPERS_PY);
|
|
1371
|
+
out += `_kern_js_object_keys(${arg})`;
|
|
1372
|
+
}
|
|
1373
|
+
else if (method === 'Object.values') {
|
|
1374
|
+
imports?.add(KERN_JS_OBJECT_HELPERS_PY);
|
|
1375
|
+
out += `_kern_js_object_values(${arg})`;
|
|
1376
|
+
}
|
|
1377
|
+
else if (method === 'Object.entries') {
|
|
1378
|
+
imports?.add(KERN_JS_OBJECT_HELPERS_PY);
|
|
1379
|
+
out += `_kern_js_object_entries(${arg})`;
|
|
1380
|
+
}
|
|
1138
1381
|
else
|
|
1139
1382
|
out += `isinstance(${arg}, list)`;
|
|
1140
1383
|
}
|
|
@@ -1786,11 +2029,14 @@ export function rewriteExpr(expr, pathParams, bodyFields = new Set(), authUser =
|
|
|
1786
2029
|
});
|
|
1787
2030
|
result = lowerPortableJsOperators(result, imports);
|
|
1788
2031
|
result = lowerJsonBuiltinCalls(result, imports);
|
|
2032
|
+
result = lowerPortableLogicPrimitives(result, imports, 'beforeMath'); // before Math: Math.round wraps date diffs
|
|
1789
2033
|
result = lowerMathBuiltinCalls(result, imports);
|
|
1790
2034
|
result = lowerNumberBuiltinCalls(result, imports);
|
|
1791
2035
|
result = lowerStringBuiltinCalls(result);
|
|
1792
|
-
result = lowerStringArgMethods(result);
|
|
2036
|
+
result = lowerStringArgMethods(result, imports);
|
|
1793
2037
|
result = lowerObjectArrayDateBuiltinCalls(result, imports);
|
|
2038
|
+
result = lowerPortableLogicPrimitives(result, imports, 'afterArrayMethods'); // Set arg may be a .map() comprehension
|
|
2039
|
+
result = lowerPortableLogicPrimitives(result, imports, 'final'); // applies to lowered membership/boolean
|
|
1794
2040
|
result = quoteObjectKeysOutsideStrings(result);
|
|
1795
2041
|
for (const replacement of replacements) {
|
|
1796
2042
|
result = result.split(replacement.placeholder).join(replacement.lowered);
|
|
@@ -1960,7 +2206,7 @@ function parseTokens(tokens) {
|
|
|
1960
2206
|
let left = parsePrimary();
|
|
1961
2207
|
while (true) {
|
|
1962
2208
|
const next = peek();
|
|
1963
|
-
if (
|
|
2209
|
+
if (next?.type !== 'OP')
|
|
1964
2210
|
break;
|
|
1965
2211
|
const opPrecedence = getPrecedence(next.value);
|
|
1966
2212
|
if (opPrecedence < precedence)
|