jaclang 0.8.8__py3-none-any.whl → 0.8.10__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.

Potentially problematic release.


This version of jaclang might be problematic. Click here for more details.

Files changed (114) hide show
  1. jaclang/cli/cli.py +194 -10
  2. jaclang/cli/cmdreg.py +144 -8
  3. jaclang/compiler/__init__.py +6 -1
  4. jaclang/compiler/codeinfo.py +16 -1
  5. jaclang/compiler/constant.py +33 -8
  6. jaclang/compiler/jac.lark +154 -62
  7. jaclang/compiler/larkparse/jac_parser.py +2 -2
  8. jaclang/compiler/parser.py +656 -149
  9. jaclang/compiler/passes/__init__.py +2 -1
  10. jaclang/compiler/passes/ast_gen/__init__.py +5 -0
  11. jaclang/compiler/passes/ast_gen/base_ast_gen_pass.py +54 -0
  12. jaclang/compiler/passes/ast_gen/jsx_processor.py +344 -0
  13. jaclang/compiler/passes/ecmascript/__init__.py +25 -0
  14. jaclang/compiler/passes/ecmascript/es_unparse.py +576 -0
  15. jaclang/compiler/passes/ecmascript/esast_gen_pass.py +2068 -0
  16. jaclang/compiler/passes/ecmascript/estree.py +972 -0
  17. jaclang/compiler/passes/ecmascript/tests/__init__.py +1 -0
  18. jaclang/compiler/passes/ecmascript/tests/fixtures/advanced_language_features.jac +170 -0
  19. jaclang/compiler/passes/ecmascript/tests/fixtures/class_separate_impl.impl.jac +30 -0
  20. jaclang/compiler/passes/ecmascript/tests/fixtures/class_separate_impl.jac +14 -0
  21. jaclang/compiler/passes/ecmascript/tests/fixtures/client_jsx.jac +89 -0
  22. jaclang/compiler/passes/ecmascript/tests/fixtures/core_language_features.jac +195 -0
  23. jaclang/compiler/passes/ecmascript/tests/test_esast_gen_pass.py +167 -0
  24. jaclang/compiler/passes/ecmascript/tests/test_js_generation.py +239 -0
  25. jaclang/compiler/passes/main/__init__.py +0 -3
  26. jaclang/compiler/passes/main/annex_pass.py +23 -1
  27. jaclang/compiler/passes/main/def_use_pass.py +1 -0
  28. jaclang/compiler/passes/main/pyast_gen_pass.py +413 -255
  29. jaclang/compiler/passes/main/pyast_load_pass.py +48 -11
  30. jaclang/compiler/passes/main/pyjac_ast_link_pass.py +2 -0
  31. jaclang/compiler/passes/main/sym_tab_build_pass.py +18 -1
  32. jaclang/compiler/passes/main/tests/fixtures/autoimpl.cl.jac +7 -0
  33. jaclang/compiler/passes/main/tests/fixtures/checker_arity.jac +3 -0
  34. jaclang/compiler/passes/main/tests/fixtures/checker_class_construct.jac +33 -0
  35. jaclang/compiler/passes/main/tests/fixtures/defuse_modpath.jac +7 -0
  36. jaclang/compiler/passes/main/tests/fixtures/member_access_type_resolve.jac +2 -1
  37. jaclang/compiler/passes/main/tests/test_checker_pass.py +31 -3
  38. jaclang/compiler/passes/main/tests/test_def_use_pass.py +12 -0
  39. jaclang/compiler/passes/main/tests/test_import_pass.py +23 -4
  40. jaclang/compiler/passes/main/tests/test_predynamo_pass.py +13 -14
  41. jaclang/compiler/passes/main/tests/test_pyast_gen_pass.py +25 -0
  42. jaclang/compiler/passes/main/type_checker_pass.py +7 -0
  43. jaclang/compiler/passes/tool/doc_ir_gen_pass.py +219 -20
  44. jaclang/compiler/passes/tool/fuse_comments_pass.py +1 -10
  45. jaclang/compiler/passes/tool/jac_formatter_pass.py +2 -2
  46. jaclang/compiler/passes/tool/tests/fixtures/import_fmt.jac +7 -1
  47. jaclang/compiler/passes/tool/tests/fixtures/tagbreak.jac +135 -29
  48. jaclang/compiler/passes/tool/tests/test_jac_format_pass.py +4 -1
  49. jaclang/compiler/passes/transform.py +9 -1
  50. jaclang/compiler/passes/uni_pass.py +5 -7
  51. jaclang/compiler/program.py +27 -26
  52. jaclang/compiler/tests/test_client_codegen.py +113 -0
  53. jaclang/compiler/tests/test_importer.py +12 -10
  54. jaclang/compiler/tests/test_parser.py +249 -3
  55. jaclang/compiler/type_system/type_evaluator.jac +1078 -0
  56. jaclang/compiler/type_system/type_utils.py +1 -1
  57. jaclang/compiler/type_system/types.py +6 -0
  58. jaclang/compiler/unitree.py +438 -82
  59. jaclang/langserve/engine.jac +224 -288
  60. jaclang/langserve/sem_manager.jac +12 -8
  61. jaclang/langserve/server.jac +48 -48
  62. jaclang/langserve/tests/fixtures/greet.py +17 -0
  63. jaclang/langserve/tests/fixtures/md_path.jac +22 -0
  64. jaclang/langserve/tests/fixtures/user.jac +15 -0
  65. jaclang/langserve/tests/test_server.py +66 -371
  66. jaclang/lib.py +17 -0
  67. jaclang/runtimelib/archetype.py +25 -25
  68. jaclang/runtimelib/client_bundle.py +169 -0
  69. jaclang/runtimelib/client_runtime.jac +586 -0
  70. jaclang/runtimelib/constructs.py +4 -2
  71. jaclang/runtimelib/machine.py +308 -139
  72. jaclang/runtimelib/meta_importer.py +111 -22
  73. jaclang/runtimelib/mtp.py +15 -0
  74. jaclang/runtimelib/server.py +1089 -0
  75. jaclang/runtimelib/tests/fixtures/client_app.jac +18 -0
  76. jaclang/runtimelib/tests/fixtures/custom_access_validation.jac +1 -1
  77. jaclang/runtimelib/tests/fixtures/savable_object.jac +4 -5
  78. jaclang/runtimelib/tests/fixtures/serve_api.jac +75 -0
  79. jaclang/runtimelib/tests/test_client_bundle.py +55 -0
  80. jaclang/runtimelib/tests/test_client_render.py +63 -0
  81. jaclang/runtimelib/tests/test_serve.py +1069 -0
  82. jaclang/settings.py +0 -3
  83. jaclang/tests/fixtures/attr_pattern_case.jac +18 -0
  84. jaclang/tests/fixtures/funccall_genexpr.jac +7 -0
  85. jaclang/tests/fixtures/funccall_genexpr.py +5 -0
  86. jaclang/tests/fixtures/iife_functions.jac +142 -0
  87. jaclang/tests/fixtures/iife_functions_client.jac +143 -0
  88. jaclang/tests/fixtures/multistatement_lambda.jac +116 -0
  89. jaclang/tests/fixtures/multistatement_lambda_client.jac +113 -0
  90. jaclang/tests/fixtures/needs_import_dup.jac +6 -4
  91. jaclang/tests/fixtures/py2jac_empty.py +0 -0
  92. jaclang/tests/fixtures/py_run.py +7 -5
  93. jaclang/tests/fixtures/pyfunc_fstr.py +2 -2
  94. jaclang/tests/fixtures/simple_lambda_test.jac +12 -0
  95. jaclang/tests/test_cli.py +134 -18
  96. jaclang/tests/test_language.py +120 -32
  97. jaclang/tests/test_reference.py +20 -3
  98. jaclang/utils/NonGPT.py +375 -0
  99. jaclang/utils/helpers.py +64 -20
  100. jaclang/utils/lang_tools.py +31 -4
  101. jaclang/utils/tests/test_lang_tools.py +5 -16
  102. jaclang/utils/treeprinter.py +8 -3
  103. {jaclang-0.8.8.dist-info → jaclang-0.8.10.dist-info}/METADATA +3 -3
  104. {jaclang-0.8.8.dist-info → jaclang-0.8.10.dist-info}/RECORD +106 -71
  105. jaclang/compiler/passes/main/binder_pass.py +0 -594
  106. jaclang/compiler/passes/main/tests/fixtures/sym_binder.jac +0 -47
  107. jaclang/compiler/passes/main/tests/test_binder_pass.py +0 -111
  108. jaclang/compiler/type_system/type_evaluator.py +0 -844
  109. jaclang/langserve/tests/session.jac +0 -294
  110. jaclang/langserve/tests/test_dev_server.py +0 -80
  111. jaclang/runtimelib/importer.py +0 -351
  112. jaclang/tests/test_typecheck.py +0 -542
  113. {jaclang-0.8.8.dist-info → jaclang-0.8.10.dist-info}/WHEEL +0 -0
  114. {jaclang-0.8.8.dist-info → jaclang-0.8.10.dist-info}/entry_points.txt +0 -0
