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
@@ -135,20 +135,53 @@ class DocIRGenPass(UniPass):
135
135
  def exit_import(self, node: uni.Import) -> None:
136
136
  """Exit import node."""
137
137
  parts: list[doc.DocType] = []
138
+ mod_items: list[doc.DocType] = []
139
+ has_comment: Optional[doc.DocType] = None
140
+ is_in_items: bool = False
138
141
  for i in node.kid:
139
142
  if isinstance(i, uni.Token) and i.name == Tok.COMMA:
140
- parts.pop()
141
- parts.append(i.gen.doc_ir)
142
- parts.append(self.space())
143
+ if is_in_items:
144
+ mod_items.pop()
145
+ mod_items.append(i.gen.doc_ir)
146
+ mod_items.append(self.line())
147
+ else:
148
+ parts.pop()
149
+ parts.append(i.gen.doc_ir)
150
+ parts.append(self.line())
151
+ elif (
152
+ i == node.kid[0] and isinstance(i, uni.Token) and i.name == Tok.COMMENT
153
+ ):
154
+ has_comment = i.gen.doc_ir
143
155
  elif isinstance(i, uni.Token) and i.name == Tok.SEMI:
144
156
  parts.pop()
145
157
  parts.append(i.gen.doc_ir)
146
158
  elif isinstance(i, uni.Token) and i.name == Tok.RBRACE:
159
+ is_in_items = False
160
+ mod_items.pop()
161
+ parts.append(
162
+ self.group(
163
+ self.concat(
164
+ [
165
+ self.indent(self.concat([self.line(), *mod_items])),
166
+ self.line(),
167
+ ]
168
+ )
169
+ )
170
+ )
147
171
  parts.append(i.gen.doc_ir)
148
- else:
172
+ elif isinstance(i, uni.Token) and i.name == Tok.LBRACE:
173
+ is_in_items = True
149
174
  parts.append(i.gen.doc_ir)
150
- parts.append(self.space())
175
+ else:
176
+ if is_in_items:
177
+ mod_items.append(i.gen.doc_ir)
178
+ mod_items.append(self.space())
179
+ else:
180
+ parts.append(i.gen.doc_ir)
181
+ parts.append(self.space())
151
182
  node.gen.doc_ir = self.group(self.concat(parts))
183
+ if has_comment:
184
+ node.gen.doc_ir = self.concat([has_comment, node.gen.doc_ir])
152
185
 
153
186
  def exit_module_item(self, node: uni.ModuleItem) -> None:
154
187
  """Generate DocIR for module items."""
@@ -178,6 +211,7 @@ class DocIRGenPass(UniPass):
178
211
  """Generate DocIR for archetypes."""
179
212
  parts: list[doc.DocType] = []
180
213
  body_parts: list[doc.DocType] = []
214
+ has_comment: Optional[doc.DocType] = None
181
215
  prev_item = None
182
216
  in_body = False
183
217
  for i in node.kid:
@@ -198,6 +232,10 @@ class DocIRGenPass(UniPass):
198
232
  parts.pop()
199
233
  parts.append(i.gen.doc_ir)
200
234
  parts.append(self.space())
235
+ elif (
236
+ i == node.kid[0] and isinstance(i, uni.Token) and i.name == Tok.COMMENT
237
+ ):
238
+ has_comment = i.gen.doc_ir
201
239
  elif isinstance(node.body, Sequence) and i in node.body:
202
240
  if not in_body:
203
241
  body_parts.append(self.hard_line())
@@ -225,13 +263,15 @@ class DocIRGenPass(UniPass):
225
263
  else:
226
264
  parts.append(i.gen.doc_ir)
227
265
  parts.append(self.space())
228
-
229
266
  node.gen.doc_ir = self.group(self.concat(parts))
267
+ if has_comment:
268
+ node.gen.doc_ir = self.concat([has_comment, node.gen.doc_ir])
230
269
 
231
270
  def exit_ability(self, node: uni.Ability) -> None:
232
271
  """Generate DocIR for abilities."""
233
272
  parts: list[doc.DocType] = []
234
273
  body_parts: list[doc.DocType] = []
274
+ has_comment: Optional[doc.DocType] = None
235
275
  in_body = False
