Explorar o código

Changed negation precedence to that of subtraction and modified rules+tests correspondingly.

Taddeus Kroes %!s(int64=14) %!d(string=hai) anos
pai
achega
42662f455b

+ 1 - 1
external/graph_drawing

@@ -1 +1 @@
-Subproject commit 70714ec0339bb043dcd92a9f233d83347ac0ae95
+Subproject commit ade1a95046e5539a4f11535892061e05b7c23b95

+ 20 - 2
src/node.py

@@ -359,7 +359,7 @@ class ExpressionNode(Node, ExpressionBase):
         self.nodes[self.nodes.index(old_child)] = new_child
 
     def graph(self):  # pragma: nocover
-        return generate_graph(self)
+        return generate_graph(negation_to_node(self))
 
     def extract_polynome_properties(self):
         """
@@ -602,7 +602,7 @@ def get_scope(node):
     scope = []
 
     for child in node:
-        if child.is_op(node.op):
+        if child.is_op(node.op) and not child.negated:
             scope += get_scope(child)
         else:
             scope.append(child)
@@ -698,3 +698,21 @@ def eq(left, right):
     Create an equality operator node.
     """
     return ExpressionNode(OP_EQ, left, right)
+
+
+def negation_to_node(node):
+    """
+    Recursively replace negation flags inside a node by explicit unary negation
+    nodes.
+    """
+    if node.negated:
+         negations = node.negated
+         node = negate(node, 0)
+
+         for i in range(negations):
+             node = ExpressionNode('-', node)
+
+    if node.is_leaf:
+        return node
+
+    return ExpressionNode(node.op, *map(negation_to_node, node))

+ 15 - 29
src/parser.py

@@ -18,7 +18,7 @@ from node import ExpressionBase, ExpressionNode as Node, \
         ExpressionLeaf as Leaf, OP_MAP, OP_DER, TOKEN_MAP, TYPE_OPERATOR, \
         OP_COMMA, OP_NEG, OP_MUL, OP_DIV, OP_POW, OP_LOG, OP_ADD, Scope, E, \
         DEFAULT_LOGARITHM_BASE, OP_VALUE_MAP, SPECIAL_TOKENS, OP_INT, \
-        OP_INT_INDEF, OP_ABS
+        OP_INT_INDEF, OP_ABS, OP_NEG, negation_to_node
 from rules import RULES
 from rules.utils import find_variable
 from strategy import pick_suggestion
@@ -90,10 +90,10 @@ class Parser(BisonParser):
         ('left', ('OR', )),
         ('left', ('AND', )),
         ('left', ('EQ', )),
-        ('left', ('MINUS', 'PLUS')),
+        ('left', ('MINUS', 'PLUS', 'NEG')),
         ('left', ('TIMES', 'DIVIDE')),
         ('right', ('FUNCTION', )),
-        ('left', ('NEG', )),
+        #('left', ('NEG', )),
         ('right', ('POW', )),
         ('left', ('SUB', )),
         ('right', ('FUNCTION_LPAREN', )),
@@ -245,13 +245,12 @@ class Parser(BisonParser):
         if not retval.negated and retval.type != TYPE_OPERATOR:
             return retval
 
-        if retval.type == TYPE_OPERATOR and retval.op in RULES:
-            handlers = RULES[retval.op]
-        else:
-            handlers = []
-
         if retval.negated:
             handlers = RULES[OP_NEG]
+        elif retval.type == TYPE_OPERATOR and retval.op in RULES:
+            handlers = RULES[retval.op]
+        else:
+            return retval
 
         for handler in handlers:
             possibilities = handler(retval)
@@ -360,7 +359,7 @@ class Parser(BisonParser):
         """
 
         if option == 0:
-            print generate_graph(values[1])
+            print generate_graph(negation_to_node(values[1]))
             return values[1]
 
         raise BisonSyntaxError('Unsupported option %d in target "%s".'
@@ -396,7 +395,7 @@ class Parser(BisonParser):
 
     def on_unary(self, target, option, names, values):
         """
-        unary : MINUS exp %prec NEG
+        unary : MINUS exp
               | FUNCTION_LPAREN exp RPAREN
               | FUNCTION exp
               | DERIVATIVE exp
@@ -408,16 +407,9 @@ class Parser(BisonParser):
         """
 
         if option == 0:  # rule: NEG exp
-            node = values[1]
-
-            # Add negation to the left-most child
-            if node.is_leaf or (node.op != OP_MUL and node.op != OP_DIV):
-                node.negated += 1
-            else:
-                child = Scope(node)[0]
-                child.negated += 1
+            values[1].negated += 1
 
-            return node
+            return values[1]
 
         if option in (1, 2):  # rule: FUNCTION_LPAREN exp RPAREN | FUNCTION exp
             op = values[0].split(' ', 1)[0]
@@ -542,19 +534,13 @@ class Parser(BisonParser):
             return Node(values[1], values[0], values[2])
 
         if option == 6:  # rule: exp MINUS exp
-            node = values[2]
-
-            # Add negation to the left-most child
-            if node.is_leaf or (node.op != OP_MUL and node.op != OP_DIV):
-                node.negated += 1
-            else:
-                node = Scope(node)[0]
-                node.negated += 1
+            right = values[2]
+            right.negated += 1
 
             # Explicit call the hook handler on the created unary negation.
-            self.hook_handler('binary', 3, names, values, node)
+            self.hook_handler('unary', 0, names, values, right)
 
-            return Node(OP_ADD, values[0], values[2])
+            return Node(OP_ADD, values[0], right)
 
         if option == 7:  # rule: power
             return Node(OP_POW, *values[0])

+ 15 - 15
src/rules/factors.py

@@ -7,17 +7,18 @@ from ..translate import _
 
 def match_expand(node):
     """
-    a * (b + c) -> ab + ac
-    (b + c) * a -> ab + ac
-    (a + b) * (c + d) -> ac + ad + bc + bd
+    a(b + c)        ->  ab + ac
+    (b + c)a        ->  ab + ac
+    (a + b)(c + d)  ->  ac + ad + bc + bd
     """
     assert node.is_op(OP_MUL)
 
     p = []
     leaves = []
     additions = []
+    scope = Scope(node)
 
-    for n in Scope(node):
+    for n in scope:
         if n.is_leaf:
             leaves.append(n)
         elif n.op == OP_ADD:
@@ -27,11 +28,11 @@ def match_expand(node):
 
             additions.append(n)
 
-    for args in product(leaves, additions):
-        p.append(P(node, expand_single, args))
+    for l, a in product(leaves, additions):
+        p.append(P(node, expand_single, (scope, l, a)))
 
-    for args in combinations(additions, 2):
-        p.append(P(node, expand_double, args))
+    for a0, a1 in combinations(additions, 2):
+        p.append(P(node, expand_double, (scope, a0, a1)))
 
     return p
 
@@ -41,12 +42,11 @@ def expand_single(root, args):
     Combine a leaf (a) multiplied with an addition of two expressions
     (b + c) to an addition of two multiplications.
 
-    a * (b + c) -> ab + ac
-    (b + c) * a -> ab + ac
+    a(b + c)  ->  ab + ac
+    (b + c)a  ->  ab + ac
     """
