|
|
@@ -1,4 +1,4 @@
|
|
|
-from node import TYPE_OPERATOR
|
|
|
+from node import TYPE_OPERATOR, OP_NEG
|
|
|
|
|
|
|
|
|
# Each rule will append its hint message to the following dictionary. The
|
|
|
@@ -7,6 +7,12 @@ from node import TYPE_OPERATOR
|
|
|
# message. The string will be processed using string.format().
|
|
|
MESSAGES = {}
|
|
|
|
|
|
+# Dictionary used to map a stringified expression tree to the index of the
|
|
|
+# best / shortest possibility.
|
|
|
+BEST_POSSIBILITY_CACHE = {}
|
|
|
+
|
|
|
+MAX_TREE_DEPTH = 20
|
|
|
+
|
|
|
|
|
|
class Possibility(object):
|
|
|
def __init__(self, root, handler, args=()):
|
|
|
@@ -39,6 +45,36 @@ class Possibility(object):
|
|
|
and self.args == other.args
|
|
|
|
|
|
|
|
|
+def find_possibilities(node, parent_op=None):
|
|
|
+ possibilities = []
|
|
|
+ handlers = []
|
|
|
+
|
|
|
+ from rules import RULES
|
|
|
+
|
|
|
+ if not node.is_leaf:
|
|
|
+ # Traverse through child nodes first using postorder traversal
|
|
|
+ for child in node:
|
|
|
+ possibilities += find_possibilities(child, node.op)
|
|
|
+
|
|
|
+ # Add operator-specific handlers. Prevent duplicate possibilities in
|
|
|
+ # n-ary nodes by only executing the handlers on the outermost node of
|
|
|
+ # related nodes with the same operator
|
|
|
+ #if node.op in RULES and (node.op != parent_op \
|
|
|
+ # or node.op not in NARY_OPERATORS):
|
|
|
+ # handlers += RULES[node.op]
|
|
|
+ if node.op in RULES:
|
|
|
+ handlers += RULES[node.op]
|
|
|
+
|
|
|
+ if node.negated:
|
|
|
+ handlers += RULES[OP_NEG]
|
|
|
+
|
|
|
+ # Run handlers
|
|
|
+ for handler in handlers:
|
|
|
+ possibilities += handler(node)
|
|
|
+
|
|
|
+ return possibilities
|
|
|
+
|
|
|
+
|
|
|
def find_parent_node(root, child):
|
|
|
nodes = [root]
|
|
|
|
|
|
@@ -59,25 +95,97 @@ def find_parent_node(root, child):
|
|
|
|
|
|
|
|
|
def apply_suggestion(root, suggestion):
|
|
|
- # TODO: clone the root node before modifying. After deep copying the root
|
|
|
- # node, the subtree_map cannot be used since the hash() of each node in the
|
|
|
- # deep copied root node has changed.
|
|
|
- #root = root.clone()
|
|
|
-
|
|
|
+ # Apply the suggestion on the sub tree root with the given arguments.
|
|
|
subtree = suggestion.handler(suggestion.root, suggestion.args)
|
|
|
- parent_node = find_parent_node(root, suggestion.root)
|
|
|
|
|
|
- # There is either a parent node or the subtree is the root node.
|
|
|
- # FIXME: FAIL: test_diagnostic_test_application in tests/test_b1_ch08.py
|
|
|
- #try:
|
|
|
- # assert bool(parent_node) != (subtree == root)
|
|
|
- #except:
|
|
|
- # print 'parent_node: %s' % (str(parent_node))
|
|
|
- # print 'subtree: %s == %s' % (str(subtree), str(root))
|
|
|
- # raise
|
|
|
+ # Find the parent node of the sub tree and, if it has a parent node,
|
|
|
+ # substitute the new sub tree into the parent's list of child nodes.
|
|
|
+ parent_node = find_parent_node(root, suggestion.root)
|
|
|
|
|
|
if parent_node:
|
|
|
parent_node.substitute(suggestion.root, subtree)
|
|
|
return root
|
|
|
|
|
|
+ # Apparently the whole tree is updated. Return the new tree instead of the
|
|
|
+ # old root node
|
|
|
return subtree
|
|
|
+
|
|
|
+
|
|
|
+def pick_best_possibility(parser, possibilities):
|
|
|
+ root = parser.root_node
|
|
|
+
|
|
|
+ #print 'pick_best_possibility for:', root
|
|
|
+
|
|
|
+ # Get the final expression
|
|
|
+ parser.set_root_node(root.clone())
|
|
|
+ result = parser.rewrite_all(index=0)
|
|
|
+
|
|
|
+ def traverse_breadth_first(node, result, depth=0):
|
|
|
+ if depth > MAX_TREE_DEPTH:
|
|
|
+ return
|
|
|
+
|
|
|
+ node_expr = str(node)
|
|
|
+
|
|
|
+ if node_expr in BEST_POSSIBILITY_CACHE:
|
|
|
+ return BEST_POSSIBILITY_CACHE[node_expr]
|
|
|
+
|
|
|
+ # If the nodes match, there is no rewrite step needed. If the nodes to
|
|
|
+ # not converge at all, the step_index will be None as well (since there
|
|
|
+ # still is no rewrite step to do).
|
|
|
+ step_index = None
|
|
|
+
|
|
|
+ #print ' ' * depth + 'node:', node, 'result:', result, 'equal:', \
|
|
|
+ # node.equals(result)
|
|
|
+
|
|
|
+ if not node.equals(result):
|
|
|
+ children = []
|
|
|
+ possibilities = find_possibilities(node)
|
|
|
+
|
|
|
+ #print ' ' * depth + (' ' * depth + '\n').join(map(str,
|
|
|
+ # enumerate(possibilities)))
|
|
|
+
|
|
|
+ for p, possibility in enumerate(possibilities):
|
|
|
+ #print ' ' * depth + 'possibility:', possibility, 'on:', node
|
|
|
+
|
|
|
+ # Clone the root node because it will be used in multiple
|
|
|
+ # substitutions
|
|
|
+ child = apply_suggestion(node.clone(), possibility)
|
|
|
+
|
|
|
+ #print ' ' * depth + 'child:', child
|
|
|
+
|
|
|
+ if child.equals(result):
|
|
|
+ step_index = p
|
|
|
+ break
|
|
|
+
|
|
|
+ children.append(child)
|
|
|
+
|
|
|
+ # If the final expression is not found in the direct children,
|
|
|
+ # start searching in the children of the children.
|
|
|
+ if step_index is None:
|
|
|
+ for c, child in enumerate(children):
|
|
|
+ child_step = traverse_breadth_first(child, result, depth + 1)
|
|
|
+
|
|
|
+ #print ' ' * depth + 'child_step:', child_step
|
|
|
+
|
|
|
+ if child_step is not None:
|
|
|
+ step_index = c
|
|
|
+ break
|
|
|
+
|
|
|
+ BEST_POSSIBILITY_CACHE[node_expr] = step_index
|
|
|
+
|
|
|
+ return step_index
|
|
|
+
|
|
|
+ #print '--- start traversal ---'
|
|
|
+
|
|
|
+ step_index = traverse_breadth_first(root, result)
|
|
|
+
|
|
|
+ #print 'step_index:', step_index
|
|
|
+
|
|
|
+ #print '--- cache: ---'
|
|
|
+ #print BEST_POSSIBILITY_CACHE
|
|
|
+
|
|
|
+ # Reset the parser's original state
|
|
|
+ parser.set_root_node(root)
|
|
|
+ parser.find_possibilities()
|
|
|
+
|
|
|
+ return parser.possibilities[step_index]
|