@kernlang/python 3.5.9-canary.218.1.77aa9b0c → 4.0.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/dist/codegen-body-python.d.ts +86 -0
- package/dist/codegen-body-python.js +894 -28
- package/dist/codegen-body-python.js.map +1 -1
- package/dist/codegen-helpers.d.ts +1 -0
- package/dist/codegen-helpers.js +19 -6
- package/dist/codegen-helpers.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 +1 -0
- package/dist/core/expr/helpers.js +109 -2
- package/dist/core/expr/helpers.js.map +1 -1
- package/dist/core/expr/index.d.ts +1 -1
- package/dist/core/expr/index.js +214 -87
- package/dist/core/expr/index.js.map +1 -1
- package/dist/core/expr/list-ops.d.ts +81 -0
- package/dist/core/expr/list-ops.js +228 -0
- package/dist/core/expr/list-ops.js.map +1 -0
- package/dist/core/handlers/index.js +1 -0
- package/dist/core/handlers/index.js.map +1 -1
- package/dist/fastapi-portable.js +1 -0
- package/dist/fastapi-portable.js.map +1 -1
- package/dist/fastapi-route.js +132 -24
- package/dist/fastapi-route.js.map +1 -1
- package/dist/generators/data.d.ts +2 -0
- package/dist/generators/data.js +417 -14
- package/dist/generators/data.js.map +1 -1
- package/dist/generators/ground.js +52 -14
- package/dist/generators/ground.js.map +1 -1
- package/dist/targets/python.js +10 -0
- package/dist/targets/python.js.map +1 -1
- package/dist/type-map.d.ts +15 -0
- package/dist/type-map.js +46 -0
- package/dist/type-map.js.map +1 -1
- package/package.json +2 -2
package/dist/core/expr/index.js
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
*/
|
|
4
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_JS_OBJECT_HELPERS_PY, KERN_JS_STRING_HELPERS_PY, KERN_TMOD_HELPER_PY, } from './helpers.js';
|
|
7
|
-
|
|
6
|
+
import { KERN_I32_HELPER_PY, KERN_JS_ARRAY_HELPERS_PY, KERN_JS_HELPER_PY, KERN_JS_OBJECT_HELPERS_PY, KERN_JS_STRING_HELPERS_PY, KERN_TMOD_HELPER_PY, } from './helpers.js';
|
|
7
|
+
import { isSharedPortableArrayMethod, isSharedPortableArrayProperty, lowerPortableArrayMethodPy, lowerPortableArrayPropertyPy, } from './list-ops.js';
|
|
8
|
+
export { KERN_FMT_HELPER_PY, KERN_I32_HELPER_PY, KERN_JS_ARRAY_HELPERS_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
9
|
// Quoted strings absorbed by the alternation; only literal `===`/`!==`
|
|
9
10
|
// outside strings get rewritten. Both single and double quotes AND
|
|
10
11
|
// backtick template literals are covered so a message like
|
|
@@ -194,12 +195,28 @@ function lowerArrowBlockClosure(arrow, ctx) {
|
|
|
194
195
|
const result = lowerJsClosureBodyToPython(arrow.body, {
|
|
195
196
|
lowerExpression: (raw) => rewriteExpr(lowerDictMemberAccess(raw, arrow.params[0]), ctx.pathParams, ctx.bodyFields, ctx.authUser, ctx.imports, undefined, ctx.closureSeq),
|
|
196
197
|
lowerCondition: (raw) => `js_truthy(${rewriteExpr(lowerDictMemberAccess(raw, arrow.params[0]), ctx.pathParams, ctx.bodyFields, ctx.authUser, ctx.imports, undefined, ctx.closureSeq)})`,
|
|
198
|
+
// Closure params are def-locals (never `nonlocal`); the lowerer excludes
|
|
199
|
+
// them and block-locals from the written-free set.
|
|
200
|
+
paramNames: arrow.params,
|
|
197
201
|
});
|
|
198
202
|
if (!result.ok)
|
|
199
203
|
return null;
|
|
200
204
|
ctx.imports?.add(KERN_JS_HELPER_PY);
|
|
201
205
|
const params = arrow.params.join(', ');
|
|
202
|
-
|
|
206
|
+
// Mutation v1 — free-variable WRITES need `nonlocal`. The route hoisted def
|
|
207
|
+
// nests INSIDE the route handler function, so a free capture that the closure
|
|
208
|
+
// writes is a handler-local (a `derive`/method-local). Unlike the class/
|
|
209
|
+
// native path (`emitBlockClosurePy`), the route path has NO loop-pinning
|
|
210
|
+
// concept — every written free name is an outer handler binding and ALL of
|
|
211
|
+
// them get a `nonlocal` declaration. Without it the def shadows the name and
|
|
212
|
+
// raises `UnboundLocalError` (read+write) or silently writes a dead local
|
|
213
|
+
// (write-only) — a live route bug this fixes. `nonlocal` is the def's FIRST
|
|
214
|
+
// body statement (Python requires it before any use). Member/index writes
|
|
215
|
+
// never appear in `writtenFreeNames` (by-reference mutation needs no decl).
|
|
216
|
+
const sortedFreeWrites = [...result.writtenFreeNames].sort();
|
|
217
|
+
const nonlocalLines = sortedFreeWrites.length > 0 ? [` nonlocal ${sortedFreeWrites.join(', ')}`] : [];
|
|
218
|
+
const bodyLines = result.lines.length > 0 ? result.lines : [' pass'];
|
|
219
|
+
const def = [`def ${name}(${params}):`, ...nonlocalLines, ...bodyLines].join('\n');
|
|
203
220
|
if (ctx.hoistedDefs) {
|
|
204
221
|
ctx.hoistedDefs.push(def);
|
|
205
222
|
}
|
|
@@ -255,29 +272,35 @@ function lowerJsArrayMethods(expr, ctx) {
|
|
|
255
272
|
// name.
|
|
256
273
|
const loopTarget = idxVar ? `${idxVar}, ${elemVar}` : elemVar;
|
|
257
274
|
const source = idxVar ? `enumerate(${receiver})` : receiver;
|
|
275
|
+
// filter/find-family predicates wrap the body in `js_truthy(...)`:
|
|
276
|
+
// a predicate that yields a JS-truthy empty container ([] / {}) must be
|
|
277
|
+
// KEPT, but Python treats [] / {} as falsy, so a bare `if body` would
|
|
278
|
+
// wrongly drop it. `js_truthy` restores JS truthiness. (`map`/`flatMap`
|
|
279
|
+
// have no predicate and are left untouched.) The helper lands once.
|
|
280
|
+
ctx.imports?.add(KERN_JS_HELPER_PY);
|
|
258
281
|
let lowered;
|
|
259
282
|
if (method === 'filter') {
|
|
260
|
-
lowered = `[${elemVar} for ${loopTarget} in ${source} if ${body}]`;
|
|
283
|
+
lowered = `[${elemVar} for ${loopTarget} in ${source} if js_truthy(${body})]`;
|
|
261
284
|
}
|
|
262
285
|
else if (method === 'find') {
|
|
263
|
-
lowered = `next((${elemVar} for ${loopTarget} in ${source} if ${body}), None)`;
|
|
286
|
+
lowered = `next((${elemVar} for ${loopTarget} in ${source} if js_truthy(${body})), None)`;
|
|
264
287
|
}
|
|
265
288
|
else if (method === 'findIndex') {
|
|
266
289
|
// index of the first match, or -1 (never raises). Bind the user's
|
|
267
290
|
// own index var when the callback has one, so `(x, i) => …i…` works.
|
|
268
291
|
const ix = idxVar ?? '__i';
|
|
269
|
-
lowered = `next((${ix} for ${ix}, ${elemVar} in enumerate(${receiver}) if ${body}), -1)`;
|
|
292
|
+
lowered = `next((${ix} for ${ix}, ${elemVar} in enumerate(${receiver}) if js_truthy(${body})), -1)`;
|
|
270
293
|
}
|
|
271
294
|
else if (method === 'findLast') {
|
|
272
295
|
// last matching element, or None
|
|
273
296
|
lowered = idxVar
|
|
274
|
-
? `next((${elemVar} for ${idxVar}, ${elemVar} in reversed(list(enumerate(${receiver}))) if ${body}), None)`
|
|
275
|
-
: `next((${elemVar} for ${elemVar} in reversed(${receiver}) if ${body}), None)`;
|
|
297
|
+
? `next((${elemVar} for ${idxVar}, ${elemVar} in reversed(list(enumerate(${receiver}))) if js_truthy(${body})), None)`
|
|
298
|
+
: `next((${elemVar} for ${elemVar} in reversed(${receiver}) if js_truthy(${body})), None)`;
|
|
276
299
|
}
|
|
277
300
|
else if (method === 'findLastIndex') {
|
|
278
301
|
// index of the last match, or -1
|
|
279
302
|
const ix = idxVar ?? '__i';
|
|
280
|
-
lowered = `next((${ix} for ${ix}, ${elemVar} in reversed(list(enumerate(${receiver}))) if ${body}), -1)`;
|
|
303
|
+
lowered = `next((${ix} for ${ix}, ${elemVar} in reversed(list(enumerate(${receiver}))) if js_truthy(${body})), -1)`;
|
|
281
304
|
}
|
|
282
305
|
else if (method === 'flatMap') {
|
|
283
306
|
// map, then flatten ONE level — JS flatMap only flattens arrays, so
|
|
@@ -303,57 +326,29 @@ function lowerJsArrayMethods(expr, ctx) {
|
|
|
303
326
|
if (closeIdx !== -1 && recvStart !== -1) {
|
|
304
327
|
const receiver = out.slice(recvStart);
|
|
305
328
|
const pre = out.slice(0, recvStart);
|
|
306
|
-
const
|
|
329
|
+
const rawArgs = splitTopLevelArgs(expr.slice(openIdx + 1, closeIdx));
|
|
330
|
+
const args = rawArgs.map((a) => method === 'fill' ? lowerJsFillArgument(a.trim(), ctx) : lowerJsArrayMethods(a.trim(), ctx));
|
|
307
331
|
let lowered = null;
|
|
308
|
-
if (method
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
332
|
+
if (isSharedPortableArrayMethod(method)) {
|
|
333
|
+
// Delegate the argument-shape (non-lambda) scalar methods —
|
|
334
|
+
// push/slice/concat/includes/indexOf/join/flat/reverse/at/fill/
|
|
335
|
+
// lastIndexOf — to the single shared list-ops lowering (also used by
|
|
336
|
+
// the class-method body emitter) so routes and class methods can't
|
|
337
|
+
// drift. The shared helper returns null for arg-count shapes it
|
|
338
|
+
// doesn't support (e.g. multi-arg concat), and `lowered` stays null so
|
|
339
|
+
// the caller falls through unchanged — the same gap as the pre-sweep
|
|
340
|
+
// inline branches. The method names here are disjoint from the
|
|
341
|
+
// lambda-bearing some/every/reduce/reduceRight/sort branches below, so
|
|
342
|
+
// chain order does not matter.
|
|
343
|
+
const imports = ctx.imports;
|
|
344
|
+
const portable = method === 'fill' && !imports ? null : lowerPortableArrayMethodPy(receiver, method, args);
|
|
345
|
+
if (portable !== null) {
|
|
346
|
+
if (method === 'fill') {
|
|
347
|
+
imports?.add(KERN_JS_ARRAY_HELPERS_PY);
|
|
348
|
+
}
|
|
349
|
+
lowered = portable;
|
|
320
350
|
}
|
|
321
351
|
}
|
|
322
|
-
else if (method === 'push') {
|
|
323
|
-
// JS Array.push mutates AND returns the new length. Python list.append
|
|
324
|
-
// returns None, so emit `(recv.append(x) or len(recv))` for exact parity
|
|
325
|
-
// (mutate + length). Single-arg only; varargs push left unsupported.
|
|
326
|
-
if (args.length === 1)
|
|
327
|
-
lowered = `(${receiver}.append(${args[0]}) or len(${receiver}))`;
|
|
328
|
-
}
|
|
329
|
-
else if (method === 'reverse') {
|
|
330
|
-
// JS Array.reverse mutates AND returns the (same, reversed) array; Python
|
|
331
|
-
// list.reverse returns None -> `(recv.reverse() or recv)` mutates + returns it.
|
|
332
|
-
lowered = `(${receiver}.reverse() or ${receiver})`;
|
|
333
|
-
}
|
|
334
|
-
else if (method === 'concat') {
|
|
335
|
-
// JS Array.concat returns a NEW array; an array arg is spread, a scalar arg
|
|
336
|
-
// is appended. Mirror with `recv + (x if isinstance(x, list) else [x])`.
|
|
337
|
-
// Single-arg only; varargs concat left unsupported.
|
|
338
|
-
if (args.length === 1)
|
|
339
|
-
lowered = `(${receiver} + (${args[0]} if isinstance(${args[0]}, list) else [${args[0]}]))`;
|
|
340
|
-
}
|
|
341
|
-
else if (method === 'join') {
|
|
342
|
-
const sep = args[0] ?? '","';
|
|
343
|
-
lowered = `${sep}.join(str(__v) for __v in ${receiver})`;
|
|
344
|
-
}
|
|
345
|
-
else if (method === 'slice') {
|
|
346
|
-
const start = args[0];
|
|
347
|
-
const end = args[1];
|
|
348
|
-
if (!start && !end)
|
|
349
|
-
lowered = `${receiver}[:]`;
|
|
350
|
-
else if (start && !end)
|
|
351
|
-
lowered = `${receiver}[${start}:]`;
|
|
352
|
-
else if (!start && end)
|
|
353
|
-
lowered = `${receiver}[:${end}]`;
|
|
354
|
-
else
|
|
355
|
-
lowered = `${receiver}[${start}:${end}]`;
|
|
356
|
-
}
|
|
357
352
|
else if (method === 'some' || method === 'every') {
|
|
358
353
|
const arrow = parseArrowCallback(expr.slice(openIdx + 1, closeIdx));
|
|
359
354
|
if (arrow && arrow.params.length >= 1) {
|
|
@@ -365,10 +360,17 @@ function lowerJsArrayMethods(expr, ctx) {
|
|
|
365
360
|
const pred = blockClosure ?? lowerJsArrayMethods(lowerDictMemberAccess(arrow.body, elemVar), ctx);
|
|
366
361
|
const loopTarget = idxVar ? `${idxVar}, ${elemVar}` : elemVar;
|
|
367
362
|
const source = idxVar ? `enumerate(${receiver})` : receiver;
|
|
363
|
+
// Wrap the predicate in `js_truthy(...)` for JS truthiness parity (a
|
|
364
|
+
// predicate yielding [] / {} is JS-truthy but Python-falsy). Skip the
|
|
365
|
+
// wrap when `pred` is ALREADY a js_truthy(...) call — the block-closure
|
|
366
|
+
// path's `lowerCondition` can emit one — to avoid emit-noise double
|
|
367
|
+
// wrapping (harmless but ugly). The helper lands once.
|
|
368
|
+
const wrappedPred = pred.startsWith('js_truthy(') ? pred : `js_truthy(${pred})`;
|
|
369
|
+
ctx.imports?.add(KERN_JS_HELPER_PY);
|
|
368
370
|
lowered =
|
|
369
371
|
method === 'some'
|
|
370
|
-
? `any(${
|
|
371
|
-
: `all(${
|
|
372
|
+
? `any(${wrappedPred} for ${loopTarget} in ${source})`
|
|
373
|
+
: `all(${wrappedPred} for ${loopTarget} in ${source})`;
|
|
372
374
|
}
|
|
373
375
|
}
|
|
374
376
|
else if (method === 'reduce') {
|
|
@@ -425,33 +427,6 @@ function lowerJsArrayMethods(expr, ctx) {
|
|
|
425
427
|
lowered = `sorted(${receiver}, key=lambda __v${LAMBDA_COLON_PLACEHOLDER} str(__v))`;
|
|
426
428
|
}
|
|
427
429
|
}
|
|
428
|
-
else if (method === 'flat') {
|
|
429
|
-
// one level: flatten nested lists, keep scalars
|
|
430
|
-
lowered = `[__y for __x in ${receiver} for __y in (__x if isinstance(__x, list) else [__x])]`;
|
|
431
|
-
}
|
|
432
|
-
else if (method === 'at') {
|
|
433
|
-
const n = args[0] ?? '0';
|
|
434
|
-
lowered = `(${receiver}[${n}] if -len(${receiver}) <= ${n} < len(${receiver}) else None)`;
|
|
435
|
-
}
|
|
436
|
-
else if (method === 'fill') {
|
|
437
|
-
const v = args[0] ?? 'None';
|
|
438
|
-
if (args.length <= 1) {
|
|
439
|
-
lowered = `[${v} for __ in ${receiver}]`;
|
|
440
|
-
}
|
|
441
|
-
else {
|
|
442
|
-
// fill(value, start, end) fills [start, end) with JS negative-index
|
|
443
|
-
// normalization; untouched positions keep their original element.
|
|
444
|
-
const s = args[1];
|
|
445
|
-
const e = args[2] ?? `len(${receiver})`;
|
|
446
|
-
lowered = `[(${v} if (${s} if ${s} >= 0 else ${s} + len(${receiver})) <= __i < (${e} if ${e} >= 0 else ${e} + len(${receiver})) else __x) for __i, __x in enumerate(${receiver})]`;
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
else if (method === 'lastIndexOf') {
|
|
450
|
-
const needle = args[0] ?? '';
|
|
451
|
-
// String receivers use rfind (correct for multi-char substrings, -1
|
|
452
|
-
// when absent); array receivers reverse-scan by element equality.
|
|
453
|
-
lowered = `(${receiver}.rfind(${needle}) if isinstance(${receiver}, str) else (len(${receiver}) - 1 - ${receiver}[::-1].index(${needle}) if ${needle} in ${receiver} else -1))`;
|
|
454
|
-
}
|
|
455
430
|
if (lowered) {
|
|
456
431
|
out = `${pre}${lowered}`;
|
|
457
432
|
i = closeIdx + 1;
|
|
@@ -459,6 +434,26 @@ function lowerJsArrayMethods(expr, ctx) {
|
|
|
459
434
|
}
|
|
460
435
|
}
|
|
461
436
|
}
|
|
437
|
+
// Portable Array *property* access (non-call `.length`). Matched only when
|
|
438
|
+
// NOT immediately followed by `(` (the method scan above owns call forms),
|
|
439
|
+
// so `arr.length` lowers to `len(arr)` while a hypothetical `arr.length(...)`
|
|
440
|
+
// call is left for the method path. Receiver taken from already-emitted
|
|
441
|
+
// `out` like the method path, so chained forms (`arr.slice(1).length`)
|
|
442
|
+
// compose naturally.
|
|
443
|
+
const mProp = expr.slice(i).match(/^\.([A-Za-z]\w*)(?!\s*\()/);
|
|
444
|
+
if (mProp && isSharedPortableArrayProperty(mProp[1])) {
|
|
445
|
+
const recvStart = findReceiverStart(out);
|
|
446
|
+
if (recvStart !== -1) {
|
|
447
|
+
const receiver = out.slice(recvStart);
|
|
448
|
+
const pre = out.slice(0, recvStart);
|
|
449
|
+
const lowered = lowerPortableArrayPropertyPy(receiver, mProp[1]);
|
|
450
|
+
if (lowered !== null) {
|
|
451
|
+
out = `${pre}${lowered}`;
|
|
452
|
+
i += mProp[0].length;
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
462
457
|
out += c;
|
|
463
458
|
i += 1;
|
|
464
459
|
}
|
|
@@ -490,6 +485,134 @@ function matchBalancedParen(expr, openIdx) {
|
|
|
490
485
|
}
|
|
491
486
|
return -1;
|
|
492
487
|
}
|
|
488
|
+
function stripOuterParens(raw) {
|
|
489
|
+
let trimmed = raw.trim();
|
|
490
|
+
while (trimmed.startsWith('(')) {
|
|
491
|
+
const close = matchBalancedParen(trimmed, 0);
|
|
492
|
+
if (close !== trimmed.length - 1)
|
|
493
|
+
break;
|
|
494
|
+
trimmed = trimmed.slice(1, -1).trim();
|
|
495
|
+
}
|
|
496
|
+
return trimmed;
|
|
497
|
+
}
|
|
498
|
+
function exactVoidOperand(raw) {
|
|
499
|
+
const trimmed = stripOuterParens(raw);
|
|
500
|
+
if (!trimmed.startsWith('void'))
|
|
501
|
+
return null;
|
|
502
|
+
const rest = trimmed.slice('void'.length);
|
|
503
|
+
if (rest === '' || (!/^\s/.test(rest) && !rest.startsWith('(')))
|
|
504
|
+
return null;
|
|
505
|
+
const operand = rest.trim();
|
|
506
|
+
if (!operand)
|
|
507
|
+
return null;
|
|
508
|
+
if (operand.startsWith('(')) {
|
|
509
|
+
const close = matchBalancedParen(operand, 0);
|
|
510
|
+
return close === operand.length - 1 ? operand : null;
|
|
511
|
+
}
|
|
512
|
+
let depth = 0;
|
|
513
|
+
let quote = null;
|
|
514
|
+
for (let i = 0; i < operand.length; i += 1) {
|
|
515
|
+
const c = operand[i];
|
|
516
|
+
if (quote) {
|
|
517
|
+
if (c === '\\')
|
|
518
|
+
i += 1;
|
|
519
|
+
else if (c === quote)
|
|
520
|
+
quote = null;
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
if (c === '"' || c === "'" || c === '`') {
|
|
524
|
+
quote = c;
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
if (c === '(' || c === '[' || c === '{') {
|
|
528
|
+
depth += 1;
|
|
529
|
+
continue;
|
|
530
|
+
}
|
|
531
|
+
if (c === ')' || c === ']' || c === '}') {
|
|
532
|
+
depth -= 1;
|
|
533
|
+
if (depth < 0)
|
|
534
|
+
return null;
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
537
|
+
if (depth === 0 && /[,+\-*/%|&^?:<>=]/.test(c) && !(i === 0 && /[+-]/.test(c)))
|
|
538
|
+
return null;
|
|
539
|
+
}
|
|
540
|
+
return depth === 0 && !quote ? operand : null;
|
|
541
|
+
}
|
|
542
|
+
function rewriteExprInContext(raw, ctx) {
|
|
543
|
+
return rewriteExpr(raw, ctx.pathParams, ctx.bodyFields, ctx.authUser, ctx.imports, ctx.hoistedDefs, ctx.closureSeq);
|
|
544
|
+
}
|
|
545
|
+
function lowerLogicalAndOr(expr) {
|
|
546
|
+
let out = '';
|
|
547
|
+
let i = 0;
|
|
548
|
+
let quote = null;
|
|
549
|
+
while (i < expr.length) {
|
|
550
|
+
const c = expr[i];
|
|
551
|
+
if (quote) {
|
|
552
|
+
out += c;
|
|
553
|
+
if (c === '\\') {
|
|
554
|
+
out += expr[i + 1] ?? '';
|
|
555
|
+
i += 2;
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
558
|
+
if (c === quote)
|
|
559
|
+
quote = null;
|
|
560
|
+
i += 1;
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
563
|
+
if (c === '"' || c === "'" || c === '`') {
|
|
564
|
+
quote = c;
|
|
565
|
+
out += c;
|
|
566
|
+
i += 1;
|
|
567
|
+
continue;
|
|
568
|
+
}
|
|
569
|
+
if (expr.startsWith('&&', i)) {
|
|
570
|
+
out += ' and ';
|
|
571
|
+
i += 2;
|
|
572
|
+
continue;
|
|
573
|
+
}
|
|
574
|
+
if (expr.startsWith('||', i)) {
|
|
575
|
+
out += ' or ';
|
|
576
|
+
i += 2;
|
|
577
|
+
continue;
|
|
578
|
+
}
|
|
579
|
+
out += c;
|
|
580
|
+
i += 1;
|
|
581
|
+
}
|
|
582
|
+
return out;
|
|
583
|
+
}
|
|
584
|
+
function lowerJsVoidExpression(raw, ctx) {
|
|
585
|
+
const voidOperand = exactVoidOperand(raw);
|
|
586
|
+
if (voidOperand === null) {
|
|
587
|
+
throw new Error('Array.fill void argument lowering expects an exact unary void expression; bind complex void expressions first.');
|
|
588
|
+
}
|
|
589
|
+
return `(${lowerJsVoidOperand(voidOperand, ctx)}, _KERN_UNDEFINED)[1]`;
|
|
590
|
+
}
|
|
591
|
+
function lowerJsVoidOperand(raw, ctx) {
|
|
592
|
+
const nestedVoidOperand = exactVoidOperand(raw);
|
|
593
|
+
if (nestedVoidOperand !== null) {
|
|
594
|
+
return lowerJsVoidExpression(raw, ctx);
|
|
595
|
+
}
|
|
596
|
+
return lowerLogicalAndOr(rewriteExprInContext(raw, ctx));
|
|
597
|
+
}
|
|
598
|
+
function lowerJsFillArgument(raw, ctx) {
|
|
599
|
+
const trimmed = stripOuterParens(raw);
|
|
600
|
+
if (trimmed === 'undefined')
|
|
601
|
+
return '_KERN_UNDEFINED';
|
|
602
|
+
const voidOperand = exactVoidOperand(trimmed);
|
|
603
|
+
if (voidOperand !== null) {
|
|
604
|
+
return `(${lowerJsVoidOperand(voidOperand, ctx)}, _KERN_UNDEFINED)[1]`;
|
|
605
|
+
}
|
|
606
|
+
// Fail-closed ONLY on a true `void` OPERATOR with a complex operand
|
|
607
|
+
// (exactVoidOperand already rejected it above). A bare identifier that
|
|
608
|
+
// merely STARTS with "void" (voidValue, voidFn()) is ordinary code and
|
|
609
|
+
// must lower normally — `void` is followed by whitespace or `(` when it
|
|
610
|
+
// is the operator.
|
|
611
|
+
if (/^void(?:\s|\()/.test(trimmed)) {
|
|
612
|
+
return lowerJsVoidExpression(trimmed, ctx);
|
|
613
|
+
}
|
|
614
|
+
return rewriteExprInContext(trimmed, ctx);
|
|
615
|
+
}
|
|
493
616
|
// Split a call's inner argument text on top-level commas, ignoring commas
|
|
494
617
|
// inside nested ()[]{} or string literals.
|
|
495
618
|
function splitTopLevelArgs(inner) {
|
|
@@ -1969,7 +2092,11 @@ export function rewriteExpr(expr, pathParams, bodyFields = new Set(), authUser =
|
|
|
1969
2092
|
const tokens = tokenizeJSExpr(expr);
|
|
1970
2093
|
const comparisonProbe = expr.replace(/>>>|>>|<</g, '');
|
|
1971
2094
|
const hasLooseComparison = /(?:===|!==|==|!=|<=|>=|<|>)/.test(comparisonProbe);
|
|
1972
|
-
const
|
|
2095
|
+
const hasVoidOperator = /\bvoid\b/.test(expr);
|
|
2096
|
+
const hasBitwiseOrModulo = !hasVoidOperator &&
|
|
2097
|
+
!expr.includes('=>') &&
|
|
2098
|
+
!hasLooseComparison &&
|
|
2099
|
+
tokens.some((t) => t.type === 'UNARY' || t.type === 'OP');
|
|
1973
2100
|
if (hasBitwiseOrModulo) {
|
|
1974
2101
|
const ast = parseTokens(tokens);
|
|
1975
2102
|
expr = codegenASTToPython(ast, imports);
|