@@ -26,8 +26,8 @@ from typing import List, Optional, Sequence, TypeVar, Union, cast
26
26
 
27
27
  import jaclang.compiler.unitree as uni
28
28
  from jaclang.compiler.constant import Constants as Con, EdgeDir, Tokens as Tok
29
- from jaclang.compiler.passes import UniPass
30
- from jaclang.settings import settings
29
+ from jaclang.compiler.passes.ast_gen import BaseAstGenPass
30
+ from jaclang.compiler.passes.ast_gen.jsx_processor import PyJsxProcessor
31
31
 
32
32
  T = TypeVar("T", bound=ast3.AST)
33
33
 
@@ -64,7 +64,6 @@ TOKEN_AST_MAP: dict[Tok, type[ast3.AST]] = {
64
64
  Tok.MINUS: ast3.Sub,
65
65
  Tok.SUB_EQ: ast3.Sub,
66
66
  Tok.BW_NOT: ast3.Invert,
67
- Tok.BW_NOT_EQ: ast3.Invert,
68
67
  Tok.NOT: ast3.Not,
69
68
  Tok.EQ: ast3.NotEq,
70
69
  Tok.EE: ast3.Eq,
@@ -89,14 +88,17 @@ UNARY_OP_MAP: dict[Tok, type[ast3.unaryop]] = {
89
88
  }
90
89
 
91
90
 
92
- class PyastGenPass(UniPass):
91
+ class PyastGenPass(BaseAstGenPass[ast3.AST]):
93
92
  """Jac blue transpilation to python pass."""
94
93
 
95
94
  def before_pass(self) -> None:
96
- for i in self.ir_in.impl_mod + self.ir_in.test_mod:
97
- PyastGenPass(ir_in=i, prog=self.prog)
95
+ self.child_passes: list[PyastGenPass] = self._init_child_passes(PyastGenPass)
98
96
  self.debuginfo: dict[str, list[str]] = {"jac_mods": []}
99
97
  self.already_added: list[str] = []
98
+ self.jaclib_imports: set[str] = set() # Track individual jaclib imports
99
+ self.builtin_imports: set[str] = set() # Track individual builtin imports
100
+ self._temp_name_counter: int = 0
101
+ self._hoisted_funcs: list[ast3.FunctionDef | ast3.AsyncFunctionDef] = []
100
102
  self.preamble: list[ast3.AST] = [
101
103
  self.sync(
102
104
  ast3.ImportFrom(
@@ -106,44 +108,17 @@ class PyastGenPass(UniPass):
106
108
  ),
107
109
  jac_node=self.ir_out,
108
110
  ),
109
- (
110
- self.sync(
111
- ast3.ImportFrom(
112
- module="jaclang.runtimelib.builtin",
113
- names=[
114
- self.sync(
115
- ast3.alias(
116
- name="*",
117
- asname=None,
118
- )
119
- )
120
- ],
121
- level=0,
122
- ),
123
- jac_node=self.ir_out,
124
- )
125
- ),
126
- (
127
- self.sync(
128
- ast3.ImportFrom(
129
- module="jaclang",
130
- names=[
131
- self.sync(
132
- ast3.alias(
133
- name="JacMachineInterface",
134
- asname=settings.pyout_jaclib_alias,
135
- )
136
- ),
137
- ],
138
- level=0,
139
- ),
140
- jac_node=self.ir_out,
141
- )
142
- ),
143
111
  ]
112
+ self.jsx_processor = PyJsxProcessor(self)
144
113
 
145
114
  def enter_node(self, node: uni.UniNode) -> None:
146
115
  """Enter node."""
116
+ if self._should_skip_client(node):
117
+ node.gen.py_ast = []
118
+ if hasattr(node.gen, "py"):
119
+ node.gen.py = "" # type: ignore[attr-defined]
120
+ self.prune()
121
+ return
147
122
  if node.gen.py_ast:
148
123
  self.prune()
149
124
  return
@@ -151,25 +126,28 @@ class PyastGenPass(UniPass):
151
126
 
152
127
  def exit_node(self, node: uni.UniNode) -> None:
153
128
  """Exit node."""
129
+ if self._should_skip_client(node):
130
+ node.gen.py_ast = []
131
+ if hasattr(node.gen, "py"):
132
+ node.gen.py = "" # type: ignore[attr-defined]
133
+ return
154
134
  super().exit_node(node)
155
- # for i in node.gen.py_ast: # Internal validation
156
- # self.node_compilable_test(i)
157
-
158
- # TODO: USE THIS TO SYNC
159
- # if isinstance(i, ast3.AST):
160
- # i.jac_link = node
161
135
 
162
- def jaclib_obj(self, obj_name: str) -> ast3.Name | ast3.Attribute:
136
+ def jaclib_obj(self, obj_name: str) -> ast3.Name:
163
137
  """Return the object from jaclib as ast node based on the import config."""
164
- return self.sync(
165
- ast3.Attribute(
166
- value=self.sync(
167
- ast3.Name(id=settings.pyout_jaclib_alias, ctx=ast3.Load())
168
- ),
169
- attr=obj_name,
170
- ctx=ast3.Load(),
171
- )
172
- )
138
+ self.jaclib_imports.add(obj_name)
139
+ return self.sync(ast3.Name(id=obj_name, ctx=ast3.Load()))
140
+
141
+ def builtin_name(self, name: str) -> ast3.Name:
142
+ """Return a builtin name and track it for importing.
143
+
144
+ Note: Some names like 'Enum' are provided by other imports (e.g., needs_enum)
145
+ and should not be added to builtin_imports.
146
+ """
147
+ # Enum is imported via needs_enum, not from builtins
148
+ if name not in ["Enum"]:
149
+ self.builtin_imports.add(name)
150
+ return self.sync(ast3.Name(id=name, ctx=ast3.Load()))
173
151
 
174
152
  def _add_preamble_once(self, key: str, node: ast3.AST) -> None:
175
153
  """Append an import statement to the preamble once."""
@@ -187,15 +165,6 @@ class PyastGenPass(UniPass):
187
165
  ),
