jaclang 0.8.4__py3-none-any.whl → 0.8.6__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 (88) hide show
  1. jaclang/cli/cli.md +1 -0
  2. jaclang/cli/cli.py +109 -37
  3. jaclang/compiler/jac.lark +3 -3
  4. jaclang/compiler/larkparse/jac_parser.py +2 -2
  5. jaclang/compiler/parser.py +14 -21
  6. jaclang/compiler/passes/main/__init__.py +5 -1
  7. jaclang/compiler/passes/main/binder_pass.py +594 -0
  8. jaclang/compiler/passes/main/cfg_build_pass.py +21 -1
  9. jaclang/compiler/passes/main/import_pass.py +8 -256
  10. jaclang/compiler/passes/main/inheritance_pass.py +10 -3
  11. jaclang/compiler/passes/main/pyast_gen_pass.py +92 -77
  12. jaclang/compiler/passes/main/pyast_load_pass.py +24 -13
  13. jaclang/compiler/passes/main/sem_def_match_pass.py +1 -1
  14. jaclang/compiler/passes/main/sym_tab_build_pass.py +4 -0
  15. jaclang/compiler/passes/main/tests/fixtures/M1.jac +3 -0
  16. jaclang/compiler/passes/main/tests/fixtures/cfg_has_var.jac +12 -0
  17. jaclang/compiler/passes/main/tests/fixtures/cfg_if_no_else.jac +11 -0
  18. jaclang/compiler/passes/main/tests/fixtures/cfg_return.jac +9 -0
  19. jaclang/compiler/passes/main/tests/fixtures/checker_imported.jac +2 -0
  20. jaclang/compiler/passes/main/tests/fixtures/checker_importer.jac +6 -0
  21. jaclang/compiler/passes/main/tests/fixtures/data_spatial_types.jac +1 -1
  22. jaclang/compiler/passes/main/tests/fixtures/import_symbol_type_infer.jac +11 -0
  23. jaclang/compiler/passes/main/tests/fixtures/infer_type_assignment.jac +5 -0
  24. jaclang/compiler/passes/main/tests/fixtures/member_access_type_inferred.jac +13 -0
  25. jaclang/compiler/passes/main/tests/fixtures/member_access_type_resolve.jac +11 -0
  26. jaclang/compiler/passes/main/tests/fixtures/sym_binder.jac +47 -0
  27. jaclang/compiler/passes/main/tests/fixtures/type_annotation_assignment.jac +8 -0
  28. jaclang/compiler/passes/main/tests/test_binder_pass.py +111 -0
  29. jaclang/compiler/passes/main/tests/test_cfg_build_pass.py +62 -24
  30. jaclang/compiler/passes/main/tests/test_checker_pass.py +87 -0
  31. jaclang/compiler/passes/main/tests/test_pyast_gen_pass.py +13 -13
  32. jaclang/compiler/passes/main/tests/test_sem_def_match_pass.py +6 -6
  33. jaclang/compiler/passes/main/type_checker_pass.py +128 -0
  34. jaclang/compiler/passes/tool/doc_ir_gen_pass.py +2 -0
  35. jaclang/compiler/passes/tool/tests/fixtures/simple_walk_fmt.jac +3 -0
  36. jaclang/compiler/program.py +32 -11
  37. jaclang/compiler/tests/test_sr_errors.py +32 -0
  38. jaclang/compiler/type_system/__init__.py +1 -0
  39. jaclang/compiler/type_system/type_evaluator.py +421 -0
  40. jaclang/compiler/type_system/type_utils.py +41 -0
  41. jaclang/compiler/type_system/types.py +240 -0
  42. jaclang/compiler/unitree.py +36 -24
  43. jaclang/langserve/dev_engine.jac +645 -0
  44. jaclang/langserve/dev_server.jac +201 -0
  45. jaclang/langserve/engine.jac +24 -5
  46. jaclang/langserve/tests/server_test/test_lang_serve.py +2 -2
  47. jaclang/langserve/tests/test_dev_server.py +80 -0
  48. jaclang/langserve/tests/test_server.py +13 -0
  49. jaclang/runtimelib/builtin.py +28 -39
  50. jaclang/runtimelib/importer.py +34 -63
  51. jaclang/runtimelib/machine.py +48 -64
  52. jaclang/runtimelib/memory.py +23 -5
  53. jaclang/runtimelib/tests/fixtures/savable_object.jac +10 -2
  54. jaclang/runtimelib/utils.py +42 -6
  55. jaclang/tests/fixtures/edge_node_walk.jac +1 -1
  56. jaclang/tests/fixtures/edges_walk.jac +1 -1
  57. jaclang/tests/fixtures/gendot_bubble_sort.jac +1 -1
  58. jaclang/tests/fixtures/py_run.jac +8 -0
  59. jaclang/tests/fixtures/py_run.py +23 -0
  60. jaclang/tests/fixtures/pyfunc.py +2 -0
  61. jaclang/tests/fixtures/pyfunc_fmt.py +60 -0
  62. jaclang/tests/fixtures/pyfunc_fstr.py +25 -0
  63. jaclang/tests/fixtures/pyfunc_kwesc.py +33 -0
  64. jaclang/tests/fixtures/python_run_test.py +19 -0
  65. jaclang/tests/test_cli.py +107 -0
  66. jaclang/tests/test_language.py +106 -5
  67. jaclang/utils/lang_tools.py +6 -3
  68. jaclang/utils/module_resolver.py +90 -0
  69. jaclang/utils/symtable_test_helpers.py +125 -0
  70. jaclang/utils/test.py +3 -4
  71. jaclang/vendor/interegular/__init__.py +34 -0
  72. jaclang/vendor/interegular/comparator.py +163 -0
  73. jaclang/vendor/interegular/fsm.py +1015 -0
  74. jaclang/vendor/interegular/patterns.py +732 -0
  75. jaclang/vendor/interegular/py.typed +0 -0
  76. jaclang/vendor/interegular/utils/__init__.py +15 -0
  77. jaclang/vendor/interegular/utils/simple_parser.py +165 -0
  78. jaclang/vendor/interegular-0.3.3.dist-info/INSTALLER +1 -0
  79. jaclang/vendor/interegular-0.3.3.dist-info/LICENSE.txt +21 -0
  80. jaclang/vendor/interegular-0.3.3.dist-info/METADATA +64 -0
  81. jaclang/vendor/interegular-0.3.3.dist-info/RECORD +20 -0
  82. jaclang/vendor/interegular-0.3.3.dist-info/REQUESTED +0 -0
  83. jaclang/vendor/interegular-0.3.3.dist-info/WHEEL +5 -0
  84. jaclang/vendor/interegular-0.3.3.dist-info/top_level.txt +1 -0
  85. {jaclang-0.8.4.dist-info → jaclang-0.8.6.dist-info}/METADATA +2 -1
  86. {jaclang-0.8.4.dist-info → jaclang-0.8.6.dist-info}/RECORD +88 -43
  87. {jaclang-0.8.4.dist-info → jaclang-0.8.6.dist-info}/WHEEL +0 -0
  88. {jaclang-0.8.4.dist-info → jaclang-0.8.6.dist-info}/entry_points.txt +0 -0
