jaclang 0.5.7__py3-none-any.whl → 0.5.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 (49) hide show
  1. jaclang/cli/cli.py +113 -7
  2. jaclang/cli/cmdreg.py +12 -0
  3. jaclang/compiler/__init__.py +58 -2
  4. jaclang/compiler/absyntree.py +1775 -61
  5. jaclang/compiler/codeloc.py +7 -0
  6. jaclang/compiler/compile.py +1 -1
  7. jaclang/compiler/constant.py +17 -0
  8. jaclang/compiler/parser.py +134 -112
  9. jaclang/compiler/passes/ir_pass.py +18 -0
  10. jaclang/compiler/passes/main/__init__.py +2 -0
  11. jaclang/compiler/passes/main/def_impl_match_pass.py +19 -3
  12. jaclang/compiler/passes/main/def_use_pass.py +1 -1
  13. jaclang/compiler/passes/main/fuse_typeinfo_pass.py +357 -0
  14. jaclang/compiler/passes/main/import_pass.py +7 -3
  15. jaclang/compiler/passes/main/pyast_gen_pass.py +350 -109
  16. jaclang/compiler/passes/main/pyast_load_pass.py +1779 -206
  17. jaclang/compiler/passes/main/registry_pass.py +126 -0
  18. jaclang/compiler/passes/main/schedules.py +4 -1
  19. jaclang/compiler/passes/main/sym_tab_build_pass.py +20 -28
  20. jaclang/compiler/passes/main/tests/test_pyast_build_pass.py +14 -5
  21. jaclang/compiler/passes/main/tests/test_registry_pass.py +39 -0
  22. jaclang/compiler/passes/main/tests/test_sym_tab_build_pass.py +8 -8
  23. jaclang/compiler/passes/main/tests/test_typeinfo_pass.py +7 -0
  24. jaclang/compiler/passes/main/type_check_pass.py +0 -1
  25. jaclang/compiler/passes/tool/jac_formatter_pass.py +8 -17
  26. jaclang/compiler/passes/tool/tests/test_unparse_validate.py +65 -0
  27. jaclang/compiler/passes/utils/mypy_ast_build.py +28 -14
  28. jaclang/compiler/symtable.py +23 -2
  29. jaclang/compiler/tests/test_parser.py +53 -0
  30. jaclang/compiler/workspace.py +52 -26
  31. jaclang/core/aott.py +193 -28
  32. jaclang/core/construct.py +59 -2
  33. jaclang/core/registry.py +115 -0
  34. jaclang/core/utils.py +25 -0
  35. jaclang/plugin/default.py +108 -26
  36. jaclang/plugin/feature.py +22 -4
  37. jaclang/plugin/spec.py +13 -7
  38. jaclang/utils/helpers.py +66 -3
  39. jaclang/utils/lang_tools.py +6 -38
  40. jaclang/utils/test.py +1 -0
  41. jaclang/utils/tests/test_lang_tools.py +11 -14
  42. jaclang/utils/treeprinter.py +10 -2
  43. {jaclang-0.5.7.dist-info → jaclang-0.5.9.dist-info}/METADATA +1 -1
  44. {jaclang-0.5.7.dist-info → jaclang-0.5.9.dist-info}/RECORD +47 -43
  45. {jaclang-0.5.7.dist-info → jaclang-0.5.9.dist-info}/WHEEL +1 -1
  46. jaclang/compiler/__jac_gen__/__init__.py +0 -0
  47. jaclang/compiler/__jac_gen__/jac_parser.py +0 -4069
  48. {jaclang-0.5.7.dist-info → jaclang-0.5.9.dist-info}/entry_points.txt +0 -0
  49. {jaclang-0.5.7.dist-info → jaclang-0.5.9.dist-info}/top_level.txt +0 -0
@@ -90,5 +90,58 @@ class TestLarkParser(TestCaseMicroSuite):
90
90
  continue
91
91
  self.assertIn(i, rules)
92
92
 