236
276
  for i in node.kid:
237
277
  if i == node.doc or (node.decorators and i in node.decorators):
@@ -259,12 +299,18 @@ class DocIRGenPass(UniPass):
259
299
  parts.pop()
260
300
  parts.append(i.gen.doc_ir)
261
301
  parts.append(self.space())
302
+ elif (
303
+ i == node.kid[0] and isinstance(i, uni.Token) and i.name == Tok.COMMENT
304
+ ):
305
+ has_comment = i.gen.doc_ir
262
306
  elif not in_body and isinstance(i, uni.Token) and i.name == Tok.DECOR_OP:
263
307
  parts.append(i.gen.doc_ir)
264
308
  else:
265
309
  parts.append(i.gen.doc_ir)
266
310
  parts.append(self.space())
267
311
  node.gen.doc_ir = self.group(self.concat(parts))
312
+ if has_comment:
313
+ node.gen.doc_ir = self.concat([has_comment, node.gen.doc_ir])
268
314
 
269
315
  def exit_func_signature(self, node: uni.FuncSignature) -> None:
270
316
  """Generate DocIR for function signatures."""
@@ -401,7 +447,7 @@ class DocIRGenPass(UniPass):
401
447
  ]
402
448
  )
403
449
  )
404
- parts.append(self.if_break(broken, flat))
450
+ parts.append(self.group(self.if_break(broken, flat)))
405
451
  else:
406
452
  parts.append(i.gen.doc_ir)
407
453
  parts.append(self.space())
@@ -846,6 +892,13 @@ class DocIRGenPass(UniPass):
846
892
  parts.append(i.gen.doc_ir)
847
893
  node.gen.doc_ir = self.group(self.concat(parts))
848
894
 
895
+ def exit_formatted_value(self, node: uni.FormattedValue) -> None:
896
+ """Generate DocIR for formatted value expressions."""
897
+ parts: list[doc.DocType] = []
898
+ for i in node.kid:
899
+ parts.append(i.gen.doc_ir)
900
+ node.gen.doc_ir = self.group(self.concat(parts))
901
+
849
902
  def exit_if_else_expr(self, node: uni.IfElseExpr) -> None:
850
903
  """Generate DocIR for conditional expressions."""
851
904
  parts: list[doc.DocType] = []
@@ -901,16 +954,16 @@ class DocIRGenPass(UniPass):
901
954
  return [expr]
902
955
 
903
956
  exprs = __flatten_bool_expr(node)
904
- for i in range(0, len(exprs) - 1, 2):
957
+ parts += [exprs[0].gen.doc_ir, self.line()]
958
+ for i in range(1, len(exprs), 2):
905
959
  (
906
- expr,
907
960
  op,
961
+ expr,
908
962
  ) = (
909
- exprs[i],
910
963
  exprs[i + 1],
964
+ exprs[i],
911
965
  )
912
966
  parts += [expr.gen.doc_ir, self.space(), op.gen.doc_ir, self.line()]
913
- parts += [exprs[-1].gen.doc_ir, self.line()]
914
967
  parts.pop()
915
968
  flat = self.concat(parts)
916
969
  node.gen.doc_ir = self.group(flat)
@@ -1108,6 +1161,8 @@ class DocIRGenPass(UniPass):
1108
1161
  """Generate DocIR for assert statements."""
1109
1162
  parts: list[doc.DocType] = []
1110
1163
  for i in node.kid:
1164
+ if isinstance(i, uni.Token) and i.name == Tok.SEMI and len(parts):
1165
+ parts.pop()
1111
1166
  parts.append(i.gen.doc_ir)
1112
1167
  parts.append(self.space())
1113
1168
  parts.pop()
@@ -1125,6 +1180,7 @@ class DocIRGenPass(UniPass):
1125
1180
  def exit_global_vars(self, node: uni.GlobalVars) -> None:
1126
1181
  """Generate DocIR for global variables."""
1127
1182
  parts: list[doc.DocType] = []
1183
+ has_comment: Optional[doc.DocType] = None
1128
1184
  for i in node.kid:
1129
1185
  if i == node.doc:
1130
1186
  parts.append(i.gen.doc_ir)