@@ -17,14 +17,11 @@ The pass runs early in the compilation pipeline to ensure all symbols from impor
17
17
  modules are available for subsequent passes like symbol table building and type checking.
18
18
  """
19
19
 
20
- import ast as py_ast
21
20
  import os
22
- from typing import Optional
23
-
24
21
 
25
22
  import jaclang.compiler.unitree as uni
26
23
  from jaclang.compiler.passes import Transform, UniPass
27
- from jaclang.compiler.passes.main import SymTabBuildPass
24
+ from jaclang.runtimelib.utils import read_file_with_encoding
28
25
  from jaclang.utils.log import logging
29
26
 
30
27
 
@@ -102,260 +99,15 @@ class JacImportDepsPass(Transform[uni.Module, uni.Module]):
102
99
  return self.prog.mod.hub[jac_init_path]
103
100
  return self.prog.compile(file_path=jac_init_path)
104
101
  elif os.path.exists(py_init_path := os.path.join(target, "__init__.py")):
105
- with open(py_init_path, "r") as f:
106
- file_source = f.read()
107
- mod = uni.Module.make_stub(
108
- inject_name=target.split(os.path.sep)[-1],
109
- inject_src=uni.Source(file_source, py_init_path),
110
- )
111
- self.prog.mod.hub[py_init_path] = mod
112
- return mod
102
+ file_source = read_file_with_encoding(py_init_path)
103
+ mod = uni.Module.make_stub(
104
+ inject_name=target.split(os.path.sep)[-1],
105
+ inject_src=uni.Source(file_source, py_init_path),
106
+ )
107
+ self.prog.mod.hub[py_init_path] = mod
108
+ return mod
113
109
  else:
114
110
  return uni.Module.make_stub(
115
111
  inject_name=target.split(os.path.sep)[-1],
116
112
  inject_src=uni.Source("", target),
117
113
  )
118
-
119
-
120
- class PyImportDepsPass(JacImportDepsPass):
121
- """Jac statically imports Python modules."""
122
-
123
- def pre_transform(self) -> None:
124
- """Initialize the PyImportPass."""
125
- self.import_from_build_list: list[tuple[uni.Import, uni.Module]] = []
126
-
127
- def transform(self, ir_in: uni.Module) -> uni.Module:
128
- """Run Importer."""
129
- self.__load_builtins()
130
- self.import_from_build_list = []
131
-
132
- # Add all modules from the program hub to last_imported to process their imports
133
- self.last_imported = list(self.prog.mod.hub.values())
134
-
135
- # Process imports until no more imported modules to process
136
- while self.last_imported:
137
- current_module = self.last_imported.pop(0)
138
- all_imports = UniPass.get_all_sub_nodes(current_module, uni.ModulePath)
139
- for i in all_imports:
140
- self.process_import(i)
141
-
142
- return ir_in
143
-
144
- def process_import(self, i: uni.ModulePath) -> None:
145
- """Process an import."""
146
- # Process import is orginally implemented to handle ModulePath in Jaclang
147
- # This won't work with py imports as this will fail to import stuff in form of
148
- # from a import b
149
- # from a import (c, d, e)
150
- # Solution to that is to get the import node and check the from loc `then`
151
- # handle it based on if there a from loc or not
152
- imp_node = i.parent_of_type(uni.Import)
153
-
154
- if imp_node.is_py:
155
- if imp_node.from_loc:
156
- msg = "Processing import from node at href="
157
- msg += uni.Module.get_href_path(imp_node)
158
- msg += f' path="{imp_node.loc.mod_path}, {imp_node.loc}"'
159
- self.__process_import_from(imp_node)
160
- else:
161
- msg = "Processing import node at href="
162
- msg += uni.Module.get_href_path(imp_node)
163
- msg += f' path="{imp_node.loc.mod_path}, {imp_node.loc}"'
164
- self.__process_import(imp_node)
165
-
166
- def __process_import_from(self, imp_node: uni.Import) -> None:
167
- """Process imports in the form of `from X import I`."""
168
- assert isinstance(imp_node.from_loc, uni.ModulePath)
169
-
170
- # Attempt to import the Python module X and process it
171
- imported_mod = self.__import_py_module(
172
- parent_node_path=uni.Module.get_href_path(imp_node),
173
- mod_path=imp_node.from_loc.dot_path_str,
174
- )
175
-
176
- if imported_mod:
177
- # Cyclic imports will happen in case of import sys
178
- # sys stub file imports sys module which means that we need
179
- # to import sys stub file again in the sys stub file and so on
180
- # This can be detected by iterating over all the parents and make sure
181
- # that the parent is in another file than the imported module
182
- if self.__check_cyclic_imports(imp_node, imported_mod):
183
- return
184
-
185
- if imported_mod.name == "builtins":
186
- return
187
-
188
- msg = f"\tRegistering module:{imported_mod.name} to "
189
- msg += f"import_from handling with {imp_node.loc.mod_path}:{imp_node.loc}"
190
-
191
- self.load_mod(imported_mod)
192
- self.import_from_build_list.append((imp_node, imported_mod))
193
- SymTabBuildPass(ir_in=imported_mod, prog=self.prog)
194
-
195
- def __process_import(self, imp_node: uni.Import) -> None:
196
- """Process the imports in form of `import X`."""
197
- # Expected that each ImportStatement will import one item
198
- # In case of this assertion fired then we need to revisit this item
199
- assert len(imp_node.items) == 1
200
- imported_item = imp_node.items[0]
201
- assert isinstance(imported_item, uni.ModulePath)
202
-
203
- imported_mod = self.__import_py_module(
204
- parent_node_path=uni.Module.get_href_path(imported_item),
205
- mod_path=imported_item.dot_path_str,
206
- imported_mod_name=(
207
- # TODO: Check this replace
208
- imported_item.dot_path_str.replace(".", "")
209
- if not imported_item.alias
210
- else imported_item.alias.sym_name
211
- ),
212
- )
213
- if imported_mod:
214
- if (
215
- self.__check_cyclic_imports(imp_node, imported_mod)
216
- or imported_mod.name == "builtins"
217
- ):
218
- return
219
- self.load_mod(imported_mod)
220
-
221
- if imp_node.is_absorb:
222
- msg = f"\tRegistering module:{imported_mod.name} to "
223
- msg += f"import_from (import all) handling with {imp_node.loc.mod_path}:{imp_node.loc}"
224
-
225
- self.import_from_build_list.append((imp_node, imported_mod))
226
- SymTabBuildPass(ir_in=imported_mod, prog=self.prog)
227
-
228
- def __import_py_module(
229
- self,
230
- parent_node_path: str,
231
- mod_path: str,
232
- imported_mod_name: Optional[str] = None,
233
- ) -> Optional[uni.Module]:
234
- """Import a python module."""
235
- from jaclang.compiler.passes.main import PyastBuildPass
236
-
237
- python_raise_map = self.prog.py_raise_map
238
- file_to_raise: Optional[str] = None
239
-
240
- if mod_path in python_raise_map:
241
- file_to_raise = python_raise_map[mod_path]
242
- else:
243
- # TODO: Is it fine to use imported_mod_name or get it from mod_path?
244
- resolved_mod_path = f"{parent_node_path}.{mod_path}"
245
- resolved_mod_path = resolved_mod_path.replace("..", ".")
246
- resolved_mod_path = resolved_mod_path.replace(
247
- f"{list(self.prog.mod.hub.values())[0]}.", ""
248
- )
249
- file_to_raise = python_raise_map.get(resolved_mod_path)
250
-
251
- if file_to_raise is None:
252
- return None
253
-
254
- try:
255
- if file_to_raise in {None, "built-in", "frozen"}:
256
- return None
257
-
258
- with open(file_to_raise, "r", encoding="utf-8") as f:
259
- file_source = f.read()
260
- mod = PyastBuildPass(
261
- ir_in=uni.PythonModuleAst(
262
- py_ast.parse(file_source),
263
- orig_src=uni.Source(file_source, file_to_raise),
264
- ),
265
- prog=self.prog,
266
- ).ir_out
267
-
268
- if mod:
269
- mod.name = imported_mod_name if imported_mod_name else mod.name
270
- if mod.name == "__init__":
271
- # (thakee): This needs to be re-done after implementing path handling properly.
272
- mod_name = mod.loc.mod_path.split(os.path.sep)[-2]
273
- mod.name = mod_name
274
- mod.scope_name = mod_name
275
- mod.is_raised_from_py = True
276
- return mod
277
- else:
278
- raise self.ice(f"\tFailed to import python module {mod_path}")
279
-
280
- except Exception as e:
281
- self.log_error(f"\tFailed to import python module {mod_path}")
282
- raise e
283
-
284
- def __load_builtins(self) -> None:
285
- """Load Python builtins using introspection."""
286
- import builtins
287
- import inspect
288
- from jaclang.compiler.passes.main import PyastBuildPass
289
-
290
- # Python constants that cannot be assigned to
291
- constants = {"True", "False", "None", "NotImplemented", "Ellipsis", "..."}
292
-
293
- # Create a synthetic source with all builtins
294
- builtin_items = []
295
- for name in dir(builtins):
296
- if name in constants:
297
- # Skip constants as they're built into Python
298
- continue
299
-
300
- if not name.startswith("_") or name in ("__import__", "__build_class__"):
301
- obj = getattr(builtins, name)
302
- # Generate appropriate stub definitions based on obj type
303
- if inspect.isclass(obj):
304
- builtin_items.append(f"class {name}: ...")
305
- elif inspect.isfunction(obj) or inspect.isbuiltin(obj):
306
- # Try to get signature safely, use generic signature if it fails
307
- try:
308
- sig = inspect.signature(obj) if callable(obj) else "()"
309
- builtin_items.append(f"def {name}{sig}: ...")
310
- except (ValueError, TypeError):
311
- builtin_items.append(f"def {name}(*args, **kwargs): ...")
312
- else:
313
- # For variables that are not constants
314
- builtin_items.append(f"{name} = None")
315
-
316
- file_source = "\n".join(builtin_items)
317
- mod = PyastBuildPass(
318
- ir_in=uni.PythonModuleAst(
319
- py_ast.parse(file_source),
320
- orig_src=uni.Source(file_source, "builtins"),
321
- ),
322
- prog=self.prog,
323
- ).ir_out
324
- SymTabBuildPass(ir_in=mod, prog=self.prog)
325
- self.prog.mod.hub["builtins"] = mod
326
- mod.is_raised_from_py = True
327
-
328
- def annex_impl(self, node: uni.Module) -> None:
329
- """Annex impl and test modules."""
330
- return None
331
-
332
- def __handle_different_site_packages(self, mod_path: str) -> str:
333
- if "site-packages" in mod_path:
334
- mod_path = mod_path[mod_path.index("site-packages") :]
335
- return mod_path
336
-
337
- def __check_cyclic_imports(
338
- self, imp_node: uni.UniNode, imported_module: uni.Module
339
- ) -> bool:
340
- """Check cyclic imports that might happen."""
341
- # Example of cyclic imports is import os
342
- # In the os stub file it imports the real os module which will cause os
343
- # stub to be raised again and so on
344
- # Another example is numpy. numpy init file imports multidim array file
345
- # which imports again more items from numpy and so on.
346
- imp_node_file = self.__handle_different_site_packages(imp_node.loc.mod_path)
347
- imported_module_file = self.__handle_different_site_packages(
348
- imported_module.loc.mod_path
349
- )
350
- if imp_node_file == imported_module_file:
351
- return True
352
-
353
- parent: Optional[uni.UniNode] = imp_node.parent
354
- while parent is not None:
355
- parent_file = self.__handle_different_site_packages(parent.loc.mod_path)
356
- if parent_file == imported_module_file:
357
- return True
358
- else:
359
- parent = parent.find_parent_of_type(uni.Module)
360
-
361
- return False
@@ -60,11 +60,18 @@ class InheritancePass(Transform[uni.Module, uni.Module]):
60
60
  if base_class_symbol is None:
61
61
  return
62
62
 
63
- base_class_symbol_table = base_class_symbol.fetch_sym_tab
63
+ base_class_symbol_table = base_class_symbol.symbol_table
64
+
65
+ # FIXME: If the base class symbol is imported from another module, the symbol table
66
+ # will be None. The imported symbols were ignored and introduced in the typecheck
67
+ # for imported module items. This needs to be investigated to ensure that even imported
68
+ # classes should have a symbol table (unless the module is not parsed and decided not to).
69
+ if base_class_symbol_table is None:
70
+ return
64
71
 
65
72
  if self.is_missing_py_symbol_table(base_class_symbol, base_class_symbol_table):
66
73
  return
67
- assert base_class_symbol_table is not None
74
+
68
75
  node.sym_tab.inherit_sym_tab(base_class_symbol_table)
69
76
 
70
77
  def inherit_from_atom_trailer(
@@ -77,7 +84,7 @@ class InheritancePass(Transform[uni.Module, uni.Module]):
77
84
  sym = self.lookup_symbol(name.sym_name, current_sym_table)
78
85
  if sym is None:
79
86
  return
80
- current_sym_table = sym.fetch_sym_tab
87
+ current_sym_table = sym.symbol_table
81
88
  # Handle Python base classes or index slice expressions
82
89
  if self.is_missing_py_symbol_table(sym, current_sym_table):
83
90
  return
@@ -187,6 +187,15 @@ class PyastGenPass(UniPass):
187
187
  ),
188
188
  )
189
189
 
190
+ def needs_mtllm(self) -> None:
191
+ """Ensure byLLM is imported only once."""
192
+ self._add_preamble_once(
193
+ self.needs_mtllm.__name__,
194
+ ast3.Import(
195
+ names=[self.sync(ast3.alias(name="byllm"), jac_node=self.ir_out)]
196
+ ),
197
+ )
198
+
190
199
  def needs_enum(self) -> None:
191
200
  """Ensure Enum utilities are imported only once."""
192
201
  self._add_preamble_once(
@@ -641,15 +650,13 @@ class PyastGenPass(UniPass):
641
650
  self.traverse(node.body)
642
651
 
643
652
  def exit_archetype(self, node: uni.Archetype) -> None:
644
- inner = None
653
+ inner: Sequence[uni.CodeBlockStmt] | Sequence[uni.EnumBlockStmt] | None = None
645
654
  if isinstance(node.body, uni.ImplDef):
646
- inner = (
647
- node.body.body if not isinstance(node.body.body, uni.FuncCall) else None
648
- )
649
- elif not isinstance(node.body, uni.FuncCall):
655
+ inner = node.body.body if not isinstance(node.body.body, uni.Expr) else None
656
+ elif not isinstance(node.body, uni.Expr):
650
657
  inner = node.body
651
- body = self.resolve_stmt_block(inner, doc=node.doc)
652
- if not body and not isinstance(node.body, uni.FuncCall):
658
+ body: list[ast3.AST] = self.resolve_stmt_block(inner, doc=node.doc)
659
+ if not body and not isinstance(node.body, uni.Expr):
653
660
  self.log_error(
654
661
  "Archetype has no body. Perhaps an impl must be imported.", node
655
662
  )
@@ -699,12 +706,10 @@ class PyastGenPass(UniPass):
699
706
 
700
707
  def exit_enum(self, node: uni.Enum) -> None:
701
708
  self.needs_enum()
702
- inner = None
709
+ inner: Sequence[uni.CodeBlockStmt] | Sequence[uni.EnumBlockStmt] | None = None
703
710
  if isinstance(node.body, uni.ImplDef):
704
- inner = (
705
- node.body.body if not isinstance(node.body.body, uni.FuncCall) else None
706
- )
707
- elif not isinstance(node.body, uni.FuncCall):
711
+ inner = node.body.body if not isinstance(node.body.body, uni.Expr) else None
712
+ elif not isinstance(node.body, uni.Expr):
708
713
  inner = node.body
709
714
  body = self.resolve_stmt_block(inner, doc=node.doc)
710
715
  decorators = (
@@ -739,17 +744,25 @@ class PyastGenPass(UniPass):
739
744
  self, model: ast3.expr, caller: ast3.expr, args: ast3.Dict
740
745
  ) -> ast3.Call:
741
746
  """Reusable method to codegen call_llm(model, caller, args)."""
742
- return self.sync(
747
+ self.needs_mtllm()
748
+ mtir_cls_ast = self.sync(
749
+ ast3.Attribute(
750
+ value=self.sync(ast3.Name(id="byllm", ctx=ast3.Load())),
751
+ attr="MTIR",
752
+ ctx=ast3.Load(),
753
+ )
754
+ )
755
+ mtir_ast = self.sync(
743
756
  ast3.Call(
744
- func=self.jaclib_obj("call_llm"),
757
+ func=self.sync(
758
+ ast3.Attribute(
759
+ value=mtir_cls_ast,
760
+ attr="factory",
761
+ ctx=ast3.Load(),
762
+ )
763
+ ),
745
764
  args=[],
746
765
  keywords=[
747
- self.sync(
748
- ast3.keyword(
749
- arg="model",
750
- value=model,
751
- )
752
- ),
753
766
  self.sync(
754
767
  ast3.keyword(
755
768
  arg="caller",
@@ -762,45 +775,45 @@ class PyastGenPass(UniPass):
762
775
  value=args,
763
776
  )
764
777
  ),
778
+ self.sync(
779
+ ast3.keyword(
780
+ arg="call_params",
781
+ value=self.sync(
782
+ ast3.Attribute(
783
+ value=model,
784
+ attr="call_params",
785
+ ctx=ast3.Load(),
786
+ ),
787
+ ),
788
+ )
789
+ ),
790
+ ],
791
+ )
792
+ )
793
+ return self.sync(
794
+ ast3.Call(
795
+ func=self.jaclib_obj("call_llm"),
796
+ args=[],
797
+ keywords=[
798
+ self.sync(
799
+ ast3.keyword(
800
+ arg="model",
801
+ value=model,
802
+ )
803
+ ),
804
+ self.sync(
805
+ ast3.keyword(
806
+ arg="mtir",
807
+ value=mtir_ast,
808
+ )
809
+ ),
765
810
  ],
766
811
  )
767
812
  )
768
-
769
- def gen_llm_call_override(self, node: uni.FuncCall) -> list[ast3.AST]:
770
- """Generate python ast nodes for llm function body override syntax.
771
-
772
- example:
773
- foo() by llm();
774
- """
775
- # from jaclang.runtimelib.machine import JacMachineInterface
776
- # return JacMachineInterface.gen_llm_call_override(self, node)
777
- if node.body_genai_call is None:
778
- raise self.ice()
779
-
780
- model = cast(ast3.expr, node.body_genai_call.gen.py_ast[0])
781
- caller = cast(ast3.expr, node.target.gen.py_ast[0])
782
-
783
- # Construct the arguments for the LLM call.
784
- keys: list[ast3.expr | None] = []
785
- values: list[ast3.expr] = []
786
- for idx, call_arg in enumerate(node.params):
787
- if isinstance(call_arg, uni.Expr):
788
- keys.append(self.sync(ast3.Constant(value=idx)))
789
- values.append(cast(ast3.expr, call_arg.gen.py_ast[0]))
790
- else:
791
- if call_arg.key:
792
- keys.append(self.sync(ast3.Constant(value=call_arg.key.sym_name)))
793
- else:
794
- keys.append(self.sync(ast3.Constant(value=idx)))
795
- values.append(cast(ast3.expr, call_arg.value.gen.py_ast[0]))
796
-
797
- args = self.sync(ast3.Dict(keys=keys, values=values))
798
-
799
- return [self._invoke_llm_call(model, caller, args)]
800
813
 
801
814
  def gen_llm_body(self, node: uni.Ability) -> list[ast3.stmt]:
802
815
  """Generate the by LLM body."""
803
- assert isinstance(node.body, uni.FuncCall)
816
+ assert isinstance(node.body, uni.Expr)
804
817
  assert isinstance(node.signature, uni.FuncSignature)
805
818
 
806
819
  # Codegen for the caller of the LLM call.
@@ -865,10 +878,10 @@ class PyastGenPass(UniPass):
865
878
  func_type = ast3.AsyncFunctionDef if node.is_async else ast3.FunctionDef
866
879
  body = (
867
880
  self.gen_llm_body(node)
868
- if isinstance(node.body, uni.FuncCall)
881
+ if isinstance(node.body, uni.Expr)
869
882
  or (
870
883
  isinstance(node.body, uni.ImplDef)
871
- and isinstance(node.body.body, uni.FuncCall)
884
+ and isinstance(node.body.body, uni.Expr)
872
885
  )
873
886
  else (
874
887
  [
@@ -886,10 +899,10 @@ class PyastGenPass(UniPass):
886
899
  (
887
900
  node.body.body
888
901
  if isinstance(node.body, uni.ImplDef)
889
- and not isinstance(node.body.body, uni.FuncCall)
902
+ and not isinstance(node.body.body, uni.Expr)
890
903
  else (
891
904
  node.body
892
- if not isinstance(node.body, uni.FuncCall)
905
+ if not isinstance(node.body, uni.Expr)
893
906
  else None
894
907
  )
895
908
  ),
@@ -939,7 +952,7 @@ class PyastGenPass(UniPass):
939
952
  decorator_list.insert(
940
953
  0, self.sync(ast3.Name(id="staticmethod", ctx=ast3.Load()))
941
954
  )
942
- if not body and not isinstance(node.body, uni.FuncCall):
955
+ if not body and not isinstance(node.body, uni.Expr):
943
956
  self.log_error(
944
957
  "Ability has no body. Perhaps an impl must be imported.", node
945
958
  )
@@ -1074,18 +1087,20 @@ class PyastGenPass(UniPass):
1074
1087
  ]
1075
1088
 
1076
1089
  def exit_param_var(self, node: uni.ParamVar) -> None:
1077
- node.gen.py_ast = [
1078
- self.sync(
1079
- ast3.arg(
1080
- arg=node.name.sym_name,
1081
- annotation=(
1082
- cast(ast3.expr, node.type_tag.gen.py_ast[0])
1083
- if node.type_tag
1084
- else None
1085
- ),
1090
+ if isinstance(node.name.gen.py_ast[0], ast3.Name):
1091
+ name = node.name.gen.py_ast[0].id
1092
+ node.gen.py_ast = [
1093
+ self.sync(
1094
+ ast3.arg(
1095
+ arg=name,
1096
+ annotation=(
1097
+ cast(ast3.expr, node.type_tag.gen.py_ast[0])
1098
+ if node.type_tag
1099
+ else None
1100
+ ),
1101
+ )
1086
1102
  )
1087
- )
1088
- ]
1103
+ ]
1089
1104
 
1090
1105
  def exit_arch_has(self, node: uni.ArchHas) -> None:
1091
1106
  vars_py: list[ast3.AST] = self.flatten([v.gen.py_ast for v in node.vars])
@@ -2297,7 +2312,11 @@ class PyastGenPass(UniPass):
2297
2312
  node.gen.py_ast = [
2298
2313
  self.sync(
2299
2314
  ast3.keyword(
2300
- arg=node.key.sym_name if node.key else None,
2315
+ arg=(
2316
+ node.key.gen.py_ast[0].id
2317
+ if node.key and isinstance(node.key.gen.py_ast[0], ast3.Name)
2318
+ else None
2319
+ ),
2301
2320
  value=cast(ast3.expr, node.value.gen.py_ast[0]),
2302
2321
  )
2303
2322
  )
@@ -2492,11 +2511,8 @@ class PyastGenPass(UniPass):
2492
2511
  return args, keywords
2493
2512
 
2494
2513
  def exit_func_call(self, node: uni.FuncCall) -> None:
2495
- if node.body_genai_call:
2496
- node.gen.py_ast = self.gen_llm_call_override(node)
2497
-
2498
2514
  # TODO: This needs to be changed to only generate parameters no the body.
2499
- elif node.genai_call:
2515
+ if node.genai_call:
2500
2516
  self.ice("Type(by llm()) call feature is temporarily disabled.")
2501
2517
 
2502
2518
  else:
@@ -2981,9 +2997,8 @@ class PyastGenPass(UniPass):
2981
2997
  node.gen.py_ast = [self.sync(op_cls())]
2982
2998
 
2983
2999
  def exit_name(self, node: uni.Name) -> None:
2984
- node.gen.py_ast = [
2985
- self.sync(ast3.Name(id=node.sym_name, ctx=node.py_ctx_func()))
2986
- ]
3000
+ name = node.sym_name[2:] if node.sym_name.startswith("<>") else node.sym_name
3001
+ node.gen.py_ast = [self.sync(ast3.Name(id=name, ctx=node.py_ctx_func()))]
2987
3002
 
2988
3003
  def exit_float(self, node: uni.Float) -> None:
2989
3004
  node.gen.py_ast = [self.sync(ast3.Constant(value=float(node.value)))]
@@ -1374,10 +1374,11 @@ class PyastBuildPass(Transform[uni.PythonModuleAst, uni.Module]):
1374
1374
  valid = [
1375
1375
  value for value in values if isinstance(value, (uni.String, uni.ExprStmt))
1376
1376
  ]
1377
- return uni.FString(
1377
+ fstr = uni.FString(
1378
1378
  parts=valid,
1379
1379
  kid=[*valid] if valid else [uni.EmptyToken()],
1380
1380
  )
1381
+ return uni.MultiString(strings=[fstr], kid=[fstr])
1381
1382
 
1382
1383
  def proc_lambda(self, node: py_ast.Lambda) -> uni.LambdaExpr:
1383
1384
  """Process python node.
