add proxy for the first part and try to develop the second part
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import weakref
|
||||
from collections.abc import Mapping
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from .ast import AST
|
||||
from .infos import CommentInfo, ParseInfo
|
||||
from .util import AsJSONMixin, asjson, asjsons
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user