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
@@ -18,6 +18,8 @@ from typing import (
18
18
  Sequence,
19
19
  Type,
20
20
  TypeVar,
21
+ Union,
22
+ cast,
21
23
  )
22
24
 
23
25
 
@@ -227,8 +229,12 @@ class UniNode:
227
229
  return res
228
230
 
229
231
 
230
- # Symbols can have mulitple definitions but resolves decl to be the
232
+ # Symbols can have multiple definitions but resolves decl to be the
231
233
  # first such definition in a given scope.
234
+ # Symbols may exist without a parent symbol table (parent_tab=None),
235
+ # such as imports like 'from a.b.c {...}' or with alias.
236
+ # These symbols(a,b,c) are not inserted into symbol tables
237
+ # but are still used for go-to-def, find references, and rename.
232
238
  class Symbol:
233
239
  """Symbol."""
234
240
 
@@ -236,7 +242,7 @@ class Symbol:
236
242
  self,
237
243
  defn: NameAtom,
238
244
  access: SymbolAccess,
239
- parent_tab: UniScopeNode,
245
+ parent_tab: Optional[UniScopeNode] = None,
240
246
  imported: bool = False,
241
247
  ) -> None:
242
248
  """Initialize."""
@@ -277,7 +283,9 @@ class Symbol:
277
283
  @property
278
284
  def symbol_table(self) -> Optional[UniScopeNode]:
279
285
  """Get symbol table."""
280
- return self.parent_tab.find_scope(self.sym_name)
286
+ if self.parent_tab:
287
+ return self.parent_tab.find_scope(self.sym_name)
288
+ return None
281
289
 
282
290
  def add_defn(self, node: NameAtom) -> None:
283
291
  """Add defn."""
@@ -345,8 +353,7 @@ class UniScopeNode(UniNode):
345
353
  else None
346
354
  )
347
355
  if force_overwrite or node.sym_name not in self.names_in_scope:
