@terminals-tech/py2bend 0.1.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/index.js ADDED
@@ -0,0 +1,1295 @@
1
+ // src/parser.ts
2
+ function tokenizeLines(source) {
3
+ return source.split("\n").map((raw, i) => {
4
+ const text = raw;
5
+ const trimmed = raw.trimStart();
6
+ const indent2 = raw.length - trimmed.length;
7
+ return { text: trimmed, indent: indent2, lineNo: i + 1 };
8
+ }).filter((l) => l.text.length > 0 && !l.text.startsWith("#"));
9
+ }
10
+ var Cursor = class {
11
+ constructor(lines, pos = 0) {
12
+ this.lines = lines;
13
+ this.pos = pos;
14
+ }
15
+ done() {
16
+ return this.pos >= this.lines.length;
17
+ }
18
+ peek() {
19
+ return this.done() ? null : this.lines[this.pos];
20
+ }
21
+ advance() {
22
+ return this.lines[this.pos++];
23
+ }
24
+ /** Collect the body block: all lines with indent > parentIndent */
25
+ collectBlock(parentIndent) {
26
+ const block = [];
27
+ while (!this.done()) {
28
+ const next = this.peek();
29
+ if (next.indent <= parentIndent) break;
30
+ block.push(this.advance());
31
+ }
32
+ return block;
33
+ }
34
+ };
35
+ var UNSUPPORTED_KEYWORDS = [
36
+ /^async\s+def\b/,
37
+ /^await\b/,
38
+ /^yield\b/,
39
+ /^import\b/,
40
+ /^from\s+\S+\s+import\b/,
41
+ /^with\b/,
42
+ /^assert\b/,
43
+ /^raise\b/,
44
+ /^del\b/,
45
+ /^global\b/,
46
+ /^nonlocal\b/
47
+ ];
48
+ function parsePython(source) {
49
+ const errors = [];
50
+ const lines = tokenizeLines(source);
51
+ try {
52
+ const cursor = new Cursor(lines);
53
+ const body = parseBlock(cursor, -1, errors);
54
+ return { ast: { type: "Module", body }, errors };
55
+ } catch (e) {
56
+ errors.push({
57
+ message: e.message,
58
+ severity: "error"
59
+ });
60
+ return { ast: null, errors };
61
+ }
62
+ }
63
+ function parseBlock(cursor, parentIndent, errors) {
64
+ const stmts = [];
65
+ while (!cursor.done()) {
66
+ const line = cursor.peek();
67
+ if (line.indent <= parentIndent) break;
68
+ cursor.advance();
69
+ const stmt = parseStatement(line, cursor, errors);
70
+ if (stmt) stmts.push(stmt);
71
+ }
72
+ return stmts;
73
+ }
74
+ function parseStatement(line, cursor, errors) {
75
+ const text = line.text;
76
+ for (const pattern of UNSUPPORTED_KEYWORDS) {
77
+ if (pattern.test(text)) {
78
+ errors.push({
79
+ message: `Unsupported syntax: ${text}`,
80
+ line: line.lineNo,
81
+ severity: "error"
82
+ });
83
+ cursor.collectBlock(line.indent);
84
+ return null;
85
+ }
86
+ }
87
+ if (text.startsWith("@")) {
88
+ const decorators = [text.slice(1).trim()];
89
+ while (!cursor.done() && cursor.peek().text.startsWith("@")) {
90
+ decorators.push(cursor.advance().text.slice(1).trim());
91
+ }
92
+ if (!cursor.done()) {
93
+ const defLine = cursor.advance();
94
+ const stmt = parseStatement(defLine, cursor, errors);
95
+ if (stmt && stmt.type === "FunctionDef") {
96
+ stmt.decorators = decorators;
97
+ }
98
+ return stmt;
99
+ }
100
+ return null;
101
+ }
102
+ if (text.startsWith("def ")) {
103
+ return parseFunctionDef(text, line, cursor, errors);
104
+ }
105
+ if (text.startsWith("class ")) {
106
+ return parseClassDef(text, line, cursor, errors);
107
+ }
108
+ if (text.startsWith("if ")) {
109
+ return parseIf(text, line, cursor, errors);
110
+ }
111
+ if (text.startsWith("for ")) {
112
+ return parseFor(text, line, cursor, errors);
113
+ }
114
+ if (text.startsWith("while ")) {
115
+ return parseWhile(text, line, cursor, errors);
116
+ }
117
+ if (text.startsWith("try:")) {
118
+ return parseTry(line, cursor, errors);
119
+ }
120
+ if (text === "return" || text.startsWith("return ")) {
121
+ return parseReturn(text, line, errors);
122
+ }
123
+ if (text === "pass") {
124
+ return null;
125
+ }
126
+ if (text.startsWith("elif ") || text === "else:" || text.startsWith("except")) {
127
+ cursor.collectBlock(line.indent);
128
+ return null;
129
+ }
130
+ const augMatch = text.match(
131
+ /^([A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*)\s*(\+\=|\-\=|\*\=|\/\=|\/\/\=|%\=|\*\*\=|&\=|\|\=)\s*(.+)$/
132
+ );
133
+ if (augMatch) {
134
+ return {
135
+ type: "AugAssign",
136
+ target: augMatch[1],
137
+ op: augMatch[2],
138
+ value: parseExpr(augMatch[3].trim(), line.lineNo, errors)
139
+ };
140
+ }
141
+ const assignMatch = text.match(
142
+ /^([A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*(?:\[[^\]]+\])?)\s*=\s*(.+)$/
143
+ );
144
+ if (assignMatch) {
145
+ const afterTarget = text.slice(assignMatch[1].length).trimStart();
146
+ if (afterTarget.startsWith("==")) {
147
+ } else {
148
+ return {
149
+ type: "Assign",
150
+ target: assignMatch[1],
151
+ value: parseExpr(assignMatch[2].trim(), line.lineNo, errors)
152
+ };
153
+ }
154
+ }
155
+ const expr = parseExpr(text, line.lineNo, errors);
156
+ return {
157
+ type: "Expr",
158
+ value: expr
159
+ };
160
+ }
161
+ function parseFunctionDef(text, line, cursor, errors) {
162
+ const match = text.match(/^def\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(([^)]*)\)\s*:/);
163
+ if (!match) {
164
+ errors.push({
165
+ message: `Invalid function definition: ${text}`,
166
+ line: line.lineNo,
167
+ severity: "error"
168
+ });
169
+ cursor.collectBlock(line.indent);
170
+ return {
171
+ type: "FunctionDef",
172
+ name: "unknown",
173
+ params: [],
174
+ body: [],
175
+ decorators: []
176
+ };
177
+ }
178
+ const name = match[1];
179
+ const paramsStr = match[2].trim();
180
+ const params = paramsStr ? paramsStr.split(",").map((p) => {
181
+ const cleaned = p.trim().split("=")[0].trim();
182
+ return cleaned.split(":")[0].trim();
183
+ }) : [];
184
+ const bodyLines = cursor.collectBlock(line.indent);
185
+ const bodyCursor = new Cursor(bodyLines);
186
+ const body = parseBlock(bodyCursor, line.indent, errors);
187
+ return {
188
+ type: "FunctionDef",
189
+ name,
190
+ params,
191
+ body,
192
+ decorators: []
193
+ };
194
+ }
195
+ function parseClassDef(text, line, cursor, errors) {
196
+ const match = text.match(
197
+ /^class\s+([A-Za-z_][A-Za-z0-9_]*)(?:\(([^)]*)\))?\s*:/
198
+ );
199
+ if (!match) {
200
+ errors.push({
201
+ message: `Invalid class definition: ${text}`,
202
+ line: line.lineNo,
203
+ severity: "error"
204
+ });
205
+ cursor.collectBlock(line.indent);
206
+ return { type: "ClassDef", name: "unknown", bases: [], body: [] };
207
+ }
208
+ const name = match[1];
209
+ const basesStr = match[2]?.trim() ?? "";
210
+ const bases = basesStr ? basesStr.split(",").map((b) => b.trim()) : [];
211
+ const bodyLines = cursor.collectBlock(line.indent);
212
+ const bodyCursor = new Cursor(bodyLines);
213
+ const body = parseBlock(bodyCursor, line.indent, errors);
214
+ return { type: "ClassDef", name, bases, body };
215
+ }
216
+ function parseIf(text, line, cursor, errors) {
217
+ const condStr = text.replace(/^if\s+/, "").replace(/\s*:\s*$/, "");
218
+ const test = parseExpr(condStr, line.lineNo, errors);
219
+ const bodyLines = cursor.collectBlock(line.indent);
220
+ const bodyCursor = new Cursor(bodyLines);
221
+ const body = parseBlock(bodyCursor, line.indent, errors);
222
+ let orelse = [];
223
+ if (!cursor.done()) {
224
+ const next = cursor.peek();
225
+ if (next.indent === line.indent && next.text.startsWith("elif ")) {
226
+ cursor.advance();
227
+ const elifText = next.text.replace(/^elif\s+/, "if ");
228
+ const elifIf = parseIf(elifText, next, cursor, errors);
229
+ orelse = [elifIf];
230
+ } else if (next.indent === line.indent && next.text === "else:") {
231
+ cursor.advance();
232
+ const elseLines = cursor.collectBlock(line.indent);
233
+ const elseCursor = new Cursor(elseLines);
234
+ orelse = parseBlock(elseCursor, line.indent, errors);
235
+ }
236
+ }
237
+ return { type: "If", test, body, orelse };
238
+ }
239
+ function parseFor(text, line, cursor, errors) {
240
+ const match = text.match(
241
+ /^for\s+([A-Za-z_][A-Za-z0-9_]*)\s+in\s+(.+)\s*:\s*$/
242
+ );
243
+ if (!match) {
244
+ errors.push({
245
+ message: `Invalid for loop: ${text}`,
246
+ line: line.lineNo,
247
+ severity: "error"
248
+ });
249
+ cursor.collectBlock(line.indent);
250
+ return {
251
+ type: "For",
252
+ target: "unknown",
253
+ iter: { type: "Name", id: "unknown" },
254
+ body: []
255
+ };
256
+ }
257
+ const target = match[1];
258
+ const iterExpr = parseExpr(match[2].trim(), line.lineNo, errors);
259
+ const bodyLines = cursor.collectBlock(line.indent);
260
+ const bodyCursor = new Cursor(bodyLines);
261
+ const body = parseBlock(bodyCursor, line.indent, errors);
262
+ return { type: "For", target, iter: iterExpr, body };
263
+ }
264
+ function parseWhile(text, line, cursor, errors) {
265
+ const condStr = text.replace(/^while\s+/, "").replace(/\s*:\s*$/, "");
266
+ const test = parseExpr(condStr, line.lineNo, errors);
267
+ const bodyLines = cursor.collectBlock(line.indent);
268
+ const bodyCursor = new Cursor(bodyLines);
269
+ const body = parseBlock(bodyCursor, line.indent, errors);
270
+ return { type: "While", test, body };
271
+ }
272
+ function parseTry(line, cursor, errors) {
273
+ const tryBodyLines = cursor.collectBlock(line.indent);
274
+ const tryCursor = new Cursor(tryBodyLines);
275
+ const tryBody = parseBlock(tryCursor, line.indent, errors);
276
+ let handler = null;
277
+ if (!cursor.done()) {
278
+ const next = cursor.peek();
279
+ if (next.indent === line.indent && next.text.startsWith("except")) {
280
+ cursor.advance();
281
+ const exceptMatch = next.text.match(
282
+ /^except\s*(?:([A-Za-z_][A-Za-z0-9_]*))?(?:\s+as\s+([A-Za-z_][A-Za-z0-9_]*))?\s*:\s*$/
283
+ );
284
+ const handlerType = exceptMatch?.[1] ?? null;
285
+ const handlerName = exceptMatch?.[2] ?? null;
286
+ const handlerBodyLines = cursor.collectBlock(line.indent);
287
+ const handlerCursor = new Cursor(handlerBodyLines);
288
+ const handlerBody = parseBlock(handlerCursor, line.indent, errors);
289
+ handler = { type: handlerType, name: handlerName, body: handlerBody };
290
+ }
291
+ }
292
+ return { type: "Try", body: tryBody, handler };
293
+ }
294
+ function parseReturn(text, line, errors) {
295
+ const valueStr = text.replace(/^return\s*/, "").trim();
296
+ if (!valueStr) {
297
+ return { type: "Return", value: null };
298
+ }
299
+ return { type: "Return", value: parseExpr(valueStr, line.lineNo, errors) };
300
+ }
301
+ function parseExpr(text, lineNo, errors) {
302
+ text = text.trim();
303
+ if (!text) {
304
+ return { type: "Constant", value: null, kind: "none" };
305
+ }
306
+ if (text.startsWith("lambda")) {
307
+ return parseLambda(text, lineNo, errors);
308
+ }
309
+ return parseBoolOr(text, lineNo, errors);
310
+ }
311
+ function parseLambda(text, lineNo, errors) {
312
+ const match = text.match(/^lambda\s*([^:]*)\s*:\s*(.+)$/);
313
+ if (!match) {
314
+ return { type: "Lambda", params: [], body: { type: "Constant", value: null, kind: "none" } };
315
+ }
316
+ const paramsStr = match[1].trim();
317
+ const params = paramsStr ? paramsStr.split(",").map((p) => p.trim()) : [];
318
+ const body = parseExpr(match[2].trim(), lineNo, errors);
319
+ return { type: "Lambda", params, body };
320
+ }
321
+ function parseBoolOr(text, lineNo, errors) {
322
+ const parts = splitTopLevel(text, " or ");
323
+ if (parts.length === 1) return parseBoolAnd(parts[0], lineNo, errors);
324
+ const values = parts.map((p) => parseBoolAnd(p, lineNo, errors));
325
+ return { type: "BoolOp", op: "or", values };
326
+ }
327
+ function parseBoolAnd(text, lineNo, errors) {
328
+ const parts = splitTopLevel(text, " and ");
329
+ if (parts.length === 1) return parseNot(parts[0], lineNo, errors);
330
+ const values = parts.map((p) => parseNot(p, lineNo, errors));
331
+ return { type: "BoolOp", op: "and", values };
332
+ }
333
+ function parseNot(text, lineNo, errors) {
334
+ text = text.trim();
335
+ if (text.startsWith("not ")) {
336
+ const operand = parseNot(text.slice(4).trim(), lineNo, errors);
337
+ return { type: "UnaryOp", op: "not", operand };
338
+ }
339
+ return parseComparison(text, lineNo, errors);
340
+ }
341
+ var COMPARISON_OPS = [" not in ", " in ", "<=", ">=", "!=", "==", "<", ">"];
342
+ function parseComparison(text, lineNo, errors) {
343
+ let bestIdx = -1;
344
+ let bestOp = "";
345
+ for (const op of COMPARISON_OPS) {
346
+ const idx = findTopLevel(text, op);
347
+ if (idx !== -1 && (bestIdx === -1 || idx < bestIdx)) {
348
+ bestIdx = idx;
349
+ bestOp = op;
350
+ }
351
+ }
352
+ if (bestIdx === -1) {
353
+ return parseAddSub(text, lineNo, errors);
354
+ }
355
+ const left = parseAddSub(text.slice(0, bestIdx).trim(), lineNo, errors);
356
+ const right = parseComparison(
357
+ text.slice(bestIdx + bestOp.length).trim(),
358
+ lineNo,
359
+ errors
360
+ );
361
+ if (right.type === "Compare") {
362
+ const rightCmp = right;
363
+ return {
364
+ type: "Compare",
365
+ left,
366
+ ops: [bestOp.trim(), ...rightCmp.ops],
367
+ comparators: [rightCmp.left, ...rightCmp.comparators]
368
+ };
369
+ }
370
+ return {
371
+ type: "Compare",
372
+ left,
373
+ ops: [bestOp.trim()],
374
+ comparators: [right]
375
+ };
376
+ }
377
+ function parseAddSub(text, lineNo, errors) {
378
+ let parenDepth = 0;
379
+ let bracketDepth = 0;
380
+ let braceDepth = 0;
381
+ let inStr = null;
382
+ let splitIdx = -1;
383
+ let splitOp = "";
384
+ for (let i = text.length - 1; i >= 1; i--) {
385
+ const ch = text[i];
386
+ if ((ch === '"' || ch === "'") && text[i - 1] !== "\\") {
387
+ if (inStr === ch) {
388
+ inStr = null;
389
+ continue;
390
+ } else if (!inStr) {
391
+ inStr = ch;
392
+ continue;
393
+ }
394
+ }
395
+ if (inStr) continue;
396
+ if (ch === ")") parenDepth++;
397
+ else if (ch === "(") parenDepth--;
398
+ else if (ch === "]") bracketDepth++;
399
+ else if (ch === "[") bracketDepth--;
400
+ else if (ch === "}") braceDepth++;
401
+ else if (ch === "{") braceDepth--;
402
+ if (parenDepth === 0 && bracketDepth === 0 && braceDepth === 0) {
403
+ if ((ch === "+" || ch === "-") && text[i - 1] !== "*" && text[i + 1] !== "=" && text[i - 1] !== "=") {
404
+ const before = text.slice(0, i).trim();
405
+ if (before.length > 0 && !before.endsWith("(") && !before.endsWith(",") && !before.endsWith("[")) {
406
+ splitIdx = i;
407
+ splitOp = ch;
408
+ break;
409
+ }
410
+ }
411
+ }
412
+ }
413
+ if (splitIdx === -1) {
414
+ return parseMulDiv(text, lineNo, errors);
415
+ }
416
+ const left = parseAddSub(text.slice(0, splitIdx).trim(), lineNo, errors);
417
+ const right = parseMulDiv(text.slice(splitIdx + 1).trim(), lineNo, errors);
418
+ return { type: "BinOp", left, op: splitOp, right };
419
+ }
420
+ function parseMulDiv(text, lineNo, errors) {
421
+ let parenDepth = 0;
422
+ let bracketDepth = 0;
423
+ let braceDepth = 0;
424
+ let inStr = null;
425
+ let splitIdx = -1;
426
+ let splitOp = "";
427
+ for (let i = text.length - 1; i >= 0; i--) {
428
+ const ch = text[i];
429
+ if ((ch === '"' || ch === "'") && (i === 0 || text[i - 1] !== "\\")) {
430
+ if (inStr === ch) {
431
+ inStr = null;
432
+ continue;
433
+ } else if (!inStr) {
434
+ inStr = ch;
435
+ continue;
436
+ }
437
+ }
438
+ if (inStr) continue;
439
+ if (ch === ")") parenDepth++;
440
+ else if (ch === "(") parenDepth--;
441
+ else if (ch === "]") bracketDepth++;
442
+ else if (ch === "[") bracketDepth--;
443
+ else if (ch === "}") braceDepth++;
444
+ else if (ch === "{") braceDepth--;
445
+ if (parenDepth === 0 && bracketDepth === 0 && braceDepth === 0) {
446
+ if (ch === "*" && i > 0 && text[i - 1] !== "*" && (i + 1 >= text.length || text[i + 1] !== "*")) {
447
+ splitIdx = i;
448
+ splitOp = "*";
449
+ break;
450
+ }
451
+ if (ch === "/" && i > 0) {
452
+ if (text[i - 1] === "/") {
453
+ splitIdx = i - 1;
454
+ splitOp = "//";
455
+ break;
456
+ }
457
+ splitIdx = i;
458
+ splitOp = "/";
459
+ break;
460
+ }
461
+ if (ch === "%") {
462
+ splitIdx = i;
463
+ splitOp = "%";
464
+ break;
465
+ }
466
+ }
467
+ }
468
+ if (splitIdx === -1) {
469
+ return parsePower(text, lineNo, errors);
470
+ }
471
+ const left = parseMulDiv(text.slice(0, splitIdx).trim(), lineNo, errors);
472
+ const right = parsePower(
473
+ text.slice(splitIdx + splitOp.length).trim(),
474
+ lineNo,
475
+ errors
476
+ );
477
+ return { type: "BinOp", left, op: splitOp, right };
478
+ }
479
+ function parsePower(text, lineNo, errors) {
480
+ const idx = findTopLevel(text, "**");
481
+ if (idx === -1) {
482
+ return parseUnary(text, lineNo, errors);
483
+ }
484
+ const left = parseUnary(text.slice(0, idx).trim(), lineNo, errors);
485
+ const right = parsePower(text.slice(idx + 2).trim(), lineNo, errors);
486
+ return { type: "BinOp", left, op: "**", right };
487
+ }
488
+ function parseUnary(text, lineNo, errors) {
489
+ text = text.trim();
490
+ if (text.startsWith("-") && text.length > 1) {
491
+ const rest = text.slice(1).trim();
492
+ if (rest && !/^\d/.test(rest)) {
493
+ const operand = parseUnary(rest, lineNo, errors);
494
+ return { type: "UnaryOp", op: "-", operand };
495
+ }
496
+ }
497
+ if (text.startsWith("+") && text.length > 1) {
498
+ const rest = text.slice(1).trim();
499
+ if (rest && !/^\d/.test(rest)) {
500
+ const operand = parseUnary(rest, lineNo, errors);
501
+ return { type: "UnaryOp", op: "+", operand };
502
+ }
503
+ }
504
+ return parsePostfix(text, lineNo, errors);
505
+ }
506
+ function parsePostfix(text, lineNo, errors) {
507
+ text = text.trim();
508
+ const parsed = parsePrimaryWithPostfix(text, lineNo, errors);
509
+ return parsed;
510
+ }
511
+ function parsePrimaryWithPostfix(text, lineNo, errors) {
512
+ text = text.trim();
513
+ let pos = 0;
514
+ const { node, endPos } = parsePrimaryAt(text, pos, lineNo, errors);
515
+ pos = endPos;
516
+ let current = node;
517
+ while (pos < text.length) {
518
+ const ch = text[pos];
519
+ if (ch === ".") {
520
+ pos++;
521
+ const attrMatch = text.slice(pos).match(/^[A-Za-z_][A-Za-z0-9_]*/);
522
+ if (attrMatch) {
523
+ const attr = attrMatch[0];
524
+ pos += attr.length;
525
+ current = { type: "Attribute", value: current, attr };
526
+ } else {
527
+ break;
528
+ }
529
+ } else if (ch === "[") {
530
+ const closeIdx = findMatchingClose(text, pos, "[", "]");
531
+ if (closeIdx === -1) break;
532
+ const sliceStr = text.slice(pos + 1, closeIdx).trim();
533
+ const sliceNode = parseExpr(sliceStr, lineNo, errors);
534
+ current = { type: "Subscript", value: current, slice: sliceNode };
535
+ pos = closeIdx + 1;
536
+ } else if (ch === "(") {
537
+ const closeIdx = findMatchingClose(text, pos, "(", ")");
538
+ if (closeIdx === -1) break;
539
+ const argsStr = text.slice(pos + 1, closeIdx).trim();
540
+ const { args, kwargs } = parseCallArgs(argsStr, lineNo, errors);
541
+ current = { type: "Call", func: current, args, kwargs };
542
+ pos = closeIdx + 1;
543
+ } else if (ch === " " || ch === " ") {
544
+ pos++;
545
+ } else {
546
+ break;
547
+ }
548
+ }
549
+ return current;
550
+ }
551
+ function parsePrimaryAt(text, pos, lineNo, errors) {
552
+ while (pos < text.length && (text[pos] === " " || text[pos] === " ")) pos++;
553
+ if (pos >= text.length) {
554
+ return {
555
+ node: { type: "Constant", value: null, kind: "none" },
556
+ endPos: pos
557
+ };
558
+ }
559
+ const remaining = text.slice(pos);
560
+ if (remaining[0] === "(") {
561
+ const closeIdx = findMatchingClose(text, pos, "(", ")");
562
+ if (closeIdx !== -1) {
563
+ const inner = text.slice(pos + 1, closeIdx).trim();
564
+ const node = parseExpr(inner, lineNo, errors);
565
+ return { node, endPos: closeIdx + 1 };
566
+ }
567
+ }
568
+ if (remaining[0] === "[") {
569
+ const closeIdx = findMatchingClose(text, pos, "[", "]");
570
+ if (closeIdx !== -1) {
571
+ const inner = text.slice(pos + 1, closeIdx).trim();
572
+ const elts = inner ? splitArgs(inner).map((e) => parseExpr(e.trim(), lineNo, errors)) : [];
573
+ return {
574
+ node: { type: "List", elts },
575
+ endPos: closeIdx + 1
576
+ };
577
+ }
578
+ }
579
+ if (remaining[0] === "{") {
580
+ const closeIdx = findMatchingClose(text, pos, "{", "}");
581
+ if (closeIdx !== -1) {
582
+ const inner = text.slice(pos + 1, closeIdx).trim();
583
+ const keys = [];
584
+ const values = [];
585
+ if (inner) {
586
+ const entries = splitArgs(inner);
587
+ for (const entry of entries) {
588
+ const colonIdx = findTopLevel(entry, ":");
589
+ if (colonIdx !== -1) {
590
+ keys.push(
591
+ parseExpr(entry.slice(0, colonIdx).trim(), lineNo, errors)
592
+ );
593
+ values.push(
594
+ parseExpr(entry.slice(colonIdx + 1).trim(), lineNo, errors)
595
+ );
596
+ }
597
+ }
598
+ }
599
+ return {
600
+ node: { type: "Dict", keys, values },
601
+ endPos: closeIdx + 1
602
+ };
603
+ }
604
+ }
605
+ if (remaining[0] === '"' || remaining[0] === "'") {
606
+ const quote = remaining[0];
607
+ if (remaining.startsWith(quote + quote + quote)) {
608
+ const endQuote = remaining.indexOf(quote + quote + quote, 3);
609
+ if (endQuote !== -1) {
610
+ const value = remaining.slice(3, endQuote);
611
+ return {
612
+ node: { type: "Constant", value, kind: "str" },
613
+ endPos: pos + endQuote + 3
614
+ };
615
+ }
616
+ }
617
+ let i = 1;
618
+ while (i < remaining.length) {
619
+ if (remaining[i] === "\\" && i + 1 < remaining.length) {
620
+ i += 2;
621
+ continue;
622
+ }
623
+ if (remaining[i] === quote) {
624
+ const value = remaining.slice(1, i);
625
+ return {
626
+ node: { type: "Constant", value, kind: "str" },
627
+ endPos: pos + i + 1
628
+ };
629
+ }
630
+ i++;
631
+ }
632
+ return {
633
+ node: {
634
+ type: "Constant",
635
+ value: remaining.slice(1),
636
+ kind: "str"
637
+ },
638
+ endPos: pos + remaining.length
639
+ };
640
+ }
641
+ if ((remaining[0] === "f" || remaining[0] === "F") && remaining.length > 1 && (remaining[1] === '"' || remaining[1] === "'")) {
642
+ const quote = remaining[1];
643
+ let i = 2;
644
+ while (i < remaining.length) {
645
+ if (remaining[i] === "\\" && i + 1 < remaining.length) {
646
+ i += 2;
647
+ continue;
648
+ }
649
+ if (remaining[i] === quote) {
650
+ const value = remaining.slice(2, i);
651
+ return {
652
+ node: { type: "Constant", value, kind: "str" },
653
+ endPos: pos + i + 1
654
+ };
655
+ }
656
+ i++;
657
+ }
658
+ return {
659
+ node: {
660
+ type: "Constant",
661
+ value: remaining.slice(2),
662
+ kind: "str"
663
+ },
664
+ endPos: pos + remaining.length
665
+ };
666
+ }
667
+ const numMatch = remaining.match(/^-?\d+(\.\d+)?/);
668
+ if (numMatch) {
669
+ const numStr = numMatch[0];
670
+ const isFloat = numStr.includes(".");
671
+ return {
672
+ node: {
673
+ type: "Constant",
674
+ value: isFloat ? parseFloat(numStr) : parseInt(numStr, 10),
675
+ kind: isFloat ? "float" : "int"
676
+ },
677
+ endPos: pos + numStr.length
678
+ };
679
+ }
680
+ const identMatch = remaining.match(/^[A-Za-z_][A-Za-z0-9_]*/);
681
+ if (identMatch) {
682
+ const id = identMatch[0];
683
+ const endPos = pos + id.length;
684
+ if (id === "True") {
685
+ return {
686
+ node: { type: "Constant", value: true, kind: "bool" },
687
+ endPos
688
+ };
689
+ }
690
+ if (id === "False") {
691
+ return {
692
+ node: { type: "Constant", value: false, kind: "bool" },
693
+ endPos
694
+ };
695
+ }
696
+ if (id === "None") {
697
+ return {
698
+ node: { type: "Constant", value: null, kind: "none" },
699
+ endPos
700
+ };
701
+ }
702
+ return {
703
+ node: { type: "Name", id },
704
+ endPos
705
+ };
706
+ }
707
+ errors.push({
708
+ message: `Unexpected token: ${remaining.slice(0, 20)}`,
709
+ line: lineNo,
710
+ severity: "error"
711
+ });
712
+ return {
713
+ node: { type: "Name", id: remaining.trim() },
714
+ endPos: pos + remaining.length
715
+ };
716
+ }
717
+ function splitTopLevel(text, delimiter) {
718
+ const parts = [];
719
+ let parenDepth = 0;
720
+ let bracketDepth = 0;
721
+ let braceDepth = 0;
722
+ let inStr = null;
723
+ let start = 0;
724
+ for (let i = 0; i <= text.length - delimiter.length; i++) {
725
+ const ch = text[i];
726
+ if ((ch === '"' || ch === "'") && (i === 0 || text[i - 1] !== "\\")) {
727
+ if (inStr === ch) {
728
+ inStr = null;
729
+ continue;
730
+ } else if (!inStr) {
731
+ inStr = ch;
732
+ continue;
733
+ }
734
+ }
735
+ if (inStr) continue;
736
+ if (ch === "(") parenDepth++;
737
+ else if (ch === ")") parenDepth--;
738
+ else if (ch === "[") bracketDepth++;
739
+ else if (ch === "]") bracketDepth--;
740
+ else if (ch === "{") braceDepth++;
741
+ else if (ch === "}") braceDepth--;
742
+ if (parenDepth === 0 && bracketDepth === 0 && braceDepth === 0 && text.slice(i, i + delimiter.length) === delimiter) {
743
+ parts.push(text.slice(start, i).trim());
744
+ start = i + delimiter.length;
745
+ i = start - 1;
746
+ }
747
+ }
748
+ parts.push(text.slice(start).trim());
749
+ return parts;
750
+ }
751
+ function findTopLevel(text, needle) {
752
+ let parenDepth = 0;
753
+ let bracketDepth = 0;
754
+ let braceDepth = 0;
755
+ let inStr = null;
756
+ for (let i = 0; i <= text.length - needle.length; i++) {
757
+ const ch = text[i];
758
+ if ((ch === '"' || ch === "'") && (i === 0 || text[i - 1] !== "\\")) {
759
+ if (inStr === ch) {
760
+ inStr = null;
761
+ continue;
762
+ } else if (!inStr) {
763
+ inStr = ch;
764
+ continue;
765
+ }
766
+ }
767
+ if (inStr) continue;
768
+ if (ch === "(") parenDepth++;
769
+ else if (ch === ")") parenDepth--;
770
+ else if (ch === "[") bracketDepth++;
771
+ else if (ch === "]") bracketDepth--;
772
+ else if (ch === "{") braceDepth++;
773
+ else if (ch === "}") braceDepth--;
774
+ if (parenDepth === 0 && bracketDepth === 0 && braceDepth === 0 && text.slice(i, i + needle.length) === needle) {
775
+ return i;
776
+ }
777
+ }
778
+ return -1;
779
+ }
780
+ function findMatchingClose(text, openIdx, open, close) {
781
+ let depth = 0;
782
+ let inStr = null;
783
+ for (let i = openIdx; i < text.length; i++) {
784
+ const ch = text[i];
785
+ if ((ch === '"' || ch === "'") && (i === 0 || text[i - 1] !== "\\")) {
786
+ if (inStr === ch) {
787
+ inStr = null;
788
+ continue;
789
+ } else if (!inStr) {
790
+ inStr = ch;
791
+ continue;
792
+ }
793
+ }
794
+ if (inStr) continue;
795
+ if (ch === open) depth++;
796
+ else if (ch === close) {
797
+ depth--;
798
+ if (depth === 0) return i;
799
+ }
800
+ }
801
+ return -1;
802
+ }
803
+ function splitArgs(text) {
804
+ return splitTopLevel(text, ",");
805
+ }
806
+ function parseCallArgs(text, lineNo, errors) {
807
+ if (!text.trim()) return { args: [], kwargs: [] };
808
+ const parts = splitArgs(text);
809
+ const args = [];
810
+ const kwargs = [];
811
+ for (const part of parts) {
812
+ const trimmed = part.trim();
813
+ const kwMatch = trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*=(?!=)\s*(.+)$/);
814
+ if (kwMatch) {
815
+ kwargs.push({
816
+ key: kwMatch[1],
817
+ value: parseExpr(kwMatch[2].trim(), lineNo, errors)
818
+ });
819
+ } else {
820
+ args.push(parseExpr(trimmed, lineNo, errors));
821
+ }
822
+ }
823
+ return { args, kwargs };
824
+ }
825
+
826
+ // src/transforms.ts
827
+ function transformModule(ast) {
828
+ return {
829
+ type: "BendModule",
830
+ source: "Module",
831
+ body: transformNodes(ast.body)
832
+ };
833
+ }
834
+ function transformNodes(nodes) {
835
+ return nodes.map(transformNode);
836
+ }
837
+ function transformNode(node) {
838
+ switch (node.type) {
839
+ case "FunctionDef":
840
+ return transformFunctionDef(node);
841
+ case "For":
842
+ return transformFor(node);
843
+ case "While":
844
+ return transformWhile(node);
845
+ case "ClassDef":
846
+ return transformClass(node);
847
+ case "Try":
848
+ return transformTry(node);
849
+ default:
850
+ return passthrough(node);
851
+ }
852
+ }
853
+ function transformFunctionDef(node) {
854
+ return {
855
+ type: "BendFunctionDef",
856
+ source: "FunctionDef",
857
+ name: node.name,
858
+ params: node.params,
859
+ body: transformNodes(node.body),
860
+ decorators: node.decorators
861
+ };
862
+ }
863
+ function transformFor(node) {
864
+ const accumulator = findAccumulator(node.body);
865
+ return {
866
+ type: "BendFold",
867
+ source: "For",
868
+ target: node.target,
869
+ iter: node.iter,
870
+ body: transformNodes(node.body),
871
+ accumulator
872
+ };
873
+ }
874
+ function findAccumulator(body) {
875
+ for (const stmt of body) {
876
+ if (stmt.type === "AugAssign") {
877
+ return stmt.target;
878
+ }
879
+ }
880
+ return null;
881
+ }
882
+ function transformWhile(node) {
883
+ return {
884
+ type: "BendLoop",
885
+ source: "While",
886
+ test: node.test,
887
+ body: transformNodes(node.body),
888
+ stateVars: []
889
+ };
890
+ }
891
+ function transformClass(node) {
892
+ const fields = extractFields(node.body);
893
+ const methods = [];
894
+ for (const member of node.body) {
895
+ if (member.type === "FunctionDef") {
896
+ methods.push(transformFunctionDef(member));
897
+ }
898
+ }
899
+ return {
900
+ type: "BendObject",
901
+ source: "ClassDef",
902
+ name: node.name,
903
+ bases: node.bases,
904
+ fields,
905
+ methods
906
+ };
907
+ }
908
+ function extractFields(body) {
909
+ const fields = [];
910
+ const initMethod = body.find(
911
+ (n) => n.type === "FunctionDef" && n.name === "__init__"
912
+ );
913
+ if (!initMethod) return fields;
914
+ for (const stmt of initMethod.body) {
915
+ if (stmt.type === "Assign") {
916
+ const assign = stmt;
917
+ if (assign.target.startsWith("self.")) {
918
+ fields.push(assign.target.slice(5));
919
+ }
920
+ }
921
+ }
922
+ return fields;
923
+ }
924
+ function transformTry(node) {
925
+ return {
926
+ type: "BendWith",
927
+ source: "Try",
928
+ body: transformNodes(node.body),
929
+ handler: node.handler ? {
930
+ name: node.handler.name,
931
+ type: node.handler.type,
932
+ body: transformNodes(node.handler.body)
933
+ } : null
934
+ };
935
+ }
936
+ function passthrough(node) {
937
+ return {
938
+ type: "BendPassthrough",
939
+ source: node.type,
940
+ node
941
+ };
942
+ }
943
+
944
+ // src/stdlib.ts
945
+ var BUILTIN_MAP = {
946
+ len: "List/len",
947
+ print: "IO/print",
948
+ range: "List/range",
949
+ int: "Num/to_int",
950
+ float: "Num/to_float",
951
+ str: "Str/from",
952
+ bool: "Bool/from",
953
+ abs: "Num/abs",
954
+ min: "Num/min",
955
+ max: "Num/max",
956
+ sum: "List/sum",
957
+ sorted: "List/sort",
958
+ reversed: "List/reverse",
959
+ enumerate: "List/enumerate",
960
+ zip: "List/zip",
961
+ map: "List/map",
962
+ filter: "List/filter",
963
+ any: "List/any",
964
+ all: "List/all",
965
+ isinstance: "Type/is",
966
+ type: "Type/of",
967
+ input: "IO/input",
968
+ open: "IO/open",
969
+ round: "Num/round",
970
+ list: "List/from",
971
+ dict: "Map/from",
972
+ tuple: "Tuple/from",
973
+ set: "Set/from"
974
+ };
975
+ var OP_MAP = {
976
+ "+": "+",
977
+ "-": "-",
978
+ "*": "*",
979
+ "/": "/",
980
+ "//": "/",
981
+ "%": "%",
982
+ "**": "**",
983
+ "&": "&",
984
+ "|": "|",
985
+ "==": "==",
986
+ "!=": "!=",
987
+ "<": "<",
988
+ ">": ">",
989
+ "<=": "<=",
990
+ ">=": ">=",
991
+ and: "&",
992
+ or: "|",
993
+ not: "!",
994
+ in: "List/contains",
995
+ "not in": "List/not_contains"
996
+ };
997
+ var METHOD_MAP = {
998
+ // list methods
999
+ "list.append": "List/push",
1000
+ "list.pop": "List/pop",
1001
+ "list.insert": "List/insert",
1002
+ "list.remove": "List/remove",
1003
+ "list.index": "List/index",
1004
+ "list.count": "List/count",
1005
+ "list.sort": "List/sort",
1006
+ "list.reverse": "List/reverse",
1007
+ "list.extend": "List/concat",
1008
+ "list.copy": "List/copy",
1009
+ "list.clear": "List/clear",
1010
+ // dict methods
1011
+ "dict.get": "Map/get",
1012
+ "dict.keys": "Map/keys",
1013
+ "dict.values": "Map/values",
1014
+ "dict.items": "Map/items",
1015
+ "dict.update": "Map/merge",
1016
+ "dict.pop": "Map/remove",
1017
+ "dict.setdefault": "Map/get_or_set",
1018
+ "dict.clear": "Map/clear",
1019
+ "dict.copy": "Map/copy",
1020
+ // str methods
1021
+ "str.split": "Str/split",
1022
+ "str.join": "Str/join",
1023
+ "str.strip": "Str/trim",
1024
+ "str.lower": "Str/lower",
1025
+ "str.upper": "Str/upper",
1026
+ "str.replace": "Str/replace",
1027
+ "str.startswith": "Str/starts_with",
1028
+ "str.endswith": "Str/ends_with",
1029
+ "str.find": "Str/find",
1030
+ "str.format": "Str/format"
1031
+ };
1032
+
1033
+ // src/codegen.ts
1034
+ function generateBend(module) {
1035
+ const lines = [];
1036
+ for (const node of module.body) {
1037
+ lines.push(generateNode(node, 0));
1038
+ }
1039
+ return lines.join("\n\n") + "\n";
1040
+ }
1041
+ function generateNode(node, depth) {
1042
+ switch (node.type) {
1043
+ case "BendFunctionDef":
1044
+ return generateFunctionDef(node, depth);
1045
+ case "BendFold":
1046
+ return generateFold(node, depth);
1047
+ case "BendLoop":
1048
+ return generateLoop(node, depth);
1049
+ case "BendObject":
1050
+ return generateObject(node, depth);
1051
+ case "BendWith":
1052
+ return generateWith(node, depth);
1053
+ case "BendPassthrough":
1054
+ return generatePassthrough(node, depth);
1055
+ default:
1056
+ return indent(depth) + `# unsupported: ${node.type}`;
1057
+ }
1058
+ }
1059
+ function generateFunctionDef(node, depth) {
1060
+ const params = node.params.join(", ");
1061
+ const head = `${indent(depth)}def ${node.name}(${params}):`;
1062
+ const bodyLines = node.body.map((n) => generateNode(n, depth + 1));
1063
+ if (bodyLines.length === 0) {
1064
+ return head + "\n" + indent(depth + 1) + "return *";
1065
+ }
1066
+ return head + "\n" + bodyLines.join("\n");
1067
+ }
1068
+ function generateFold(node, depth) {
1069
+ const iterExpr = generateExpr(node.iter, depth);
1070
+ const head = `${indent(depth)}fold ${iterExpr}:`;
1071
+ const consCase = `${indent(depth + 1)}case List/Cons:`;
1072
+ const bodyLines = node.body.map((n) => generateNode(n, depth + 2));
1073
+ const nilCase = `${indent(depth + 1)}case List/Nil:`;
1074
+ const nilBody = node.accumulator ? `${indent(depth + 2)}${node.accumulator}` : `${indent(depth + 2)}*`;
1075
+ return [head, consCase, ...bodyLines, nilCase, nilBody].join("\n");
1076
+ }
1077
+ function generateLoop(node, depth) {
1078
+ const testExpr = generateExpr(node.test, depth);
1079
+ const head = `${indent(depth)}bend x = 0:`;
1080
+ const whenLine = `${indent(depth + 1)}when ${testExpr}:`;
1081
+ const bodyLines = node.body.map((n) => generateNode(n, depth + 2));
1082
+ const forkLine = `${indent(depth + 2)}fork(x + 1)`;
1083
+ const elseLine = `${indent(depth + 1)}else:`;
1084
+ const elseBody = `${indent(depth + 2)}x`;
1085
+ return [head, whenLine, ...bodyLines, forkLine, elseLine, elseBody].join(
1086
+ "\n"
1087
+ );
1088
+ }
1089
+ function generateObject(node, depth) {
1090
+ const fieldsStr = node.fields.length > 0 ? ` { ${node.fields.join(", ")} }` : "";
1091
+ const head = `${indent(depth)}object ${node.name}${fieldsStr}`;
1092
+ if (node.methods.length === 0) {
1093
+ return head;
1094
+ }
1095
+ const nonInitMethods = node.methods.filter((m) => m.name !== "__init__");
1096
+ if (nonInitMethods.length === 0) {
1097
+ return head;
1098
+ }
1099
+ const methodLines = nonInitMethods.map(
1100
+ (m) => generateFunctionDef(m, depth)
1101
+ );
1102
+ return head + "\n\n" + methodLines.join("\n\n");
1103
+ }
1104
+ function generateWith(node, depth) {
1105
+ const head = `${indent(depth)}with Result:`;
1106
+ const bodyLines = node.body.map((n) => generateNode(n, depth + 1));
1107
+ if (!node.handler) {
1108
+ return [head, ...bodyLines].join("\n");
1109
+ }
1110
+ const handlerLines = node.handler.body.map(
1111
+ (n) => generateNode(n, depth + 1)
1112
+ );
1113
+ const handlerHead = node.handler.name ? `${indent(depth)}catch ${node.handler.name}:` : `${indent(depth)}catch _err:`;
1114
+ return [head, ...bodyLines, handlerHead, ...handlerLines].join("\n");
1115
+ }
1116
+ function generatePassthrough(node, depth) {
1117
+ return generatePyStatement(node.node, depth);
1118
+ }
1119
+ function generatePyStatement(node, depth) {
1120
+ switch (node.type) {
1121
+ case "Return":
1122
+ return generateReturn(node, depth);
1123
+ case "Assign":
1124
+ return generateAssign(node, depth);
1125
+ case "AugAssign":
1126
+ return generateAugAssign(node, depth);
1127
+ case "If":
1128
+ return generateIf(node, depth);
1129
+ case "Expr":
1130
+ return indent(depth) + generateExpr(node.value, depth);
1131
+ default:
1132
+ return indent(depth) + generateExpr(node, depth);
1133
+ }
1134
+ }
1135
+ function generateReturn(node, depth) {
1136
+ if (!node.value) {
1137
+ return `${indent(depth)}return *`;
1138
+ }
1139
+ return `${indent(depth)}return ${generateExpr(node.value, depth)}`;
1140
+ }
1141
+ function generateAssign(node, depth) {
1142
+ return `${indent(depth)}let ${node.target} = ${generateExpr(node.value, depth)}`;
1143
+ }
1144
+ function generateAugAssign(node, depth) {
1145
+ const op = node.op.slice(0, -1);
1146
+ const mappedOp = OP_MAP[op] ?? op;
1147
+ return `${indent(depth)}let ${node.target} = (${node.target} ${mappedOp} ${generateExpr(node.value, depth)})`;
1148
+ }
1149
+ function generateIf(node, depth) {
1150
+ const testExpr = generateExpr(node.test, depth);
1151
+ const head = `${indent(depth)}if ${testExpr}:`;
1152
+ const bodyLines = node.body.map(
1153
+ (n) => generatePyStatement(n, depth + 1)
1154
+ );
1155
+ if (!node.orelse || node.orelse.length === 0) {
1156
+ return [head, ...bodyLines].join("\n");
1157
+ }
1158
+ const elseLine = `${indent(depth)}else:`;
1159
+ const elseLines = node.orelse.map(
1160
+ (n) => generatePyStatement(n, depth + 1)
1161
+ );
1162
+ return [head, ...bodyLines, elseLine, ...elseLines].join("\n");
1163
+ }
1164
+ function generateExpr(node, _depth) {
1165
+ switch (node.type) {
1166
+ case "Name":
1167
+ return node.id;
1168
+ case "Constant":
1169
+ return generateConstant(node);
1170
+ case "BinOp": {
1171
+ const binop = node;
1172
+ const mappedOp = OP_MAP[binop.op] ?? binop.op;
1173
+ return `(${generateExpr(binop.left, _depth)} ${mappedOp} ${generateExpr(binop.right, _depth)})`;
1174
+ }
1175
+ case "UnaryOp": {
1176
+ const unary = node;
1177
+ const mappedOp = OP_MAP[unary.op] ?? unary.op;
1178
+ if (mappedOp === "!") {
1179
+ return `(!${generateExpr(unary.operand, _depth)})`;
1180
+ }
1181
+ return `(${mappedOp}${generateExpr(unary.operand, _depth)})`;
1182
+ }
1183
+ case "Compare": {
1184
+ const cmp = node;
1185
+ if (cmp.ops.length === 1) {
1186
+ const mappedOp = OP_MAP[cmp.ops[0]] ?? cmp.ops[0];
1187
+ return `(${generateExpr(cmp.left, _depth)} ${mappedOp} ${generateExpr(cmp.comparators[0], _depth)})`;
1188
+ }
1189
+ const parts = [];
1190
+ let prevExpr = generateExpr(cmp.left, _depth);
1191
+ for (let i = 0; i < cmp.ops.length; i++) {
1192
+ const mappedOp = OP_MAP[cmp.ops[i]] ?? cmp.ops[i];
1193
+ const rightExpr = generateExpr(cmp.comparators[i], _depth);
1194
+ parts.push(`(${prevExpr} ${mappedOp} ${rightExpr})`);
1195
+ prevExpr = rightExpr;
1196
+ }
1197
+ return parts.join(" & ");
1198
+ }
1199
+ case "BoolOp": {
1200
+ const boolop = node;
1201
+ const mappedOp = OP_MAP[boolop.op] ?? boolop.op;
1202
+ const exprs = boolop.values.map((v) => generateExpr(v, _depth));
1203
+ return `(${exprs.join(` ${mappedOp} `)})`;
1204
+ }
1205
+ case "Call": {
1206
+ const call = node;
1207
+ const funcName = generateExpr(call.func, _depth);
1208
+ const mappedName = BUILTIN_MAP[funcName] ?? funcName;
1209
+ const args = call.args.map((a) => generateExpr(a, _depth));
1210
+ const kwargs = call.kwargs.map(
1211
+ (kw) => `${kw.key}=${generateExpr(kw.value, _depth)}`
1212
+ );
1213
+ const allArgs = [...args, ...kwargs].join(", ");
1214
+ return `${mappedName}(${allArgs})`;
1215
+ }
1216
+ case "Attribute": {
1217
+ const attr = node;
1218
+ return `${generateExpr(attr.value, _depth)}.${attr.attr}`;
1219
+ }
1220
+ case "Subscript": {
1221
+ const sub = node;
1222
+ return `List/get(${generateExpr(sub.value, _depth)}, ${generateExpr(sub.slice, _depth)})`;
1223
+ }
1224
+ case "List": {
1225
+ const list = node;
1226
+ const elts = list.elts.map((e) => generateExpr(e, _depth));
1227
+ return `[${elts.join(", ")}]`;
1228
+ }
1229
+ case "Dict": {
1230
+ const dict = node;
1231
+ const entries = dict.keys.map(
1232
+ (k, i) => `${generateExpr(k, _depth)}: ${generateExpr(dict.values[i], _depth)}`
1233
+ );
1234
+ return `{${entries.join(", ")}}`;
1235
+ }
1236
+ case "Lambda": {
1237
+ const lam = node;
1238
+ const params = lam.params.join(", ");
1239
+ return `@${params} ${generateExpr(lam.body, _depth)}`;
1240
+ }
1241
+ default:
1242
+ return `/* unsupported: ${node.type} */`;
1243
+ }
1244
+ }
1245
+ function generateConstant(node) {
1246
+ switch (node.kind) {
1247
+ case "str":
1248
+ return `"${String(node.value).replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
1249
+ case "int":
1250
+ case "float":
1251
+ return String(node.value);
1252
+ case "bool":
1253
+ return node.value ? "True" : "False";
1254
+ case "none":
1255
+ return "*";
1256
+ default:
1257
+ return String(node.value);
1258
+ }
1259
+ }
1260
+ function indent(depth) {
1261
+ return " ".repeat(depth);
1262
+ }
1263
+
1264
+ // src/index.ts
1265
+ function compile(pythonSource) {
1266
+ const warnings = [];
1267
+ const { ast, errors: parseErrors } = parsePython(pythonSource);
1268
+ if (!ast) {
1269
+ return {
1270
+ bend: "",
1271
+ errors: parseErrors,
1272
+ warnings
1273
+ };
1274
+ }
1275
+ const errors = parseErrors.filter((e) => e.severity === "error");
1276
+ const parseWarnings = parseErrors.filter((e) => e.severity === "warning");
1277
+ warnings.push(...parseWarnings.map((w) => w.message));
1278
+ const bendModule = transformModule(ast);
1279
+ const bend = generateBend(bendModule);
1280
+ return {
1281
+ bend,
1282
+ errors,
1283
+ warnings
1284
+ };
1285
+ }
1286
+ export {
1287
+ BUILTIN_MAP,
1288
+ METHOD_MAP,
1289
+ OP_MAP,
1290
+ compile,
1291
+ generateBend,
1292
+ parsePython,
1293
+ transformModule
1294
+ };
1295
+ //# sourceMappingURL=index.js.map