File: node.py

package info (click to toggle)
opencv 4.10.0%2Bdfsg-5
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 282,092 kB
  • sloc: cpp: 1,178,079; xml: 682,621; python: 49,092; lisp: 31,150; java: 25,469; ansic: 11,039; javascript: 6,085; sh: 1,214; cs: 601; perl: 494; objc: 210; makefile: 173
file content (233 lines) | stat: -rw-r--r-- 8,299 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
import abc
import enum
import itertools
from typing import (Iterator, Type, TypeVar, Dict,
                    Optional, Tuple, DefaultDict)
from collections import defaultdict

import weakref


ASTNodeSubtype = TypeVar("ASTNodeSubtype", bound="ASTNode")
NodeType = Type["ASTNode"]
NameToNode = Dict[str, ASTNodeSubtype]


class ASTNodeType(enum.Enum):
    Namespace = enum.auto()
    Class = enum.auto()
    Function = enum.auto()
    Enumeration = enum.auto()
    Constant = enum.auto()


class ASTNode:
    """Represents an element of the Abstract Syntax Tree produced by parsing
    public C++ headers.

    NOTE: Every node manages a lifetime of its children nodes. Children nodes
    contain only weak references to their direct parents, so there are no
    circular dependencies.
    """

    def __init__(self, name: str, parent: Optional["ASTNode"] = None,
                 export_name: Optional[str] = None) -> None:
        """ASTNode initializer

        Args:
            name (str): name of the node, should be unique inside enclosing
                context (There can't be 2 classes with the same name defined
                in the same namespace).
            parent (ASTNode, optional): parent node expressing node context.
                None corresponds to globally defined object e.g. root namespace
                or function without namespace. Defaults to None.
            export_name (str, optional): export name of the node used to resolve
                issues in languages without proper overload resolution and
                provide more meaningful naming. Defaults to None.
        """

        FORBIDDEN_SYMBOLS = ";,*&#/|\\@!()[]^% "
        for forbidden_symbol in FORBIDDEN_SYMBOLS:
            assert forbidden_symbol not in name, \
                "Invalid node identifier '{}' - contains 1 or more "\
                "forbidden symbols: ({})".format(name, FORBIDDEN_SYMBOLS)

        assert ":" not in name, \
            "Name '{}' contains C++ scope symbols (':'). Convert the name to "\
            "Python style and create appropriate parent nodes".format(name)

        assert "." not in name, \
            "Trying to create a node with '.' symbols in its name ({}). " \
            "Dots are supposed to be a scope delimiters, so create all nodes in ('{}') " \
            "and add '{}' as a last child node".format(
                name,
                "->".join(name.split('.')[:-1]),
                name.rsplit('.', maxsplit=1)[-1]
            )

        self.__name = name
        self.export_name = name if export_name is None else export_name
        self._parent: Optional["ASTNode"] = None
        self.parent = parent
        self.is_exported = True
        self._children: DefaultDict[ASTNodeType, NameToNode] = defaultdict(dict)

    def __str__(self) -> str:
        return "{}('{}' exported as '{}')".format(
            self.node_type.name, self.name, self.export_name
        )

    def __repr__(self) -> str:
        return str(self)

    @abc.abstractproperty
    def children_types(self) -> Tuple[ASTNodeType, ...]:
        """Set of ASTNode types that are allowed to be children of this node

        Returns:
            Tuple[ASTNodeType, ...]: Types of children nodes
        """
        pass

    @abc.abstractproperty
    def node_type(self) -> ASTNodeType:
        """Type of the ASTNode that can be used to distinguish nodes without
        importing all subclasses of ASTNode

        Returns:
            ASTNodeType: Current node type
        """
        pass

    def node_type_name(self) -> str:
        return f"{self.node_type.name}::{self.name}"

    @property
    def name(self) -> str:
        return self.__name

    @property
    def native_name(self) -> str:
        return self.full_name.replace(".", "::")

    @property
    def full_name(self) -> str:
        return self._construct_full_name("name")

    @property
    def full_export_name(self) -> str:
        return self._construct_full_name("export_name")

    @property
    def parent(self) -> Optional["ASTNode"]:
        return self._parent

    @parent.setter
    def parent(self, value: Optional["ASTNode"]) -> None:
        assert value is None or isinstance(value, ASTNode), \
            "ASTNode.parent should be None or another ASTNode, " \
            "but got: {}".format(type(value))

        if value is not None:
            value.__check_child_before_add(self, self.name)

        # Detach from previous parent
        if self._parent is not None:
            self._parent._children[self.node_type].pop(self.name)

        if value is None:
            self._parent = None
            return

        # Set a weak reference to a new parent and add self to its children
        self._parent = weakref.proxy(value)
        value._children[self.node_type][self.name] = self

    def __check_child_before_add(self, child: ASTNodeSubtype,
                                 name: str) -> None:
        assert len(self.children_types) > 0, (
            f"Trying to add child node '{child.node_type_name}' to node "
            f"'{self.node_type_name}' that can't have children nodes"
        )

        assert child.node_type in self.children_types, \
            "Trying to add child node '{}' to node '{}' " \
            "that supports only ({}) as its children types".format(
                child.node_type_name, self.node_type_name,
                ",".join(t.name for t in self.children_types)
            )

        if self._find_child(child.node_type, name) is not None:
            raise ValueError(
                f"Node '{self.node_type_name}' already has a "
                f"child '{child.node_type_name}'"
            )

    def _add_child(self, child_type: Type[ASTNodeSubtype], name: str,
                   **kwargs) -> ASTNodeSubtype:
        """Creates a child of the node with the given type and performs common
        validation checks:
        - Node can have children of the provided type
        - Node doesn't have child with the same name

        NOTE: Shouldn't be used directly by a user.

        Args:
            child_type (Type[ASTNodeSubtype]): Type of the child to create.
            name (str): Name of the child.
            **kwargs: Extra keyword arguments supplied to child_type.__init__
                method.

        Returns:
            ASTNodeSubtype: Created ASTNode
        """
        return child_type(name, parent=self, **kwargs)

    def _find_child(self, child_type: ASTNodeType,
                    name: str) -> Optional[ASTNodeSubtype]:
        """Looks for child node with the given type and name.

        Args:
            child_type (ASTNodeType): Type of the child node.
            name (str): Name of the child node.

        Returns:
            Optional[ASTNodeSubtype]: child node if it can be found, None
                otherwise.
        """
        if child_type not in self._children:
            return None
        return self._children[child_type].get(name, None)

    def _construct_full_name(self, property_name: str) -> str:
        """Traverses nodes hierarchy upright to the root node and constructs a
        full name of the node using original or export names depending on the
        provided `property_name` argument.

        Args:
            property_name (str): Name of the property to quire from node to get
                its name. Should be `name` or `export_name`.

        Returns:
            str: full node name where each node part is divided with a dot.
        """
        def get_name(node: ASTNode) -> str:
            return getattr(node, property_name)

        assert property_name in ('name', 'export_name'), 'Invalid name property'

        name_parts = [get_name(self), ]
        parent = self.parent
        while parent is not None:
            name_parts.append(get_name(parent))
            parent = parent.parent
        return ".".join(reversed(name_parts))

    def __iter__(self) -> Iterator["ASTNode"]:
        return iter(itertools.chain.from_iterable(
            node
            # Iterate over mapping between node type and nodes dict
            for children_nodes in self._children.values()
            # Iterate over mapping between node name and node
            for node in children_nodes.values()
        ))