瀏覽代碼

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)