فهرست منبع

Pick the best possibiltity using the shortest path with breadth first traversal.

Sander Mathijs van Veen 13 سال پیش
والد
کامیت
d67e91b7da
4فایلهای تغییر یافته به همراه148 افزوده شده و 30 حذف شده
  1. 9 6
      src/parser.py
  2. 123 15
      src/possibilities.py
  3. 1 7
      src/strategy.py
  4. 15 2
      src/validation.py

+ 9 - 6
src/parser.py

@@ -22,7 +22,7 @@ from node import ExpressionNode as Node, \
 from rules.utils import find_variable
 from rules.precedences import IMPLICIT_RULES
 from strategy import find_possibilities
-from possibilities import apply_suggestion
+from possibilities import apply_suggestion, pick_best_possibility
 
 import Queue
 import re
@@ -258,7 +258,7 @@ class Parser(BisonParser):
 
         if self.interactive:
             if self.possibilities:
-                print self.possibilities[0]
+                print pick_best_possibility(self, self.possibilities)
             else:
                 print 'No further reduction is possible.'
 
@@ -268,13 +268,16 @@ class Parser(BisonParser):
         for i, p in enumerate(self.possibilities):
             print '%d %s' % (i, p)
 
-    def rewrite(self, index=0, verbose=False, check_implicit=True):
+    def rewrite(self, index=None, verbose=False, check_implicit=True):
         self.find_possibilities()
 
         if not self.possibilities:
             return
 
-        suggestion = self.possibilities[index]
+        if index is None:
+            suggestion = pick_best_possibility(self, self.possibilities)
+        else:
+            suggestion = self.possibilities[index]
 
         if self.verbose:
             print 'EXPLICIT:', suggestion
@@ -327,10 +330,10 @@ class Parser(BisonParser):
 
         return self.root_node
 
-    def rewrite_all(self, verbose=False):
+    def rewrite_all(self, index=None, verbose=False):
         i = 0
 
-        while self.rewrite(verbose=verbose):
+        while self.rewrite(index=index, verbose=verbose):
             i += 1
 
             if i > 100:

+ 123 - 15
src/possibilities.py

@@ -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]

+ 1 - 7
src/strategy.py

@@ -87,16 +87,10 @@ def depth_possibilities(node, depth=0, parent_op=None):
 
 def find_possibilities(node):
     """
-    Find all possibilities inside a node and return them in a list.
+    Find all possibilities inside a node and return them as a list.
     """
     possibilities = depth_possibilities(node)
-    #import copy
-    #old_possibilities = copy.deepcopy(possibilities)
     possibilities.sort(compare_possibilities)
-    #get_handler = lambda (p, d): str(p.handler)
-    #if old_possibilities != possibilities:
-    #    print 'before:', '\n    '.join(map(get_handler, old_possibilities))
-    #    print 'after:', '\n    '.join(map(get_handler, possibilities))
 
     return [p for p, depth in possibilities]
 

+ 15 - 2
src/validation.py

@@ -1,5 +1,5 @@
 from parser import Parser
-from possibilities import apply_suggestion
+from possibilities import apply_suggestion, BEST_POSSIBILITY_CACHE
 from strategy import find_possibilities
 from tests.parser import ParserWrapper
 
@@ -16,7 +16,20 @@ def validate(exp, result):
         if node.equals(result):
             return True
 
-        for p in find_possibilities(node):
+        node_expr = str(node)
+
+        if node_expr in BEST_POSSIBILITY_CACHE:
+            possibility_index = BEST_POSSIBILITY_CACHE[node_expr]
+
+            # If there is no possible rewrite step, bail out
+            if possibility_index is None:
+                return False
+
+            possibilities = [possibility_index]
+        else:
+            possibilities = find_possibilities(node)
+
+        for p in possibilities:
             # Clone the root node because it will be used in multiple
             # substitutions
             child = apply_suggestion(node.clone(), p)