188
166
  )
189
167
 
190
- def needs_mtllm(self) -> None:
191
- """Ensure byLLM is imported only once."""
192
- self._add_preamble_once(
193
- self.needs_mtllm.__name__,
194
- ast3.Import(
195
- names=[self.sync(ast3.alias(name="byllm"), jac_node=self.ir_out)]
196
- ),
197
- )
198
-
199
168
  def needs_enum(self) -> None:
200
169
  """Ensure Enum utilities are imported only once."""
201
170
  self._add_preamble_once(
@@ -221,6 +190,33 @@ class PyastGenPass(UniPass):
221
190
  ),
222
191
  )
223
192
 
193
+ def _next_temp_name(self, prefix: str) -> str:
194
+ """Generate a deterministic temporary name for synthesized definitions."""
195
+ self._temp_name_counter += 1
196
+ return f"__jac_{prefix}_{self._temp_name_counter}"
197
+
198
+ def _function_expr_from_def(
199
+ self,
200
+ func_def: ast3.FunctionDef | ast3.AsyncFunctionDef,
201
+ jac_node: uni.UniNode,
202
+ filename_hint: str,
203
+ ) -> ast3.Name:
204
+ """Convert a synthesized function definition into an executable expression.
205
+
206
+ Instead of using make_block_lambda at runtime, we hoist the function
207
+ definition to be emitted before the current statement, then return
208
+ a reference to the function name.
209
+ """
210
+ # Ensure locations are fixed
211
+ ast3.fix_missing_locations(func_def)
212
+ # Add the function to the hoisted list - it will be emitted before the current statement
213
+ self._hoisted_funcs.append(func_def)
214
+ # Return a reference to the function name
215
+ return self.sync(
216
+ ast3.Name(id=func_def.name, ctx=ast3.Load()),
217
+ jac_node=jac_node,
218
+ )
219
+
224
220
  def _get_sem_decorator(self, node: uni.UniNode) -> ast3.Call | None:
