jaclang 0.8.8__py3-none-any.whl → 0.8.9__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 (36) hide show
  1. jaclang/cli/cli.py +64 -2
  2. jaclang/compiler/constant.py +6 -1
  3. jaclang/compiler/jac.lark +34 -41
  4. jaclang/compiler/larkparse/jac_parser.py +2 -2
  5. jaclang/compiler/parser.py +143 -27
  6. jaclang/compiler/passes/main/def_use_pass.py +1 -0
  7. jaclang/compiler/passes/main/pyast_gen_pass.py +151 -83
  8. jaclang/compiler/passes/main/pyast_load_pass.py +2 -0
  9. jaclang/compiler/passes/main/tests/test_checker_pass.py +0 -1
  10. jaclang/compiler/passes/main/tests/test_predynamo_pass.py +13 -14
  11. jaclang/compiler/passes/tool/doc_ir_gen_pass.py +104 -20
  12. jaclang/compiler/passes/tool/jac_formatter_pass.py +2 -2
  13. jaclang/compiler/passes/tool/tests/fixtures/import_fmt.jac +7 -1
  14. jaclang/compiler/passes/tool/tests/fixtures/tagbreak.jac +135 -29
  15. jaclang/compiler/program.py +6 -2
  16. jaclang/compiler/type_system/type_evaluator.jac +959 -0
  17. jaclang/compiler/unitree.py +23 -13
  18. jaclang/lib.py +17 -0
  19. jaclang/runtimelib/archetype.py +25 -25
  20. jaclang/runtimelib/constructs.py +2 -2
  21. jaclang/runtimelib/machine.py +57 -47
  22. jaclang/settings.py +1 -2
  23. jaclang/tests/fixtures/attr_pattern_case.jac +18 -0
  24. jaclang/tests/fixtures/funccall_genexpr.jac +7 -0
  25. jaclang/tests/fixtures/funccall_genexpr.py +5 -0
  26. jaclang/tests/fixtures/py2jac_empty.py +0 -0
  27. jaclang/tests/test_cli.py +134 -18
  28. jaclang/tests/test_language.py +146 -29
  29. jaclang/tests/test_reference.py +3 -1
  30. jaclang/utils/helpers.py +20 -4
  31. jaclang/utils/tests/test_lang_tools.py +4 -15
  32. {jaclang-0.8.8.dist-info → jaclang-0.8.9.dist-info}/METADATA +1 -1
  33. {jaclang-0.8.8.dist-info → jaclang-0.8.9.dist-info}/RECORD +35 -30
  34. jaclang/compiler/type_system/type_evaluator.py +0 -844
  35. {jaclang-0.8.8.dist-info → jaclang-0.8.9.dist-info}/WHEEL +0 -0
  36. {jaclang-0.8.8.dist-info → jaclang-0.8.9.dist-info}/entry_points.txt +0 -0
@@ -108,9 +108,14 @@ class JacParser(Transform[uni.Source, uni.Module]):
108
108
 
109
109
  def feed_current_token(iparser: jl.InteractiveParser, tok: jl.Token) -> bool:
110
110
  """Feed the current token to the parser."""
111
+ max_attempts = 100 # Prevent infinite loops
112
+ attempts = 0
111
113
  while tok.type not in iparser.accepts():
114
+ if attempts >= max_attempts:
115
+ return False # Give up after too many attempts
112
116
  if not try_feed_missing_token(iparser):
113
117
  return False
118
+ attempts += 1
114
119
  iparser.feed_token(tok)
115
120
  return True
116
121
 
@@ -650,11 +655,12 @@ class JacParser(Transform[uni.Source, uni.Module]):
650
655
  def sem_def(self, _: None) -> uni.SemDef:
651
656
  """Grammar rule.
652
657
 
653
- sem_def: KW_SEM dotted_name EQ multistring SEMI
658
+ sem_def: KW_SEM dotted_name (EQ | KW_IS) STRING SEMI
654
659
  """
655
660
  self.consume_token(Tok.KW_SEM)
656
661
  target = self.extract_from_list(self.consume(list), uni.NameAtom)
657
- self.consume_token(Tok.EQ)
662
+ if not self.match_token(Tok.KW_IS):
663
+ self.consume_token(Tok.EQ)
658
664
  value = self.consume(uni.String)
659
665
  self.consume_token(Tok.SEMI)
