jaclang 0.5.11__py3-none-any.whl → 0.5.16__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 (41) hide show
  1. jaclang/cli/cli.py +20 -0
  2. jaclang/compiler/__init__.py +35 -19
  3. jaclang/compiler/absyntree.py +106 -97
  4. jaclang/compiler/generated/jac_parser.py +4069 -0
  5. jaclang/compiler/jac.lark +655 -0
  6. jaclang/compiler/parser.py +44 -31
  7. jaclang/compiler/passes/main/fuse_typeinfo_pass.py +92 -37
  8. jaclang/compiler/passes/main/import_pass.py +8 -5
  9. jaclang/compiler/passes/main/pyast_gen_pass.py +512 -352
  10. jaclang/compiler/passes/main/pyast_load_pass.py +271 -64
  11. jaclang/compiler/passes/main/registry_pass.py +3 -7
  12. jaclang/compiler/passes/main/tests/test_pyast_gen_pass.py +2 -0
  13. jaclang/compiler/passes/main/type_check_pass.py +4 -1
  14. jaclang/compiler/passes/tool/jac_formatter_pass.py +7 -0
  15. jaclang/compiler/passes/tool/tests/test_unparse_validate.py +16 -0
  16. jaclang/compiler/passes/utils/mypy_ast_build.py +93 -0
  17. jaclang/compiler/tests/test_importer.py +15 -0
  18. jaclang/core/aott.py +4 -3
  19. jaclang/core/construct.py +1 -1
  20. jaclang/core/importer.py +109 -51
  21. jaclang/core/llms.py +29 -0
  22. jaclang/core/registry.py +22 -0
  23. jaclang/core/utils.py +72 -0
  24. jaclang/plugin/default.py +127 -8
  25. jaclang/plugin/feature.py +29 -2
  26. jaclang/plugin/spec.py +25 -2
  27. jaclang/utils/helpers.py +7 -9
  28. jaclang/utils/lang_tools.py +37 -13
  29. jaclang/utils/test.py +1 -3
  30. jaclang/utils/tests/test_lang_tools.py +6 -0
  31. jaclang/vendor/lark/grammars/common.lark +59 -0
  32. jaclang/vendor/lark/grammars/lark.lark +62 -0
  33. jaclang/vendor/lark/grammars/python.lark +302 -0
  34. jaclang/vendor/lark/grammars/unicode.lark +7 -0
  35. {jaclang-0.5.11.dist-info → jaclang-0.5.16.dist-info}/METADATA +1 -1
  36. {jaclang-0.5.11.dist-info → jaclang-0.5.16.dist-info}/RECORD +40 -34
  37. jaclang/compiler/__jac_gen__/jac_parser.py +0 -4069
  38. /jaclang/compiler/{__jac_gen__ → generated}/__init__.py +0 -0
  39. {jaclang-0.5.11.dist-info → jaclang-0.5.16.dist-info}/WHEEL +0 -0
  40. {jaclang-0.5.11.dist-info → jaclang-0.5.16.dist-info}/entry_points.txt +0 -0
  41. {jaclang-0.5.11.dist-info → jaclang-0.5.16.dist-info}/top_level.txt +0 -0
@@ -16,6 +16,22 @@ class JacUnparseTests(TestCaseMicroSuite, AstSyncTestMixin):
16
16
 
17
17
  TargetPass = JacFormatPass
18
18
 
19
+ def test_double_unparse(self) -> None:
20
+ """Parse micro jac file."""
21
+ filename = "../../../../../../examples/manual_code/circle.jac"
22
+ try:
23
+ code_gen_pure = jac_file_to_pass(
24
+ self.fixture_abs_path(filename),
25
+ target=PyastGenPass,
26
+ schedule=without_format,
27
+ )
28
+ x = code_gen_pure.ir.unparse()
29
+ y = code_gen_pure.ir.unparse()
30
+ self.assertEqual(x, y)
31
+ except Exception as e:
32
+ print("\n".join(unified_diff(x.splitlines(), y.splitlines())))
33
+ self.skipTest(f"Test failed, but skipping instead of failing: {e}")
34
+
19
35
  def micro_suite_test(self, filename: str) -> None:
20
36
  """Parse micro jac file."""
21
37
  try:
@@ -9,8 +9,12 @@ import pathlib
9
9
  import jaclang
10
10
  from jaclang.compiler.absyntree import AstNode