225
221
  """Create a semstring decorator for the given semantic strings.
226
222
 
@@ -283,7 +279,10 @@ class PyastGenPass(UniPass):
283
279
  else {}
284
280
  )
285
281
 
286
- if not semstr and not inner_semstr:
282
+ # Only add sem decorator if there's actual semantic content
283
+ if not semstr and (
284
+ not inner_semstr or all(not v for v in inner_semstr.values())
285
+ ):
287
286
  return None
288
287
 
289
288
  return self.sync(
@@ -308,15 +307,9 @@ class PyastGenPass(UniPass):
308
307
  )
309
308
  )
310
309
 
311
- def flatten(self, body: list[T | list[T] | None]) -> list[T]:
312
- """Flatten a list of items or lists into a single list."""
313
- new_body: list[T] = []
314
- for item in body:
315
- if isinstance(item, list):
316
- new_body.extend(item)
317
- elif item is not None:
318
- new_body.append(item)
319
- return new_body
310
+ def _should_skip_client(self, node: uni.UniNode) -> bool:
311
+ """Check if node is a client-facing declaration that should skip Python codegen."""
312
+ return isinstance(node, uni.ClientFacingNode) and node.is_client_decl
320
313
 
321
314
  def sync(
322
315
  self, py_node: T, jac_node: Optional[uni.UniNode] = None, deep: bool = False
@@ -373,7 +366,7 @@ class PyastGenPass(UniPass):
373
366
  [self.sync(ast3.Pass())]
374
367
  if isinstance(node, Sequence) and not valid_stmts
375
368
  else (
376
- self.flatten(
369
+ self._flatten_ast_list(
377
370
  [
378
371
  x.gen.py_ast
379
372
  for x in valid_stmts
@@ -417,31 +410,55 @@ class PyastGenPass(UniPass):
417
410
  node.gen.py_ast = node.tag.gen.py_ast
418
411
 
419
412
  def exit_module(self, node: uni.Module) -> None:
420
- clean_body = [i for i in node.body if not isinstance(i, uni.ImplDef)]
421
- pre_body: list[uni.UniNode] = []
422
- for pbody in node.impl_mod:
423
- pre_body = [*pre_body, *pbody.body]
424
- pre_body = [*pre_body, *clean_body]
425
- for pbody in node.test_mod:
426
- pre_body = [*pre_body, *pbody.body]
427
- body = (
428
- [
413
+ # Merge jaclib and builtin imports from child passes
414
+ for child_pass in self.child_passes:
415
+ self.jaclib_imports.update(child_pass.jaclib_imports)
416
+ self.builtin_imports.update(child_pass.builtin_imports)
417
+
418
+ # Add builtin imports if any were used
419
+ if self.builtin_imports:
420
+ self.preamble.append(
429
421
  self.sync(
430
- ast3.Expr(value=cast(ast3.expr, node.doc.gen.py_ast[0])),
431
- jac_node=node.doc,
432
- ),
433
- *self.preamble,
434
- *[x.gen.py_ast for x in pre_body],
435
- ]
436
- if node.doc
437
- else [*self.preamble, *[x.gen.py_ast for x in pre_body]]
438
- )
439
- new_body = []
440
- for i in body:
441
- if isinstance(i, list):
442
- new_body += i
443
- else:
444
- new_body.append(i) if i else None
422
+ ast3.ImportFrom(
423
+ module="jaclang.runtimelib.builtin",
424
+ names=[
425
+ self.sync(ast3.alias(name=name, asname=None))
426
+ for name in sorted(self.builtin_imports)
427
+ ],
428
+ level=0,
429
+ ),
430
+ jac_node=self.ir_out,
431
+ )
432
+ )
433
+
434
+ # Add library imports at the end of preamble
435
+ if self.jaclib_imports:
436
+ self.preamble.append(
437
+ self.sync(
438
+ ast3.ImportFrom(
439
+ module="jaclang.lib",
440
+ names=[
441
+ self.sync(ast3.alias(name=name, asname=None))
442
+ for name in sorted(self.jaclib_imports)
443
+ ],
444
+ level=0,
445
+ ),
446
+ jac_node=self.ir_out,
447
+ )
448
+ )
449
+
450
+ merged_body = self._merge_module_bodies(node)
451
+ body_items: list[ast3.AST | list[ast3.AST] | None] = [*self.preamble]
452
+ body_items.extend(item.gen.py_ast for item in merged_body)
453
+
454
+ if node.doc:
455
+ doc_stmt = self.sync(
456
+ ast3.Expr(value=cast(ast3.expr, node.doc.gen.py_ast[0])),
457
+ jac_node=node.doc,
458
+ )
459
+ body_items.insert(0, doc_stmt)
460
+
461
+ new_body = self._flatten_ast_list(body_items)
445
462
  node.gen.py_ast = [
446
463
  self.sync(
447
464
  ast3.Module(
@@ -458,7 +475,7 @@ class PyastGenPass(UniPass):
458
475
  ast3.Expr(value=cast(ast3.expr, node.doc.gen.py_ast[0])),
459
476
  jac_node=node.doc,
460
477
  )
461
- assigns_ast: list[ast3.AST] = self.flatten(
478
+ assigns_ast: list[ast3.AST] = self._flatten_ast_list(
462
479
  [a.gen.py_ast for a in node.assignments]
463
480
  )
464
481
  if isinstance(doc, ast3.AST):
@@ -466,7 +483,9 @@ class PyastGenPass(UniPass):
466
483
  else:
467
484
  raise self.ice()
468
485
  else:
469
- node.gen.py_ast = self.flatten([a.gen.py_ast for a in node.assignments])
486
+ node.gen.py_ast = self._flatten_ast_list(
487
+ [a.gen.py_ast for a in node.assignments]
488
+ )
470
489
 
471
490
  def exit_test(self, node: uni.Test) -> None:
472
491
  test_name = node.name.sym_name
@@ -687,18 +706,17 @@ class PyastGenPass(UniPass):
687
706
  if node.arch_type.name != Tok.KW_CLASS:
688
707
  base_classes.append(self.jaclib_obj(node.arch_type.value.capitalize()))
689
708
 
690
- node.gen.py_ast = [
691
- self.sync(
692
- ast3.ClassDef(
693
- name=node.name.sym_name,
694
- bases=[cast(ast3.expr, i) for i in base_classes],
695
- keywords=[],
696
- body=[cast(ast3.stmt, i) for i in body],
697
- decorator_list=[cast(ast3.expr, i) for i in decorators],
698
- type_params=[],
699
- )
709
+ class_def = self.sync(
710
+ ast3.ClassDef(
711
+ name=node.name.sym_name,
712
+ bases=[cast(ast3.expr, i) for i in base_classes],
713
+ keywords=[],
714
+ body=[cast(ast3.stmt, i) for i in body],
715
+ decorator_list=[cast(ast3.expr, i) for i in decorators],
716
+ type_params=[],
700
717
  )
701
- ]
718
+ )
719
+ node.gen.py_ast = [class_def]
702
720
 
703
721
  def enter_enum(self, node: uni.Enum) -> None:
704
722
  if isinstance(node.body, uni.ImplDef):
@@ -722,19 +740,18 @@ class PyastGenPass(UniPass):
722
740
  decorators.append(sem_decorator)
723
741
 
724
742
  base_classes = [cast(ast3.expr, i.gen.py_ast[0]) for i in node.base_classes]
725
- base_classes.append(self.sync(ast3.Name(id="Enum", ctx=ast3.Load())))
726
- node.gen.py_ast = [
727
- self.sync(
728
- ast3.ClassDef(
729
- name=node.name.sym_name,
730
- bases=[cast(ast3.expr, i) for i in base_classes],
731
- keywords=[],
732
- body=[cast(ast3.stmt, i) for i in body],
733
- decorator_list=[cast(ast3.expr, i) for i in decorators],
734
- type_params=[],
735
- )
743
+ base_classes.append(self.builtin_name("Enum"))
744
+ class_def = self.sync(
745
+ ast3.ClassDef(
746
+ name=node.name.sym_name,
747
+ bases=[cast(ast3.expr, i) for i in base_classes],
748
+ keywords=[],
749
+ body=[cast(ast3.stmt, i) for i in body],
750
+ decorator_list=[cast(ast3.expr, i) for i in decorators],
751
+ type_params=[],
736
752
  )
737
- ]
753
+ )
754
+ node.gen.py_ast = [class_def]
738
755
 
739
756
  def enter_ability(self, node: uni.Ability) -> None:
740
757
  if isinstance(node.body, uni.ImplDef):
@@ -744,23 +761,9 @@ class PyastGenPass(UniPass):
744
761
  self, model: ast3.expr, caller: ast3.expr, args: ast3.Dict
745
762
  ) -> ast3.Call:
746
763
  """Reusable method to codegen call_llm(model, caller, args)."""
747
- self.needs_mtllm()
748
- mtir_cls_ast = self.sync(
749
- ast3.Attribute(
750
- value=self.sync(ast3.Name(id="byllm", ctx=ast3.Load())),
751
- attr="MTIR",
752
- ctx=ast3.Load(),
753
- )
754
- )
755
764
  mtir_ast = self.sync(
756
765
  ast3.Call(
757
- func=self.sync(
758
- ast3.Attribute(
759
- value=mtir_cls_ast,
760
- attr="factory",
761
- ctx=ast3.Load(),
762
- )
763
- ),
766
+ func=self.jaclib_obj("get_mtir"),
764
767
  args=[],
765
768
  keywords=[
766
769
  self.sync(
@@ -928,7 +931,9 @@ class PyastGenPass(UniPass):
928
931
  if isinstance(node.signature, uni.EventSignature):
929
932
  decorator_list.append(
930
933
  self.jaclib_obj(
931
- "entry" if node.signature.event.name == Tok.KW_ENTRY else "exit"
934
+ "on_entry"
935
+ if node.signature.event.name == Tok.KW_ENTRY
936
+ else "on_exit"
932
937
  )
933
938
  )
934
939
 
@@ -943,11 +948,9 @@ class PyastGenPass(UniPass):
943
948
  )
944
949
  )
945
950
  if node.is_abstract:
946
- decorator_list.append(
947
- self.sync(ast3.Name(id="abstractmethod", ctx=ast3.Load()))
948
- )
951
+ decorator_list.append(self.builtin_name("abstractmethod"))
949
952
  if node.is_override:
950
- decorator_list.append(self.sync(ast3.Name(id="override", ctx=ast3.Load())))
953
+ decorator_list.append(self.builtin_name("override"))
951
954
  if node.is_static:
952
955
  decorator_list.insert(
953
956
  0, self.sync(ast3.Name(id="staticmethod", ctx=ast3.Load()))
@@ -962,36 +965,35 @@ class PyastGenPass(UniPass):
962
965
  if isinstance(node.signature, uni.FuncSignature) and node.signature.return_type:
963
966
  ast_returns = cast(ast3.expr, node.signature.return_type.gen.py_ast[0])
964
967
 
965
- node.gen.py_ast = [
966
- self.sync(
967
- func_type(
968
- name=node.name_ref.sym_name,
969
- args=(
970
- cast(ast3.arguments, node.signature.gen.py_ast[0])
971
- if node.signature
972
- else self.sync(
973
- ast3.arguments(
974
- posonlyargs=[],
975
- args=(
976
- [self.sync(ast3.arg(arg="self", annotation=None))]
977
- if node.is_method
978
- else []
979
- ),
980
- vararg=None,
981
- kwonlyargs=[],
982
- kw_defaults=[],
983
- kwarg=None,
984
- defaults=[],
985
- )
968
+ func_def = self.sync(
969
+ func_type(
970
+ name=node.name_ref.sym_name,
971
+ args=(
972
+ cast(ast3.arguments, node.signature.gen.py_ast[0])
973
+ if node.signature
974
+ else self.sync(
975
+ ast3.arguments(
976
+ posonlyargs=[],
977
+ args=(
978
+ [self.sync(ast3.arg(arg="self", annotation=None))]
979
+ if node.is_method
980
+ else []
981
+ ),
982
+ vararg=None,
983
+ kwonlyargs=[],
984
+ kw_defaults=[],
985
+ kwarg=None,
986
+ defaults=[],
986
987
  )
987
- ),
988
- body=[cast(ast3.stmt, i) for i in body],
989
- decorator_list=[cast(ast3.expr, i) for i in decorator_list],
990
- returns=ast_returns,
991
- type_params=[],
992
- )
988
+ )
989
+ ),
990
+ body=[cast(ast3.stmt, i) for i in body],
991
+ decorator_list=[cast(ast3.expr, i) for i in decorator_list],
992
+ returns=ast_returns,
993
+ type_params=[],
993
994
  )
994
- ]
995
+ )
996
+ node.gen.py_ast = [func_def]
995
997
 
996
998
  def exit_impl_def(self, node: uni.ImplDef) -> None:
997
999
  pass
@@ -1111,7 +1113,9 @@ class PyastGenPass(UniPass):
1111
1113
  ]
1112
1114
 
1113
1115
  def exit_arch_has(self, node: uni.ArchHas) -> None:
1114
- vars_py: list[ast3.AST] = self.flatten([v.gen.py_ast for v in node.vars])
1116
+ vars_py: list[ast3.AST] = self._flatten_ast_list(
1117
+ [v.gen.py_ast for v in node.vars]
1118
+ )
1115
1119
  if node.doc:
1116
1120
  doc = self.sync(
1117
1121
  ast3.Expr(value=cast(ast3.expr, node.doc.gen.py_ast[0])),
@@ -1145,7 +1149,7 @@ class PyastGenPass(UniPass):
1145
1149
  elif is_static_var:
1146
1150
  annotation = self.sync(
1147
1151
  ast3.Subscript(
1148
- value=self.sync(ast3.Name(id="ClassVar", ctx=ast3.Load())),
1152
+ value=self.builtin_name("ClassVar"),
1149
1153
  slice=cast(ast3.expr, annotation),
1150
1154
  ctx=ast3.Load(),
1151
1155
  )
@@ -1222,8 +1226,29 @@ class PyastGenPass(UniPass):
1222
1226
  ]
1223
1227
 
1224
1228
  def exit_typed_ctx_block(self, node: uni.TypedCtxBlock) -> None:
1225
- # TODO: Come back
1226
- pass
1229
+ loc = self.sync(
1230
+ ast3.Name(id=Con.HERE.value, ctx=ast3.Load())
1231
+ if node.from_walker
1232
+ else ast3.Name(id=Con.VISITOR.value, ctx=ast3.Load())
1233
+ )
1234
+ node.gen.py_ast = [
1235
+ self.sync(
1236
+ ast3.If(
1237
+ test=self.sync(
1238
+ ast3.Call(
1239
+ func=self.sync(ast3.Name(id="isinstance", ctx=ast3.Load())),
1240
+ args=[
1241
+ loc,
1242
+ cast(ast3.expr, node.type_ctx.gen.py_ast[0]),
1243
+ ],
1244
+ keywords=[],
1245
+ )
1246
+ ),
1247
+ body=cast(list[ast3.stmt], self.resolve_stmt_block(node.body)),
1248
+ orelse=[],
1249
+ )
1250
+ )
1251
+ ]
1227
1252
 
1228
1253
  def exit_if_stmt(self, node: uni.IfStmt) -> None:
1229
1254
  node.gen.py_ast = [
@@ -1259,19 +1284,24 @@ class PyastGenPass(UniPass):
1259
1284
  node.gen.py_ast = self.resolve_stmt_block(node.body)
1260
1285
 
1261
1286
  def exit_expr_stmt(self, node: uni.ExprStmt) -> None:
1262
- node.gen.py_ast = [
1263
- (
1264
- self.sync(ast3.Expr(value=cast(ast3.expr, node.expr.gen.py_ast[0])))
1265
- if not node.in_fstring
1266
- else self.sync(
1267
- ast3.FormattedValue(
1268
- value=cast(ast3.expr, node.expr.gen.py_ast[0]),
1269
- conversion=-1,
1270
- format_spec=None,
1271
- )
1287
+ # Collect any hoisted functions that were generated during expression evaluation
1288
+ hoisted = self._hoisted_funcs[:]
1289
+ self._hoisted_funcs.clear()
1290
+
1291
+ expr_stmt = (
1292
+ self.sync(ast3.Expr(value=cast(ast3.expr, node.expr.gen.py_ast[0])))
1293
+ if not node.in_fstring
1294
+ else self.sync(
1295
+ ast3.FormattedValue(
1296
+ value=cast(ast3.expr, node.expr.gen.py_ast[0]),
1297
+ conversion=-1,
1298
+ format_spec=None,
1272
1299
  )
1273
1300
  )
1274
- ]
1301
+ )
1302
+
1303
+ # Emit hoisted functions before the expression statement
1304
+ node.gen.py_ast = [*hoisted, expr_stmt]
1275
1305
 
1276
1306
  def exit_concurrent_expr(self, node: uni.ConcurrentExpr) -> None:
1277
1307
  func = ""
@@ -1708,7 +1738,7 @@ class PyastGenPass(UniPass):
1708
1738
  value=self.sync(
1709
1739
  self.sync(
1710
1740
  ast3.Call(
1711
- func=self.jaclib_obj("report"),
1741
+ func=self.jaclib_obj("log_report"),
1712
1742
  args=cast(list[ast3.expr], node.expr.gen.py_ast),
1713
1743
  keywords=[],
1714
1744
  )
@@ -1865,48 +1895,49 @@ class PyastGenPass(UniPass):
1865
1895
  )
1866
1896
  targets_ast = [cast(ast3.expr, t.gen.py_ast[0]) for t in node.target]
1867
1897
 
1898
+ # Collect any hoisted functions that were generated during expression evaluation
1899
+ hoisted = self._hoisted_funcs[:]
1900
+ self._hoisted_funcs.clear()
1901
+
1868
1902
  if node.type_tag:
1869
- node.gen.py_ast = [
1870
- self.sync(
1871
- ast3.AnnAssign(
1872
- target=cast(ast3.Name, targets_ast[0]),
1873
- annotation=cast(ast3.expr, node.type_tag.gen.py_ast[0]),
1874
- value=(
1875
- cast(ast3.expr, node.value.gen.py_ast[0])
1876
- if node.value
1877
- else None
1878
- ),
1879
- simple=int(isinstance(targets_ast[0], ast3.Name)),
1880
- )
1903
+ assignment_stmt: ast3.AnnAssign | ast3.Assign | ast3.AugAssign = self.sync(
1904
+ ast3.AnnAssign(
1905
+ target=cast(ast3.Name, targets_ast[0]),
1906
+ annotation=cast(ast3.expr, node.type_tag.gen.py_ast[0]),
1907
+ value=(
1908
+ cast(ast3.expr, node.value.gen.py_ast[0])
1909
+ if node.value
1910
+ else None
1911
+ ),
1912
+ simple=int(isinstance(targets_ast[0], ast3.Name)),
1881
1913
  )
1882
- ]
1914
+ )
1883
1915
  elif node.aug_op:
1884
- node.gen.py_ast = [
1885
- self.sync(
1886
- ast3.AugAssign(
1887
- target=cast(ast3.Name, targets_ast[0]),
1888
- op=cast(ast3.operator, node.aug_op.gen.py_ast[0]),
1889
- value=(
1890
- cast(ast3.expr, value)
1891
- if isinstance(value, ast3.expr)
1892
- else ast3.Constant(value=None)
1893
- ),
1894
- )
1916
+ assignment_stmt = self.sync(
1917
+ ast3.AugAssign(
1918
+ target=cast(ast3.Name, targets_ast[0]),
1919
+ op=cast(ast3.operator, node.aug_op.gen.py_ast[0]),
1920
+ value=(
1921
+ cast(ast3.expr, value)
1922
+ if isinstance(value, ast3.expr)
1923
+ else ast3.Constant(value=None)
1924
+ ),
1895
1925
  )
1896
- ]
1926
+ )
1897
1927
  else:
1898
- node.gen.py_ast = [
1899
- self.sync(
1900
- ast3.Assign(
1901
- targets=cast(list[ast3.expr], targets_ast),
1902
- value=(
1903
- cast(ast3.expr, value)
1904
- if isinstance(value, ast3.expr)
1905
- else ast3.Constant(value=None)
1906
- ),
1907
- )
1928
+ assignment_stmt = self.sync(
1929
+ ast3.Assign(
1930
+ targets=cast(list[ast3.expr], targets_ast),
1931
+ value=(
1932
+ cast(ast3.expr, value)
1933
+ if isinstance(value, ast3.expr)
1934
+ else ast3.Constant(value=None)
1935
+ ),
1908
1936
  )
1909
- ]
1937
+ )
1938
+
1939
+ # Emit hoisted functions before the assignment
1940
+ node.gen.py_ast = [*hoisted, assignment_stmt]
1910
1941
 
1911
1942
  def exit_binary_expr(self, node: uni.BinaryExpr) -> None:
1912
1943
  if isinstance(node.op, uni.ConnectOp):
@@ -1999,7 +2030,7 @@ class PyastGenPass(UniPass):
1999
2030
  keywords.append(
2000
2031
  self.sync(
2001
2032
  ast3.keyword(
2002
- arg="filter",
2033
+ arg="filter_on",
2003
2034
  value=cast(
2004
2035
  ast3.expr,
2005
2036
  node.op.edge_spec.filter_cond.gen.py_ast[0],
@@ -2134,28 +2165,80 @@ class PyastGenPass(UniPass):
2134
2165
  ]
2135
2166
 
2136
2167
  def exit_lambda_expr(self, node: uni.LambdaExpr) -> None:
2137
- # Python lambda expressions don't support type annotations
2168
+ # Multi-statement lambda emits a synthesized function definition that is
2169
+ # hoisted before the current statement.
2170
+ if isinstance(node.body, list):
2171
+ if node.signature and node.signature.gen.py_ast:
2172
+ arguments = cast(ast3.arguments, node.signature.gen.py_ast[0])
2173
+ else:
2174
+ arguments = self.sync(
2175
+ ast3.arguments(
2176
+ posonlyargs=[],
2177
+ args=[],
2178
+ kwonlyargs=[],
2179
+ kw_defaults=[],
2180
+ defaults=[],
2181
+ ),
2182
+ jac_node=node,
2183
+ )
2184
+ body_stmts = [
2185
+ cast(ast3.stmt, stmt)
2186
+ for stmt in self.resolve_stmt_block(node.body, doc=None)
2187
+ ]
2188
+ if not body_stmts:
2189
+ body_stmts = [self.sync(ast3.Pass(), jac_node=node)]
2190
+ func_name = self._next_temp_name("lambda")
2191
+ returns = (
2192
+ cast(ast3.expr, node.signature.return_type.gen.py_ast[0])
2193
+ if node.signature
2194
+ and node.signature.return_type
2195
+ and node.signature.return_type.gen.py_ast
2196
+ else None
2197
+ )
2198
+ func_def = self.sync(
2199
+ ast3.FunctionDef(
2200
+ name=func_name,
2201
+ args=arguments,
2202
+ body=body_stmts,
2203
+ decorator_list=[],
2204
+ returns=returns,
2205
+ type_params=[],
2206
+ ),
2207
+ jac_node=node,
2208
+ )
2209
+ node.gen.py_ast = [
2210
+ self._function_expr_from_def(
2211
+ func_def=func_def,
2212
+ jac_node=node,
2213
+ filename_hint=f"<jac_lambda:{func_name}>",
2214
+ )
2215
+ ]
2216
+ return
2217
+
2218
+ # Single-expression lambda maps directly to Python lambda; strip annotations.
2138
2219
  if node.signature:
2139
2220
  self._remove_lambda_param_annotations(node.signature)
2140
-
2221
+ if node.signature and node.signature.gen.py_ast:
2222
+ arguments = cast(ast3.arguments, node.signature.gen.py_ast[0])
2223
+ else:
2224
+ arguments = self.sync(
2225
+ ast3.arguments(
2226
+ posonlyargs=[],
2227
+ args=[],
2228
+ kwonlyargs=[],
2229
+ kw_defaults=[],
2230
+ defaults=[],
2231
+ ),
2232
+ jac_node=node,
2233
+ )
2234
+ body_expr = cast(ast3.expr, node.body.gen.py_ast[0])
2141
2235
  node.gen.py_ast = [
2142
2236
  self.sync(
2143
2237
  ast3.Lambda(
2144
- args=(
2145
- cast(ast3.arguments, node.signature.gen.py_ast[0])
2146
- if node.signature
2147
- else self.sync(
2148
- ast3.arguments(
2149
- posonlyargs=[],
2150
- args=[],
2151
- kwonlyargs=[],
2152
- kw_defaults=[],
2153
- defaults=[],
2154
- )
2155
- )
2156
- ),
2157
- body=cast(ast3.expr, node.body.gen.py_ast[0]),
2158
- )
2238
+ args=arguments,
2239
+ body=body_expr,
2240
+ ),
2241
+ jac_node=node,
2159
2242
  )
2160
2243
  ]
2161
2244
 
@@ -2206,7 +2289,7 @@ class PyastGenPass(UniPass):
2206
2289
  node.gen.py_ast = [
2207
2290
  self.sync(
2208
2291
  ast3.Call(
2209
- func=self.sync(ast3.Name(id="jobj", ctx=ast3.Load())),
2292
+ func=self.builtin_name("jobj"),
2210
2293
  args=[],
2211
2294
  keywords=[
2212
2295
  self.sync(
@@ -2239,9 +2322,8 @@ class PyastGenPass(UniPass):
2239
2322
  for i in str_seq:
2240
2323
  if isinstance(i, uni.String):
2241
2324
  pieces.append(i.lit_value)
2242
- elif isinstance(i, uni.FString):
2243
- pieces.extend(get_pieces(i.parts)) if i.parts else None
2244
- elif isinstance(i, uni.ExprStmt):
2325
+ elif isinstance(i, (uni.FString, uni.ExprStmt)):
2326
+ # pieces.extend(get_pieces(i.parts)) if i.parts else None
2245
2327
  pieces.append(i.gen.py_ast[0])
2246
2328
  elif isinstance(i, uni.Token) and i.name in [Tok.LBRACE, Tok.RBRACE]:
2247
2329
  continue
@@ -2281,11 +2363,32 @@ class PyastGenPass(UniPass):
2281
2363
  node.gen.py_ast = [combined_multi[0]]
2282
2364
 
2283
2365
  def exit_f_string(self, node: uni.FString) -> None:
2284
- py_parts: list[list[ast3.AST]] = [
2285
- cast(list[ast3.AST], p.gen.py_ast) for p in node.parts
2366
+ node.gen.py_ast = [
2367
+ self.sync(
2368
+ ast3.JoinedStr(
2369
+ values=[
2370
+ cast(ast3.expr, part.gen.py_ast[0])
2371
+ for part in node.parts
2372
+ if part.gen.py_ast
2373
+ ],
2374
+ )
2375
+ )
2376
+ ]
2377
+
2378
+ def exit_formatted_value(self, node: uni.FormattedValue) -> None:
2379
+ node.gen.py_ast = [
2380
+ self.sync(
2381
+ ast3.FormattedValue(
2382
+ value=cast(ast3.expr, node.format_part.gen.py_ast[0]),
2383
+ conversion=node.conversion,
2384
+ format_spec=(
2385
+ cast(ast3.expr, node.format_spec.gen.py_ast[0])
2386
+ if node.format_spec
2387
+ else None
2388
+ ),
2389
+ )
2390
+ )
2286
2391
  ]
2287
- parts = self.flatten(cast(list[list[ast3.AST] | ast3.AST | None], py_parts))
2288
- node.gen.py_ast = parts if parts else [self.sync(ast3.Constant(value=""))]
2289
2392
 
2290
2393
  def exit_list_val(self, node: uni.ListVal) -> None:
2291
2394
  elts = [cast(ast3.expr, v.gen.py_ast[0]) for v in node.values]
@@ -2431,7 +2534,7 @@ class PyastGenPass(UniPass):
2431
2534
  node.gen.py_ast = [
2432
2535
  self.sync(
2433
2536
  ast3.Call(
2434
- func=self.jaclib_obj("filter"),
2537
+ func=self.jaclib_obj("filter_on"),
2435
2538
  args=[],
2436
2539
  keywords=[
2437
2540
  self.sync(
@@ -2454,7 +2557,7 @@ class PyastGenPass(UniPass):
2454
2557
  node.gen.py_ast = [
2455
2558
  self.sync(
2456
2559
  ast3.Call(
2457
- func=self.jaclib_obj("assign"),
2560
+ func=self.jaclib_obj("assign_all"),
2458
2561
  args=cast(
2459
2562
  list[ast3.expr],
2460
2563
  [node.target.gen.py_ast[0], node.right.gen.py_ast[0]],
@@ -2501,6 +2604,24 @@ class PyastGenPass(UniPass):
2501
2604
  ]
2502
2605
 
2503
2606
  def exit_atom_unit(self, node: uni.AtomUnit) -> None:
2607
+ if (
2608
+ isinstance(node.value, uni.Ability)
2609
+ and node.value.gen.py_ast
2610
+ and isinstance(
2611
+ node.value.gen.py_ast[0], (ast3.FunctionDef, ast3.AsyncFunctionDef)
2612
+ )
2613
+ ):
2614
+ func_ast = cast(
2615
+ ast3.FunctionDef | ast3.AsyncFunctionDef, node.value.gen.py_ast[0]
2616
+ )
2617
+ node.gen.py_ast = [
2618
+ self._function_expr_from_def(
2619
+ func_def=func_ast,
2620
+ jac_node=node,
2621
+ filename_hint=f"<jac_iife:{func_ast.name}>",
2622
+ )
2623
+ ]
2624
+ return
2504
2625
  node.gen.py_ast = node.value.gen.py_ast
2505
2626
 
2506
2627
  def gen_call_args(
@@ -2649,7 +2770,7 @@ class PyastGenPass(UniPass):
2649
2770
 
2650
2771
  pynode = self.sync(
2651
2772
  ast3.Call(
2652
- func=self.jaclib_obj("Path"),
2773
+ func=self.jaclib_obj("OPath"),
2653
2774
  args=[cast(ast3.expr, origin or cur.gen.py_ast[0])],
2654
2775
  keywords=[],
2655
2776
  )
@@ -2685,7 +2806,7 @@ class PyastGenPass(UniPass):
2685
2806
  func=self.sync(
2686
2807
  ast3.Attribute(
2687
2808
  value=pynode,
2688
- attr=f"_{cur.edge_dir.name.lower()}",
2809
+ attr=f"edge_{cur.edge_dir.name.lower()}",
2689
2810
  ctx=ast3.Load(),
2690
2811
  )
2691
2812
  ),
@@ -3026,6 +3147,11 @@ class PyastGenPass(UniPass):
3026
3147
 
3027
3148
  def exit_name(self, node: uni.Name) -> None:
3028
3149
  name = node.sym_name
3150
+ # Track if this name is a known builtin
3151
+ import jaclang.runtimelib.builtin
3152
+
3153
+ if name in set(jaclang.runtimelib.builtin.__all__):
3154
+ self.builtin_imports.add(name)
3029
3155
  node.gen.py_ast = [self.sync(ast3.Name(id=name, ctx=node.py_ctx_func()))]
3030
3156
 
3031
3157
  def exit_float(self, node: uni.Float) -> None:
@@ -3051,6 +3177,38 @@ class PyastGenPass(UniPass):
3051
3177
  def exit_ellipsis(self, node: uni.Ellipsis) -> None:
3052
3178
  node.gen.py_ast = [self.sync(ast3.Constant(value=...))]
3053
3179
 
3180
+ def exit_jsx_element(self, node: uni.JsxElement) -> None:
3181
+ """Generate Python AST for JSX elements."""
3182
+ self.jsx_processor.element(node)
3183
+
3184
+ def exit_jsx_element_name(self, node: uni.JsxElementName) -> None:
3185
+ """Generate Python AST for JSX element names."""
3186
+ self.jsx_processor.element_name(node)
3187
+
3188
+ def exit_jsx_attribute(self, node: uni.JsxAttribute) -> None:
3189
+ """Bases class for JSX attributes - handled by subclasses."""
3190
+ pass
3191
+
3192
+ def exit_jsx_spread_attribute(self, node: uni.JsxSpreadAttribute) -> None:
3193
+ """Generate Python AST for JSX spread attributes."""
3194
+ self.jsx_processor.spread_attribute(node)
3195
+
3196
+ def exit_jsx_normal_attribute(self, node: uni.JsxNormalAttribute) -> None:
3197
+ """Generate Python AST for JSX normal attributes."""
3198
+ self.jsx_processor.normal_attribute(node)
3199
+
3200
+ def exit_jsx_child(self, node: uni.JsxChild) -> None:
3201
+ """Bases class for JSX children - handled by subclasses."""
3202
+ pass
3203
+
3204
+ def exit_jsx_text(self, node: uni.JsxText) -> None:
3205
+ """Generate Python AST for JSX text nodes."""
3206
+ self.jsx_processor.text(node)
3207
+
3208
+ def exit_jsx_expression(self, node: uni.JsxExpression) -> None:
3209
+ """Generate Python AST for JSX expression children."""
3210
+ self.jsx_processor.expression(node)
3211
+
3054
3212
  def exit_semi(self, node: uni.Semi) -> None:
3055
3213
  pass
3056
3214