@@ -2337,20 +2338,30 @@ class PyastBuildPass(Transform[uni.PythonModuleAst, uni.Module]):
2337
2338
  arg: _Identifier | None
2338
2339
  value: expr
2339
2340
  """
2340
- arg = uni.Name(
2341
- orig_src=self.orig_src,
2342
- name=Tok.NAME,
2343
- value=node.arg if node.arg else "_",
2344
- line=node.lineno,
2345
- end_line=node.end_lineno if node.end_lineno else node.lineno,
2346
- col_start=node.col_offset,
2347
- col_end=node.col_offset + len(node.arg if node.arg else "_"),
2348
- pos_start=0,
2349
- pos_end=0,
2350
- )
2341
+ arg = None
2342
+ if node.arg:
2343
+ from jaclang.compiler import TOKEN_MAP
2344
+
2345
+ reserved_keywords = [v for _, v in TOKEN_MAP.items()]
2346
+ arg_value = (
2347
+ node.arg if node.arg not in reserved_keywords else f"<>{node.arg}"
2348
+ )
2349
+ arg = uni.Name(
2350
+ orig_src=self.orig_src,
2351
+ name=Tok.NAME,
2352
+ value=arg_value,
2353
+ line=node.lineno,
2354
+ end_line=node.end_lineno if node.end_lineno else node.lineno,
2355
+ col_start=node.col_offset,
2356
+ col_end=node.col_offset + len(node.arg if node.arg else "_"),
2357
+ pos_start=0,
2358
+ pos_end=0,
2359
+ )
2351
2360
  value = self.convert(node.value)
2352
2361
  if isinstance(value, uni.Expr):
2353
- return uni.KWPair(key=arg, value=value, kid=[arg, value])
2362
+ return uni.KWPair(
2363
+ key=arg, value=value, kid=[arg, value] if arg else [value]
2364
+ )
2354
2365
  else:
2355
2366
  raise self.ice()
2356
2367
 
@@ -47,7 +47,7 @@ class SemDefMatchPass(Transform[uni.Module, uni.Module]):
47
47
  sym = current_sym_tab.lookup(part, deep=False)
48
48
  if sym is None:
49
49
  return None
50
- current_sym_tab = sym.fetch_sym_tab
50
+ current_sym_tab = sym.symbol_table
51
51
  return sym
52
52
 
53
53
  def connect_sems(
@@ -84,6 +84,10 @@ class SymTabBuildPass(UniPass):
84
84
  else:
85
85
  pass # Need to support pythonic import symbols with dots in it
86
86
 
87
+ def exit_module_item(self, node: uni.ModuleItem) -> None:
88
+ sym_node = node.alias or node.name
89
+ sym_node.sym_tab.def_insert(sym_node, single_decl="import")
90
+
87
91
  def enter_archetype(self, node: uni.Archetype) -> None:
88
92
  self.push_scope_and_link(node)
89
93
  assert node.parent_scope is not None
@@ -0,0 +1,3 @@
1
+
2
+
3
+ glob k = 19;
@@ -0,0 +1,12 @@
1
+ obj Rock {
2
+ has pellets:list;
3
+
4
+ def count_pellets() -> int {
5
+ return self.pellets.length();
6
+ }
7
+ }
8
+
9
+ with entry {
10
+ rock = Rock(pellets=[1, 2, 3]);
11
+ print("Number of pellets: " + rock.count_pellets().to_string());
12
+ }