660
666
  return uni.SemDef(
@@ -1705,15 +1711,18 @@ class JacParser(Transform[uni.Source, uni.Module]):
1705
1711
  lambda_expr: KW_LAMBDA func_decl_params? (RETURN_HINT expression)? COLON expression
1706
1712
  """
1707
1713
  return_type: uni.Expr | None = None
1714
+ return_hint_tok: uni.Token | None = None
1708
1715
  sig_kid: list[uni.UniNode] = []
1709
1716
  self.consume_token(Tok.KW_LAMBDA)
1710
1717
  params = self.match(list)
1711
- if self.match_token(Tok.RETURN_HINT):
1718
+ if return_hint_tok := self.match_token(Tok.RETURN_HINT):
1712
1719
  return_type = self.consume(uni.Expr)
1713
1720
  self.consume_token(Tok.COLON)
1714
1721
  body = self.consume(uni.Expr)
1715
1722
  if params:
1716
1723
  sig_kid.extend(params)
1724
+ if return_hint_tok:
1725
+ sig_kid.append(return_hint_tok)
1717
1726
  if return_type:
1718
1727
  sig_kid.append(return_type)
1719
1728
  signature = (
@@ -1731,7 +1740,11 @@ class JacParser(Transform[uni.Source, uni.Module]):
1731
1740
  if params or return_type
1732
1741
  else None
1733
1742
  )
1734
- new_kid = [i for i in self.cur_nodes if i != params and i != return_type]
1743
+ new_kid = [
1744
+ i
1745
+ for i in self.cur_nodes
1746
+ if i != params and i != return_type and i != return_hint_tok
1747
+ ]
1735
1748
  new_kid.insert(1, signature) if signature else None
1736
1749
  return uni.LambdaExpr(
1737
1750
  signature=signature,
@@ -1965,25 +1978,31 @@ class JacParser(Transform[uni.Source, uni.Module]):
1965
1978
  )
1966
1979
  return self._binary_expr_unwind(self.cur_nodes)
1967
1980
 
1981
+ def await_expr(self, _: None) -> uni.Expr:
1982
+ """Grammar rule.
1983
+
1984
+ await_expr: KW_AWAIT? pipe_call
1985
+ """
1986
+ if self.match_token(Tok.KW_AWAIT):
1987
+ operand = self.consume(uni.Expr)
1988
+ return uni.AwaitExpr(
1989
+ target=operand,
1990
+ kid=self.cur_nodes,
1991
+ )
1992
+ return self._binary_expr_unwind(self.cur_nodes)
1993
+
1968
1994
  def pipe_call(self, _: None) -> uni.Expr:
1969
1995
  """Grammar rule.
1970
1996
 
1971
- pipe_call: (PIPE_FWD | A_PIPE_FWD | KW_SPAWN | KW_AWAIT)? atomic_chain
1997
+ pipe_call: (PIPE_FWD | A_PIPE_FWD | KW_SPAWN)? atomic_chain
1972
1998
  """
1973
- if len(self.cur_nodes) == 2:
1974
- if self.match_token(Tok.KW_AWAIT):
1975
- target = self.consume(uni.Expr)
1976
- return uni.AwaitExpr(
1977
- target=target,
1978
- kid=self.cur_nodes,
1979
- )
1980
- elif op := self.match(uni.Token):
1981
- operand = self.consume(uni.Expr)
1982
- return uni.UnaryExpr(
1983
- op=op,
1984
- operand=operand,
1985
- kid=self.cur_nodes,
1986
- )
1999
+ if len(self.cur_nodes) == 2 and (op := self.match(uni.Token)):
2000
+ operand = self.consume(uni.Expr)
2001
+ return uni.UnaryExpr(
2002
+ op=op,
2003
+ operand=operand,
2004
+ kid=self.cur_nodes,
2005
+ )
1987
2006
  return self._binary_expr_unwind(self.cur_nodes)
1988
2007
 
1989
2008
  def aug_op(self, _: None) -> uni.Token:
@@ -1991,7 +2010,6 @@ class JacParser(Transform[uni.Source, uni.Module]):
1991
2010
 
1992
2011
  aug_op: RSHIFT_EQ
1993
2012
  | LSHIFT_EQ
1994
- | BW_NOT_EQ
1995
2013
  | BW_XOR_EQ
1996
2014
  | BW_OR_EQ
1997
2015
  | BW_AND_EQ
@@ -2045,6 +2063,14 @@ class JacParser(Transform[uni.Source, uni.Module]):
2045
2063
  atomic_call: atomic_chain LPAREN param_list? by_llm? RPAREN
2046
2064
  """
2047
2065
  target = self.consume(uni.Expr)
2066
+ gencompr = self.match(uni.GenCompr)
2067
+ if gencompr:
2068
+ return uni.FuncCall(
2069
+ target=target,
2070
+ params=[gencompr],
2071
+ genai_call=None,
2072
+ kid=self.cur_nodes,
2073
+ )
2048
2074
  self.consume_token(Tok.LPAREN)
2049
2075
  params_sn = self.match(list)
2050
2076
  genai_call = self.match(uni.Expr)
@@ -2188,10 +2214,22 @@ class JacParser(Transform[uni.Source, uni.Module]):
2188
2214
 
2189
2215
  fstring: FSTR_START fstr_parts FSTR_END
2190
2216
  | FSTR_SQ_START fstr_sq_parts FSTR_SQ_END
2217
+ | FSTR_TRIPLE_START fstr_triple_parts FSTR_TRIPLE_END
2218
+ | FSTR_SQ_TRIPLE_START fstr_sq_triple_parts FSTR_SQ_TRIPLE_END
2191
2219
  """
2192
- self.match_token(Tok.FSTR_START) or self.consume_token(Tok.FSTR_SQ_START)
2220
+ (
2221
+ self.match_token(Tok.FSTR_TRIPLE_START)
2222
+ or self.match_token(Tok.FSTR_SQ_TRIPLE_START)
2223
+ or self.match_token(Tok.FSTR_START)
2224
+ or self.consume_token(Tok.FSTR_SQ_START)
2225
+ )
2193
2226
  target = self.match(list)
2194
- self.match_token(Tok.FSTR_END) or self.consume_token(Tok.FSTR_SQ_END)
2227
+ (
2228
+ self.match_token(Tok.FSTR_TRIPLE_END)
2229
+ or self.match_token(Tok.FSTR_SQ_TRIPLE_END)
2230
+ or self.match_token(Tok.FSTR_END)
2231
+ or self.consume_token(Tok.FSTR_SQ_END)
2232
+ )
2195
2233
  return uni.FString(
2196
2234
  parts=(
2197
2235
  self.extract_from_list(target, (uni.String, uni.ExprStmt))
@@ -2239,6 +2277,44 @@ class JacParser(Transform[uni.Source, uni.Module]):
2239
2277
  ]
2240
2278
  return valid_parts
2241
2279
 
2280
+ def fstr_triple_parts(self, _: None) -> list[uni.UniNode]:
2281
+ """Grammar rule.
2282
+
2283
+ fstr_triple_parts: (FSTR_TRIPLE_PIECE | FSTR_BESC | LBRACE expression RBRACE )*
2284
+ """
2285
+ valid_parts: list[uni.UniNode] = [
2286
+ (
2287
+ i
2288
+ if isinstance(i, uni.String)
2289
+ else (
2290
+ uni.ExprStmt(expr=i, in_fstring=True, kid=[i])
2291
+ if isinstance(i, uni.Expr)
2292
+ else i
2293
+ )
2294
+ )
2295
+ for i in self.cur_nodes
2296
+ ]
2297
+ return valid_parts
2298
+
2299
+ def fstr_sq_triple_parts(self, _: None) -> list[uni.UniNode]:
2300
+ """Grammar rule.
2301
+
2302
+ fstr_sq_triple_parts: (FSTR_SQ_TRIPLE_PIECE | FSTR_BESC | LBRACE expression RBRACE )*
2303
+ """
2304
+ valid_parts: list[uni.UniNode] = [
2305
+ (
2306
+ i
2307
+ if isinstance(i, uni.String)
2308
+ else (
2309
+ uni.ExprStmt(expr=i, in_fstring=True, kid=[i])
2310
+ if isinstance(i, uni.Expr)
2311
+ else i
2312
+ )
2313
+ )
2314
+ for i in self.cur_nodes
2315
+ ]
2316
+ return valid_parts
2317
+
2242
2318
  def list_val(self, _: None) -> uni.ListVal:
2243
2319
  """Grammar rule.
2244
2320
 
@@ -2468,7 +2544,7 @@ class JacParser(Transform[uni.Source, uni.Module]):
2468
2544
  def assignment_list(self, _: None) -> list[uni.UniNode]:
2469
2545
  """Grammar rule.
2470
2546
 
2471
- assignment_list: (assignment_list COMMA)? (assignment | NAME)
2547
+ assignment_list: (assignment | named_ref) (COMMA (assignment | named_ref))* COMMA?
2472
2548
  """
2473
2549
 
2474
2550
  def name_to_assign(name_consume: uni.NameAtom) -> uni.Assignment:
@@ -2476,15 +2552,23 @@ class JacParser(Transform[uni.Source, uni.Module]):
2476
2552
  target=[name_consume], value=None, type_tag=None, kid=[name_consume]
2477
2553
  )
