jaclang 0.8.4__py3-none-any.whl → 0.8.5__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 (53) hide show
  1. jaclang/cli/cli.py +74 -22
  2. jaclang/compiler/jac.lark +3 -3
  3. jaclang/compiler/larkparse/jac_parser.py +2 -2
  4. jaclang/compiler/parser.py +14 -21
  5. jaclang/compiler/passes/main/__init__.py +3 -1
  6. jaclang/compiler/passes/main/binder_pass.py +594 -0
  7. jaclang/compiler/passes/main/import_pass.py +8 -256
  8. jaclang/compiler/passes/main/inheritance_pass.py +2 -2
  9. jaclang/compiler/passes/main/pyast_gen_pass.py +35 -69
  10. jaclang/compiler/passes/main/pyast_load_pass.py +24 -13
  11. jaclang/compiler/passes/main/sem_def_match_pass.py +1 -1
  12. jaclang/compiler/passes/main/tests/fixtures/M1.jac +3 -0
  13. jaclang/compiler/passes/main/tests/fixtures/sym_binder.jac +47 -0
  14. jaclang/compiler/passes/main/tests/test_binder_pass.py +111 -0
  15. jaclang/compiler/passes/main/tests/test_pyast_gen_pass.py +13 -13
  16. jaclang/compiler/passes/main/tests/test_sem_def_match_pass.py +6 -6
  17. jaclang/compiler/passes/tool/doc_ir_gen_pass.py +2 -0
  18. jaclang/compiler/passes/tool/tests/fixtures/simple_walk_fmt.jac +6 -0
  19. jaclang/compiler/program.py +15 -8
  20. jaclang/compiler/tests/test_sr_errors.py +32 -0
  21. jaclang/compiler/unitree.py +21 -15
  22. jaclang/langserve/engine.jac +23 -4
  23. jaclang/langserve/tests/test_server.py +13 -0
  24. jaclang/runtimelib/importer.py +33 -62
  25. jaclang/runtimelib/utils.py +29 -0
  26. jaclang/tests/fixtures/pyfunc_fmt.py +60 -0
  27. jaclang/tests/fixtures/pyfunc_fstr.py +25 -0
  28. jaclang/tests/fixtures/pyfunc_kwesc.py +33 -0
  29. jaclang/tests/fixtures/python_run_test.py +19 -0
  30. jaclang/tests/test_cli.py +67 -0
  31. jaclang/tests/test_language.py +96 -1
  32. jaclang/utils/lang_tools.py +3 -3
  33. jaclang/utils/module_resolver.py +90 -0
  34. jaclang/utils/symtable_test_helpers.py +125 -0
  35. jaclang/utils/test.py +3 -4
  36. jaclang/vendor/interegular/__init__.py +34 -0
  37. jaclang/vendor/interegular/comparator.py +163 -0
  38. jaclang/vendor/interegular/fsm.py +1015 -0
  39. jaclang/vendor/interegular/patterns.py +732 -0
  40. jaclang/vendor/interegular/py.typed +0 -0
  41. jaclang/vendor/interegular/utils/__init__.py +15 -0
  42. jaclang/vendor/interegular/utils/simple_parser.py +165 -0
  43. jaclang/vendor/interegular-0.3.3.dist-info/INSTALLER +1 -0
  44. jaclang/vendor/interegular-0.3.3.dist-info/LICENSE.txt +21 -0
  45. jaclang/vendor/interegular-0.3.3.dist-info/METADATA +64 -0
  46. jaclang/vendor/interegular-0.3.3.dist-info/RECORD +20 -0
  47. jaclang/vendor/interegular-0.3.3.dist-info/REQUESTED +0 -0
  48. jaclang/vendor/interegular-0.3.3.dist-info/WHEEL +5 -0
  49. jaclang/vendor/interegular-0.3.3.dist-info/top_level.txt +1 -0
  50. {jaclang-0.8.4.dist-info → jaclang-0.8.5.dist-info}/METADATA +1 -1
  51. {jaclang-0.8.4.dist-info → jaclang-0.8.5.dist-info}/RECORD +53 -29
  52. {jaclang-0.8.4.dist-info → jaclang-0.8.5.dist-info}/WHEEL +0 -0
  53. {jaclang-0.8.4.dist-info → jaclang-0.8.5.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,594 @@
1
+ """Binding Pass for the Jac compiler."""
2
+
3
+ import ast as py_ast
4
+ import os
5
+ from typing import Optional
6
+
7
+ import jaclang.compiler.unitree as uni
8
+ from jaclang.compiler.passes import UniPass
9
+ from jaclang.compiler.unitree import UniScopeNode
10
+ from jaclang.runtimelib.utils import read_file_with_encoding
11
+
12
+
13
+ class BinderPass(UniPass):
14
+ """Jac Binder pass."""
15
+
16
+ def before_pass(self) -> None:
17
+ """Before pass."""
18
+ if self.prog.mod.main == self.ir_in:
19
+ self.load_builtins()
20
+ self.scope_stack: list[UniScopeNode] = []
21
+ self.globals_stack: list[list[uni.Symbol]] = []
22
+
23
+ ###########################################################
24
+ ## Helper functions for symbol table stack manipulations ##
25
+ ###########################################################
26
+ def push_scope_and_link(self, key_node: uni.UniScopeNode) -> None:
27
+ """Add scope into scope stack."""
28
+ if not len(self.scope_stack):
29
+ self.scope_stack.append(key_node)
30
+ else:
31
+ self.scope_stack.append(self.cur_scope.link_kid_scope(key_node=key_node))
32
+
33
+ def pop_scope(self) -> UniScopeNode:
34
+ """Remove current scope from scope stack."""
35
+ return self.scope_stack.pop()
36
+
37
+ @property
38
+ def cur_scope(self) -> UniScopeNode:
39
+ """Return current scope."""
40
+ return self.scope_stack[-1]
41
+
42
+ @property
43
+ def cur_globals(self) -> list[uni.Symbol]:
44
+ """Get current global symbols."""
45
+ if len(self.globals_stack):
46
+ return self.globals_stack[-1]
47
+ else:
48
+ return []
49
+
50
+ # TODO: Every call for this function should be moved to symbol table it self
51
+ def check_global(self, node_name: str) -> Optional[uni.Symbol]:
52
+ """Check if symbol exists in global scope."""
53
+ for symbol in self.cur_globals:
54
+ if symbol.sym_name == node_name:
55
+ return symbol
56
+ return None
57
+
58
+ @property
59
+ def cur_module_scope(self) -> UniScopeNode:
60
+ """Return the current module."""
61
+ return self.scope_stack[0]
62
+
63
+ ###############################################
64
+ ## Handling for nodes that creates new scope ##
65
+ ###############################################
66
+ SCOPE_NODES = (
67
+ uni.MatchCase,
68
+ uni.DictCompr,
69
+ uni.ListCompr,
70
+ uni.GenCompr,
71
+ uni.SetCompr,
72
+ uni.LambdaExpr,
73
+ uni.WithStmt,
74
+ uni.WhileStmt,
75
+ uni.InForStmt,
76
+ uni.IterForStmt,
77
+ uni.TryStmt,
78
+ uni.Except,
79
+ uni.FinallyStmt,
80
+ uni.IfStmt,
81
+ uni.ElseIf,
82
+ uni.ElseStmt,
83
+ uni.TypedCtxBlock,
84
+ uni.Module,
85
+ uni.Ability,
86
+ uni.Test,
87
+ uni.Archetype,
88
+ uni.ImplDef,
89
+ uni.SemDef,
90
+ uni.Enum,
91
+ )
92
+
93
+ GLOBAL_STACK_NODES = (uni.Ability, uni.Archetype)
94
+
95
+ def enter_node(self, node: uni.UniNode) -> None:
96
+ if isinstance(node, self.SCOPE_NODES):
97
+ self.push_scope_and_link(node)
98
+ if isinstance(node, self.GLOBAL_STACK_NODES):
99
+ self.globals_stack.append([])
100
+ super().enter_node(node)
101
+
102
+ def exit_node(self, node: uni.UniNode) -> None:
103
+ if isinstance(node, self.SCOPE_NODES):
104
+ self.pop_scope()
105
+ if isinstance(node, self.GLOBAL_STACK_NODES):
106
+ self.globals_stack.pop()
107
+ super().exit_node(node)
108
+
109
+ #########################################
110
+ ## AtomTrailer and Symbol Chain Helpers ##
111
+ #########################################
112
+ def handle_symbol_chain(self, node: uni.AtomTrailer, operation: str) -> bool:
113
+ """Handle symbol chains in AtomTrailer nodes."""
114
+ attr_list = node.as_attr_list
115
+ if not attr_list:
116
+ return False
117
+
118
+ first_obj = attr_list[0]
119
+ first_obj_sym = self.cur_scope.lookup(first_obj.sym_name)
120
+
121
+ if not first_obj_sym:
122
+ return False
123
+
124
+ if first_obj_sym.imported:
125
+ return self._handle_imported_chain(node, operation)
126
+ else:
127
+ return self._handle_local_chain(first_obj_sym, attr_list, operation)
128
+
129
+ def _handle_imported_chain(self, node: uni.AtomTrailer, operation: str) -> bool:
130
+ """Handle chains that start with imported symbols."""
131
+ try:
132
+ self.resolve_import(node)
133
+ return True
134
+ except Exception:
135
+ return False
136
+
137
+ def _handle_local_chain(
138
+ self, first_sym: uni.Symbol, attr_list: list[uni.AstSymbolNode], operation: str
139
+ ) -> bool:
140
+ """Handle chains within local scope."""
141
+ try:
142
+ current_sym_tab = first_sym.symbol_table
143
+
144
+ if operation == "define":
145
+ first_sym.add_defn(attr_list[0].name_spec)
146
+ else: # operation == 'use'
147
+ first_sym.add_use(attr_list[0].name_spec)
148
+
149
+ for attr_node in attr_list[1:]:
150
+ if not current_sym_tab:
151
+ break
152
+
153
+ attr_sym = current_sym_tab.lookup(attr_node.sym_name)
154
+ if not attr_sym:
155
+ # TODO # self.log_error(
156
+ # f"Could not resolve attribute '{attr_node.sym_name}' in chain"
157
+ # )
158
+ return False
159
+
160
+ if operation == "define":
161
+ attr_sym.add_defn(attr_node)
162
+ else: # operation == 'use'
163
+ attr_sym.add_use(attr_node)
164
+
165
+ current_sym_tab = attr_sym.symbol_table
166
+ return True
167
+ except Exception:
168
+ return False
169
+
170
+ def handle_simple_symbol(
171
+ self, symbol_node: uni.AstSymbolNode, operation: str
172
+ ) -> bool:
173
+ """Handle simple symbol nodes (non-chain)."""
174
+ glob_sym = self.check_global(symbol_node.sym_name)
175
+
176
+ if glob_sym:
177
+ symbol_node.name_spec._sym = glob_sym
178
+ if operation == "define":
179
+ glob_sym.add_defn(symbol_node)
180
+ else: # operation == 'use'
181
+ glob_sym.add_use(symbol_node)
182
+ return True
183
+ else:
184
+ if operation == "define":
185
+ self.cur_scope.def_insert(symbol_node, single_decl="assignment")
186
+ else: # operation == 'use'
187
+ found_symbol = self.cur_scope.lookup(symbol_node.sym_name)
188
+ if found_symbol:
189
+ found_symbol.add_use(symbol_node)
190
+ else:
191
+ # Symbol not found, could be first use - define it
192
+ self.cur_scope.def_insert(symbol_node)
193
+ return True
194
+
195
+ #####################################
196
+ ## Main logic for symbols creation ##
197
+ #####################################
198
+ def enter_assignment(self, node: uni.Assignment) -> None:
199
+ """Enter assignment node."""
200
+ for target in node.target:
201
+ self._process_assignment_target(target)
202
+
203
+ def _process_assignment_target(self, target: uni.Expr) -> None:
204
+ """Process individual assignment target."""
205
+ if isinstance(target, uni.AtomTrailer):
206
+ self.handle_symbol_chain(target, "define")
207
+ elif isinstance(target, uni.AstSymbolNode):
208
+ self.handle_simple_symbol(target, "define")
209
+ else:
210
+ pass
211
+ # TODO
212
+ # self.log_error("Assignment target not valid")
213
+
214
+ def enter_ability(self, node: uni.Ability) -> None:
215
+ """Enter ability node and set up method context."""
216
+ assert node.parent_scope is not None
217
+ node.parent_scope.def_insert(node, access_spec=node, single_decl="ability")
218
+
219
+ if node.is_method:
220
+ self._setup_method_context(node)
221
+
222
+ def _setup_method_context(self, node: uni.Ability) -> None:
223
+ """Set up method context by defining 'self', 'super', and event context symbols if needed."""
224
+ self_name = uni.Name.gen_stub_from_node(node, "self")
225
+ self.cur_scope.def_insert(self_name)
226
+ node.parent_of_type(uni.Archetype)
227
+
228
+ self.cur_scope.def_insert(
229
+ uni.Name.gen_stub_from_node(node, "super", set_name_of=node.method_owner)
230
+ )
231
+
232
+ if node.signature and isinstance(node.signature, uni.EventSignature):
233
+ self._setup_event_context(node)
234
+
235
+ def _setup_event_context(self, node: uni.Ability) -> None:
236
+ """Set up 'here' and 'visitor' symbols for event signatures."""
237
+ try:
238
+ arch_type = node.method_owner.arch_type.name
239
+
240
+ if arch_type == "KW_WALKER":
241
+ self._setup_walker_context(node)
242
+ elif arch_type == "KW_NODE":
243
+ self._setup_node_context(node)
244
+ except Exception:
245
+ pass
246
+ # TODO
247
+ # self.log_error(f"Error while setting up event context: {str(e)}")
248
+
249
+ def _setup_walker_context(self, node: uni.Ability) -> None:
250
+ """Init 'here' for walker; link symbol table to parent."""
251
+ self.cur_scope.def_insert(
252
+ uni.Name.gen_stub_from_node(node, "here", set_name_of=node.method_owner)
253
+ )
254
+ node_name = node.signature.arch_tag_info.unparse()
255
+ self.cur_scope.lookup(node_name).symbol_table
256
+
257
+ def _setup_node_context(self, node: uni.Ability) -> None:
258
+ """Init 'visitor' for node; link symbol table to parent."""
259
+ self.cur_scope.def_insert(
260
+ uni.Name.gen_stub_from_node(node, "visitor", set_name_of=node.method_owner)
261
+ )
262
+ walker_name = node.signature.arch_tag_info.unparse()
263
+ self.cur_scope.lookup(walker_name).symbol_table
264
+
265
+ def enter_global_stmt(self, node: uni.GlobalStmt) -> None:
266
+ """Enter global statement."""
267
+ for name in node.target:
268
+ sym = self.cur_module_scope.lookup(name.sym_name)
269
+ if not sym:
270
+ sym = self.cur_module_scope.def_insert(name, single_decl="assignment")
271
+ self.globals_stack[-1].append(sym)
272
+
273
+ def enter_import(self, node: uni.Import) -> None:
274
+ """Enter import statement."""
275
+ if node.is_absorb:
276
+ return None
277
+ for item in node.items:
278
+ if item.alias:
279
+ self.cur_scope.def_insert(
280
+ item.alias, imported=True, single_decl="import"
281
+ )
282
+ else:
283
+ self.cur_scope.def_insert(item, imported=True)
284
+
285
+ def enter_test(self, node: uni.Test) -> None:
286
+ """Enter test node and add unittest methods."""
287
+ import unittest
288
+
289
+ for method_name in [
290
+ j for j in dir(unittest.TestCase()) if j.startswith("assert")
291
+ ]:
292
+ self.cur_scope.def_insert(
293
+ uni.Name.gen_stub_from_node(node, method_name, set_name_of=node)
294
+ )
295
+
296
+ def enter_archetype(self, node: uni.Archetype) -> None:
297
+ """Enter archetype node."""
298
+ assert node.parent_scope is not None
299
+ node.parent_scope.def_insert(node, access_spec=node, single_decl="archetype")
300
+
301
+ def enter_impl_def(self, node: uni.ImplDef) -> None:
302
+ """Enter implementation definition."""
303
+ assert node.parent_scope is not None
304
+ node.parent_scope.def_insert(node, single_decl="impl")
305
+
306
+ def enter_sem_def(self, node: uni.SemDef) -> None:
307
+ """Enter semantic definition."""
308
+ assert node.parent_scope is not None
309
+ node.parent_scope.def_insert(node, single_decl="sem")
310
+
311
+ def enter_enum(self, node: uni.Enum) -> None:
312
+ """Enter enum node."""
313
+ assert node.parent_scope is not None
314
+ node.parent_scope.def_insert(node, access_spec=node, single_decl="enum")
315
+
316
+ def enter_param_var(self, node: uni.ParamVar) -> None:
317
+ """Enter parameter variable."""
318
+ self.cur_scope.def_insert(node)
319
+
320
+ def enter_has_var(self, node: uni.HasVar) -> None:
321
+ """Enter has variable."""
322
+ if isinstance(node.parent, uni.ArchHas):
323
+ self.cur_scope.def_insert(
324
+ node, single_decl="has var", access_spec=node.parent
325
+ )
326
+ else:
327
+ self.ice("Inconsistency in AST, has var should be under arch has")
328
+
329
+ def enter_in_for_stmt(self, node: uni.InForStmt) -> None:
330
+ """Enter for-in statement."""
331
+ self._process_assignment_target(node.target)
332
+
333
+ def enter_func_call(self, node: uni.FuncCall) -> None:
334
+ """Enter function call node."""
335
+ if isinstance(node.target, uni.AtomTrailer):
336
+ self.handle_symbol_chain(node.target, "use")
337
+ elif isinstance(node.target, uni.AstSymbolNode):
338
+ if self._handle_builtin_symbol(node.target.sym_name, node.target):
339
+ return
340
+
341
+ self.handle_simple_symbol(node.target, "use")
342
+ else:
343
+ pass
344
+ # TODO
345
+ # self.log_error("Function call target not valid")
346
+
347
+ ##################################
348
+ ## Comprehensions support ##
349
+ ##################################
350
+ def enter_list_compr(self, node: uni.ListCompr) -> None:
351
+ """Enter list comprehension with correct traversal order."""
352
+ self.prune()
353
+ for compr in node.compr:
354
+ self.traverse(compr)
355
+ self.traverse(node.out_expr)
356
+
357
+ def enter_gen_compr(self, node: uni.GenCompr) -> None:
358
+ """Enter generator comprehension."""
359
+ self.enter_list_compr(node)
360
+
361
+ def enter_set_compr(self, node: uni.SetCompr) -> None:
362
+ """Enter set comprehension."""
363
+ self.enter_list_compr(node)
364
+
365
+ def enter_dict_compr(self, node: uni.DictCompr) -> None:
366
+ """Enter dictionary comprehension."""
367
+ self.prune()
368
+ for compr in node.compr:
369
+ self.traverse(compr)
370
+ self.traverse(node.kv_pair)
371
+
372
+ def enter_inner_compr(self, node: uni.InnerCompr) -> None:
373
+ """Enter inner comprehension."""
374
+ if isinstance(node.target, uni.AtomTrailer):
375
+ self.cur_scope.chain_def_insert(node.target.as_attr_list)
376
+ elif isinstance(node.target, uni.AstSymbolNode):
377
+ self.cur_scope.def_insert(node.target)
378
+ else:
379
+ pass
380
+ # TODO
381
+ # self.log_error("Named target not valid")
382
+
383
+ #####################
384
+ ## Collecting uses ##
385
+ #####################
386
+ def exit_name(self, node: uni.Name) -> None:
387
+ """Exit name node and record usage."""
388
+ if isinstance(node.parent, uni.AtomTrailer):
389
+ return
390
+
391
+ # Check if this is a builtin symbol first
392
+ if self._handle_builtin_symbol(node.value, node):
393
+ return
394
+
395
+ glob_sym = self.check_global(node.value)
396
+ if glob_sym:
397
+ if not node.sym:
398
+ glob_sym.add_use(node)
399
+ else:
400
+ self.cur_scope.use_lookup(node, sym_table=self.cur_scope)
401
+
402
+ def enter_builtin_type(self, node: uni.BuiltinType) -> None:
403
+ """Enter builtins node like str, int, list, etc."""
404
+ self._handle_builtin_symbol(node.value, node)
405
+
406
+ def enter_expr_as_item(self, node: uni.ExprAsItem) -> None:
407
+ """Enter expression as item (for with statements)."""
408
+ if node.alias:
409
+ self._process_assignment_target(node.alias)
410
+
411
+ ############################
412
+ ## Import Resolution Logic ##
413
+ ############################
414
+ def resolve_import(self, node: uni.UniNode) -> None:
415
+ """Resolve imports for atom trailers like 'apple.color.bla.blah'."""
416
+ if isinstance(node, uni.AtomTrailer):
417
+ self._resolve_atom_trailer_import(node)
418
+ else:
419
+ self.log_warning(
420
+ f"Import resolution not implemented for {type(node).__name__}"
421
+ )
422
+
423
+ def _resolve_assignment_imports(self, node: uni.Assignment) -> None:
424
+ """Handle import resolution for assignment statements."""
425
+ for target in node.target:
426
+ if isinstance(target, uni.AtomTrailer):
427
+ self._resolve_atom_trailer_import(target)
428
+
429
+ def _resolve_for_loop_imports(self, node: uni.InForStmt) -> None:
430
+ """Handle import resolution for for-loop statements."""
431
+ if isinstance(node.target, uni.AtomTrailer):
432
+ self._resolve_atom_trailer_import(node.target)
433
+
434
+ def _resolve_atom_trailer_import(self, atom_trailer: uni.AtomTrailer) -> None:
435
+ """Resolve imports for atom trailer chains."""
436
+ attr_list = atom_trailer.as_attr_list
437
+ if not attr_list:
438
+ raise ValueError("Atom trailer must have at least one attribute")
439
+
440
+ first_obj = attr_list[0]
441
+ first_obj_sym = self.cur_scope.lookup(first_obj.sym_name)
442
+ if not first_obj_sym or not first_obj_sym.imported:
443
+ return
444
+
445
+ import_node = self._find_import_for_symbol(first_obj_sym)
446
+ if not import_node:
447
+ # TODO
448
+ # self.log_error(
449
+ # f"Could not find import statement for symbol '{first_obj_sym.sym_name}'"
450
+ # )
451
+ return
452
+
453
+ module_path = self._get_module_path_from_symbol(first_obj_sym)
454
+ if not module_path:
455
+ # TODO
456
+ # self.log_error("Could not resolve module path for import")
457
+ return
458
+
459
+ linked_module = self._parse_and_link_module(module_path, first_obj_sym)
460
+ if linked_module:
461
+ self._link_attribute_chain(attr_list, first_obj_sym, linked_module)
462
+
463
+ def _find_import_for_symbol(self, symbol: uni.Symbol) -> Optional[uni.Import]:
464
+ """Find the import statement that declares the given symbol."""
465
+ if not symbol.decl:
466
+ return None
467
+
468
+ import_node: uni.Import = symbol.decl.find_parent_of_type(uni.Import)
469
+ if import_node:
470
+ return import_node
471
+ self.ice(f"Symbol '{symbol.sym_name}' does not have a valid import declaration")
472
+ return None
473
+
474
+ # TODO: this is an important function: it should support all 7 types of imports here
475
+ def _get_module_path_from_symbol(self, symbol: uni.Symbol) -> Optional[str]:
476
+ """Extract module path from symbol's import declaration."""
477
+ mod_item_node = symbol.decl.find_parent_of_type(uni.ModuleItem)
478
+ if mod_item_node:
479
+ mod_path_node = mod_item_node.find_parent_of_type(uni.Import).from_loc
480
+ else:
481
+ mod_path_node = symbol.decl.find_parent_of_type(uni.ModulePath)
482
+
483
+ return mod_path_node.resolve_relative_path() if mod_path_node else None
484
+
485
+ def _link_attribute_chain(
486
+ self,
487
+ attr_list: list[uni.AstSymbolNode],
488
+ first_symbol: uni.Symbol,
489
+ current_module: uni.Module,
490
+ ) -> None:
491
+ """Link the full attribute chain by resolving each symbol."""
492
+ current_symbol = first_symbol
493
+ current_sym_table = current_module.sym_tab
494
+
495
+ # Add use for the first symbol
496
+ first_obj = attr_list[0]
497
+ current_symbol.add_use(first_obj)
498
+
499
+ # Iterate through remaining attributes in the chain
500
+ for attr_node in attr_list[1:]:
501
+ if not current_sym_table:
502
+ return
503
+
504
+ attr_symbol = current_sym_table.lookup(attr_node.sym_name)
505
+ if not attr_symbol:
506
+ # self.log_error(
507
+ # f"Could not resolve attribute '{attr_node.sym_name}' in chain"
508
+ # )
509
+ # TODO:
510
+ break
511
+
512
+ attr_symbol.add_use(attr_node)
513
+ current_symbol = attr_symbol
514
+ current_sym_table = current_symbol.symbol_table
515
+
516
+ # TODO:move this to Jac Program
517
+ def _parse_and_link_module(
518
+ self, module_path: str, symbol: uni.Symbol
519
+ ) -> Optional[uni.Module]:
520
+ """Parse the module and link it to the symbol."""
521
+ try:
522
+ if module_path in self.prog.mod.hub:
523
+ existing_module = self.prog.mod.hub[module_path]
524
+ # symbol.symbol_table = existing_module.sym_tab
525
+ return existing_module
526
+
527
+ source_str = read_file_with_encoding(module_path)
528
+
529
+ parsed_module: uni.Module = self.prog.parse_str(
530
+ source_str=source_str, file_path=module_path
531
+ )
532
+ BinderPass(ir_in=parsed_module, prog=self.prog)
533
+ # symbol.symbol_table = parsed_module.sym_tab
534
+
535
+ return parsed_module
536
+
537
+ except Exception:
538
+ # TODO
539
+ # self.log_error(f"Failed to parse module '{module_path}': {str(e)}")
540
+ return None
541
+
542
+ def load_builtins(self) -> None:
543
+ """Load built-in symbols from the builtins.pyi file."""
544
+ try:
545
+ builtins_path = os.path.join(
546
+ os.path.dirname(__file__),
547
+ "../../../vendor/typeshed/stdlib/builtins.pyi",
548
+ )
549
+
550
+ # lets keep this line until typeshed are merged with jaclang
551
+ if not os.path.exists(builtins_path):
552
+ self.log_warning(f"Builtins file not found at {builtins_path}")
553
+ return
554
+
555
+ file_source = read_file_with_encoding(builtins_path)
556
+
557
+ from jaclang.compiler.passes.main.pyast_load_pass import PyastBuildPass
558
+
559
+ mod = PyastBuildPass(
560
+ ir_in=uni.PythonModuleAst(
561
+ py_ast.parse(file_source),
562
+ orig_src=uni.Source(file_source, builtins_path),
563
+ ),
564
+ prog=self.prog,
565
+ ).ir_out
566
+
567
+ if mod:
568
+ self.prog.mod.hub["builtins"] = mod
569
+ BinderPass(ir_in=mod, prog=self.prog)
570
+
571
+ except Exception as e:
572
+ self.log_error(f"Failed to load builtins: {str(e)}")
573
+
574
+ def _is_builtin_symbol(self, symbol_name: str) -> bool:
575
+ """Check if a symbol is a builtin symbol."""
576
+ builtins_mod = self.prog.mod.hub["builtins"]
577
+ return symbol_name in builtins_mod.sym_tab.names_in_scope
578
+
579
+ def _handle_builtin_symbol(
580
+ self, symbol_name: str, target_node: uni.AstSymbolNode
581
+ ) -> bool:
582
+ """Handle builtin symbol lookup and linking."""
583
+ if not self._is_builtin_symbol(symbol_name):
584
+ return False
585
+
586
+ builtins_mod = self.prog.mod.hub["builtins"]
587
+ builtin_symbol = builtins_mod.sym_tab.lookup(symbol_name)
588
+
589
+ if not builtin_symbol:
590
+ return False
591
+
592
+ target_node.name_spec._sym = builtin_symbol
593
+ builtin_symbol.add_use(target_node)
594
+ return True