@@ -1133,15 +1189,22 @@ class DocIRGenPass(UniPass):
1133
1189
  parts.pop()
1134
1190
  parts.append(i.gen.doc_ir)
1135
1191
  parts.append(self.space())
1192
+ elif (
1193
+ i == node.kid[0] and isinstance(i, uni.Token) and i.name == Tok.COMMENT
1194
+ ):
1195
+ has_comment = i.gen.doc_ir
1136
1196
  else:
1137
1197
  parts.append(i.gen.doc_ir)
1138
1198
  parts.append(self.space())
1139
1199
  node.gen.doc_ir = self.group(self.concat(parts))
1200
+ if has_comment:
1201
+ node.gen.doc_ir = self.concat([has_comment, node.gen.doc_ir])
1140
1202
 
1141
1203
  def exit_module_code(self, node: uni.ModuleCode) -> None:
1142
1204
  """Generate DocIR for module code."""
1143
1205
  parts: list[doc.DocType] = []
1144
1206
  body_parts: list[doc.DocType] = []
1207
+ has_comment: Optional[doc.DocType] = None
1145
1208
  in_body = False
1146
1209
  for i in node.kid:
1147
1210
  if node.doc and i is node.doc:
@@ -1153,6 +1216,10 @@ class DocIRGenPass(UniPass):
1153
1216
  elif isinstance(i, uni.Token) and i.name == Tok.COLON:
1154
1217
  parts.pop()
1155
1218
  parts.append(i.gen.doc_ir)
1219
+ elif (
1220
+ i == node.kid[0] and isinstance(i, uni.Token) and i.name == Tok.COMMENT
1221
+ ):
1222
+ has_comment = i.gen.doc_ir
1156
1223
  elif isinstance(node.body, Sequence) and i in node.body:
1157
1224
  if not in_body:
1158
1225
  parts.pop()
@@ -1175,6 +1242,8 @@ class DocIRGenPass(UniPass):
1175
1242
  parts.append(i.gen.doc_ir)
1176
1243
  parts.append(self.space())
1177
1244
  node.gen.doc_ir = self.group(self.concat(parts))
1245
+ if has_comment:
1246
+ node.gen.doc_ir = self.concat([has_comment, node.gen.doc_ir])
1178
1247
 
1179
1248
  def exit_global_stmt(self, node: uni.GlobalStmt) -> None:
1180
1249
  """Generate DocIR for global statements."""
@@ -1264,7 +1333,9 @@ class DocIRGenPass(UniPass):
1264
1333
  ]
1265
1334
  )
1266
1335
  )