2478
2554
 
2479
- if self.match(list):
2480
- self.consume_token(Tok.COMMA)
2555
+ # Match first (assignment | named_ref)
2481
2556
  if self.match(uni.Assignment):
2482
2557
  pass
2483
2558
  elif name_consume := self.match(uni.NameAtom):
2484
2559
  self.cur_nodes[self.node_idx - 1] = name_to_assign(name_consume)
2485
2560
  else:
2486
- assign = self.consume(uni.Assignment)
2487
- self.cur_nodes[self.node_idx - 1] = assign
2561
+ raise self.ice()
2562
+
2563
+ # Match (COMMA (assignment | named_ref))* COMMA?
2564
+ while self.match_token(Tok.COMMA):
2565
+ if self.match(uni.Assignment):
2566
+ pass
2567
+ elif name_consume := self.match(uni.NameAtom):
2568
+ self.cur_nodes[self.node_idx - 1] = name_to_assign(name_consume)
2569
+ else:
2570
+ break # trailing comma
2571
+
2488
2572
  return self.flat_cur_nodes
2489
2573
 
2490
2574
  def type_ref(self, kid: list[uni.UniNode]) -> uni.TypeRef:
@@ -2946,6 +3030,36 @@ class JacParser(Transform[uni.Source, uni.Module]):
2946
3030
  value = self.consume(uni.MatchPattern)