11
11
  from jaclang.compiler.passes import Pass
12
+ from jaclang.compiler.passes.main.fuse_typeinfo_pass import (
13
+ FuseTypeInfoPass,
14
+ ) # TODO: Put in better place
12
15
 
13
16
  import mypy.build as myb
17
+ import mypy.checkexpr as mycke
14
18
  import mypy.errors as mye
15
19
  import mypy.fastparse as myfp
16
20
  from mypy.build import BuildSource
@@ -77,6 +81,70 @@ class BuildManager(myb.BuildManager):
77
81
  return tree
78
82
 
79
83
 
84
+ class ExpressionChecker(mycke.ExpressionChecker):
85
+ """Overrides to mypy expression checker for direct AST pass through."""
86
+
87
+ def __init__(
88
+ self,
89
+ tc: mycke.mypy.checker.TypeChecker,
90
+ msg: mycke.MessageBuilder,
91
+ plugin: mycke.Plugin,
92
+ per_line_checking_time_ns: dict[int, int],
93
+ ) -> None:
94
+ """Override to mypy expression checker for direct AST pass through."""
95
+ super().__init__(tc, msg, plugin, per_line_checking_time_ns)
96
+
97
+ def visit_list_expr(self, e: mycke.ListExpr) -> mycke.Type:
98
+ """Type check a list expression [...]."""
99
+ out = super().visit_list_expr(e)
100
+ FuseTypeInfoPass.node_type_hash[e] = out
101
+ return out
102
+
103
+ def visit_set_expr(self, e: mycke.SetExpr) -> mycke.Type:
104
+ """Type check a set expression {...}."""
105
+ out = super().visit_set_expr(e)
106
+ FuseTypeInfoPass.node_type_hash[e] = out
107
+ return out
108
+
109
+ def visit_tuple_expr(self, e: myfp.TupleExpr) -> myb.Type:
110
+ """Type check a tuple expression (...)."""
111
+ out = super().visit_tuple_expr(e)
112
+ FuseTypeInfoPass.node_type_hash[e] = out
113
+ return out
114
+
115
+ def visit_dict_expr(self, e: myfp.DictExpr) -> myb.Type:
116
+ """Type check a dictionary expression {...}."""
117
+ out = super().visit_dict_expr(e)
118
+ FuseTypeInfoPass.node_type_hash[e] = out
119
+ return out
120
+
121
+ def visit_list_comprehension(self, e: myfp.ListComprehension) -> myb.Type:
122
+ """Type check a list comprehension."""
123
+ out = super().visit_list_comprehension(e)
124
+ FuseTypeInfoPass.node_type_hash[e] = out
125
+ return out
126
+
127
+ def visit_set_comprehension(self, e: myfp.SetComprehension) -> myb.Type:
128
+ """Type check a set comprehension."""
129
+ out = super().visit_set_comprehension(e)
130
+ FuseTypeInfoPass.node_type_hash[e] = out
131
+ return out
132
+
133
+ def visit_generator_expr(self, e: myfp.GeneratorExpr) -> myb.Type:
134
+ """Type check a generator expression."""
135
+ out = super().visit_generator_expr(e)
136
+ FuseTypeInfoPass.node_type_hash[e] = out
137
+ return out
138
+
139
+ def visit_dictionary_comprehension(
140
+ self, e: myfp.DictionaryComprehension
141
+ ) -> myb.Type:
142
+ """Type check a dict comprehension."""
143
+ out = super().visit_dictionary_comprehension(e)
144
+ FuseTypeInfoPass.node_type_hash[e] = out
145
+ return out
146
+
147
+
80
148
  class State(myb.State):
81
149
  """Overrides to mypy state for direct AST pass through."""
82
150
 
@@ -201,6 +269,31 @@ class State(myb.State):
201
269
  self.parse_file(temporary=temporary, ast_override=ast_override)
202
270
  self.compute_dependencies()
203
271
 
