@kernlang/python 3.5.6-canary.196.1.72010f21 → 3.5.6
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 +37 -0
- package/dist/codegen-body-python.js +239 -0
- package/dist/codegen-body-python.js.map +1 -1
- package/dist/codegen-python.d.ts +1 -0
- package/dist/codegen-python.js +1 -1
- package/dist/codegen-python.js.map +1 -1
- package/dist/core/emit-imports.d.ts +11 -0
- package/dist/core/emit-imports.js +166 -0
- package/dist/core/emit-imports.js.map +1 -0
- package/dist/core/emit-models.d.ts +14 -0
- package/dist/core/emit-models.js +86 -0
- package/dist/core/emit-models.js.map +1 -0
- package/dist/core/fence-diagnostics.d.ts +17 -0
- package/dist/core/fence-diagnostics.js +86 -0
- package/dist/core/fence-diagnostics.js.map +1 -0
- package/dist/core/index.d.ts +3 -0
- package/dist/core/index.js +4 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/type-mapper.d.ts +4 -0
- package/dist/core/type-mapper.js +8 -0
- package/dist/core/type-mapper.js.map +1 -0
- package/dist/fastapi-portable.d.ts +7 -1
- package/dist/fastapi-portable.js +95 -73
- package/dist/fastapi-portable.js.map +1 -1
- package/dist/fastapi-response.d.ts +3 -1
- package/dist/fastapi-response.js +374 -26
- package/dist/fastapi-response.js.map +1 -1
- package/dist/generators/data.d.ts +3 -1
- package/dist/generators/data.js +28 -1
- package/dist/generators/data.js.map +1 -1
- package/dist/index.d.ts +2 -3
- package/dist/index.js +2 -3
- package/dist/index.js.map +1 -1
- package/dist/targets/python.d.ts +5 -0
- package/dist/targets/python.js +50 -0
- package/dist/targets/python.js.map +1 -0
- package/dist/transpiler-fastapi.js +23 -66
- package/dist/transpiler-fastapi.js.map +1 -1
- package/package.json +2 -2
package/dist/fastapi-response.js
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* extractExprCode — extract expression code from IR prop
|
|
7
7
|
* addRespondImports — add necessary imports for respond node
|
|
8
8
|
*/
|
|
9
|
-
import { getProps } from '@kernlang/core';
|
|
9
|
+
import { getProps, lowerJsClosureBodyToPython } from '@kernlang/core';
|
|
10
|
+
import { KERN_I32_HELPER_PY, KERN_JS_HELPER_PY, KERN_TMOD_HELPER_PY } from './codegen-body-python.js';
|
|
10
11
|
import { escapePyStr, quoteObjectKeysOutsideStrings } from './fastapi-utils.js';
|
|
11
12
|
import { toSnakeCase } from './type-map.js';
|
|
12
13
|
export function generateRespondFastAPI(respondNode, indent) {
|
|
@@ -111,7 +112,7 @@ function lowerDictMemberAccess(text, varName) {
|
|
|
111
112
|
// (codex review of ab192611). A lone `x.method()` is unchanged.
|
|
112
113
|
const dataFields = fields.slice(0, -1);
|
|
113
114
|
const methodField = fields[fields.length - 1];
|
|
114
|
-
out += varName + dataFields.map((field) => `[${JSON.stringify(field)}]`).join('')
|
|
115
|
+
out += `${varName + dataFields.map((field) => `[${JSON.stringify(field)}]`).join('')}.${methodField}`;
|
|
115
116
|
}
|
|
116
117
|
else {
|
|
117
118
|
out += varName + fields.map((field) => `[${JSON.stringify(field)}]`).join('');
|
|
@@ -125,6 +126,53 @@ function lowerDictMemberAccess(text, varName) {
|
|
|
125
126
|
}
|
|
126
127
|
return out;
|
|
127
128
|
}
|
|
129
|
+
// A statement body that is EXACTLY `{ return E; }` is semantically identical to the
|
|
130
|
+
// expression body `E`, so unwrap it to reuse the expression-bodied lowering (slice 1 of
|
|
131
|
+
// native closures, #5). Richer statement bodies (locals, control flow, side effects
|
|
132
|
+
// before the return) need full closure lowering (hoisted nested defs) and are NOT handled
|
|
133
|
+
// here — they stay untouched (still unsupported) rather than mis-lowered. The scan is
|
|
134
|
+
// string/bracket-aware so `{ return f({a:1}); }` unwraps but `{ return a; more(); }` does not.
|
|
135
|
+
function unwrapSingleReturnBlock(body) {
|
|
136
|
+
const t = body.trim();
|
|
137
|
+
if (t.length < 2 || t[0] !== '{' || t[t.length - 1] !== '}')
|
|
138
|
+
return body;
|
|
139
|
+
const topLevelBreaks = (s, breakOnSemicolon) => {
|
|
140
|
+
let depth = 0;
|
|
141
|
+
let inStr = null;
|
|
142
|
+
for (let i = 0; i < s.length; i++) {
|
|
143
|
+
const c = s[i];
|
|
144
|
+
if (inStr) {
|
|
145
|
+
if (c === inStr && s[i - 1] !== '\\')
|
|
146
|
+
inStr = null;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (c === '"' || c === "'" || c === '`')
|
|
150
|
+
inStr = c;
|
|
151
|
+
else if (c === '{' || c === '(' || c === '[')
|
|
152
|
+
depth++;
|
|
153
|
+
else if (c === '}' || c === ')' || c === ']') {
|
|
154
|
+
depth--;
|
|
155
|
+
// the opening brace must match the FINAL char, else `{..}{..}` etc.
|
|
156
|
+
if (!breakOnSemicolon && depth === 0 && i !== s.length - 1)
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
else if (breakOnSemicolon && c === ';' && depth === 0)
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
return false;
|
|
163
|
+
};
|
|
164
|
+
if (topLevelBreaks(t, false))
|
|
165
|
+
return body; // outer braces don't span the whole body
|
|
166
|
+
let inner = t.slice(1, -1).trim();
|
|
167
|
+
if (!/^return\b/.test(inner))
|
|
168
|
+
return body;
|
|
169
|
+
inner = inner.slice(6).trim();
|
|
170
|
+
if (inner.endsWith(';'))
|
|
171
|
+
inner = inner.slice(0, -1).trim();
|
|
172
|
+
if (!inner || topLevelBreaks(inner, true))
|
|
173
|
+
return body; // empty or multi-statement
|
|
174
|
+
return inner;
|
|
175
|
+
}
|
|
128
176
|
// Parse an arrow callback's argument text into `{ params, body }`, or null when
|
|
129
177
|
// it isn't a single arrow function (e.g. `.map(fn)` with a bare reference, which
|
|
130
178
|
// is left unchanged). Handles `(p) => body`, `p => body`, and `(p, i) => body`.
|
|
@@ -140,12 +188,12 @@ function parseArrowCallback(inner) {
|
|
|
140
188
|
const params = splitTopLevelArgs(trimmed.slice(1, close))
|
|
141
189
|
.map((s) => s.trim())
|
|
142
190
|
.filter(Boolean);
|
|
143
|
-
return { params, body: after.slice(2).trim() };
|
|
191
|
+
return { params, body: unwrapSingleReturnBlock(after.slice(2).trim()) };
|
|
144
192
|
}
|
|
145
193
|
const m = trimmed.match(/^([A-Za-z_$][\w$]*)\s*=>\s*([\s\S]+)$/);
|
|
146
194
|
if (!m)
|
|
147
195
|
return null;
|
|
148
|
-
return { params: [m[1]], body: m[2].trim() };
|
|
196
|
+
return { params: [m[1]], body: unwrapSingleReturnBlock(m[2].trim()) };
|
|
149
197
|
}
|
|
150
198
|
// Lower JS arrow-callback array methods to Python comprehensions:
|
|
151
199
|
// arr.filter((x) => pred) -> [x for x in arr if pred]
|
|
@@ -170,6 +218,7 @@ const PORTABLE_ARRAY_METHODS = new Set([
|
|
|
170
218
|
'sort',
|
|
171
219
|
'flat',
|
|
172
220
|
'at',
|
|
221
|
+
'push',
|
|
173
222
|
'reverse',
|
|
174
223
|
'concat',
|
|
175
224
|
'fill',
|
|
@@ -177,7 +226,31 @@ const PORTABLE_ARRAY_METHODS = new Set([
|
|
|
177
226
|
'reduceRight',
|
|
178
227
|
]);
|
|
179
228
|
const LAMBDA_COLON_PLACEHOLDER = '__KERN_LAMBDA_COLON__';
|
|
180
|
-
function
|
|
229
|
+
function lowerArrowBlockClosure(arrow, ctx) {
|
|
230
|
+
if (!arrow.body.trim().startsWith('{'))
|
|
231
|
+
return null;
|
|
232
|
+
const seq = ctx.closureSeq ?? { n: 0 };
|
|
233
|
+
const name = `__kern_closure_${seq.n++}`;
|
|
234
|
+
if (!ctx.closureSeq)
|
|
235
|
+
ctx.closureSeq = seq;
|
|
236
|
+
const result = lowerJsClosureBodyToPython(arrow.body, {
|
|
237
|
+
lowerExpression: (raw) => rewriteFastAPIExpr(lowerDictMemberAccess(raw, arrow.params[0]), ctx.pathParams, ctx.bodyFields, ctx.authUser, ctx.imports, undefined, ctx.closureSeq),
|
|
238
|
+
lowerCondition: (raw) => `js_truthy(${rewriteFastAPIExpr(lowerDictMemberAccess(raw, arrow.params[0]), ctx.pathParams, ctx.bodyFields, ctx.authUser, ctx.imports, undefined, ctx.closureSeq)})`,
|
|
239
|
+
});
|
|
240
|
+
if (!result.ok)
|
|
241
|
+
return null;
|
|
242
|
+
ctx.imports?.add(KERN_JS_HELPER_PY);
|
|
243
|
+
const params = arrow.params.join(', ');
|
|
244
|
+
const def = [`def ${name}(${params}):`, ...(result.lines.length > 0 ? result.lines : [' pass'])].join('\n');
|
|
245
|
+
if (ctx.hoistedDefs) {
|
|
246
|
+
ctx.hoistedDefs.push(def);
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
ctx.imports?.add(def);
|
|
250
|
+
}
|
|
251
|
+
return `${name}(${arrow.params.join(', ')})`;
|
|
252
|
+
}
|
|
253
|
+
function lowerJsArrayMethods(expr, ctx) {
|
|
181
254
|
let out = '';
|
|
182
255
|
let i = 0;
|
|
183
256
|
let quote = null;
|
|
@@ -216,7 +289,8 @@ function lowerJsArrayMethods(expr, imports) {
|
|
|
216
289
|
const idxVar = arrow.params[1];
|
|
217
290
|
// Recurse for nested array methods in the body; subscript the element
|
|
218
291
|
// var's member access. The index var (if any) stays a bare int.
|
|
219
|
-
const
|
|
292
|
+
const blockClosure = lowerArrowBlockClosure(arrow, ctx);
|
|
293
|
+
const body = blockClosure ?? lowerJsArrayMethods(lowerDictMemberAccess(arrow.body, elemVar), ctx);
|
|
220
294
|
// A second callback param is the element index — bind it via
|
|
221
295
|
// enumerate() for every method, not just map, so a predicate that
|
|
222
296
|
// references the index (`(x, i) => i > 0`) doesn't emit an unbound
|
|
@@ -270,7 +344,7 @@ function lowerJsArrayMethods(expr, imports) {
|
|
|
270
344
|
if (closeIdx !== -1 && recvStart !== -1) {
|
|
271
345
|
const receiver = out.slice(recvStart);
|
|
272
346
|
const pre = out.slice(0, recvStart);
|
|
273
|
-
const args = splitTopLevelArgs(expr.slice(openIdx + 1, closeIdx)).map((a) => lowerJsArrayMethods(a.trim(),
|
|
347
|
+
const args = splitTopLevelArgs(expr.slice(openIdx + 1, closeIdx)).map((a) => lowerJsArrayMethods(a.trim(), ctx));
|
|
274
348
|
let lowered = null;
|
|
275
349
|
if (method === 'includes') {
|
|
276
350
|
const needle = args[0] ?? '';
|
|
@@ -286,6 +360,25 @@ function lowerJsArrayMethods(expr, imports) {
|
|
|
286
360
|
lowered = `(next((__i for __i, __v in enumerate(${receiver}) if __v == ${needle}), -1))`;
|
|
287
361
|
}
|
|
288
362
|
}
|
|
363
|
+
else if (method === 'push') {
|
|
364
|
+
// JS Array.push mutates AND returns the new length. Python list.append
|
|
365
|
+
// returns None, so emit `(recv.append(x) or len(recv))` for exact parity
|
|
366
|
+
// (mutate + length). Single-arg only; varargs push left unsupported.
|
|
367
|
+
if (args.length === 1)
|
|
368
|
+
lowered = `(${receiver}.append(${args[0]}) or len(${receiver}))`;
|
|
369
|
+
}
|
|
370
|
+
else if (method === 'reverse') {
|
|
371
|
+
// JS Array.reverse mutates AND returns the (same, reversed) array; Python
|
|
372
|
+
// list.reverse returns None -> `(recv.reverse() or recv)` mutates + returns it.
|
|
373
|
+
lowered = `(${receiver}.reverse() or ${receiver})`;
|
|
374
|
+
}
|
|
375
|
+
else if (method === 'concat') {
|
|
376
|
+
// JS Array.concat returns a NEW array; an array arg is spread, a scalar arg
|
|
377
|
+
// is appended. Mirror with `recv + (x if isinstance(x, list) else [x])`.
|
|
378
|
+
// Single-arg only; varargs concat left unsupported.
|
|
379
|
+
if (args.length === 1)
|
|
380
|
+
lowered = `(${receiver} + (${args[0]} if isinstance(${args[0]}, list) else [${args[0]}]))`;
|
|
381
|
+
}
|
|
289
382
|
else if (method === 'join') {
|
|
290
383
|
const sep = args[0] ?? '","';
|
|
291
384
|
lowered = `${sep}.join(str(__v) for __v in ${receiver})`;
|
|
@@ -309,7 +402,8 @@ function lowerJsArrayMethods(expr, imports) {
|
|
|
309
402
|
const idxVar = arrow.params[1];
|
|
310
403
|
// Only the element var is dict-subscripted; the index var stays a
|
|
311
404
|
// bare int and must be bound via enumerate() when present.
|
|
312
|
-
const
|
|
405
|
+
const blockClosure = lowerArrowBlockClosure(arrow, ctx);
|
|
406
|
+
const pred = blockClosure ?? lowerJsArrayMethods(lowerDictMemberAccess(arrow.body, elemVar), ctx);
|
|
313
407
|
const loopTarget = idxVar ? `${idxVar}, ${elemVar}` : elemVar;
|
|
314
408
|
const source = idxVar ? `enumerate(${receiver})` : receiver;
|
|
315
409
|
lowered =
|
|
@@ -326,10 +420,10 @@ function lowerJsArrayMethods(expr, imports) {
|
|
|
326
420
|
const elemVar = arrow.params[1];
|
|
327
421
|
let body = lowerDictMemberAccess(arrow.body, accVar);
|
|
328
422
|
body = lowerDictMemberAccess(body, elemVar);
|
|
329
|
-
const loweredBody = lowerJsArrayMethods(body,
|
|
330
|
-
imports?.add('import functools');
|
|
423
|
+
const loweredBody = lowerJsArrayMethods(body, ctx);
|
|
424
|
+
ctx.imports?.add('import functools');
|
|
331
425
|
if (rawArgs.length >= 2) {
|
|
332
|
-
const seed = lowerJsArrayMethods(rawArgs[1].trim(),
|
|
426
|
+
const seed = lowerJsArrayMethods(rawArgs[1].trim(), ctx);
|
|
333
427
|
lowered = `functools.reduce(lambda ${accVar}, ${elemVar}${LAMBDA_COLON_PLACEHOLDER} ${loweredBody}, ${receiver}, ${seed})`;
|
|
334
428
|
}
|
|
335
429
|
else {
|
|
@@ -346,10 +440,10 @@ function lowerJsArrayMethods(expr, imports) {
|
|
|
346
440
|
const elemVar = arrow.params[1];
|
|
347
441
|
let body = lowerDictMemberAccess(arrow.body, accVar);
|
|
348
442
|
body = lowerDictMemberAccess(body, elemVar);
|
|
349
|
-
const loweredBody = lowerJsArrayMethods(body,
|
|
350
|
-
imports?.add('import functools');
|
|
443
|
+
const loweredBody = lowerJsArrayMethods(body, ctx);
|
|
444
|
+
ctx.imports?.add('import functools');
|
|
351
445
|
if (rawArgs.length >= 2) {
|
|
352
|
-
const seed = lowerJsArrayMethods(rawArgs[1].trim(),
|
|
446
|
+
const seed = lowerJsArrayMethods(rawArgs[1].trim(), ctx);
|
|
353
447
|
lowered = `functools.reduce(lambda ${accVar}, ${elemVar}${LAMBDA_COLON_PLACEHOLDER} ${loweredBody}, ${receiver}[::-1], ${seed})`;
|
|
354
448
|
}
|
|
355
449
|
else {
|
|
@@ -365,8 +459,8 @@ function lowerJsArrayMethods(expr, imports) {
|
|
|
365
459
|
if (arrow && arrow.params.length >= 2) {
|
|
366
460
|
const a = arrow.params[0];
|
|
367
461
|
const b = arrow.params[1];
|
|
368
|
-
const body = lowerJsArrayMethods(arrow.body,
|
|
369
|
-
imports?.add('import functools');
|
|
462
|
+
const body = lowerJsArrayMethods(arrow.body, ctx);
|
|
463
|
+
ctx.imports?.add('import functools');
|
|
370
464
|
lowered = `sorted(${receiver}, key=functools.cmp_to_key(lambda ${a}, ${b}${LAMBDA_COLON_PLACEHOLDER} ${body}))`;
|
|
371
465
|
}
|
|
372
466
|
else {
|
|
@@ -380,14 +474,9 @@ function lowerJsArrayMethods(expr, imports) {
|
|
|
380
474
|
else if (method === 'at') {
|
|
381
475
|
const n = args[0] ?? '0';
|
|
382
476
|
lowered = `(${receiver}[${n}] if -len(${receiver}) <= ${n} < len(${receiver}) else None)`;
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
//
|
|
386
|
-
lowered = `${receiver}[::-1]`;
|
|
387
|
-
}
|
|
388
|
-
else if (method === 'concat') {
|
|
389
|
-
// array args only (scalar-arg concat is out of scope)
|
|
390
|
-
lowered = args.length ? `(${receiver} + ${args.join(' + ')})` : `${receiver}[:]`;
|
|
477
|
+
// NB: `reverse` and `concat` are handled earlier in this chain (they mutate /
|
|
478
|
+
// accept scalar args per JS) — main's later array-only duplicates were dropped
|
|
479
|
+
// in the roadmap-stack merge (they were dead + concat broke on scalar args).
|
|
391
480
|
}
|
|
392
481
|
else if (method === 'fill') {
|
|
393
482
|
const v = args[0] ?? 'None';
|
|
@@ -1776,7 +1865,20 @@ function lowerPortableJsOperators(expr, imports) {
|
|
|
1776
1865
|
result = replaceJsOperator(result, '??', looseBinaryStops, (l, r) => `(${l} if ${l} is not None else ${r})`);
|
|
1777
1866
|
return result;
|
|
1778
1867
|
}
|
|
1779
|
-
export function rewriteFastAPIExpr(expr, pathParams, bodyFields = new Set(), authUser = false, imports) {
|
|
1868
|
+
export function rewriteFastAPIExpr(expr, pathParams, bodyFields = new Set(), authUser = false, imports, hoistedDefs, closureSeq) {
|
|
1869
|
+
try {
|
|
1870
|
+
const tokens = tokenizeJSExpr(expr);
|
|
1871
|
+
const comparisonProbe = expr.replace(/>>>|>>|<</g, '');
|
|
1872
|
+
const hasLooseComparison = /(?:===|!==|==|!=|<=|>=|<|>)/.test(comparisonProbe);
|
|
1873
|
+
const hasBitwiseOrModulo = !expr.includes('=>') && !hasLooseComparison && tokens.some((t) => t.type === 'UNARY' || t.type === 'OP');
|
|
1874
|
+
if (hasBitwiseOrModulo) {
|
|
1875
|
+
const ast = parseTokens(tokens);
|
|
1876
|
+
expr = codegenASTToPython(ast, imports);
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
catch (_err) {
|
|
1880
|
+
// Graceful fallback to original expr string if parsing/emission fails
|
|
1881
|
+
}
|
|
1780
1882
|
const { maskedExpr, replacements } = extractTemplateLiterals(expr, pathParams, bodyFields, authUser, imports);
|
|
1781
1883
|
let result = maskedExpr;
|
|
1782
1884
|
// Spread → unpacking first, so the request-ref rewrites below see clean
|
|
@@ -1832,7 +1934,7 @@ export function rewriteFastAPIExpr(expr, pathParams, bodyFields = new Set(), aut
|
|
|
1832
1934
|
// Array methods first (so any `===` inside an arrow body is hoisted into
|
|
1833
1935
|
// a list-comprehension predicate that the strict-equality pass below
|
|
1834
1936
|
// then catches).
|
|
1835
|
-
result = lowerJsArrayMethods(result, imports);
|
|
1937
|
+
result = lowerJsArrayMethods(result, { pathParams, bodyFields, authUser, imports, hoistedDefs, closureSeq });
|
|
1836
1938
|
// Strict equality: skip text inside quoted strings so a user message
|
|
1837
1939
|
// like `"use === for strict equality"` doesn't get mangled to `==`.
|
|
1838
1940
|
result = result.replace(STRICT_EQ_RE, (match) => {
|
|
@@ -1919,4 +2021,250 @@ export function addRespondImports(respondNode, imports) {
|
|
|
1919
2021
|
if (rp.error)
|
|
1920
2022
|
imports.add('from fastapi import HTTPException');
|
|
1921
2023
|
}
|
|
2024
|
+
function tokenizeJSExpr(expr) {
|
|
2025
|
+
const tokens = [];
|
|
2026
|
+
let i = 0;
|
|
2027
|
+
while (i < expr.length) {
|
|
2028
|
+
while (i < expr.length && /\s/.test(expr[i])) {
|
|
2029
|
+
i++;
|
|
2030
|
+
}
|
|
2031
|
+
if (i >= expr.length)
|
|
2032
|
+
break;
|
|
2033
|
+
const char = expr[i];
|
|
2034
|
+
if (char === '(') {
|
|
2035
|
+
tokens.push({ type: 'LP' });
|
|
2036
|
+
i++;
|
|
2037
|
+
continue;
|
|
2038
|
+
}
|
|
2039
|
+
if (char === ')') {
|
|
2040
|
+
tokens.push({ type: 'RP' });
|
|
2041
|
+
i++;
|
|
2042
|
+
continue;
|
|
2043
|
+
}
|
|
2044
|
+
if (char === '~') {
|
|
2045
|
+
tokens.push({ type: 'UNARY', value: '~' });
|
|
2046
|
+
i++;
|
|
2047
|
+
continue;
|
|
2048
|
+
}
|
|
2049
|
+
if (char === '"' || char === "'" || char === '`') {
|
|
2050
|
+
const quote = char;
|
|
2051
|
+
let val = quote;
|
|
2052
|
+
i++;
|
|
2053
|
+
while (i < expr.length) {
|
|
2054
|
+
const c = expr[i];
|
|
2055
|
+
val += c;
|
|
2056
|
+
if (c === '\\') {
|
|
2057
|
+
val += expr[i + 1] ?? '';
|
|
2058
|
+
i += 2;
|
|
2059
|
+
continue;
|
|
2060
|
+
}
|
|
2061
|
+
if (c === quote) {
|
|
2062
|
+
i++;
|
|
2063
|
+
break;
|
|
2064
|
+
}
|
|
2065
|
+
i++;
|
|
2066
|
+
}
|
|
2067
|
+
tokens.push({ type: 'TEXT', value: val });
|
|
2068
|
+
continue;
|
|
2069
|
+
}
|
|
2070
|
+
if (char === '&') {
|
|
2071
|
+
if (expr[i + 1] === '&') {
|
|
2072
|
+
// Fall through to TEXT
|
|
2073
|
+
}
|
|
2074
|
+
else {
|
|
2075
|
+
tokens.push({ type: 'OP', value: '&' });
|
|
2076
|
+
i++;
|
|
2077
|
+
continue;
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
2080
|
+
if (char === '|') {
|
|
2081
|
+
if (expr[i + 1] === '|') {
|
|
2082
|
+
// Fall through to TEXT
|
|
2083
|
+
}
|
|
2084
|
+
else {
|
|
2085
|
+
tokens.push({ type: 'OP', value: '|' });
|
|
2086
|
+
i++;
|
|
2087
|
+
continue;
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
if (char === '^' || char === '%') {
|
|
2091
|
+
tokens.push({ type: 'OP', value: char });
|
|
2092
|
+
i++;
|
|
2093
|
+
continue;
|
|
2094
|
+
}
|
|
2095
|
+
if (char === '<' && expr[i + 1] === '<') {
|
|
2096
|
+
tokens.push({ type: 'OP', value: '<<' });
|
|
2097
|
+
i += 2;
|
|
2098
|
+
continue;
|
|
2099
|
+
}
|
|
2100
|
+
// `>>>` (unsigned right shift) is intentionally OUT of the int32 AST path
|
|
2101
|
+
// (deferred — see the numbermodel oracle note). Bail the whole expr out of
|
|
2102
|
+
// the AST path by throwing (caught in rewriteFastAPIExpr) so the downstream
|
|
2103
|
+
// string-level operator lowering — which 32-bit-masks `>>>` correctly —
|
|
2104
|
+
// handles it. Must be checked BEFORE `>>`, else `>>` greedily eats two of
|
|
2105
|
+
// the three `>` and leaves a stray `>` that mangles the operand.
|
|
2106
|
+
if (char === '>' && expr[i + 1] === '>' && expr[i + 2] === '>') {
|
|
2107
|
+
throw new Error('unsupported-operator: >>> (defer to string lowering)');
|
|
2108
|
+
}
|
|
2109
|
+
if (char === '>' && expr[i + 1] === '>') {
|
|
2110
|
+
tokens.push({ type: 'OP', value: '>>' });
|
|
2111
|
+
i += 2;
|
|
2112
|
+
continue;
|
|
2113
|
+
}
|
|
2114
|
+
let text = '';
|
|
2115
|
+
while (i < expr.length) {
|
|
2116
|
+
const c = expr[i];
|
|
2117
|
+
if (c === '(' || c === ')' || c === '~' || c === '^' || c === '%') {
|
|
2118
|
+
break;
|
|
2119
|
+
}
|
|
2120
|
+
if (c === '"' || c === "'" || c === '`') {
|
|
2121
|
+
break;
|
|
2122
|
+
}
|
|
2123
|
+
if (c === '&') {
|
|
2124
|
+
if (expr[i + 1] === '&') {
|
|
2125
|
+
text += '&&';
|
|
2126
|
+
i += 2;
|
|
2127
|
+
continue;
|
|
2128
|
+
}
|
|
2129
|
+
else {
|
|
2130
|
+
break;
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
if (c === '|') {
|
|
2134
|
+
if (expr[i + 1] === '|') {
|
|
2135
|
+
text += '||';
|
|
2136
|
+
i += 2;
|
|
2137
|
+
continue;
|
|
2138
|
+
}
|
|
2139
|
+
else {
|
|
2140
|
+
break;
|
|
2141
|
+
}
|
|
2142
|
+
}
|
|
2143
|
+
if (c === '<' && expr[i + 1] === '<') {
|
|
2144
|
+
break;
|
|
2145
|
+
}
|
|
2146
|
+
if (c === '>' && expr[i + 1] === '>') {
|
|
2147
|
+
break;
|
|
2148
|
+
}
|
|
2149
|
+
text += c;
|
|
2150
|
+
i++;
|
|
2151
|
+
}
|
|
2152
|
+
if (text) {
|
|
2153
|
+
tokens.push({ type: 'TEXT', value: text.trimEnd() });
|
|
2154
|
+
}
|
|
2155
|
+
}
|
|
2156
|
+
return tokens;
|
|
2157
|
+
}
|
|
2158
|
+
function parseTokens(tokens) {
|
|
2159
|
+
let index = 0;
|
|
2160
|
+
function peek() {
|
|
2161
|
+
return tokens[index];
|
|
2162
|
+
}
|
|
2163
|
+
function consume() {
|
|
2164
|
+
return tokens[index++];
|
|
2165
|
+
}
|
|
2166
|
+
function getPrecedence(op) {
|
|
2167
|
+
switch (op) {
|
|
2168
|
+
case '|':
|
|
2169
|
+
return 1;
|
|
2170
|
+
case '^':
|
|
2171
|
+
return 2;
|
|
2172
|
+
case '&':
|
|
2173
|
+
return 3;
|
|
2174
|
+
case '<<':
|
|
2175
|
+
case '>>':
|
|
2176
|
+
return 4;
|
|
2177
|
+
case '%':
|
|
2178
|
+
return 5;
|
|
2179
|
+
default:
|
|
2180
|
+
return 0;
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
function parseExpression(precedence) {
|
|
2184
|
+
let left = parsePrimary();
|
|
2185
|
+
while (true) {
|
|
2186
|
+
const next = peek();
|
|
2187
|
+
if (!next || next.type !== 'OP')
|
|
2188
|
+
break;
|
|
2189
|
+
const opPrecedence = getPrecedence(next.value);
|
|
2190
|
+
if (opPrecedence < precedence)
|
|
2191
|
+
break;
|
|
2192
|
+
consume();
|
|
2193
|
+
const right = parseExpression(opPrecedence + 1);
|
|
2194
|
+
left = { type: 'binary', op: next.value, left, right };
|
|
2195
|
+
}
|
|
2196
|
+
return left;
|
|
2197
|
+
}
|
|
2198
|
+
function parsePrimary() {
|
|
2199
|
+
const t = peek();
|
|
2200
|
+
if (!t)
|
|
2201
|
+
throw new Error('Unexpected EOF');
|
|
2202
|
+
if (t.type === 'UNARY') {
|
|
2203
|
+
consume();
|
|
2204
|
+
const arg = parseExpression(6);
|
|
2205
|
+
return { type: 'unary', op: t.value, arg };
|
|
2206
|
+
}
|
|
2207
|
+
if (t.type === 'LP') {
|
|
2208
|
+
consume();
|
|
2209
|
+
const inner = parseExpression(0);
|
|
2210
|
+
const next = peek();
|
|
2211
|
+
if (next && next.type === 'RP') {
|
|
2212
|
+
consume();
|
|
2213
|
+
}
|
|
2214
|
+
return { type: 'group', arg: inner };
|
|
2215
|
+
}
|
|
2216
|
+
if (t.type === 'TEXT') {
|
|
2217
|
+
consume();
|
|
2218
|
+
return { type: 'text', value: t.value };
|
|
2219
|
+
}
|
|
2220
|
+
consume();
|
|
2221
|
+
return { type: 'text', value: t.type === 'OP' ? t.value : '' };
|
|
2222
|
+
}
|
|
2223
|
+
return parseExpression(0);
|
|
2224
|
+
}
|
|
2225
|
+
function codegenASTToPython(node, imports) {
|
|
2226
|
+
switch (node.type) {
|
|
2227
|
+
case 'text':
|
|
2228
|
+
return node.value;
|
|
2229
|
+
case 'group':
|
|
2230
|
+
return `(${codegenASTToPython(node.arg, imports)})`;
|
|
2231
|
+
case 'unary': {
|
|
2232
|
+
const argStr = codegenASTToPython(node.arg, imports);
|
|
2233
|
+
if (node.op === '~') {
|
|
2234
|
+
imports?.add(KERN_I32_HELPER_PY);
|
|
2235
|
+
return `_i32(~_i32(${argStr}))`;
|
|
2236
|
+
}
|
|
2237
|
+
return `${node.op}${argStr}`;
|
|
2238
|
+
}
|
|
2239
|
+
case 'binary': {
|
|
2240
|
+
const leftStr = codegenASTToPython(node.left, imports);
|
|
2241
|
+
const rightStr = codegenASTToPython(node.right, imports);
|
|
2242
|
+
if (node.op === '|') {
|
|
2243
|
+
imports?.add(KERN_I32_HELPER_PY);
|
|
2244
|
+
return `_i32(_i32(${leftStr}) | _i32(${rightStr}))`;
|
|
2245
|
+
}
|
|
2246
|
+
if (node.op === '&') {
|
|
2247
|
+
imports?.add(KERN_I32_HELPER_PY);
|
|
2248
|
+
return `_i32(_i32(${leftStr}) & _i32(${rightStr}))`;
|
|
2249
|
+
}
|
|
2250
|
+
if (node.op === '^') {
|
|
2251
|
+
imports?.add(KERN_I32_HELPER_PY);
|
|
2252
|
+
return `_i32(_i32(${leftStr}) ^ _i32(${rightStr}))`;
|
|
2253
|
+
}
|
|
2254
|
+
if (node.op === '<<') {
|
|
2255
|
+
imports?.add(KERN_I32_HELPER_PY);
|
|
2256
|
+
return `_i32(_i32(${leftStr}) << (_i32(${rightStr}) & 31))`;
|
|
2257
|
+
}
|
|
2258
|
+
if (node.op === '>>') {
|
|
2259
|
+
imports?.add(KERN_I32_HELPER_PY);
|
|
2260
|
+
return `_i32(_i32(${leftStr}) >> (_i32(${rightStr}) & 31))`;
|
|
2261
|
+
}
|
|
2262
|
+
if (node.op === '%') {
|
|
2263
|
+
imports?.add(KERN_TMOD_HELPER_PY);
|
|
2264
|
+
return `_tmod(${leftStr}, ${rightStr})`;
|
|
2265
|
+
}
|
|
2266
|
+
return `${leftStr} ${node.op} ${rightStr}`;
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
1922
2270
|
//# sourceMappingURL=fastapi-response.js.map
|