2947
3031
  return uni.MatchKVPair(key=pattern, value=value, kid=self.cur_nodes)
2948
3032
 
3033
+ def attr_pattern(self, _: None) -> uni.MatchValue:
3034
+ """Grammar rule.
3035
+
3036
+ attr_pattern: NAME (DOT NAME)+
3037
+ """
3038
+ name_idx = 0
3039
+ cur_element = self.consume(uni.NameAtom)
3040
+ name_idx += 1
3041
+ trailer: uni.AtomTrailer | None = None
3042
+ while dot := self.match_token(Tok.DOT):
3043
+ target = trailer if trailer else cur_element
3044
+ right = self.consume(uni.NameAtom)
3045
+ name_idx += 2
3046
+ trailer = uni.AtomTrailer(
3047
+ target=target,
3048
+ right=right,
3049
+ is_attr=True,
3050
+ is_null_ok=False,
3051
+ kid=[target, dot, right],
3052
+ )
3053
+ name = trailer if trailer else cur_element
3054
+ if not isinstance(name, (uni.NameAtom, uni.AtomTrailer)):
3055
+ raise TypeError(
3056
+ f"Expected name to be either NameAtom or AtomTrailer, got {type(name)}"
3057
+ )
3058
+ return uni.MatchValue(
3059
+ value=name,
3060
+ kid=[name, *self.flat_cur_nodes[name_idx:]],
3061
+ )
3062
+
2949
3063
  def class_pattern(self, _: None) -> uni.MatchArch:
