jaclang 0.8.1__py3-none-any.whl → 0.8.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 (77) hide show
  1. jaclang/__init__.py +6 -0
  2. jaclang/cli/cli.py +21 -50
  3. jaclang/compiler/codeinfo.py +0 -1
  4. jaclang/compiler/jac.lark +12 -10
  5. jaclang/compiler/larkparse/jac_parser.py +2 -2
  6. jaclang/compiler/parser.py +18 -10
  7. jaclang/compiler/passes/main/__init__.py +0 -14
  8. jaclang/compiler/passes/main/annex_pass.py +2 -8
  9. jaclang/compiler/passes/main/cfg_build_pass.py +38 -12
  10. jaclang/compiler/passes/main/import_pass.py +3 -11
  11. jaclang/compiler/passes/main/pyast_gen_pass.py +243 -592
  12. jaclang/compiler/passes/main/sym_tab_link_pass.py +2 -5
  13. jaclang/compiler/passes/main/tests/test_cfg_build_pass.py +2 -8
  14. jaclang/compiler/passes/main/tests/test_decl_impl_match_pass.py +7 -8
  15. jaclang/compiler/passes/main/tests/test_import_pass.py +5 -18
  16. jaclang/compiler/passes/main/tests/test_pyast_gen_pass.py +2 -6
  17. jaclang/compiler/passes/main/tests/test_sub_node_pass.py +1 -3
  18. jaclang/compiler/passes/main/tests/test_sym_tab_link_pass.py +20 -17
  19. jaclang/compiler/passes/tool/doc_ir_gen_pass.py +237 -105
  20. jaclang/compiler/passes/tool/jac_formatter_pass.py +2 -0
  21. jaclang/compiler/passes/tool/tests/fixtures/archetype_frmt.jac +14 -0
  22. jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/triple_quoted_string.jac +5 -4
  23. jaclang/compiler/passes/tool/tests/fixtures/import_fmt.jac +6 -0
  24. jaclang/compiler/passes/tool/tests/fixtures/simple_walk_fmt.jac +3 -3
  25. jaclang/compiler/passes/tool/tests/fixtures/tagbreak.jac +9 -0
  26. jaclang/compiler/passes/tool/tests/test_jac_format_pass.py +18 -3
  27. jaclang/compiler/passes/tool/tests/test_unparse_validate.py +2 -2
  28. jaclang/compiler/program.py +21 -60
  29. jaclang/compiler/tests/fixtures/pkg_import_lib_py/__init__.py +2 -8
  30. jaclang/compiler/tests/fixtures/pkg_import_lib_py/sub/__init__.py +1 -5
  31. jaclang/compiler/tests/test_importer.py +10 -13
  32. jaclang/compiler/unitree.py +32 -16
  33. jaclang/langserve/__init__.jac +1 -1
  34. jaclang/langserve/engine.jac +113 -108
  35. jaclang/langserve/server.jac +17 -2
  36. jaclang/langserve/tests/server_test/test_lang_serve.py +138 -46
  37. jaclang/langserve/tests/server_test/utils.py +35 -9
  38. jaclang/langserve/tests/test_sem_tokens.py +1 -1
  39. jaclang/langserve/tests/test_server.py +3 -7
  40. jaclang/runtimelib/archetype.py +127 -5
  41. jaclang/runtimelib/importer.py +51 -94
  42. jaclang/runtimelib/machine.py +391 -268
  43. jaclang/runtimelib/meta_importer.py +86 -0
  44. jaclang/runtimelib/tests/fixtures/graph_purger.jac +24 -26
  45. jaclang/runtimelib/tests/fixtures/other_root_access.jac +25 -16
  46. jaclang/runtimelib/tests/test_jaseci.py +3 -1
  47. jaclang/tests/fixtures/arch_rel_import_creation.jac +23 -23
  48. jaclang/tests/fixtures/async_ability.jac +43 -10
  49. jaclang/tests/fixtures/async_function.jac +18 -0
  50. jaclang/tests/fixtures/async_walker.jac +17 -12
  51. jaclang/tests/fixtures/create_dynamic_archetype.jac +25 -28
  52. jaclang/tests/fixtures/deep/deeper/deep_outer_import.jac +7 -4
  53. jaclang/tests/fixtures/deep/deeper/snd_lev.jac +2 -2
  54. jaclang/tests/fixtures/deep/deeper/snd_lev_dup.jac +6 -0
  55. jaclang/tests/fixtures/deep/one_lev.jac +2 -2
  56. jaclang/tests/fixtures/deep/one_lev_dup.jac +4 -3
  57. jaclang/tests/fixtures/dynamic_archetype.jac +19 -12
  58. jaclang/tests/fixtures/foo.jac +14 -22
  59. jaclang/tests/fixtures/jac_from_py.py +1 -1
  60. jaclang/tests/fixtures/jp_importer.jac +6 -6
  61. jaclang/tests/fixtures/jp_importer_auto.jac +5 -3
  62. jaclang/tests/fixtures/unicode_strings.jac +24 -0
  63. jaclang/tests/fixtures/walker_update.jac +5 -7
  64. jaclang/tests/test_language.py +138 -140
  65. jaclang/tests/test_reference.py +9 -4
  66. jaclang/tests/test_typecheck.py +13 -26
  67. jaclang/utils/lang_tools.py +7 -5
  68. jaclang/utils/module_resolver.py +23 -0
  69. {jaclang-0.8.1.dist-info → jaclang-0.8.2.dist-info}/METADATA +1 -1
  70. {jaclang-0.8.1.dist-info → jaclang-0.8.2.dist-info}/RECORD +72 -70
  71. jaclang/compiler/passes/main/tests/fixtures/main_err.jac +0 -6
  72. jaclang/compiler/passes/main/tests/fixtures/second_err.jac +0 -4
  73. jaclang/compiler/passes/tool/tests/fixtures/corelib.jac +0 -644
  74. jaclang/compiler/passes/tool/tests/test_doc_ir_gen_pass.py +0 -29
  75. jaclang/tests/fixtures/deep/deeper/__init__.jac +0 -1
  76. {jaclang-0.8.1.dist-info → jaclang-0.8.2.dist-info}/WHEEL +0 -0
  77. {jaclang-0.8.1.dist-info → jaclang-0.8.2.dist-info}/entry_points.txt +0 -0
@@ -1,4 +1,5 @@
1
1
  """Test for Jac language server[VSCE] features"""
2
+
2
3
  import os
3
4
  import pytest
4
5
 
@@ -16,19 +17,18 @@ from jaclang.langserve.tests.server_test.utils import (
16
17
  create_temp_jac_file,
17
18
  get_code,
18
19
  get_jac_file_path,
20
+ get_simple_code,
19
21
  create_ls_with_workspace, # new helper
20
22
  )
21
23
  from jaclang.vendor.pygls.uris import from_fs_path
22
24
  from jaclang.vendor.pygls.workspace import Workspace
23
25
  from jaclang import JacMachineInterface as _
24
-
25
- JacLangServer = _.py_jac_import(
26
- "....langserve.engine", __file__, items={"JacLangServer": None}
27
- )[0]
28
- (did_open, did_save, did_change, formatting) = _.py_jac_import( "....langserve.server", __file__, items={"did_open": None, "did_save": None, "did_change": None, "formatting": None})
26
+ from jaclang.langserve.engine import JacLangServer
27
+ from jaclang.langserve.server import did_open, did_save, did_change, formatting
29
28
 
30
29
  JAC_FILE = get_jac_file_path()
31
30
 
31
+
32
32
  class TestLangServe:
33
33
  """Test class for Jac language server features."""
34
34
 
@@ -95,7 +95,7 @@ class TestLangServe:
95
95
  temp_file_path = create_temp_jac_file(code)
96
96
  uri = from_fs_path(temp_file_path)
97
97
  ls.lsp._workspace = Workspace(os.path.dirname(temp_file_path), ls)
98
-
98
+
99
99
  params = DidOpenTextDocumentParams(
100
100
  text_document=TextDocumentItem(
101
101
  uri=uri,
@@ -130,7 +130,9 @@ class TestLangServe:
130
130
  ls.shutdown()
131
131
  assert hasattr(sem_tokens, "data")
132
132
  assert isinstance(sem_tokens.data, list)
133
- assert len(sem_tokens.data) == 0 # TODO: we should retain the sem tokens, will be fixed in next PR
133
+ assert (
134
+ len(sem_tokens.data) == 0
135
+ ) # TODO: we should retain the sem tokens, will be fixed in next PR
134
136
 
135
137
  os.remove(temp_file_path)
136
138
 
@@ -141,14 +143,17 @@ class TestLangServe:
141
143
  temp_file_path = create_temp_jac_file(code)
142
144
  uri, ls = create_ls_with_workspace(temp_file_path)
143
145
 
144
- await did_open(ls, DidOpenTextDocumentParams(
145
- text_document=TextDocumentItem(
146
- uri=uri,
147
- language_id="jac",
148
- version=1,
149
- text=code,
150
- )
151
- ))
146
+ await did_open(
147
+ ls,
148
+ DidOpenTextDocumentParams(
149
+ text_document=TextDocumentItem(
150
+ uri=uri,
151
+ language_id="jac",
152
+ version=1,
153
+ text=code,
154
+ )
155
+ ),
156
+ )
152
157
 
153
158
  params = DidSaveTextDocumentParams(
154
159
  text_document=TextDocumentItem(
@@ -166,12 +171,14 @@ class TestLangServe:
166
171
 
167
172
  # Now simulate a syntax error by updating the workspace and saving
168
173
  broken_code = get_code("error")
169
- ls.workspace.put_text_document(TextDocumentItem(
170
- uri=uri,
171
- language_id="jac",
172
- version=3,
173
- text=broken_code,
174
- ))
174
+ ls.workspace.put_text_document(
175
+ TextDocumentItem(
176
+ uri=uri,
177
+ language_id="jac",
178
+ version=3,
179
+ text=broken_code,
180
+ )
181
+ )
175
182
  params = DidSaveTextDocumentParams(
176
183
  text_document=TextDocumentItem(
177
184
  uri=uri,
@@ -181,6 +188,9 @@ class TestLangServe:
181
188
  )
182
189
  )
183
190
  await did_save(ls, params)
191
+ sem_tokens = ls.get_semantic_tokens(uri)
192
+ # semantic tokens should still be present even if there is a syntax error
193
+ assert len(sem_tokens.data) == 300
184
194
  diagnostics = ls.diagnostics.get(uri, [])
185
195
  assert isinstance(diagnostics, list)
186
196
  assert len(diagnostics) == 1
@@ -196,26 +206,31 @@ class TestLangServe:
196
206
  temp_file_path = create_temp_jac_file(code)
197
207
  uri, ls = create_ls_with_workspace(temp_file_path)
198
208
 
199
- await did_open(ls, DidOpenTextDocumentParams(
200
- text_document=TextDocumentItem(
201
- uri=uri,
202
- language_id="jac",
203
- version=1,
204
- text=code,
205
- )
206
- ))
209
+ await did_open(
210
+ ls,
211
+ DidOpenTextDocumentParams(
212
+ text_document=TextDocumentItem(
213
+ uri=uri,
214
+ language_id="jac",
215
+ version=1,
216
+ text=code,
217
+ )
218
+ ),
219
+ )
207
220
 
208
221
  # No error, should be no diagnostics
209
222
  params = DidChangeTextDocumentParams(
210
223
  text_document=VersionedTextDocumentIdentifier(uri=uri, version=2),
211
- content_changes=[{"text": "\n" + code}]
224
+ content_changes=[{"text": "\n" + code}],
225
+ )
226
+ ls.workspace.put_text_document(
227
+ TextDocumentItem(
228
+ uri=uri,
229
+ language_id="jac",
230
+ version=2,
231
+ text="\n" + code,
232
+ )
212
233
  )
213
- ls.workspace.put_text_document(TextDocumentItem(
214
- uri=uri,
215
- language_id="jac",
216
- version=2,
217
- text="\n" + code,
218
- ))
219
234
  await did_change(ls, params)
220
235
  diagnostics = ls.diagnostics.get(uri, [])
221
236
  assert isinstance(diagnostics, list)
@@ -226,15 +241,20 @@ class TestLangServe:
226
241
  error_code = "\nerror"
227
242
  params = DidChangeTextDocumentParams(
228
243
  text_document=VersionedTextDocumentIdentifier(uri=uri, version=3),
229
- content_changes=[{"text": error_code + code}]
244
+ content_changes=[{"text": error_code + code}],
245
+ )
246
+ ls.workspace.put_text_document(
247
+ TextDocumentItem(
248
+ uri=uri,
249
+ language_id="jac",
250
+ version=3,
251
+ text=error_code + code,
252
+ )
230
253
  )
231
- ls.workspace.put_text_document(TextDocumentItem(
232
- uri=uri,
233
- language_id="jac",
234
- version=3,
235
- text=error_code + code,
236
- ))
237
254
  await did_change(ls, params)
255
+ sem_tokens = ls.get_semantic_tokens(uri)
256
+ # semantic tokens should still be present even if there is a syntax error
257
+ assert len(sem_tokens.data) == 300
238
258
  diagnostics = ls.diagnostics.get(uri, [])
239
259
  assert isinstance(diagnostics, list)
240
260
  assert len(diagnostics) == 1
@@ -250,12 +270,84 @@ class TestLangServe:
250
270
  uri, ls = create_ls_with_workspace(temp_file_path)
251
271
  params = DocumentFormattingParams(
252
272
  text_document=TextDocumentIdentifier(uri=uri),
253
- options={"tabSize": 4, "insertSpaces": True}
273
+ options={"tabSize": 4, "insertSpaces": True},
254
274
  )
255
275
  edits = formatting(ls, params)
256
276
  assert isinstance(edits, list)
257
277
  assert isinstance(edits[0], TextEdit)
258
- assert len(edits[0].new_text) > 100 # it is a random number to check if the text is changed
278
+ assert (
279
+ len(edits[0].new_text) > 100
280
+ ) # it is a random number to check if the text is changed
259
281
  print(edits[0].new_text)
260
282
  ls.shutdown()
261
- os.remove(temp_file_path)
283
+ os.remove(temp_file_path)
284
+
285
+ @pytest.mark.asyncio
286
+ async def test_multifile_workspace(self):
287
+ """Test opening multiple Jac files in a workspace."""
288
+ code1 = get_simple_code("")
289
+ code2 = get_simple_code("error")
290
+ temp_file_path1 = create_temp_jac_file(code1)
291
+ temp_file_path2 = create_temp_jac_file(code2)
292
+
293
+ uri1, ls = create_ls_with_workspace(temp_file_path1)
294
+ uri2 = from_fs_path(temp_file_path2)
295
+
296
+ await did_open(
297
+ ls,
298
+ DidOpenTextDocumentParams(
299
+ text_document=TextDocumentItem(
300
+ uri=uri1,
301
+ language_id="jac",
302
+ version=1,
303
+ text=code1,
304
+ )
305
+ ),
306
+ )
307
+ await did_open(
308
+ ls,
309
+ DidOpenTextDocumentParams(
310
+ text_document=TextDocumentItem(
311
+ uri=uri2,
312
+ language_id="jac",
313
+ version=1,
314
+ text=code2,
315
+ )
316
+ ),
317
+ )
318
+
319
+ diagnostics1 = ls.diagnostics.get(uri1, [])
320
+ diagnostics2 = ls.diagnostics.get(uri2, [])
321
+ assert len(diagnostics1) == 0
322
+ assert len(diagnostics2) == 1
323
+ assert diagnostics2[0].message == "Syntax Error"
324
+
325
+ before_sem_tokens_1 = ls.get_semantic_tokens(uri1)
326
+ before_sem_tokens_2 = ls.get_semantic_tokens(uri2)
327
+ assert len(before_sem_tokens_1.data) == 15
328
+ assert len(before_sem_tokens_2.data) == 0
329
+
330
+ changed_code = get_simple_code("glob x = 90;")
331
+ ls.workspace.put_text_document(
332
+ TextDocumentItem(
333
+ uri=uri1,
334
+ language_id="jac",
335
+ version=2,
336
+ text=changed_code,
337
+ )
338
+ )
339
+ params = DidChangeTextDocumentParams(
340
+ text_document=VersionedTextDocumentIdentifier(uri=uri1, version=2),
341
+ content_changes=[{"text": changed_code}],
342
+ )
343
+ await did_change(ls, params)
344
+
345
+ after_sem_tokens_1 = ls.get_semantic_tokens(uri1)
346
+ after_sem_tokens_2 = ls.get_semantic_tokens(uri2)
347
+
348
+ assert len(after_sem_tokens_1.data) == 20
349
+ assert len(after_sem_tokens_2.data) == 0
350
+
351
+ ls.shutdown()
352
+ os.remove(temp_file_path1)
353
+ os.remove(temp_file_path2)
@@ -1,4 +1,5 @@
1
1
  """Unit test utilities for JacLangServer."""
2
+
2
3
  import os
3
4
  import tempfile
4
5
 
@@ -6,27 +7,31 @@ from jaclang.vendor.pygls.uris import from_fs_path
6
7
  from jaclang.vendor.pygls.workspace import Workspace
7
8
 
8
9
  from textwrap import dedent
9
- from jaclang import JacMachineInterface as _
10
- JacLangServer = _.py_jac_import(
11
- "....langserve.engine", __file__, items={"JacLangServer": None}
12
- )[0]
10
+
13
11
 
14
12
  def get_jac_file_path():
15
13
  """Return the absolute path to the sample Jac file used for testing."""
16
14
  return os.path.abspath(
17
- os.path.join(os.path.dirname(__file__), "../../../../examples/manual_code/circle.jac")
15
+ os.path.join(
16
+ os.path.dirname(__file__), "../../../../examples/manual_code/circle.jac"
17
+ )
18
18
  )
19
19
 
20
+
20
21
  def create_temp_jac_file(initial_content: str = "") -> str:
21
22
  """Create a temporary Jac file with optional initial content and return its path."""
22
- temp = tempfile.NamedTemporaryFile(delete=False, suffix=".jac", mode="w", encoding="utf-8")
23
+ temp = tempfile.NamedTemporaryFile(
24
+ delete=False, suffix=".jac", mode="w", encoding="utf-8"
25
+ )
23
26
  temp.write(initial_content)
24
27
  temp.close()
25
28
  return temp.name
26
29
 
30
+
27
31
  def get_code(code: str) -> str:
28
32
  """Generate a sample Jac code snippet with optional test code injected."""
29
- jac_code = dedent(f'''
33
+ jac_code = dedent(
34
+ f'''
30
35
  """
31
36
  This module demonstrates a simple circle class and a function to calculate
32
37
  the area of a circle in all of Jac's glory.
@@ -107,12 +112,33 @@ def get_code(code: str) -> str:
107
112
  c = Circle(RAD);
108
113
  check c.shape_type == ShapeType.CIRCLE;
109
114
  }}
110
- ''')
115
+ '''
116
+ )
111
117
  return jac_code
112
118
 
119
+
120
+ def get_simple_code(code: str) -> str:
121
+ """Generate a sample Jac code snippet with optional test code injected."""
122
+ jac_code = dedent(
123
+ f"""
124
+
125
+ # Unit Tests!
126
+ glob expected_area0 = 78.53981633974483;
127
+ glob expected_area1 = 78.53981633974483;
128
+ {code}
129
+ glob expected_area2 = 78.53981633974483;
130
+
131
+
132
+ """
133
+ )
134
+ return jac_code
135
+
136
+
113
137
  def create_ls_with_workspace(file_path: str):
114
138
  """Create JacLangServer and workspace for a given file path, return (uri, ls)."""
139
+ from jaclang.langserve.engine import JacLangServer
140
+
115
141
  ls = JacLangServer()
116
142
  uri = from_fs_path(file_path)
117
143
  ls.lsp._workspace = Workspace(os.path.dirname(file_path), ls)
118
- return uri, ls
144
+ return uri, ls
@@ -4,10 +4,10 @@ import copy
4
4
  import lsprotocol.types as lspt
5
5
  from jaclang import JacMachineInterface as _
6
6
  from jaclang.utils.test import TestCase
7
+ from jaclang.langserve.sem_manager import SemTokManager
7
8
 
8
9
  from typing import Tuple
9
10
 
10
- SemTokManager = _.py_jac_import("..sem_manager", __file__, items={"SemTokManager": None})[0]
11
11
 
12
12
  class TestUpdateSemTokens(TestCase):
13
13
  """Test update semantic tokens"""
@@ -5,13 +5,9 @@ from jaclang.vendor.pygls.workspace import Workspace
5
5
  import lsprotocol.types as lspt
6
6
  import pytest
7
7
  from jaclang import JacMachineInterface as _
8
+ from jaclang.langserve.engine import JacLangServer
9
+ from .session import LspSession
8
10
 
9
- JacLangServer = _.py_jac_import(
10
- "...langserve.engine", __file__, items={"JacLangServer": None}
11
- )[0]
12
- LspSession = _.py_jac_import(
13
- "session", __file__, items={"LspSession": None}
14
- )[0]
15
11
 
16
12
  class TestJacLangServer(TestCase):
17
13
 
@@ -143,7 +139,7 @@ class TestJacLangServer(TestCase):
143
139
  lsp.deep_check(guess_game_file)
144
140
  self.assertIn(
145
141
  "guess_game4.jac:16:8-16:21",
146
- str(lsp.get_definition(guess_game_file, lspt.Position(14, 45))),
142
+ str(lsp.get_definition(guess_game_file, lspt.Position(15, 45))),
147
143
  )
148
144
 
149
145
  def test_go_to_definition_method_manual_impl(self) -> None:
@@ -2,21 +2,24 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import inspect
6
5
  from dataclasses import asdict, dataclass, field, fields, is_dataclass
7
6
  from enum import IntEnum
8
7
  from functools import cached_property
8
+ from inspect import _empty, signature
9
9
  from logging import getLogger
10
10
  from pickle import dumps
11
11
  from types import UnionType
12
- from typing import Any, Callable, ClassVar, Optional, TypeVar
12
+ from typing import Any, Callable, ClassVar, Optional, TypeAlias, TypeVar
13
13
  from uuid import UUID, uuid4
14
14
 
15
+ from ..compiler.constant import EdgeDir
16
+
15
17
 
16
18
  logger = getLogger(__name__)
17
19
 
18
20
  TARCH = TypeVar("TARCH", bound="Archetype")
19
21
  TANCH = TypeVar("TANCH", bound="Anchor")
22
+ T = TypeVar("T")
20
23
 
21
24
 
22
25
  class AccessLevel(IntEnum):
@@ -66,6 +69,125 @@ class AnchorReport:
66
69
  context: dict[str, Any]
67
70
 
68
71
 
72
+ DataSpatialFilter: TypeAlias = (
73
+ Callable[["Archetype"], bool] | "Archetype" | list["Archetype"] | None
74
+ )
75
+
76
+
77
+ @dataclass(eq=False, repr=False)
78
+ class DataSpatialDestination:
79
+ """Object-Spatial Destination."""
80
+
81
+ direction: EdgeDir
82
+ edge: Callable[["Archetype"], bool] | None = None
83
+ node: Callable[["Archetype"], bool] | None = None
84
+
85
+ def edge_filter(self, arch: Archetype) -> bool:
86
+ """Filter edge."""
87
+ return not self.edge or self.edge(arch)
88
+
89
+ def node_filter(self, arch: Archetype) -> bool:
90
+ """Filter node."""
91
+ return not self.node or self.node(arch)
92
+
93
+
94
+ @dataclass(eq=False, repr=False)
95
+ class DataSpatialPath:
96
+ """Object-Spatial Path."""
97
+
98
+ origin: list[NodeArchetype]
99
+ destinations: list[DataSpatialDestination]
100
+ edge_only: bool
101
+ from_visit: bool
102
+
103
+ def __init__(
104
+ self,
105
+ origin: NodeArchetype | list[NodeArchetype],
106
+ destinations: list[DataSpatialDestination] | None = None,
107
+ ) -> None:
108
+ """Override Init."""
109
+ if not isinstance(origin, list):
110
+ origin = [origin]
111
+ self.origin = origin
112
+ self.destinations = [] if destinations is None else destinations
113
+ self.edge_only = False
114
+ self.from_visit = False
115
+
116
+ def convert(
117
+ self,
118
+ filter: DataSpatialFilter,
119
+ ) -> Callable[["Archetype"], bool] | None:
120
+ """Convert filter."""
121
+ if not filter:
122
+ return None
123
+ if callable(filter):
124
+ return filter
125
+ elif isinstance(filter, list):
126
+ return lambda i: i in filter
127
+ return lambda i: i == filter
128
+
129
+ def append(
130
+ self,
131
+ direction: EdgeDir,
132
+ edge: DataSpatialFilter,
133
+ node: DataSpatialFilter,
134
+ ) -> DataSpatialPath:
135
+ """Append destination."""
136
+ self.destinations.append(
137
+ DataSpatialDestination(direction, self.convert(edge), self.convert(node))
138
+ )
139
+ return self
140
+
141
+ def _out(
142
+ self, edge: DataSpatialFilter = None, node: DataSpatialFilter = None
143
+ ) -> DataSpatialPath:
144
+ """Override greater than function."""
145
+ return self.append(EdgeDir.OUT, edge, node)
146
+
147
+ def _in(
148
+ self, edge: DataSpatialFilter = None, node: DataSpatialFilter = None
149
+ ) -> DataSpatialPath:
150
+ """Override greater than function."""
151
+ return self.append(EdgeDir.IN, edge, node)
152
+
153
+ def _any(
154
+ self, edge: DataSpatialFilter = None, node: DataSpatialFilter = None
155
+ ) -> DataSpatialPath:
156
+ """Override greater than function."""
157
+ return self.append(EdgeDir.ANY, edge, node)
158
+
159
+ def edge(self) -> DataSpatialPath:
160
+ """Set edge only."""
161
+ self.edge_only = True
162
+ return self
163
+
164
+ def visit(self) -> DataSpatialPath:
165
+ """Set from visit."""
166
+ self.from_visit = True
167
+ return self
168
+
169
+ def repr_builder(self, repr: str, dest: DataSpatialDestination, mark: str) -> str:
170
+ """Repr builder."""
171
+ repr += mark
172
+ repr += f' (edge{" filter" if dest.edge else ""}) '
173
+ repr += mark
174
+ repr += f' (node{" filter" if dest.node else ""}) '
175
+ return repr
176
+
177
+ def __repr__(self) -> str:
178
+ """Override repr."""
179
+ repr = "nodes "
180
+ for dest in self.destinations:
181
+ match dest.direction:
182
+ case EdgeDir.IN:
183
+ repr = self.repr_builder(repr, dest, "<<")
184
+ case EdgeDir.OUT:
185
+ repr = self.repr_builder(repr, dest, ">>")
186
+ case _:
187
+ repr = self.repr_builder(repr, dest, "--")
188
+ return repr.strip()
189
+
190
+
69
191
  @dataclass(eq=False, repr=False, kw_only=True)
70
192
  class Anchor:
71
193
  """Object Anchor."""
@@ -329,7 +451,7 @@ class Root(NodeArchetype):
329
451
 
330
452
  @dataclass(eq=False)
331
453
  class DataSpatialFunction:
332
- """Data Spatial Function."""
454
+ """Object-Spatial Function."""
333
455
 
334
456
  name: str
335
457
  func: Callable[[Any, Any], Any]
@@ -337,9 +459,9 @@ class DataSpatialFunction:
337
459
  @cached_property
338
460
  def trigger(self) -> type | UnionType | tuple[type | UnionType, ...] | None:
339
461
  """Get function parameter annotations."""
340
- parameters = inspect.signature(self.func, eval_str=True).parameters
462
+ parameters = signature(self.func, eval_str=True).parameters
341
463
  if len(parameters) >= 2:
342
464
  second_param = list(parameters.values())[1]
343
465
  ty = second_param.annotation
344
- return ty if ty != inspect._empty else None
466
+ return ty if ty != _empty else None
345
467
  return None