jaclang 0.7.1__py3-none-any.whl → 0.7.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of jaclang might be problematic. Click here for more details.

Files changed (85) hide show
  1. jaclang/cli/cli.py +2 -2
  2. jaclang/compiler/absyntree.py +378 -277
  3. jaclang/compiler/codeloc.py +2 -2
  4. jaclang/compiler/constant.py +2 -0
  5. jaclang/compiler/jac.lark +25 -19
  6. jaclang/compiler/parser.py +115 -92
  7. jaclang/compiler/passes/main/access_modifier_pass.py +15 -9
  8. jaclang/compiler/passes/main/def_impl_match_pass.py +29 -11
  9. jaclang/compiler/passes/main/def_use_pass.py +48 -17
  10. jaclang/compiler/passes/main/fuse_typeinfo_pass.py +49 -30
  11. jaclang/compiler/passes/main/import_pass.py +12 -7
  12. jaclang/compiler/passes/main/pyast_gen_pass.py +110 -47
  13. jaclang/compiler/passes/main/pyast_load_pass.py +49 -13
  14. jaclang/compiler/passes/main/pyjac_ast_link_pass.py +25 -11
  15. jaclang/compiler/passes/main/pyout_pass.py +3 -1
  16. jaclang/compiler/passes/main/registry_pass.py +6 -6
  17. jaclang/compiler/passes/main/sym_tab_build_pass.py +30 -72
  18. jaclang/compiler/passes/main/tests/test_decl_def_match_pass.py +21 -4
  19. jaclang/compiler/passes/main/tests/test_def_use_pass.py +5 -10
  20. jaclang/compiler/passes/main/tests/test_import_pass.py +8 -0
  21. jaclang/compiler/passes/main/tests/test_type_check_pass.py +1 -1
  22. jaclang/compiler/passes/main/type_check_pass.py +2 -1
  23. jaclang/compiler/passes/tool/jac_formatter_pass.py +44 -11
  24. jaclang/compiler/passes/tool/tests/fixtures/corelib.jac +16 -0
  25. jaclang/compiler/passes/tool/tests/fixtures/corelib_fmt.jac +16 -0
  26. jaclang/compiler/passes/tool/tests/fixtures/doc_string.jac +15 -0
  27. jaclang/compiler/passes/tool/tests/test_jac_format_pass.py +7 -5
  28. jaclang/compiler/passes/tool/tests/test_unparse_validate.py +1 -2
  29. jaclang/compiler/passes/transform.py +2 -4
  30. jaclang/{core/registry.py → compiler/semtable.py} +1 -3
  31. jaclang/compiler/symtable.py +39 -31
  32. jaclang/compiler/tests/test_parser.py +2 -2
  33. jaclang/core/aott.py +112 -16
  34. jaclang/core/{construct.py → architype.py} +44 -93
  35. jaclang/core/constructs.py +44 -0
  36. jaclang/core/context.py +157 -0
  37. jaclang/core/importer.py +18 -9
  38. jaclang/core/llms/anthropic.py +31 -2
  39. jaclang/core/llms/base.py +3 -3
  40. jaclang/core/llms/groq.py +4 -1
  41. jaclang/core/llms/huggingface.py +4 -1
  42. jaclang/core/llms/ollama.py +4 -1
  43. jaclang/core/llms/openai.py +6 -2
  44. jaclang/core/llms/togetherai.py +4 -1
  45. jaclang/core/memory.py +53 -2
  46. jaclang/core/test.py +90 -0
  47. jaclang/core/utils.py +2 -2
  48. jaclang/langserve/engine.py +119 -122
  49. jaclang/langserve/server.py +27 -5
  50. jaclang/langserve/tests/fixtures/circle.jac +16 -12
  51. jaclang/langserve/tests/fixtures/circle_err.jac +3 -3
  52. jaclang/langserve/tests/fixtures/circle_pure.impl.jac +8 -4
  53. jaclang/langserve/tests/fixtures/circle_pure.jac +2 -2
  54. jaclang/langserve/tests/test_server.py +114 -0
  55. jaclang/langserve/utils.py +104 -10
  56. jaclang/plugin/builtin.py +1 -1
  57. jaclang/plugin/default.py +46 -90
  58. jaclang/plugin/feature.py +32 -16
  59. jaclang/plugin/spec.py +17 -19
  60. jaclang/plugin/tests/test_features.py +0 -33
  61. jaclang/settings.py +4 -0
  62. jaclang/tests/fixtures/abc.jac +16 -12
  63. jaclang/tests/fixtures/byllmissue.jac +12 -0
  64. jaclang/tests/fixtures/edgetypetest.jac +16 -0
  65. jaclang/tests/fixtures/hash_init_check.jac +17 -0
  66. jaclang/tests/fixtures/impl_match_confused.impl.jac +1 -0
  67. jaclang/tests/fixtures/impl_match_confused.jac +5 -0
  68. jaclang/tests/fixtures/math_question.jpg +0 -0
  69. jaclang/tests/fixtures/maxfail_run_test.jac +17 -5
  70. jaclang/tests/fixtures/nosigself.jac +19 -0
  71. jaclang/tests/fixtures/run_test.jac +17 -5
  72. jaclang/tests/fixtures/walker_override.jac +21 -0
  73. jaclang/tests/fixtures/with_llm_vision.jac +25 -0
  74. jaclang/tests/test_bugs.py +19 -0
  75. jaclang/tests/test_cli.py +1 -1
  76. jaclang/tests/test_language.py +116 -11
  77. jaclang/tests/test_reference.py +1 -1
  78. jaclang/utils/lang_tools.py +5 -4
  79. jaclang/utils/test.py +2 -1
  80. jaclang/utils/treeprinter.py +35 -4
  81. {jaclang-0.7.1.dist-info → jaclang-0.7.5.dist-info}/METADATA +3 -2
  82. {jaclang-0.7.1.dist-info → jaclang-0.7.5.dist-info}/RECORD +84 -71
  83. jaclang/core/shelve_storage.py +0 -55
  84. {jaclang-0.7.1.dist-info → jaclang-0.7.5.dist-info}/WHEEL +0 -0
  85. {jaclang-0.7.1.dist-info → jaclang-0.7.5.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,157 @@
1
+ """Core constructs for Jac Language."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import unittest
6
+ from contextvars import ContextVar
7
+ from typing import Callable, Optional
8
+ from uuid import UUID
9
+
10
+ from .architype import Architype, Root
11
+ from .memory import Memory, ShelveStorage
12
+
13
+
14
+ class ExecutionContext:
15
+ """Default Execution Context implementation."""
16
+
17
+ mem: Optional[Memory]
18
+ root: Optional[Root]
19
+
20
+ def __init__(self) -> None:
21
+ """Create execution context."""
22
+ super().__init__()
23
+ self.mem = ShelveStorage()
24
+ self.root = None
25
+
26
+ def init_memory(self, session: str = "") -> None:
27
+ """Initialize memory."""
28
+ if session:
29
+ self.mem = ShelveStorage(session)
30
+ else:
31
+ self.mem = Memory()
32
+
33
+ def get_root(self) -> Root:
34
+ """Get the root object."""
35
+ if self.mem is None:
36
+ raise ValueError("Memory not initialized")
37
+
38
+ if not self.root:
39
+ root = self.mem.get_obj(UUID(int=0))
40
+ if root is None:
41
+ self.root = Root()
42
+ self.mem.save_obj(self.root, persistent=self.root._jac_.persistent)
43
+ elif not isinstance(root, Root):
44
+ raise ValueError(f"Invalid root object: {root}")
45
+ else:
46
+ self.root = root
47
+ return self.root
48
+
49
+ def get_obj(self, obj_id: UUID) -> Architype | None:
50
+ """Get object from memory."""
51
+ if self.mem is None:
52
+ raise ValueError("Memory not initialized")
53
+
54
+ return self.mem.get_obj(obj_id)
55
+
56
+ def save_obj(self, item: Architype, persistent: bool) -> None:
57
+ """Save object to memory."""
58
+ if self.mem is None:
59
+ raise ValueError("Memory not initialized")
60
+
61
+ self.mem.save_obj(item, persistent)
62
+
63
+ def reset(self) -> None:
64
+ """Reset the execution context."""
65
+ if self.mem:
66
+ self.mem.close()
67
+ self.mem = None
68
+ self.root = None
69
+
70
+
71
+ exec_context: ContextVar[ExecutionContext | None] = ContextVar(
72
+ "ExecutionContext", default=None
73
+ )
74
+
75
+
76
+ class JacTestResult(unittest.TextTestResult):
77
+ """Jac test result class."""
78
+
79
+ def __init__(
80
+ self,
81
+ stream, # noqa
82
+ descriptions, # noqa
83
+ verbosity: int,
84
+ max_failures: Optional[int] = None,
85
+ ) -> None:
86
+ """Initialize FailFastTestResult object."""
87
+ super().__init__(stream, descriptions, verbosity) # noqa
88
+ self.failures_count = JacTestCheck.failcount
89
+ self.max_failures = max_failures
90
+
91
+ def addFailure(self, test, err) -> None: # noqa
92
+ """Count failures and stop."""
93
+ super().addFailure(test, err)
94
+ self.failures_count += 1
95
+ if self.max_failures is not None and self.failures_count >= self.max_failures:
96
+ self.stop()
97
+
98
+ def stop(self) -> None:
99
+ """Stop the test execution."""
100
+ self.shouldStop = True
101
+
102
+
103
+ class JacTextTestRunner(unittest.TextTestRunner):
104
+ """Jac test runner class."""
105
+
106
+ def __init__(self, max_failures: Optional[int] = None, **kwargs) -> None: # noqa
107
+ """Initialize JacTextTestRunner object."""
108
+ self.max_failures = max_failures
109
+ super().__init__(**kwargs)
110
+
111
+ def _makeResult(self) -> JacTestResult: # noqa
112
+ """Override the method to return an instance of JacTestResult."""
113
+ return JacTestResult(
114
+ self.stream,
115
+ self.descriptions,
116
+ self.verbosity,
117
+ max_failures=self.max_failures,
118
+ )
119
+
120
+
121
+ class JacTestCheck:
122
+ """Jac Testing and Checking."""
123
+
124
+ test_case = unittest.TestCase()
125
+ test_suite = unittest.TestSuite()
126
+ breaker = False
127
+ failcount = 0
128
+
129
+ @staticmethod
130
+ def reset() -> None:
131
+ """Clear the test suite."""
132
+ JacTestCheck.test_case = unittest.TestCase()
133
+ JacTestCheck.test_suite = unittest.TestSuite()
134
+
135
+ @staticmethod
136
+ def run_test(xit: bool, maxfail: int | None, verbose: bool) -> None:
137
+ """Run the test suite."""
138
+ verb = 2 if verbose else 1
139
+ runner = JacTextTestRunner(max_failures=maxfail, failfast=xit, verbosity=verb)
140
+ result = runner.run(JacTestCheck.test_suite)
141
+ if result.wasSuccessful():
142
+ print("Passed successfully.")
143
+ else:
144
+ fails = len(result.failures)
145
+ JacTestCheck.failcount += fails
146
+ JacTestCheck.breaker = (
147
+ (JacTestCheck.failcount >= maxfail) if maxfail else True
148
+ )
149
+
150
+ @staticmethod
151
+ def add_test(test_fun: Callable) -> None:
152
+ """Create a new test."""
153
+ JacTestCheck.test_suite.addTest(unittest.FunctionTestCase(test_fun))
154
+
155
+ def __getattr__(self, name: str) -> object:
156
+ """Make convenient check.Equal(...) etc."""
157
+ return getattr(JacTestCheck.test_case, name)
jaclang/core/importer.py CHANGED
@@ -11,7 +11,6 @@ from jaclang.compiler.absyntree import Module
11
11
  from jaclang.compiler.compile import compile_jac
12
12
  from jaclang.compiler.constant import Constants as Con
13
13
  from jaclang.core.utils import sys_path_context
14
- from jaclang.utils.log import logging
15
14
 
16
15
 
17
16
  def jac_importer(
@@ -21,7 +20,7 @@ def jac_importer(
21
20
  cachable: bool = True,
22
21
  mdl_alias: Optional[str] = None,
23
22
  override_name: Optional[str] = None,
24
- mod_bundle: Optional[Module] = None,
23
+ mod_bundle: Optional[Module | str] = None,
25
24
  lng: Optional[str] = "jac",
26
25
  items: Optional[dict[str, Union[str, bool]]] = None,
27
26
  ) -> Optional[types.ModuleType]:
@@ -41,6 +40,14 @@ def jac_importer(
41
40
  elif not override_name and not package_path and module_name in sys.modules:
42
41
  return sys.modules[module_name]
43
42
 
43
+ valid_mod_bundle = (
44
+ sys.modules[mod_bundle].__jac_mod_bundle__
45
+ if isinstance(mod_bundle, str)
46
+ and mod_bundle in sys.modules
47
+ and "__jac_mod_bundle__" in sys.modules[mod_bundle].__dict__
48
+ else mod_bundle
49
+ )
50
+
44
51
  caller_dir = get_caller_dir(target, base_path, dir_path)
45
52
  full_target = path.normpath(path.join(caller_dir, file_name))
46
53
 
@@ -51,10 +58,10 @@ def jac_importer(
51
58
  else:
52
59
  module_name = override_name if override_name else module_name
53
60
  module = create_jac_py_module(
54
- mod_bundle, module_name, package_path, full_target
61
+ valid_mod_bundle, module_name, package_path, full_target
55
62
  )
56
- if mod_bundle:
57
- codeobj = mod_bundle.mod_deps[full_target].gen.py_bytecode
63
+ if valid_mod_bundle:
64
+ codeobj = valid_mod_bundle.mod_deps[full_target].gen.py_bytecode
58
65
  codeobj = marshal.loads(codeobj) if isinstance(codeobj, bytes) else None
59
66
  else:
60
67
  gen_dir = path.join(caller_dir, Con.JAC_GEN_DIR)
@@ -69,9 +76,8 @@ def jac_importer(
69
76
  else:
70
77
  result = compile_jac(full_target, cache_result=cachable)
71
78
  if result.errors_had or not result.ir.gen.py_bytecode:
72
- for e in result.errors_had:
73
- print(e)
74
- logging.error(e)
79
+ # for e in result.errors_had:
80
+ # print(e)
75
81
  return None
76
82
  else:
77
83
  codeobj = marshal.loads(result.ir.gen.py_bytecode)
@@ -84,7 +90,10 @@ def jac_importer(
84
90
 
85
91
 
86
92
  def create_jac_py_module(
87
- mod_bundle: Optional[Module], module_name: str, package_path: str, full_target: str
93
+ mod_bundle: Optional[Module | str],
94
+ module_name: str,
95
+ package_path: str,
96
+ full_target: str,
88
97
  ) -> types.ModuleType:
89
98
  """Create a module."""
90
99
  module = types.ModuleType(module_name)
@@ -45,12 +45,41 @@ class Anthropic(BaseLLM):
45
45
  self.client = anthropic.Anthropic()
46
46
  self.verbose = verbose
47
47
  self.max_tries = max_tries
48
- self.model_name = kwargs.get("model_name", "claude-3-sonnet-20240229")
48
+ self.model_name = str(kwargs.get("model_name", "claude-3-sonnet-20240229"))
49
49
  self.temperature = kwargs.get("temperature", 0.7)
50
50
  self.max_tokens = kwargs.get("max_tokens", 1024)
51
51
 
52
- def __infer__(self, meaning_in: str, **kwargs: dict) -> str:
52
+ def __infer__(self, meaning_in: str | list[dict], **kwargs: dict) -> str:
53
53
  """Infer a response from the input meaning."""
54
+ if not isinstance(meaning_in, str):
55
+ assert self.model_name.startswith(
56
+ ("claude-3-opus", "claude-3-sonnet", "claude-3-haiku")
57
+ ), f"Model {self.model_name} is not multimodal, use a multimodal model instead."
58
+
59
+ import re
60
+
61
+ formatted_meaning_in = []
62
+ for item in meaning_in:
63
+ if item["type"] == "image_url":
64
+ # "data:image/jpeg;base64,base64_string"
65
+ img_match = re.match(
66
+ r"data:(image/[a-zA-Z]*);base64,(.*)", item["source"]
67
+ )
68
+ if img_match:
69
+ media_type, base64_string = img_match.groups()
70
+ formatted_meaning_in.append(
71
+ {
72
+ "type": "image",
73
+ "source": {
74
+ "type": "base64",
75
+ "media_type": media_type,
76
+ "data": base64_string,
77
+ },
78
+ }
79
+ )
80
+ continue
81
+ formatted_meaning_in.append(item)
82
+ meaning_in = formatted_meaning_in
54
83
  messages = [{"role": "user", "content": meaning_in}]
55
84
  output = self.client.messages.create(
56
85
  model=kwargs.get("model_name", self.model_name),
jaclang/core/llms/base.py CHANGED
@@ -112,11 +112,11 @@ class BaseLLM:
112
112
  self.max_tries = max_tries
113
113
  raise NotImplementedError
114
114
 
115
- def __infer__(self, meaning_in: str, **kwargs: dict) -> str:
115
+ def __infer__(self, meaning_in: str | list[dict], **kwargs: dict) -> str:
116
116
  """Infer a response from the input meaning."""
117
117
  raise NotImplementedError
118
118
 
119
- def __call__(self, input_text: str, **kwargs: dict) -> str:
119
+ def __call__(self, input_text: str | list[dict], **kwargs: dict) -> str:
120
120
  """Infer a response from the input text."""
121
121
  if self.verbose:
122
122
  logger.info(f"Meaning In\n{input_text}")
@@ -131,7 +131,7 @@ class BaseLLM:
131
131
  ) -> str:
132
132
  """Resolve the output string to return the reasoning and output."""
133
133
  if self.verbose:
134
- logger.opt(colors=True).info(f"Meaning Out\n<green>{meaning_out}</green>")
134
+ logger.info(f"Meaning Out\n{meaning_out}")
135
135
  output_match = re.search(r"\[Output\](.*)", meaning_out)
136
136
  output = output_match.group(1).strip() if output_match else None
137
137
  if not output_match:
jaclang/core/llms/groq.py CHANGED
@@ -49,8 +49,11 @@ class Groq(BaseLLM):
49
49
  self.temperature = kwargs.get("temperature", 0.7)
50
50
  self.max_tokens = kwargs.get("max_tokens", 1024)
51
51
 
52
- def __infer__(self, meaning_in: str, **kwargs: dict) -> str:
52
+ def __infer__(self, meaning_in: str | list[dict], **kwargs: dict) -> str:
53
53
  """Infer a response from the input meaning."""
54
+ assert isinstance(
55
+ meaning_in, str
56
+ ), "Currently Multimodal models are not supported. Please provide a string input."
54
57
  messages = [{"role": "user", "content": meaning_in}]
55
58
  model_params = {
56
59
  k: v
@@ -61,8 +61,11 @@ class Huggingface(BaseLLM):
61
61
  self.temperature = kwargs.get("temperature", 0.7)
62
62
  self.max_tokens = kwargs.get("max_new_tokens", 1024)
63
63
 
64
- def __infer__(self, meaning_in: str, **kwargs: dict) -> str:
64
+ def __infer__(self, meaning_in: str | list[dict], **kwargs: dict) -> str:
65
65
  """Infer a response from the input meaning."""
66
+ assert isinstance(
67
+ meaning_in, str
68
+ ), "Currently Multimodal models are not supported. Please provide a string input."
66
69
  messages = [{"role": "user", "content": meaning_in}]
67
70
  output = self.pipe(
68
71
  messages,
@@ -51,8 +51,11 @@ class Ollama(BaseLLM):
51
51
  k: v for k, v in kwargs.items() if k not in ["model_name", "host"]
52
52
  }
53
53
 
54
- def __infer__(self, meaning_in: str, **kwargs: dict) -> str:
54
+ def __infer__(self, meaning_in: str | list[dict], **kwargs: dict) -> str:
55
55
  """Infer a response from the input meaning."""
56
+ assert isinstance(
57
+ meaning_in, str
58
+ ), "Currently Multimodal models are not supported. Please provide a string input."
56
59
  model = str(kwargs.get("model_name", self.model_name))
57
60
  if not self.check_model(model):
58
61
  self.download_model(model)
@@ -45,12 +45,16 @@ class OpenAI(BaseLLM):
45
45
  self.client = openai.OpenAI()
46
46
  self.verbose = verbose
47
47
  self.max_tries = max_tries
48
- self.model_name = kwargs.get("model_name", "gpt-3.5-turbo")
48
+ self.model_name = str(kwargs.get("model_name", "gpt-3.5-turbo"))
49
49
  self.temperature = kwargs.get("temperature", 0.7)
50
50
  self.max_tokens = kwargs.get("max_tokens", 1024)
51
51
 
52
- def __infer__(self, meaning_in: str, **kwargs: dict) -> str:
52
+ def __infer__(self, meaning_in: str | list[dict], **kwargs: dict) -> str:
53
53
  """Infer a response from the input meaning."""
54
+ if not isinstance(meaning_in, str):
55
+ assert self.model_name.startswith(
56
+ ("gpt-4o", "gpt-4-turbo")
57
+ ), f"Model {self.model_name} is not multimodal, use a multimodal model instead."
54
58
  messages = [{"role": "user", "content": meaning_in}]
55
59
  output = self.client.chat.completions.create(
56
60
  model=kwargs.get("model_name", self.model_name),
@@ -48,8 +48,11 @@ class TogetherAI(BaseLLM):
48
48
  self.temperature = kwargs.get("temperature", 0.7)
49
49
  self.max_tokens = kwargs.get("max_tokens", 1024)
50
50
 
51
- def __infer__(self, meaning_in: str, **kwargs: dict) -> str:
51
+ def __infer__(self, meaning_in: str | list[dict], **kwargs: dict) -> str:
52
52
  """Infer a response from the input meaning."""
53
+ assert isinstance(
54
+ meaning_in, str
55
+ ), "Currently Multimodal models are not supported. Please provide a string input."
53
56
  messages = [{"role": "user", "content": meaning_in}]
54
57
  output = self.client.chat.completions.create(
55
58
  model=kwargs.get("model_name", self.model_name),
jaclang/core/memory.py CHANGED
@@ -1,8 +1,11 @@
1
- """Memory abstraction for jaseci plugin."""
1
+ """Core constructs for Jac Language."""
2
2
 
3
+ from __future__ import annotations
4
+
5
+ import shelve
3
6
  from uuid import UUID
4
7
 
5
- from jaclang.core.construct import Architype
8
+ from .architype import Architype
6
9
 
7
10
 
8
11
  class Memory:
@@ -46,3 +49,51 @@ class Memory:
46
49
  def close(self) -> None:
47
50
  """Close any connection, if applicable."""
48
51
  self.mem.clear()
52
+
53
+
54
+ class ShelveStorage(Memory):
55
+ """Shelve storage for jaclang runtime object."""
56
+
57
+ storage: shelve.Shelf | None = None
58
+
59
+ def __init__(self, session: str = "") -> None:
60
+ """Init shelve storage."""
61
+ super().__init__()
62
+ if session:
63
+ self.connect(session)
64
+
65
+ def get_obj_from_store(self, obj_id: UUID) -> Architype | None:
66
+ """Get object from the underlying store."""
67
+ obj = super().get_obj_from_store(obj_id)
68
+ if obj is None and self.storage:
69
+ obj = self.storage.get(str(obj_id))
70
+ if obj is not None:
71
+ self.mem[obj_id] = obj
72
+
73
+ return obj
74
+
75
+ def has_obj_in_store(self, obj_id: UUID | str) -> bool:
76
+ """Check if the object exists in the underlying store."""
77
+ return obj_id in self.mem or (
78
+ str(obj_id) in self.storage if self.storage else False
79
+ )
80
+
81
+ def commit(self) -> None:
82
+ """Commit changes to persistent storage."""
83
+ if self.storage is not None:
84
+ for obj_id, obj in self.save_obj_list.items():
85
+ self.storage[str(obj_id)] = obj
86
+ self.save_obj_list.clear()
87
+
88
+ def connect(self, session: str) -> None:
89
+ """Connect to storage."""
90
+ self.session = session
91
+ self.storage = shelve.open(session)
92
+
93
+ def close(self) -> None:
94
+ """Close the storage."""
95
+ super().close()
96
+ self.commit()
97
+ if self.storage:
98
+ self.storage.close()
99
+ self.storage = None
jaclang/core/test.py ADDED
@@ -0,0 +1,90 @@
1
+ """Core constructs for Jac Language."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import unittest
6
+ from typing import Callable, Optional
7
+
8
+
9
+ class JacTestResult(unittest.TextTestResult):
10
+ """Jac test result class."""
11
+
12
+ def __init__(
13
+ self,
14
+ stream, # noqa
15
+ descriptions, # noqa
16
+ verbosity: int,
17
+ max_failures: Optional[int] = None,
18
+ ) -> None:
19
+ """Initialize FailFastTestResult object."""
20
+ super().__init__(stream, descriptions, verbosity) # noqa
21
+ self.failures_count = JacTestCheck.failcount
22
+ self.max_failures = max_failures
23
+
24
+ def addFailure(self, test, err) -> None: # noqa
25
+ """Count failures and stop."""
26
+ super().addFailure(test, err)
27
+ self.failures_count += 1
28
+ if self.max_failures is not None and self.failures_count >= self.max_failures:
29
+ self.stop()
30
+
31
+ def stop(self) -> None:
32
+ """Stop the test execution."""
33
+ self.shouldStop = True
34
+
35
+
36
+ class JacTextTestRunner(unittest.TextTestRunner):
37
+ """Jac test runner class."""
38
+
39
+ def __init__(self, max_failures: Optional[int] = None, **kwargs) -> None: # noqa
40
+ """Initialize JacTextTestRunner object."""
41
+ self.max_failures = max_failures
42
+ super().__init__(**kwargs)
43
+
44
+ def _makeResult(self) -> JacTestResult: # noqa
45
+ """Override the method to return an instance of JacTestResult."""
46
+ return JacTestResult(
47
+ self.stream,
48
+ self.descriptions,
49
+ self.verbosity,
50
+ max_failures=self.max_failures,
51
+ )
52
+
53
+
54
+ class JacTestCheck:
55
+ """Jac Testing and Checking."""
56
+
57
+ test_case = unittest.TestCase()
58
+ test_suite = unittest.TestSuite()
59
+ breaker = False
60
+ failcount = 0
61
+
62
+ @staticmethod
63
+ def reset() -> None:
64
+ """Clear the test suite."""
65
+ JacTestCheck.test_case = unittest.TestCase()
66
+ JacTestCheck.test_suite = unittest.TestSuite()
67
+
68
+ @staticmethod
69
+ def run_test(xit: bool, maxfail: int | None, verbose: bool) -> None:
70
+ """Run the test suite."""
71
+ verb = 2 if verbose else 1
72
+ runner = JacTextTestRunner(max_failures=maxfail, failfast=xit, verbosity=verb)
73
+ result = runner.run(JacTestCheck.test_suite)
74
+ if result.wasSuccessful():
75
+ print("Passed successfully.")
76
+ else:
77
+ fails = len(result.failures)
78
+ JacTestCheck.failcount += fails
79
+ JacTestCheck.breaker = (
80
+ (JacTestCheck.failcount >= maxfail) if maxfail else True
81
+ )
82
+
83
+ @staticmethod
84
+ def add_test(test_fun: Callable) -> None:
85
+ """Create a new test."""
86
+ JacTestCheck.test_suite.addTest(unittest.FunctionTestCase(test_fun))
87
+
88
+ def __getattr__(self, name: str) -> object:
89
+ """Make convenient check.Equal(...) etc."""
90
+ return getattr(JacTestCheck.test_case, name)
jaclang/core/utils.py CHANGED
@@ -8,10 +8,10 @@ from contextlib import contextmanager
8
8
  from typing import Callable, Iterator, TYPE_CHECKING
9
9
 
10
10
  import jaclang.compiler.absyntree as ast
11
- from jaclang.core.registry import SemScope
11
+ from jaclang.compiler.semtable import SemScope
12
12
 
13
13
  if TYPE_CHECKING:
14
- from jaclang.core.construct import NodeAnchor, NodeArchitype
14
+ from jaclang.core.constructs import NodeAnchor, NodeArchitype
15
15
 
16
16
 
17
17
  @contextmanager