93
+ def test_all_ast_has_normalize(self) -> None:
94
+ """Test for enter/exit name diffs with parser."""
95
+ import jaclang.compiler.absyntree as ast
96
+ import inspect
97
+ import sys
98
+
99
+ exclude = [
100
+ "AstNode",
101
+ "WalkerStmtOnlyNode",
102
+ "JacSource",
103
+ "EmptyToken",
104
+ "AstSymbolNode",
105
+ "AstImplNeedingNode",
106
+ "AstAccessNode",
107
+ "TokenSymbol",
108
+ "Literal",
109
+ "AstDocNode",
110
+ "AstSemStrNode",
111
+ "PythonModuleAst",
112
+ "AstAsyncNode",
113
+ "AstElseBodyNode",
114
+ "AstTypedVarNode",
115
+ "AstImplOnlyNode",
116
+ "Expr",
117
+ "AtomExpr",
118
+ "ElementStmt",
119
+ "ArchBlockStmt",
120
+ "EnumBlockStmt",
121
+ "CodeBlockStmt",
122
+ "NameSpec",
123
+ "ArchSpec",
124
+ "MatchPattern",
125
+ ]
126
+ module_name = ast.__name__
127
+ module = sys.modules[module_name]
128
+
129
+ # Retrieve the source code of the module
130
+ source_code = inspect.getsource(module)
131
+
132
+ classes = inspect.getmembers(module, inspect.isclass)
133
+ ast_node_classes = [
134
+ cls
135
+ for _, cls in classes
136
+ if issubclass(cls, ast.AstNode) and not issubclass(cls, ast.Token)
137
+ ]
138
+
139
+ ordered_classes = sorted(
140
+ ast_node_classes, key=lambda cls: source_code.find(f"class {cls.__name__}")
141
+ )
142
+ for cls in ordered_classes:
143
+ if cls.__name__ not in exclude:
144
+ self.assertIn("normalize", cls.__dict__)
145
+
93
146
 
94
147
  TestLarkParser.self_attach_micro_tests()
@@ -3,7 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import os
6
- from typing import Sequence
6
+ from typing import Optional, Sequence
7
7
 
8
8
  import jaclang.compiler.absyntree as ast
9
9
  from jaclang.compiler.compile import jac_str_to_pass
@@ -33,7 +33,7 @@ class ModuleInfo:
33
33
 
34
34
  def __init__(
35
35
  self,
36
- ir: ast.Module,
36
+ ir: Optional[ast.Module],
37
37
  errors: Sequence[Alert],
38
38
  warnings: Sequence[Alert],
39
39
  ) -> None:
@@ -46,10 +46,11 @@ class ModuleInfo:
46
46
  class Workspace:
47
47
  """Class for managing workspace."""
48
48
 
49
- def __init__(self, path: str) -> None:
49
+ def __init__(self, path: str, lazy_parse: bool = False) -> None:
50
50
  """Initialize workspace."""
51
51
  self.path = path
52
52
  self.modules: dict[str, ModuleInfo] = {}
53
+ self.lazy_parse = lazy_parse
53
54
  self.rebuild_workspace()
54
55
 
55
56
  def rebuild_workspace(self) -> None:
@@ -63,6 +64,15 @@ class Workspace:
63
64
  ]:
64
65
  if file in self.modules:
65
66
  continue
67
+ if self.lazy_parse:
68
+ # If lazy_parse is True, add the file to modules with empty IR
69
+ self.modules[file] = ModuleInfo(
70
+ ir=None,
71
+ errors=[],
72
+ warnings=[],
73
+ )
74
+ continue
75
+
66
76
  with open(file, "r") as f:
67
77
  source = f.read()
68
78
  build = jac_str_to_pass(
@@ -98,10 +108,13 @@ class Workspace:
98
108
  warnings=build.warnings_had,
99
109
  )
100
110
 
101
- def rebuild_file(self, file_path: str, deep: bool = False) -> bool:
111
+ def rebuild_file(
112
+ self, file_path: str, deep: bool = False, source: str = ""
113
+ ) -> bool:
102
114
  """Rebuild a file."""
103
- with open(file_path, "r") as f:
104
- source = f.read()
115
+ if source == "":
116
+ with open(file_path, "r") as f:
117
+ source = f.read()
105
118
  build = jac_str_to_pass(
106
119
  jac_str=source,
107
120
  file_path=file_path,
@@ -152,30 +165,40 @@ class Workspace:
152
165
  self, file_path: str, deep: bool = False
153
166
  ) -> list[ast.ModulePath]:
154
167
  """Return a list of dependencies for a file."""
168
+ mod_ir = self.modules[file_path].ir
155
169
  if deep:
