jaclang 0.7.0__py3-none-any.whl → 0.7.2__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 (62) hide show
  1. jaclang/compiler/absyntree.py +53 -50
  2. jaclang/compiler/compile.py +21 -0
  3. jaclang/compiler/passes/main/__init__.py +2 -2
  4. jaclang/compiler/passes/main/def_impl_match_pass.py +10 -8
  5. jaclang/compiler/passes/main/def_use_pass.py +14 -7
  6. jaclang/compiler/passes/main/fuse_typeinfo_pass.py +20 -1
  7. jaclang/compiler/passes/main/import_pass.py +60 -11
  8. jaclang/compiler/passes/main/pyast_gen_pass.py +65 -4
  9. jaclang/compiler/passes/main/pyast_load_pass.py +2 -1
  10. jaclang/compiler/passes/main/pyjac_ast_link_pass.py +6 -1
  11. jaclang/compiler/passes/main/pyout_pass.py +3 -1
  12. jaclang/compiler/passes/main/schedules.py +4 -3
  13. jaclang/compiler/passes/main/tests/fixtures/incautoimpl.jac +7 -0
  14. jaclang/compiler/passes/main/tests/test_decl_def_match_pass.py +4 -4
  15. jaclang/compiler/passes/main/tests/test_import_pass.py +21 -0
  16. jaclang/compiler/passes/main/tests/test_type_check_pass.py +1 -1
  17. jaclang/compiler/passes/tool/jac_formatter_pass.py +14 -2
  18. jaclang/compiler/passes/tool/tests/fixtures/doc_string.jac +15 -0
  19. jaclang/compiler/passes/tool/tests/test_jac_format_pass.py +7 -5
  20. jaclang/compiler/passes/tool/tests/test_unparse_validate.py +1 -2
  21. jaclang/compiler/symtable.py +21 -1
  22. jaclang/core/aott.py +107 -11
  23. jaclang/core/construct.py +171 -5
  24. jaclang/core/llms/anthropic.py +31 -2
  25. jaclang/core/llms/base.py +3 -3
  26. jaclang/core/llms/groq.py +4 -1
  27. jaclang/core/llms/huggingface.py +4 -1
  28. jaclang/core/llms/ollama.py +4 -1
  29. jaclang/core/llms/openai.py +6 -2
  30. jaclang/core/llms/togetherai.py +4 -1
  31. jaclang/langserve/engine.py +193 -121
  32. jaclang/langserve/server.py +35 -6
  33. jaclang/langserve/tests/fixtures/circle.jac +73 -0
  34. jaclang/langserve/tests/fixtures/circle_err.jac +73 -0
  35. jaclang/langserve/tests/fixtures/circle_pure.impl.jac +32 -0
  36. jaclang/langserve/tests/fixtures/circle_pure.jac +34 -0
  37. jaclang/langserve/tests/fixtures/circle_pure_err.impl.jac +32 -0
  38. jaclang/langserve/tests/fixtures/circle_pure_err.jac +34 -0
  39. jaclang/langserve/tests/test_server.py +156 -1
  40. jaclang/langserve/utils.py +127 -2
  41. jaclang/plugin/default.py +25 -83
  42. jaclang/plugin/feature.py +10 -12
  43. jaclang/plugin/tests/test_features.py +0 -33
  44. jaclang/settings.py +1 -0
  45. jaclang/tests/fixtures/byllmissue.jac +3 -0
  46. jaclang/tests/fixtures/hash_init_check.jac +17 -0
  47. jaclang/tests/fixtures/math_question.jpg +0 -0
  48. jaclang/tests/fixtures/nosigself.jac +19 -0
  49. jaclang/tests/fixtures/type_info.jac +1 -1
  50. jaclang/tests/fixtures/walker_override.jac +21 -0
  51. jaclang/tests/fixtures/with_llm_vision.jac +25 -0
  52. jaclang/tests/test_cli.py +1 -1
  53. jaclang/tests/test_language.py +61 -11
  54. jaclang/utils/helpers.py +3 -5
  55. jaclang/utils/test.py +1 -1
  56. jaclang/utils/treeprinter.py +19 -2
  57. {jaclang-0.7.0.dist-info → jaclang-0.7.2.dist-info}/METADATA +3 -2
  58. {jaclang-0.7.0.dist-info → jaclang-0.7.2.dist-info}/RECORD +60 -48
  59. jaclang/core/memory.py +0 -48
  60. jaclang/core/shelve_storage.py +0 -55
  61. {jaclang-0.7.0.dist-info → jaclang-0.7.2.dist-info}/WHEEL +0 -0
  62. {jaclang-0.7.0.dist-info → jaclang-0.7.2.dist-info}/entry_points.txt +0 -0
