multilingualprogramming 0.2.0__py3-none-any.whl

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 (61) hide show
  1. multilingualprogramming/__init__.py +74 -0
  2. multilingualprogramming/__main__.py +194 -0
  3. multilingualprogramming/codegen/__init__.py +12 -0
  4. multilingualprogramming/codegen/executor.py +215 -0
  5. multilingualprogramming/codegen/python_generator.py +592 -0
  6. multilingualprogramming/codegen/repl.py +489 -0
  7. multilingualprogramming/codegen/runtime_builtins.py +308 -0
  8. multilingualprogramming/core/__init__.py +12 -0
  9. multilingualprogramming/core/ir.py +29 -0
  10. multilingualprogramming/core/lowering.py +24 -0
  11. multilingualprogramming/datetime/__init__.py +11 -0
  12. multilingualprogramming/datetime/date_parser.py +190 -0
  13. multilingualprogramming/datetime/mp_date.py +210 -0
  14. multilingualprogramming/datetime/mp_datetime.py +153 -0
  15. multilingualprogramming/datetime/mp_time.py +147 -0
  16. multilingualprogramming/datetime/resource_loader.py +18 -0
  17. multilingualprogramming/exceptions.py +158 -0
  18. multilingualprogramming/imports.py +150 -0
  19. multilingualprogramming/keyword/__init__.py +13 -0
  20. multilingualprogramming/keyword/keyword_registry.py +249 -0
  21. multilingualprogramming/keyword/keyword_validator.py +59 -0
  22. multilingualprogramming/keyword/language_pack_validator.py +110 -0
  23. multilingualprogramming/lexer/__init__.py +11 -0
  24. multilingualprogramming/lexer/lexer.py +570 -0
  25. multilingualprogramming/lexer/source_reader.py +91 -0
  26. multilingualprogramming/lexer/token.py +54 -0
  27. multilingualprogramming/lexer/token_types.py +38 -0
  28. multilingualprogramming/numeral/__init__.py +11 -0
  29. multilingualprogramming/numeral/abstract_numeral.py +232 -0
  30. multilingualprogramming/numeral/complex_numeral.py +190 -0
  31. multilingualprogramming/numeral/fraction_numeral.py +165 -0
  32. multilingualprogramming/numeral/mp_numeral.py +243 -0
  33. multilingualprogramming/numeral/numeral_converter.py +151 -0
  34. multilingualprogramming/numeral/roman_numeral.py +301 -0
  35. multilingualprogramming/numeral/unicode_numeral.py +292 -0
  36. multilingualprogramming/parser/__init__.py +28 -0
  37. multilingualprogramming/parser/ast_nodes.py +459 -0
  38. multilingualprogramming/parser/ast_printer.py +677 -0
  39. multilingualprogramming/parser/error_messages.py +75 -0
  40. multilingualprogramming/parser/parser.py +1796 -0
  41. multilingualprogramming/parser/semantic_analyzer.py +689 -0
  42. multilingualprogramming/parser/surface_normalizer.py +282 -0
  43. multilingualprogramming/resources/datetime/eras.json +23 -0
  44. multilingualprogramming/resources/datetime/formats.json +32 -0
  45. multilingualprogramming/resources/datetime/months.json +150 -0
  46. multilingualprogramming/resources/datetime/weekdays.json +90 -0
  47. multilingualprogramming/resources/parser/error_messages.json +310 -0
  48. multilingualprogramming/resources/repl/commands.json +636 -0
  49. multilingualprogramming/resources/usm/builtins_aliases.json +731 -0
  50. multilingualprogramming/resources/usm/keywords.json +1063 -0
  51. multilingualprogramming/resources/usm/operators.json +532 -0
  52. multilingualprogramming/resources/usm/schema.json +34 -0
  53. multilingualprogramming/resources/usm/surface_patterns.json +1523 -0
  54. multilingualprogramming/unicode_string.py +140 -0
  55. multilingualprogramming/version.py +9 -0
  56. multilingualprogramming-0.2.0.dist-info/METADATA +350 -0
  57. multilingualprogramming-0.2.0.dist-info/RECORD +61 -0
  58. multilingualprogramming-0.2.0.dist-info/WHEEL +5 -0
  59. multilingualprogramming-0.2.0.dist-info/entry_points.txt +3 -0
  60. multilingualprogramming-0.2.0.dist-info/licenses/LICENSE +674 -0
  61. multilingualprogramming-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,677 @@