1267
- if isinstance(node.parent, uni.Assignment):
1336
+ if isinstance(node.parent, (uni.Assignment)) or (
1337
+ isinstance(node.parent, uni.IfStmt) and isinstance(node.value, uni.BoolExpr)
1338
+ ):
1268
1339
  node.gen.doc_ir = self.if_break(
1269
1340
  flat_contents=self.group(self.concat(parts[1:-1])),
1270
1341
  break_contents=broken,
@@ -1300,6 +1371,7 @@ class DocIRGenPass(UniPass):
1300
1371
  def exit_py_inline_code(self, node: uni.PyInlineCode) -> None:
1301
1372
  """Generate DocIR for Python inline code blocks."""
1302
1373
  parts: list[doc.DocType] = []
1374
+ has_comment: Optional[doc.DocType] = None
1303
1375
  for i in node.kid:
1304
1376
  if i == node.doc:
1305
1377
  parts.append(i.gen.doc_ir)
@@ -1309,14 +1381,21 @@ class DocIRGenPass(UniPass):
1309
1381
  parts.append(i.gen.doc_ir)
1310
1382
  parts.append(self.text("::py::"))
1311
1383
  parts.append(self.hard_line())
1384
+ elif (
1385
+ i == node.kid[0] and isinstance(i, uni.Token) and i.name == Tok.COMMENT
1386
+ ):
1387
+ has_comment = i.gen.doc_ir
1312
1388
  else:
1313
1389
  parts.append(i.gen.doc_ir)
1314
1390
  parts.append(self.space())
1315
1391
  node.gen.doc_ir = self.group(self.concat(parts))
1392
+ if has_comment:
1393
+ node.gen.doc_ir = self.concat([has_comment, node.gen.doc_ir])
1316
1394
 
1317
1395
  def exit_test(self, node: uni.Test) -> None:
1318
1396
  """Generate DocIR for test nodes."""
1319
1397
  parts: list[doc.DocType] = []
1398
+ has_comment: Optional[doc.DocType] = None
1320
1399
  for i in node.kid:
1321
1400
  if i == node.doc:
1322
1401
  parts.append(i.gen.doc_ir)
@@ -1325,10 +1404,16 @@ class DocIRGenPass(UniPass):
1325
1404
  if not i.value.startswith("_jac_gen_"):
1326
1405
  parts.append(i.gen.doc_ir)
1327
1406
  parts.append(self.space())
1407
+ elif (
1408
+ i == node.kid[0] and isinstance(i, uni.Token) and i.name == Tok.COMMENT
1409
+ ):
1410
+ has_comment = i.gen.doc_ir
1328
1411
  else:
1329
1412
  parts.append(i.gen.doc_ir)
1330
1413
  parts.append(self.space())
1331
1414
  node.gen.doc_ir = self.group(self.concat(parts))
1415
+ if has_comment:
1416
+ node.gen.doc_ir = self.concat([has_comment, node.gen.doc_ir])
1332
1417
 
1333
1418
  def exit_match_stmt(self, node: uni.MatchStmt) -> None:
1334
1419
  """Generate DocIR for match statements."""
@@ -1512,6 +1597,7 @@ class DocIRGenPass(UniPass):
1512
1597
  """Generate DocIR for implementation definitions."""
1513
1598
  parts: list[doc.DocType] = []
1514
1599
  body_parts: list[doc.DocType] = []
1600
+ has_comment: Optional[doc.DocType] = None
1515
1601
  in_body = False
1516
1602
  for i in node.kid:
1517
1603
  if i == node.doc or (node.decorators and i in node.decorators):
@@ -1519,6 +1605,10 @@ class DocIRGenPass(UniPass):
1519
1605
  parts.append(self.hard_line())
1520
1606
  elif self.is_within(i, node.target):
1521
1607
  parts.append(i.gen.doc_ir)
1608
+ elif (
1609
+ i == node.kid[0] and isinstance(i, uni.Token) and i.name == Tok.COMMENT
1610
+ ):
1611
+ has_comment = i.gen.doc_ir
1522
1612
  elif (
1523
1613
  in_body
1524
1614
  or isinstance(node.body, Sequence)
@@ -1546,10 +1636,13 @@ class DocIRGenPass(UniPass):
1546
1636
  parts.append(i.gen.doc_ir)
1547
1637
  parts.append(self.space())
1548
1638
  node.gen.doc_ir = self.group(self.concat(parts))
1639
+ if has_comment:
1640
+ node.gen.doc_ir = self.concat([has_comment, node.gen.doc_ir])
1549
1641
 
1550
1642
  def exit_sem_def(self, node: uni.SemDef) -> None:
1551
1643
  """Generate DocIR for semantic definitions."""
1552
1644
  parts: list[doc.DocType] = []
1645
+ has_comment: Optional[doc.DocType] = None
1553
1646
  for i in node.kid:
1554
1647
  if i in node.target:
1555
1648
  parts.append(i.gen.doc_ir)
@@ -1557,10 +1650,16 @@ class DocIRGenPass(UniPass):
1557
1650
  parts.pop()
1558
1651
  parts.append(i.gen.doc_ir)
1559
1652
  parts.append(self.space())
1653
+ elif (
1654
+ i == node.kid[0] and isinstance(i, uni.Token) and i.name == Tok.COMMENT
1655
+ ):
1656
+ has_comment = i.gen.doc_ir
1560
1657
  else:
1561
1658
  parts.append(i.gen.doc_ir)
1562
1659
  parts.append(self.space())
1563
1660
  node.gen.doc_ir = self.group(self.concat(parts))
1661
+ if has_comment:
1662
+ node.gen.doc_ir = self.concat([has_comment, node.gen.doc_ir])
1564
1663
 
1565
1664
  def exit_event_signature(self, node: uni.EventSignature) -> None:
1566
1665
  """Generate DocIR for event signatures."""
@@ -1614,14 +1713,6 @@ class DocIRGenPass(UniPass):
1614
1713
  and isinstance(node.parent, uni.FString)
1615
1714
  )
1616
1715
 
1617
- if "\n" in node.value:
1618
- lines = node.value.split("\n")
1619
- parts: list[doc.DocType] = [self.text(lines[0])]
1620
- for line in lines[1:]:
1621
- parts.append(self.hard_line())
1622
- parts.append(self.text(line.lstrip()))
1623
- node.gen.doc_ir = self.group(self.concat(parts))
1624
- return
1625
1716
  if is_escaped_curly:
1626
1717
  node.gen.doc_ir = self.concat(
1627
1718
  [self.text(node.value), self.text(node.value)]
@@ -1663,3 +1754,111 @@ class DocIRGenPass(UniPass):
1663
1754
  node.gen.doc_ir = self.group(
1664
1755
  self.concat([self.text(node.value), self.hard_line()])
1665
1756
  )
1757
+
1758
+ def exit_jsx_element(self, node: uni.JsxElement) -> None:
1759
+ """Generate DocIR for JSX elements - kid-centric beautiful formatting!"""
1760
+ parts: list[doc.DocType] = []
1761
+ prev = None
1762
+
1763
+ # Check if we have any JSX element children
1764
+ # Use node.children instead of node.kid to avoid counting opening/closing tags
1765
+ has_jsx_elem_children = any(
1766
+ isinstance(k, uni.JsxElement) for k in node.children
1767
+ )
1768
+
1769
+ # Only break/indent if we have JSX element children
1770
+ # (simple text/expression children stay inline)
1771
+ should_format_children = has_jsx_elem_children
1772
+
1773
+ for i in node.kid:
1774
+ # Add line break between attributes (allows them to wrap nicely)
1775
+ if (
1776
+ prev
1777
+ and isinstance(prev, (uni.JsxElementName, uni.JsxAttribute))
1778
+ and isinstance(i, uni.JsxAttribute)
1779
+ ):
1780
+ parts.append(self.line())
1781
+ # Add hard line between JSX element children, or before first child
1782
+ elif (
1783
+ prev
1784
+ and (
1785
+ (
1786
+ isinstance(prev, (uni.JsxChild, uni.JsxElement))
1787
+ and isinstance(i, (uni.JsxChild, uni.JsxElement))
1788
+ )
1789
+ or (
1790
+ isinstance(prev, (uni.JsxElementName, uni.JsxAttribute))
1791
+ and isinstance(i, (uni.JsxChild, uni.JsxElement))
1792
+ )
1793
+ )
1794
+ and should_format_children
1795
+ ):
1796
+ parts.append(self.hard_line())
1797
+
1798
+ # Indent JSX element children, but not text/expression children
1799
+ if isinstance(i, uni.JsxElement) and should_format_children:
1800
+ parts.append(self.indent(i.gen.doc_ir))
1801
+ else:
1802
+ parts.append(i.gen.doc_ir)
1803
+
1804
+ prev = i
1805
+
1806
+ node.gen.doc_ir = self.group(self.concat(parts))
1807
+
1808
+ def exit_jsx_element_name(self, node: uni.JsxElementName) -> None:
1809
+ """Generate DocIR for JSX element names."""
1810
+ parts: list[doc.DocType] = []
1811
+ for i in node.kid:
1812
+ parts.append(i.gen.doc_ir)
1813
+ node.gen.doc_ir = self.concat(parts)
1814
+
1815
+ def exit_jsx_spread_attribute(self, node: uni.JsxSpreadAttribute) -> None:
1816
+ """Generate DocIR for JSX spread attributes."""
1817
+ parts: list[doc.DocType] = []
1818
+ for i in node.kid:
1819
+ parts.append(i.gen.doc_ir)
1820
+ node.gen.doc_ir = self.concat(parts)
1821
+
1822
+ def exit_jsx_normal_attribute(self, node: uni.JsxNormalAttribute) -> None:
1823
+ """Generate DocIR for JSX normal attributes."""
1824
+ # Normalize to ensure LBRACE/RBRACE tokens are added for expression values
1825
+ node.normalize()
1826
+ parts: list[doc.DocType] = []
1827
+ for i in node.kid:
1828
+ # Tokens created by normalize() have empty doc_ir, so regenerate it
1829
+ if (
1830
+ isinstance(i, uni.Token)
1831
+ and isinstance(i.gen.doc_ir, doc.Text)
1832
+ and not i.gen.doc_ir.text
1833
+ ):
1834
+ i.gen.doc_ir = self.text(i.value)
1835
+ elif not isinstance(
1836
+ i.gen.doc_ir,
1837
+ (
1838
+ doc.Text,
1839
+ doc.Concat,
1840
+ doc.Group,
1841
+ doc.Indent,
1842
+ doc.Line,
1843
+ doc.Align,
1844
+ doc.IfBreak,
1845
+ ),
1846
+ ):
1847
+ # For nodes with invalid doc_ir, generate it by visiting
1848
+ self.enter_exit(i)
1849
+ parts.append(i.gen.doc_ir)
1850
+ node.gen.doc_ir = self.concat(parts)
1851
+
1852
+ def exit_jsx_text(self, node: uni.JsxText) -> None:
1853
+ """Generate DocIR for JSX text."""
1854
+ parts: list[doc.DocType] = []
1855
+ for i in node.kid:
1856
+ parts.append(i.gen.doc_ir)
1857
+ node.gen.doc_ir = self.concat(parts)
1858
+
1859
+ def exit_jsx_expression(self, node: uni.JsxExpression) -> None:
1860
+ """Generate DocIR for JSX expressions."""
1861
+ parts: list[doc.DocType] = []
1862
+ for i in node.kid:
1863
+ parts.append(i.gen.doc_ir)
1864
+ node.gen.doc_ir = self.concat(parts)
@@ -41,19 +41,9 @@ class FuseCommentsPass(UniPass):
41
41
  except StopIteration:
42
42
  next_comment = None
43
43
 
44
- # Handle possible leading comments
45
- if next_comment and (not next_code or _is_before(next_comment, next_code)):
46
- self.ir_out.src_terminals.insert(0, next_comment)
47
-
48
44
  # Merge streams in order
49
45
  while next_comment or next_code:
50
46
  if next_comment and (not next_code or _is_before(next_comment, next_code)):
51
- # Add comment token
52
- if merged and (last_token := merged[-1]):
53
- self.ir_out.src_terminals.insert(
54
- self.ir_out.src_terminals.index(last_token) + 1,
55
- next_comment,
56
- )
57
47
  merged.append(next_comment)
58
48
  try:
59
49
  next_comment = next(comments)
@@ -66,6 +56,7 @@ class FuseCommentsPass(UniPass):
66
56
  next_code = next(code_tokens)
67
57
  except StopIteration:
68
58
  next_code = None
59
+ self.ir_out.src_terminals[:] = merged
69
60
 
70
61
  return merged
71
62
 
@@ -51,7 +51,7 @@ class JacFormatPass(Transform[uni.Module, uni.Module]):
51
51
 
52
52
  if isinstance(cur, doc.Text):
53
53
  remaining -= len(cur.text)
54
- if remaining < 0:
54
+ if remaining <= 0:
55
55
  return False
56
56
 
57
57
  elif isinstance(cur, doc.Line):
@@ -63,7 +63,7 @@ class JacFormatPass(Transform[uni.Module, uni.Module]):
63
63
  continue
64
64
  # regular soft line becomes a single space in flat mode
65
65
  remaining -= 1
66
- if remaining < 0:
66
+ if remaining <= 0:
67
67
  return False
68
68
 
69
69
  # --- Structural nodes (walk children in LIFO) ---
@@ -1,6 +1,12 @@
1
1
  import from datetime { datetime }
2
2
  import from uuid { UUID, uuid4 }
3
3
  import from jaclang.compiler.constant { EdgeDir }
4
- import from jaclang.plugin { Archetype, ArchetypeProtocol, DSFunc, AbsRootHook, hookimpl }
4
+ import from jaclang.plugin {
5
+ Archetype,
6
+ ArchetypeProtocol,
7
+ DSFunc,
8
+ AbsRootHook,
9
+ hookimpl
10
+ }
5
11
  import math;
6
12
  import numpy as np;