@@ -127,7 +127,6 @@ class PyastBuildPass(Pass[ast.PythonModuleAst]):
127
127
  is_imported=False,
128
128
  kid=valid,
129
129
  )
130
- ret.gen.py_ast = [node]
131
130
  return self.nu(ret)
132
131
 
133
132
  def proc_function_def(
@@ -209,6 +208,8 @@ class PyastBuildPass(Pass[ast.PythonModuleAst]):
209
208
  kid = ([doc] if doc else []) + (
210
209
  [name, sig, valid_body] if sig else [name, valid_body]
211
210
  )
211
+ if not sig:
212
+ raise self.ice("Function signature not found")
212
213
  ret = ast.Ability(
213
214
  name_ref=name,
214
215
  is_async=False,
@@ -1,4 +1,9 @@
1
- """Jac to python ast link pass."""
1
+ """Jac to python ast link pass.
2
+
3
+ This pass is needed so cases where there are multiple Jac nodes relevant to a
4
+ single python node can be linked. For example FuncDef doesn't have a Name node
5
+ however Ability does.
6
+ """
2
7
 
3
8
  import ast as ast3
4
9
 
@@ -35,7 +35,9 @@ class PyOutPass(Pass):
35
35
  f"Unable to find module {node.loc.mod_path} or no code present.", node
36
36
  )
37
37
  return
38
- mods = [node] + self.get_all_sub_nodes(node, ast.Module)
38
+ mods = [node] + [
39
+ i for i in self.get_all_sub_nodes(node, ast.Module) if not i.stub_only
40
+ ]
39
41
  for mod in mods:
40
42
  mod_path, out_path_py, out_path_pyc = self.get_output_targets(mod)
41
43
  if os.path.exists(out_path_pyc) and os.path.getmtime(
@@ -9,7 +9,7 @@ from __future__ import annotations
9
9
  from .sub_node_tab_pass import SubNodeTabPass # noqa: I100
10
10
  from .import_pass import JacImportPass, PyImportPass # noqa: I100
11
11
  from .sym_tab_build_pass import SymTabBuildPass # noqa: I100
12
- from .def_impl_match_pass import DeclDefMatchPass # noqa: I100
12
+ from .def_impl_match_pass import DeclImplMatchPass # noqa: I100
13
13
  from .def_use_pass import DefUsePass # noqa: I100
14
14
  from .pyout_pass import PyOutPass # noqa: I100
15
15
  from .pybc_gen_pass import PyBytecodeGenPass # noqa: I100
@@ -25,7 +25,7 @@ py_code_gen = [
25
25
  JacImportPass,
26
26
  PyImportPass,
27
27
  SymTabBuildPass,
28
- DeclDefMatchPass,
28
+ DeclImplMatchPass,
29
29
  DefUsePass,
30
30
  RegistryPass,
31
31
  PyastGenPass,
@@ -33,5 +33,6 @@ py_code_gen = [
33
33
  PyBytecodeGenPass,
34
34
  ]
35
35
 
36
- py_code_gen_typed = [*py_code_gen, JacTypeCheckPass, FuseTypeInfoPass, AccessCheckPass]
36
+ type_checker_sched = [JacTypeCheckPass, FuseTypeInfoPass, AccessCheckPass]
37
+ py_code_gen_typed = [*py_code_gen, *type_checker_sched]
37
38
  py_compiler = [*py_code_gen, PyOutPass]
@@ -0,0 +1,7 @@
1
+ include:jac autoimpl;
2
+
3
+ with entry {
4
+ foo();
5
+ bar();
6
+ baz();
7
+ }
@@ -1,11 +1,11 @@
1
1
  """Test pass module."""
2
2
 
3
3
  from jaclang.compiler.compile import jac_file_to_pass
4
- from jaclang.compiler.passes.main import DeclDefMatchPass
4
+ from jaclang.compiler.passes.main import DeclImplMatchPass
5
5
  from jaclang.utils.test import TestCase
6
6
 
7
7
 
8
- class DeclDefMatchPassTests(TestCase):
8
+ class DeclImplMatchPassTests(TestCase):
9
9
  """Test pass module."""
10
10
 
11
11
  def setUp(self) -> None:
@@ -14,7 +14,7 @@ class DeclDefMatchPassTests(TestCase):
14
14
 
15
15
  def test_ability_connected_to_decl(self) -> None:
16
16
  """Basic test for pass."""
17
- state = jac_file_to_pass(self.fixture_abs_path("base.jac"), DeclDefMatchPass)
17
+ state = jac_file_to_pass(self.fixture_abs_path("base.jac"), DeclImplMatchPass)
18
18
  self.assertFalse(state.errors_had)
19
19
  self.assertIn("(o)Test.(c)say_hi", state.ir.sym_tab.tab)
20
20
  self.assertIsNotNone(state.ir.sym_tab.tab["(o)Test.(c)say_hi"].decl.body)
@@ -23,7 +23,7 @@ class DeclDefMatchPassTests(TestCase):
23
23
 
24
24
  def test_ability_connected_to_decl_post(self) -> None:
25
25
  """Basic test for pass."""
26
- state = jac_file_to_pass(self.fixture_abs_path("base2.jac"), DeclDefMatchPass)
26
+ state = jac_file_to_pass(self.fixture_abs_path("base2.jac"), DeclImplMatchPass)
27
27
  self.assertFalse(state.errors_had)
28
28
  self.assertIn("(o)Test.(c)say_hi", state.ir.sym_tab.tab)
29
29
  self.assertIsNotNone(state.ir.sym_tab.tab["(o)Test.(c)say_hi"].decl.body)
@@ -28,3 +28,24 @@ class ImportPassPassTests(TestCase):
28
28
  self.assertIn("getme.impl", mod_names)
29
29
  self.assertIn("autoimpl.impl", mod_names)
30
30
  self.assertIn("autoimpl.something.else.impl", mod_names)
31
+
32
+ def test_import_include_auto_impl(self) -> None:
33
+ """Basic test for pass."""
34
+ state = jac_file_to_pass(
35
+ self.fixture_abs_path("incautoimpl.jac"), JacImportPass
36
+ )
37
+ num_modules = len(state.ir.get_all_sub_nodes(ast.Module))
38
+ mod_names = [i.name for i in state.ir.get_all_sub_nodes(ast.Module)]
39
+ self.assertEqual(num_modules, 4)
40
+ self.assertIn("getme.impl", mod_names)
41
+ self.assertIn("autoimpl", mod_names)
42
+ self.assertIn("autoimpl.impl", mod_names)
43
+ self.assertIn("autoimpl.something.else.impl", mod_names)
44
+
45
+ def test_annexalbe_by_discovery(self) -> None:
46
+ """Basic test for pass."""
47
+ state = jac_file_to_pass(
48
+ self.fixture_abs_path("incautoimpl.jac"), JacImportPass
49
+ )
50
+ for i in state.ir.get_all_sub_nodes(ast.Module):
51
+ self.assertEqual(i.annexable_by, self.fixture_abs_path("autoimpl.jac"))
@@ -59,6 +59,6 @@ class MypyTypeCheckPassTests(TestCase):
59
59
  self.assertIn("HasVar - species - Type: builtins.str", out)
60
60
  self.assertIn("myDog - Type: type_info.Dog", out)
61
61
  self.assertIn("Body - Type: type_info.Dog.Body", out)
62
- self.assertEqual(out.count("Type: builtins.str"), 27)
62
+ self.assertEqual(out.count("Type: builtins.str"), 28)
63
63
  for i in lis:
64
64
  self.assertNotIn(i, out)
@@ -421,7 +421,10 @@ class JacFormatPass(Pass):
421
421
  self.emit_ln(node, "")
422
422
  self.emit_ln(node, i.gen.jac)
423
423
  if isinstance(i, ast.Token) and i.name == Tok.KW_BY:
424
- self.emit(node, f"{i.gen.jac} ")
424
+ if not node.params:
425
+ self.emit(node, f"{i.gen.jac} ")
426
+ else:
427
+ self.emit(node, f" {i.gen.jac} ")
425
428
  else:
426
429
  if (
427
430
  line_break_needed
@@ -2402,7 +2405,16 @@ class JacFormatPass(Pass):
2402
2405
  and isinstance(node.parent.parent, ast.FString)
2403
2406
  ):
2404
2407
  self.emit(node, node.value)
2405
- self.emit(node, node.value)
2408
+ if "\n" in node.value:
2409
+ string_type = node.value[0:3]
2410
+ pure_string = node.value[3:-3]
2411
+ lines = pure_string.split("\n")
2412
+ self.emit(node, string_type)
2413
+ for line in lines[:-1]:
2414
+ self.emit_ln(node, line)
2415
+ self.emit_ln(node, f"{lines[-1]}{string_type}")
2416
+ else:
2417
+ self.emit(node, node.value)
2406
2418
 
2407
2419
  def enter_bool(self, node: ast.Bool) -> None:
2408
2420
  """Sub objects.
@@ -0,0 +1,15 @@
1
+ class foo {
2
+ """
3
+ doc string
4
+ """
5
+ can print_here() {
6
+ print("i'm here");
7
+ }
8
+
9
+ """this is a
10
+ multiline docstring
11
+ to test"""
12
+ can also_print() {
13
+ print("I'm in also");
14
+ }
15
+ }
@@ -44,8 +44,8 @@ class JacFormatPassTests(TestCaseMicroSuite, AstSyncTestMixin):
44
44
 
45
45
  if diff:
46
46
  print(f"Differences found in comparison:\n{diff}")
47
- # raise AssertionError("Files differ after formatting.")
48
- self.skipTest("Test failed, but skipping instead of failing.")
47
+ raise AssertionError("Files differ after formatting.")
48
+
49
49
  except FileNotFoundError:
50
50
  print(f"File not found: {original_file} or {formatted_file}")
51
51
  raise
@@ -76,6 +76,9 @@ class JacFormatPassTests(TestCaseMicroSuite, AstSyncTestMixin):
76
76
  self.compare_files(
77
77
  os.path.join(self.fixture_abs_path(""), "corelib_fmt.jac"),
78
78
  )
79
+ self.compare_files(
80
+ os.path.join(self.fixture_abs_path(""), "doc_string.jac"),
81
+ )
79
82
 
80
83
  def test_compare_myca_fixtures(self) -> None:
81
84
  """Tests if files in the myca fixtures directory do not change after being formatted."""
@@ -135,14 +138,13 @@ class JacFormatPassTests(TestCaseMicroSuite, AstSyncTestMixin):
135
138
  diff = "\n".join(unified_diff(before.splitlines(), after.splitlines()))
136
139
  self.assertFalse(diff, "AST structures differ after formatting.")
137
140
 
138
- except Exception:
141
+ except Exception as e:
139
142
  print(add_line_numbers(code_gen_pure.ir.source.code))
140
143
  print("\n+++++++++++++++++++++++++++++++++++++++\n")
141
144
  print(add_line_numbers(code_gen_format.ir.gen.jac))
142
145
  print("\n+++++++++++++++++++++++++++++++++++++++\n")
143
146
  print("\n".join(unified_diff(before.splitlines(), after.splitlines())))
144
- self.skipTest("Test failed, but skipping instead of failing.")
145
- # raise e
147
+ raise e
146
148
 
147
149
 
148
150
  JacFormatPassTests.self_attach_micro_tests()
@@ -30,7 +30,7 @@ class JacUnparseTests(TestCaseMicroSuite, AstSyncTestMixin):
30
30
  self.assertEqual(x, y)
31
31
  except Exception as e:
32
32
  print("\n".join(unified_diff(x.splitlines(), y.splitlines())))
33
- self.skipTest(f"Test failed, but skipping instead of failing: {e}")
33
+ raise e
34
34
 
35
35
  def micro_suite_test(self, filename: str) -> None:
36
36
  """Parse micro jac file."""
@@ -75,7 +75,6 @@ class JacUnparseTests(TestCaseMicroSuite, AstSyncTestMixin):
75
75
 
76
76
  except Exception as e:
77
77
  raise e
78
- # self.skipTest(f"Test failed, but skipping instead of failing: {e}")
79
78
 
80
79
 
81
80
  JacUnparseTests.self_attach_micro_tests()
@@ -17,7 +17,7 @@ class SymbolType(Enum):
17
17
 
18
18
  MODULE = "module" # LSP: Module
19
19
  MOD_VAR = "mod_var" # LSP: Variable
20
- VAR = "var" # LSP: Variable
20
+ VAR = "variable" # LSP: Variable
21
21
  IMM_VAR = "immutable" # LSP: Constant
22
22
  ABILITY = "ability" # LSP: Function
23
23
  OBJECT_ARCH = "object" # LSP: Class
@@ -54,6 +54,12 @@ class SymbolInfo:
54
54
  self.acc_tag: Optional[SymbolAccess] = acc_tag
55
55
  self.typ_sym_table: Optional[SymbolTable] = None
56
56
 
57
+ @property
58
+ def clean_type(self) -> str:
59
+ """Get clean type."""
60
+ ret_type = self.typ.replace("builtins.", "").replace("NoType", "")
61
+ return ret_type
62
+
57
63
 
58
64
  class SymbolAccess(Enum):
59
65
  """Symbol types."""
@@ -101,6 +107,20 @@ class Symbol:
101
107
  """Get sym_type."""
102
108
  return self.decl.sym_type
103
109
 
110
+ @property
111
+ def sym_path_str(self) -> str:
112
+ """Return a full path of the symbol."""
113
+ out = [self.defn[0].sym_name]
114
+ current_tab = self.parent_tab
115
+ while current_tab is not None:
116
+ out.append(current_tab.name)
117
+ if current_tab.has_parent():
118
+ current_tab = current_tab.parent
119
+ else:
120
+ break
121
+ out.reverse()
122
+ return ".".join(out)
123
+
104
124
  def add_defn(self, node: ast.AstSymbolNode) -> None:
105
125
  """Add defn."""
106
126
  self.defn.append(node)
jaclang/core/aott.py CHANGED
@@ -4,18 +4,30 @@ AOTT: Automated Operational Type Transformation.
4
4
  This has all the necessary functions to perform the AOTT operations.
5
5
  """
6
6
 
7
+ import base64
8
+ import logging
7
9
  import re
8
10
  from enum import Enum
11
+ from io import BytesIO
9
12
  from typing import Any
10
13
 
14
+
15
+ try:
16
+ from PIL import Image
17
+ except ImportError:
18
+ Image = None
19
+
11
20
  from jaclang.core.llms.base import BaseLLM
12
21
  from jaclang.core.registry import SemInfo, SemRegistry, SemScope
13
22
 
14
23
 
24
+ IMG_FORMATS = ["PngImageFile", "JpegImageFile"]
25
+
26
+
15
27
  def aott_raise(
16
28
  model: BaseLLM,
17
29
  information: str,
18
- inputs_information: str,
30
+ inputs_information: str | list[dict],
19
31
  output_information: str,
20
32
  type_explanations: str,
21
33
  action: str,
@@ -25,18 +37,43 @@ def aott_raise(
25
37
  model_params: dict,
26
38
  ) -> str:
27
39
  """AOTT Raise uses the information (Meanings types values) provided to generate a prompt(meaning in)."""
40
+ system_prompt = model.MTLLM_SYSTEM_PROMPT
41
+ meaning_in: str | list[dict]
28
42
  if method != "ReAct":
29
- system_prompt = model.MTLLM_SYSTEM_PROMPT
30
- mtllm_prompt = model.MTLLM_PROMPT.format(
31
- information=information,
32
- inputs_information=inputs_information,
33
- output_information=output_information,
34
- type_explanations=type_explanations,
35
- action=action,
36
- context=context,
37
- )
38
43
  method_prompt = model.MTLLM_METHOD_PROMPTS[method]
39
- meaning_in = f"{system_prompt}\n{mtllm_prompt}\n{method_prompt}"
44
+ if isinstance(inputs_information, str):
45
+ mtllm_prompt = model.MTLLM_PROMPT.format(
46
+ information=information,
47
+ inputs_information=inputs_information,
48
+ output_information=output_information,
49
+ type_explanations=type_explanations,
50
+ action=action,
51
+ context=context,
52
+ ).strip()
53
+ meaning_in = f"{system_prompt}\n{mtllm_prompt}\n{method_prompt}".strip()
54
+ else:
55
+ upper_half = model.MTLLM_PROMPT.split("{inputs_information}")[0]
56
+ lower_half = model.MTLLM_PROMPT.split("{inputs_information}")[1]
57
+ upper_half = upper_half.format(
58
+ information=information,
59
+ context=context,
60
+ )
61
+ lower_half = lower_half.format(
62
+ output_information=output_information,
63
+ type_explanations=type_explanations,
64
+ action=action,
65
+ )
66
+ meaning_in = (
67
+ [
68
+ {"type": "text", "text": system_prompt},
69
+ {"type": "text", "text": upper_half},
70
+ ]
71
+ + inputs_information
72
+ + [
73
+ {"type": "text", "text": lower_half},
74
+ {"type": "text", "text": method_prompt},
75
+ ]
76
+ )
40
77
  return model(meaning_in, **model_params)
41
78
  else:
42
79
  assert tools, "Tools must be provided for the ReAct method."
@@ -212,3 +249,62 @@ class Tool:
212
249
  """Initialize the Tool class."""
213
250
  # TODO: Implement the Tool class
214
251
  pass
252
+
253
+
254
+ def get_input_information(
255
+ inputs: list[tuple[str, str, str, Any]], type_collector: list
256
+ ) -> str | list[dict]:
257
+ """
258
+ Get the input information for the AOTT operation.
259
+
260
+ Returns:
261
+ str | list[dict]: If the input does not contain images, returns a string with the input information.
262
+ If the input contains images, returns a list of dictionaries representing the input information,
263
+ where each dictionary contains either text or image_url.
264
+
265
+ """
266
+ contains_imgs = any(get_type_annotation(i[3]) in IMG_FORMATS for i in inputs)
267
+ if not contains_imgs:
268
+ inputs_information_list = []
269
+ for i in inputs:
270
+ typ_anno = get_type_annotation(i[3])
271
+ type_collector.extend(extract_non_primary_type(typ_anno))
272
+ inputs_information_list.append(
273
+ f"{i[0]} ({i[2]}) ({typ_anno}) = {get_object_string(i[3])}"
274
+ )
275
+ return "\n".join(inputs_information_list)
276
+ else:
277
+ inputs_information_dict_list: list[dict] = []
278
+ for i in inputs:
279
+ if get_type_annotation(i[3]) in IMG_FORMATS:
280
+ img_base64 = image_to_base64(i[3])
281
+ image_repr: list[dict] = [
282
+ {
283
+ "type": "text",
284
+ "text": f"{i[0]} ({i[2]}) (Image) = ",
285
+ },
286
+ {"type": "image_url", "image_url": {"url": img_base64}},
287
+ ]
288
+ inputs_information_dict_list.extend(image_repr)
289
+ continue
290
+ typ_anno = get_type_annotation(i[3])
291
+ type_collector.extend(extract_non_primary_type(typ_anno))
292
+ inputs_information_dict_list.append(
293
+ {
294
+ "type": "text",
295
+ "text": f"{i[0]} ({i[2]}) ({typ_anno}) = {get_object_string(i[3])}",
296
+ }
297
+ )
298
+ return inputs_information_dict_list
299
+
300
+
301
+ def image_to_base64(image: Image) -> str:
302
+ """Convert an image to base64 expected by OpenAI."""
303
+ if not Image:
304
+ log = logging.getLogger(__name__)
305
+ log.error("Pillow is not installed. Please install Pillow to use images.")
306
+ return ""
307
+ img_format = image.format
308
+ with BytesIO() as buffer:
309
+ image.save(buffer, format=img_format, quality=100)
310
+ return f"data:image/{img_format.lower()};base64,{base64.b64encode(buffer.getvalue()).decode()}"
jaclang/core/construct.py CHANGED
@@ -2,14 +2,15 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import shelve
5
6
  import unittest
7
+ from contextvars import ContextVar
6
8
  from dataclasses import dataclass, field
7
9
  from typing import Callable, Optional
8
10
  from uuid import UUID, uuid4
9
11
 
10
12
  from jaclang.compiler.constant import EdgeDir
11
13
  from jaclang.core.utils import collect_node_connections
12
- from jaclang.plugin.feature import JacFeature as Jac
13
14
  from jaclang.plugin.spec import DSFunc
14
15
 
15
16
 
@@ -57,6 +58,8 @@ class NodeAnchor(ObjectAnchor):
57
58
 
58
59
  def populate_edges(self) -> None:
59
60
  """Populate edges from edge ids."""
61
+ from jaclang.plugin.feature import JacFeature as Jac
62
+
60
63
  if len(self.edges) == 0 and len(self.edge_ids) > 0:
61
64
  for e_id in self.edge_ids:
62
65
  edge = Jac.context().get_obj(e_id)
@@ -354,11 +357,15 @@ class NodeArchitype(Architype):
354
357
 
355
358
  def __init__(self) -> None:
356
359
  """Create node architype."""
360
+ from jaclang.plugin.feature import JacFeature as Jac
361
+
357
362
  self._jac_: NodeAnchor = NodeAnchor(obj=self)
358
363
  Jac.context().save_obj(self, persistent=self._jac_.persistent)
359
364
 
360
365
  def save(self) -> None:
361
366
  """Save the node to the memory/storage hierarchy."""
367
+ from jaclang.plugin.feature import JacFeature as Jac
368
+
362
369
  self._jac_.persistent = True
363
370
  Jac.context().save_obj(self, persistent=True)
364
371
 
@@ -383,11 +390,15 @@ class EdgeArchitype(Architype):
383
390
 
384
391
  def __init__(self) -> None:
385
392
  """Create edge architype."""
393
+ from jaclang.plugin.feature import JacFeature as Jac
394
+
386
395
  self._jac_: EdgeAnchor = EdgeAnchor(obj=self)
387
396
  Jac.context().save_obj(self, persistent=self.persistent)
388
397
 
389
398
  def save(self) -> None:
390
399
  """Save the edge to the memory/storage hierarchy."""
400
+ from jaclang.plugin.feature import JacFeature as Jac
401
+
391
402
  self.persistent = True
392
403
  Jac.context().save_obj(self, persistent=True)
393
404
 
@@ -405,6 +416,8 @@ class EdgeArchitype(Architype):
405
416
 
406
417
  def populate_nodes(self) -> None:
407
418
  """Populate nodes for the edges from node ids."""
419
+ from jaclang.plugin.feature import JacFeature as Jac
420
+
408
421
  if self._jac_.source_id:
409
422
  obj = Jac.context().get_obj(self._jac_.source_id)
410
423
  if obj is None:
@@ -439,6 +452,13 @@ class WalkerArchitype(Architype):
439
452
  self._jac_: WalkerAnchor = WalkerAnchor(obj=self)
440
453
 
441
454
 
455
+ class GenericEdge(EdgeArchitype):
456
+ """Generic Root Node."""
457
+
458
+ _jac_entry_funcs_ = []
459
+ _jac_exit_funcs_ = []
460
+
461
+
442
462
  class Root(NodeArchitype):
443
463
  """Generic Root Node."""
444
464
 
@@ -460,11 +480,157 @@ class Root(NodeArchitype):
460
480
  self._jac_.edges = []
461
481
 
462
482
 
463
- class GenericEdge(EdgeArchitype):
464
- """Generic Root Node."""
483
+ class Memory:
484
+ """Memory module interface."""
465
485
 
466
- _jac_entry_funcs_ = []
467
- _jac_exit_funcs_ = []
486
+ mem: dict[UUID, Architype] = {}
487
+ save_obj_list: dict[UUID, Architype] = {}
488
+
489
+ def __init__(self) -> None:
490
+ """init."""
491
+ pass
492
+
493
+ def get_obj(self, obj_id: UUID) -> Architype | None:
494
+ """Get object from memory."""
495
+ return self.get_obj_from_store(obj_id)
496
+
497
+ def get_obj_from_store(self, obj_id: UUID) -> Architype | None:
498
+ """Get object from the underlying store."""
499
+ ret = self.mem.get(obj_id)
500
+ return ret
501
+
502
+ def has_obj(self, obj_id: UUID) -> bool:
503
+ """Check if the object exists."""
504
+ return self.has_obj_in_store(obj_id)
505
+
506
+ def has_obj_in_store(self, obj_id: UUID) -> bool:
507
+ """Check if the object exists in the underlying store."""
508
+ return obj_id in self.mem
509
+
510
+ def save_obj(self, item: Architype, persistent: bool) -> None:
511
+ """Save object."""
512
+ self.mem[item._jac_.id] = item
513
+ if persistent:
514
+ # TODO: check if it needs to be saved, i.e. dirty or not
515
+ self.save_obj_list[item._jac_.id] = item
516
+
517
+ def commit(self) -> None:
518
+ """Commit changes to persistent storage, if applicable."""
519
+ pass
520
+
521
+ def close(self) -> None:
522
+ """Close any connection, if applicable."""
523
+ self.mem.clear()
524
+
525
+
526
+ class ShelveStorage(Memory):
527
+ """Shelve storage for jaclang runtime object."""
528
+
529
+ storage: shelve.Shelf | None = None
530
+
531
+ def __init__(self, session: str = "") -> None:
532
+ """Init shelve storage."""
533
+ super().__init__()
534
+ if session:
535
+ self.connect(session)
536
+
537
+ def get_obj_from_store(self, obj_id: UUID) -> Architype | None:
538
+ """Get object from the underlying store."""
539
+ obj = super().get_obj_from_store(obj_id)
540
+ if obj is None and self.storage:
541
+ obj = self.storage.get(str(obj_id))
542
+ if obj is not None:
543
+ self.mem[obj_id] = obj
544
+
545
+ return obj
546
+
547
+ def has_obj_in_store(self, obj_id: UUID | str) -> bool:
548
+ """Check if the object exists in the underlying store."""
549
+ return obj_id in self.mem or (
550
+ str(obj_id) in self.storage if self.storage else False
551
+ )
552
+
553
+ def commit(self) -> None:
554
+ """Commit changes to persistent storage."""
555
+ if self.storage is not None:
556
+ for obj_id, obj in self.save_obj_list.items():
557
+ self.storage[str(obj_id)] = obj
558
+ self.save_obj_list.clear()
559
+
560
+ def connect(self, session: str) -> None:
561
+ """Connect to storage."""
562
+ self.session = session
563
+ self.storage = shelve.open(session)
564
+
565
+ def close(self) -> None:
566
+ """Close the storage."""
567
+ super().close()
568
+ self.commit()
569
+ if self.storage:
570
+ self.storage.close()
571
+ self.storage = None
572
+
573
+
574
+ class ExecutionContext:
575
+ """Default Execution Context implementation."""
576
+
577
+ mem: Optional[Memory]
578
+ root: Optional[Root]
579
+
580
+ def __init__(self) -> None:
581
+ """Create execution context."""
582
+ super().__init__()
583
+ self.mem = ShelveStorage()
584
+ self.root = None
585
+
586
+ def init_memory(self, session: str = "") -> None:
587
+ """Initialize memory."""
588
+ if session:
589
+ self.mem = ShelveStorage(session)
590
+ else:
591
+ self.mem = Memory()
592
+
593
+ def get_root(self) -> Root:
594
+ """Get the root object."""
595
+ if self.mem is None:
596
+ raise ValueError("Memory not initialized")
597
+
598
+ if not self.root:
599
+ root = self.mem.get_obj(UUID(int=0))
600
+ if root is None:
601
+ self.root = Root()
602
+ self.mem.save_obj(self.root, persistent=self.root._jac_.persistent)
603
+ elif not isinstance(root, Root):
604
+ raise ValueError(f"Invalid root object: {root}")
605
+ else:
606
+ self.root = root
607
+ return self.root
608
+
609
+ def get_obj(self, obj_id: UUID) -> Architype | None:
610
+ """Get object from memory."""
611
+ if self.mem is None:
612
+ raise ValueError("Memory not initialized")
613
+
614
+ return self.mem.get_obj(obj_id)
615
+
616
+ def save_obj(self, item: Architype, persistent: bool) -> None:
617
+ """Save object to memory."""
618
+ if self.mem is None:
619
+ raise ValueError("Memory not initialized")
620
+
621
+ self.mem.save_obj(item, persistent)
622
+
623
+ def reset(self) -> None:
624
+ """Reset the execution context."""
625
+ if self.mem:
626
+ self.mem.close()
627
+ self.mem = None
628
+ self.root = None
629
+
630
+
631
+ exec_context: ContextVar[ExecutionContext | None] = ContextVar(
632
+ "ExecutionContext", default=None
633
+ )
468
634
 
469
635
 
470
636
  class JacTestResult(unittest.TextTestResult):