Enhanced PythonEvaluator to accept concepts
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
from core.builtin_concepts import BuiltinConcepts, ListConcept
|
||||
from core.concept import Concept
|
||||
import ast
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NodeParent:
|
||||
"""
|
||||
Class that represent the ancestor of a Node
|
||||
For example, the 'For' nodes has three fields (target, iter and body)
|
||||
So, for a node under For.iter
|
||||
node -> For
|
||||
field -> iter
|
||||
"""
|
||||
|
||||
def __init__(self, node, field):
|
||||
self.node = node
|
||||
self.field = field
|
||||
|
||||
def __repr__(self):
|
||||
if self.node is None:
|
||||
return None
|
||||
|
||||
if self.field is None:
|
||||
return self.node.get_node_type()
|
||||
|
||||
return self.node.get_node_type() + "." + self.field
|
||||
|
||||
def __eq__(self, other):
|
||||
# I can compare with type for simplification
|
||||
if isinstance(other, tuple):
|
||||
return self.node.get_node_type() == other[0] and self.field == other[1]
|
||||
|
||||
# normal equals implementation
|
||||
if not isinstance(other, NodeParent):
|
||||
return False
|
||||
|
||||
return self.node.get_node_type() == other.node.get_node_type() and self.field == other.field
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.node.get_node_type(), self.field))
|
||||
|
||||
|
||||
class NodeConcept(Concept):
|
||||
def __init__(self, key, parent: NodeParent):
|
||||
super().__init__(key, True, False, key)
|
||||
self.parent = parent
|
||||
|
||||
def get_node_type(self):
|
||||
return self.key
|
||||
|
||||
|
||||
class GenericNodeConcept(NodeConcept):
|
||||
def __init__(self, node_type, parent):
|
||||
super().__init__(BuiltinConcepts.GENERIC_NODE, parent)
|
||||
self.node_type = node_type
|
||||
|
||||
def __repr__(self):
|
||||
return "Generic:" + self.node_type
|
||||
|
||||
def get_node_type(self):
|
||||
return self.node_type
|
||||
|
||||
def get_value(self):
|
||||
if self.node_type == "Name":
|
||||
return self.get_prop("id")
|
||||
|
||||
if self.node_type == "arg":
|
||||
return self.get_prop("arg")
|
||||
|
||||
return self.body
|
||||
|
||||
|
||||
class IdentifierConcept(NodeConcept):
|
||||
def __init__(self, parent, name):
|
||||
super().__init__(BuiltinConcepts.IDENTIFIER_NODE, parent)
|
||||
self.body = name
|
||||
|
||||
|
||||
def transform(node):
|
||||
"""
|
||||
Transform Python AST node into concept nodes
|
||||
for better usage
|
||||
:param node:
|
||||
:return:
|
||||
"""
|
||||
|
||||
def _transform(node, parent):
|
||||
node_type = node.__class__.__name__
|
||||
concept = GenericNodeConcept(node_type, parent).init_key()
|
||||
for field in node._fields:
|
||||
if not hasattr(node, field):
|
||||
continue
|
||||
|
||||
value = getattr(node, field)
|
||||
if isinstance(value, list):
|
||||
lst = ListConcept().init_key()
|
||||
for i in value:
|
||||
lst.append(_transform(i, NodeParent(concept, field)))
|
||||
concept.set_prop(field, lst)
|
||||
elif isinstance(value, ast.AST):
|
||||
concept.set_prop(field, _transform(value, NodeParent(concept, field)))
|
||||
else:
|
||||
concept.set_prop(field, value)
|
||||
return concept
|
||||
|
||||
return _transform(node, None)
|
||||
@@ -0,0 +1,122 @@
|
||||
from core.ast.nodes import GenericNodeConcept, NodeConcept
|
||||
from core.builtin_concepts import ListConcept
|
||||
|
||||
|
||||
class ConceptNodeVisitor:
|
||||
"""
|
||||
Base class to visit NodeConcept
|
||||
It is insolently inspired by python AST.Visitor class
|
||||
"""
|
||||
|
||||
def visit(self, node):
|
||||
|
||||
"""Visit a node."""
|
||||
name = node.node_type if isinstance(node, GenericNodeConcept) else node.name
|
||||
name = str(name).capitalize()
|
||||
|
||||
method = 'visit_' + name
|
||||
visitor = getattr(self, method, self.generic_visit)
|
||||
return visitor(node)
|
||||
|
||||
def generic_visit(self, node):
|
||||
"""Called if no explicit visitor function exists for a node."""
|
||||
for field, value in iter_props(node):
|
||||
if isinstance(value, ListConcept):
|
||||
for item in value:
|
||||
if isinstance(item, NodeConcept):
|
||||
self.visit(item)
|
||||
elif isinstance(value, NodeConcept):
|
||||
self.visit(value)
|
||||
|
||||
def visit_Constant(self, node):
|
||||
value = node.get_prop("value")
|
||||
type_name = _const_node_type_names.get(type(value))
|
||||
if type_name is None:
|
||||
for cls, name in _const_node_type_names.items():
|
||||
if isinstance(value, cls):
|
||||
type_name = name
|
||||
break
|
||||
if type_name is not None:
|
||||
method = 'visit_' + type_name
|
||||
try:
|
||||
visitor = getattr(self, method)
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
import warnings
|
||||
warnings.warn(f"{method} is deprecated; add visit_Constant",
|
||||
PendingDeprecationWarning, 2)
|
||||
return visitor(node)
|
||||
return self.generic_visit(node)
|
||||
|
||||
|
||||
class UnreferencedNamesVisitor(ConceptNodeVisitor):
|
||||
def __init__(self, sheerka):
|
||||
self.names = set()
|
||||
self.sheerka = sheerka
|
||||
|
||||
def visit_Name(self, node):
|
||||
parents = get_parents(node)
|
||||
if ("For", "target") in parents: # variable used by the 'for' iteration
|
||||
return
|
||||
|
||||
if ("Call", "func") in parents: # name of the function
|
||||
return
|
||||
|
||||
if ("Assign", "targets") in parents: # variable which is assigned
|
||||
return
|
||||
|
||||
if self.can_be_discarded(self.sheerka.value(node), parents):
|
||||
return
|
||||
|
||||
self.names.add(self.sheerka.value(node))
|
||||
|
||||
def can_be_discarded(self, variable_name, parents):
|
||||
|
||||
for node in (parent.node for parent in parents):
|
||||
if node is None:
|
||||
return False
|
||||
|
||||
if node.get_node_type() == "For" and self.sheerka.value(node.get_prop("target")) == variable_name:
|
||||
# variable used by the loop
|
||||
return True
|
||||
|
||||
if node.get_node_type() == "FunctionDef":
|
||||
# variable defined as a function parameter
|
||||
args = node.get_prop("args")
|
||||
args_values = list(self.sheerka.values(args.get_prop("args")))
|
||||
if variable_name in args_values:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def get_parents(node):
|
||||
if node.parent is None:
|
||||
return []
|
||||
|
||||
res = []
|
||||
while True:
|
||||
if node.parent is None:
|
||||
break
|
||||
res.append(node.parent)
|
||||
node = node.parent.node
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def iter_props(node):
|
||||
for p in node.props:
|
||||
yield p, node.props[p].value
|
||||
|
||||
|
||||
_const_node_type_names = {
|
||||
bool: 'NameConstant', # should be before int
|
||||
type(None): 'NameConstant',
|
||||
int: 'Num',
|
||||
float: 'Num',
|
||||
complex: 'Num',
|
||||
str: 'Str',
|
||||
bytes: 'Bytes',
|
||||
type(...): 'Ellipsis',
|
||||
}
|
||||
Reference in New Issue
Block a user