-    a, bc = args
+    scope, a, bc = args
     b, c = bc
-    scope = Scope(root)
 
     # Replace 'a' with the new expression
     scope.replace(a, a * b + a * c)
@@ -64,10 +64,10 @@ def expand_double(root, args):
     """
     Rewrite two multiplied additions to an addition of four multiplications.
 
-    (a + b) * (c + d) -> ac + ad + bc + bd
+    (a + b)(c + d)  ->  ac + ad + bc + bd
     """
-    (a, b), (c, d) = ab, cd = args
-    scope = Scope(root)
+    scope, ab, cd = args
+    (a, b), (c, d) = ab, cd
 
     # Replace 'a + b' with the new expression
     scope.replace(ab, a * c + a * d + b * c + b * d)

+ 5 - 1
src/rules/fractions.py

@@ -126,7 +126,8 @@ def equalize_denominators(root, args):
             else:
                 nom = L(mult) * n
 
-            scope.replace(fraction, negate(nom / L(d.value * mult), n.negated))
+            scope.replace(fraction, negate(nom / L(d.value * mult),
+                                           fraction.negated))
 
     return scope.as_nary_node()
 
@@ -338,6 +339,9 @@ def match_equal_fraction_parts(node):
     n_scope, d_scope = fraction_scopes(node)
     p = []
 
+    if len(n_scope) == 1 and len(d_scope) == 1:
+        return p
+
     # Look for matching parts in scopes
     for i, n in enumerate(n_scope):
         for j, d in enumerate(d_scope):

+ 12 - 4
src/rules/groups.py

@@ -26,7 +26,7 @@ def match_combine_groups(node):
 
     for n in scope:
         if not n.is_numeric():
-            groups.append((Leaf(1), n, n))
+            groups.append((Leaf(1), n, n, True))
 
         # Each number multiplication yields a group, multiple occurences of
         # the same group can be replaced by a single one
@@ -35,17 +35,25 @@ def match_combine_groups(node):
             l = len(n_scope)
 
             for i, sub_node in enumerate(n_scope):
+                # TODO: use utitlity function evals_to_numeric
+                #if evals_to_numeric(sub_node):
                 if sub_node.is_numeric():
                     others = [n_scope[j] for j in range(i) + range(i + 1, l)]
 
                     if len(others) == 1:
                         g = others[0]
                     else:
-                        g = nary_node('*', others)
+                        g = nary_node(OP_MUL, others)
 
-                    groups.append((sub_node, g, n))
+                    groups.append((sub_node, g, n, False))
+
+    for (c0, g0, n0, root0), (c1, g1, n1, root1) in combinations(groups, 2):
+        if not root0:
+            c0 = c0.negate(n0.negated)
+
+        if not root1:
+            c1 = c1.negate(n1.negated)
 
-    for (c0, g0, n0), (c1, g1, n1) in combinations(groups, 2):
         if g0.equals(g1):
             p.append(P(node, combine_groups, (scope, c0, g0, n0, c1, g1, n1)))
         elif g0.equals(g1, ignore_negation=True):

+ 1 - 1
src/rules/lineq.py

@@ -23,7 +23,7 @@ def match_move_term(node):
     # Multiplication
     x / a = b  ->  x / a * a = b * a  # =>*  x = a * b
     a / x = b  ->  a / x * x = b * x  # =>*  x = a / b
-    -x = b  ->  -x * -1 = b * -1  # =>*  x = -b
+    -x = b  ->  -x * -1 = b * -1      # =>*  x = -b
     """
     assert node.is_op(OP_EQ)
 

+ 52 - 54
src/rules/negation.py

@@ -6,9 +6,10 @@ from ..translate import _
 def match_negated_factor(node):
     """
     This rule assures that negations in the scope of a multiplication are
-    brought to the most left node in the multiplication's scope.
+    brought to the multiplication itself.
 
-    Example:
+    Examples:
+    (-a)b   ->  -ab
     a * -b  ->  -ab
     """
     assert node.is_op(OP_MUL)
@@ -16,10 +17,7 @@ def match_negated_factor(node):
     p = []
     scope = Scope(node)
 
-    # FIXME: The negation that is brought outside is assigned to the first
-    # element in the scope during the next parsing step:
-    # -ab -> -(ab), but -(ab) is printed as -ab
-    for factor in scope[1:]:
+    for factor in scope:
         if factor.negated:
             p.append(P(node, negated_factor, (scope, factor)))
 
@@ -28,13 +26,13 @@ def match_negated_factor(node):
 
 def negated_factor(root, args):
     """
+    (-a)b   ->  -ab
     a * -b  ->  -ab
     """
     scope, factor = args
-    scope[0] = -scope[0]
     scope.replace(factor, +factor)
 
-    return scope.as_nary_node()
+    return -scope.as_nary_node()
 
 
 MESSAGES[negated_factor] = \
@@ -44,27 +42,33 @@ MESSAGES[negated_factor] = \
 def match_negate_polynome(node):
     """
     --a       ->  a
+    ----a     ->  --a
     -(a + b)  ->  -a - b
+    -0        ->  0
     """
-    #print 'match_negate_polynome:', node, node.negated
     assert node.negated, str(node.negated) + '; ' + str(node)
 
     p = []
 
-    if node.negated == 2:
-        # --a
-        p.append(P(node, double_negation, ()))
+    if not (node.negated & 1):
+        # --a, ----a
+        p.append(P(node, double_negation))
 
-    if node.is_op(OP_ADD):
+    if node == 0 or node == 0.0:
+        # -0
+        p.append(P(node, negated_zero))
+    elif node.is_op(OP_ADD):
         # -(a + b)  ->  -a - b
-        p.append(P(node, negate_polynome, ()))
+        p.append(P(node, negate_polynome))
 
     return p
 
 
 def double_negation(root, args):
     """
-    --a  ->  a
+    --a    ->  a
+    ----a  ->  --a
+    ...
     """
     return root.reduce_negation(2)
 
@@ -72,6 +76,16 @@ def double_negation(root, args):
 MESSAGES[double_negation] = _('Remove double negation in {0}.')
 
 
+def negated_zero(root, args):
+    """
+    -0  ->  0
+    """
+    return root.reduce_negation()
+
+
+MESSAGES[negated_zero] = _('Remove negation from zero.')
+
+
 def negate_polynome(root, args):
     """
     -(a + b)  ->  -a - b
@@ -88,65 +102,49 @@ def negate_polynome(root, args):
 MESSAGES[negate_polynome] = _('Apply negation to the polynome {0}.')
 
 
-#def negate_group(root, args):
-#    """
-#    -(a * -3c)       ->  a * 3c
-#    -(a * ... * -b)  ->  ab
-#    """
-#    node, scope = args
-#
-#    for i, n in enumerate(scope):
-#        if n.negated:
-#            scope[i] = n.reduce_negation()
-#
-#    return nary_node('*', scope).reduce_negation()
-#
-#
-#MESSAGES[negate_polynome] = _('Apply negation to the subexpression {1[0]}.')
-
-
 def match_negated_division(node):
     """
-    -a / -b  ->  a / b
+    (-a) / b  ->  -a / b
+    a / (-b)  ->  -a / b
+
+    Note that:
+    (-a) / (-b)  ->  -a / (-b)  ->  --a / b  ->  a / b
     """
     assert node.is_op(OP_DIV)
 
     a, b = node
