289 lines
9.3 KiB
Python
Executable File
289 lines
9.3 KiB
Python
Executable File
# -*- coding: utf-8 -*-
|
|
# Copyright JS Foundation and other contributors, https://js.foundation/
|
|
#
|
|
# Redistribution and use in source and binary forms, with or without
|
|
# modification, are permitted provided that the following conditions are met:
|
|
#
|
|
# * Redistributions of source code must retain the above copyright
|
|
# notice, this list of conditions and the following disclaimer.
|
|
# * Redistributions in binary form must reproduce the above copyright
|
|
# notice, this list of conditions and the following disclaimer in the
|
|
# documentation and/or other materials provided with the distribution.
|
|
#
|
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
# ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
|
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
from __future__ import unicode_literals
|
|
|
|
import json
|
|
import types
|
|
from collections import deque
|
|
|
|
from .objects import Object
|
|
from .compat import PY3, unicode
|
|
|
|
|
|
class VisitRecursionError(Exception):
|
|
pass
|
|
|
|
|
|
class Visited(object):
|
|
def __init__(self, result):
|
|
if isinstance(result, Visited):
|
|
result = result.result
|
|
self.result = result
|
|
|
|
|
|
class Visitor(object):
|
|
"""
|
|
An Object visitor base class that walks the abstract syntax tree and calls a
|
|
visitor function for every Object found. This function may return a value
|
|
which is forwarded by the `visit` method.
|
|
|
|
This class is meant to be subclassed, with the subclass adding visitor
|
|
methods.
|
|
|
|
Per default the visitor functions for the nodes are ``'visit_'`` +
|
|
class name of the Object. So a `Module` Object visit function would
|
|
be `visit_Module`. This behavior can be changed by overriding
|
|
the `visit` method. If no visitor function exists for an Object
|
|
(return value `None`) the `generic_visit` visitor is used instead.
|
|
"""
|
|
|
|
def __call__(self, obj, metadata):
|
|
return self.transform(obj, metadata)
|
|
|
|
def transform(self, obj, metadata):
|
|
"""Transform an Object."""
|
|
if isinstance(obj, Object):
|
|
method = 'transform_' + obj.__class__.__name__
|
|
transformer = getattr(self, method, self.transform_Object)
|
|
new_obj = transformer(obj, metadata)
|
|
if new_obj is not None and obj is not new_obj:
|
|
obj = new_obj
|
|
return obj
|
|
|
|
def transform_Object(self, obj, metadata):
|
|
"""Called if no explicit transform function exists for an Object."""
|
|
return obj
|
|
|
|
def generic_visit(self, obj):
|
|
return self.visit(self.visit_Object(obj))
|
|
|
|
def visit(self, obj):
|
|
"""Visit an Object."""
|
|
if not hasattr(self, 'visitors'):
|
|
self._visit_context = {}
|
|
self._visit_count = 0
|
|
try:
|
|
self._visit_count += 1
|
|
stack = deque()
|
|
stack.append((obj, None))
|
|
last_result = None
|
|
while stack:
|
|
try:
|
|
last, visited = stack[-1]
|
|
if isinstance(last, types.GeneratorType):
|
|
stack.append((last.send(last_result), None))
|
|
last_result = None
|
|
elif isinstance(last, Visited):
|
|
stack.pop()
|
|
last_result = last.result
|
|
elif isinstance(last, Object):
|
|
if last in self._visit_context:
|
|
if self._visit_context[last] == self.visit_Object:
|
|
visitor = self.visit_RecursionError
|
|
else:
|
|
visitor = self.visit_Object
|
|
else:
|
|
method = 'visit_' + last.__class__.__name__
|
|
visitor = getattr(self, method, self.visit_Object)
|
|
self._visit_context[last] = visitor
|
|
stack.pop()
|
|
stack.append((visitor(last), last))
|
|
else:
|
|
method = 'visit_' + last.__class__.__name__
|
|
visitor = getattr(self, method, self.visit_Generic)
|
|
stack.pop()
|
|
stack.append((visitor(last), None))
|
|
except StopIteration:
|
|
stack.pop()
|
|
if visited and visited in self._visit_context:
|
|
del self._visit_context[visited]
|
|
return last_result
|
|
finally:
|
|
self._visit_count -= 1
|
|
if self._visit_count <= 0:
|
|
self._visit_context = {}
|
|
|
|
def visit_RecursionError(self, obj):
|
|
raise VisitRecursionError
|
|
|
|
def visit_Object(self, obj):
|
|
"""Called if no explicit visitor function exists for an Object."""
|
|
yield obj.__dict__
|
|
yield Visited(obj)
|
|
|
|
def visit_Generic(self, obj):
|
|
"""Called if no explicit visitor function exists for an object."""
|
|
yield Visited(obj)
|
|
|
|
def visit_list(self, obj):
|
|
for item in obj:
|
|
yield item
|
|
yield Visited(obj)
|
|
|
|
visit_Array = visit_list
|
|
|
|
def visit_dict(self, obj):
|
|
for field, value in list(obj.items()):
|
|
if not field.startswith('_'):
|
|
yield value
|
|
yield Visited(obj)
|
|
|
|
|
|
class NodeVisitor(Visitor):
|
|
pass
|
|
|
|
|
|
class ReprVisitor(Visitor):
|
|
def visit(self, obj, indent=4, nl="\n", sp="", skip=()):
|
|
self.level = 0
|
|
if isinstance(indent, int):
|
|
indent = " " * indent
|
|
self.indent = indent
|
|
self.nl = nl
|
|
self.sp = sp
|
|
self.skip = skip
|
|
return super(ReprVisitor, self).visit(obj)
|
|
|
|
def visit_RecursionError(self, obj):
|
|
yield Visited("...")
|
|
|
|
def visit_Object(self, obj):
|
|
value_repr = yield obj.__dict__
|
|
yield Visited(value_repr)
|
|
|
|
def visit_Generic(self, obj):
|
|
yield Visited(repr(obj))
|
|
|
|
def visit_list(self, obj):
|
|
indent1 = self.indent * self.level
|
|
indent2 = indent1 + self.indent
|
|
self.level += 1
|
|
try:
|
|
items = []
|
|
for item in obj:
|
|
v = yield item
|
|
items.append(v)
|
|
if items:
|
|
value_repr = "[%s%s%s%s%s%s%s]" % (
|
|
self.sp,
|
|
self.nl,
|
|
indent2,
|
|
(",%s%s%s" % (self.nl, self.sp, indent2)).join(items),
|
|
self.nl,
|
|
indent1,
|
|
self.sp,
|
|
)
|
|
else:
|
|
value_repr = "[]"
|
|
finally:
|
|
self.level -= 1
|
|
|
|
yield Visited(value_repr)
|
|
|
|
visit_Array = visit_list
|
|
|
|
def visit_dict(self, obj):
|
|
indent1 = self.indent * self.level
|
|
indent2 = indent1 + self.indent
|
|
self.level += 1
|
|
try:
|
|
items = []
|
|
for k, item in obj.items():
|
|
if item is not None and not k.startswith('_') and k not in self.skip:
|
|
v = yield item
|
|
items.append("%s: %s" % (k, v))
|
|
if items:
|
|
value_repr = "{%s%s%s%s%s%s%s}" % (
|
|
self.sp,
|
|
self.nl,
|
|
indent2,
|
|
(",%s%s%s" % (self.nl, self.sp, indent2)).join(items),
|
|
self.nl,
|
|
indent1,
|
|
self.sp,
|
|
)
|
|
else:
|
|
value_repr = "{}"
|
|
finally:
|
|
self.level -= 1
|
|
|
|
yield Visited(value_repr)
|
|
|
|
if PY3:
|
|
def visit_str(self, obj):
|
|
value_repr = json.dumps(obj)
|
|
yield Visited(value_repr)
|
|
else:
|
|
def visit_unicode(self, obj):
|
|
value_repr = json.dumps(obj)
|
|
yield Visited(value_repr)
|
|
|
|
def visit_SourceLocation(self, obj):
|
|
old_indent, self.indent = self.indent, ""
|
|
old_nl, self.nl = self.nl, ""
|
|
old_sp, self.sp = self.sp, ""
|
|
try:
|
|
yield obj
|
|
finally:
|
|
self.indent = old_indent
|
|
self.nl = old_nl
|
|
self.sp = old_sp
|
|
|
|
|
|
class ToDictVisitor(Visitor):
|
|
map = {
|
|
'isAsync': 'async',
|
|
'allowAwait': 'await',
|
|
}
|
|
|
|
def visit_RecursionError(self, obj):
|
|
yield Visited({
|
|
'error': "Infinite recursion detected...",
|
|
})
|
|
|
|
def visit_Object(self, obj):
|
|
obj = yield obj.__dict__
|
|
yield Visited(obj)
|
|
|
|
def visit_list(self, obj):
|
|
items = []
|
|
for item in obj:
|
|
v = yield item
|
|
items.append(v)
|
|
yield Visited(items)
|
|
|
|
visit_Array = visit_list
|
|
|
|
def visit_dict(self, obj):
|
|
items = []
|
|
for k, item in obj.items():
|
|
if item is not None and not k.startswith('_'):
|
|
v = yield item
|
|
k = unicode(k)
|
|
items.append((self.map.get(k, k), v))
|
|
yield Visited(dict(items))
|
|
|
|
def visit_SRE_Pattern(self, obj):
|
|
yield Visited({})
|