272
+ def type_checker(self) -> myb.TypeChecker:
273
+ """Return the type checker for this state."""
274
+ if not self._type_checker:
275
+ assert (
276
+ self.tree is not None
277
+ ), "Internal error: must be called on parsed file only"
278
+ manager = self.manager
279
+ self._type_checker = myb.TypeChecker(
280
+ manager.errors,
281
+ manager.modules,
282
+ self.options,
283
+ self.tree,
284
+ self.xpath,
285
+ manager.plugin,
286
+ self.per_line_checking_time_ns,
287
+ )
288
+ self._type_checker.expr_checker = ExpressionChecker(
289
+ self._type_checker,
290
+ self._type_checker.msg,
291
+ self._type_checker.plugin,
292
+ self.per_line_checking_time_ns,
293
+ )
294
+
295
+ return self._type_checker
296
+
204
297
  def parse_file(
205
298
  self, *, temporary: bool = False, ast_override: myb.MypyFile | None = None
206
299
  ) -> None:
@@ -1,8 +1,10 @@
1
1
  """Tests for Jac Loader."""
2
2
 
3
+ import io
3
4
  import sys
4
5
 
5
6
  from jaclang import jac_import
7
+ from jaclang.cli import cli
6
8
  from jaclang.utils.test import TestCase
7
9
 
8
10
 
@@ -23,3 +25,16 @@ class TestLoader(TestCase):
23
25
  jac_import("fixtures.hello_world", base_path=__file__)
24
26
  self.assertIn("module 'hello_world'", str(sys.modules))
25
27
  self.assertIn("/tests/fixtures/hello_world.jac", str(sys.modules))
28
+
29
+ def test_jac_py_import(self) -> None:
30
+ """Basic test for pass."""
31
+ captured_output = io.StringIO()
32
+ sys.stdout = captured_output
33
+ cli.run(self.fixture_abs_path("../../../tests/fixtures/jp_importer.jac"))
34
+ sys.stdout = sys.__stdout__
35
+ stdout_value = captured_output.getvalue()
36
+ self.assertIn("Hello World!", stdout_value)
37
+ self.assertIn(
38
+ "{SomeObj(a=10): 'check'} [MyObj(apple=5, banana=7), MyObj(apple=5, banana=7)]",
39
+ stdout_value,
40
+ )
jaclang/core/aott.py CHANGED
@@ -150,9 +150,10 @@ def get_all_type_explanations(type_list: list, mod_registry: SemRegistry) -> dic
150
150
  """Get all type explanations from the input type list."""
151
151
  collected_type_explanations = {}
152
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
153
+ type_explanation_str, nested_types = get_type_explanation(
154
+ type_item, mod_registry
155
+ )
156
+ if type_explanation_str is not None:
156
157
  if type_item not in collected_type_explanations:
157
158
  collected_type_explanations[type_item] = type_explanation_str
158
159
  if nested_types:
jaclang/core/construct.py CHANGED
@@ -409,7 +409,7 @@ class JacTestCheck:
409
409
 
410
410
  def __getattr__(self, name: str) -> Union[bool, Any]:
411
411
  """Make convenient check.Equal(...) etc."""
412
- return getattr(JacTestCheck.test_case, "assert" + name)
412
+ return getattr(JacTestCheck.test_case, name)
413
413
 
414
414
 
415
415
  root = Root()
jaclang/core/importer.py CHANGED
@@ -1,10 +1,11 @@
1
1
  """Special Imports for Jac Code."""
2
2
 
3
+ import importlib
3
4
  import marshal
4
5
  import sys
5
6
  import types
6
- from os import path
7
- from typing import Optional
7
+ from os import getcwd, path
8
+ from typing import Optional, Union
8
9
 
9
10
  from jaclang.compiler.absyntree import Module
10
11
  from jaclang.compiler.compile import compile_jac
@@ -15,80 +16,137 @@ from jaclang.utils.log import logging
15
16
  def jac_importer(
16
17
  target: str,
17
18
  base_path: str,
19
+ absorb: bool = False,
18
20
  cachable: bool = True,
21
+ mdl_alias: Optional[str] = None,
19
22
  override_name: Optional[str] = None,
20
23
  mod_bundle: Optional[Module] = None,
24
+ lng: Optional[str] = None,
25
+ items: Optional[dict[str, Union[str, bool]]] = None,
21
26
  ) -> Optional[types.ModuleType]:
22
27
  """Core Import Process."""
23
- dir_path, file_name = path.split(path.join(*(target.split("."))) + ".jac")
24
-
28
+ dir_path, file_name = (
29
+ path.split(path.join(*(target.split("."))) + ".py")
30
+ if lng == "py"
31
+ else path.split(path.join(*(target.split("."))) + ".jac")
32
+ )
25
33
  module_name = path.splitext(file_name)[0]