+    p = []
 
-    if a.negated and b.negated:
-        return [P(node, double_negated_division, ())]
-    elif b.negated:
-        return [P(node, single_negated_division, (a, +b))]
+    if a.negated:
+        p.append(P(node, negated_nominator))
 
-    return []
+    if b.negated:
+        p.append(P(node, negated_denominator))
+
+    return p
 
 
-def single_negated_division(root, args):
+def negated_nominator(root, args):
     """
-    a / -b  ->  -a / b
+    (-a) / b  ->  -a / b
     """
-    a, b = args
-
-    # FIXME: "-a/b" results in "-(a/b)", which will cause a loop.
+    a, b = root
 
-    return -a / b
+    return -(+a / b).negate(root.negated)
 
 
-MESSAGES[single_negated_division] = \
-        _('Bring negation outside of the division: -{1} / {2}.')
+MESSAGES[negated_nominator] = \
+        _('Bring nominator negation in {0} outside to the fraction itself.')
 
 
-def double_negated_division(root, args):
+def negated_denominator(root, args):
     """
-    -a / -b  ->  a / b
+    a / (-b)  ->  -a / b
     """
     a, b = root
 
-    return +a / +b
-
-
-MESSAGES[double_negated_division] = \
-        _('Eliminate top and bottom negation in {0}.')
+    return -(a / +b).negate(root.negated)
 
 
-# TODO: negated multiplication: -a * -b = ab
+MESSAGES[negated_denominator] = \
+        _('Bring denominator negation in {0} outside to the fraction itself.')

+ 13 - 28
src/rules/numerics.py

@@ -99,6 +99,10 @@ def match_divide_numerics(node):
     assert node.is_op(OP_DIV)
 
     n, d = node
+
+    if n.negated or d.negated:
+        return []
+
     nv, dv = n.value, d.value
 
     if n.is_int() and d.is_int():
@@ -107,20 +111,18 @@ def match_divide_numerics(node):
         if not mod:
             # 6 / 2  ->  3
             # 3 / 2  ->  3 / 2
-            return [P(node, divide_numerics, (nv, dv, n.negated + d.negated))]
+            return [P(node, divide_numerics)]
 
         gcd = greatest_common_divisor(nv, dv)
 
         if 1 < gcd <= nv:
             # 2 / 4  ->  1 / 2
-            # TODO: Test with negations!
             return [P(node, reduce_fraction_constants, (gcd,))]
 
-        if nv > dv:
-            # 4 / 3  ->  1 + 1 / 3
-            # TODO: Test with negations!
-            return [P(node, fraction_to_int_fraction,
-                      ((nv - mod) / dv, mod, dv))]
+        #if nv > dv:
+        #    # 4 / 3  ->  1 + 1 / 3
+        #    return [P(node, fraction_to_int_fraction,
+        #              ((nv - mod) / dv, mod, dv))]
     elif n.is_numeric() and d.is_numeric():
         if d == 1.0:
             # 3 / 1.0  ->  3
@@ -129,7 +131,7 @@ def match_divide_numerics(node):
         # 3.0 / 2  ->  1.5
         # 3 / 2.0  ->  1.5
         # 3.0 / 2.0  ->  1.5
-        return [P(node, divide_numerics, (nv, dv, n.negated + d.negated))]
+        return [P(node, divide_numerics)]
 
     return []
 
@@ -145,9 +147,9 @@ def divide_numerics(root, args):
     3.0 / 2.0  ->  1.5
     3 / 1.0    ->  3
     """
-    n, d, negated = args
+    n, d = root
 
-    return Leaf(n / d).negate(negated)
+    return Leaf(n.value / d.value).negate(root.negated)
 
 
 MESSAGES[divide_numerics] = _('Divide constant {1} by constant {2}.')
@@ -164,29 +166,12 @@ def reduce_fraction_constants(root, args):
     gcd = args[0]
     a, b = root
 
-    return Leaf(a.value / gcd).negate(a.negated) \
-           / Leaf(b.value / gcd).negate(b.negated)
+    return Leaf(a.value / gcd) / Leaf(b.value / gcd)
 
 
 MESSAGES[reduce_fraction_constants] = _('Simplify fraction {0}.')
 
 
-def fraction_to_int_fraction(root, args):
-    """
-    Combine two divided integer into an integer with a fraction.
-
-    Examples:
-    4 / 3  ->  1 + 1 / 3
-    """
-    integer, nominator, denominator = map(Leaf, args)
-
-    return integer + nominator / denominator
-
-
-MESSAGES[fraction_to_int_fraction] = _('Expand fraction with nominator greater'
-    ' than denominator {0} to an integer plus a fraction.')
-
-
 def match_multiply_zero(node):
     """
     a * 0    ->  0

+ 3 - 3
src/strategy.py

@@ -1,5 +1,5 @@
 from rules.sort import move_constant
-from rules.numerics import reduce_fraction_constants, fraction_to_int_fraction
+from rules.numerics import reduce_fraction_constants
 from rules.logarithmic import factor_in_exponent_multiplicant
 
 
@@ -9,8 +9,8 @@ def pick_suggestion(possibilities):
 
     # TODO: pick the best suggestion.
     for suggestion, p in enumerate(possibilities + [None]):
-        if p and p.handler not in [move_constant, fraction_to_int_fraction,
-                reduce_fraction_constants, factor_in_exponent_multiplicant]:
+        if p and p.handler not in [move_constant, reduce_fraction_constants,
+                                   factor_in_exponent_multiplicant]:
             break
 
     if not p:

+ 10 - 5
tests/parser.py

@@ -2,6 +2,11 @@ import sys
 
 from external.graph_drawing.graph import generate_graph
 from external.graph_drawing.line import generate_line
+from src.node import negation_to_node
+
+
+def create_graph(node):
+    return generate_graph(negation_to_node(node))
 
 
 class ParserWrapper(object):
