File: objectmodel.py

package info (click to toggle)
python-tatsu 5.15.1%2Bds-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 904 kB
  • sloc: python: 10,128; makefile: 54
file content (204 lines) | stat: -rw-r--r-- 5,419 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
from __future__ import annotations

import weakref
from collections.abc import Mapping
from dataclasses import dataclass
from typing import Any

from tatsu.util import AsJSONMixin, asjson, asjsons

from .ast import AST
from .infos import CommentInfo, ParseInfo

BASE_CLASS_TOKEN = '::'  # noqa: S105


@dataclass(eq=False)
class Node(AsJSONMixin):
    _parent: Node | None = None
    _children: list[Node] | None = None
    ast: AST | None = None
    ctx: Any = None
    parseinfo: ParseInfo | None = None

    def __init__(self, ast=None, **attributes):
        super().__init__()
        if isinstance(ast, dict):
            ast = AST(ast)
        self.ast = ast

        for name, value in attributes.items():
            setattr(self, name, value)

        self.__post_init__()

    def __post_init__(self):
        ast = self.ast

        if not self.parseinfo and isinstance(ast, AST):
            self.parseinfo = ast.parseinfo

        if not isinstance(ast, Mapping):
            return

        for name in set(ast) - {'parseinfo'}:
            try:
                setattr(self, name, ast[name])
            except AttributeError as e:
                raise AttributeError(f"'{name}' is a reserved name") from e

        self._children = self.children_list()
        if self.parseinfo is None:
            del self.parseinfo

    @property
    def parent(self):
        return self._parent

    @property
    def line(self):
        if self.parseinfo:
            return self.parseinfo.line
        return None

    @property
    def endline(self):
        if self.parseinfo:
            return self.parseinfo.endline
        return None

    def text_lines(self):
        if self.parseinfo:
            return self.parseinfo.text_lines()
        return None

    def line_index(self):
        return self.parseinfo.line_index()

    @property
    def col(self):
        return self.line_info.col if self.line_info else None

    @property
    def context(self):
        return self.ctx

    @property
    def line_info(self):
        if self.parseinfo:
            return self.parseinfo.tokenizer.line_info(self.parseinfo.pos)
        return None

    @property
    def text(self):
        if not self.parseinfo:
            return ''
        text = self.parseinfo.tokenizer.text
        return text[self.parseinfo.pos: self.parseinfo.endpos]

    @property
    def comments(self):
        if self.parseinfo:
            return self.parseinfo.tokenizer.comments(self.parseinfo.pos)
        return CommentInfo([], [])

    @property
    def _deref(self):
        # use this to get the actual object over weakref instances
        return self

    def _find_children(self):
        def with_parent(node):
            node._parent = weakref.proxy(self)
            return node

        def children_of(child):
            if isinstance(child, weakref.ReferenceType | weakref.ProxyType):
                return
            elif isinstance(child, Node):
                yield with_parent(child)
            elif isinstance(child, Mapping):
                for name, value in child.items():
                    if name.startswith('_'):
                        continue
                    yield from children_of(value)
            elif isinstance(child, list | tuple):
                yield from (
                    with_parent(c) for c in child if isinstance(c, Node)
                )

        children = list(self._pubdict().items())
        if not children:
            yield from children_of(self.ast)
        else:
            for name, child in children:
                if name.startswith('_'):
                    continue
                yield from children_of(child)

    def children_list(self):
        if self._children is not None:
            return self._children
        return list(self._find_children())

    def children_set(self):
        return set(self.children_list())

    def children(self):
        return self.children_list()

    def asjson(self):
        return asjson(self)

    def _pubdict(self):
        return {
            name: value
            for name, value in super()._pubdict().items()
            if name not in {'ast', 'ctx', 'parent', 'parseinfo'}
        }

    def __str__(self):
        return asjsons(self)

    def __hash__(self):
        if self.ast is not None:
            if isinstance(self.ast, list):
                return hash(tuple(self.ast))
            elif isinstance(self.ast, dict):
                return hash(AST(self.ast))
            else:
                return hash(self.ast)
        else:
            return id(self)

    def __eq__(self, other):
        if id(self) == id(other):
            return True
        elif self.ast is None:
            return False
        elif not getattr(other, 'ast', None):
            return False
        else:
            return self.ast == other.ast

    def _nonrefdict(self):
        return {
            name: value
            for name, value in vars(self).items()
            if (
                name not in {'_parent', '_children'}
                and type(value)
                not in {weakref.ReferenceType, weakref.ProxyType}
            )
        }

    # NOTE: pickling is important for parallel parsing
    def __getstate__(self):
        return self._nonrefdict()

    def __setstate__(self, state):
        self.__dict__.update(state)
        self.children_list()


ParseModel = Node