1
+ #
2
+ # SPDX-FileCopyrightText: 2024 John Samuel <johnsamuelwrites@gmail.com>
3
+ #
4
+ # SPDX-License-Identifier: GPL-3.0-or-later
5
+ #
6
+
7
+ """AST pretty-printer for debugging and visualization."""
8
+
9
+ # pylint: disable=duplicate-code
10
+
11
+ class ASTPrinter:
12
+ """Pretty-prints an AST as indented text."""
13
+
14
+ def __init__(self, indent_str=" "):
15
+ self.indent_str = indent_str
16
+ self._depth = 0
17
+ self._lines = []
18
+
19
+ def print(self, node):
20
+ """Generate a pretty-printed string of the AST."""
21
+ self._depth = 0
22
+ self._lines = []
23
+ node.accept(self)
24
+ return "\n".join(self._lines)
25
+
26
+ def _emit(self, text):
27
+ """Add a line at the current indentation level."""
28
+ self._lines.append(self.indent_str * self._depth + text)
29
+
30
+ def _indent(self):
31
+ self._depth += 1
32
+
33
+ def _dedent(self):
34
+ self._depth -= 1
35
+
36
+ def _visit_body(self, body):
37
+ """Visit a list of statements."""
38
+ self._indent()
39
+ for stmt in body:
40
+ stmt.accept(self)
41
+ self._dedent()
42
+
43
+ # ------------------------------------------------------------------
44
+ # Visitors
45
+ # ------------------------------------------------------------------
46
+
47
+ def visit_Program(self, node):
48
+ self._emit("Program")
49
+ self._visit_body(node.body)
50
+
51
+ def visit_NumeralLiteral(self, node):
52
+ self._emit(f"NumeralLiteral {node.value!r}")
53
+
54
+ def visit_StringLiteral(self, node):
55
+ self._emit(f"StringLiteral {node.value!r}")
56
+
57
+ def visit_DateLiteral(self, node):
58
+ self._emit(f"DateLiteral {node.value!r}")
59
+
60
+ def visit_BooleanLiteral(self, node):
61
+ self._emit(f"BooleanLiteral {node.value}")
62
+
63
+ def visit_NoneLiteral(self, _node):
64
+ self._emit("NoneLiteral")
65
+
66
+ def visit_ListLiteral(self, node):
67
+ self._emit("ListLiteral")
68
+ self._indent()
69
+ for elem in node.elements:
70
+ elem.accept(self)
71
+ self._dedent()
72
+
73
+ def visit_DictLiteral(self, node):
74
+ self._emit("DictLiteral")
75
+ self._indent()
76
+ for entry in node.entries:
77
+ if isinstance(entry, tuple):
78
+ key, value = entry
79
+ self._emit("pair:")
80
+ self._indent()
81
+ key.accept(self)
82
+ value.accept(self)
83
+ self._dedent()
84
+ else:
85
+ self._emit("unpack:")
86
+ self._indent()
87
+ entry.accept(self)
88
+ self._dedent()
89
+ self._dedent()
90
+
91
+ def visit_SetLiteral(self, node):
92
+ self._emit("SetLiteral")
93
+ self._indent()
94
+ for elem in node.elements:
95
+ elem.accept(self)
96
+ self._dedent()
97
+
98
+ def visit_DictUnpackEntry(self, node):
99
+ self._emit("DictUnpackEntry")
100
+ self._indent()
101
+ node.value.accept(self)
102
+ self._dedent()
103
+
104
+ def visit_Identifier(self, node):
105
+ self._emit(f"Identifier {node.name!r}")
106
+
107
+ def visit_BinaryOp(self, node):
108
+ self._emit(f"BinaryOp {node.op!r}")
109
+ self._indent()
110
+ node.left.accept(self)
111
+ node.right.accept(self)
112
+ self._dedent()
113
+
114
+ def visit_UnaryOp(self, node):
115
+ self._emit(f"UnaryOp {node.op!r}")
116
+ self._indent()
117
+ node.operand.accept(self)
118
+ self._dedent()
119
+
120
+ def visit_BooleanOp(self, node):
121
+ self._emit(f"BooleanOp {node.op!r}")
122
+ self._indent()
123
+ for val in node.values:
124
+ val.accept(self)
125
+ self._dedent()
126
+
127
+ def visit_CompareOp(self, node):
128
+ self._emit("CompareOp")
129
+ self._indent()
130
+ node.left.accept(self)
131
+ for op, right in node.comparators:
132
+ self._emit(f"op: {op!r}")
133
+ right.accept(self)
134
+ self._dedent()
135
+
136
+ def visit_CallExpr(self, node):
137
+ self._emit("CallExpr")
138
+ self._indent()
139
+ self._emit("func:")
140
+ self._indent()
141
+ node.func.accept(self)
142
+ self._dedent()
143
+ if node.args:
144
+ self._emit("args:")
145
+ self._indent()
146
+ for arg in node.args:
147
+ arg.accept(self)
148
+ self._dedent()
149
+ if node.keywords:
150
+ self._emit("keywords:")
151
+ self._indent()
152
+ for name, val in node.keywords:
153
+ self._emit(f"{name}=")
154
+ self._indent()
155
+ val.accept(self)
156
+ self._dedent()
157
+ self._dedent()
158
+ self._dedent()
159
+
160
+ def visit_AttributeAccess(self, node):
161
+ self._emit(f"AttributeAccess .{node.attr!r}")
162
+ self._indent()
163
+ node.obj.accept(self)
164
+ self._dedent()
165
+
166
+ def visit_IndexAccess(self, node):
167
+ self._emit("IndexAccess")
168
+ self._indent()
169
+ node.obj.accept(self)
170
+ self._emit("index:")
171
+ self._indent()
172
+ node.index.accept(self)
173
+ self._dedent()
174
+ self._dedent()
175
+
176
+ def visit_LambdaExpr(self, node):
177
+ self._emit(f"LambdaExpr params={node.params!r}")
178
+ self._indent()
179
+ node.body.accept(self)
180
+ self._dedent()
181
+
182
+ def visit_YieldExpr(self, node):
183
+ self._emit("YieldExpr")
184
+ if node.value:
185
+ self._indent()
186
+ node.value.accept(self)
187
+ self._dedent()
188
+
189
+ def visit_AwaitExpr(self, node):
190
+ self._emit("AwaitExpr")
191
+ self._indent()
192
+ node.value.accept(self)
193
+ self._dedent()
194
+
195
+ def visit_NamedExpr(self, node):
196
+ self._emit("NamedExpr")
197
+ self._indent()
198
+ self._emit("target:")
199
+ self._indent()
200
+ node.target.accept(self)
201
+ self._dedent()
202
+ self._emit("value:")
203
+ self._indent()
204
+ node.value.accept(self)
205
+ self._dedent()
206
+ self._dedent()
207
+
208
+ def visit_ConditionalExpr(self, node):
209
+ self._emit("ConditionalExpr")
210
+ self._indent()
211
+ self._emit("condition:")
212
+ self._indent()
213
+ node.condition.accept(self)
214
+ self._dedent()
215
+ self._emit("true:")
216
+ self._indent()
217
+ node.true_expr.accept(self)
218
+ self._dedent()
219
+ self._emit("false:")
220
+ self._indent()
221
+ node.false_expr.accept(self)
222
+ self._dedent()
223
+ self._dedent()
224
+
225
+ def visit_VariableDeclaration(self, node):
226
+ kind = "const" if node.is_const else "let"
227
+ self._emit(f"VariableDeclaration ({kind}) {node.name!r}")
228
+ self._indent()
229
+ node.value.accept(self)
230
+ self._dedent()
231
+
232
+ def visit_Assignment(self, node):
233
+ self._emit(f"Assignment op={node.op!r}")
234
+ self._indent()
235
+ self._emit("target:")
236
+ self._indent()
237
+ node.target.accept(self)
238
+ self._dedent()
239
+ self._emit("value:")
240
+ self._indent()
241
+ node.value.accept(self)
242
+ self._dedent()
243
+ self._dedent()
244
+
245
+ def visit_AnnAssignment(self, node):
246
+ self._emit("AnnAssignment")
247
+ self._indent()
248
+ self._emit("target:")
249
+ self._indent()
250
+ node.target.accept(self)
251
+ self._dedent()
252
+ self._emit("annotation:")
253
+ self._indent()
254
+ node.annotation.accept(self)
255
+ self._dedent()
256
+ if node.value:
257
+ self._emit("value:")
258
+ self._indent()
259
+ node.value.accept(self)
260
+ self._dedent()
261
+ self._dedent()
262
+
263
+ def visit_ExpressionStatement(self, node):
264
+ self._emit("ExpressionStatement")
265
+ self._indent()
266
+ node.expression.accept(self)
267
+ self._dedent()
268
+
269
+ def visit_PassStatement(self, _node):
270
+ self._emit("PassStatement")
271
+
272
+ def visit_ReturnStatement(self, node):
273
+ self._emit("ReturnStatement")
274
+ if node.value:
275
+ self._indent()
276
+ node.value.accept(self)
277
+ self._dedent()
278
+
279
+ def visit_BreakStatement(self, _node):
280
+ self._emit("BreakStatement")
281
+
282
+ def visit_ContinueStatement(self, _node):
283
+ self._emit("ContinueStatement")
284
+
285
+ def visit_RaiseStatement(self, node):
286
+ self._emit("RaiseStatement")
287
+ if node.value:
288
+ self._indent()
289
+ node.value.accept(self)
290
+ self._dedent()
291
+
292
+ def visit_AssertStatement(self, node):
293
+ self._emit("AssertStatement")
294
+ self._indent()
295
+ self._emit("test:")
296
+ self._indent()
297
+ node.test.accept(self)
298
+ self._dedent()
299
+ if node.msg:
300
+ self._emit("msg:")
301
+ self._indent()
302
+ node.msg.accept(self)
303
+ self._dedent()
304
+ self._dedent()
305
+
306
+ def visit_ChainedAssignment(self, node):
307
+ self._emit("ChainedAssignment")
308
+ self._indent()
309
+ self._emit("targets:")
310
+ self._indent()
311
+ for target in node.targets:
312
+ target.accept(self)
313
+ self._dedent()
314
+ self._emit("value:")
315
+ self._indent()
316
+ node.value.accept(self)
317
+ self._dedent()
318
+ self._dedent()
319
+
320
+ def visit_GlobalStatement(self, node):
321
+ self._emit(f"GlobalStatement {node.names!r}")
322
+
323
+ def visit_LocalStatement(self, node):
324
+ self._emit(f"LocalStatement {node.names!r}")
325
+
326
+ def visit_YieldStatement(self, node):
327
+ self._emit("YieldStatement")
328
+ if node.value:
329
+ self._indent()
330
+ node.value.accept(self)
331
+ self._dedent()
332
+
333
+ def visit_IfStatement(self, node):
334
+ self._emit("IfStatement")
335
+ self._indent()
336
+ self._emit("condition:")
337
+ self._indent()
338
+ node.condition.accept(self)
339
+ self._dedent()
340
+ self._emit("body:")
341
+ self._visit_body(node.body)
342
+ for elif_cond, elif_body in node.elif_clauses:
343
+ self._emit("elif:")
344
+ self._indent()
345
+ elif_cond.accept(self)
346
+ self._dedent()
347
+ self._emit("elif_body:")
348
+ self._visit_body(elif_body)
349
+ if node.else_body:
350
+ self._emit("else:")
351
+ self._visit_body(node.else_body)
352
+ self._dedent()
353
+
354
+ def visit_WhileLoop(self, node):
355
+ self._emit("WhileLoop")
356
+ self._indent()
357
+ self._emit("condition:")
358
+ self._indent()
359
+ node.condition.accept(self)
360
+ self._dedent()
361
+ self._emit("body:")
362
+ self._visit_body(node.body)
363
+ if node.else_body:
364
+ self._emit("else:")
365
+ self._visit_body(node.else_body)
366
+ self._dedent()
367
+
368
+ def visit_ForLoop(self, node):
369
+ self._emit(f"ForLoop async={getattr(node, 'is_async', False)}")
370
+ self._indent()
371
+ self._emit("target:")
372
+ self._indent()
373
+ node.target.accept(self)
374
+ self._dedent()
375
+ self._emit("iterable:")
376
+ self._indent()
377
+ node.iterable.accept(self)
378
+ self._dedent()
379
+ self._emit("body:")
380
+ self._visit_body(node.body)
381
+ if getattr(node, "else_body", None):
382
+ self._emit("else:")
383
+ self._visit_body(node.else_body)
384
+ self._dedent()
385
+
386
+ def visit_FunctionDef(self, node):
387
+ for dec in getattr(node, 'decorators', []):
388
+ self._emit("Decorator")
389
+ self._indent()
390
+ dec.accept(self)
391
+ self._dedent()
392
+ self._emit(
393
+ f"FunctionDef name={node.name!r} async={getattr(node, 'is_async', False)}"
394
+ )
395
+ self._indent()
396
+ self._emit(f"params: {node.params!r}")
397
+ if getattr(node, "return_annotation", None):
398
+ self._emit("returns:")
399
+ self._indent()
400
+ node.return_annotation.accept(self)
401
+ self._dedent()
402
+ self._emit("body:")
403
+ self._visit_body(node.body)
404
+ self._dedent()
405
+
406
+ def visit_ClassDef(self, node):
407
+ for dec in getattr(node, 'decorators', []):
408
+ self._emit("Decorator")
409
+ self._indent()
410
+ dec.accept(self)
411
+ self._dedent()
412
+ self._emit(f"ClassDef name={node.name!r}")
413
+ self._indent()
414
+ if node.bases:
415
+ self._emit("bases:")
416
+ self._indent()
417
+ for base in node.bases:
418
+ base.accept(self)
419
+ self._dedent()
420
+ self._emit("body:")
421
+ self._visit_body(node.body)
422
+ self._dedent()
423
+
424
+ def visit_TryStatement(self, node):
425
+ self._emit("TryStatement")
426
+ self._indent()
427
+ self._emit("body:")
428
+ self._visit_body(node.body)
429
+ for handler in node.handlers:
430
+ handler.accept(self)
431
+ if node.else_body:
432
+ self._emit("else:")
433
+ self._visit_body(node.else_body)
434
+ if node.finally_body:
435
+ self._emit("finally:")
436
+ self._visit_body(node.finally_body)
437
+ self._dedent()
438
+
439
+ def visit_ExceptHandler(self, node):
440
+ parts = ["ExceptHandler"]
441
+ if node.exc_type:
442
+ parts.append(f"type={node.exc_type.name!r}")
443
+ if node.name:
444
+ parts.append(f"as={node.name!r}")
445
+ self._emit(" ".join(parts))
446
+ self._indent()
447
+ for stmt in node.body:
448
+ stmt.accept(self)
449
+ self._dedent()
450
+
451
+ def visit_MatchStatement(self, node):
452
+ self._emit("MatchStatement")
453
+ self._indent()
454
+ self._emit("subject:")
455
+ self._indent()
456
+ node.subject.accept(self)
457
+ self._dedent()
458
+ for case in node.cases:
459
+ case.accept(self)
460
+ self._dedent()
461
+
462
+ def visit_CaseClause(self, node):
463
+ if node.is_default:
464
+ self._emit("DefaultClause")
465
+ else:
466
+ self._emit("CaseClause")
467
+ self._indent()
468
+ self._emit("pattern:")
469
+ self._indent()
470
+ node.pattern.accept(self)
471
+ self._dedent()
472
+ self._dedent()
473
+ self._indent()
474
+ for stmt in node.body:
475
+ stmt.accept(self)
476
+ self._dedent()
477
+
478
+ def visit_WithStatement(self, node):
479
+ self._emit(f"WithStatement async={getattr(node, 'is_async', False)}")
480
+ self._indent()
481
+ self._emit("items:")
482
+ self._indent()
483
+ for context_expr, name in node.items:
484
+ self._emit(f"item as={name!r}")
485
+ self._indent()
486
+ context_expr.accept(self)
487
+ self._dedent()
488
+ self._dedent()
489
+ self._emit("body:")
490
+ self._visit_body(node.body)
491
+ self._dedent()
492
+
493
+ def visit_ImportStatement(self, node):
494
+ parts = [f"ImportStatement module={node.module!r}"]
495
+ if node.alias:
496
+ parts.append(f"as={node.alias!r}")
497
+ self._emit(" ".join(parts))
498
+
499
+ def visit_FromImportStatement(self, node):
500
+ self._emit(f"FromImportStatement module={node.module!r}")
501
+ self._indent()
502
+ for name, alias in node.names:
503
+ if alias:
504
+ self._emit(f"{name} as {alias}")
505
+ else:
506
+ self._emit(name)
507
+ self._dedent()
508
+
509
+ def visit_SliceExpr(self, node):
510
+ self._emit("SliceExpr")
511
+ self._indent()
512
+ if node.start:
513
+ self._emit("start:")
514
+ self._indent()
515
+ node.start.accept(self)
516
+ self._dedent()
517
+ if node.stop:
518
+ self._emit("stop:")
519
+ self._indent()
520
+ node.stop.accept(self)
521
+ self._dedent()
522
+ if node.step:
523
+ self._emit("step:")
524
+ self._indent()
525
+ node.step.accept(self)
526
+ self._dedent()
527
+ self._dedent()
528
+
529
+ def visit_Parameter(self, node):
530
+ parts = [f"Parameter {node.name!r}"]
531
+ if node.is_vararg:
532
+ parts.append("*")
533
+ if node.is_kwarg:
534
+ parts.append("**")
535
+ self._emit(" ".join(parts))
536
+ if node.annotation or node.default:
537
+ self._indent()
538
+ if node.annotation:
539
+ self._emit("annotation:")
540
+ self._indent()
541
+ node.annotation.accept(self)
542
+ self._dedent()
543
+ self._emit("default:")
544
+ if node.default:
545
+ self._indent()
546
+ node.default.accept(self)
547
+ self._dedent()
548
+ else:
549
+ self._emit("None")
550
+ self._dedent()
551
+
552
+ def visit_StarredExpr(self, node):
553
+ prefix = "**" if node.is_double else "*"
554
+ self._emit(f"StarredExpr {prefix}")
555
+ self._indent()
556
+ node.value.accept(self)
557
+ self._dedent()
558
+
559
+ def visit_TupleLiteral(self, node):
560
+ self._emit("TupleLiteral")
561
+ self._indent()
562
+ for elem in node.elements:
563
+ elem.accept(self)
564
+ self._dedent()
565
+
566
+ def visit_ListComprehension(self, node):
567
+ self._emit("ListComprehension")
568
+ self._indent()
569
+ self._emit("element:")
570
+ self._indent()
571
+ node.element.accept(self)
572
+ self._dedent()
573
+ self._emit("target:")
574
+ self._indent()
575
+ node.target.accept(self)
576
+ self._dedent()
577
+ self._emit("iterable:")
578
+ self._indent()
579
+ node.iterable.accept(self)
580
+ self._dedent()
581
+ if node.conditions:
582
+ self._emit("conditions:")
583
+ self._indent()
584
+ for cond in node.conditions:
585
+ cond.accept(self)
586
+ self._dedent()
587
+ self._dedent()
588
+
589
+ def visit_DictComprehension(self, node):
590
+ self._emit("DictComprehension")
591
+ self._indent()
592
+ self._emit("key:")
593
+ self._indent()
594
+ node.key.accept(self)
595
+ self._dedent()
596
+ self._emit("value:")
597
+ self._indent()
598
+ node.value.accept(self)
599
+ self._dedent()
600
+ self._emit("target:")
601
+ self._indent()
602
+ node.target.accept(self)
603
+ self._dedent()
604
+ self._emit("iterable:")
605
+ self._indent()
606
+ node.iterable.accept(self)
607
+ self._dedent()
608
+ if node.conditions:
609
+ self._emit("conditions:")
610
+ self._indent()
611
+ for cond in node.conditions:
612
+ cond.accept(self)
613
+ self._dedent()
614
+ self._dedent()
615
+
616
+ def visit_GeneratorExpr(self, node):
617
+ self._emit("GeneratorExpr")
618
+ self._indent()
619
+ self._emit("element:")
620
+ self._indent()
621
+ node.element.accept(self)
622
+ self._dedent()
623
+ self._emit("target:")
624
+ self._indent()
625
+ node.target.accept(self)
626
+ self._dedent()
627
+ self._emit("iterable:")
628
+ self._indent()
629
+ node.iterable.accept(self)
630
+ self._dedent()
631
+ if node.conditions:
632
+ self._emit("conditions:")
633
+ self._indent()
634
+ for cond in node.conditions:
635
+ cond.accept(self)
636
+ self._dedent()
637
+ self._dedent()
638
+
639
+ def visit_SetComprehension(self, node):
640
+ self._emit("SetComprehension")
641
+ self._indent()
642
+ self._emit("element:")
643
+ self._indent()
644
+ node.element.accept(self)
645
+ self._dedent()
646
+ self._emit("target:")
647
+ self._indent()
648
+ node.target.accept(self)
649
+ self._dedent()
650
+ self._emit("iterable:")
651
+ self._indent()
652
+ node.iterable.accept(self)
653
+ self._dedent()
654
+ if node.conditions:
655
+ self._emit("conditions:")
656
+ self._indent()
657
+ for cond in node.conditions:
658
+ cond.accept(self)
659
+ self._dedent()
660
+ self._dedent()
661
+
662
+ def visit_FStringLiteral(self, node):
663
+ self._emit("FStringLiteral")
664
+ self._indent()
665
+ for part in node.parts:
666
+ if isinstance(part, str):
667
+ self._emit(f"text: {part!r}")
668
+ else:
669
+ self._emit("expr:")
670
+ self._indent()
671
+ part.accept(self)
672
+ self._dedent()
673
+ self._dedent()
674
+
675
+ def generic_visit(self, node):
676
+ """Fallback rendering for nodes without a specialized visitor."""
677
+ self._emit(f"{type(node).__name__}")