26
34
  package_path = dir_path.replace(path.sep, ".")
27
35
 
28
- if package_path and f"{package_path}.{module_name}" in sys.modules:
36
+ if package_path and f"{package_path}.{module_name}" in sys.modules and lng != "py":
29
37
  return sys.modules[f"{package_path}.{module_name}"]
30
- elif not package_path and module_name in sys.modules:
38
+ elif not package_path and module_name in sys.modules and lng != "py":
31
39
  return sys.modules[module_name]
32
40
 
33
41
  caller_dir = path.dirname(base_path) if not path.isdir(base_path) else base_path
34
- caller_dir = path.dirname(caller_dir) if target.startswith("..") else caller_dir
42
+ if not caller_dir:
43
+ caller_dir = getcwd()
44
+ chomp_target = target
45
+ if chomp_target.startswith("."):
46
+ chomp_target = chomp_target[1:]
47
+ while chomp_target.startswith("."):
48
+ caller_dir = path.dirname(caller_dir)
49
+ chomp_target = chomp_target[1:]
35
50
  caller_dir = path.join(caller_dir, dir_path)
36
51
 
37
52
  full_target = path.normpath(path.join(caller_dir, file_name))
38
-
39
- if mod_bundle:
40
- codeobj = (
41
- mod_bundle.gen.py_bytecode
42
- if full_target == mod_bundle.loc.mod_path
43
- else mod_bundle.mod_deps[full_target].gen.py_bytecode
44
- )
45
- if isinstance(codeobj, bytes):
46
- codeobj = marshal.loads(codeobj)
47
- else:
48
- gen_dir = path.join(caller_dir, Con.JAC_GEN_DIR)
49
- pyc_file_path = path.join(gen_dir, module_name + ".jbc")
50
- if (
51
- cachable
52
- and path.exists(pyc_file_path)
53
- and path.getmtime(pyc_file_path) > path.getmtime(full_target)
54
- ):
55
- with open(pyc_file_path, "rb") as f:
56
- codeobj = marshal.load(f)
57
- else:
58
- result = compile_jac(full_target, cache_result=cachable)
59
- if result.errors_had or not result.ir.gen.py_bytecode:
60
- for e in result.errors_had:
61
- print(e)
62
- logging.error(e)
63
- return None
64
- else:
65
- codeobj = marshal.loads(result.ir.gen.py_bytecode)
53
+ path_added = False
54
+ if caller_dir not in sys.path:
55
+ sys.path.append(caller_dir)
56
+ path_added = True
66
57
 
67
58
  module_name = override_name if override_name else module_name
68
59
  module = types.ModuleType(module_name)
69
60
  module.__file__ = full_target
70
61
  module.__name__ = module_name
71
62
  module.__dict__["__jac_mod_bundle__"] = mod_bundle
63
+ if lng != "py":
64
+ if mod_bundle:
65
+ codeobj = (
66
+ mod_bundle.gen.py_bytecode
67
+ if full_target == mod_bundle.loc.mod_path
68
+ else mod_bundle.mod_deps[full_target].gen.py_bytecode
69
+ )
70
+ if isinstance(codeobj, bytes):
71
+ codeobj = marshal.loads(codeobj)
72
+ else:
72
73
 
73
- if package_path:
74
- parts = package_path.split(".")
75
- for i in range(len(parts)):
76
- package_name = ".".join(parts[: i + 1])
77
- if package_name not in sys.modules:
78
- sys.modules[package_name] = types.ModuleType(package_name)
74
+ gen_dir = path.join(caller_dir, Con.JAC_GEN_DIR)
75
+ pyc_file_path = path.join(gen_dir, module_name + ".jbc")
76
+ if (
77
+ cachable
78
+ and path.exists(pyc_file_path)
79
+ and path.getmtime(pyc_file_path) > path.getmtime(full_target)
80
+ ):
81
+ with open(pyc_file_path, "rb") as f:
82
+ codeobj = marshal.load(f)
83
+ else:
84
+ result = compile_jac(full_target, cache_result=cachable)
85
+ if result.errors_had or not result.ir.gen.py_bytecode:
86
+ for e in result.errors_had:
87
+ print(e)
88
+ logging.error(e)
89
+ return None
90
+ else:
91
+ codeobj = marshal.loads(result.ir.gen.py_bytecode)
79
92
 