348
- self.names_in_scope[node.sym_name] = Symbol(
349
- defn=node.name_spec,
356
+ self.names_in_scope[node.sym_name] = node.name_spec.create_symbol(
350
357
  access=(
351
358
  access_spec
352
359
  if isinstance(access_spec, SymbolAccess)
@@ -570,6 +577,13 @@ class AstAccessNode(UniNode):
570
577
  T = TypeVar("T", bound=UniNode)
571
578
 
572
579
 
580
+ class ClientFacingNode:
581
+ """Mixin for nodes that can be marked as client-facing declarations."""
582
+
583
+ def __init__(self, is_client_decl: bool = False) -> None:
584
+ self.is_client_decl = is_client_decl
585
+
586
+
573
587
  class AstDocNode(UniNode):
574
588
  """Nodes that have access."""
575
589
 
@@ -767,6 +781,16 @@ class NameAtom(AtomExpr, EnumBlockStmt):
767
781
  def sym_category(self) -> SymbolType:
768
782
  return self._sym_category
769
783
 
784
+ def create_symbol(
785
+ self,
786
+ access: SymbolAccess,
787
+ parent_tab: Optional[UniScopeNode] = None,
788
+ imported: bool = False,
789
+ ) -> Symbol:
790
+ """Create symbol."""
791
+ sym = Symbol(defn=self, access=access, parent_tab=parent_tab, imported=imported)
792
+ return sym
793
+
770
794
  @property
771
795
  def clean_type(self) -> str:
772
796
  ret_type = self.expr_type.replace("builtins.", "").replace("NoType", "")
@@ -890,6 +914,7 @@ class Module(AstDocNode, UniScopeNode):
890
914
  if not self.stub_only and (
891
915
  self.loc.mod_path.endswith(".impl.jac")
892
916
  or self.loc.mod_path.endswith(".test.jac")
917
+ or self.loc.mod_path.endswith(".cl.jac")
893
918
  ):
894
919
  head_mod_name = self.name.split(".")[0]
895
920
  potential_path = os.path.join(
@@ -899,7 +924,11 @@ class Module(AstDocNode, UniScopeNode):
899
924
  if os.path.exists(potential_path) and potential_path != self.loc.mod_path:
900
925
  return potential_path
901
926
  annex_dir = os.path.split(os.path.dirname(self.loc.mod_path))[-1]
902
- if annex_dir.endswith(".impl") or annex_dir.endswith(".test"):
927
+ if (
928
+ annex_dir.endswith(".impl")
929
+ or annex_dir.endswith(".test")
930
+ or annex_dir.endswith(".cl")
931
+ ):
903
932
  head_mod_name = os.path.split(os.path.dirname(self.loc.mod_path))[
904
933
  -1
905
934
  ].split(".")[0]
@@ -987,7 +1016,7 @@ class ProgramModule(UniNode):
987
1016
  self.hub: dict[str, Module] = {self.loc.mod_path: main_mod} if main_mod else {}
988
1017
 
989
1018
 
990
- class GlobalVars(ElementStmt, AstAccessNode):
1019
+ class GlobalVars(ClientFacingNode, ElementStmt, AstAccessNode):
991
1020
  """GlobalVars node type for Jac Ast."""
992
1021
 
993
1022
  def __init__(
@@ -1003,6 +1032,7 @@ class GlobalVars(ElementStmt, AstAccessNode):
1003
1032
  UniNode.__init__(self, kid=kid)
1004
1033
  AstAccessNode.__init__(self, access=access)
1005
1034
  AstDocNode.__init__(self, doc=doc)
1035
+ ClientFacingNode.__init__(self)
1006
1036
 
1007
1037
  def normalize(self, deep: bool = False) -> bool:
1008
1038
  res = True
@@ -1014,6 +1044,8 @@ class GlobalVars(ElementStmt, AstAccessNode):
1014
1044
  new_kid: list[UniNode] = []
1015
1045
  if self.doc:
1016
1046
  new_kid.append(self.doc)
1047
+ if self.is_client_decl:
1048
+ new_kid.append(self.gen_token(Tok.KW_CLIENT))
1017
1049
  if self.is_frozen:
1018
1050
  new_kid.append(self.gen_token(Tok.KW_LET))
1019
1051
  else:
@@ -1028,7 +1060,7 @@ class GlobalVars(ElementStmt, AstAccessNode):
1028
1060
  return res
1029
1061
 
1030
1062
 
1031
- class Test(AstSymbolNode, ElementStmt, UniScopeNode):
1063
+ class Test(ClientFacingNode, AstSymbolNode, ElementStmt, UniScopeNode):
1032
1064
  """Test node type for Jac Ast."""
1033
1065
 
1034
1066
  TEST_COUNT = 0
@@ -1074,6 +1106,7 @@ class Test(AstSymbolNode, ElementStmt, UniScopeNode):
1074
1106
  )
1075
1107
  AstDocNode.__init__(self, doc=doc)
1076
1108
  UniScopeNode.__init__(self, name=self.sym_name)
1109
+ ClientFacingNode.__init__(self)
1077
1110
 
1078
1111
  def normalize(self, deep: bool = False) -> bool:
1079
1112
  res = True
@@ -1085,6 +1118,8 @@ class Test(AstSymbolNode, ElementStmt, UniScopeNode):
1085
1118
  new_kid: list[UniNode] = []
1086
1119
  if self.doc:
1087
1120
  new_kid.append(self.doc)
1121
+ if self.is_client_decl:
1122
+ new_kid.append(self.gen_token(Tok.KW_CLIENT))
1088
1123
  new_kid.append(self.gen_token(Tok.KW_TEST))
1089
1124
  new_kid.append(self.name)
1090
1125
  new_kid.append(self.gen_token(Tok.LBRACE))
@@ -1095,7 +1130,7 @@ class Test(AstSymbolNode, ElementStmt, UniScopeNode):
1095
1130
  return res
1096
1131
 
1097
1132
 
1098
- class ModuleCode(ElementStmt, ArchBlockStmt, EnumBlockStmt):
1133
+ class ModuleCode(ClientFacingNode, ElementStmt, ArchBlockStmt, EnumBlockStmt):
1099
1134
  """ModuleCode node type for Jac Ast."""
1100
1135
 
1101
1136
  def __init__(
@@ -1111,6 +1146,7 @@ class ModuleCode(ElementStmt, ArchBlockStmt, EnumBlockStmt):
1111
1146
  UniNode.__init__(self, kid=kid)
1112
1147
  AstDocNode.__init__(self, doc=doc)
1113
1148
  EnumBlockStmt.__init__(self, is_enum_stmt=is_enum_stmt)
1149
+ ClientFacingNode.__init__(self)
1114
1150
 
1115
1151
  def normalize(self, deep: bool = False) -> bool:
1116
1152
  res = True
@@ -1122,6 +1158,8 @@ class ModuleCode(ElementStmt, ArchBlockStmt, EnumBlockStmt):
1122
1158
  new_kid: list[UniNode] = []
1123
1159
  if self.doc:
1124
1160
  new_kid.append(self.doc)
1161
+ if self.is_client_decl:
1162
+ new_kid.append(self.gen_token(Tok.KW_CLIENT))
1125
1163
  new_kid.append(self.gen_token(Tok.KW_WITH))
1126
1164
  new_kid.append(self.gen_token(Tok.KW_ENTRY))
1127
1165
  if self.name:
@@ -1164,7 +1202,7 @@ class PyInlineCode(ElementStmt, ArchBlockStmt, EnumBlockStmt, CodeBlockStmt):
1164
1202
  return res
1165
1203
 
1166
1204
 
1167
- class Import(ElementStmt, CodeBlockStmt):
1205
+ class Import(ClientFacingNode, ElementStmt, CodeBlockStmt):
1168
1206
  """Import node type for Jac Ast."""
1169
1207
 
1170
1208
  def __init__(
@@ -1182,6 +1220,7 @@ class Import(ElementStmt, CodeBlockStmt):
1182
1220
  UniNode.__init__(self, kid=kid)
1183
1221
  AstDocNode.__init__(self, doc=doc)
1184
1222
  CodeBlockStmt.__init__(self)
1223
+ ClientFacingNode.__init__(self)
1185
1224
 
1186
1225
  @property
1187
1226
  def is_py(self) -> bool:
@@ -1235,6 +1274,8 @@ class Import(ElementStmt, CodeBlockStmt):
1235
1274
  new_kid: list[UniNode] = []
1236
1275
  if self.doc:
1237
1276
  new_kid.append(self.doc)
1277
+ if self.is_client_decl:
1278
+ new_kid.append(self.gen_token(Tok.KW_CLIENT))
1238
1279
  if self.is_absorb:
1239
1280
  new_kid.append(self.gen_token(Tok.KW_INCLUDE))
1240
1281
  else:
@@ -1255,7 +1296,7 @@ class Import(ElementStmt, CodeBlockStmt):
1255
1296
  return res
1256
1297
 
1257
1298
 
1258
- class ModulePath(AstSymbolNode):
1299
+ class ModulePath(UniNode):
1259
1300
  """ModulePath node type for Jac Ast."""
1260
1301
 
1261
1302
  def __init__(
@@ -1269,33 +1310,36 @@ class ModulePath(AstSymbolNode):
1269
1310
  self.level = level
1270
1311
  self.alias = alias
1271
1312
  self.abs_path: Optional[str] = None
1272
-
1273
- name_spec = alias if alias else path[0] if path else None
1274
-
1275
1313
  UniNode.__init__(self, kid=kid)
1276
- if not name_spec:
1277
- pkg_name = self.loc.mod_path
1278
- for _ in range(self.level):
1279
- pkg_name = os.path.dirname(pkg_name)
1280
- pkg_name = pkg_name.split(os.sep)[-1]
1281
- name_spec = Name.gen_stub_from_node(self, pkg_name)
1282
- self.level += 1
1283
- if not isinstance(name_spec, Name):
1284
- raise ValueError("ModulePath should have a name spec. Impossible.")
1285
- AstSymbolNode.__init__(
1286
- self,
1287
- sym_name=name_spec.sym_name,
1288
- name_spec=name_spec,
1289
- sym_category=SymbolType.MODULE,
1290
- )
1314
+
1315
+ @property
1316
+ def is_import_from(self) -> bool:
1317
+ """Check if this modulepath is from import."""
1318
+ if self.parent and isinstance(self.parent, Import):
1319
+ return self.parent.from_loc == self
1320
+ return False
1291
1321
 
1292
1322
  @property
1293
1323
  def dot_path_str(self) -> str:
1294
1324
  """Get path string."""
1295
1325
  return ("." * self.level) + ".".join(
1296
- [p.value for p in self.path] if self.path else [self.name_spec.sym_name]
1326
+ [p.value for p in self.path] if self.path else []
1297
1327
  )
1298
1328
 
1329
+ def resolve_relative_path(self, target_item: Optional[str] = None) -> str:
1330
+ """Convert an import target string into a relative file path."""
1331
+ target = self.dot_path_str + (f".{target_item}" if target_item else "")
1332
+ return resolve_relative_path(target, self.loc.mod_path)
1333
+
1334
+ def resolve_relative_path_list(self) -> list[str]:
1335
+ """Convert an import target string into a relative file path."""
1336
+ parts = self.dot_path_str.split(".")
1337
+ paths = []
1338
+ for i in range(len(parts)):
1339
+ sub_path = ".".join(parts[: i + 1])
1340
+ paths.append(resolve_relative_path(sub_path, self.loc.mod_path))
1341
+ return paths
1342
+
1299
1343
  def normalize(self, deep: bool = False) -> bool:
1300
1344
  res = True
1301
1345
  if deep:
@@ -1317,13 +1361,8 @@ class ModulePath(AstSymbolNode):
1317
1361
  self.set_kids(nodes=new_kid)
1318
1362
  return res
1319
1363
 
1320
- def resolve_relative_path(self, target_item: Optional[str] = None) -> str:
1321
- """Convert an import target string into a relative file path."""
1322
- target = self.dot_path_str + (f".{target_item}" if target_item else "")
1323
- return resolve_relative_path(target, self.loc.mod_path)
1324
-
1325
1364
 
1326
- class ModuleItem(AstSymbolNode):
1365
+ class ModuleItem(UniNode):
1327
1366
  """ModuleItem node type for Jac Ast."""
1328
1367
 
1329
1368
  def __init__(
@@ -1335,12 +1374,6 @@ class ModuleItem(AstSymbolNode):
1335
1374
  self.name = name
1336
1375
  self.alias = alias
1337
1376
  UniNode.__init__(self, kid=kid)
1338
- AstSymbolNode.__init__(
1339
- self,
1340
- sym_name=alias.sym_name if alias else name.sym_name,
1341
- name_spec=alias or name,
1342
- sym_category=SymbolType.MOD_VAR,
1343
- )
1344
1377
  self.abs_path: Optional[str] = None
1345
1378
 
1346
1379
  @property
@@ -1371,6 +1404,7 @@ class ModuleItem(AstSymbolNode):
1371
1404
 
1372
1405
 
1373
1406
  class Archetype(
1407
+ ClientFacingNode,
1374
1408
  ArchSpec,
1375
1409
  AstAccessNode,
1376
1410
  ArchBlockStmt,
@@ -1412,10 +1446,10 @@ class Archetype(
1412
1446
  ArchSpec.__init__(self, decorators=decorators)
1413
1447
  UniScopeNode.__init__(self, name=self.sym_name)
1414
1448
  CodeBlockStmt.__init__(self)
1449
+ ClientFacingNode.__init__(self)
1415
1450
 
1416
- @property
1417
- def is_abstract(self) -> bool:
1418
- body = (
1451
+ def _get_impl_resolved_body(self) -> list:
1452
+ return (
1419
1453
  list(self.body)
1420
1454
  if isinstance(self.body, Sequence)
1421
1455
  else (
@@ -1425,8 +1459,23 @@ class Archetype(
1425
1459
  else []
1426
1460
  )
1427
1461
  )
1462
+
1463
+ @property
1464
+ def is_abstract(self) -> bool:
1465
+ body = self._get_impl_resolved_body()
1428
1466
  return any(isinstance(i, Ability) and i.is_abstract for i in body)
1429
1467
 
1468
+ def get_has_vars(self) -> list[HasVar]:
1469
+ body = self._get_impl_resolved_body()
1470
+ has_vars: list[HasVar] = []
1471
+ for node in body:
1472
+ if not isinstance(node, ArchHas):
1473
+ continue
1474
+ for has_ in node.vars:
1475
+ if isinstance(has_, HasVar):
1476
+ has_vars.append(has_)
1477
+ return has_vars
1478
+
1430
1479
  def normalize(self, deep: bool = False) -> bool:
1431
1480
  res = True
1432
1481
  if deep:
@@ -1448,6 +1497,8 @@ class Archetype(
1448
1497
  new_kid: list[UniNode] = []
1449
1498
  if self.doc:
1450
1499
  new_kid.append(self.doc)
1500
+ if self.is_client_decl:
1501
+ new_kid.append(self.gen_token(Tok.KW_CLIENT))
1451
1502
  if self.decorators:
1452
1503
  new_kid.append(self.gen_token(Tok.DECOR_OP))
1453
1504
  for idx, dec in enumerate(self.decorators):
@@ -1639,7 +1690,14 @@ class SemDef(ElementStmt, AstSymbolNode, UniScopeNode):
1639
1690
  return res
1640
1691
 
1641
1692
 
1642
- class Enum(ArchSpec, AstAccessNode, AstImplNeedingNode, ArchBlockStmt, UniScopeNode):
1693
+ class Enum(
1694
+ ClientFacingNode,
1695
+ ArchSpec,
1696
+ AstAccessNode,
1697
+ AstImplNeedingNode,
1698
+ ArchBlockStmt,
1699
+ UniScopeNode,
1700
+ ):
1643
1701
  """Enum node type for Jac Ast."""
1644
1702
 
1645
1703
  def __init__(
@@ -1666,6 +1724,7 @@ class Enum(ArchSpec, AstAccessNode, AstImplNeedingNode, ArchBlockStmt, UniScopeN
1666
1724
  AstDocNode.__init__(self, doc=doc)
1667
1725
  ArchSpec.__init__(self, decorators=decorators)
1668
1726
  UniScopeNode.__init__(self, name=self.sym_name)
1727
+ ClientFacingNode.__init__(self)
1669
1728
 
1670
1729
  def normalize(self, deep: bool = False) -> bool:
1671
1730
  res = True
@@ -1693,6 +1752,8 @@ class Enum(ArchSpec, AstAccessNode, AstImplNeedingNode, ArchBlockStmt, UniScopeN
1693
1752
  new_kid.append(self.gen_token(Tok.DECOR_OP))
1694
1753
  if self.doc:
1695
1754
  new_kid.append(self.doc)
1755
+ if self.is_client_decl:
1756
+ new_kid.append(self.gen_token(Tok.KW_CLIENT))
1696
1757
  new_kid.append(self.gen_token(Tok.KW_ENUM))
1697
1758
  if self.access:
1698
1759
  new_kid.append(self.access)
@@ -1723,6 +1784,7 @@ class Enum(ArchSpec, AstAccessNode, AstImplNeedingNode, ArchBlockStmt, UniScopeN
1723
1784
 
1724
1785
 
1725
1786
  class Ability(
1787
+ ClientFacingNode,
1726
1788
  AstAccessNode,
1727
1789
  ElementStmt,
1728
1790
  AstAsyncNode,
@@ -1735,7 +1797,7 @@ class Ability(
1735
1797
 
1736
1798
  def __init__(
1737
1799
  self,
1738
- name_ref: NameAtom,
1800
+ name_ref: Optional[NameAtom],
1739
1801
  is_async: bool,
1740
1802
  is_override: bool,
1741
1803
  is_static: bool,
@@ -1756,10 +1818,36 @@ class Ability(
1756
1818
 
1757
1819
  UniNode.__init__(self, kid=kid)
1758
1820
  AstImplNeedingNode.__init__(self, body=body)
1821
+
1822
+ # Create a synthetic name_ref if none provided
1823
+ if name_ref is None:
1824
+ # Extract location info from kid for positioning
1825
+ # Note: kid should always contain at least KW_CAN token from parser
1826
+ first_tok = kid[0] if kid and isinstance(kid[0], Token) else None
1827
+ if first_tok is None:
1828
+ raise ValueError(
1829
+ "Cannot create synthetic name_ref without location info."
1830
+ )
1831
+ synthetic_name_ref = Name(
1832
+ orig_src=first_tok.orig_src,
1833
+ name=Tok.NAME,
1834
+ value=self.py_resolve_name(),
1835
+ line=first_tok.line_no,
1836
+ end_line=first_tok.end_line,
1837
+ col_start=first_tok.c_start,
1838
+ col_end=first_tok.c_end,
1839
+ pos_start=first_tok.pos_start,
1840
+ pos_end=first_tok.pos_end,
1841
+ is_enum_stmt=False,
1842
+ )
1843
+ name_spec_for_init: Name | NameAtom = synthetic_name_ref
1844
+ else:
1845
+ name_spec_for_init = name_ref
1846
+
1759
1847
  AstSymbolNode.__init__(
1760
1848
  self,
1761
1849
  sym_name=self.py_resolve_name(),
1762
- name_spec=name_ref,
1850
+ name_spec=name_spec_for_init,
1763
1851
  sym_category=SymbolType.ABILITY,
1764
1852
  )
1765
1853
  AstAccessNode.__init__(self, access=access)
@@ -1767,6 +1855,7 @@ class Ability(
1767
1855
  AstAsyncNode.__init__(self, is_async=is_async)
1768
1856
  UniScopeNode.__init__(self, name=self.sym_name)
1769
1857
  CodeBlockStmt.__init__(self)
1858
+ ClientFacingNode.__init__(self)
1770
1859
 
1771
1860
  @property
1772
1861
  def is_method(self) -> bool:
@@ -1812,7 +1901,18 @@ class Ability(
1812
1901
  return mn, mx
1813
1902
 
1814
1903
  def py_resolve_name(self) -> str:
1815
- if isinstance(self.name_ref, Name):
1904
+ if self.name_ref is None:
1905
+ # Generate anonymous name based on event type and location
1906
+ event_type = (
1907
+ "entry"
1908
+ if isinstance(self.signature, EventSignature)
1909
+ and self.signature.event.name == Tok.KW_ENTRY
1910
+ else "exit"
1911
+ )
1912
+ return (
1913
+ f"__ability_{event_type}_{self.loc.first_line}_{self.loc.col_start}__"
1914
+ )
1915
+ elif isinstance(self.name_ref, Name):
1816
1916
  return self.name_ref.value
1817
1917
  elif isinstance(self.name_ref, SpecialVarRef):
1818
1918
  return self.name_ref.py_resolve_name()
@@ -1822,7 +1922,7 @@ class Ability(
1822
1922
  def normalize(self, deep: bool = False) -> bool:
1823
1923
  res = True
1824
1924
  if deep:
1825
- res = self.name_ref.normalize(deep)
1925
+ res = self.name_ref.normalize(deep) if self.name_ref else res
1826
1926
  res = res and self.access.normalize(deep) if self.access else res
1827
1927
  res = res and self.signature.normalize(deep) if self.signature else res
1828
1928
  if isinstance(self.body, ImplDef):
@@ -1838,6 +1938,8 @@ class Ability(
1838
1938
  new_kid: list[UniNode] = []
1839
1939
  if self.doc:
1840
1940
  new_kid.append(self.doc)
1941
+ if self.is_client_decl:
1942
+ new_kid.append(self.gen_token(Tok.KW_CLIENT))
1841
1943
  if self.decorators:
1842
1944
  new_kid.append(self.gen_token(Tok.DECOR_OP))
1843
1945
  for idx, dec in enumerate(self.decorators):
@@ -1858,7 +1960,8 @@ class Ability(
1858
1960
  )
1859
1961
  if self.access:
1860
1962
  new_kid.append(self.access)
1861
- new_kid.append(self.name_ref)
1963
+ if self.name_ref:
1964
+ new_kid.append(self.name_ref)
1862
1965
  if self.signature:
1863
1966
  new_kid.append(self.signature)
1864
1967
  if self.is_genai_ability:
@@ -1940,6 +2043,8 @@ class FuncSignature(UniNode):
1940
2043
  new_kid = new_kid[:-1]
1941
2044
  if not is_lambda:
1942
2045
  new_kid.append(self.gen_token(Tok.RPAREN))
2046
+ elif not new_kid:
2047
+ new_kid.extend([self.gen_token(Tok.LPAREN), self.gen_token(Tok.RPAREN)])
1943
2048
  if self.return_type:
1944
2049
  new_kid.append(self.gen_token(Tok.RETURN_HINT))
1945
2050
  new_kid.append(self.return_type)
@@ -2960,6 +3065,8 @@ class Assignment(AstTypedVarNode, EnumBlockStmt, CodeBlockStmt):
2960
3065
  res = res and self.type_tag.normalize(deep) if self.type_tag else res
2961
3066
  res = res and self.aug_op.normalize(deep) if self.aug_op else res
2962
3067
  new_kid: list[UniNode] = []
3068
+ if self.mutable and not self.is_enum_stmt:
3069
+ new_kid.append(self.gen_token(Tok.KW_LET))
2963
3070
  for idx, targ in enumerate(self.target):
2964
3071
  new_kid.append(targ)
2965
3072
  if idx < len(self.target) - 1:
@@ -3106,12 +3213,15 @@ class LambdaExpr(Expr, UniScopeNode):
3106
3213
 
3107
3214
  def __init__(
3108
3215
  self,
3109
- body: Expr,
3216
+ body: Union[Expr, Sequence[CodeBlockStmt]],
3110
3217
  kid: Sequence[UniNode],
3111
3218
  signature: Optional[FuncSignature] = None,
3112
3219
  ) -> None:
3113
3220
  self.signature = signature
3114
- self.body = body
3221
+ if isinstance(body, Sequence) and not isinstance(body, Expr):
3222
+ self.body: Expr | Sequence[CodeBlockStmt] = list(body)
3223
+ else:
3224
+ self.body = cast(Expr, body)
3115
3225
  UniNode.__init__(self, kid=kid)
3116
3226
  Expr.__init__(self)
3117
3227
  UniScopeNode.__init__(self, name=f"{self.__class__.__name__}")
@@ -3120,11 +3230,21 @@ class LambdaExpr(Expr, UniScopeNode):
3120
3230
  res = True
3121
3231
  if deep:
3122
3232
  res = self.signature.normalize(deep) if self.signature else res
3123
- res = res and self.body.normalize(deep)
3233
+ if isinstance(self.body, list):
3234
+ for stmt in self.body:
3235
+ res = res and stmt.normalize(deep)
3236
+ elif isinstance(self.body, Expr):
3237
+ res = res and self.body.normalize(deep)
3124
3238
  new_kid: list[UniNode] = [self.gen_token(Tok.KW_LAMBDA)]
3125
3239
  if self.signature:
3126
3240
  new_kid.append(self.signature)
3127
- new_kid += [self.gen_token(Tok.COLON), self.body]
3241
+ # For code block lambdas, we add LBRACE, statements, RBRACE
3242
+ if isinstance(self.body, list):
3243
+ new_kid.append(self.gen_token(Tok.LBRACE))
3244
+ new_kid.extend(self.body)
3245
+ new_kid.append(self.gen_token(Tok.RBRACE))
3246
+ elif isinstance(self.body, Expr):
3247
+ new_kid += [self.gen_token(Tok.COLON), self.body]
3128
3248
  self.set_kids(nodes=new_kid)
3129
3249
  return res
3130
3250
 
@@ -3216,10 +3336,14 @@ class FString(AtomExpr):
3216
3336
 
3217
3337
  def __init__(
3218
3338
  self,
3219
- parts: Sequence[String | ExprStmt],
3339
+ start: Optional[Token],
3340
+ parts: Sequence[String | FormattedValue],
3341
+ end: Optional[Token],
3220
3342
  kid: Sequence[UniNode],
3221
3343
  ) -> None:
3222
- self.parts: list[String | ExprStmt] = list(parts)
3344
+ self.start = start
3345
+ self.parts: list[String | FormattedValue] = list(parts)
3346
+ self.end = end
3223
3347
  UniNode.__init__(self, kid=kid)
3224
3348
  Expr.__init__(self)
3225
3349
  AstSymbolStubNode.__init__(self, sym_type=SymbolType.STRING)
@@ -3229,33 +3353,53 @@ class FString(AtomExpr):
3229
3353
  if deep:
3230
3354
  for part in self.parts:
3231
3355
  res = res and part.normalize(deep)
3232
- new_kid: list[UniNode] = []
3233
- # Determine quote type from first token
3234
- start_token = self.kid[0] if isinstance(self.kid[0], Token) else None
3235
- if start_token:
3236
- if start_token.name == Tok.FSTR_SQ_TRIPLE_START:
3237
- start_tok, end_tok = Tok.FSTR_SQ_TRIPLE_START, Tok.FSTR_SQ_TRIPLE_END
3238
- elif start_token.name == Tok.FSTR_TRIPLE_START:
3239
- start_tok, end_tok = Tok.FSTR_TRIPLE_START, Tok.FSTR_TRIPLE_END
3240
- elif start_token.name == Tok.FSTR_SQ_START:
3241
- start_tok, end_tok = Tok.FSTR_SQ_START, Tok.FSTR_SQ_END
3242
- else:
3243
- start_tok, end_tok = Tok.FSTR_START, Tok.FSTR_END
3244
- else:
3245
- start_tok, end_tok = Tok.FSTR_START, Tok.FSTR_END
3356
+ new_kid: list[UniNode] = (
3357
+ [self.gen_token(self.start)] if self.start is not None else []
3358
+ )
3359
+ for part in self.parts:
3360
+ new_kid.append(part)
3361
+ if self.end is not None:
3362
+ new_kid.append(self.gen_token(self.end))
3363
+ self.set_kids(nodes=new_kid)
3364
+ return res
3246
3365
 
3247
- new_kid.append(self.gen_token(start_tok))
3248
- for i in self.parts:
3249
- if isinstance(i, String):
3250
- i.value = (
3251
- "{{" if i.value == "{" else "}}" if i.value == "}" else i.value
3252
- )
3253
- new_kid.append(i)
3254
- else:
3255
- new_kid.append(self.gen_token(Tok.LBRACE))
3256
- new_kid.append(i)
3257
- new_kid.append(self.gen_token(Tok.RBRACE))
3258
- new_kid.append(self.gen_token(end_tok))
3366
+ def unparse(self) -> str:
3367
+ valid = self.normalize()
3368
+ res = "".join([i.unparse() for i in self.kid])
3369
+ if not valid:
3370
+ raise NotImplementedError(f"Node {type(self).__name__} is not valid.")
3371
+ return res
3372
+
3373
+
3374
+ class FormattedValue(Expr):
3375
+ """FormattedValue node type for Jac Ast."""
3376
+
3377
+ def __init__(
3378
+ self,
3379
+ format_part: Expr,
3380
+ conversion: int,
3381
+ format_spec: Expr | None,
3382
+ kid: Sequence[UniNode],
3383
+ ) -> None:
3384
+ self.format_part: Expr = format_part
3385
+ self.conversion: int = conversion
3386
+ self.format_spec: Expr | None = format_spec
3387
+ UniNode.__init__(self, kid=kid)
3388
+ Expr.__init__(self)
3389
+
3390
+ def normalize(self, deep: bool = False) -> bool:
3391
+ res = True
3392
+ if deep:
3393
+ res = self.format_part.normalize(deep)
3394
+ res = res and self.format_spec.normalize(deep) if self.format_spec else res
3395
+ new_kid: list[UniNode] = [self.gen_token(Tok.LBRACE)]
3396
+ new_kid.append(self.format_part)
3397
+ if self.conversion != -1:
3398
+ new_kid.append(self.gen_token(Tok.CONV, value="!" + chr(self.conversion)))
3399
+ if self.format_spec:
3400
+ new_kid.append(self.gen_token(Tok.COLON))
3401
+ new_kid.append(self.format_spec)
3402
+ new_kid.append(self.gen_token(Tok.RBRACE))
3259
3403
  self.set_kids(nodes=new_kid)
3260
3404
  return res
3261
3405
 
@@ -3649,7 +3793,7 @@ class AtomUnit(Expr):
3649
3793
 
3650
3794
  def __init__(
3651
3795
  self,
3652
- value: Expr | YieldExpr,
3796
+ value: Expr | YieldExpr | Ability,
3653
3797
  kid: Sequence[UniNode],
3654
3798
  ) -> None:
3655
3799
  self.value = value
@@ -4050,6 +4194,206 @@ class AssignCompr(AtomExpr):
4050
4194
  return res
4051
4195
 
4052
4196
 
4197
+ # JSX Nodes
4198
+ # ---------
4199
+
4200
+
4201
+ class JsxElement(AtomExpr):
4202
+ """JsxElement node type for Jac Ast."""
4203
+
4204
+ def __init__(
4205
+ self,
4206
+ name: Optional["JsxElementName"],
4207
+ attributes: Optional[Sequence["JsxAttribute"]],
4208
+ children: Optional[Sequence["JsxChild"]],
4209
+ is_self_closing: bool,
4210
+ is_fragment: bool,
4211
+ kid: Sequence[UniNode],
4212
+ ) -> None:
4213
+ self.name = name
4214
+ self.attributes = list(attributes) if attributes else []
4215
+ self.children = list(children) if children else []
4216
+ self.is_self_closing = is_self_closing
4217
+ self.is_fragment = is_fragment
4218
+ UniNode.__init__(self, kid=kid)
4219
+ Expr.__init__(self)
4220
+ AstSymbolStubNode.__init__(self, sym_type=SymbolType.OBJECT_ARCH)
4221
+
4222
+ def normalize(self, deep: bool = False) -> bool:
4223
+ """Normalize JSX element by recursively normalizing children.
4224
+
4225
+ Unlike most normalize methods, JSX elements don't need to rebuild
4226
+ their kid structure since the parser already creates it correctly.
4227
+ We just need to normalize child nodes if deep=True.
4228
+ """
4229
+ res = True
4230
+ if deep:
4231
+ if self.name:
4232
+ res = res and self.name.normalize(deep)
4233
+ if self.attributes:
4234
+ for attr in self.attributes:
4235
+ res = res and attr.normalize(deep)
4236
+ if self.children:
4237
+ for child in self.children:
4238
+ res = res and child.normalize(deep)
4239
+ return res
4240
+
4241
+
4242
+ class JsxElementName(UniNode):
4243
+ """JsxElementName node type for Jac Ast."""
4244
+
4245
+ def __init__(
4246
+ self,
4247
+ parts: Sequence[Name],
4248
+ kid: Sequence[UniNode],
4249
+ ) -> None:
4250
+ self.parts = list(parts)
4251
+ UniNode.__init__(self, kid=kid)
4252
+
4253
+ def normalize(self, deep: bool = False) -> bool:
4254
+ res = True
4255
+ if deep:
4256
+ for part in self.parts:
4257
+ res = res and part.normalize(deep)
4258
+ new_kid: list[UniNode] = []
4259
+ for i, part in enumerate(self.parts):
4260
+ new_kid.append(part)
4261
+ if i < len(self.parts) - 1:
4262
+ new_kid.append(self.gen_token(Tok.DOT))
4263
+ self.set_kids(nodes=new_kid)
4264
+ return res
4265
+
4266
+
4267
+ class JsxAttribute(UniNode):
4268
+ """JsxAttribute node type for Jac Ast (base class)."""
4269
+
4270
+ def __init__(self, kid: Sequence[UniNode]) -> None:
4271
+ UniNode.__init__(self, kid=kid)
4272
+
4273
+ def normalize(self, deep: bool = False) -> bool:
4274
+ """Normalize the node (base implementation)."""
4275
+ # Base class normalize - subclasses should override if needed
4276
+ return True
4277
+
4278
+
4279
+ class JsxSpreadAttribute(JsxAttribute):
4280
+ """JsxSpreadAttribute node type for Jac Ast."""
4281
+
4282
+ def __init__(
4283
+ self,
4284
+ expr: Expr,
4285
+ kid: Sequence[UniNode],
4286
+ ) -> None:
4287
+ self.expr = expr
4288
+ JsxAttribute.__init__(self, kid=kid)
4289
+
4290
+ def normalize(self, deep: bool = False) -> bool:
4291
+ res = True
4292
+ if deep:
4293
+ res = self.expr.normalize(deep)
4294
+ new_kid: list[UniNode] = [
4295
+ self.gen_token(Tok.LBRACE),
4296
+ self.gen_token(Tok.ELLIPSIS),
4297
+ self.expr,
4298
+ self.gen_token(Tok.RBRACE),
4299
+ ]
4300
+ self.set_kids(nodes=new_kid)
4301
+ return res
4302
+
4303
+
4304
+ class JsxNormalAttribute(JsxAttribute):
4305
+ """JsxNormalAttribute node type for Jac Ast."""
4306
+
4307
+ def __init__(
4308
+ self,
4309
+ name: Name,
4310
+ value: Optional[Union[String, Expr]],
4311
+ kid: Sequence[UniNode],
4312
+ ) -> None:
4313
+ self.name = name
4314
+ self.value = value
4315
+ JsxAttribute.__init__(self, kid=kid)
4316
+
4317
+ def normalize(self, deep: bool = False) -> bool:
4318
+ res = True
4319
+ if deep:
4320
+ res = self.name.normalize(deep)
4321
+ if self.value:
4322
+ res = res and self.value.normalize(deep)
4323
+ new_kid: list[UniNode] = [self.name]
4324
+ if self.value:
4325
+ new_kid.append(self.gen_token(Tok.EQ))
4326
+ if isinstance(self.value, String):
4327
+ new_kid.append(self.value)
4328
+ else: # Expression in braces
4329
+ new_kid.extend(
4330
+ [
4331
+ self.gen_token(Tok.LBRACE),
4332
+ self.value,
4333
+ self.gen_token(Tok.RBRACE),
4334
+ ]
4335
+ )
4336
+ self.set_kids(nodes=new_kid)
4337
+ return res
4338
+
4339
+
4340
+ class JsxChild(UniNode):
4341
+ """JsxChild node type for Jac Ast (base class)."""
4342
+
4343
+ def __init__(self, kid: Sequence[UniNode]) -> None:
4344
+ UniNode.__init__(self, kid=kid)
4345
+
4346
+ def normalize(self, deep: bool = False) -> bool:
4347
+ """Normalize the node (base implementation)."""
4348
+ # Base class normalize - subclasses should override if needed
4349
+ return True
4350
+
4351
+
4352
+ class JsxText(JsxChild):
4353
+ """JsxText node type for Jac Ast."""
4354
+
4355
+ def __init__(
4356
+ self,
4357
+ value: str,
4358
+ kid: Sequence[UniNode],
4359
+ ) -> None:
4360
+ self.value = value
4361
+ JsxChild.__init__(self, kid=kid)
4362
+
4363
+ def normalize(self, deep: bool = False) -> bool:
4364
+ # JSX text is represented as a token
4365
+ if isinstance(self.value, Token):
4366
+ new_kid: list[UniNode] = [self.value]
4367
+ else:
4368
+ new_kid = [self.gen_token(Tok.JSX_TEXT, value=str(self.value))]
4369
+ self.set_kids(nodes=new_kid)
4370
+ return True
4371
+
4372
+
4373
+ class JsxExpression(JsxChild):
4374
+ """JsxExpression node type for Jac Ast."""
4375
+
4376
+ def __init__(
4377
+ self,
4378
+ expr: Expr,
4379
+ kid: Sequence[UniNode],
4380
+ ) -> None:
4381
+ self.expr = expr
4382
+ JsxChild.__init__(self, kid=kid)
4383
+
4384
+ def normalize(self, deep: bool = False) -> bool:
4385
+ res = True
4386
+ if deep:
4387
+ res = self.expr.normalize(deep)
4388
+ new_kid: list[UniNode] = [
4389
+ self.gen_token(Tok.LBRACE),
4390
+ self.expr,
4391
+ self.gen_token(Tok.RBRACE),
4392
+ ]
4393
+ self.set_kids(nodes=new_kid)
4394
+ return res
4395
+
4396
+
4053
4397
  # Match Nodes
4054
4398
  # ------------
4055
4399
 
@@ -4656,6 +5000,8 @@ class String(Literal):
4656
5000
 
4657
5001
  def unparse(self) -> str:
4658
5002
  super().unparse()
5003
+ if self.parent and isinstance(self.parent, FString):
5004
+ return self.lit_value
4659
5005
  return self.value
4660
5006
 
4661
5007