2950
3064
  """Grammar rule.
2951
3065
 
@@ -3052,6 +3166,8 @@ class JacParser(Transform[uni.Source, uni.Module]):
3052
3166
  Tok.FSTR_BESC,
3053
3167
  Tok.FSTR_PIECE,
3054
3168
  Tok.FSTR_SQ_PIECE,
3169
+ Tok.FSTR_TRIPLE_PIECE,
3170
+ Tok.FSTR_SQ_TRIPLE_PIECE,
3055
3171
  ]:
3056
3172
  ret_type = uni.String
3057
3173
  if token.type == Tok.FSTR_BESC:
@@ -37,6 +37,7 @@ class DefUsePass(UniPass):
37
37
  + node.get_all_sub_nodes(uni.DisengageStmt)
38
38
  + node.get_all_sub_nodes(uni.EdgeOpRef)
39
39
  + node.get_all_sub_nodes(uni.EventSignature)
40
+ + node.get_all_sub_nodes(uni.TypedCtxBlock)
40
41
  ):
41
42
  i.from_walker = True
42
43
 
@@ -64,7 +64,6 @@ TOKEN_AST_MAP: dict[Tok, type[ast3.AST]] = {
64
64
  Tok.MINUS: ast3.Sub,
65
65
  Tok.SUB_EQ: ast3.Sub,
66
66
  Tok.BW_NOT: ast3.Invert,
67
- Tok.BW_NOT_EQ: ast3.Invert,
68
67
  Tok.NOT: ast3.Not,
69
68
  Tok.EQ: ast3.NotEq,
70
69
  Tok.EE: ast3.Eq,
@@ -92,11 +91,34 @@ UNARY_OP_MAP: dict[Tok, type[ast3.unaryop]] = {
92
91
  class PyastGenPass(UniPass):
93
92
  """Jac blue transpilation to python pass."""
94
93
 
94
+ # Builtins that should be imported from jaclang.runtimelib.builtin
95
+ KNOWN_BUILTINS = {
96
+ "abstractmethod",
97
+ "ClassVar",
98
+ "override",
99
+ "printgraph",
100
+ "jid",
101
+ "jobj",
102
+ "grant",
103
+ "revoke",
104
+ "allroots",
105
+ "save",
106
+ "commit",
107
+ "NoPerm",
108
+ "ReadPerm",
109
+ "ConnectPerm",
110
+ "WritePerm",
111
+ }
112
+
95
113
  def before_pass(self) -> None:
114
+ self.child_passes: list[PyastGenPass] = []
96
115
  for i in self.ir_in.impl_mod + self.ir_in.test_mod:
97
- PyastGenPass(ir_in=i, prog=self.prog)
116
+ child_pass = PyastGenPass(ir_in=i, prog=self.prog)
117
+ self.child_passes.append(child_pass)
98
118
  self.debuginfo: dict[str, list[str]] = {"jac_mods": []}
99
119
  self.already_added: list[str] = []
120
+ self.jaclib_imports: set[str] = set() # Track individual jaclib imports
121
+ self.builtin_imports: set[str] = set() # Track individual builtin imports
100
122
  self.preamble: list[ast3.AST] = [
101
123
  self.sync(
102
124
  ast3.ImportFrom(
@@ -106,40 +128,6 @@ class PyastGenPass(UniPass):
106
128
  ),
107
129
  jac_node=self.ir_out,
108
130
  ),
109
- (
110
- self.sync(
111
- ast3.ImportFrom(
112
- module="jaclang.runtimelib.builtin",
113
- names=[
114
- self.sync(
115
- ast3.alias(
116
- name="*",
117
- asname=None,
118
- )
119
- )
120
- ],
121
- level=0,
122
- ),
123
- jac_node=self.ir_out,
124
- )
125
- ),
126
- (
127
- self.sync(
128
- ast3.ImportFrom(
129
- module="jaclang",
130
- names=[
131
- self.sync(
132
- ast3.alias(
133
- name="JacMachineInterface",
134
- asname=settings.pyout_jaclib_alias,
135
- )
136
- ),
137
- ],
138
- level=0,
139
- ),
140
- jac_node=self.ir_out,
141
- )
142
- ),
143
131
  ]
144
132
 
145
133
  def enter_node(self, node: uni.UniNode) -> None:
@@ -161,15 +149,31 @@ class PyastGenPass(UniPass):
161
149
 
162
150
  def jaclib_obj(self, obj_name: str) -> ast3.Name | ast3.Attribute:
163
151
  """Return the object from jaclib as ast node based on the import config."""
164
- return self.sync(
165
- ast3.Attribute(
166
- value=self.sync(
167
- ast3.Name(id=settings.pyout_jaclib_alias, ctx=ast3.Load())
168
- ),
169
- attr=obj_name,
170
- ctx=ast3.Load(),
152
+ if settings.library_mode:
153
+ self.jaclib_imports.add(obj_name)
154
+ return self.sync(ast3.Name(id=obj_name, ctx=ast3.Load()))
155
+ else:
156
+ self.needs_jaclib()
157
+ return self.sync(
158
+ ast3.Attribute(
159
+ value=self.sync(
160
+ ast3.Name(id=settings.pyout_jaclib_alias, ctx=ast3.Load())
161
+ ),
162
+ attr=obj_name,
163
+ ctx=ast3.Load(),
164
+ )
171
165
  )
172
- )
166
+
167
+ def builtin_name(self, name: str) -> ast3.Name:
168
+ """Return a builtin name and track it for importing.
169
+
170
+ Note: Some names like 'Enum' are provided by other imports (e.g., needs_enum)
171
+ and should not be added to builtin_imports.
172
+ """
173
+ # Enum is imported via needs_enum, not from builtins
174
+ if name not in ["Enum"]:
175
+ self.builtin_imports.add(name)
176
+ return self.sync(ast3.Name(id=name, ctx=ast3.Load()))
173
177
 
174
178
  def _add_preamble_once(self, key: str, node: ast3.AST) -> None:
175
179
  """Append an import statement to the preamble once."""
@@ -187,15 +191,6 @@ class PyastGenPass(UniPass):
187
191
  ),
188
192
  )
189
193
 
190
- def needs_mtllm(self) -> None:
191
- """Ensure byLLM is imported only once."""
192
- self._add_preamble_once(
193
- self.needs_mtllm.__name__,
194
- ast3.Import(
195
- names=[self.sync(ast3.alias(name="byllm"), jac_node=self.ir_out)]
196
- ),
197
- )
198
-
199
194
  def needs_enum(self) -> None:
200
195
  """Ensure Enum utilities are imported only once."""
201
196
  self._add_preamble_once(
@@ -221,6 +216,24 @@ class PyastGenPass(UniPass):
221
216
  ),
222
217
  )
223
218
 
219
+ def needs_jaclib(self) -> None:
220
+ """Ensure JacMachineInterface is imported only once."""
221
+ self._add_preamble_once(
222
+ self.needs_jaclib.__name__,
223
+ ast3.ImportFrom(
224
+ module="jaclang",
225
+ names=[
226
+ self.sync(
227
+ ast3.alias(
228
+ name="JacMachineInterface",
229
+ asname=settings.pyout_jaclib_alias,
230
+ )
231
+ )
232
+ ],
233
+ level=0,
234
+ ),
235
+ )
236
+
224
237
  def _get_sem_decorator(self, node: uni.UniNode) -> ast3.Call | None:
225
238
  """Create a semstring decorator for the given semantic strings.