156
- return [
157
- i
158
- for i in self.modules[file_path].ir.get_all_sub_nodes(ast.ModulePath)
159
- if i.parent
160
- and isinstance(i.parent, ast.Import)
161
- and i.parent.lang.tag.value == "jac"
162
- ]
170
+ return (
171
+ [
172
+ i
173
+ for i in mod_ir.get_all_sub_nodes(ast.ModulePath)
174
+ if i.parent
175
+ and isinstance(i.parent, ast.Import)
176
+ and i.parent.lang.tag.value == "jac"
177
+ ]
178
+ if mod_ir
179
+ else []
180
+ )
163
181
  else:
164
- return [
165
- i
166
- for i in self.modules[file_path].ir.get_all_sub_nodes(ast.ModulePath)
167
- if i.loc.mod_path == file_path
168
- and i.parent
169
- and isinstance(i.parent, ast.Import)
170
- and i.parent.lang.tag.value == "jac"
171
- ]
182
+ return (
183
+ [
184
+ i
185
+ for i in mod_ir.get_all_sub_nodes(ast.ModulePath)
186
+ if i.loc.mod_path == file_path
187
+ and i.parent
188
+ and isinstance(i.parent, ast.Import)
189
+ and i.parent.lang.tag.value == "jac"
190
+ ]
191
+ if mod_ir
192
+ else []
193
+ )
172
194
 
173
195
  def get_symbols(self, file_path: str) -> Sequence[Symbol]:
174
196
  """Return a list of symbols for a file."""
175
197
  symbols = []
198
+ mod_ir = self.modules[file_path].ir
176
199
  if file_path in self.modules:
177
- root_table = self.modules[file_path].ir.sym_tab
178
- if file_path in self.modules and isinstance(root_table, SymbolTable):
200
+ root_table = mod_ir.sym_tab if mod_ir else None
201
+ if file_path in self.modules and root_table:
179
202
  for i in sym_tab_list(sym_tab=root_table, file_path=file_path):
180
203
  symbols += list(i.tab.values())
181
204
  return symbols
@@ -191,10 +214,13 @@ class Workspace:
191
214
 
192
215
  def get_uses(self, file_path: str) -> Sequence[ast.AstSymbolNode]: # need test
193
216
  """Return a list of definitions for a file."""
194
- uses = []
217
+ mod_ir = self.modules[file_path].ir
218
+ uses: list[ast.AstSymbolNode] = []
219
+ if self.lazy_parse:
220
+ return uses
195
221
  if file_path in self.modules:
196
- root_table = self.modules[file_path].ir.sym_tab
197
- if file_path in self.modules and isinstance(root_table, SymbolTable):
222
+ root_table = mod_ir.sym_tab if mod_ir else None
223
+ if file_path in self.modules and root_table:
198
224
  for i in sym_tab_list(sym_tab=root_table, file_path=file_path):
199
225
  uses += i.uses
200
226
  return uses
jaclang/core/aott.py CHANGED
@@ -4,65 +4,230 @@ AOTT: Automated Operational Type Transformation.
4
4
  This has all the necessary functions to perform the AOTT operations.
5
5
  """
6
6
 
7
+ import re
8
+ from enum import Enum
7
9
  from typing import Any
8
10
 
11
+ from jaclang.core.registry import SemInfo, SemRegistry, SemScope
9
12
 
10
- prompt_template = """
13
+
14
+ PROMPT_TEMPLATE = """
11
15
  [System Prompt]
12
- This is an operation you must perform and return the output values. Neither, the methodology,
13
- extra sentences nor the code are not needed.
16
+ This is an operation you must perform and return the output values. Neither, the methodology, extra sentences nor the code are not needed.
17
+ Input/Type formatting: Explanation of the Input (variable_name) (type) = value
14
18
 
15
19
  [Information]
16
- {information_str}
20
+ {information}
17
21
 
18
- [Inputs and Input Type Information]
19
- {input_types_n_information_str}
22
+ [Inputs Information]
23
+ {inputs_information}
20
24
 
21
- [Output Type]
22
- {output_type_str}
25
+ [Output Information]
26
+ {output_information}
23
27
 
24
- [Output Type Explanations]
25
- {output_type_info_str}
28
+ [Type Explanations]
29
+ {type_explanations}
26
30
 
27
31
  [Action]
28
32
  {action}
29
33
 
30
34
  {reason_suffix}
31
- """
35
+ """ # noqa E501
32
36
 
33
- with_reason_suffix = """
37
+ WITH_REASON_SUFFIX = """
34
38
  Reason and return the output result(s) only, adhering to the provided Type in the following format
35
39
 
36
40
  [Reasoning] <Reason>
37
41
  [Output] <Result>
38
42
  """
