jaclang 0.5.17__py3-none-any.whl → 0.6.0__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 (51) hide show
  1. jaclang/__init__.py +2 -6
  2. jaclang/cli/cli.py +4 -2
  3. jaclang/compiler/__init__.py +12 -5
  4. jaclang/compiler/absyntree.py +23 -23
  5. jaclang/compiler/generated/jac_parser.py +2 -2
  6. jaclang/compiler/jac.lark +9 -9
  7. jaclang/compiler/parser.py +76 -21
  8. jaclang/compiler/passes/ir_pass.py +10 -8
  9. jaclang/compiler/passes/main/__init__.py +3 -2
  10. jaclang/compiler/passes/main/access_modifier_pass.py +173 -0
  11. jaclang/compiler/passes/main/fuse_typeinfo_pass.py +3 -2
  12. jaclang/compiler/passes/main/import_pass.py +33 -21
  13. jaclang/compiler/passes/main/pyast_gen_pass.py +99 -44
  14. jaclang/compiler/passes/main/pyast_load_pass.py +141 -77
  15. jaclang/compiler/passes/main/pyout_pass.py +14 -13
  16. jaclang/compiler/passes/main/registry_pass.py +8 -3
  17. jaclang/compiler/passes/main/schedules.py +5 -3
  18. jaclang/compiler/passes/main/sym_tab_build_pass.py +47 -37
  19. jaclang/compiler/passes/main/tests/test_import_pass.py +2 -2
  20. jaclang/compiler/passes/tool/jac_formatter_pass.py +85 -23
  21. jaclang/compiler/passes/tool/tests/test_jac_format_pass.py +11 -4
  22. jaclang/compiler/passes/transform.py +2 -0
  23. jaclang/compiler/symtable.py +10 -3
  24. jaclang/compiler/tests/test_importer.py +9 -0
  25. jaclang/compiler/workspace.py +19 -11
  26. jaclang/core/aott.py +34 -63
  27. jaclang/core/importer.py +73 -65
  28. jaclang/core/llms/__init__.py +20 -0
  29. jaclang/core/llms/anthropic.py +61 -0
  30. jaclang/core/llms/base.py +206 -0
  31. jaclang/core/llms/groq.py +67 -0
  32. jaclang/core/llms/huggingface.py +73 -0
  33. jaclang/core/llms/ollama.py +78 -0
  34. jaclang/core/llms/openai.py +61 -0
  35. jaclang/core/llms/togetherai.py +60 -0
  36. jaclang/core/llms/utils.py +9 -0
  37. jaclang/core/utils.py +16 -1
  38. jaclang/plugin/default.py +47 -16
  39. jaclang/plugin/feature.py +9 -6
  40. jaclang/plugin/spec.py +8 -1
  41. jaclang/settings.py +95 -0
  42. jaclang/utils/helpers.py +6 -2
  43. jaclang/utils/treeprinter.py +9 -6
  44. jaclang/vendor/mypy/checker.py +2 -3
  45. jaclang-0.6.0.dist-info/METADATA +17 -0
  46. {jaclang-0.5.17.dist-info → jaclang-0.6.0.dist-info}/RECORD +49 -39
  47. jaclang/core/llms.py +0 -29
  48. jaclang-0.5.17.dist-info/METADATA +0 -7
  49. {jaclang-0.5.17.dist-info → jaclang-0.6.0.dist-info}/WHEEL +0 -0
  50. {jaclang-0.5.17.dist-info → jaclang-0.6.0.dist-info}/entry_points.txt +0 -0
  51. {jaclang-0.5.17.dist-info → jaclang-0.6.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,73 @@
1
+ """Huggingface client for MTLLM."""
2
+
3
+ from .base import BaseLLM
4
+
5
+
6
+ REASON_SUFFIX = """
7
+ Reason and return the output results(s) only such that <Output> should be eval(<Output>) Compatible and reflects the
8
+ expected output type, Follow the format below to provide the reasoning for the output result(s).
9
+
10
+ [Reasoning] <Reasoning>
11
+ [Output] <Output>
12
+ """
13
+
14
+ NORMAL_SUFFIX = """Return the output result(s) only such that <Output> should be eval(<Output>) Compatible and
15
+ reflects the expected output type, Follow the format below to provide the output result(s).
16
+
17
+ [Output] <Output>
18
+ """ # noqa E501
19
+
20
+ CHAIN_OF_THOUGHT_SUFFIX = """
21
+ Generate and return the output result(s) only, adhering to the provided Type in the following format. Perform the operation in a chain of thoughts.(Think Step by Step)
22
+
23
+ [Chain of Thoughts] <Chain of Thoughts>
24
+ [Output] <Result>
25
+ """ # noqa E501
26
+
27
+ REACT_SUFFIX = """
28
+ """ # noqa E501
29
+
30
+
31
+ class Huggingface(BaseLLM):
32
+ """Huggingface API client for Large Language Models (LLMs)."""
33
+
34
+ MTLLM_METHOD_PROMPTS: dict[str, str] = {
35
+ "Normal": NORMAL_SUFFIX,
36
+ "Reason": REASON_SUFFIX,
37
+ "Chain-of-Thoughts": CHAIN_OF_THOUGHT_SUFFIX,
38
+ "ReAct": REACT_SUFFIX,
39
+ }
40
+
41
+ def __init__(
42
+ self, verbose: bool = False, max_tries: int = 10, **kwargs: dict
43
+ ) -> None:
44
+ """Initialize the Huggingface API client."""
45
+ import torch # type: ignore
46
+ from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline # type: ignore
47
+
48
+ torch.random.manual_seed(0)
49
+ model = AutoModelForCausalLM.from_pretrained(
50
+ kwargs.get("model_name", "microsoft/Phi-3-mini-128k-instruct"),
51
+ device_map=kwargs.get("device_map", "cuda"),
52
+ torch_dtype="auto",
53
+ trust_remote_code=True,
54
+ )
55
+ tokenizer = AutoTokenizer.from_pretrained(
56
+ kwargs.get("model_name", "microsoft/Phi-3-mini-128k-instruct")
57
+ )
58
+ self.verbose = verbose
59
+ self.max_tries = max_tries
60
+ self.pipe = pipeline("text-generation", model=model, tokenizer=tokenizer)
61
+ self.temperature = kwargs.get("temperature", 0.7)
62
+ self.max_tokens = kwargs.get("max_new_tokens", 1024)
63
+
64
+ def __infer__(self, meaning_in: str, **kwargs: dict) -> str:
65
+ """Infer a response from the input meaning."""
66
+ messages = [{"role": "user", "content": meaning_in}]
67
+ output = self.pipe(
68
+ messages,
69
+ temperature=kwargs.get("temperature", self.temperature),
70
+ max_length=kwargs.get("max_new_tokens", self.max_tokens),
71
+ **kwargs,
72
+ )
73
+ return output[0]["generated_text"][-1]["content"]
@@ -0,0 +1,78 @@
1
+ """Ollama client for MTLLM."""
2
+
3
+ from .base import BaseLLM
4
+
5
+ REASON_SUFFIX = """
6
+ Reason and return the output results(s) only such that <Output> should be eval(<Output>) Compatible and reflects the
7
+ expected output type, Follow the format below to provide the reasoning for the output result(s).
8
+
9
+ [Reasoning] <Reasoning>
10
+ [Output] <Output>
11
+ """
12
+
13
+ NORMAL_SUFFIX = """Return the output result(s) only such that <Output> should be eval(<Output>) Compatible and
14
+ reflects the expected output type, Follow the format below to provide the output result(s).
15
+
16
+ [Output] <Output>
17
+ """ # noqa E501
18
+
19
+ CHAIN_OF_THOUGHT_SUFFIX = """
20
+ Generate and return the output result(s) only, adhering to the provided Type in the following format. Perform the operation in a chain of thoughts.(Think Step by Step)
21
+
22
+ [Chain of Thoughts] <Chain of Thoughts>
23
+ [Output] <Result>
24
+ """ # noqa E501
25
+
26
+ REACT_SUFFIX = """
27
+ """ # noqa E501
28
+
29
+
30
+ class Ollama(BaseLLM):
31
+ """Ollama API client for Large Language Models (LLMs)."""
32
+
33
+ MTLLM_METHOD_PROMPTS: dict[str, str] = {
34
+ "Normal": NORMAL_SUFFIX,
35
+ "Reason": REASON_SUFFIX,
36
+ "Chain-of-Thoughts": CHAIN_OF_THOUGHT_SUFFIX,
37
+ "ReAct": REACT_SUFFIX,
38
+ }
39
+
40
+ def __init__(
41
+ self, verbose: bool = False, max_tries: int = 10, **kwargs: dict
42
+ ) -> None:
43
+ """Initialize the Ollama API client."""
44
+ import ollama # type: ignore
45
+
46
+ self.client = ollama.Client(host=kwargs.get("host", "http://localhost:11434"))
47
+ self.verbose = verbose
48
+ self.max_tries = max_tries
49
+ self.model_name = kwargs.get("model_name", "phi3")
50
+ self.default_model_params = {
51
+ k: v for k, v in kwargs.items() if k not in ["model_name", "host"]
52
+ }
53
+
54
+ def __infer__(self, meaning_in: str, **kwargs: dict) -> str:
55
+ """Infer a response from the input meaning."""
56
+ model = str(kwargs.get("model_name", self.model_name))
57
+ if not self.check_model(model):
58
+ self.download_model(model)
59
+ model_params = {k: v for k, v in kwargs.items() if k not in ["model_name"]}
60
+ messages = [{"role": "user", "content": meaning_in}]
61
+ output = self.client.chat(
62
+ model=model,
63
+ messages=messages,
64
+ options={**self.default_model_params, **model_params},
65
+ )
66
+ return output["message"]["content"]
67
+
68
+ def check_model(self, model_name: str) -> bool:
69
+ """Check if the model is available."""
70
+ try:
71
+ self.client.show(model_name)
72
+ return True
73
+ except Exception:
74
+ return False
75
+
76
+ def download_model(self, model_name: str) -> None:
77
+ """Download the model."""
78
+ self.client.pull(model_name)
@@ -0,0 +1,61 @@
1
+ """Anthropic API client for MTLLM."""
2
+
3
+ from .base import BaseLLM
4
+
5
+
6
+ REASON_SUFFIX = """
7
+ Reason and return the output result(s) only, adhering to the provided Type in the following format
8
+
9
+ [Reasoning] <Reason>
10
+ [Output] <Result>
11
+ """
12
+
13
+ NORMAL_SUFFIX = """Generate and return the output result(s) only, adhering to the provided Type in the following format
14
+
15
+ [Output] <result>
16
+ """ # noqa E501
17
+
18
+ CHAIN_OF_THOUGHT_SUFFIX = """
19
+ Generate and return the output result(s) only, adhering to the provided Type in the following format. Perform the operation in a chain of thoughts.(Think Step by Step)
20
+
21
+ [Chain of Thoughts] <Chain of Thoughts>
22
+ [Output] <Result>
23
+ """ # noqa E501
24
+
25
+ REACT_SUFFIX = """
26
+ """ # noqa E501
27
+
28
+
29
+ class OpenAI(BaseLLM):
30
+ """Anthropic API client for MTLLM."""
31
+
32
+ MTLLM_METHOD_PROMPTS: dict[str, str] = {
33
+ "Normal": NORMAL_SUFFIX,
34
+ "Reason": REASON_SUFFIX,
35
+ "Chain-of-Thoughts": CHAIN_OF_THOUGHT_SUFFIX,
36
+ "ReAct": REACT_SUFFIX,
37
+ }
38
+
39
+ def __init__(
40
+ self, verbose: bool = False, max_tries: int = 10, **kwargs: dict
41
+ ) -> None:
42
+ """Initialize the Anthropic API client."""
43
+ import openai # type: ignore
44
+
45
+ self.client = openai.OpenAI()
46
+ self.verbose = verbose
47
+ self.max_tries = max_tries
48
+ self.model_name = kwargs.get("model_name", "gpt-3.5-turbo")
49
+ self.temperature = kwargs.get("temperature", 0.7)
50
+ self.max_tokens = kwargs.get("max_tokens", 1024)
51
+
52
+ def __infer__(self, meaning_in: str, **kwargs: dict) -> str:
53
+ """Infer a response from the input meaning."""
54
+ messages = [{"role": "user", "content": meaning_in}]
55
+ output = self.client.chat.completions.create(
56
+ model=kwargs.get("model_name", self.model_name),
57
+ temperature=kwargs.get("temperature", self.temperature),
58
+ max_tokens=kwargs.get("max_tokens", self.max_tokens),
59
+ messages=messages,
60
+ )
61
+ return output.choices[0].message.content
@@ -0,0 +1,60 @@
1
+ """Anthropic API client for MTLLM."""
2
+
3
+ from .base import BaseLLM
4
+
5
+ REASON_SUFFIX = """
6
+ Reason and return the output result(s) only, adhering to the provided Type in the following format
7
+
8
+ [Reasoning] <Reason>
9
+ [Output] <Result>
10
+ """
11
+
12
+ NORMAL_SUFFIX = """Generate and return the output result(s) only, adhering to the provided Type in the following format
13
+
14
+ [Output] <result>
15
+ """ # noqa E501
16
+
17
+ CHAIN_OF_THOUGHT_SUFFIX = """
18
+ Generate and return the output result(s) only, adhering to the provided Type in the following format. Perform the operation in a chain of thoughts.(Think Step by Step)
19
+
20
+ [Chain of Thoughts] <Chain of Thoughts>
21
+ [Output] <Result>
22
+ """ # noqa E501
23
+
24
+ REACT_SUFFIX = """
25
+ """ # noqa E501
26
+
27
+
28
+ class TogetherAI(BaseLLM):
29
+ """Anthropic API client for MTLLM."""
30
+
31
+ MTLLM_METHOD_PROMPTS: dict[str, str] = {
32
+ "Normal": NORMAL_SUFFIX,
33
+ "Reason": REASON_SUFFIX,
34
+ "Chain-of-Thoughts": CHAIN_OF_THOUGHT_SUFFIX,
35
+ "ReAct": REACT_SUFFIX,
36
+ }
37
+
38
+ def __init__(
39
+ self, verbose: bool = False, max_tries: int = 10, **kwargs: dict
40
+ ) -> None:
41
+ """Initialize the Anthropic API client."""
42
+ import together # type: ignore
43
+
44
+ self.client = together.Together()
45
+ self.verbose = verbose
46
+ self.max_tries = max_tries
47
+ self.model_name = kwargs.get("model_name", "mistralai/Mistral-7B-Instruct-v0.3")
48
+ self.temperature = kwargs.get("temperature", 0.7)
49
+ self.max_tokens = kwargs.get("max_tokens", 1024)
50
+
51
+ def __infer__(self, meaning_in: str, **kwargs: dict) -> str:
52
+ """Infer a response from the input meaning."""
53
+ messages = [{"role": "user", "content": meaning_in}]
54
+ output = self.client.chat.completions.create(
55
+ model=kwargs.get("model_name", self.model_name),
56
+ temperature=kwargs.get("temperature", self.temperature),
57
+ max_tokens=kwargs.get("max_tokens", self.max_tokens),
58
+ messages=messages,
59
+ )
60
+ return output.choices[0].message.content
@@ -0,0 +1,9 @@
1
+ """Utility functions for the LLMs module."""
2
+
3
+ try:
4
+ from loguru import logger # noqa F401
5
+ except ImportError:
6
+ import logging
7
+
8
+ logging.basicConfig(level=logging.INFO)
9
+ logger = logging.getLogger(__name__)
jaclang/core/utils.py CHANGED
@@ -3,7 +3,9 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import ast as ast3
6
- from typing import Callable, TYPE_CHECKING
6
+ import sys
7
+ from contextlib import contextmanager
8
+ from typing import Callable, Iterator, TYPE_CHECKING
7
9
 
8
10
  import jaclang.compiler.absyntree as ast
9
11
  from jaclang.core.registry import SemScope
@@ -12,6 +14,19 @@ if TYPE_CHECKING:
12
14
  from jaclang.core.construct import NodeAnchor, NodeArchitype
13
15
 
14
16
 
17
+ @contextmanager
18
+ def sys_path_context(path: str) -> Iterator[None]:
19
+ """Add a path to sys.path temporarily."""
20
+ novel_path = path not in sys.path
21
+ try:
22
+ if novel_path:
23
+ sys.path.append(path)
24
+ yield
25
+ finally:
26
+ if novel_path:
27
+ sys.path.remove(path)
28
+
29
+
15
30
  def collect_node_connections(
16
31
  current_node: NodeAnchor,
17
32
  visited_nodes: set,
jaclang/plugin/default.py CHANGED
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import fnmatch
6
+ import html
6
7
  import os
7
8
  import pickle
8
9
  import types
@@ -18,7 +19,6 @@ from jaclang.core.aott import (
18
19
  get_all_type_explanations,
19
20
  get_info_types,
20
21
  get_object_string,
21
- get_reasoning_output,
22
22
  get_type_annotation,
23
23
  )
24
24
  from jaclang.core.construct import (
@@ -85,7 +85,11 @@ class JacFeatureDefaults:
85
85
  for i in on_entry + on_exit:
86
86
  i.resolve(cls)
87
87
  if not issubclass(cls, arch_base):
88
+ # Saving the module path and reassign it after creating cls
89
+ # So the jac modules are part of the correct module
90
+ cur_module = cls.__module__
88
91
  cls = type(cls.__name__, (cls, arch_base), {})
92
+ cls.__module__ = cur_module
89
93
  cls._jac_entry_funcs_ = on_entry # type: ignore
90
94
  cls._jac_exit_funcs_ = on_exit # type: ignore
91
95
  else:
@@ -423,10 +427,16 @@ class JacFeatureDefaults:
423
427
 
424
428
  @staticmethod
425
429
  @hookimpl
426
- def get_root() -> Architype:
430
+ def get_root() -> Root:
427
431
  """Jac's assign comprehension feature."""
428
432
  return root
429
433
 
434
+ @staticmethod
435
+ @hookimpl
436
+ def get_root_type() -> Type[Root]:
437
+ """Jac's root getter."""
438
+ return Root
439
+
430
440
  @staticmethod
431
441
  @hookimpl
432
442
  def build_edge(
@@ -582,9 +592,15 @@ class JacFeatureDefaults:
582
592
  _scope = SemScope.get_scope_from_str(scope)
583
593
  assert _scope is not None
584
594
 
585
- reason = False
586
- if "reason" in model_params:
587
- reason = model_params.pop("reason")
595
+ method = model_params.pop("method") if "method" in model_params else "Normal"
596
+ available_methods = model.MTLLM_METHOD_PROMPTS.keys()
597
+ assert (
598
+ method in available_methods
599
+ ), f"Invalid method: {method}. Select from {available_methods}"
600
+
601
+ context = (
602
+ "\n".join(model_params.pop("context")) if "context" in model_params else ""
603
+ )
588
604
 
589
605
  type_collector: list = []
590
606
  information, collected_types = get_info_types(_scope, mod_registry, incl_info)
@@ -600,22 +616,34 @@ class JacFeatureDefaults:
600
616
 
601
617
  output_information = f"{outputs[0]} ({outputs[1]})"
602
618
  type_collector.extend(extract_non_primary_type(outputs[1]))
619
+ output_type_explanations = "\n".join(
620
+ list(
621
+ get_all_type_explanations(
622
+ extract_non_primary_type(outputs[1]), mod_registry
623
+ ).values()
624
+ )
625
+ )
603
626
 
604
627
  type_explanations_list = list(
605
628
  get_all_type_explanations(type_collector, mod_registry).values()
606
629
  )
607
630
  type_explanations = "\n".join(type_explanations_list)
608
631
 
609
- meaning_in = aott_raise(
610
- information,
611
- inputs_information,
612
- output_information,
613
- type_explanations,
614
- action,
615
- reason,
632
+ meaning_out = aott_raise(
633
+ model=model,
634
+ information=information,
635
+ inputs_information=inputs_information,
636
+ output_information=output_information,
637
+ type_explanations=type_explanations,
638
+ action=action,
639
+ context=context,
640
+ method=method,
641
+ tools=[],
642
+ model_params=model_params,
643
+ )
644
+ output = model.resolve_output(
645
+ meaning_out, outputs[0], outputs[1], output_type_explanations
616
646
  )
617
- meaning_out = model.__infer__(meaning_in, **model_params)
618
- reasoning, output = get_reasoning_output(meaning_out)
619
647
  return output
620
648
 
621
649
 
@@ -691,13 +719,16 @@ class JacBuiltin:
691
719
  for source, target, edge in connections:
692
720
  dot_content += (
693
721
  f"{visited_nodes.index(source)} -> {visited_nodes.index(target)} "
694
- f' [label="{edge._jac_.obj.__class__.__name__} "];\n'
722
+ f' [label="{html.escape(str(edge._jac_.obj.__class__.__name__))} "];\n'
695
723
  )
696
724
  for node_ in visited_nodes:
697
725
  color = (
698
726
  colors[node_depths[node_]] if node_depths[node_] < 25 else colors[24]
699
727
  )
700
- dot_content += f'{visited_nodes.index(node_)} [label="{node_._jac_.obj}" fillcolor="{color}"];\n'
728
+ dot_content += (
729
+ f'{visited_nodes.index(node_)} [label="{html.escape(str(node_._jac_.obj))}"'
730
+ f'fillcolor="{color}"];\n'
731
+ )
701
732
  if dot_file:
702
733
  with open(dot_file, "w") as f:
703
734
  f.write(dot_content + "}")
jaclang/plugin/feature.py CHANGED
@@ -3,7 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import types
6
- from typing import Any, Callable, Optional, Type, TypeAlias, Union
6
+ from typing import Any, Callable, Optional, Type, Union
7
7
 
8
8
  from jaclang.compiler.absyntree import Module
9
9
  from jaclang.core.construct import (
@@ -31,8 +31,6 @@ class JacFeature:
31
31
  from jaclang.plugin.spec import DSFunc
32
32
  from jaclang.compiler.constant import EdgeDir
33
33
 
34
- RootType: TypeAlias = Root
35
-
36
34
  @staticmethod
37
35
  def make_architype(
38
36
  cls: type,
@@ -82,7 +80,7 @@ class JacFeature:
82
80
  mdl_alias: Optional[str] = None,
83
81
  override_name: Optional[str] = None,
84
82
  mod_bundle: Optional[Module] = None,
85
- lng: Optional[str] = None,
83
+ lng: Optional[str] = "jac",
86
84
  items: Optional[dict[str, Union[str, bool]]] = None,
87
85
  ) -> Optional[types.ModuleType]:
88
86
  """Core Import Process."""
@@ -218,10 +216,15 @@ class JacFeature:
218
216
  return pm.hook.assign_compr(target=target, attr_val=attr_val)
219
217
 
220
218
  @staticmethod
221
- def get_root() -> Architype:
222
- """Jac's assign comprehension feature."""
219
+ def get_root() -> Root:
220
+ """Jac's root getter."""
223
221
  return pm.hook.get_root()
224
222
 
223
+ @staticmethod
224
+ def get_root_type() -> Type[Root]:
225
+ """Jac's root type getter."""
226
+ return pm.hook.get_root_type()
227
+
225
228
  @staticmethod
226
229
  def build_edge(
227
230
  is_undirected: bool,
jaclang/plugin/spec.py CHANGED
@@ -12,6 +12,7 @@ from jaclang.plugin.default import (
12
12
  EdgeArchitype,
13
13
  EdgeDir,
14
14
  NodeArchitype,
15
+ Root,
15
16
  WalkerArchitype,
16
17
  )
17
18
 
@@ -198,7 +199,13 @@ class JacFeatureSpec:
198
199
 
199
200
  @staticmethod
200
201
  @hookspec(firstresult=True)
201
- def get_root() -> Architype:
202
+ def get_root() -> Root:
203
+ """Jac's root getter."""
204
+ raise NotImplementedError
205
+
206
+ @staticmethod
207
+ @hookspec(firstresult=True)
208
+ def get_root_type() -> Type[Root]:
202
209
  """Jac's root getter."""
203
210
  raise NotImplementedError
204
211
 
jaclang/settings.py ADDED
@@ -0,0 +1,95 @@
1
+ """Main settings of Jac lang."""
2
+
3
+ import configparser
4
+ import os
5
+ from dataclasses import dataclass, fields
6
+
7
+
8
+ @dataclass
9
+ class Settings:
10
+ """Main settings of Jac lang."""
11
+
12
+ fuse_type_info_debug: bool = False
13
+ py_raise: bool = False
14
+
15
+ def __post_init__(self) -> None:
16
+ """Initialize settings."""
17
+ home_dir = os.path.expanduser("~")
18
+ config_dir = os.path.join(home_dir, ".jaclang")
19
+ self.config_file_path = os.path.join(config_dir, "config.ini")
20
+ os.makedirs(config_dir, exist_ok=True)
21
+ if not os.path.exists(self.config_file_path):
22
+ with open(self.config_file_path, "w") as f:
23
+ f.write("[settings]\n")
24
+ self.load_all()
25
+
26
+ def load_all(self) -> None:
27
+ """Load settings from all available sources."""
28
+ self.load_config_file()
29
+ self.load_env_vars()
30
+
31
+ def load_config_file(self) -> None:
32
+ """Load settings from a configuration file."""
33
+ config_parser = configparser.ConfigParser()
34
+ config_parser.read(self.config_file_path)
35
+ if "settings" in config_parser:
36
+ for key in config_parser["settings"]:
37
+ if key in [f.name for f in fields(self)]:
38
+ setattr(
39
+ self, key, self.convert_type(config_parser["settings"][key])
40
+ )
41
+
42
+ def load_env_vars(self) -> None:
43
+ """Override settings from environment variables if available."""
44
+ for key in [f.name for f in fields(self)]:
45
+ env_value = os.getenv("JACLANG_" + key.upper())
46
+ if env_value is not None:
47
+ setattr(self, key, self.convert_type(env_value))
48
+
49
+ # def load_command_line_arguments(self):
50
+ # """Override settings from command-line arguments if provided."""
51
+ # parser = argparse.ArgumentParser()
52
+ # parser.add_argument(
53
+ # "--debug",
54
+ # type=self.str_to_bool,
55
+ # nargs="?",
56
+ # const=True,
57
+ # default=self.config["debug"],
58
+ # )
59
+ # parser.add_argument("--port", type=int, default=self.config["port"])
60
+ # parser.add_argument("--host", default=self.config["host"])
61
+ # args = parser.parse_args()
62
+
63
+ def str_to_bool(self, value: str) -> bool:
64
+ """Convert string to boolean."""
65
+ return value.lower() in ("yes", "y", "true", "t", "1")
66
+
67
+ def convert_type(self, value: str) -> bool | str | int:
68
+ """Convert string values from the config to the appropriate type."""
69
+ if value.isdigit():
70
+ return int(value)
71
+ if value.lower() in (
72
+ "true",
73
+ "false",
74
+ "t",
75
+ "f",
76
+ "yes",
77
+ "no",
78
+ "y",
79
+ "n",
80
+ "1",
81
+ "0",
82
+ ):
83
+ return self.str_to_bool(value)
84
+ return value
85
+
86
+ def __str__(self) -> str:
87
+ """Return string representation of the settings."""
88
+ return "\n".join(
89
+ [f"{field.name}: {getattr(self, field.name)}" for field in fields(self)]
90
+ )
91
+
92
+
93
+ settings = Settings()
94
+
95
+ __all__ = ["settings"]
jaclang/utils/helpers.py CHANGED
@@ -131,7 +131,6 @@ def import_target_to_relative_path(
131
131
  import_level: int,
132
132
  import_target: str,
133
133
  base_path: Optional[str] = None,
134
- file_extension: str = ".jac",
135
134
  ) -> str:
136
135
  """Convert an import target string into a relative file path."""
137
136
  if not base_path:
@@ -141,7 +140,12 @@ def import_target_to_relative_path(
141
140
  actual_parts = parts[traversal_levels:]
142
141
  for _ in range(traversal_levels):
143
142
  base_path = os.path.dirname(base_path)
144
- relative_path = os.path.join(base_path, *actual_parts) + file_extension
143
+ relative_path = os.path.join(base_path, *actual_parts)
144
+ relative_path = (
145
+ relative_path + ".jac"
146
+ if os.path.exists(relative_path + ".jac")
147
+ else relative_path
148
+ )
145
149
  return relative_path
146
150
 
147
151
 
@@ -88,16 +88,19 @@ def print_ast_tree(
88
88
  from jaclang.compiler.absyntree import AstSymbolNode, Token
89
89
 
90
90
  def __node_repr_in_tree(node: AstNode) -> str:
91
+ access = (
92
+ f"Access: {node.access.tag.value}"
93
+ if isinstance(node, ast.AstAccessNode) and node.access is not None
94
+ else ""
95
+ )
91
96
  if isinstance(node, Token) and isinstance(node, AstSymbolNode):
92
- return (
93
- f"{node.__class__.__name__} - {node.value} - Type: {node.sym_info.typ}"
94
- )
97
+ return f"{node.__class__.__name__} - {node.value} - Type: {node.sym_info.typ}, {access}"
95
98
  elif isinstance(node, Token):
96
- return f"{node.__class__.__name__} - {node.value}"
99
+ return f"{node.__class__.__name__} - {node.value}, {access}"
97
100
  elif isinstance(node, AstSymbolNode):
98
- return f"{node.__class__.__name__} - {node.sym_name} - Type: {node.sym_info.typ}"
101
+ return f"{node.__class__.__name__} - {node.sym_name} - Type: {node.sym_info.typ}, {access}"
99
102
  else:
100
- return f"{node.__class__.__name__}"
103
+ return f"{node.__class__.__name__}, {access}"
101
104
 
102
105
  def __node_repr_in_py_tree(node: ast3.AST) -> str:
103
106
  if isinstance(node, ast3.Constant):
@@ -3637,11 +3637,10 @@ class TypeChecker(NodeVisitor[None], CheckerPluginInterface):
3637
3637
  if (
3638
3638
  lv.node.final_unset_in_class
3639
3639
  and not lv.node.final_set_in_init
3640
- and not self.is_stub
3641
- and # It is OK to skip initializer in stub files.
3640
+ and not self.is_stub # It is OK to skip initializer in stub files.
3642
3641
  # Avoid extra error messages, if there is no type in Final[...],
3643
3642
  # then we already reported the error about missing r.h.s.
3644
- isinstance(s, AssignmentStmt)
3643
+ and isinstance(s, AssignmentStmt)
3645
3644
  and s.type is not None
3646
3645
  ):
3647
3646
  self.msg.final_without_value(s)