jaclang 0.8.9__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 (103) hide show
  1. jaclang/cli/cli.py +147 -25
  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 -13
  6. jaclang/compiler/jac.lark +130 -31
  7. jaclang/compiler/larkparse/jac_parser.py +2 -2
  8. jaclang/compiler/parser.py +567 -176
  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/pyast_gen_pass.py +324 -234
  28. jaclang/compiler/passes/main/pyast_load_pass.py +46 -11
  29. jaclang/compiler/passes/main/pyjac_ast_link_pass.py +2 -0
  30. jaclang/compiler/passes/main/sym_tab_build_pass.py +18 -1
  31. jaclang/compiler/passes/main/tests/fixtures/autoimpl.cl.jac +7 -0
  32. jaclang/compiler/passes/main/tests/fixtures/checker_arity.jac +3 -0
  33. jaclang/compiler/passes/main/tests/fixtures/checker_class_construct.jac +33 -0
  34. jaclang/compiler/passes/main/tests/fixtures/defuse_modpath.jac +7 -0
  35. jaclang/compiler/passes/main/tests/fixtures/member_access_type_resolve.jac +2 -1
  36. jaclang/compiler/passes/main/tests/test_checker_pass.py +31 -2
  37. jaclang/compiler/passes/main/tests/test_def_use_pass.py +12 -0
  38. jaclang/compiler/passes/main/tests/test_import_pass.py +23 -4
  39. jaclang/compiler/passes/main/tests/test_pyast_gen_pass.py +25 -0
  40. jaclang/compiler/passes/main/type_checker_pass.py +7 -0
  41. jaclang/compiler/passes/tool/doc_ir_gen_pass.py +115 -0
  42. jaclang/compiler/passes/tool/fuse_comments_pass.py +1 -10
  43. jaclang/compiler/passes/tool/tests/test_jac_format_pass.py +4 -1
  44. jaclang/compiler/passes/transform.py +9 -1
  45. jaclang/compiler/passes/uni_pass.py +5 -7
  46. jaclang/compiler/program.py +22 -25
  47. jaclang/compiler/tests/test_client_codegen.py +113 -0
  48. jaclang/compiler/tests/test_importer.py +12 -10
  49. jaclang/compiler/tests/test_parser.py +249 -3
  50. jaclang/compiler/type_system/type_evaluator.jac +169 -50
  51. jaclang/compiler/type_system/type_utils.py +1 -1
  52. jaclang/compiler/type_system/types.py +6 -0
  53. jaclang/compiler/unitree.py +430 -84
  54. jaclang/langserve/engine.jac +224 -288
  55. jaclang/langserve/sem_manager.jac +12 -8
  56. jaclang/langserve/server.jac +48 -48
  57. jaclang/langserve/tests/fixtures/greet.py +17 -0
  58. jaclang/langserve/tests/fixtures/md_path.jac +22 -0
  59. jaclang/langserve/tests/fixtures/user.jac +15 -0
  60. jaclang/langserve/tests/test_server.py +66 -371
  61. jaclang/lib.py +1 -1
  62. jaclang/runtimelib/client_bundle.py +169 -0
  63. jaclang/runtimelib/client_runtime.jac +586 -0
  64. jaclang/runtimelib/constructs.py +2 -0
  65. jaclang/runtimelib/machine.py +259 -100
  66. jaclang/runtimelib/meta_importer.py +111 -22
  67. jaclang/runtimelib/mtp.py +15 -0
  68. jaclang/runtimelib/server.py +1089 -0
  69. jaclang/runtimelib/tests/fixtures/client_app.jac +18 -0
  70. jaclang/runtimelib/tests/fixtures/custom_access_validation.jac +1 -1
  71. jaclang/runtimelib/tests/fixtures/savable_object.jac +4 -5
  72. jaclang/runtimelib/tests/fixtures/serve_api.jac +75 -0
  73. jaclang/runtimelib/tests/test_client_bundle.py +55 -0
  74. jaclang/runtimelib/tests/test_client_render.py +63 -0
  75. jaclang/runtimelib/tests/test_serve.py +1069 -0
  76. jaclang/settings.py +0 -2
  77. jaclang/tests/fixtures/iife_functions.jac +142 -0
  78. jaclang/tests/fixtures/iife_functions_client.jac +143 -0
  79. jaclang/tests/fixtures/multistatement_lambda.jac +116 -0
  80. jaclang/tests/fixtures/multistatement_lambda_client.jac +113 -0
  81. jaclang/tests/fixtures/needs_import_dup.jac +6 -4
  82. jaclang/tests/fixtures/py_run.py +7 -5
  83. jaclang/tests/fixtures/pyfunc_fstr.py +2 -2
  84. jaclang/tests/fixtures/simple_lambda_test.jac +12 -0
  85. jaclang/tests/test_cli.py +1 -1
  86. jaclang/tests/test_language.py +10 -39
  87. jaclang/tests/test_reference.py +17 -2
  88. jaclang/utils/NonGPT.py +375 -0
  89. jaclang/utils/helpers.py +44 -16
  90. jaclang/utils/lang_tools.py +31 -4
  91. jaclang/utils/tests/test_lang_tools.py +1 -1
  92. jaclang/utils/treeprinter.py +8 -3
  93. {jaclang-0.8.9.dist-info → jaclang-0.8.10.dist-info}/METADATA +3 -3
  94. {jaclang-0.8.9.dist-info → jaclang-0.8.10.dist-info}/RECORD +96 -66
  95. jaclang/compiler/passes/main/binder_pass.py +0 -594
  96. jaclang/compiler/passes/main/tests/fixtures/sym_binder.jac +0 -47
  97. jaclang/compiler/passes/main/tests/test_binder_pass.py +0 -111
  98. jaclang/langserve/tests/session.jac +0 -294
  99. jaclang/langserve/tests/test_dev_server.py +0 -80
  100. jaclang/runtimelib/importer.py +0 -351
  101. jaclang/tests/test_typecheck.py +0 -542
  102. {jaclang-0.8.9.dist-info → jaclang-0.8.10.dist-info}/WHEEL +0 -0
  103. {jaclang-0.8.9.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
 
@@ -88,37 +88,17 @@ UNARY_OP_MAP: dict[Tok, type[ast3.unaryop]] = {
88
88
  }
89
89
 
90
90
 
91
- class PyastGenPass(UniPass):
91
+ class PyastGenPass(BaseAstGenPass[ast3.AST]):
92
92
  """Jac blue transpilation to python pass."""
93
93
 
94
- # Builtins that should be imported from jaclang.runtimelib.builtin
95
- KNOWN_BUILTINS = {
96
- "abstractmethod",
97
- "ClassVar",
98
- "override",
99
- "printgraph",
100
- "jid",
101
- "jobj",
102
- "grant",
103
- "revoke",
104
- "allroots",
105
- "save",
106
- "commit",
107
- "NoPerm",
108
- "ReadPerm",
109
- "ConnectPerm",
110
- "WritePerm",
111
- }
112
-
113
94
  def before_pass(self) -> None:
114
- self.child_passes: list[PyastGenPass] = []
115
- for i in self.ir_in.impl_mod + self.ir_in.test_mod:
116
- child_pass = PyastGenPass(ir_in=i, prog=self.prog)
117
- self.child_passes.append(child_pass)
95
+ self.child_passes: list[PyastGenPass] = self._init_child_passes(PyastGenPass)
118
96
  self.debuginfo: dict[str, list[str]] = {"jac_mods": []}
119
97
  self.already_added: list[str] = []
120
98
  self.jaclib_imports: set[str] = set() # Track individual jaclib imports
121
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] = []
122
102
  self.preamble: list[ast3.AST] = [
123
103
  self.sync(
124
104
  ast3.ImportFrom(
@@ -129,9 +109,16 @@ class PyastGenPass(UniPass):
129
109
  jac_node=self.ir_out,
130
110
  ),
131
111
  ]
112
+ self.jsx_processor = PyJsxProcessor(self)
132
113
 
133
114
  def enter_node(self, node: uni.UniNode) -> None:
134
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
135
122
  if node.gen.py_ast:
136
123
  self.prune()
137
124
  return
@@ -139,30 +126,17 @@ class PyastGenPass(UniPass):
139
126
 
140
127
  def exit_node(self, node: uni.UniNode) -> None:
141
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
142
134
  super().exit_node(node)
143
- # for i in node.gen.py_ast: # Internal validation
144
- # self.node_compilable_test(i)
145
-
146
- # TODO: USE THIS TO SYNC
147
- # if isinstance(i, ast3.AST):
148
- # i.jac_link = node
149
135
 
150
- def jaclib_obj(self, obj_name: str) -> ast3.Name | ast3.Attribute:
136
+ def jaclib_obj(self, obj_name: str) -> ast3.Name:
151
137
  """Return the object from jaclib as ast node based on the import config."""
152
- if settings.library_mode:
153
- self.jaclib_imports.add(obj_name)
154
- return self.sync(ast3.Name(id=obj_name, ctx=ast3.Load()))
155
- else:
156
- self.needs_jaclib()
157
- return self.sync(
158
- ast3.Attribute(
159
- value=self.sync(
160
- ast3.Name(id=settings.pyout_jaclib_alias, ctx=ast3.Load())
161
- ),
162
- attr=obj_name,
163
- ctx=ast3.Load(),
164
- )
165
- )
138
+ self.jaclib_imports.add(obj_name)
139
+ return self.sync(ast3.Name(id=obj_name, ctx=ast3.Load()))
166
140
 
167
141
  def builtin_name(self, name: str) -> ast3.Name:
168
142
  """Return a builtin name and track it for importing.
@@ -216,22 +190,31 @@ class PyastGenPass(UniPass):
216
190
  ),
217
191
  )
218
192
 
219
- def needs_jaclib(self) -> None:
220
- """Ensure JacMachineInterface is imported only once."""
221
- self._add_preamble_once(
222
- self.needs_jaclib.__name__,
223
- ast3.ImportFrom(
224
- module="jaclang",
225
- names=[
226
- self.sync(
227
- ast3.alias(
228
- name="JacMachineInterface",
229
- asname=settings.pyout_jaclib_alias,
230
- )
231
- )
232
- ],
233
- level=0,
234
- ),
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,
235
218
  )
236
219
 
237
220
  def _get_sem_decorator(self, node: uni.UniNode) -> ast3.Call | None:
@@ -324,15 +307,9 @@ class PyastGenPass(UniPass):
324
307
  )
325
308
  )
326
309
 
327
- def flatten(self, body: list[T | list[T] | None]) -> list[T]:
328
- """Flatten a list of items or lists into a single list."""
329
- new_body: list[T] = []
330
- for item in body:
331
- if isinstance(item, list):
332
- new_body.extend(item)
333
- elif item is not None:
334
- new_body.append(item)
335
- 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
336
313
 
337
314
  def sync(
338
315
  self, py_node: T, jac_node: Optional[uni.UniNode] = None, deep: bool = False
@@ -389,7 +366,7 @@ class PyastGenPass(UniPass):
389
366
  [self.sync(ast3.Pass())]
390
367
  if isinstance(node, Sequence) and not valid_stmts
391
368
  else (
392
- self.flatten(
369
+ self._flatten_ast_list(
393
370
  [
394
371
  x.gen.py_ast
395
372
  for x in valid_stmts
@@ -433,14 +410,9 @@ class PyastGenPass(UniPass):
433
410
  node.gen.py_ast = node.tag.gen.py_ast
434
411
 
435
412
  def exit_module(self, node: uni.Module) -> None:
436
- # Check if any child passes (impl_mod or test_mod) needed jaclib
413
+ # Merge jaclib and builtin imports from child passes
437
414
  for child_pass in self.child_passes:
438
- if "needs_jaclib" in child_pass.already_added:
439
- self.needs_jaclib()
440
- break
441
- # Merge jaclib and builtin imports from child passes
442
- if settings.library_mode:
443
- self.jaclib_imports.update(child_pass.jaclib_imports)
415
+ self.jaclib_imports.update(child_pass.jaclib_imports)
444
416
  self.builtin_imports.update(child_pass.builtin_imports)
445
417
 
446
418
  # Add builtin imports if any were used
@@ -459,8 +431,8 @@ class PyastGenPass(UniPass):
459
431
  )
460
432
  )
461
433
 
462
- # Add library mode imports at the end of preamble
463
- if settings.library_mode and self.jaclib_imports:
434
+ # Add library imports at the end of preamble
435
+ if self.jaclib_imports:
464
436
  self.preamble.append(
465
437
  self.sync(
466
438
  ast3.ImportFrom(
@@ -475,31 +447,18 @@ class PyastGenPass(UniPass):
475
447
  )
476
448
  )
477
449
 
478
- clean_body = [i for i in node.body if not isinstance(i, uni.ImplDef)]
479
- pre_body: list[uni.UniNode] = []
480
- for pbody in node.impl_mod:
481
- pre_body = [*pre_body, *pbody.body]
482
- pre_body = [*pre_body, *clean_body]
483
- for pbody in node.test_mod:
484
- pre_body = [*pre_body, *pbody.body]
485
- body = (
486
- [
487
- self.sync(
488
- ast3.Expr(value=cast(ast3.expr, node.doc.gen.py_ast[0])),
489
- jac_node=node.doc,
490
- ),
491
- *self.preamble,
492
- *[x.gen.py_ast for x in pre_body],
493
- ]
494
- if node.doc
495
- else [*self.preamble, *[x.gen.py_ast for x in pre_body]]
496
- )
497
- new_body = []
498
- for i in body:
499
- if isinstance(i, list):
500
- new_body += i
501
- else:
502
- new_body.append(i) if i else None
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)
503
462
  node.gen.py_ast = [
504
463
  self.sync(
505
464
  ast3.Module(
@@ -516,7 +475,7 @@ class PyastGenPass(UniPass):
516
475
  ast3.Expr(value=cast(ast3.expr, node.doc.gen.py_ast[0])),
517
476
  jac_node=node.doc,
518
477
  )
519
- assigns_ast: list[ast3.AST] = self.flatten(
478
+ assigns_ast: list[ast3.AST] = self._flatten_ast_list(
520
479
  [a.gen.py_ast for a in node.assignments]
521
480
  )
522
481
  if isinstance(doc, ast3.AST):
@@ -524,7 +483,9 @@ class PyastGenPass(UniPass):
524
483
  else:
525
484
  raise self.ice()
526
485
  else:
527
- 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
+ )
528
489
 
529
490
  def exit_test(self, node: uni.Test) -> None:
530
491
  test_name = node.name.sym_name
@@ -745,18 +706,17 @@ class PyastGenPass(UniPass):
745
706
  if node.arch_type.name != Tok.KW_CLASS:
746
707
  base_classes.append(self.jaclib_obj(node.arch_type.value.capitalize()))
747
708
 
748
- node.gen.py_ast = [
749
- self.sync(
750
- ast3.ClassDef(
751
- name=node.name.sym_name,
752
- bases=[cast(ast3.expr, i) for i in base_classes],
753
- keywords=[],
754
- body=[cast(ast3.stmt, i) for i in body],
755
- decorator_list=[cast(ast3.expr, i) for i in decorators],
756
- type_params=[],
757
- )
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=[],
758
717
  )
759
- ]
718
+ )
719
+ node.gen.py_ast = [class_def]
760
720
 
761
721
  def enter_enum(self, node: uni.Enum) -> None:
762
722
  if isinstance(node.body, uni.ImplDef):
@@ -781,18 +741,17 @@ class PyastGenPass(UniPass):
781
741
 
782
742
  base_classes = [cast(ast3.expr, i.gen.py_ast[0]) for i in node.base_classes]
783
743
  base_classes.append(self.builtin_name("Enum"))
784
- node.gen.py_ast = [
785
- self.sync(
786
- ast3.ClassDef(
787
- name=node.name.sym_name,
788
- bases=[cast(ast3.expr, i) for i in base_classes],
789
- keywords=[],
790
- body=[cast(ast3.stmt, i) for i in body],
791
- decorator_list=[cast(ast3.expr, i) for i in decorators],
792
- type_params=[],
793
- )
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=[],
794
752
  )
795
- ]
753
+ )
754
+ node.gen.py_ast = [class_def]
796
755
 
797
756
  def enter_ability(self, node: uni.Ability) -> None:
798
757
  if isinstance(node.body, uni.ImplDef):
@@ -1006,36 +965,35 @@ class PyastGenPass(UniPass):
1006
965
  if isinstance(node.signature, uni.FuncSignature) and node.signature.return_type:
1007
966
  ast_returns = cast(ast3.expr, node.signature.return_type.gen.py_ast[0])
1008
967
 
1009
- node.gen.py_ast = [
1010
- self.sync(
1011
- func_type(
1012
- name=node.name_ref.sym_name,
1013
- args=(
1014
- cast(ast3.arguments, node.signature.gen.py_ast[0])
1015
- if node.signature
1016
- else self.sync(
1017
- ast3.arguments(
1018
- posonlyargs=[],
1019
- args=(
1020
- [self.sync(ast3.arg(arg="self", annotation=None))]
1021
- if node.is_method
1022
- else []
1023
- ),
1024
- vararg=None,
1025
- kwonlyargs=[],
1026
- kw_defaults=[],
1027
- kwarg=None,
1028
- defaults=[],
1029
- )
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=[],
1030
987
  )
1031
- ),
1032
- body=[cast(ast3.stmt, i) for i in body],
1033
- decorator_list=[cast(ast3.expr, i) for i in decorator_list],
1034
- returns=ast_returns,
1035
- type_params=[],
1036
- )
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=[],
1037
994
  )
1038
- ]
995
+ )
996
+ node.gen.py_ast = [func_def]
1039
997
 
1040
998
  def exit_impl_def(self, node: uni.ImplDef) -> None:
1041
999
  pass
@@ -1155,7 +1113,9 @@ class PyastGenPass(UniPass):
1155
1113
  ]
1156
1114
 
1157
1115
  def exit_arch_has(self, node: uni.ArchHas) -> None:
1158
- 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
+ )
1159
1119
  if node.doc:
1160
1120
  doc = self.sync(
1161
1121
  ast3.Expr(value=cast(ast3.expr, node.doc.gen.py_ast[0])),
@@ -1324,19 +1284,24 @@ class PyastGenPass(UniPass):
1324
1284
  node.gen.py_ast = self.resolve_stmt_block(node.body)
1325
1285
 
1326
1286
  def exit_expr_stmt(self, node: uni.ExprStmt) -> None:
1327
- node.gen.py_ast = [
1328
- (
1329
- self.sync(ast3.Expr(value=cast(ast3.expr, node.expr.gen.py_ast[0])))
1330
- if not node.in_fstring
1331
- else self.sync(
1332
- ast3.FormattedValue(
1333
- value=cast(ast3.expr, node.expr.gen.py_ast[0]),
1334
- conversion=-1,
1335
- format_spec=None,
1336
- )
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,
1337
1299
  )
1338
1300
  )
1339
- ]
1301
+ )
1302
+
1303
+ # Emit hoisted functions before the expression statement
1304
+ node.gen.py_ast = [*hoisted, expr_stmt]
1340
1305
 
1341
1306
  def exit_concurrent_expr(self, node: uni.ConcurrentExpr) -> None:
1342
1307
  func = ""
@@ -1773,7 +1738,7 @@ class PyastGenPass(UniPass):
1773
1738
  value=self.sync(
1774
1739
  self.sync(
1775
1740
  ast3.Call(
1776
- func=self.jaclib_obj("report"),
1741
+ func=self.jaclib_obj("log_report"),
1777
1742
  args=cast(list[ast3.expr], node.expr.gen.py_ast),
1778
1743
  keywords=[],
1779
1744
  )
@@ -1930,48 +1895,49 @@ class PyastGenPass(UniPass):
1930
1895
  )
1931
1896
  targets_ast = [cast(ast3.expr, t.gen.py_ast[0]) for t in node.target]
1932
1897
 
1898
+ # Collect any hoisted functions that were generated during expression evaluation
1899
+ hoisted = self._hoisted_funcs[:]
1900
+ self._hoisted_funcs.clear()
1901
+
1933
1902
  if node.type_tag:
1934
- node.gen.py_ast = [
1935
- self.sync(
1936
- ast3.AnnAssign(
1937
- target=cast(ast3.Name, targets_ast[0]),
1938
- annotation=cast(ast3.expr, node.type_tag.gen.py_ast[0]),
1939
- value=(
1940
- cast(ast3.expr, node.value.gen.py_ast[0])
1941
- if node.value
1942
- else None
1943
- ),
1944
- simple=int(isinstance(targets_ast[0], ast3.Name)),
1945
- )
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)),
1946
1913
  )
1947
- ]
1914
+ )
1948
1915
  elif node.aug_op:
1949
- node.gen.py_ast = [
1950
- self.sync(
1951
- ast3.AugAssign(
1952
- target=cast(ast3.Name, targets_ast[0]),
1953
- op=cast(ast3.operator, node.aug_op.gen.py_ast[0]),
1954
- value=(
1955
- cast(ast3.expr, value)
1956
- if isinstance(value, ast3.expr)
1957
- else ast3.Constant(value=None)
1958
- ),
1959
- )
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
+ ),
1960
1925
  )
1961
- ]
1926
+ )
1962
1927
  else:
1963
- node.gen.py_ast = [
1964
- self.sync(
1965
- ast3.Assign(
1966
- targets=cast(list[ast3.expr], targets_ast),
1967
- value=(
1968
- cast(ast3.expr, value)
1969
- if isinstance(value, ast3.expr)
1970
- else ast3.Constant(value=None)
1971
- ),
1972
- )
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
+ ),
1973
1936
  )
1974
- ]
1937
+ )
1938
+
1939
+ # Emit hoisted functions before the assignment
1940
+ node.gen.py_ast = [*hoisted, assignment_stmt]
1975
1941
 
1976
1942
  def exit_binary_expr(self, node: uni.BinaryExpr) -> None:
1977
1943
  if isinstance(node.op, uni.ConnectOp):
@@ -2199,28 +2165,80 @@ class PyastGenPass(UniPass):
2199
2165
  ]
2200
2166
 
2201
2167
  def exit_lambda_expr(self, node: uni.LambdaExpr) -> None:
2202
- # 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.
2203
2219
  if node.signature:
2204
2220
  self._remove_lambda_param_annotations(node.signature)
2205
-
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])
2206
2235
  node.gen.py_ast = [
2207
2236
  self.sync(
2208
2237
  ast3.Lambda(
2209
- args=(
2210
- cast(ast3.arguments, node.signature.gen.py_ast[0])
2211
- if node.signature
2212
- else self.sync(
2213
- ast3.arguments(
2214
- posonlyargs=[],
2215
- args=[],
2216
- kwonlyargs=[],
2217
- kw_defaults=[],
2218
- defaults=[],
2219
- )
2220
- )
2221
- ),
2222
- body=cast(ast3.expr, node.body.gen.py_ast[0]),
2223
- )
2238
+ args=arguments,
2239
+ body=body_expr,
2240
+ ),
2241
+ jac_node=node,
2224
2242
  )
2225
2243
  ]
2226
2244
 
@@ -2304,9 +2322,8 @@ class PyastGenPass(UniPass):
2304
2322
  for i in str_seq:
2305
2323
  if isinstance(i, uni.String):
2306
2324
  pieces.append(i.lit_value)
2307
- elif isinstance(i, uni.FString):
2308
- pieces.extend(get_pieces(i.parts)) if i.parts else None
2309
- 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
2310
2327
  pieces.append(i.gen.py_ast[0])
2311
2328
  elif isinstance(i, uni.Token) and i.name in [Tok.LBRACE, Tok.RBRACE]:
2312
2329
  continue
@@ -2346,11 +2363,32 @@ class PyastGenPass(UniPass):
2346
2363
  node.gen.py_ast = [combined_multi[0]]
2347
2364
 
2348
2365
  def exit_f_string(self, node: uni.FString) -> None:
2349
- py_parts: list[list[ast3.AST]] = [
2350
- 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
+ )
2351
2391
  ]
2352
- parts = self.flatten(cast(list[list[ast3.AST] | ast3.AST | None], py_parts))
2353
- node.gen.py_ast = parts if parts else [self.sync(ast3.Constant(value=""))]
2354
2392
 
2355
2393
  def exit_list_val(self, node: uni.ListVal) -> None:
2356
2394
  elts = [cast(ast3.expr, v.gen.py_ast[0]) for v in node.values]
@@ -2566,6 +2604,24 @@ class PyastGenPass(UniPass):
2566
2604
  ]
2567
2605
 
2568
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
2569
2625
  node.gen.py_ast = node.value.gen.py_ast
2570
2626
 
2571
2627
  def gen_call_args(
@@ -2714,7 +2770,7 @@ class PyastGenPass(UniPass):
2714
2770
 
2715
2771
  pynode = self.sync(
2716
2772
  ast3.Call(
2717
- func=self.jaclib_obj("Path"),
2773
+ func=self.jaclib_obj("OPath"),
2718
2774
  args=[cast(ast3.expr, origin or cur.gen.py_ast[0])],
2719
2775
  keywords=[],
2720
2776
  )
@@ -3092,7 +3148,9 @@ class PyastGenPass(UniPass):
3092
3148
  def exit_name(self, node: uni.Name) -> None:
3093
3149
  name = node.sym_name
3094
3150
  # Track if this name is a known builtin
3095
- if name in self.KNOWN_BUILTINS:
3151
+ import jaclang.runtimelib.builtin
3152
+
3153
+ if name in set(jaclang.runtimelib.builtin.__all__):
3096
3154
  self.builtin_imports.add(name)
3097
3155
  node.gen.py_ast = [self.sync(ast3.Name(id=name, ctx=node.py_ctx_func()))]
3098
3156
 
@@ -3119,6 +3177,38 @@ class PyastGenPass(UniPass):
3119
3177
  def exit_ellipsis(self, node: uni.Ellipsis) -> None:
3120
3178
  node.gen.py_ast = [self.sync(ast3.Constant(value=...))]
3121
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
+
3122
3212
  def exit_semi(self, node: uni.Semi) -> None:
3123
3213
  pass
3124
3214