Explorar o código

Implemented negate_group and fixed subtree_map bug

Sander Mathijs van Veen %!s(int64=14) %!d(string=hai) anos
pai
achega
90e5ed83e8

+ 4 - 0
src/node.py

@@ -272,6 +272,10 @@ class ExpressionNode(Node, ExpressionBase):
           non-strictly equal.
         """
         if not other.is_op(self.op):
+            # FIXME: this is if-clause is a problem. To fix this problem
+            # permanently, normalize ("x * -1" -> "-1x") before comparing to
+            # the other node.
+
             return False
 
         if self.op in (OP_ADD, OP_MUL):

+ 28 - 8
src/parser.py

@@ -181,15 +181,20 @@ class Parser(BisonParser):
 
     def hook_handler(self, target, option, names, values, retval):
         if target in ['exp', 'line', 'input'] or not retval \
-                or retval.type != TYPE_OPERATOR or retval.op not in RULES:
+                or retval.type != TYPE_OPERATOR:
             return retval
 
-        # Update the subtree map to let the subtree point to its parent node.
-        parent_nodes = self.subtree_map.keys()
+        if self.subtree_map:
+            # Update the subtree map to let the subtree point to its parent
+            # node.
+            parent_nodes = self.subtree_map.keys()
 
-        for child in retval:
-            if child in parent_nodes:
-                self.subtree_map[child] = retval
+            for child in retval:
+                if child in parent_nodes:
+                    self.subtree_map[child] = retval
+
+        if retval.op not in RULES:
+            return retval
 
         for handler in RULES[retval.op]:
             possibilities = handler(retval)
@@ -213,12 +218,20 @@ class Parser(BisonParser):
     def rewrite(self):
         suggestion = pick_suggestion(self.last_possibilities)
 
+        if self.verbose:
+            print 'applying suggestion:', suggestion
+
         if not suggestion:
             return self.root_node
 
         expression = apply_suggestion(self.root_node, self.subtree_map,
                                     suggestion)
+
+        if self.verbose:
+            print 'After application, expression=', expression
+
         self.read_queue.put_nowait(str(expression))
+
         return expression
 
     #def hook_run(self, filename, retval):
@@ -266,6 +279,7 @@ class Parser(BisonParser):
             return values[0]
 
         if option == 2:  # rule: DEBUG NEWLINE
+            self.root_node = values[0]
             return values[0]
 
         if option == 3:  # rule: HINT NEWLINE
@@ -277,7 +291,8 @@ class Parser(BisonParser):
             return
 
         if option == 5:  # rule: REWRITE NEWLINE
-            return self.rewrite()
+            self.root_node = self.rewrite()
+            return self.root_node
 
         if option == 6:
             raise RuntimeError('on_line: exception raised')
@@ -346,7 +361,12 @@ class Parser(BisonParser):
             return Node(values[1], values[0], values[2])
 
         if option == 4:  # rule: exp MINUS exp
-            return Node('+', values[0], Node('-', values[2]))
+            # It is necessary to call the hook_handler here explicitly, since
+            # the minus operator is internally represented as two nodes (unary
+            # negation and binary plus).
+            node = Node('-', values[2])
+            node = self.hook_handler(target, option, names, values, node)
+            return Node('+', values[0], node)
 
         raise BisonSyntaxError('Unsupported option %d in target "%s".'
                                % (option, target))  # pragma: nocover

+ 3 - 3
src/possibilities.py

@@ -61,9 +61,9 @@ def pick_suggestion(possibilities):
 
 
 def apply_suggestion(root, subtree_map, suggestion):
-    # 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.
+    # 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_clone = root.clone()
 
     subtree = suggestion.handler(suggestion.root, suggestion.args)

+ 3 - 2
src/rules/__init__.py

@@ -1,4 +1,4 @@
-from ..node import OP_ADD, OP_MUL, OP_DIV, OP_POW
+from ..node import OP_ADD, OP_MUL, OP_DIV, OP_POW, OP_NEG
 from .poly import match_combine_polynomes
 from .groups import match_combine_groups
 from .factors import match_expand
@@ -9,7 +9,7 @@ from .powers import match_add_exponents, match_subtract_exponents, \
 from .numerics import match_divide_numerics, match_multiply_numerics
 from .fractions import match_constant_division, match_add_constant_fractions, \
         match_expand_and_add_fractions
-
+from .negation import match_negate_group
 
 RULES = {
         OP_ADD: [match_add_constant_fractions, match_combine_polynomes, \
@@ -21,4 +21,5 @@ RULES = {
         OP_POW: [match_multiply_exponents, match_duplicate_exponent, \
                  match_remove_negative_exponent, match_exponent_to_root, \
                  match_extend_exponent],
+        OP_NEG: [match_negate_group],
         }

+ 4 - 3
src/rules/groups.py

@@ -1,7 +1,7 @@
 from itertools import combinations
 
 from ..node import ExpressionNode as Node, ExpressionLeaf as Leaf, Scope, \
-        OP_ADD, OP_MUL
+        OP_ADD, OP_MUL, OP_NEG
 from ..possibilities import Possibility as P, MESSAGES
 from ..translate import _
 
@@ -34,7 +34,8 @@ def match_combine_groups(node):
             l = len(scope)
 
             for i, sub_node in enumerate(scope):
-                if sub_node.is_numeric():
+                if sub_node.is_numeric() or (sub_node.is_op(OP_NEG)
+                                             and sub_node[0].is_numeric()):
                     others = [scope[j] for j in range(i) + range(i + 1, l)]
 
                     if len(others) == 1:
@@ -56,7 +57,7 @@ def combine_groups(root, args):
 
     scope = Scope(root)
 
-    if not isinstance(c0, Leaf):
+    if not isinstance(c0, Leaf) and not isinstance(c0, Node):
         c0 = Leaf(c0)
 
     # Replace the left node with the new expression

+ 88 - 0
src/rules/negation.py

@@ -0,0 +1,88 @@
+from ..node import OP_NEG, OP_ADD, OP_MUL, get_scope, nary_node
+from ..possibilities import Possibility as P, MESSAGES
+from ..translate import _
+
+
+def match_negate_group(node):
+    """
+    --a                 ->  a
+    --ab                ->  ab
+    -(-ab + c)          ->  --ab - c
+    -(a + b + ... + z)  ->  -a + -b + ... + -z
+    """
+    assert node.is_op(OP_NEG)
+
+    val = node[0]
+
+    if val.is_op(OP_NEG):
+        # --a
+        return [P(node, double_negation, (node,))]
+
+    if not val.is_leaf():
+        scope = get_scope(val)
+
+        if not any(map(lambda n: n.is_op(OP_NEG), scope)):
+            return []
+
+        if val.is_op(OP_MUL):
+            # --ab
+            return [P(node, negate_polynome, (node, scope))]
+
+        elif val.is_op(OP_ADD):
+            # -(ab + c)   ->  -ab - c
+            # -(-ab + c)  ->  ab - c
+            return [P(node, negate_group, (node, scope))]
+
+    return []
+
+
+def negate_polynome(root, args):
+    """
+    # -a * -3c  ->  a * 3c
+    --a * 3c  ->  a * 3c
+    --ab      ->  ab
+    --abc     ->  abc
+    """
+    node, scope = args
+
+    for i, n in enumerate(scope):
+        # XXX: validate this property!
+        if n.is_op(OP_NEG):
+            scope[i] = n[0]
+            return nary_node('*', scope)
+
+    raise RuntimeError('No negation node found in scope.')
+
+
+MESSAGES[negate_polynome] = _('Apply negation to the polynome {1[0]}.')
+
+
+def negate_group(root, args):
+    """
+    -(-ab + ... + c)  ->  --ab + ... + -c
+    """
+    node, scope = args
+
+    print 'negate_group:', node, map(str, scope)
+
+    # Negate each group
+    for i, n in enumerate(scope):
+        scope[i] = -n
+
+    print nary_node('+', scope)
+    return nary_node('+', scope)
+
+
+MESSAGES[negate_group] = _('Apply negation to the subexpression {1[0]}.')
+
+
+def double_negation(root, args):
+    """
+    --a  ->  a
+    """
+    node = args[0]
+
+    return node[0][0]
+
+
+MESSAGES[double_negation] = _('Remove double negation in {1}.')

+ 0 - 1
src/rules/numerics.py

@@ -151,7 +151,6 @@ def multiply_numerics(root, args):
         if hash(n) == hash(n0):
             # Replace the left node with the new expression
             scope.append(substitution)
-            #scope.append(n)
         elif hash(n) != hash(n1):
             # Remove the right node
             scope.append(n)

+ 10 - 2
tests/rulestestcase.py

@@ -46,6 +46,14 @@ class RulesTestCase(unittest.TestCase):
                 self.assertMultiLineEqual(str(rewrite(exp)),
                                           str(rewrite_chain[i+1]))
         except AssertionError:  # pragma: nocover
-            print 'rewrite failed:', exp, '->', rewrite_chain[i+1]
-            print 'rewrite chain:', rewrite_chain
+            print 'rewrite failed: "%s"  ->  "%s"' \
+                    % (str(exp), str(rewrite_chain[i+1]))
+            print 'rewrite chain index: %d' % i
+            print 'rewrite chain: ---'
+
+            for i, c in enumerate(rewrite_chain):
+                print '%2d  %s' % (i, str(c))
+
+            print '-' * 30
+
             raise

+ 15 - 8
tests/test_leiden_oefenopgave.py

@@ -6,11 +6,9 @@ class TestLeidenOefenopgave(TestCase):
         for chain in [['-5(x2 - 3x + 6)', '-5(x ^ 2 - 3x) - 5 * 6',
                        '-5 * x ^ 2 - 5 * -3x - 5 * 6',
                        '-5 * x ^ 2 - -15x - 5 * 6',
-                       # FIXME: '-5 * x ^ 2 - 5 * -3x - 30',
-                       # FIXME: '-5 * x ^ 2 - -15x - 5 * 6',
-                       # FIXME: '-5 * x ^ 2 + 15x - 5 * 6',
-                       # FIXME: '-5 * x ^ 2 + 15x - 30',
-                       ], #'-30 + 15 * x - 5 * x ^ 2'],
+                       '-5 * x ^ 2 + 15x - 5 * 6',
+                       '-5 * x ^ 2 + 15x - 30',
+                       ],
                      ]:
             self.assertRewrite(chain)
 
@@ -70,12 +68,21 @@ class TestLeidenOefenopgave(TestCase):
                        'xx + x * -1 - 1x - 1 * -1',
                        'x ^ (1 + 1) + x * -1 - 1x - 1 * -1',
                        'x ^ 2 + x * -1 - 1x - 1 * -1',
-                       # FIXME: 'x ^ 2 + (-1 - 1)x - 1 * -1',
-                       # FIXME: 'x ^ 2 - 2x - 1 * -1',
-                       # FIXME: 'x ^ 2 - 2x + 1',
+                       'x ^ 2 + (-1 - 1)x - 1 * -1',
+                       'x ^ 2 - 2x - 1 * -1',
+                       'x ^ 2 - 2x + 1',
                      ]]:
             self.assertRewrite(chain)
 
+    def test_1_4_1(self):
+        self.assertRewrite(['x * -1 + 1x', '(-1 + 1)x', '0x',])  # FIXME: '0'])
+
+    def test_1_4_2(self):
+        self.assertRewrite(['x * -1 - 1x', '(-1 + -1)x', '-2x'])
+
+    def test_1_4_3(self):
+        self.assertRewrite(['x * -1 + x * -1', '(-1 + -1)x', '-2x'])
+
     def test_2(self):
         pass