80
- setattr(sys.modules[package_path], module_name, module)
81
- sys.modules[f"{package_path}.{module_name}"] = module
82
- sys.modules[module_name] = module
93
+ if package_path:
94
+ parts = package_path.split(".")
95
+ for i in range(len(parts)):
96
+ package_name = ".".join(parts[: i + 1])
97
+ if package_name not in sys.modules:
98
+ sys.modules[package_name] = types.ModuleType(package_name)
99
+
100
+ setattr(sys.modules[package_path], module_name, module)
101
+ sys.modules[f"{package_path}.{module_name}"] = module
102
+ sys.modules[module_name] = module
103
+
104
+ if not codeobj:
105
+ raise ImportError(f"No bytecode found for {full_target}")
106
+ exec(codeobj, module.__dict__)
107
+
108
+ (
109
+ py_import(target=target, items=items, absorb=absorb, mdl_alias=mdl_alias)
110
+ if lng == "py" or lng == "jac"
111
+ else None
112
+ )
83
113
 
84
- path_added = False
85
- if caller_dir not in sys.path:
86
- sys.path.append(caller_dir)
87
- path_added = True
88
- if not codeobj:
89
- raise ImportError(f"No bytecode found for {full_target}")
90
- exec(codeobj, module.__dict__)
91
114
  if path_added:
92
115
  sys.path.remove(caller_dir)
93
116
 
94
117
  return module
118
+
119
+
120
+ def py_import(
121
+ target: str,
122
+ items: Optional[dict[str, Union[str, bool]]] = None,
123
+ absorb: bool = False,
124
+ mdl_alias: Optional[str] = None,
125
+ ) -> None:
126
+ """Import a Python module."""
127
+ try:
128
+ target = target.lstrip(".") if target.startswith("..") else target
129
+ imported_module = importlib.import_module(target)
130
+ main_module = __import__("__main__")
131
+ # importer = importlib.import_module(caller)
132
+ if absorb:
133
+ for name in dir(imported_module):
134
+ if not name.startswith("_"):
135
+ setattr(main_module, name, getattr(imported_module, name))
136
+
137
+ elif items:
138
+ for name, alias in items.items():
139
+ setattr(
140
+ main_module,
141
+ alias if isinstance(alias, str) else name,
142
+ getattr(imported_module, name),
143
+ )
144
+
145
+ else:
146
+ setattr(
147
+ __import__("__main__"),
148
+ mdl_alias if isinstance(mdl_alias, str) else target,
149
+ imported_module,
150
+ )
151
+ except ImportError:
152
+ print(f"Failed to import module {target}")
jaclang/core/llms.py ADDED
@@ -0,0 +1,29 @@
1
+ """LLMs (Large Language Models) module for Jaclang."""
2
+
3
+ import anthropic
4
+
5
+
6
+ class Anthropic:
7
+ """Anthropic API client for Large Language Models (LLMs)."""
8
+
9
+ MTLLM_PROMPT: str = ""
10
+ MTLLM_REASON_SUFFIX: str = ""
11
+ MTLLM_WO_REASON_SUFFIX: str = ""
12
+
13
+ def __init__(self, **kwargs: dict) -> None:
14
+ """Initialize the Anthropic API client."""
15
+ self.client = anthropic.Anthropic()
16
+ self.model_name = kwargs.get("model_name", "claude-3-sonnet-20240229")
17
+ self.temperature = kwargs.get("temperature", 0.7)
18
+ self.max_tokens = kwargs.get("max_tokens", 1024)
19
+
20
+ def __infer__(self, meaning_in: str, **kwargs: dict) -> str:
21
+ """Infer a response from the input meaning."""
22
+ messages = [{"role": "user", "content": meaning_in}]
23
+ output = self.client.messages.create(
24
+ model=kwargs.get("model_name", self.model_name),
25
+ temperature=kwargs.get("temperature", self.temperature),
26
+ max_tokens=kwargs.get("max_tokens", self.max_tokens),
27
+ messages=messages,
28
+ )
29
+ return output.content[0].text
jaclang/core/registry.py CHANGED
@@ -57,6 +57,20 @@ class SemScope:
57
57
  parent = SemScope(scope_name, scope_type, parent)
58
58
  return parent
59
59
 
