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.
- jaclang/cli/cli.py +113 -7
- jaclang/cli/cmdreg.py +12 -0
- jaclang/compiler/__init__.py +58 -2
- jaclang/compiler/absyntree.py +1775 -61
- jaclang/compiler/codeloc.py +7 -0
- jaclang/compiler/compile.py +1 -1
- jaclang/compiler/constant.py +17 -0
- jaclang/compiler/parser.py +134 -112
- jaclang/compiler/passes/ir_pass.py +18 -0
- jaclang/compiler/passes/main/__init__.py +2 -0
- jaclang/compiler/passes/main/def_impl_match_pass.py +19 -3
- jaclang/compiler/passes/main/def_use_pass.py +1 -1
- jaclang/compiler/passes/main/fuse_typeinfo_pass.py +357 -0
- jaclang/compiler/passes/main/import_pass.py +7 -3
- jaclang/compiler/passes/main/pyast_gen_pass.py +350 -109
- jaclang/compiler/passes/main/pyast_load_pass.py +1779 -206
- jaclang/compiler/passes/main/registry_pass.py +126 -0
- jaclang/compiler/passes/main/schedules.py +4 -1
- jaclang/compiler/passes/main/sym_tab_build_pass.py +20 -28
- jaclang/compiler/passes/main/tests/test_pyast_build_pass.py +14 -5
- jaclang/compiler/passes/main/tests/test_registry_pass.py +39 -0
- jaclang/compiler/passes/main/tests/test_sym_tab_build_pass.py +8 -8
- jaclang/compiler/passes/main/tests/test_typeinfo_pass.py +7 -0
- jaclang/compiler/passes/main/type_check_pass.py +0 -1
- jaclang/compiler/passes/tool/jac_formatter_pass.py +8 -17
- jaclang/compiler/passes/tool/tests/test_unparse_validate.py +65 -0
- jaclang/compiler/passes/utils/mypy_ast_build.py +28 -14
- jaclang/compiler/symtable.py +23 -2
- jaclang/compiler/tests/test_parser.py +53 -0
- jaclang/compiler/workspace.py +52 -26
- jaclang/core/aott.py +193 -28
- jaclang/core/construct.py +59 -2
- jaclang/core/registry.py +115 -0
- jaclang/core/utils.py +25 -0
- jaclang/plugin/default.py +108 -26
- jaclang/plugin/feature.py +22 -4
- jaclang/plugin/spec.py +13 -7
- jaclang/utils/helpers.py +66 -3
- jaclang/utils/lang_tools.py +6 -38
- jaclang/utils/test.py +1 -0
- jaclang/utils/tests/test_lang_tools.py +11 -14
- jaclang/utils/treeprinter.py +10 -2
- {jaclang-0.5.7.dist-info → jaclang-0.5.9.dist-info}/METADATA +1 -1
- {jaclang-0.5.7.dist-info → jaclang-0.5.9.dist-info}/RECORD +47 -43
- {jaclang-0.5.7.dist-info → jaclang-0.5.9.dist-info}/WHEEL +1 -1
- jaclang/compiler/__jac_gen__/__init__.py +0 -0
- jaclang/compiler/__jac_gen__/jac_parser.py +0 -4069
- {jaclang-0.5.7.dist-info → jaclang-0.5.9.dist-info}/entry_points.txt +0 -0
- {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()
|
jaclang/compiler/workspace.py
CHANGED
|
@@ -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(
|
|
111
|
+
def rebuild_file(
|
|
112
|
+
self, file_path: str, deep: bool = False, source: str = ""
|
|
113
|
+
) -> bool:
|
|
102
114
|
"""Rebuild a file."""
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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 =
|
|
178
|
-
if file_path in self.modules and
|
|
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
|
-
|
|
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 =
|
|
197
|
-
if file_path in self.modules and
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
{
|
|
20
|
+
{information}
|
|
17
21
|
|
|
18
|
-
[Inputs
|
|
19
|
-
{
|
|
22
|
+
[Inputs Information]
|
|
23
|
+
{inputs_information}
|
|
20
24
|
|
|
21
|
-
[Output
|
|
22
|
-
{
|
|
25
|
+
[Output Information]
|
|
26
|
+
{output_information}
|
|
23
27
|
|
|
24
|
-
[
|
|
25
|
-
{
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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=
|
|
65
|
+
reason_suffix=WITH_REASON_SUFFIX if reason else WITHOUT_REASON_SUFFIX,
|
|
63
66
|
)
|
|
64
67
|
|
|
65
68
|
|
|
66
|
-
def
|
|
67
|
-
"""
|
|
68
|
-
|
|
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
|
-
|
|
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:
|
jaclang/core/registry.py
ADDED
|
@@ -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)
|