296 lines
8.5 KiB
Python
296 lines
8.5 KiB
Python
|
# -*- coding: utf-8 -*-
|
||
|
def _name(node):
|
||
|
if node['type'] == 'Identifier':
|
||
|
return node['name']
|
||
|
if node['type'] == 'MemberExpression':
|
||
|
return "%s.%s" % (_name(node['object']), _name(node['property']))
|
||
|
raise ValueError("Unnamable node type %s" % node['type'])
|
||
|
|
||
|
def _value(node, strict=False):
|
||
|
t = node['type']
|
||
|
if t == 'Identifier':
|
||
|
return node['name']
|
||
|
elif t == 'Literal':
|
||
|
return node['value']
|
||
|
msg = '<%s has no value>' % _identify(node, {})
|
||
|
if strict:
|
||
|
raise ValueError(msg)
|
||
|
return msg
|
||
|
|
||
|
def _identify(valnode, ns):
|
||
|
if valnode is None:
|
||
|
return "None"
|
||
|
# already identified and re-set in the ns?
|
||
|
if isinstance(valnode, Printable):
|
||
|
return valnode
|
||
|
|
||
|
# check other non-empty returns
|
||
|
t = valnode['type']
|
||
|
if t == "Literal":
|
||
|
if valnode.get('regex'):
|
||
|
return valnode['regex']['pattern']
|
||
|
return valnode['value']
|
||
|
elif t == "Identifier":
|
||
|
n = valnode['name']
|
||
|
return ns.get(n, Global(n))
|
||
|
elif t == "NewExpression":
|
||
|
return Instance(_identify(valnode['callee'], ns))
|
||
|
elif t == "ObjectExpression":
|
||
|
return Namespace({
|
||
|
_value(prop['key']): _identify(prop['value'], ns)
|
||
|
for prop in valnode['properties']
|
||
|
})
|
||
|
elif t == "MemberExpression":
|
||
|
return Deref(
|
||
|
_identify(valnode['object'], ns),
|
||
|
_identify(valnode['property'], ns) if valnode['computed'] else valnode['property']['name'],
|
||
|
)
|
||
|
elif t == "CallExpression":
|
||
|
return Call(
|
||
|
_identify(valnode['callee'], ns),
|
||
|
[_identify(arg, ns) for arg in valnode['arguments']],
|
||
|
)
|
||
|
elif t == 'BinaryExpression':
|
||
|
return "%s %s %s" % (
|
||
|
_identify(valnode['left'], ns),
|
||
|
valnode['operator'],
|
||
|
_identify(valnode['right'], ns),
|
||
|
)
|
||
|
else:
|
||
|
return t
|
||
|
|
||
|
class Counter(object):
|
||
|
__slots__ = ['_number']
|
||
|
def __init__(self):
|
||
|
self._number = 0
|
||
|
|
||
|
def __bool__(self):
|
||
|
return bool(self._number)
|
||
|
__nonzero__ = __bool__
|
||
|
def __int__(self):
|
||
|
return self._number
|
||
|
|
||
|
def get(self):
|
||
|
return self._number
|
||
|
def increment(self):
|
||
|
self._number += 1
|
||
|
return self._number
|
||
|
def decrement(self):
|
||
|
self._number -= 1
|
||
|
return self._number
|
||
|
|
||
|
def __enter__(self):
|
||
|
self._number += 1
|
||
|
return self._number
|
||
|
def __exit__(self, *args):
|
||
|
self._number -= 1
|
||
|
|
||
|
class Printable(object):
|
||
|
__slots__ = []
|
||
|
depth = Counter()
|
||
|
def __repr__(self):
|
||
|
with self.depth as i:
|
||
|
if i > 2:
|
||
|
return "%s(...)" % type(self).__name__
|
||
|
return "%s(%s)" % (type(self).__name__, ', '.join(
|
||
|
"%s=%r" % (k, getattr(self, k))
|
||
|
for k in self.__slots__
|
||
|
if not k.startswith('_')
|
||
|
if getattr(self, k)
|
||
|
))
|
||
|
|
||
|
|
||
|
def resolve(obj, store):
|
||
|
if obj is None:
|
||
|
return None
|
||
|
|
||
|
if isinstance(obj, (type(u''), bool, int, float)):
|
||
|
return obj
|
||
|
|
||
|
if isinstance(obj, ModuleProxy):
|
||
|
return resolve(obj.get(), store)
|
||
|
|
||
|
try:
|
||
|
if getattr(obj, 'resolved', False):
|
||
|
return obj
|
||
|
return obj.resolve(store)
|
||
|
except AttributeError:
|
||
|
raise TypeError("Unresolvable {!r}".format(obj))
|
||
|
|
||
|
class Resolvable(Printable):
|
||
|
"""
|
||
|
For types resolved in place
|
||
|
"""
|
||
|
__slots__ = ['resolved']
|
||
|
|
||
|
def __init__(self):
|
||
|
super(Resolvable, self).__init__()
|
||
|
self.resolved = False
|
||
|
def resolve(self, store):
|
||
|
self.resolved = True
|
||
|
return self
|
||
|
|
||
|
class Namespace(Resolvable):
|
||
|
__slots__ = ['attrs']
|
||
|
def __init__(self, attrs):
|
||
|
super(Namespace, self).__init__()
|
||
|
self.attrs = attrs
|
||
|
def resolve(self, store):
|
||
|
r = super(Namespace, self).resolve(store)
|
||
|
self.attrs = {
|
||
|
k: resolve(v, store)
|
||
|
for k, v in self.attrs.items()
|
||
|
}
|
||
|
return r
|
||
|
def __getitem__(self, key):
|
||
|
assert isinstance(key, type(u'')), "%r is not a namespace key" % key
|
||
|
try:
|
||
|
return self.attrs[key]
|
||
|
except KeyError:
|
||
|
return Global(self)[key]
|
||
|
|
||
|
class Module(Resolvable):
|
||
|
__slots__ = ('name', 'comments', 'exports', 'augments', 'dependencies', '_store')
|
||
|
def __init__(self, name, store, comments=()):
|
||
|
super(Module, self).__init__()
|
||
|
self.name = name
|
||
|
self._store = store
|
||
|
self.comments = tuple(comments)
|
||
|
self.exports = None
|
||
|
self.augments = []
|
||
|
self.dependencies = set()
|
||
|
store[name] = self
|
||
|
def add_dependency(self, depname):
|
||
|
dep = ModuleProxy(depname, self._store)
|
||
|
self.dependencies.add(dep)
|
||
|
return dep
|
||
|
def get(self):
|
||
|
return self
|
||
|
def resolve(self, store=None):
|
||
|
r = super(Module, self).resolve(store)
|
||
|
self.exports = resolve(self.exports, self._store)
|
||
|
self.augments = [resolve(a, self._store) for a in self.augments]
|
||
|
self.dependencies = {
|
||
|
resolve(d, self._store)
|
||
|
for d in self.dependencies
|
||
|
}
|
||
|
return r
|
||
|
def __getitem__(self, k):
|
||
|
try:
|
||
|
return self.exports[k]
|
||
|
except KeyError:
|
||
|
return Global(self)[k]
|
||
|
|
||
|
class ModuleProxy(object):
|
||
|
def __init__(self, name, store):
|
||
|
self.name = name
|
||
|
self.store = store
|
||
|
def get(self):
|
||
|
# web.web_client is not an actual module
|
||
|
return self.store.get(self.name, '<unstored %s>' % self.name)
|
||
|
def __repr__(self):
|
||
|
return repr(self.get())
|
||
|
def __hash__(self):
|
||
|
return hash(self.name)
|
||
|
def __eq__(self, other):
|
||
|
return self.name == other.name
|
||
|
|
||
|
class Class(Resolvable):
|
||
|
__slots__ = ['name', 'members', 'extends', 'mixins', 'comments']
|
||
|
def __init__(self, name, extends):
|
||
|
super(Class, self).__init__()
|
||
|
self.name = name
|
||
|
self.extends = extends
|
||
|
self.members = {}
|
||
|
self.mixins = []
|
||
|
self.comments = []
|
||
|
|
||
|
def resolve(self, store):
|
||
|
r = super(Class, self).resolve(store)
|
||
|
self.extends = resolve(self.extends, store)
|
||
|
self.mixins = [resolve(m, store) for m in self.mixins]
|
||
|
return r
|
||
|
|
||
|
class Instance(Resolvable):
|
||
|
__slots__ = ['type']
|
||
|
def __init__(self, type):
|
||
|
super(Instance, self).__init__()
|
||
|
self.type = type
|
||
|
|
||
|
def resolve(self, store):
|
||
|
r = super(Instance, self).resolve(store)
|
||
|
self.type = resolve(self.type, store)
|
||
|
if isinstance(self.type, Module):
|
||
|
self.type = self.type.exports
|
||
|
return r
|
||
|
|
||
|
def __getitem__(self, key):
|
||
|
return Global(self)['key']
|
||
|
|
||
|
class Function(Resolvable):
|
||
|
__slots__ = ['comments']
|
||
|
def __init__(self, comments):
|
||
|
super(Function, self).__init__()
|
||
|
self.comments = comments
|
||
|
|
||
|
class Deref(Printable):
|
||
|
__slots__ = ['object', 'property']
|
||
|
def __init__(self, object, property):
|
||
|
self.object = object
|
||
|
self.property = property
|
||
|
def resolve(self, store):
|
||
|
return resolve(self.object, store)[self.property]
|
||
|
|
||
|
class Call(Resolvable):
|
||
|
__slots__ = ['callable']
|
||
|
def __init__(self, callable, arguments):
|
||
|
super(Call, self).__init__()
|
||
|
self.callable = callable
|
||
|
def resolve(self, store):
|
||
|
r = super(Call, self).resolve(store)
|
||
|
self.callable = resolve(self.callable, store)
|
||
|
return r
|
||
|
def __getitem__(self, item):
|
||
|
return Global(self)[item]
|
||
|
|
||
|
def get(node, it, *path):
|
||
|
if not path:
|
||
|
return node[it]
|
||
|
return get(node[it], *path)
|
||
|
|
||
|
|
||
|
def match(node, pattern):
|
||
|
"""Checks that ``pattern`` is a subset of ``node``.
|
||
|
|
||
|
If a sub-pattern is a callable it will be called with the current
|
||
|
sub-node and its result is the match's. Other sub-patterns are
|
||
|
checked by equality against the correspoding sub-node.
|
||
|
|
||
|
Can be used to quickly check the descendants of a node of suitable
|
||
|
type.
|
||
|
|
||
|
TODO: support checking list content? Would be convenient to
|
||
|
constraint e.g. CallExpression based on their parameters
|
||
|
|
||
|
"""
|
||
|
if node is None and pattern is not None:
|
||
|
return
|
||
|
if callable(pattern):
|
||
|
return pattern(node)
|
||
|
if isinstance(node, list):
|
||
|
return all(len(node) > i and match(node[i], v) for i, v in pattern.items())
|
||
|
if isinstance(pattern, dict):
|
||
|
return all(match(node.get(k), v) for k, v in pattern.items())
|
||
|
return node == pattern
|
||
|
|
||
|
|
||
|
class Global(object):
|
||
|
def __init__(self, name):
|
||
|
self.name = name
|
||
|
def __repr__(self):
|
||
|
return '<external %s>' % self.name
|
||
|
def __getitem__(self, name):
|
||
|
return Global('%s.%s' % (self.name, name))
|
||
|
def resolve(self, store):
|
||
|
return self
|