39
43
 
40
- without_reason_suffix = """Generate and return the output result(s) only, adhering to the provided Type in the
41
- following format
44
+ WITHOUT_REASON_SUFFIX = """Generate and return the output result(s) only, adhering to the provided Type in the following format
42
45
 
43
46
  [Output] <result>
44
- """
47
+ """ # noqa E501
45
48
 
46
49
 
47
50
  def aott_raise(
48
- information_str: str,
49
- input_types_n_information_str: str,
50
- output_type_str: str,
51
- output_type_info_str: str,
51
+ information: str,
52
+ inputs_information: str,
53
+ output_information: str,
54
+ type_explanations: str,
52
55
  action: str,
53
56
  reason: bool,
54
57
  ) -> str:
55
58
  """AOTT Raise uses the information (Meanings types values) provided to generate a prompt(meaning in)."""
56
- return prompt_template.format(
57
- information_str=information_str,
58
- input_types_n_information_str=input_types_n_information_str,
59
- output_type_str=output_type_str,
60
- output_type_info_str=output_type_info_str,
59
+ return PROMPT_TEMPLATE.format(
60
+ information=information,
61
+ inputs_information=inputs_information,
62
+ output_information=output_information,
63
+ type_explanations=type_explanations,
61
64
  action=action,
62
- reason_suffix=with_reason_suffix if reason else without_reason_suffix,
65
+ reason_suffix=WITH_REASON_SUFFIX if reason else WITHOUT_REASON_SUFFIX,
63
66
  )
64
67
 
65
68
 
66
- def aott_lower(meaning_out: str, output_type_info: tuple) -> Any: # noqa: ANN401
67
- """AOTT Lower uses the meaning out provided by the language model and return the result in the desired type."""
68
- return meaning_out
69
+ def get_reasoning_output(s: str) -> tuple:
70
+ """Get the reasoning and output from the meaning out string."""
71
+ reasoning_match = re.search(r"\[Reasoning\](.*)\[Output\]", s)
72
+ output_match = re.search(r"\[Output\](.*)", s)
73
+
74
+ if reasoning_match and output_match:
75
+ reasoning = reasoning_match.group(1)
76
+ output = output_match.group(1)
77
+ return (reasoning.strip(), output.strip())
78
+ elif output_match:
79
+ output = output_match.group(1)
80
+ return (None, output.strip())
81
+ else:
82
+ return (None, None)
83
+
84
+
85
+ def get_info_types(
86
+ scope: SemScope, mod_registry: SemRegistry, incl_info: list[tuple[str, str]]
87
+ ) -> tuple[str, list[str]]:
88
+ """Filter the registry data based on the scope and return the info string."""
89
+ collected_types = []
90
+ avail_scopes = []
91
+ while True:
92
+ avail_scopes.append(str(scope))
93
+ if not scope.parent:
94
+ break
95
+ scope = scope.parent
96
+
97
+ filtered_registry = SemRegistry()
98
+ for _scope, sem_info_list in mod_registry.registry.items():
99
+ if str(_scope) in avail_scopes:
100
+ filtered_registry.registry[_scope] = sem_info_list
101
+
102
+ info_str = []
103
+ for incl in incl_info:
104
+ _, sem_info = filtered_registry.lookup(name=incl[0])
105
+ if sem_info and isinstance(sem_info, SemInfo):
106
+ (
107
+ collected_types.extend(extract_non_primary_type(sem_info.type))
108
+ if sem_info.type
109
+ else None
110
+ )
111
+ info_str.append(
112
+ f"{sem_info.semstr} ({sem_info.name}) ({sem_info.type}) = {get_object_string(incl[1])}"
113
+ )
114
+ return ("\n".join(info_str), collected_types)
115
+
116
+
117
+ def get_object_string(obj: Any) -> Any: # noqa: ANN401
118
+ """Get the string representation of the input object."""
119
+ if isinstance(obj, str):
120
+ return f'"{obj}"'
121
+ elif isinstance(obj, (int, float, bool)):
122
+ return str(obj)
123
+ elif isinstance(obj, list):
124
+ return "[" + ", ".join(get_object_string(item) for item in obj) + "]"
125
+ elif isinstance(obj, tuple):
126
+ return "(" + ", ".join(get_object_string(item) for item in obj) + ")"
127
+ elif isinstance(obj, dict):
128
+ return (
129
+ "{"
130
+ + ", ".join(
131
+ f"{get_object_string(key)}: {get_object_string(value)}"
132
+ for key, value in obj.items()
133
+ )
134
+ + "}"
135
+ )
136
+ elif isinstance(obj, Enum):
137
+ return f"{obj.__class__.__name__}.{obj.name}"
138
+ elif hasattr(obj, "__dict__"):
139
+ args = ", ".join(
140
+ f"{key}={get_object_string(value)}"
141
+ for key, value in vars(obj).items()
142
+ if key != "_jac_"
143
+ )
144
+ return f"{obj.__class__.__name__}({args})"
145
+ else:
146
+ return str(obj)
147
+
148
+
149
+ def get_all_type_explanations(type_list: list, mod_registry: SemRegistry) -> dict:
150
+ """Get all type explanations from the input type list."""
151
+ collected_type_explanations = {}
152
+ for type_item in type_list:
153
+ type_explanation = get_type_explanation(type_item, mod_registry)
154
+ if type_explanation is not None:
155
+ type_explanation_str, nested_types = type_explanation
156
+ if type_item not in collected_type_explanations:
157
+ collected_type_explanations[type_item] = type_explanation_str
158
+ if nested_types:
159
+ nested_collected_type_explanations = get_all_type_explanations(
160
+ list(nested_types), mod_registry
161
+ )
162
+ for k, v in nested_collected_type_explanations.items():
163
+ if k not in collected_type_explanations:
164
+ collected_type_explanations[k] = v
165
+ return collected_type_explanations
166
+
167
+
168
+ def get_type_explanation(
169
+ type_str: str, mod_registry: SemRegistry
170
+ ) -> tuple[str | None, set[str] | None]:
171
+ """Get the type explanation of the input type string."""
172
+ scope, sem_info = mod_registry.lookup(name=type_str)
173
+ if isinstance(sem_info, SemInfo) and sem_info.type:
174
+ sem_info_scope = SemScope(sem_info.name, sem_info.type, scope)
175
+ _, type_info = mod_registry.lookup(scope=sem_info_scope)
176
+ type_info_str = []
177
+ type_info_types = []
178
+ if sem_info.type == "Enum" and isinstance(type_info, list):
179
+ for enum_item in type_info:
180
+ type_info_str.append(
181
+ f"{enum_item.semstr} (EnumItem) ({enum_item.name})"
182
+ )
183
+ elif sem_info.type in ["obj", "class", "node", "edge"] and isinstance(
184
+ type_info, list
185
+ ):
186
+ for arch_item in type_info:
187
+ type_info_str.append(
188
+ f"{arch_item.semstr} ({arch_item.type}) ({arch_item.name})"
189
+ )
190
+ if arch_item.type and extract_non_primary_type(arch_item.type):
191
+ type_info_types.extend(extract_non_primary_type(arch_item.type))
192
+ return (
193
+ f"{sem_info.semstr} ({sem_info.type}) ({sem_info.name}) = {', '.join(type_info_str)}",
194
+ set(type_info_types),
195
+ )
196
+ return None, None
197
+
198
+
199
+ def extract_non_primary_type(type_str: str) -> list:
200
+ """Extract non-primary types from the type string."""
201
+ if not type_str:
202
+ return []
203
+ pattern = r"(?:\[|,\s*|\|)([a-zA-Z_][a-zA-Z0-9_]*)|([a-zA-Z_][a-zA-Z0-9_]*)"
204
+ matches = re.findall(pattern, type_str)
205
+ primary_types = [
206
+ "str",
207
+ "int",
208
+ "float",
209
+ "bool",
210
+ "list",
211
+ "dict",
212
+ "tuple",
213
+ "set",
214
+ "Any",
215
+ "None",
216
+ ]
217
+ non_primary_types = [m for t in matches for m in t if m and m not in primary_types]
218
+ return non_primary_types
219
+
220
+
221
+ def get_type_annotation(data: Any) -> str: # noqa: ANN401
222
+ """Get the type annotation of the input data."""
223
+ if isinstance(data, dict):
224
+ class_name = next(
225
+ (value.__class__.__name__ for value in data.values() if value is not None),
226
+ None,
227
+ )
228
+ if class_name:
229
+ return f"dict[str, {class_name}]"
230
+ else:
231
+ return "dict[str, Any]"
232
+ else:
233
+ return str(type(data).__name__)
jaclang/core/construct.py CHANGED
@@ -328,11 +328,58 @@ class DSFunc:
328
328
  self.func = getattr(cls, self.name)
329
329
 
330
330
 
331
+ class JacTestResult(unittest.TextTestResult):
332
+ """Jac test result class."""
333
+
334
+ def __init__(
335
+ self,
336
+ stream, # noqa
337
+ descriptions, # noqa
338
+ verbosity: int,
339
+ max_failures: Optional[int] = None,
340
+ ) -> None:
341
+ """Initialize FailFastTestResult object."""
342
+ super().__init__(stream, descriptions, verbosity) # noqa
343
+ self.failures_count = JacTestCheck.failcount
344
+ self.max_failures = max_failures
345
+
346
+ def addFailure(self, test, err) -> None: # noqa
347
+ """Count failures and stop."""
348
+ super().addFailure(test, err)
349
+ self.failures_count += 1
350
+ if self.max_failures is not None and self.failures_count >= self.max_failures:
351
+ self.stop()
352
+
353
+ def stop(self) -> None:
354
+ """Stop the test execution."""
355
+ self.shouldStop = True
356
+
357
+
358
+ class JacTextTestRunner(unittest.TextTestRunner):
359
+ """Jac test runner class."""
360
+
361
+ def __init__(self, max_failures: Optional[int] = None, **kwargs) -> None: # noqa
362
+ """Initialize JacTextTestRunner object."""
363
+ self.max_failures = max_failures
364
+ super().__init__(**kwargs)
365
+
366
+ def _makeResult(self) -> JacTestResult: # noqa
367
+ """Override the method to return an instance of JacTestResult."""
368
+ return JacTestResult(
369
+ self.stream,
370
+ self.descriptions,
371
+ self.verbosity,
372
+ max_failures=self.max_failures,
373
+ )
374
+
375
+
331
376
  class JacTestCheck:
332
377
  """Jac Testing and Checking."""
333
378
 
334
379
  test_case = unittest.TestCase()
335
380
  test_suite = unittest.TestSuite()
381
+ breaker = False
382
+ failcount = 0
336
383
 
337
384
  @staticmethod
338
385
  def reset() -> None:
@@ -341,9 +388,19 @@ class JacTestCheck:
341
388
  JacTestCheck.test_suite = unittest.TestSuite()
342
389
 
343
390
  @staticmethod
344
- def run_test() -> None:
391
+ def run_test(xit: bool, maxfail: int | None, verbose: bool) -> None:
345
392
  """Run the test suite."""
346
- unittest.TextTestRunner().run(JacTestCheck.test_suite)
393
+ verb = 2 if verbose else 1
394
+ runner = JacTextTestRunner(max_failures=maxfail, failfast=xit, verbosity=verb)
395
+ result = runner.run(JacTestCheck.test_suite)
396
+ if result.wasSuccessful():
397
+ print("Passed successfully.")
398
+ else:
399
+ fails = len(result.failures)
400
+ JacTestCheck.failcount += fails
401
+ JacTestCheck.breaker = (
402
+ (JacTestCheck.failcount >= maxfail) if maxfail else True
403
+ )
347
404
 
348
405
  @staticmethod
349
406
  def add_test(test_fun: Callable) -> None:
@@ -0,0 +1,115 @@
1
+ """Registry Utilities.
2
+
3
+ This module contains classes and functions for managing the registry of
4
+ semantic information.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Optional
10
+
11
+
12
+ class SemInfo:
13
+ """Semantic information class."""
14
+
15
+ def __init__(
16
+ self, name: str, type: Optional[str] = None, semstr: Optional[str] = None
17
+ ) -> None:
18
+ """Initialize the class."""
19
+ self.name = name
20
+ self.type = type
21
+ self.semstr = semstr
22
+
23
+ def __repr__(self) -> str:
24
+ """Return the string representation of the class."""
25
+ return f"{self.semstr} ({self.type}) ({self.name})"
26
+
27
+
28
+ class SemScope:
29
+ """Scope class."""
30
+
31
+ def __init__(
32
+ self, scope: str, type: str, parent: Optional[SemScope] = None
33
+ ) -> None:
34
+ """Initialize the class."""
35
+ self.parent = parent
36
+ self.type = type
37
+ self.scope = scope
38
+
39
+ def __str__(self) -> str:
40
+ """Return the string representation of the class."""
41
+ if self.parent:
42
+ return f"{self.parent}.{self.scope}({self.type})"
43
+ return f"{self.scope}({self.type})"
44
+
45
+ def __repr__(self) -> str:
46
+ """Return the string representation of the class."""
47
+ return self.__str__()
48
+
49
+ @staticmethod
50
+ def get_scope_from_str(scope_str: str) -> Optional[SemScope]:
51
+ """Get scope from string."""
52
+ scope_list = scope_str.split(".")
53
+ parent = None
54
+ for scope in scope_list:
55
+ scope_name, scope_type = scope.split("(")
56
+ scope_type = scope_type[:-1]
57
+ parent = SemScope(scope_name, scope_type, parent)
58
+ return parent
59
+
60
+
61
+ class SemRegistry:
62
+ """Registry class."""
63
+
64
+ def __init__(self) -> None:
65
+ """Initialize the class."""
66
+ self.registry: dict[SemScope, list[SemInfo]] = {}
67
+
68
+ def add(self, scope: SemScope, seminfo: SemInfo) -> None:
69
+ """Add semantic information to the registry."""
70
+ for k in self.registry.keys():
71
+ if str(k) == str(scope):
72
+ scope = k
73
+ break
74
+ else:
75
+ self.registry[scope] = []
76
+ self.registry[scope].append(seminfo)
77
+
78
+ def lookup(
79
+ self,
80
+ scope: Optional[SemScope] = None,
81
+ name: Optional[str] = None,
82
+ type: Optional[str] = None,
83
+ ) -> tuple[Optional[SemScope], Optional[SemInfo | list[SemInfo]]]:
84
+ """Lookup semantic information in the registry."""
85
+ if scope:
86
+ for k, v in self.registry.items():
87
+ if str(k) == str(scope):
88
+ if name:
89
+ for i in v:
90
+ if i.name == name:
91
+ return k, i
92
+ elif type:
93
+ for i in v:
94
+ if i.type == type:
95
+ return k, i
96
+ else:
97
+ return k, v
98
+ else:
99
+ for k, v in self.registry.items():
100
+ if name:
101
+ for i in v:
102
+ if i.name == name:
103
+ return k, i
104
+ elif type:
105
+ for i in v:
106
+ if i.type == type:
107
+ return k, i
108
+ return None, None
109
+
110
+ def pp(self) -> None:
111
+ """Pretty print the registry."""
112
+ for k, v in self.registry.items():
113
+ print(k)
114
+ for i in v:
115
+ print(f" {i.name} {i.type} {i.semstr}")
jaclang/core/utils.py CHANGED
@@ -4,6 +4,8 @@ from __future__ import annotations
4
4
 
5
5
  from typing import Callable, TYPE_CHECKING
6
6
 
7
+ import jaclang.compiler.absyntree as ast
8
+ from jaclang.core.registry import SemScope
7
9
 
8
10
  if TYPE_CHECKING:
9
11
  from jaclang.core.construct import NodeAnchor, NodeArchitype
@@ -88,3 +90,26 @@ def traverse_graph(
88
90
  else:
89
91
 
90
92
  dfs(other_nd, cur_depth + 1)
93
+
94
+
95
+ def get_sem_scope(node: ast.AstNode) -> SemScope:
96
+ """Get scope of the node."""
97
+ a = (
98
+ node.name
99
+ if isinstance(node, ast.Module)
100
+ else node.name.value if isinstance(node, (ast.Enum, ast.Architype)) else ""
101
+ )
102
+ if isinstance(node, ast.Module):
103
+ return SemScope(a, "Module", None)
104
+ elif isinstance(node, (ast.Enum, ast.Architype)):
105
+ node_type = (
106
+ node.__class__.__name__
107
+ if isinstance(node, ast.Enum)
108
+ else node.arch_type.value
109
+ )
110
+ if node.parent:
111
+ return SemScope(a, node_type, get_sem_scope(node.parent))
112
+ else:
113
+ if node.parent:
114
+ return get_sem_scope(node.parent)
115
+ return SemScope("", "", None)