Commit e17779f2 authored by Taddeüs Kroes's avatar Taddeüs Kroes

Reimplemented line printer using precedences in accordance with the parser.

- All special cases for negation have been removed, except when they are also
  present in the parser.
- Some callbacks have been added to be able to customize output for certain
  operators in an extension of the Node class. This prevents chaotic sceneries
  in the graph_drawing library.
- Some useful unit tests have been added to test the precedence- and
  callback-meschanisms.
parent 107e93a1
This diff is collapsed.
......@@ -7,6 +7,7 @@ class Node(object):
super(Node, self).__init__()
self.value, self.nodes = value, list(nodes)
self.is_leaf = False
self.no_spacing = kwargs.get('no_spacing', False)
self.negated = kwargs.get('negated', 0)
def __getitem__(self, n):
......@@ -26,11 +27,17 @@ class Node(object):
and self.nodes == node.nodes
def __neg__(self):
copied = deepcopy(self)
copied = self.clone()
copied.negated += 1
return copied
def __pos__(self):
copied = self.clone()
copied.negated = max(copied.negated - 1, 0)
return copied
def __str__(self):
return '<Node value=%s nodes=%s negated=%d>' \
% (str(self.value), str(self.nodes), self.negated)
......@@ -41,11 +48,39 @@ class Node(object):
def title(self):
return str(self.value)
def operator(self):
return self.value
def clone(self):
return deepcopy(self)
def arity(self):
return len(self)
def is_postfix(self):
return self.value == '\''
def is_negation(self):
return self.value == '-' and len(self) == 1
def custom_line(self):
pass
def preprocess_str_exp(self):
pass
def postprocess_str(self, string):
return string
class Leaf(Node):
def __init__(self, value, **kwargs):
super(Leaf, self).__init__(value, **kwargs)
self.value = value
if type(value) in (int, float) and value < 0:
self.value = abs(value)
self.negated += 1
self.nodes = None
self.is_leaf = True
......
import unittest
import doctest
import new
import line
from node import Node as N, Leaf as L
......@@ -38,6 +39,8 @@ class TestLine(unittest.TestCase):
minus = N('-', l0, plus)
self.assertEquals(generate_line(minus), '1 - (2 + 3)')
power = N('^', l0, N('_', l1, l2))
self.assertEquals(generate_line(power), '1 ^ 2 _ 3')
power = N('^', l0, N('^', l1, l2))
self.assertEquals(generate_line(power), '1 ^ 2 ^ 3')
power = N('^', N('^', l0, l1), l2)
......@@ -63,11 +66,8 @@ class TestLine(unittest.TestCase):
self.assertEquals(generate_line(plus), '1 + 2 + 3')
def test_function(self):
exp = L('x')
inf = L('oo')
minus_inf = -L('oo')
integral = N('int', exp, minus_inf, inf)
self.assertEquals(generate_line(integral), 'int(x, -oo, oo)')
sin = N('sin', N('*', L(2), L('x')))
self.assertEquals(generate_line(sin), 'sin(2x)')
def test_mod(self):
l0, l1 = L(1), L(2)
......@@ -77,7 +77,7 @@ class TestLine(unittest.TestCase):
def test_multiplication_identifiers(self):
a, b = L('a'), L('b')
self.assertEquals(generate_line(N('*', a, b)), 'ab')
self.assertEquals(generate_line(N('*', a, -b)), 'a(-b)')
self.assertEquals(generate_line(N('*', a, -b)), 'a * -b')
def test_multiplication_constant_identifier(self):
l0, a = L(2), L('a')
......@@ -206,8 +206,14 @@ class TestLine(unittest.TestCase):
neg = -N('-', L(1), L(2))
self.assertEquals(generate_line(neg), '-(1 - 2)')
# FIXME: neg = N('+', L(1), N('+', L(1), L(2)))
# FIXME: self.assertEquals(generate_line(neg), '1 + 1 + 2')
neg = N('+', N('+', L(1), L(2)), L(3))
self.assertEquals(generate_line(neg), '1 + 2 + 3')
neg = N('+', L(1), N('+', L(1), L(2)))
self.assertEquals(generate_line(neg), '1 + 1 + 2')
self.assertEquals(generate_line(neg), '1 + (1 + 2)')
neg = N('+', L(1), -N('+', L(1), L(2)))
self.assertEquals(generate_line(neg), '1 - (1 + 2)')
......@@ -219,7 +225,7 @@ class TestLine(unittest.TestCase):
self.assertEquals(generate_line(neg), '-4a')
neg = N('*', L(4), -L('a'))
self.assertEquals(generate_line(neg), '4(-a)')
self.assertEquals(generate_line(neg), '4 * -a')
neg = -N('*', L(4), L(5))
self.assertEquals(generate_line(neg), '-4 * 5')
......@@ -234,11 +240,15 @@ class TestLine(unittest.TestCase):
self.assertEquals(generate_line(plus), 'a / b - c / d')
mul = N('*', N('+', L('a'), L('b')), -N('+', L('c'), L('d')))
self.assertEquals(generate_line(mul), '(a + b)(-(c + d))')
self.assertEquals(generate_line(mul), '(a + b) * -(c + d)')
def test_double_negation(self):
neg = --L(1)
self.assertEquals(generate_line(neg), '--1')
neg = --N('*', L('x'), L(2))
self.assertEquals(generate_line(neg), '--x * 2')
neg = --N('^', L('x'), L(2))
self.assertEquals(generate_line(neg), '--x ^ 2')
def test_divide_fractions(self):
a, b, c, d = L('a'), L('b'), L('c'), L('d')
......@@ -246,3 +256,85 @@ class TestLine(unittest.TestCase):
self.assertEquals(generate_line(div), 'a / (b / c)')
div = N('/', N('/', a, b), N('/', c, d))
self.assertEquals(generate_line(div), 'a / b / (c / d)')
def test_prime(self):
a, b, c, d = L('a'), L('b'), L('c'), L('d')
root = N('*', a, N("'", b))
self.assertEquals(generate_line(root), "a b'")
root = N("'", -a)
self.assertEquals(generate_line(root), "-a'")
root = -N("'", a)
self.assertEquals(generate_line(root), "-(a')")
root = N("'", N('*', a, b))
self.assertEquals(generate_line(root), "(ab)'")
root = N("'", N('/', a, b))
def test_function(self):
root = N('sin', L('x'))
self.assertEquals(generate_line(root), 'sin x')
root = N('sin', N('+', L('x'), L(2)))
self.assertEquals(generate_line(root), 'sin(x + 2)')
root = N('dummyfunc', L('x'), L(2))
self.assertEquals(generate_line(root), 'dummyfunc(x, 2)')
def test_no_spacing(self):
root = N('+', L('x'), L(2), no_spacing=True)
self.assertEquals(generate_line(root), 'x+2')
def test_explicit_parentheses(self):
root = N('[]', L('x'))
self.assertEquals(generate_line(root), '[x]')
root = N('()', L('x'))
self.assertEquals(generate_line(root), '(x)')
root = N('{}', L('x'))
self.assertEquals(generate_line(root), '{x}')
root = N('^', N('[]', N('+', L('x'), L('y'))), L(2))
self.assertEquals(generate_line(root), '[x + y] ^ 2')
def test_abs(self):
root = N('||', L('x'))
self.assertEquals(generate_line(root), '|x|')
root = N('||', N('+', L('x'), L(1)))
self.assertEquals(generate_line(root), '|x + 1|')
root = N('ln', N('||', L('x')))
self.assertEquals(generate_line(root), 'ln|x|')
def test_postprocess_str(self):
root = N('int', N('^', L('x'), L(2)), L('x'))
root.arity = lambda: 1
root.postprocess_str = lambda s: s + ' dx'
self.assertEquals(generate_line(root), 'int x ^ 2 dx')
def test_concat_with_negation(self):
root = N('*', -L(2), L('x'))
self.assertEquals(generate_line(root), '(-2)x')
root = N('*', N('*', L(3), -L(2)), L('x'))
self.assertEquals(generate_line(root), '3 * -2x')
root = N('*', L(3), -L(2), L('x'))
self.assertEquals(generate_line(root), '3 * -2 * x')
def test_first_child_negation(self):
root = N('*', -L(1), L(2))
self.assertEquals(generate_line(root), '(-1)2')
root = -N('*', L(1), L(2))
self.assertEquals(generate_line(root), '-1 * 2')
root = N('/', -L(1), L(2))
self.assertEquals(generate_line(root), '(-1) / 2')
root = -N('/', L(1), L(2))
self.assertEquals(generate_line(root), '-1 / 2')
def test_postfix_brackets(self):
root = N('*', L('x'), N("'", N('[]', N('^', L('x'), L(2)))))
self.assertEquals(generate_line(root), "x[x ^ 2]'")
def test_custom_line(self):
root = N('*', L(1), L(2))
root.custom_line = lambda: 'test'
self.assertEquals(generate_line(root), 'test')
def test_preprocess_str_exp(self):
root = N('-', L(1))
def addbrackets(self): self[0] = N('[]', self[0])
root.preprocess_str_exp = new.instancemethod(addbrackets, root)
self.assertEquals(generate_line(root), '-[1]')
......@@ -51,3 +51,6 @@ class TestNode(unittest.TestCase):
self.assertEqual(Node('+', l1, l2, negated=1).negated, 1)
self.assertEqual(Leaf(1, negated=2).negated, 2)
def test_negated_int_constructor(self):
self.assertEquals(-Leaf(2), Leaf(-2))
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