226
239
 
@@ -283,7 +296,10 @@ class PyastGenPass(UniPass):
283
296
  else {}
284
297
  )
285
298
 
286
- if not semstr and not inner_semstr:
299
+ # Only add sem decorator if there's actual semantic content
300
+ if not semstr and (
301
+ not inner_semstr or all(not v for v in inner_semstr.values())
302
+ ):
287
303
  return None
288
304
 
289
305
  return self.sync(
@@ -417,6 +433,48 @@ class PyastGenPass(UniPass):
417
433
  node.gen.py_ast = node.tag.gen.py_ast
418
434
 
419
435
  def exit_module(self, node: uni.Module) -> None:
436
+ # Check if any child passes (impl_mod or test_mod) needed jaclib
437
+ for child_pass in self.child_passes:
438
+ if "needs_jaclib" in child_pass.already_added:
439
+ self.needs_jaclib()
440
+ break
441
+ # Merge jaclib and builtin imports from child passes
442
+ if settings.library_mode:
443
+ self.jaclib_imports.update(child_pass.jaclib_imports)
444
+ self.builtin_imports.update(child_pass.builtin_imports)
445
+
446
+ # Add builtin imports if any were used
447
+ if self.builtin_imports:
448
+ self.preamble.append(
449
+ self.sync(
450
+ ast3.ImportFrom(
451
+ module="jaclang.runtimelib.builtin",
452
+ names=[
453
+ self.sync(ast3.alias(name=name, asname=None))
454
+ for name in sorted(self.builtin_imports)
455
+ ],
456
+ level=0,
457
+ ),
458
+ jac_node=self.ir_out,
459
+ )
460
+ )
461
+
462
+ # Add library mode imports at the end of preamble
463
+ if settings.library_mode and self.jaclib_imports:
464
+ self.preamble.append(
465
+ self.sync(
466
+ ast3.ImportFrom(
467
+ module="jaclang.lib",
468
+ names=[
469
+ self.sync(ast3.alias(name=name, asname=None))
470
+ for name in sorted(self.jaclib_imports)
471
+ ],
472
+ level=0,
473
+ ),
474
+ jac_node=self.ir_out,
475
+ )
476
+ )
477
+
420
478
  clean_body = [i for i in node.body if not isinstance(i, uni.ImplDef)]
421
479
  pre_body: list[uni.UniNode] = []
422
480
  for pbody in node.impl_mod:
@@ -722,7 +780,7 @@ class PyastGenPass(UniPass):
722
780
  decorators.append(sem_decorator)
723
781
 
724
782
  base_classes = [cast(ast3.expr, i.gen.py_ast[0]) for i in node.base_classes]
725
- base_classes.append(self.sync(ast3.Name(id="Enum", ctx=ast3.Load())))
783
+ base_classes.append(self.builtin_name("Enum"))
726
784
  node.gen.py_ast = [
727
785
  self.sync(
728
786
  ast3.ClassDef(
@@ -744,23 +802,9 @@ class PyastGenPass(UniPass):
744
802
  self, model: ast3.expr, caller: ast3.expr, args: ast3.Dict
745
803
  ) -> ast3.Call:
746
804
  """Reusable method to codegen call_llm(model, caller, args)."""
747
- self.needs_mtllm()
748
- mtir_cls_ast = self.sync(
749
- ast3.Attribute(
750
- value=self.sync(ast3.Name(id="byllm", ctx=ast3.Load())),
751
- attr="MTIR",
752
- ctx=ast3.Load(),
753
- )
754
- )
755
805
  mtir_ast = self.sync(
756
806
  ast3.Call(
757
- func=self.sync(
758
- ast3.Attribute(
759
- value=mtir_cls_ast,
760
- attr="factory",
761
- ctx=ast3.Load(),
762
- )
763
- ),
807
+ func=self.jaclib_obj("get_mtir"),
764
808
  args=[],
765
809
  keywords=[
766
810
  self.sync(
@@ -928,7 +972,9 @@ class PyastGenPass(UniPass):
928
972
  if isinstance(node.signature, uni.EventSignature):
929
973
  decorator_list.append(
930
974
  self.jaclib_obj(
931
- "entry" if node.signature.event.name == Tok.KW_ENTRY else "exit"
975
+ "on_entry"
976
+ if node.signature.event.name == Tok.KW_ENTRY
977
+ else "on_exit"
932
978
  )
933
979
  )
934
980
 
@@ -943,11 +989,9 @@ class PyastGenPass(UniPass):
943
989
  )
944
990
  )
945
991
  if node.is_abstract:
946
- decorator_list.append(
947
- self.sync(ast3.Name(id="abstractmethod", ctx=ast3.Load()))
948
- )
992
+ decorator_list.append(self.builtin_name("abstractmethod"))
949
993
  if node.is_override:
950
- decorator_list.append(self.sync(ast3.Name(id="override", ctx=ast3.Load())))
994
+ decorator_list.append(self.builtin_name("override"))
951
995
  if node.is_static:
952
996
  decorator_list.insert(
953
997
  0, self.sync(ast3.Name(id="staticmethod", ctx=ast3.Load()))
@@ -1145,7 +1189,7 @@ class PyastGenPass(UniPass):
1145
1189
  elif is_static_var:
1146
1190
  annotation = self.sync(
1147
1191
  ast3.Subscript(
1148
- value=self.sync(ast3.Name(id="ClassVar", ctx=ast3.Load())),
1192
+ value=self.builtin_name("ClassVar"),
1149
1193
  slice=cast(ast3.expr, annotation),
1150
1194
  ctx=ast3.Load(),
1151
1195
  )
@@ -1222,8 +1266,29 @@ class PyastGenPass(UniPass):
1222
1266
  ]
1223
1267
 
1224
1268
  def exit_typed_ctx_block(self, node: uni.TypedCtxBlock) -> None:
1225
- # TODO: Come back
1226
- pass
1269
+ loc = self.sync(
1270
+ ast3.Name(id=Con.HERE.value, ctx=ast3.Load())
1271
+ if node.from_walker
1272
+ else ast3.Name(id=Con.VISITOR.value, ctx=ast3.Load())
1273
+ )
1274
+ node.gen.py_ast = [
1275
+ self.sync(
1276
+ ast3.If(
1277
+ test=self.sync(
1278
+ ast3.Call(
1279
+ func=self.sync(ast3.Name(id="isinstance", ctx=ast3.Load())),
1280
+ args=[
1281
+ loc,
1282
+ cast(ast3.expr, node.type_ctx.gen.py_ast[0]),
1283
+ ],
1284
+ keywords=[],
1285
+ )
1286
+ ),
1287
+ body=cast(list[ast3.stmt], self.resolve_stmt_block(node.body)),
1288
+ orelse=[],
1289
+ )
1290
+ )
1291
+ ]
1227
1292
 
1228
1293
  def exit_if_stmt(self, node: uni.IfStmt) -> None:
1229
1294
  node.gen.py_ast = [
@@ -1999,7 +2064,7 @@ class PyastGenPass(UniPass):
1999
2064
  keywords.append(
2000
2065
  self.sync(
2001
2066
  ast3.keyword(
2002
- arg="filter",
2067
+ arg="filter_on",
2003
2068
  value=cast(
2004
2069
  ast3.expr,
2005
2070
  node.op.edge_spec.filter_cond.gen.py_ast[0],
@@ -2206,7 +2271,7 @@ class PyastGenPass(UniPass):
2206
2271
  node.gen.py_ast = [
2207
2272
  self.sync(
2208
2273
  ast3.Call(
2209
- func=self.sync(ast3.Name(id="jobj", ctx=ast3.Load())),
2274
+ func=self.builtin_name("jobj"),
2210
2275
  args=[],
2211
2276
  keywords=[
2212
2277
  self.sync(
@@ -2431,7 +2496,7 @@ class PyastGenPass(UniPass):
2431
2496
  node.gen.py_ast = [
2432
2497
  self.sync(
2433
2498
  ast3.Call(
2434
- func=self.jaclib_obj("filter"),
2499
+ func=self.jaclib_obj("filter_on"),
2435
2500
  args=[],
2436
2501
  keywords=[
2437
2502
  self.sync(
@@ -2454,7 +2519,7 @@ class PyastGenPass(UniPass):
2454
2519
  node.gen.py_ast = [
2455
2520
  self.sync(
2456
2521
  ast3.Call(
2457
- func=self.jaclib_obj("assign"),
2522
+ func=self.jaclib_obj("assign_all"),
2458
2523
  args=cast(
2459
2524
  list[ast3.expr],
2460
2525
  [node.target.gen.py_ast[0], node.right.gen.py_ast[0]],
@@ -2685,7 +2750,7 @@ class PyastGenPass(UniPass):
2685
2750
  func=self.sync(
2686
2751
  ast3.Attribute(
2687
2752
  value=pynode,
2688
- attr=f"_{cur.edge_dir.name.lower()}",
2753
+ attr=f"edge_{cur.edge_dir.name.lower()}",
2689
2754
  ctx=ast3.Load(),
2690
2755
  )
2691
2756
  ),
@@ -3026,6 +3091,9 @@ class PyastGenPass(UniPass):
3026
3091
 
3027
3092
  def exit_name(self, node: uni.Name) -> None:
3028
3093
  name = node.sym_name
3094
+ # Track if this name is a known builtin
3095
+ if name in self.KNOWN_BUILTINS:
3096
+ self.builtin_imports.add(name)
3029
3097
  node.gen.py_ast = [self.sync(ast3.Name(id=name, ctx=node.py_ctx_func()))]
3030
3098
 
3031
3099
  def exit_float(self, node: uni.Float) -> None:
@@ -96,6 +96,8 @@ class PyastBuildPass(Transform[uni.PythonModuleAst, uni.Module]):
96
96
  body: list[stmt]
97
97
  type_ignores: list[TypeIgnore]
98
98
  """
99
+ if not node.body:
100
+ return uni.Module.make_stub(inject_src=self.ir_in)
99
101
  elements: list[uni.UniNode] = [self.convert(i) for i in node.body]
100
102
  elements[0] = (
101
103
  elements[0].expr
@@ -175,7 +175,6 @@ class TypeCheckerPassTests(TestCase):
175
175
  """, program.errors_had[0].pretty_print())
176
176
 
177
177
  def test_param_arg_match(self) -> None:
178
- path = self.fixture_abs_path("checker_param_types.jac")
179
178
  program = JacProgram()
180
179
  path = self.fixture_abs_path("checker_arg_param_match.jac")
181
180
  mod = program.compile(path)