Implemented negate_group and fixed subtree_map bug

parent d1eb149c
...@@ -272,6 +272,10 @@ class ExpressionNode(Node, ExpressionBase): ...@@ -272,6 +272,10 @@ class ExpressionNode(Node, ExpressionBase):
non-strictly equal. non-strictly equal.
""" """
if not other.is_op(self.op): 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 return False
if self.op in (OP_ADD, OP_MUL): if self.op in (OP_ADD, OP_MUL):
......
...@@ -181,15 +181,20 @@ class Parser(BisonParser): ...@@ -181,15 +181,20 @@ class Parser(BisonParser):
def hook_handler(self, target, option, names, values, retval): def hook_handler(self, target, option, names, values, retval):
if target in ['exp', 'line', 'input'] or not 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 return retval
# Update the subtree map to let the subtree point to its parent node. if self.subtree_map:
parent_nodes = self.subtree_map.keys() # Update the subtree map to let the subtree point to its parent
# node.
parent_nodes = self.subtree_map.keys()
for child in retval: for child in retval:
if child in parent_nodes: if child in parent_nodes:
self.subtree_map[child] = retval self.subtree_map[child] = retval
if retval.op not in RULES:
return retval
for handler in RULES[retval.op]: for handler in RULES[retval.op]:
possibilities = handler(retval) possibilities = handler(retval)
...@@ -213,12 +218,20 @@ class Parser(BisonParser): ...@@ -213,12 +218,20 @@ class Parser(BisonParser):
def rewrite(self): def rewrite(self):
suggestion = pick_suggestion(self.last_possibilities) suggestion = pick_suggestion(self.last_possibilities)
if self.verbose:
print 'applying suggestion:', suggestion
if not suggestion: if not suggestion:
return self.root_node return self.root_node
expression = apply_suggestion(self.root_node, self.subtree_map, expression = apply_suggestion(self.root_node, self.subtree_map,
suggestion) suggestion)
if self.verbose:
print 'After application, expression=', expression
self.read_queue.put_nowait(str(expression)) self.read_queue.put_nowait(str(expression))
return expression return expression
#def hook_run(self, filename, retval): #def hook_run(self, filename, retval):
...@@ -266,6 +279,7 @@ class Parser(BisonParser): ...@@ -266,6 +279,7 @@ class Parser(BisonParser):
return values[0] return values[0]
if option == 2: # rule: DEBUG NEWLINE if option == 2: # rule: DEBUG NEWLINE
self.root_node = values[0]
return values[0] return values[0]
if option == 3: # rule: HINT NEWLINE if option == 3: # rule: HINT NEWLINE
...@@ -277,7 +291,8 @@ class Parser(BisonParser): ...@@ -277,7 +291,8 @@ class Parser(BisonParser):
return return
if option == 5: # rule: REWRITE NEWLINE if option == 5: # rule: REWRITE NEWLINE
return self.rewrite() self.root_node = self.rewrite()
return self.root_node
if option == 6: if option == 6:
raise RuntimeError('on_line: exception raised') raise RuntimeError('on_line: exception raised')
...@@ -346,7 +361,12 @@ class Parser(BisonParser): ...@@ -346,7 +361,12 @@ class Parser(BisonParser):
return Node(values[1], values[0], values[2]) return Node(values[1], values[0], values[2])
if option == 4: # rule: exp MINUS exp 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".' raise BisonSyntaxError('Unsupported option %d in target "%s".'
% (option, target)) # pragma: nocover % (option, target)) # pragma: nocover
......
...@@ -61,9 +61,9 @@ def pick_suggestion(possibilities): ...@@ -61,9 +61,9 @@ def pick_suggestion(possibilities):
def apply_suggestion(root, subtree_map, suggestion): def apply_suggestion(root, subtree_map, suggestion):
# clone the root node before modifying. After deep copying the root node, # TODO: clone the root node before modifying. After deep copying the root
# the subtree_map cannot be used since the hash() of each node in the deep # node, the subtree_map cannot be used since the hash() of each node in the
# copied root node has changed. # deep copied root node has changed.
#root_clone = root.clone() #root_clone = root.clone()
subtree = suggestion.handler(suggestion.root, suggestion.args) subtree = suggestion.handler(suggestion.root, suggestion.args)
......
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 .poly import match_combine_polynomes
from .groups import match_combine_groups from .groups import match_combine_groups
from .factors import match_expand from .factors import match_expand
...@@ -9,7 +9,7 @@ from .powers import match_add_exponents, match_subtract_exponents, \ ...@@ -9,7 +9,7 @@ from .powers import match_add_exponents, match_subtract_exponents, \
from .numerics import match_divide_numerics, match_multiply_numerics from .numerics import match_divide_numerics, match_multiply_numerics
from .fractions import match_constant_division, match_add_constant_fractions, \ from .fractions import match_constant_division, match_add_constant_fractions, \
match_expand_and_add_fractions match_expand_and_add_fractions
from .negation import match_negate_group
RULES = { RULES = {
OP_ADD: [match_add_constant_fractions, match_combine_polynomes, \ OP_ADD: [match_add_constant_fractions, match_combine_polynomes, \
...@@ -21,4 +21,5 @@ RULES = { ...@@ -21,4 +21,5 @@ RULES = {
OP_POW: [match_multiply_exponents, match_duplicate_exponent, \ OP_POW: [match_multiply_exponents, match_duplicate_exponent, \
match_remove_negative_exponent, match_exponent_to_root, \ match_remove_negative_exponent, match_exponent_to_root, \
match_extend_exponent], match_extend_exponent],
OP_NEG: [match_negate_group],
} }
from itertools import combinations from itertools import combinations
from ..node import ExpressionNode as Node, ExpressionLeaf as Leaf, Scope, \ 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 ..possibilities import Possibility as P, MESSAGES
from ..translate import _ from ..translate import _
...@@ -34,7 +34,8 @@ def match_combine_groups(node): ...@@ -34,7 +34,8 @@ def match_combine_groups(node):
l = len(scope) l = len(scope)
for i, sub_node in enumerate(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)] others = [scope[j] for j in range(i) + range(i + 1, l)]
if len(others) == 1: if len(others) == 1:
...@@ -56,7 +57,7 @@ def combine_groups(root, args): ...@@ -56,7 +57,7 @@ def combine_groups(root, args):
scope = Scope(root) scope = Scope(root)
if not isinstance(c0, Leaf): if not isinstance(c0, Leaf) and not isinstance(c0, Node):
c0 = Leaf(c0) c0 = Leaf(c0)
# Replace the left node with the new expression # Replace the left node with the new expression
......
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}.')
...@@ -151,7 +151,6 @@ def multiply_numerics(root, args): ...@@ -151,7 +151,6 @@ def multiply_numerics(root, args):
if hash(n) == hash(n0): if hash(n) == hash(n0):
# Replace the left node with the new expression # Replace the left node with the new expression
scope.append(substitution) scope.append(substitution)
#scope.append(n)
elif hash(n) != hash(n1): elif hash(n) != hash(n1):
# Remove the right node # Remove the right node
scope.append(n) scope.append(n)
......
...@@ -46,6 +46,14 @@ class RulesTestCase(unittest.TestCase): ...@@ -46,6 +46,14 @@ class RulesTestCase(unittest.TestCase):
self.assertMultiLineEqual(str(rewrite(exp)), self.assertMultiLineEqual(str(rewrite(exp)),
str(rewrite_chain[i+1])) str(rewrite_chain[i+1]))
except AssertionError: # pragma: nocover except AssertionError: # pragma: nocover
print 'rewrite failed:', exp, '->', rewrite_chain[i+1] print 'rewrite failed: "%s" -> "%s"' \
print 'rewrite chain:', rewrite_chain % (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 raise
...@@ -6,11 +6,9 @@ class TestLeidenOefenopgave(TestCase): ...@@ -6,11 +6,9 @@ class TestLeidenOefenopgave(TestCase):
for chain in [['-5(x2 - 3x + 6)', '-5(x ^ 2 - 3x) - 5 * 6', for chain in [['-5(x2 - 3x + 6)', '-5(x ^ 2 - 3x) - 5 * 6',
'-5 * x ^ 2 - 5 * -3x - 5 * 6', '-5 * x ^ 2 - 5 * -3x - 5 * 6',
'-5 * x ^ 2 - -15x - 5 * 6', '-5 * x ^ 2 - -15x - 5 * 6',
# FIXME: '-5 * x ^ 2 - 5 * -3x - 30', '-5 * x ^ 2 + 15x - 5 * 6',
# FIXME: '-5 * x ^ 2 - -15x - 5 * 6', '-5 * x ^ 2 + 15x - 30',
# FIXME: '-5 * x ^ 2 + 15x - 5 * 6', ],
# FIXME: '-5 * x ^ 2 + 15x - 30',
], #'-30 + 15 * x - 5 * x ^ 2'],
]: ]:
self.assertRewrite(chain) self.assertRewrite(chain)
...@@ -70,12 +68,21 @@ class TestLeidenOefenopgave(TestCase): ...@@ -70,12 +68,21 @@ class TestLeidenOefenopgave(TestCase):
'xx + x * -1 - 1x - 1 * -1', 'xx + x * -1 - 1x - 1 * -1',
'x ^ (1 + 1) + 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',
# FIXME: 'x ^ 2 + (-1 - 1)x - 1 * -1', 'x ^ 2 + (-1 - 1)x - 1 * -1',
# FIXME: 'x ^ 2 - 2x - 1 * -1', 'x ^ 2 - 2x - 1 * -1',
# FIXME: 'x ^ 2 - 2x + 1', 'x ^ 2 - 2x + 1',
]]: ]]:
self.assertRewrite(chain) 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): def test_2(self):
pass pass
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment