pyVHDLModelTreesitter 0.1.0__tar.gz

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.
@@ -0,0 +1,5 @@
1
+ /.coverage*
2
+ /.junit.xml
3
+ /.venv
4
+ /dist/
5
+ __pycache__/
@@ -0,0 +1,13 @@
1
+ Copyright 2025 Dominic Adam Walters
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
@@ -0,0 +1,34 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyVHDLModelTreesitter
3
+ Version: 0.1.0
4
+ Summary: An implementation of pyVHDLModel using treesitter
5
+ Project-URL: Repository, https://gitlab.com/dawalters/pyVHDLModelTreesitter
6
+ Author: Dominic Adam Walters
7
+ License-File: LICENSE
8
+ Keywords: language,model,treesitter,vhdl
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3 :: Only
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Requires-Python: >=3.12
15
+ Requires-Dist: pyvhdlmodel==0.33.1
16
+ Requires-Dist: tree-sitter-vhdl==1.5.0
17
+ Requires-Dist: tree-sitter==0.25.2
18
+ Provides-Extra: dev
19
+ Requires-Dist: coverage==7.13.5; extra == 'dev'
20
+ Requires-Dist: hatch==1.16.5; extra == 'dev'
21
+ Requires-Dist: mypy==1.20.1; extra == 'dev'
22
+ Requires-Dist: pre-commit==4.5.1; extra == 'dev'
23
+ Requires-Dist: pytest-cov==7.1.0; extra == 'dev'
24
+ Requires-Dist: pytest-sugar==1.1.1; extra == 'dev'
25
+ Requires-Dist: pytest-xdist==3.8.0; extra == 'dev'
26
+ Requires-Dist: pytest==9.0.3; extra == 'dev'
27
+ Requires-Dist: ruff==0.15.10; extra == 'dev'
28
+ Requires-Dist: sphinx==9.1.0; extra == 'dev'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # pyVHDLModelTreesitter
32
+
33
+ `pyVHDLModelTreesitter` is an implementation of the `pyVHDLModel` API using
34
+ `tree-sitter-vhdl`.
@@ -0,0 +1,4 @@
1
+ # pyVHDLModelTreesitter
2
+
3
+ `pyVHDLModelTreesitter` is an implementation of the `pyVHDLModel` API using
4
+ `tree-sitter-vhdl`.
@@ -0,0 +1,119 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "pyVHDLModelTreesitter"
7
+ version = "0.1.0"
8
+ description = "An implementation of pyVHDLModel using treesitter"
9
+ readme = "README.md"
10
+ requires-python = ">=3.12"
11
+ dependencies = [
12
+ "pyVHDLModel == 0.33.1",
13
+ "tree-sitter == 0.25.2",
14
+ "tree-sitter-vhdl == 1.5.0", # TODO: try and update this?
15
+ ]
16
+ authors = [
17
+ { name = "Dominic Adam Walters" },
18
+ ]
19
+ keywords = ["language", "model", "treesitter", "vhdl"]
20
+ classifiers = [
21
+ "Development Status :: 3 - Alpha",
22
+ "License :: OSI Approved :: MIT License",
23
+ "Programming Language :: Python :: 3 :: Only",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Intended Audience :: Developers",
26
+ ]
27
+
28
+ [project.optional-dependencies]
29
+ dev = [
30
+ "coverage == 7.13.5",
31
+ "hatch == 1.16.5",
32
+ "mypy == 1.20.1",
33
+ "pre-commit == 4.5.1",
34
+ "pytest == 9.0.3",
35
+ "pytest-cov == 7.1.0",
36
+ "pytest-sugar == 1.1.1",
37
+ "pytest-xdist == 3.8.0",
38
+ "ruff == 0.15.10",
39
+ "sphinx == 9.1.0",
40
+ ]
41
+
42
+ [project.urls]
43
+ Repository = "https://gitlab.com/dawalters/pyVHDLModelTreesitter"
44
+
45
+ [tool.codespell]
46
+ ignore-words-list = "inout"
47
+
48
+ [tool.coverage.html]
49
+ directory = ".htmlcov"
50
+
51
+ [tool.coverage.report]
52
+ exclude_also = [
53
+ "@overload",
54
+ "if TYPE_CHECKING:",
55
+ ]
56
+
57
+ [tool.coverage.run]
58
+ branch = true
59
+
60
+ [tool.coverage.xml]
61
+ output = ".coverage.xml"
62
+
63
+ [tool.hatch.build]
64
+ include = [
65
+ "src/pyVHDLModelTreesitter/*.py",
66
+ "src/pyVHDLModelTreesitter/py.typed",
67
+ ]
68
+
69
+ [tool.hatch.build.targets.wheel]
70
+ packages = ["src/pyVHDLModelTreesitter"]
71
+
72
+ [tool.hatch.envs.hatch-test]
73
+ dependencies = [
74
+ "coverage == 7.13.5",
75
+ "pytest == 9.0.3",
76
+ "pytest-cov == 7.1.0",
77
+ "pytest-sugar == 1.1.1",
78
+ "pytest-xdist == 3.8.0",
79
+ ]
80
+
81
+ [[tool.hatch.envs.hatch-test.matrix]]
82
+ python = ["3.12"]
83
+
84
+ [tool.mypy]
85
+ strict = true
86
+ warn_unreachable = true
87
+ implicit_reexport = true
88
+
89
+ [tool.pytest.ini_options]
90
+ addopts = "--capture=fd -n 0 --cov=pyVHDLModelTreesitter --cov-report html --cov-report xml --verbose --junitxml=.junit.xml -o junit_family=legacy"
91
+ testpaths = [
92
+ "tests",
93
+ ]
94
+
95
+ [tool.ruff]
96
+ unsafe-fixes = true
97
+
98
+ [tool.ruff.lint]
99
+ fixable = ["ALL"]
100
+ ignore = [
101
+ "CPY001", # I don't want copyright notices in every file
102
+ "D203", # I don't want blank lines before class docstrings
103
+ "D212", # I want docstring opening lines to be after the """
104
+ "DOC201", # I don't want to mandate documenting return type with typehints
105
+ "FIX002", # I want to be able to type TODO
106
+ "N999", # I want to use the same structure as pyVHDLModel
107
+ "TD002", # I don't want TODOs to need authors
108
+ "TD003", # I don't want TODOs to need issue IDs
109
+ ]
110
+ preview = true
111
+ select = [
112
+ "ALL",
113
+ ]
114
+ unfixable = [
115
+ "T201", # Don't fix print statements
116
+ ]
117
+
118
+ [tool.ruff.lint.flake8-self]
119
+ ignore-names = ["_AddEntity"]
@@ -0,0 +1,29 @@
1
+ """Submodule mirroring modified aspects of pyVHDLModel.Base."""
2
+
3
+ __all__ = [
4
+ "parse_as_mode",
5
+ ]
6
+
7
+ import pyVHDLModel
8
+
9
+
10
+ def parse_as_mode(string: str) -> pyVHDLModel.Base.Mode | None:
11
+ """
12
+ Try and convert incorrect inputs.
13
+
14
+ This will accept any mix of the following:
15
+ - Incorrect capitalisation
16
+ - "IN"
17
+ - "iN"
18
+ - "in"
19
+ - Split words with whitespace
20
+ - "in out"
21
+ """
22
+ # TODO: make a PR on pyVHDLModel to add this
23
+ string = "".join(string.split()).lower().capitalize()
24
+ if string == "Inout":
25
+ string = "InOut"
26
+ try:
27
+ return pyVHDLModel.Base.Mode[string]
28
+ except IndexError:
29
+ return None
@@ -0,0 +1,278 @@
1
+ """Submodule mirroring modified aspects of pyVHDLModel.DesignUnit."""
2
+
3
+ __all__ = [
4
+ "Entity",
5
+ "LibraryClause",
6
+ "UseClause",
7
+ ]
8
+
9
+ from collections.abc import Iterable
10
+ from functools import cmp_to_key
11
+ from typing import Self, cast
12
+
13
+ import pyVHDLModel
14
+ import tree_sitter
15
+ from pyVHDLModel.DesignUnit import LibraryClause as BaseLibraryClause
16
+ from pyVHDLModel.DesignUnit import UseClause as BaseUseClause
17
+
18
+ from pyVHDLModelTreesitter._error import ExtraNodeError, NodeTextIsNoneError
19
+ from pyVHDLModelTreesitter.Interface import (
20
+ GenericConstantInterfaceItem,
21
+ PortSignalInterfaceItem,
22
+ )
23
+ from pyVHDLModelTreesitter.Name import SelectedName, SimpleName
24
+ from pyVHDLModelTreesitter.Symbol import LibraryReferenceSymbol, PackageReferenceSymbol
25
+ from pyVHDLModelTreesitter.treesitter import get_node_or_raise, get_nodes_or_raise
26
+ from pyVHDLModelTreesitter.treesitter.query import (
27
+ EntityDeclaration,
28
+ InterfaceDeclarations,
29
+ LibraryClauses,
30
+ LibraryNamespaces,
31
+ SelectedNames,
32
+ UseClauses,
33
+ )
34
+
35
+
36
+ class LibraryClause(BaseLibraryClause): # noqa: D101
37
+ @classmethod
38
+ def from_treesitter_node(
39
+ cls,
40
+ node: tree_sitter.Node,
41
+ ) -> list[Self]:
42
+ """
43
+ Create one or more LibraryClauses from a treesitter node.
44
+
45
+ Raises
46
+ ------
47
+ NodeTextIsNoneError:
48
+ If the `library_namespace` treesitter node contains no text.
49
+
50
+ """
51
+ captures = LibraryClauses.capture(node)
52
+ library_clause_nodes = get_nodes_or_raise(
53
+ node,
54
+ captures,
55
+ "library-clause",
56
+ LibraryClauses.TEXT,
57
+ )
58
+ ret = []
59
+ for library_clause_node in library_clause_nodes:
60
+ captures = LibraryNamespaces.capture(library_clause_node)
61
+ library_namespace_nodes = get_nodes_or_raise(
62
+ node,
63
+ captures,
64
+ "library",
65
+ LibraryNamespaces.TEXT,
66
+ )
67
+ symbols = []
68
+ for library_namespace_node in library_namespace_nodes:
69
+ if library_namespace_node.text is None:
70
+ raise NodeTextIsNoneError(node, LibraryNamespaces.TEXT, "library")
71
+ library = library_namespace_node.text.decode()
72
+ symbols.append(LibraryReferenceSymbol(SimpleName(library)))
73
+ ret.append(cls(symbols))
74
+ return ret
75
+
76
+ def __eq__(self, other: object) -> bool: # noqa: D105
77
+ if not isinstance(other, LibraryClause):
78
+ return False
79
+ # Required for LSP completions
80
+ other = cast("LibraryClause", other) # type: ignore[redundant-cast]
81
+ return self._symbols == other._symbols
82
+
83
+ def __hash__(self) -> int: # noqa: D105
84
+ return hash(
85
+ (self._symbols,),
86
+ )
87
+
88
+ def __repr__(self) -> str: # noqa: D105
89
+ return f"{self.__class__.__name__}(_symbols={self._symbols!r}, )"
90
+
91
+
92
+ class UseClause(BaseUseClause): # noqa: D101
93
+ @classmethod
94
+ def from_treesitter_node(
95
+ cls,
96
+ node: tree_sitter.Node,
97
+ ) -> list[Self]:
98
+ """
99
+ Create one or more UseClauses from a treesitter node.
100
+
101
+ Raises
102
+ ------
103
+ NodeTextIsNoneError:
104
+ If the `library_namespace` treesitter node contains no text.
105
+
106
+ """
107
+ captures = UseClauses.capture(node)
108
+ use_clause_nodes = get_nodes_or_raise(
109
+ node,
110
+ captures,
111
+ "use-clause",
112
+ UseClauses.TEXT,
113
+ )
114
+ ret = []
115
+ for use_clause_node in use_clause_nodes:
116
+ captures = SelectedNames.capture(use_clause_node)
117
+ selected_name_nodes = get_nodes_or_raise(
118
+ node,
119
+ captures,
120
+ "selected-name",
121
+ SelectedNames.TEXT,
122
+ )
123
+ symbols = []
124
+ for selected_name_node in selected_name_nodes:
125
+ if selected_name_node.text is None:
126
+ raise NodeTextIsNoneError(node, SelectedNames.TEXT, "selected-name")
127
+ selected_name = selected_name_node.text.decode()
128
+ selected_name_parts = selected_name.split(".")
129
+ selected_name_obj: SimpleName | SelectedName = SimpleName(
130
+ selected_name_parts[0],
131
+ )
132
+ for part in selected_name_parts[1:]:
133
+ selected_name_obj = SelectedName(part, selected_name_obj)
134
+ symbols.append(PackageReferenceSymbol(selected_name_obj))
135
+ ret.append(cls(symbols))
136
+ return ret
137
+
138
+ def __eq__(self, other: object) -> bool: # noqa: D105
139
+ if not isinstance(other, UseClause):
140
+ return False
141
+ # Required for LSP completions
142
+ other = cast("UseClause", other) # type: ignore[redundant-cast]
143
+ return self._symbols == other._symbols
144
+
145
+ def __hash__(self) -> int: # noqa: D105
146
+ return hash(
147
+ (self._symbols,),
148
+ )
149
+
150
+ def __repr__(self) -> str: # noqa: D105
151
+ return f"{self.__class__.__name__}(_symbols={self._symbols!r}, )"
152
+
153
+
154
+ class Entity(pyVHDLModel.Entity): # noqa: D101
155
+ @classmethod
156
+ def from_treesitter_node(
157
+ cls,
158
+ node: tree_sitter.Node,
159
+ *,
160
+ contextItems: Iterable[LibraryClause | UseClause] = [], # noqa: N803
161
+ ) -> Self:
162
+ """
163
+ Create an Entity from a treesitter node.
164
+
165
+ Raises
166
+ ------
167
+ ExtraNodeError:
168
+ If there is more than one `generics` or `ports` capture.
169
+ NodeTextIsNoneError:
170
+ If the `name` capture has no text.
171
+
172
+ """
173
+ captures = EntityDeclaration.capture(node)
174
+ name_node = get_node_or_raise(
175
+ node,
176
+ captures,
177
+ "name",
178
+ EntityDeclaration.TEXT,
179
+ )
180
+ if name_node.text is None:
181
+ raise NodeTextIsNoneError(node, EntityDeclaration.TEXT, "name")
182
+ name_text = name_node.text.decode()
183
+ generics_nodes = captures.get("generics", [])
184
+ if len(generics_nodes) > 1:
185
+ raise ExtraNodeError(node, EntityDeclaration.TEXT, "generics")
186
+ ports_nodes = captures.get("ports", [])
187
+ if len(ports_nodes) > 1:
188
+ raise ExtraNodeError(node, EntityDeclaration.TEXT, "generics")
189
+ generic_items = None
190
+ if len(generics_nodes) == 1:
191
+ generics_node = generics_nodes[0]
192
+ captures = InterfaceDeclarations.capture(generics_node)
193
+ generic_items_with_nodes = [
194
+ (
195
+ GenericConstantInterfaceItem.from_treesitter_node(interface_node),
196
+ interface_node,
197
+ )
198
+ for interface_node in captures.get("interface", [])
199
+ ]
200
+ generic_items = [
201
+ obj
202
+ for obj, _ in sorted(
203
+ generic_items_with_nodes,
204
+ key=cmp_to_key(lambda l, r: l[1].start_byte - r[1].start_byte), # type: ignore[index] # noqa: E741
205
+ )
206
+ ]
207
+ port_items = None
208
+ if len(ports_nodes) == 1:
209
+ ports_node = ports_nodes[0]
210
+ captures = InterfaceDeclarations.capture(ports_node)
211
+ port_items_with_nodes = [
212
+ (
213
+ PortSignalInterfaceItem.from_treesitter_node(interface_node),
214
+ interface_node,
215
+ )
216
+ for interface_node in captures.get("interface", [])
217
+ ]
218
+ port_items = [
219
+ obj
220
+ for obj, _ in sorted(
221
+ port_items_with_nodes,
222
+ key=cmp_to_key(lambda l, r: l[1].start_byte - r[1].start_byte), # type: ignore[index] # noqa: E741
223
+ )
224
+ ]
225
+ return cls(
226
+ identifier=name_text,
227
+ contextItems=contextItems,
228
+ genericItems=generic_items,
229
+ portItems=port_items,
230
+ declaredItems=None, # TODO: implement
231
+ statements=None, # TODO: implement
232
+ documentation=None, # TODO: implement
233
+ allowBlackbox=None, # TODO: implement
234
+ )
235
+
236
+ def __eq__(self, other: object) -> bool: # noqa: D105
237
+ if not isinstance(other, Entity):
238
+ return False
239
+ # Required for LSP completions
240
+ other = cast("Entity", other) # type: ignore[redundant-cast]
241
+ return (
242
+ self._identifier == other._identifier
243
+ and self._contextItems == other._contextItems
244
+ and self._genericItems == other._genericItems
245
+ and self._portItems == other._portItems
246
+ and self._declaredItems == other._declaredItems
247
+ and self._statements == other._statements
248
+ and self._documentation == other._documentation
249
+ and self._allowBlackbox == other._allowBlackbox
250
+ )
251
+
252
+ def __hash__(self) -> int: # noqa: D105
253
+ return hash(
254
+ (
255
+ self._identifier,
256
+ self._contextItems,
257
+ self._genericItems,
258
+ self._portItems,
259
+ self._declaredItems,
260
+ self._statements,
261
+ self._documentation,
262
+ self._allowBlackbox,
263
+ ),
264
+ )
265
+
266
+ def __repr__(self) -> str: # noqa: D105
267
+ return (
268
+ f"{self.__class__.__name__}("
269
+ f"_identifier={self._identifier!r}, "
270
+ f"_contextItems={self._contextItems!r}, "
271
+ f"_genericItems={self._genericItems!r}, "
272
+ f"_portItems={self._portItems!r}, "
273
+ f"_declaredItems={self._declaredItems}, "
274
+ f"_statements={self._statements}, "
275
+ f"_documentation={self._documentation}, "
276
+ f"_allowBlackbox={self._allowBlackbox}, "
277
+ ")"
278
+ )
@@ -0,0 +1,80 @@
1
+ """Submodule mirroring modified aspects of pyVHDLModel.Expression."""
2
+
3
+ __all__ = [
4
+ "EnumerationLiteral",
5
+ "FloatingPointLiteral",
6
+ "IntegerLiteral",
7
+ "StringLiteral",
8
+ ]
9
+
10
+ from typing import cast
11
+
12
+ import pyVHDLModel
13
+
14
+
15
+ class EnumerationLiteral(pyVHDLModel.Expression.EnumerationLiteral): # noqa: D101
16
+ def __eq__(self, other: object) -> bool: # noqa: D105
17
+ if not isinstance(other, EnumerationLiteral):
18
+ return False
19
+ # Required for LSP completions
20
+ other = cast("EnumerationLiteral", other) # type: ignore[redundant-cast]
21
+ return self._value.lower() == other._value.lower()
22
+
23
+ def __hash__(self) -> int: # noqa: D105
24
+ return hash(
25
+ (self._value,),
26
+ )
27
+
28
+ def __repr__(self) -> str: # noqa: D105
29
+ return f"{self.__class__.__name__}(_value={self._value!r}, )"
30
+
31
+
32
+ class FloatingPointLiteral(pyVHDLModel.Expression.FloatingPointLiteral): # noqa: D101
33
+ def __eq__(self, other: object) -> bool: # noqa: D105
34
+ if not isinstance(other, FloatingPointLiteral):
35
+ return False
36
+ # Required for LSP completions
37
+ other = cast("FloatingPointLiteral", other) # type: ignore[redundant-cast]
38
+ return self._value == other._value
39
+
40
+ def __hash__(self) -> int: # noqa: D105
41
+ return hash(
42
+ (self._value,),
43
+ )
44
+
45
+ def __repr__(self) -> str: # noqa: D105
46
+ return f"{self.__class__.__name__}(_value={self._value!r}, )"
47
+
48
+
49
+ class IntegerLiteral(pyVHDLModel.Expression.IntegerLiteral): # noqa: D101
50
+ def __eq__(self, other: object) -> bool: # noqa: D105
51
+ if not isinstance(other, IntegerLiteral):
52
+ return False
53
+ # Required for LSP completions
54
+ other = cast("IntegerLiteral", other) # type: ignore[redundant-cast]
55
+ return self._value == other._value
56
+
57
+ def __hash__(self) -> int: # noqa: D105
58
+ return hash(
59
+ (self._value,),
60
+ )
61
+
62
+ def __repr__(self) -> str: # noqa: D105
63
+ return f"{self.__class__.__name__}(_value={self._value!r}, )"
64
+
65
+
66
+ class StringLiteral(pyVHDLModel.Expression.StringLiteral): # noqa: D101
67
+ def __eq__(self, other: object) -> bool: # noqa: D105
68
+ if not isinstance(other, StringLiteral):
69
+ return False
70
+ # Required for LSP completions
71
+ other = cast("StringLiteral", other) # type: ignore[redundant-cast]
72
+ return self._value == other._value
73
+
74
+ def __hash__(self) -> int: # noqa: D105
75
+ return hash(
76
+ (self._value,),
77
+ )
78
+
79
+ def __repr__(self) -> str: # noqa: D105
80
+ return f"{self.__class__.__name__}(_value={self._value!r}, )"
@@ -0,0 +1,298 @@
1
+ """Submodule defining the various interfaces that are available."""
2
+
3
+ __all__ = [
4
+ "GenericConstantInterfaceItem",
5
+ "PortSignalInterfaceItem",
6
+ ]
7
+
8
+ from typing import Self, cast
9
+
10
+ import pyVHDLModel
11
+ from tree_sitter import Node
12
+
13
+ from pyVHDLModelTreesitter._error import (
14
+ ExtraNodeError,
15
+ MissingNodeError,
16
+ NodeTextIsNoneError,
17
+ )
18
+ from pyVHDLModelTreesitter.Base import parse_as_mode
19
+ from pyVHDLModelTreesitter.Expression import (
20
+ EnumerationLiteral,
21
+ FloatingPointLiteral,
22
+ IntegerLiteral,
23
+ StringLiteral,
24
+ )
25
+ from pyVHDLModelTreesitter.Name import SimpleName
26
+ from pyVHDLModelTreesitter.Symbol import SimpleSubtypeSymbol
27
+ from pyVHDLModelTreesitter.treesitter import get_node_or_raise
28
+ from pyVHDLModelTreesitter.treesitter.query import InterfaceDeclaration
29
+
30
+
31
+ def parse_simple_expression(node: Node) -> pyVHDLModel.Expression.BaseExpression:
32
+ """
33
+ Parse a 'simple_expression' to a model object.
34
+
35
+ Raises
36
+ ------
37
+ MissingNodeError:
38
+ If `node` doesn't have exactly one child.
39
+
40
+ """
41
+ if node.child_count != 1:
42
+ raise MissingNodeError(
43
+ node,
44
+ InterfaceDeclaration.TEXT,
45
+ "simple_expression",
46
+ )
47
+ expression_child_node = node.children[0]
48
+ assert expression_child_node.text is not None # noqa: S101
49
+ # TODO: support more simple expressions
50
+ if expression_child_node.type == "decimal_float":
51
+ return FloatingPointLiteral(
52
+ float(expression_child_node.text.decode()),
53
+ )
54
+ if expression_child_node.type == "decimal_integer":
55
+ return IntegerLiteral(
56
+ int(expression_child_node.text.decode()),
57
+ )
58
+ if expression_child_node.type == "library_constant_boolean":
59
+ return EnumerationLiteral(
60
+ expression_child_node.text.decode(),
61
+ )
62
+ if expression_child_node.type == "name":
63
+ assert expression_child_node.child_count == 1 # noqa: S101
64
+ name_node = expression_child_node.children[0]
65
+ assert name_node.text is not None # noqa: S101
66
+ if name_node.type == "library_constant_std_logic":
67
+ return EnumerationLiteral(
68
+ name_node.text.decode()[1:-1],
69
+ )
70
+ if expression_child_node.type == "string_literal":
71
+ return StringLiteral(
72
+ expression_child_node.text.decode()[1:-1],
73
+ )
74
+ message = f"Default of type '{expression_child_node.type}' is not yet implemented"
75
+ raise NotImplementedError(message)
76
+
77
+
78
+ class GenericConstantInterfaceItem(pyVHDLModel.Interface.GenericConstantInterfaceItem): # noqa: D101
79
+ @classmethod
80
+ def from_treesitter_node(
81
+ cls,
82
+ node: Node,
83
+ ) -> Self:
84
+ """
85
+ Create a GenericConstantInterfaceItem from a treesitter node.
86
+
87
+ Raises
88
+ ------
89
+ MissingNodeError:
90
+ If `default` is present but isn't a simple expression.
91
+ NodeTextIsNoneError:
92
+ If the `name`, `type`, or `default` capture has no text.
93
+ ExtraNodeError:
94
+ If there is more than one `default`.
95
+
96
+ """
97
+ captures = InterfaceDeclaration.capture(node)
98
+ name_node = get_node_or_raise(
99
+ node,
100
+ captures,
101
+ "name",
102
+ InterfaceDeclaration.TEXT,
103
+ )
104
+ type_node = get_node_or_raise(
105
+ node,
106
+ captures,
107
+ "type",
108
+ InterfaceDeclaration.TEXT,
109
+ )
110
+ default_nodes = captures.get("default")
111
+
112
+ if name_node.text is None:
113
+ raise NodeTextIsNoneError(node, InterfaceDeclaration.TEXT, "name")
114
+ name_text = name_node.text.decode()
115
+
116
+ mode = pyVHDLModel.Base.Mode.In
117
+
118
+ if type_node.text is None:
119
+ raise NodeTextIsNoneError(node, InterfaceDeclaration.TEXT, "type")
120
+ type_text = type_node.text.decode()
121
+
122
+ default_expression: pyVHDLModel.Expression.BaseExpression | None = None
123
+ if default_nodes is not None:
124
+ if len(default_nodes) > 1:
125
+ raise ExtraNodeError(node, InterfaceDeclaration.TEXT, "default")
126
+ default_node = default_nodes[0]
127
+ # TODO: support non simple expressions
128
+ assert default_node.child_count == 1 # noqa: S101
129
+ simple_expression_node = default_node.children[0]
130
+ assert simple_expression_node.type == "simple_expression" # noqa: S101
131
+ if simple_expression_node is None:
132
+ raise MissingNodeError(
133
+ default_node,
134
+ InterfaceDeclaration.TEXT,
135
+ "simple_expression",
136
+ )
137
+ default_expression = parse_simple_expression(simple_expression_node)
138
+
139
+ return cls(
140
+ # TODO: why is there a list of identifiers?
141
+ identifiers=[name_text],
142
+ mode=mode,
143
+ # TODO: support types that aren't simple
144
+ subtype=SimpleSubtypeSymbol(SimpleName(type_text)),
145
+ defaultExpression=default_expression,
146
+ documentation=None,
147
+ )
148
+
149
+ def __eq__(self, other: object) -> bool: # noqa: D105
150
+ if not isinstance(other, GenericConstantInterfaceItem):
151
+ return False
152
+ # Required for LSP completions
153
+ other = cast("GenericConstantInterfaceItem", other) # type: ignore[redundant-cast]
154
+ return (
155
+ self._identifiers == other._identifiers
156
+ and self._mode == other._mode
157
+ and self._subtype == other._subtype
158
+ and self._defaultExpression == other._defaultExpression
159
+ )
160
+
161
+ def __hash__(self) -> int: # noqa: D105
162
+ return hash(
163
+ (
164
+ self._identifiers,
165
+ self._mode,
166
+ self._subtype,
167
+ self._defaultExpression,
168
+ ),
169
+ )
170
+
171
+ def __repr__(self) -> str: # noqa: D105
172
+ return (
173
+ f"{self.__class__.__name__}("
174
+ f"_identifiers={self._identifiers}, "
175
+ f"_mode={self._mode}, "
176
+ f"_subtype={self._subtype!r}, "
177
+ f"_defaultExpression={self._defaultExpression!r}, "
178
+ ")"
179
+ )
180
+
181
+
182
+ class PortSignalInterfaceItem(pyVHDLModel.Interface.PortSignalInterfaceItem): # noqa: D101
183
+ @classmethod
184
+ def from_treesitter_node(
185
+ cls,
186
+ node: Node,
187
+ ) -> Self:
188
+ """
189
+ Create a PortSignalInterfaceItem from a treesitter node.
190
+
191
+ Raises
192
+ ------
193
+ ExtraNodeError:
194
+ If `default` is captured more than once.
195
+ MissingNodeError:
196
+ If `default` is present but isn't a simple expression.
197
+ NodeTextIsNoneError:
198
+ If the `name`, `type`, `mode`, or `default` capture has no text.
199
+ ValueError:
200
+ If `mode` is not a valid mode.
201
+
202
+ """
203
+ captures = InterfaceDeclaration.capture(node)
204
+ name_node = get_node_or_raise(
205
+ node,
206
+ captures,
207
+ "name",
208
+ InterfaceDeclaration.TEXT,
209
+ )
210
+ mode_node = get_node_or_raise(
211
+ node,
212
+ captures,
213
+ "mode",
214
+ InterfaceDeclaration.TEXT,
215
+ )
216
+ type_node = get_node_or_raise(
217
+ node,
218
+ captures,
219
+ "type",
220
+ InterfaceDeclaration.TEXT,
221
+ )
222
+ default_nodes = captures.get("default")
223
+
224
+ if name_node.text is None:
225
+ raise NodeTextIsNoneError(node, InterfaceDeclaration.TEXT, "name")
226
+ name_text = name_node.text.decode()
227
+
228
+ if mode_node.text is None:
229
+ raise NodeTextIsNoneError(node, InterfaceDeclaration.TEXT, "mode")
230
+ mode_text = mode_node.text.decode()
231
+
232
+ if type_node.text is None:
233
+ raise NodeTextIsNoneError(node, InterfaceDeclaration.TEXT, "type")
234
+ type_text = type_node.text.decode()
235
+
236
+ default_expression: pyVHDLModel.Expression.BaseExpression | None = None
237
+ if default_nodes is not None:
238
+ if len(default_nodes) > 1:
239
+ raise ExtraNodeError(node, InterfaceDeclaration.TEXT, "default")
240
+ default_node = default_nodes[0]
241
+ # TODO: support non simple expressions
242
+ assert default_node.child_count == 1 # noqa: S101
243
+ simple_expression_node = default_node.children[0]
244
+ assert simple_expression_node.type == "simple_expression" # noqa: S101
245
+ if simple_expression_node is None:
246
+ raise MissingNodeError(
247
+ default_node,
248
+ InterfaceDeclaration.TEXT,
249
+ "simple_expression",
250
+ )
251
+ default_expression = parse_simple_expression(simple_expression_node)
252
+
253
+ mode = parse_as_mode(mode_text)
254
+ if mode is None:
255
+ message = f"'{mode_text}' is not a valid VHDL interface mode"
256
+ raise ValueError(message)
257
+
258
+ return cls(
259
+ # TODO: why is there a list of identifiers?
260
+ identifiers=[name_text],
261
+ mode=mode,
262
+ # TODO: support types that aren't simple
263
+ subtype=SimpleSubtypeSymbol(SimpleName(type_text)),
264
+ defaultExpression=default_expression,
265
+ documentation=None,
266
+ )
267
+
268
+ def __eq__(self, other: object) -> bool: # noqa: D105
269
+ if not isinstance(other, PortSignalInterfaceItem):
270
+ return False
271
+ # Required for LSP completions
272
+ other = cast("PortSignalInterfaceItem", other) # type: ignore[redundant-cast]
273
+ return (
274
+ self._identifiers == other._identifiers
275
+ and self._mode == other._mode
276
+ and self._subtype == other._subtype
277
+ and self._defaultExpression == other._defaultExpression
278
+ )
279
+
280
+ def __hash__(self) -> int: # noqa: D105
281
+ return hash(
282
+ (
283
+ self._identifiers,
284
+ self._mode,
285
+ self._subtype,
286
+ self._defaultExpression,
287
+ ),
288
+ )
289
+
290
+ def __repr__(self) -> str: # noqa: D105
291
+ return (
292
+ f"{self.__class__.__name__}("
293
+ f"_identifiers={self._identifiers}, "
294
+ f"_mode={self._mode}, "
295
+ f"_subtype={self._subtype!r}, "
296
+ f"_defaultExpression={self._defaultExpression!r}, "
297
+ ")"
298
+ )
@@ -0,0 +1,46 @@
1
+ """Submodule mirroring modified aspects of pyVHDLModel.Name."""
2
+
3
+ __all__ = [
4
+ "SelectedName",
5
+ "SimpleName",
6
+ ]
7
+
8
+ from typing import cast
9
+
10
+ from pyVHDLModel.Name import (
11
+ SelectedName as BaseSelectedName,
12
+ )
13
+ from pyVHDLModel.Name import (
14
+ SimpleName as BaseSimpleName,
15
+ )
16
+
17
+
18
+ class SimpleName(BaseSimpleName): # noqa: D101
19
+ def __eq__(self, other: object) -> bool: # noqa: D105
20
+ if not isinstance(other, SimpleName):
21
+ return False
22
+ # Required for LSP completions
23
+ other = cast("SimpleName", other) # type: ignore[redundant-cast]
24
+ return self._identifier == other._identifier
25
+
26
+ def __hash__(self) -> int: # noqa: D105
27
+ return hash(
28
+ (self._identifier,),
29
+ )
30
+
31
+
32
+ class SelectedName(BaseSelectedName): # noqa: D101
33
+ def __eq__(self, other: object) -> bool: # noqa: D105
34
+ if not isinstance(other, SelectedName):
35
+ return False
36
+ # Required for LSP completions
37
+ other = cast("SelectedName", other) # type: ignore[redundant-cast]
38
+ return self._identifier == other._identifier and self._prefix == other._prefix
39
+
40
+ def __hash__(self) -> int: # noqa: D105
41
+ return hash(
42
+ (
43
+ self._identifier,
44
+ self._prefix,
45
+ ),
46
+ )
@@ -0,0 +1,62 @@
1
+ """Submodule mirroring modified aspects of pyVHDLModel.Symbol."""
2
+
3
+ __all__ = [
4
+ "LibraryReferenceSymbol",
5
+ "PackageReferenceSymbol",
6
+ "SimpleSubtypeSymbol",
7
+ ]
8
+
9
+ from typing import cast
10
+
11
+ import pyVHDLModel
12
+
13
+
14
+ class LibraryReferenceSymbol(pyVHDLModel.Symbol.LibraryReferenceSymbol): # noqa: D101
15
+ def __eq__(self, other: object) -> bool: # noqa: D105
16
+ if not isinstance(other, LibraryReferenceSymbol):
17
+ return False
18
+ # Required for LSP completions
19
+ other = cast("LibraryReferenceSymbol", other) # type: ignore[redundant-cast]
20
+ return self._name == other._name
21
+
22
+ def __hash__(self) -> int: # noqa: D105
23
+ return hash(
24
+ (self._name,),
25
+ )
26
+
27
+ def __repr__(self) -> str: # noqa: D105
28
+ return f"{self.__class__.__name__}(_name={self._name}, )"
29
+
30
+
31
+ class PackageReferenceSymbol(pyVHDLModel.Symbol.PackageReferenceSymbol): # noqa: D101
32
+ def __eq__(self, other: object) -> bool: # noqa: D105
33
+ if not isinstance(other, PackageReferenceSymbol):
34
+ return False
35
+ # Required for LSP completions
36
+ other = cast("PackageReferenceSymbol", other) # type: ignore[redundant-cast]
37
+ return self._name == other._name
38
+
39
+ def __hash__(self) -> int: # noqa: D105
40
+ return hash(
41
+ (self._name,),
42
+ )
43
+
44
+ def __repr__(self) -> str: # noqa: D105
45
+ return f"{self.__class__.__name__}(_name={self._name}, )"
46
+
47
+
48
+ class SimpleSubtypeSymbol(pyVHDLModel.Symbol.SimpleSubtypeSymbol): # noqa: D101
49
+ def __eq__(self, other: object) -> bool: # noqa: D105
50
+ if not isinstance(other, SimpleSubtypeSymbol):
51
+ return False
52
+ # Required for LSP completions
53
+ other = cast("SimpleSubtypeSymbol", other) # type: ignore[redundant-cast]
54
+ return self._name == other._name
55
+
56
+ def __hash__(self) -> int: # noqa: D105
57
+ return hash(
58
+ (self._name,),
59
+ )
60
+
61
+ def __repr__(self) -> str: # noqa: D105
62
+ return f"{self.__class__.__name__}(_name={self._name}, )"
@@ -0,0 +1,228 @@
1
+ """The pyVHDLModelTreesitter package."""
2
+
3
+ __all__: list[str] = [
4
+ "Design",
5
+ "DesignUnit",
6
+ "Document",
7
+ "Entity",
8
+ "Expression",
9
+ "ExtraNodeError",
10
+ "Interface",
11
+ "Library",
12
+ "MissingNodeError",
13
+ "Name",
14
+ "NodeTextIsNoneError",
15
+ "Symbol",
16
+ ]
17
+
18
+ from ._error import ExtraNodeError, MissingNodeError, NodeTextIsNoneError # noqa: I001
19
+ from . import DesignUnit, Expression, Interface, Name, Symbol
20
+ from .DesignUnit import Entity
21
+
22
+ from pathlib import Path
23
+ from typing import Self, cast
24
+
25
+ from pyVHDLModel import (
26
+ Design as BaseDesign,
27
+ Document as BaseDocument,
28
+ Library as BaseLibrary,
29
+ )
30
+ from pyVHDLModelTreesitter.DesignUnit import LibraryClause, UseClause
31
+ from pyVHDLModelTreesitter.treesitter.query import PARSER, DesignUnits
32
+
33
+
34
+ class Library(BaseLibrary): # noqa: D101
35
+ def __eq__(self, other: object) -> bool: # noqa: D105
36
+ if not isinstance(other, Library):
37
+ return False
38
+ # Required for LSP completions
39
+ other = cast("Library", other) # type: ignore[redundant-cast]
40
+ return (
41
+ self._allowBlackbox == other._allowBlackbox
42
+ and self._contexts == other._contexts
43
+ and self._configurations == other._configurations
44
+ and self._entities == other._entities
45
+ and self._architectures == other._architectures
46
+ and self._packages == other._packages
47
+ and self._packageBodies == other._packageBodies
48
+ )
49
+
50
+ def __hash__(self) -> int: # noqa: D105
51
+ return hash(
52
+ (
53
+ self._allowBlackbox,
54
+ self._contexts,
55
+ self._configurations,
56
+ self._entities,
57
+ self._architectures,
58
+ self._packages,
59
+ self._packageBodies,
60
+ ),
61
+ )
62
+
63
+ def __repr__(self) -> str: # noqa: D105
64
+ return (
65
+ f"{self.__class__.__name__}("
66
+ f"_allowBlackbox={self._allowBlackbox}, "
67
+ f"_contexts={self._contexts}, "
68
+ f"_configurations={self._configurations}, "
69
+ f"_entities={list(self._entities.values())!r}, "
70
+ f"_architectures={self._architectures}, "
71
+ f"_packages={self._packages}, "
72
+ f"_packageBodies={self._packageBodies}, "
73
+ ")"
74
+ )
75
+
76
+
77
+ class Document(BaseDocument): # noqa: D101
78
+ @classmethod
79
+ def from_file(cls, path: str | Path) -> Self: # noqa: C901 # TODO: refactor
80
+ """
81
+ Create a Document, and all ModelEntities it contains.
82
+
83
+ Raises
84
+ ------
85
+ MissingNodeError:
86
+ - There are no `design_unit` nodes.
87
+ - Any `design_unit` node has no children.
88
+ NotImplementedError:
89
+ - If a node is encountered that isn't implemented.
90
+
91
+ """
92
+ if isinstance(path, str):
93
+ path = Path(path)
94
+ document = cls(path)
95
+
96
+ tree = PARSER.parse(path.read_bytes())
97
+ node = tree.root_node
98
+ captures = DesignUnits.capture(node)
99
+ design_unit_captures = captures.get("design-unit")
100
+ if design_unit_captures is None:
101
+ raise MissingNodeError(node, DesignUnits.TEXT, "design-unit")
102
+ for design_unit_capture in design_unit_captures:
103
+ if len(design_unit_capture.children) == 0:
104
+ raise MissingNodeError(node, DesignUnits.TEXT, "design-unit")
105
+ design_unit_child = design_unit_capture.children[-1]
106
+ context_children = design_unit_capture.children[:-1]
107
+ context_items: list[LibraryClause | UseClause] = []
108
+ for context_child in context_children:
109
+ if context_child is None:
110
+ raise MissingNodeError(node, DesignUnits.TEXT, "design-unit")
111
+ if context_child.type == "library_clause":
112
+ context_items.extend(
113
+ LibraryClause.from_treesitter_node(context_child),
114
+ )
115
+ elif context_child.type == "use_clause":
116
+ context_items.extend(
117
+ UseClause.from_treesitter_node(context_child),
118
+ )
119
+ else:
120
+ message = f"Node of type '{context_child.type}' is not supported"
121
+ raise NotImplementedError(message)
122
+ if design_unit_child is None:
123
+ raise MissingNodeError(node, DesignUnits.TEXT, "design-unit")
124
+ if design_unit_child.type == "entity_declaration":
125
+ entity = Entity.from_treesitter_node(node, contextItems=context_items)
126
+ document._AddEntity(entity)
127
+ else:
128
+ message = f"Node of type '{design_unit_child.type}' is not supported"
129
+ raise NotImplementedError(message)
130
+ return document
131
+
132
+ def __eq__(self, other: object) -> bool: # noqa: D105
133
+ if not isinstance(other, Document):
134
+ return False
135
+ # Required for LSP completions
136
+ other = cast("Document", other) # type: ignore[redundant-cast]
137
+ return (
138
+ self._path == other._path
139
+ and self._contexts == other._contexts
140
+ and self._configurations == other._configurations
141
+ and self._entities == other._entities
142
+ and self._architectures == other._architectures
143
+ and self._packages == other._packages
144
+ and self._packageBodies == other._packageBodies
145
+ and self._verificationUnits == other._verificationUnits
146
+ and self._verificationProperties == other._verificationProperties
147
+ and self._verificationModes == other._verificationModes
148
+ )
149
+
150
+ def __hash__(self) -> int: # noqa: D105
151
+ return hash(
152
+ (
153
+ self._path,
154
+ self._contexts,
155
+ self._configurations,
156
+ self._entities,
157
+ self._architectures,
158
+ self._packages,
159
+ self._packageBodies,
160
+ self._verificationUnits,
161
+ self._verificationProperties,
162
+ self._verificationModes,
163
+ ),
164
+ )
165
+
166
+ def __repr__(self) -> str: # noqa: D105
167
+ return (
168
+ f"{self.__class__.__name__}("
169
+ f"_path={self._path}, "
170
+ f"_contexts={self._contexts}, "
171
+ f"_configurations={self._configurations}, "
172
+ f"_entities={list(self._entities.values())!r}, "
173
+ f"_architectures={self._architectures}, "
174
+ f"_packages={self._packages}, "
175
+ f"_packageBodies={self._packageBodies}, "
176
+ f"_verificationUnits={self._verificationUnits}, "
177
+ f"_verificationProperties={self._verificationProperties}, "
178
+ f"_verificationModes={self._verificationModes}, "
179
+ ")"
180
+ )
181
+
182
+
183
+ class Design(BaseDesign): # noqa: D101
184
+ def GetLibrary( # noqa: D102, N802
185
+ self,
186
+ name: str,
187
+ ) -> Library:
188
+ library = self.Libraries.get(name)
189
+ if library is not None:
190
+ return cast("Library", library)
191
+ library = Library(
192
+ name,
193
+ allowBlackbox=self._allowBlackbox,
194
+ )
195
+ self.AddLibrary(library)
196
+ return library
197
+
198
+ def __eq__(self, other: object) -> bool: # noqa: D105
199
+ if not isinstance(other, Design):
200
+ return False
201
+ # Required for LSP completions
202
+ other = cast("Design", other) # type: ignore[redundant-cast]
203
+ return (
204
+ self._name == other._name
205
+ and self._allowBlackbox == other._allowBlackbox
206
+ and self._libraries == other._libraries
207
+ and self._documents == other._documents
208
+ )
209
+
210
+ def __hash__(self) -> int: # noqa: D105
211
+ return hash(
212
+ (
213
+ self._name,
214
+ self._allowBlackbox,
215
+ self._libraries,
216
+ self._documents,
217
+ ),
218
+ )
219
+
220
+ def __repr__(self) -> str: # noqa: D105
221
+ return (
222
+ f"{self.__class__.__name__}("
223
+ f"_name={self._name}, "
224
+ f"_allowBlackbox={self._allowBlackbox}, "
225
+ f"_libraries={list(self._libraries.values())!r}, "
226
+ f"_documents={self._documents!r}, "
227
+ ")"
228
+ )
@@ -0,0 +1,39 @@
1
+ """Submodule defining errors."""
2
+
3
+ from tree_sitter import Node
4
+
5
+
6
+ class BaseQueryCaptureError(RuntimeError):
7
+ """Error raised when a capture fails for some reason."""
8
+
9
+ def __init__(self, node: Node, query: str, name: str, message: str) -> None:
10
+ """Construct."""
11
+ assert node.text is not None # noqa: S101
12
+ super().__init__(f"Capture '@{name}': {message}: {node.text.decode()}: {node}")
13
+ self._node = node
14
+ self._query = query
15
+ self._name = name
16
+
17
+
18
+ class MissingNodeError(BaseQueryCaptureError):
19
+ """Error raised when an expected node is not found."""
20
+
21
+ def __init__(self, node: Node, query: str, name: str) -> None:
22
+ """Construct."""
23
+ super().__init__(node, query, name, "Not found in node")
24
+
25
+
26
+ class ExtraNodeError(BaseQueryCaptureError):
27
+ """Error raised when an expected node is found too many times."""
28
+
29
+ def __init__(self, node: Node, query: str, name: str) -> None:
30
+ """Construct."""
31
+ super().__init__(node, query, name, "Found too many times in node")
32
+
33
+
34
+ class NodeTextIsNoneError(BaseQueryCaptureError):
35
+ """Error raised when a node that is expected to have a `text` field doesn't."""
36
+
37
+ def __init__(self, node: Node, query: str, name: str) -> None:
38
+ """Construct."""
39
+ super().__init__(node, query, name, "`text` attribute not found for node")