60
+ @property
61
+ def as_type_str(self) -> Optional[str]:
62
+ """Return the type string representation of the SemsScope."""
63
+ if self.type not in ["class", "node", "obj"]:
64
+ return None
65
+ type_str = self.scope
66
+ node = self.parent
67
+ while node and node.parent:
68
+ if node.type not in ["class", "node", "obj"]:
69
+ return type_str
70
+ type_str = f"{node.scope}.{type_str}"
71
+ node = node.parent
72
+ return type_str
73
+
60
74
 
61
75
  class SemRegistry:
62
76
  """Registry class."""
@@ -107,6 +121,14 @@ class SemRegistry:
107
121
  return k, i
108
122
  return None, None
109
123
 
124
+ @property
125
+ def module_scope(self) -> SemScope:
126
+ """Get the module scope."""
127
+ for i in self.registry.keys():
128
+ if not i.parent:
129
+ break
130
+ return i
131
+
110
132
  def pp(self) -> None:
111
133
  """Pretty print the registry."""
112
134
  for k, v in self.registry.items():
jaclang/core/utils.py CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import ast as ast3
5
6
  from typing import Callable, TYPE_CHECKING
6
7
 
7
8
  import jaclang.compiler.absyntree as ast
@@ -113,3 +114,74 @@ def get_sem_scope(node: ast.AstNode) -> SemScope:
113
114
  if node.parent:
114
115
  return get_sem_scope(node.parent)
115
116
  return SemScope("", "", None)
117
+
118
+
119
+ def extract_type(node: ast.AstNode) -> list[str]:
120
+ """Collect type information in assignment using bfs."""
121
+ extracted_type = []
122
+ if isinstance(node, (ast.BuiltinType, ast.Token)):
123
+ extracted_type.append(node.value)
124
+ for child in node.kid:
125
+ extracted_type.extend(extract_type(child))
126
+ return extracted_type
127
+
128
+
129
+ def extract_params(
130
+ body: ast.FuncCall,
131
+ ) -> tuple[dict[str, ast.Expr], list[tuple[str, ast3.AST]], list[tuple[str, ast3.AST]]]:
132
+ """Extract model parameters, include and exclude information."""
133
+ model_params = {}
134
+ include_info = []
135
+ exclude_info = []
136
+ if body.params:
137
+ for param in body.params.items:
138
+ if isinstance(param, ast.KWPair) and isinstance(param.key, ast.Name):
139
+ key = param.key.value
140
+ value = param.value
141
+ if key not in ["incl_info", "excl_info"]:
142
+ model_params[key] = value
143
+ elif key == "incl_info":
144
+ if isinstance(value, ast.AtomUnit):
145
+ var_name = (
146
+ value.value.right.value
147
+ if isinstance(value.value, ast.AtomTrailer)
148
+ and isinstance(value.value.right, ast.Name)
149
+ else (
150
+ value.value.value
151
+ if isinstance(value.value, ast.Name)
152
+ else ""
153
+ )
154
+ )
155
+ include_info.append((var_name, value.gen.py_ast[0]))
156
+ elif isinstance(value, ast.TupleVal) and value.values:
157
+ for i in value.values.items:
158
+ var_name = (
159
+ i.right.value
160
+ if isinstance(i, ast.AtomTrailer)
161
+ and isinstance(i.right, ast.Name)
162
+ else (i.value if isinstance(i, ast.Name) else "")
163
+ )
164
+ include_info.append((var_name, i.gen.py_ast[0]))
165
+ elif key == "excl_info":
166
+ if isinstance(value, ast.AtomUnit):
167
+ var_name = (
168
+ value.value.right.value
169
+ if isinstance(value.value, ast.AtomTrailer)
170
+ and isinstance(value.value.right, ast.Name)
171
+ else (
172
+ value.value.value
173
+ if isinstance(value.value, ast.Name)
174
+ else ""
175
+ )
176
+ )
177
+ exclude_info.append((var_name, value.gen.py_ast[0]))
178
+ elif isinstance(value, ast.TupleVal) and value.values:
179
+ for i in value.values.items:
180
+ var_name = (
181
+ i.right.value
182
+ if isinstance(i, ast.AtomTrailer)
183
+ and isinstance(i.right, ast.Name)
184
+ else (i.value if isinstance(i, ast.Name) else "")
185
+ )
186
+ exclude_info.append((var_name, i.gen.py_ast[0]))
187
+ return model_params, include_info, exclude_info