@@ -90,9 +95,9 @@ def run_expressions(base_class, expressions, fail=True, silent=False,
 
             if not silent and hasattr(res, 'nodes'):
                 print >>sys.stderr, 'result graph:'
-                print >>sys.stderr, generate_graph(res)
+                print >>sys.stderr, create_graph(res)
                 print >>sys.stderr, 'expected graph:'
-                print >>sys.stderr, generate_graph(out)
+                print >>sys.stderr, create_graph(out)
 
             if fail:
                 raise
@@ -114,16 +119,16 @@ def apply_expressions(base_class, expressions, fail=True, silent=False,
 
             if not silent and hasattr(res, 'nodes'):
                 print >>sys.stderr, 'result graph:'
-                print >>sys.stderr, generate_graph(res)
+                print >>sys.stderr, create_graph(res)
                 print >>sys.stderr, 'expected graph:'
-                print >>sys.stderr, generate_graph(out)
+                print >>sys.stderr, create_graph(out)
 
             if fail:
                 raise
 
 
 def graph(parser, *exp, **kwargs):
-    return generate_graph(ParserWrapper(parser, **kwargs).run(exp))
+    return create_graph(ParserWrapper(parser, **kwargs).run(exp))
 
 
 def line(parser, *exp, **kwargs):

+ 4 - 4
tests/test_b1_ch08.py

@@ -10,10 +10,10 @@ class TestB1Ch08(unittest.TestCase):
     def test_diagnostic_test_parser(self):
         run_expressions(Parser, [
             ('6*5^2', L(6) * L(5) ** 2),
-            ('-5*(-3)^2', (-L(5)) * (-L(3)) ** 2),
-            ('7p-3p', L(7) * 'p' + (-L(3) * 'p')),
-            ('-5a*-6', (-L(5)) * 'a' * (-L(6))),
-            ('3a-8--5-2a', L(3) * 'a' + -L(8) + (--L(5)) + (-L(2) * 'a')),
+            ('-5*(-3)^2', -(L(5) * (-L(3)) ** 2)),
+            ('7p-3p', L(7) * 'p' + -(L(3) * 'p')),
+            ('-5a*-6', -(L(5) * 'a' * -L(6))),
+            ('3a-8--5-2a', L(3) * 'a' + -L(8) + --(L(5)) + -(L(2) * 'a')),
             ])
 
     def test_diagnostic_test_application(self):

+ 4 - 4
tests/test_b1_ch10.py

@@ -9,12 +9,12 @@ class TestB1Ch10(unittest.TestCase):
 
     def test_diagnostic_test(self):
         run_expressions(Parser, [
-            ('5(a-2b)', L(5) * (L('a') + (-L(2) * 'b'))),
+            ('5(a-2b)', L(5) * (L('a') + -(L(2) * 'b'))),
             ('-(3a+6b)', -(L(3) * L('a') + L(6) * 'b')),
             ('18-(a-12)', L(18) + -(L('a') + -L(12))),
             ('-p-q+5(p-q)-3q-2(p-q)',
-                -L('p') + -L('q') + L(5) * (L('p') + -L('q')) + (-L(3) * 'q') \
-                + (-L(2) * (L('p') + -L('q')))
+                -L('p') + -L('q') + L(5) * (L('p') + -L('q')) + -(L(3) * 'q') \
+                + -(L(2) * (L('p') + -L('q')))
             ),
             ('(2+3/7)^4',
                 N('^', N('+', L(2), N('/', L(3), L(7))), L(4))
@@ -28,7 +28,7 @@ class TestB1Ch10(unittest.TestCase):
                 )
             ),
             ('-x3*-2x5',
-                -(L('x') ** L(3)) * - L(2) * L('x') ** L(5)
+                -(L('x') ** L(3) * -(L(2) * L('x') ** L(5)))
             ),
             ('(7x2y3)^2/(7x2y3)',
                 N('/',

+ 37 - 31
tests/test_leiden_oefenopgave.py

@@ -3,15 +3,16 @@ from tests.rulestestcase import RulesTestCase as TestCase, rewrite
 
 class TestLeidenOefenopgave(TestCase):
     def test_1_1(self):
-        for chain in [['-5(x2 - 3x + 6)', '-5(x ^ 2 - 3x) - 5 * 6',
-                       '-5x ^ 2 - 5 * -3x - 5 * 6',
-                       '-5x ^ 2 - -15x - 5 * 6',
-                       '-5x ^ 2 + 15x - 5 * 6',
-                       '-5x ^ 2 + 15x - 30',
-                       ],
-                     ]:
-            self.assertRewrite(chain)
-
+        self.assertRewrite([
+            '-5(x2 - 3x + 6)',
+            '-(5(x ^ 2 - 3x) + 5 * 6)',
+            '-(5x ^ 2 + 5(-3x) + 5 * 6)',
+            '-(5x ^ 2 - 5 * 3x + 5 * 6)',
+            '-(5x ^ 2 - 15x + 5 * 6)',
+            '-(5x ^ 2 - 15x + 30)',
+            '-5x ^ 2 - -15x - 30',
+            '-5x ^ 2 + 15x - 30',
+        ])
         return
 
         for exp, solution in [
@@ -70,16 +71,18 @@ class TestLeidenOefenopgave(TestCase):
     def test_1_4(self):
         # (x-1)^2 -> x^2 - 2x + 1
         self.assertRewrite([
-            '(x-1)^2', '(x - 1)(x - 1)',
-            'xx + x * -1 - 1x - 1 * -1',
-            'x ^ (1 + 1) + x * -1 - 1x - 1 * -1',
-            'x ^ 2 + x * -1 - 1x - 1 * -1',
-            'x ^ 2 - x * 1 - 1x - 1 * -1',
-            'x ^ 2 - x - 1x - 1 * -1',
-            'x ^ 2 - x - x - 1 * -1',
-            'x ^ 2 + (1 + 1) * -x - 1 * -1',
-            'x ^ 2 + 2 * -x - 1 * -1',
-            'x ^ 2 - 2x - 1 * -1',
+            '(x - 1) ^ 2',
+            '(x - 1)(x - 1)',
+            'xx + x(-1) + (-1)x + (-1)(-1)',
+            'x ^ (1 + 1) + x(-1) + (-1)x + (-1)(-1)',
+            'x ^ 2 + x(-1) + (-1)x + (-1)(-1)',
+            'x ^ 2 - x * 1 + (-1)x + (-1)(-1)',
+            'x ^ 2 - x + (-1)x + (-1)(-1)',
+            'x ^ 2 - x - 1x + (-1)(-1)',
+            'x ^ 2 - x - x + (-1)(-1)',
+            'x ^ 2 + (1 + 1)(-x) + (-1)(-1)',
+            'x ^ 2 + 2(-x) + (-1)(-1)',
+            'x ^ 2 - 2x + (-1)(-1)',
             'x ^ 2 - 2x - -1',
             'x ^ 2 - 2x + 1',
         ])
@@ -101,20 +104,20 @@ class TestLeidenOefenopgave(TestCase):
             '-x * 1 - 1x',
             '-x - 1x',
             '-x - x',
-            '(1 + 1) * -x',
-            '2 * -x',
+            '(1 + 1)(-x)',
+            '2(-x)',
             '-2x',
         ])
 
     def test_1_4_3(self):
         self.assertRewrite([
             'x * -1 + x * -1',
-            '-x * 1 + x * -1',
-            '-x + x * -1',
+            '-x * 1 + x(-1)',
+            '-x + x(-1)',
             '-x - x * 1',
             '-x - x',
-            '(1 + 1) * -x',
-            '2 * -x',
+            '(1 + 1)(-x)',
+            '2(-x)',
             '-2x',
         ])
 
@@ -132,14 +135,16 @@ class TestLeidenOefenopgave(TestCase):
             '(4x + 5) * -(5 - 4x)',
             '(4x + 5)(-5 - -4x)',
             '(4x + 5)(-5 + 4x)',
-            '4x * -5 + 4x * 4x + 5 * -5 + 5 * 4x',
-            '-20x + 4x * 4x + 5 * -5 + 5 * 4x',
-            '-20x + 16xx + 5 * -5 + 5 * 4x',
-            '-20x + 16x ^ (1 + 1) + 5 * -5 + 5 * 4x',
-            '-20x + 16x ^ 2 + 5 * -5 + 5 * 4x',
+            '4x(-5) + 4x * 4x + 5(-5) + 5 * 4x',
+            '(-20)x + 4x * 4x + 5(-5) + 5 * 4x',
+            '-20x + 4x * 4x + 5(-5) + 5 * 4x',
+            '-20x + 16xx + 5(-5) + 5 * 4x',
+            '-20x + 16x ^ (1 + 1) + 5(-5) + 5 * 4x',
+            '-20x + 16x ^ 2 + 5(-5) + 5 * 4x',
             '-20x + 16x ^ 2 - 25 + 5 * 4x',
             '-20x + 16x ^ 2 - 25 + 20x',
-            '(-20 + 20)x + 16x ^ 2 - 25',
+            '(-1 + 1)20x + 16x ^ 2 - 25',
+            '0 * 20x + 16x ^ 2 - 25',
             '0x + 16x ^ 2 - 25',
             '0 + 16x ^ 2 - 25',
             '16x ^ 2 - 25',
@@ -164,6 +169,7 @@ class TestLeidenOefenopgave(TestCase):
             '2/7 - 4/11',
             '22 / 77 - 28 / 77',
             '(22 - 28) / 77',
+            '(-6) / 77',
             '-6 / 77',
         ])
 

+ 46 - 39
tests/test_leiden_oefenopgave_v12.py

@@ -3,51 +3,58 @@ from tests.rulestestcase import RulesTestCase as TestCase
 
 class TestLeidenOefenopgaveV12(TestCase):
     def test_1_a(self):
-        self.assertRewrite(['-5(x2 - 3x + 6)',
-                            '-5(x ^ 2 - 3x) - 5 * 6',
-                            '-5x ^ 2 - 5 * -3x - 5 * 6',
-                            '-5x ^ 2 - -15x - 5 * 6',
-                            '-5x ^ 2 + 15x - 5 * 6',
-                            '-5x ^ 2 + 15x - 30'])
+        self.assertRewrite([
+            '-5(x2 - 3x + 6)',
+            '-(5(x ^ 2 - 3x) + 5 * 6)',
+            '-(5x ^ 2 + 5(-3x) + 5 * 6)',
+            '-(5x ^ 2 - 5 * 3x + 5 * 6)',
+            '-(5x ^ 2 - 15x + 5 * 6)',
+            '-(5x ^ 2 - 15x + 30)',
+            '-5x ^ 2 - -15x - 30',
+            '-5x ^ 2 + 15x - 30',
+        ])
 
     def test_1_d(self):
-        self.assertRewrite(['(2x + x)x',
-                            '(2 + 1)xx',
-                            '3xx',
-                            '3x ^ (1 + 1)',
-                            '3x ^ 2'])
+        self.assertRewrite([
+            '(2x + x)x',
+            '(2 + 1)xx',
+            '3xx',
+            '3x ^ (1 + 1)',
+            '3x ^ 2',
+        ])
 
     def test_1_e(self):
         self.assertRewrite([
             '-2(6x - 4) ^ 2x',
             '-2(6x - 4)(6x - 4)x',
-            '(-2 * 6x - 2 * -4)(6x - 4)x',
-            '(-12x - 2 * -4)(6x - 4)x',
-            '(-12x - -8)(6x - 4)x',
-            '(-12x + 8)(6x - 4)x',
-            '(-12x * 6x - 12x * -4 + 8 * 6x + 8 * -4)x',
-            '(-72xx - 12x * -4 + 8 * 6x + 8 * -4)x',
-            '(-72x ^ (1 + 1) - 12x * -4 + 8 * 6x + 8 * -4)x',
-            '(-72x ^ 2 - 12x * -4 + 8 * 6x + 8 * -4)x',
-            '(-72x ^ 2 - -48x + 8 * 6x + 8 * -4)x',
-            '(-72x ^ 2 + 48x + 8 * 6x + 8 * -4)x',
-            '(-72x ^ 2 + 48x + 48x + 8 * -4)x',
-            '(-72x ^ 2 + (1 + 1)48x + 8 * -4)x',
-            '(-72x ^ 2 + 2 * 48x + 8 * -4)x',
-            '(-72x ^ 2 + 96x + 8 * -4)x',
-            '(-72x ^ 2 + 96x - 32)x',
-            'x(-72x ^ 2 + 96x) + x * -32',
-            'x * -72x ^ 2 + x * 96x + x * -32',
-            '-x * 72x ^ 2 + x * 96x + x * -32',
-            '-x ^ (1 + 2)72 + x * 96x + x * -32',
-            '-x ^ 3 * 72 + x * 96x + x * -32',
-            '-x ^ 3 * 72 + x ^ (1 + 1)96 + x * -32',
-            '-x ^ 3 * 72 + x ^ 2 * 96 + x * -32',
+            '-(2 * 6x + 2(-4))(6x - 4)x',
+            '-(12x + 2(-4))(6x - 4)x',
+            '-(12x - 8)(6x - 4)x',
+            '-(12x * 6x + 12x(-4) + (-8)6x + (-8)(-4))x',
+            '-(72xx + 12x(-4) + (-8)6x + (-8)(-4))x',
+            '-(72x ^ (1 + 1) + 12x(-4) + (-8)6x + (-8)(-4))x',
+            '-(72x ^ 2 + 12x(-4) + (-8)6x + (-8)(-4))x',
+            '-(72x ^ 2 + (-48)x + (-8)6x + (-8)(-4))x',
+            '-(72x ^ 2 - 48x + (-8)6x + (-8)(-4))x',
+            '-(72x ^ 2 - 48x + (-48)x + (-8)(-4))x',
+            '-(72x ^ 2 - 48x - 48x + (-8)(-4))x',
+            '-(72x ^ 2 + (1 + 1)(-48x) + (-8)(-4))x',
+            '-(72x ^ 2 + 2(-48x) + (-8)(-4))x',
+            '-(72x ^ 2 - 2 * 48x + (-8)(-4))x',
+            '-(72x ^ 2 - 96x + (-8)(-4))x',
+            '-(72x ^ 2 - 96x - -32)x',
+            '-(72x ^ 2 - 96x + 32)x',
+            '-(x(72x ^ 2 - 96x) + x * 32)',
+            '-(x * 72x ^ 2 + x(-96x) + x * 32)',
+            '-(x ^ (1 + 2)72 + x(-96x) + x * 32)',
+            '-(x ^ 3 * 72 + x(-96x) + x * 32)',
+            '-(x ^ 3 * 72 - x * 96x + x * 32)',
+            '-(x ^ 3 * 72 - x ^ (1 + 1)96 + x * 32)',
+            '-(x ^ 3 * 72 - x ^ 2 * 96 + x * 32)',
+            '-x ^ 3 * 72 - -x ^ 2 * 96 - x * 32',
             '-x ^ 3 * 72 + x ^ 2 * 96 - x * 32',
-            '72 * -x ^ 3 + x ^ 2 * 96 - x * 32',
             '-72x ^ 3 + x ^ 2 * 96 - x * 32',
             '-72x ^ 3 + 96x ^ 2 - x * 32',
-            '-72x ^ 3 + 96x ^ 2 + 32 * -x',
             '-72x ^ 3 + 96x ^ 2 - 32x',
         ])
 
@@ -67,7 +74,7 @@ class TestLeidenOefenopgaveV12(TestCase):
             'b ^ 2 * a ^ 7 / b ^ 3',
             'b ^ 2 / b ^ 3 * a ^ 7 / 1',
             'b ^ (2 - 3)a ^ 7 / 1',
-            'b ^ -1 * a ^ 7 / 1',
+            'b ^ (-1)a ^ 7 / 1',
             '1 / b ^ 1 * a ^ 7 / 1',
             '1 / b * a ^ 7 / 1',
             'a ^ 7 * 1 / b / 1',
@@ -105,9 +112,9 @@ class TestLeidenOefenopgaveV12(TestCase):
     def test_2_f(self):
         self.assertRewrite([
             '(4b) ^ -2',
-            '4 ^ -2 * b ^ -2',
-            '1 / 4 ^ 2 * b ^ -2',
-            '1 / 16 * b ^ -2',
+            '4 ^ (-2)b ^ (-2)',
+            '1 / 4 ^ 2 * b ^ (-2)',
+            '1 / 16 * b ^ (-2)',
             '1 / 16 * (1 / b ^ 2)',
             '1 * 1 / (16b ^ 2)',
             '1 / (16b ^ 2)',

+ 11 - 1
tests/test_node.py

@@ -1,6 +1,6 @@
 from src.node import ExpressionNode as N, ExpressionLeaf as L, Scope, \
         nary_node, get_scope, OP_ADD, infinity, absolute, sin, cos, tan, log, \
-        ln, der, integral, indef, eq
+        ln, der, integral, indef, eq, negation_to_node
 from tests.rulestestcase import RulesTestCase, tree
 
 
@@ -106,6 +106,10 @@ class TestNode(RulesTestCase):
         plus = N('+', N('+', N('+', *self.l[:2]), self.l[2]), self.l[3])
         self.assertEqual(get_scope(plus), self.l)
 
+    def test_get_scope_negation(self):
+        root, a, b, cd = tree('a * b * -cd, a, b, -cd')
+        self.assertEqual(get_scope(root), [a, b, cd])
+
     def test_equals_node_leaf(self):
         a, b = plus = tree('a + b')
 
@@ -296,3 +300,9 @@ class TestNode(RulesTestCase):
     def test_eq(self):
         x, a, b, expect = tree('x, a, b, x + a = b')
         self.assertEqual(eq(x + a, b), expect)
+
+    def test_negation_to_node(self):
+        a = tree('a')
+        self.assertEqual(negation_to_node(-a), N('-', a))
+        self.assertEqual(negation_to_node(-(a + 1)), N('-', a + 1))
+        self.assertEqual(negation_to_node(-(a - 1)), N('-', a + N('-', 1)))

+ 0 - 8
tests/test_parser.py

@@ -63,14 +63,6 @@ class TestParser(unittest.TestCase):
         # FIXME: self.assertEqual(tree('a|b|'), tree('a * |b|'))
         # FIXME: self.assertEqual(tree('|a|b'), tree('|a| * b'))
 
-    def test_moved_negation(self):
-        a, b = tree('a,b')
-
-        self.assertEqual(tree('-ab'), (-a) * b)
-        self.assertEqual(tree('-(ab)'), (-a) * b)
-        self.assertEqual(tree('-a / b'), (-a) / b)
-        self.assertEqual(tree('-(a / b)'), (-a) / b)
-
     def test_functions(self):
         x = tree('x')
 

+ 16 - 4
tests/test_rewrite.py

@@ -4,11 +4,23 @@ from tests.rulestestcase import RulesTestCase as TestCase
 class TestRewrite(TestCase):
 
     def test_addition_rewrite(self):
-        self.assertRewrite(['2 + 3 + 4', '5 + 4', '9'])
+        self.assertRewrite([
+            '2 + 3 + 4',
+            '5 + 4',
+            '9',
+        ])
 
     def test_addition_identifiers_rewrite(self):
-        self.assertRewrite(['2 + 3a + 4', '6 + 3a'])
+        self.assertRewrite([
+            '2 + 3a + 4',
+            '6 + 3a',
+        ])
 
     def test_division_rewrite(self):
-        self.assertRewrite(['2/7 - 4/11', '22 / 77 - 28 / 77',
-                            '(22 - 28) / 77', '-6 / 77'])
+        self.assertRewrite([
+            '2/7 - 4/11',
+            '22 / 77 - 28 / 77',
+            '(22 - 28) / 77',
+            '(-6) / 77',
+            '-6 / 77',
+        ])

+ 14 - 17
tests/test_rules_factors.py

@@ -1,4 +1,5 @@
 from src.rules.factors import match_expand, expand_single, expand_double
+from src.node import Scope
 from src.possibilities import Possibility as P
 from tests.rulestestcase import RulesTestCase, tree
 
@@ -10,45 +11,41 @@ class TestRulesFactors(RulesTestCase):
         b, c = bc
 
         root = a * bc
-        possibilities = match_expand(root)
-        self.assertEqualPos(possibilities,
-                [P(root, expand_single, (a, bc))])
+        self.assertEqualPos(match_expand(root),
+                [P(root, expand_single, (Scope(root), a, bc))])
 
         root = bc * a
-        possibilities = match_expand(root)
-        self.assertEqualPos(possibilities,
-                [P(root, expand_single, (a, bc))])
+        self.assertEqualPos(match_expand(root),
+                [P(root, expand_single, (Scope(root), a, bc))])
 
         root = a * d * bc
-        possibilities = match_expand(root)
-        self.assertEqualPos(possibilities,
-                [P(root, expand_single, (a, bc)),
-                 P(root, expand_single, (d, bc))])
+        self.assertEqualPos(match_expand(root),
+                [P(root, expand_single, (Scope(root), a, bc)),
+                 P(root, expand_single, (Scope(root), d, bc))])
 
         ab, cd = root = (a + b) * (c + d)
-        possibilities = match_expand(root)
-        self.assertEqualPos(possibilities,
-                [P(root, expand_double, (ab, cd))])
+        self.assertEqualPos(match_expand(root),
+                [P(root, expand_double, (Scope(root), ab, cd))])
 
     def test_expand_single(self):
         a, b, c, d = tree('a,b,c,d')
         bc = b + c
 
         root = a * bc
-        self.assertEqualNodes(expand_single(root, (a, bc)),
+        self.assertEqualNodes(expand_single(root, (Scope(root), a, bc)),
                               a * b + a * c)
 
         root = a * d * bc
-        self.assertEqualNodes(expand_single(root, (a, bc)),
+        self.assertEqualNodes(expand_single(root, (Scope(root), a, bc)),
                               (a * b + a * c) * d)
 
     def test_expand_double(self):
         (a, b), (c, d) = ab, cd = tree('a + b,c + d')
 
         root = ab * cd
-        self.assertEqualNodes(expand_double(root, (ab, cd)),
+        self.assertEqualNodes(expand_double(root, (Scope(root), ab, cd)),
                               a * c + a * d + b * c + b * d)
 
         root = a * ab * b * cd * c
-        self.assertEqualNodes(expand_double(root, (ab, cd)),
+        self.assertEqualNodes(expand_double(root, (Scope(root), ab, cd)),
                               a * (a * c + a * d + b * c + b * d) * b * c)

+ 3 - 4
tests/test_rules_fractions.py

@@ -185,9 +185,8 @@ class TestRulesFractions(RulesTestCase):
                  P(root, divide_fraction_parts, (b, s0, s1, 1, 1)),
                  P(root, divide_fraction_parts, (c, s0, s1, 2, 0))])
 
-        root = tree('-a / a')
-        self.assertEqualPos(match_equal_fraction_parts(root),
-                [P(root, divide_fraction_parts, (a, [-a], [a], 0, 0))])
+        root = tree('a / a')
+        self.assertEqualPos(match_equal_fraction_parts(root), [])
 
         (ap, b), aq = root = tree('a ^ p * b / a ^ q')
         self.assertEqualPos(match_equal_fraction_parts(root),
@@ -224,7 +223,7 @@ class TestRulesFractions(RulesTestCase):
         result = divide_fraction_parts(root, (c, [a, b, c], [c, b, a], 2, 0))
         self.assertEqual(result, a * b / (b * a))
 
-        (a, b), a = root = tree('-ab / a')
+        (a, b), a = root = tree('(-a)b / a')
         result = divide_fraction_parts(root, (a, [-a, b], [a], 0, 0))
         self.assertEqual(result, -b / 1)
 

+ 7 - 1
tests/test_rules_groups.py

@@ -95,7 +95,13 @@ class TestRulesGroups(RulesTestCase):
         root, l1 = tree('ab + b + ba,1')
         abb, ba = root
         ab, b = abb
-
         self.assertEqualNodes(combine_groups(root,
                               (Scope(root), l1, ab, ab, l1, ba, ba)),
                               (l1 + 1) * ab + b)
+
+    def test_combine_groups_negation(self):
+        root, expect = tree('3a - 2a, -(3 + 2)a')
+        (l3, a0), (l2, a1) = n0, n1 = root
+        self.assertEqualNodes(combine_groups(root,
+                              (Scope(root), l3, a0, n0, l2, a1, n1)),
+                              expect)

+ 1 - 1
tests/test_rules_integrals.py

@@ -98,7 +98,7 @@ class TestRulesIntegrals(RulesTestCase):
                 [P(root, split_negation_to_constant)])
 
     def test_split_negation_to_constant(self):
-        root, expect = tree('int -x2 dx, int -1x2 dx')
+        root, expect = tree('int -x2 dx, int (-1)x2 dx')
         self.assertEqual(split_negation_to_constant(root, ()), expect)
 
     def test_factor_out_constant(self):

+ 10 - 7
tests/test_rules_lineq.py

@@ -69,12 +69,14 @@ class TestRulesLineq(RulesTestCase):
             '(2 + 3)x = -3x - 5 - -3x',
             '5x = -3x - 5 - -3x',
             '5x = -3x - 5 + 3x',
-            '5x = (-3 + 3)x - 5',
+            '5x = (-1 + 1)3x - 5',
+            '5x = 0 * 3x - 5',
             '5x = 0x - 5',
             '5x = 0 - 5',
             '5x = -5',
-            '5x / 5 = -5 / 5',
-            'x / 1 = -5 / 5',
+            '5x / 5 = (-5) / 5',
+            'x / 1 = (-5) / 5',
+            'x = (-5) / 5',
             'x = -5 / 5',
             'x = -1',
         ])
@@ -82,10 +84,11 @@ class TestRulesLineq(RulesTestCase):
     def test_match_move_term_chain_advanced(self):
         self.assertRewrite([
             '-x = a',
-            '-x * -1 = a * -1',
-            '--x * 1 = a * -1',
-            'x * 1 = a * -1',
-            'x = a * -1',
+            '(-x)(-1) = a(-1)',
+            '-x(-1) = a(-1)',
+            '--x * 1 = a(-1)',
+            '--x = a(-1)',
+            'x = a(-1)',
             'x = -a * 1',
             'x = -a',
         ])

+ 47 - 30
tests/test_rules_negation.py

@@ -1,7 +1,7 @@
 from src.rules.negation import match_negated_factor, negated_factor, \
-        match_negate_polynome, negate_polynome, double_negation, \
-        match_negated_division, single_negated_division, \
-        double_negated_division
+        match_negate_polynome, negate_polynome, negated_zero, \
+        double_negation, match_negated_division, negated_nominator, \
+        negated_denominator
 from src.node import Scope
 from src.possibilities import Possibility as P
 from tests.rulestestcase import RulesTestCase, tree
@@ -14,7 +14,7 @@ class TestRulesNegation(RulesTestCase):
         self.assertEqualPos(match_negated_factor(root),
                 [P(root, negated_factor, (Scope(root), b))])
 
-        (a, b), c = root = tree('a * -b * -c')
+        (a, b), c = root = tree('a * (-b) * -c')
         scope = Scope(root)
         self.assertEqualPos(match_negated_factor(root),
                 [P(root, negated_factor, (scope, b)),
@@ -22,34 +22,44 @@ class TestRulesNegation(RulesTestCase):
 
     def test_negated_factor(self):
         a, b = root = tree('a * -b')
-        self.assertEqualNodes(negated_factor(root, (Scope(root), b)),
-                              -a * +b)
+        self.assertEqual(negated_factor(root, (Scope(root), b)), -(a * +b))
 
-        (a, b), c = root = tree('a * -b * -c')
-        self.assertEqualNodes(negated_factor(root, (Scope(root), b)),
-                              -a * +b * c)
-        self.assertEqualNodes(negated_factor(root, (Scope(root), c)),
-                              -a * b * +c)
+        (a, b), c = root = tree('a * (-b) * -c')
+        self.assertEqual(negated_factor(root, (Scope(root), b)), -(a * +b * c))
+        self.assertEqual(negated_factor(root, (Scope(root), c)), -(a * b * +c))
 
     def test_match_negate_polynome(self):
         root = tree('--a')
         self.assertEqualPos(match_negate_polynome(root),
-                [P(root, double_negation, ())])
+                [P(root, double_negation)])
+
+        root = tree('-0')
+        self.assertEqualPos(match_negate_polynome(root),
+                [P(root, negated_zero)])
+
+        root = tree('--0')
+        self.assertEqualPos(match_negate_polynome(root),
+                [P(root, double_negation),
+                 P(root, negated_zero)])
 
         root = tree('-(a + b)')
         self.assertEqualPos(match_negate_polynome(root),
-                [P(root, negate_polynome, ())])
+                [P(root, negate_polynome)])
 
     def test_double_negation(self):
         root = tree('--a')
-        self.assertEqualNodes(double_negation(root, ()), ++root)
+        self.assertEqual(double_negation(root, ()), ++root)
+
+    def test_negated_zero(self):
+        root = tree('-0')
+        self.assertEqual(negated_zero(root, ()), 0)
 
     def test_negate_polynome(self):
         a, b = root = tree('-(a + b)')
-        self.assertEqualNodes(negate_polynome(root, ()), -a + -b)
+        self.assertEqual(negate_polynome(root, ()), -a + -b)
 
         a, b = root = tree('-(a - b)')
-        self.assertEqualNodes(negate_polynome(root, ()), -a + -b)
+        self.assertEqual(negate_polynome(root, ()), -a + -b)
 
     def test_match_negated_division_none(self):
         self.assertEqual(match_negated_division(tree('1 / 2')), [])
@@ -58,25 +68,32 @@ class TestRulesNegation(RulesTestCase):
         l1, l2 = root = tree('-1 / 2')
         self.assertEqualPos(match_negated_division(root), [])
 
+        l1, l2 = root = tree('(-1) / 2')
+        self.assertEqualPos(match_negated_division(root),
+                [P(root, negated_nominator)])
+
         l1, l2 = root = tree('1 / -2')
-        possibilities = match_negated_division(root)
-        self.assertEqualPos(possibilities,
-                [P(root, single_negated_division, (l1, +l2))])
+        self.assertEqualPos(match_negated_division(root),
+                [P(root, negated_denominator)])
 
     def test_match_negated_division_double(self):
-        root = tree('-1 / -2')
+        root = tree('(-1) / -2')
+        self.assertEqualPos(match_negated_division(root),
+                [P(root, negated_nominator),
+                 P(root, negated_denominator)])
 
-        possibilities = match_negated_division(root)
-        self.assertEqualPos(possibilities,
-                [P(root, double_negated_division, ())])
+    def test_negated_nominator(self):
+        l1, l2 = root = tree('(-1) / 2')
+        self.assertEqual(negated_nominator(root, ()), -(+l1 / l2))
 
-    def test_single_negated_division(self):
+    def test_negated_denominator(self):
         l1, l2 = root = tree('1 / -2')
-        self.assertEqualNodes(single_negated_division(root, (l1, +l2)),
-                              -l1 / +l2)
+        self.assertEqual(negated_denominator(root, ()), -(l1 / +l2))
 
     def test_double_negated_division(self):
-        l1, l2 = root = tree('-1 / -2')
-
-        self.assertEqualNodes(double_negated_division(root, ()),
-                              +l1 / +l2)
+        self.assertRewrite([
+            '(-a) / (-b)',
+            '-a / (-b)',
+            '--a / b',
+            'a / b',
+        ])

+ 24 - 37
tests/test_rules_numerics.py

@@ -1,6 +1,6 @@
 from src.rules.numerics import match_add_numerics, add_numerics, \
         match_divide_numerics, divide_numerics, reduce_fraction_constants, \
-        fraction_to_int_fraction, match_multiply_numerics, multiply_numerics, \
+        match_multiply_numerics, multiply_numerics, \
         raise_numerics
 from src.node import ExpressionLeaf as L, Scope
 from src.possibilities import Possibility as P
@@ -42,67 +42,54 @@ class TestRulesNumerics(RulesTestCase):
 
         root = i6 / i2
         possibilities = match_divide_numerics(root)
-        self.assertEqualPos(possibilities,
-                [P(root, divide_numerics, (6, 2, 0))])
+        self.assertEqualPos(possibilities, [P(root, divide_numerics)])
 
         root = -i6 / i2
-        possibilities = match_divide_numerics(root)
-        self.assertEqualPos(possibilities,
-                [P(root, divide_numerics, (6, 2, 1))])
+        self.assertEqualPos(match_divide_numerics(root), [])
 
-        root = i3 / i2
-        possibilities = match_divide_numerics(root)
-        self.assertEqualPos(possibilities,
-                [P(root, fraction_to_int_fraction, (1, 1, 2))])
+        root = i6 / -i2
+        self.assertEqualPos(match_divide_numerics(root), [])
 
         root = i2 / i4
-        possibilities = match_divide_numerics(root)
-        self.assertEqualPos(possibilities,
+        self.assertEqualPos(match_divide_numerics(root),
                 [P(root, reduce_fraction_constants, (2,))])
 
         root = f3 / i2
-        possibilities = match_divide_numerics(root)
-        self.assertEqualPos(possibilities,
-                [P(root, divide_numerics, (3.0, 2, 0))])
+        self.assertEqualPos(match_divide_numerics(root),
+                [P(root, divide_numerics)])
 
         root = i3 / f2
-        possibilities = match_divide_numerics(root)
-        self.assertEqualPos(possibilities,
-                [P(root, divide_numerics, (3, 2.0, 0))])
+        self.assertEqualPos(match_divide_numerics(root),
+                [P(root, divide_numerics)])
 
         root = f3 / f2
-        possibilities = match_divide_numerics(root)
-        self.assertEqualPos(possibilities,
-                [P(root, divide_numerics, (3.0, 2.0, 0))])
+        self.assertEqualPos(match_divide_numerics(root),
+                [P(root, divide_numerics)])
 
         root = i3 / f1
-        possibilities = match_divide_numerics(root)
-        self.assertEqualPos(possibilities,
-                [P(root, divide_numerics, (3, 1, 0))])
+        self.assertEqualPos(match_divide_numerics(root),
+                [P(root, divide_numerics)])
 
         root = a / b
-        possibilities = match_divide_numerics(root)
-        self.assertEqualPos(possibilities, [])
+        self.assertEqualPos(match_divide_numerics(root), [])
 
     def test_divide_numerics(self):
         i2, i3, i6, f2, f3 = tree('2,3,6,2.0,3.0')
 
-        self.assertEqual(divide_numerics(i6 / i2, (6, 2, 0)), 3)
-        self.assertEqual(divide_numerics(f3 / i2, (3.0, 2, 0)), 1.5)
-        self.assertEqual(divide_numerics(i3 / f2, (3, 2.0, 0)), 1.5)
-        self.assertEqual(divide_numerics(f3 / f2, (3.0, 2.0, 0)), 1.5)
-
-        self.assertEqual(divide_numerics(i6 / i2, (6, 2, 1)), -3)
-        self.assertEqual(divide_numerics(i6 / i2, (6, 2, 2)), --i3)
+        self.assertEqual(divide_numerics(i6 / i2, ()), 3)
+        self.assertEqual(divide_numerics(f3 / i2, ()), 1.5)
+        self.assertEqual(divide_numerics(i3 / f2, ()), 1.5)
+        self.assertEqual(divide_numerics(f3 / f2, ()), 1.5)
+        self.assertEqual(divide_numerics(-(i6 / i2), ()), -i3)
 
     def test_reduce_fraction_constants(self):
         l1, l2 = tree('1,2')
         self.assertEqual(reduce_fraction_constants(l2 / 4, (2,)), l1 / l2)
 
-    def test_fraction_to_int_fraction(self):
-        l1, l4 = tree('1,4')
-        self.assertEqual(fraction_to_int_fraction(l4 / 3, (1, 1, 3)),
-                         l1 + l1 / 3)
+    #def test_fraction_to_int_fraction(self):
+    #    l1, l4 = tree('1,4')
+    #    self.assertEqual(fraction_to_int_fraction(l4 / 3, (1, 1, 3)),
+    #                     l1 + l1 / 3)
 
     def test_match_multiply_numerics(self):
         i2, i3, i6, f2, f3, f6 = tree('2,3,6,2.0,3.0,6.0')