@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.
Files changed (39) hide show
  1. package/dist/codegen-body-python.d.ts +37 -0
  2. package/dist/codegen-body-python.js +239 -0
  3. package/dist/codegen-body-python.js.map +1 -1
  4. package/dist/codegen-python.d.ts +1 -0
  5. package/dist/codegen-python.js +1 -1
  6. package/dist/codegen-python.js.map +1 -1
  7. package/dist/core/emit-imports.d.ts +11 -0
  8. package/dist/core/emit-imports.js +166 -0
  9. package/dist/core/emit-imports.js.map +1 -0
  10. package/dist/core/emit-models.d.ts +14 -0
  11. package/dist/core/emit-models.js +86 -0
  12. package/dist/core/emit-models.js.map +1 -0
  13. package/dist/core/fence-diagnostics.d.ts +17 -0
  14. package/dist/core/fence-diagnostics.js +86 -0
  15. package/dist/core/fence-diagnostics.js.map +1 -0
  16. package/dist/core/index.d.ts +3 -0
  17. package/dist/core/index.js +4 -0
  18. package/dist/core/index.js.map +1 -0
  19. package/dist/core/type-mapper.d.ts +4 -0
  20. package/dist/core/type-mapper.js +8 -0
  21. package/dist/core/type-mapper.js.map +1 -0
  22. package/dist/fastapi-portable.d.ts +7 -1
  23. package/dist/fastapi-portable.js +95 -73
  24. package/dist/fastapi-portable.js.map +1 -1
  25. package/dist/fastapi-response.d.ts +3 -1
  26. package/dist/fastapi-response.js +374 -26
  27. package/dist/fastapi-response.js.map +1 -1
  28. package/dist/generators/data.d.ts +3 -1
  29. package/dist/generators/data.js +28 -1
  30. package/dist/generators/data.js.map +1 -1
  31. package/dist/index.d.ts +2 -3
  32. package/dist/index.js +2 -3
  33. package/dist/index.js.map +1 -1
  34. package/dist/targets/python.d.ts +5 -0
  35. package/dist/targets/python.js +50 -0
  36. package/dist/targets/python.js.map +1 -0
  37. package/dist/transpiler-fastapi.js +23 -66
  38. package/dist/transpiler-fastapi.js.map +1 -1
  39. package/package.json +2 -2
@@ -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('') + `.${methodField}`;
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 lowerJsArrayMethods(expr, imports) {
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 body = lowerJsArrayMethods(lowerDictMemberAccess(arrow.body, elemVar), imports);
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(), imports));
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 pred = lowerJsArrayMethods(lowerDictMemberAccess(arrow.body, elemVar), imports);
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, imports);
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(), imports);
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, imports);
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(), imports);
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, imports);
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
- else if (method === 'reverse') {
385
- // JS reverses in place AND returns the array; we only need the value.
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