import sys
from unittest import TestCase
from io import StringIO
from decimal import Decimal
import math
import operator

from rpnpy import Calculator
from rpnpy.errors import StackError
from rpnpy.modifiers import Modifiers, strToModifiers


class TestDebug(TestCase):
    "Test setting/toggling debug output"

    def testDebugOffByDefault(self):
        "debugging must be off by default"
        c = Calculator()
        self.assertFalse(c._debug)

    def testStartWithDebugOn(self):
        "debugging must be settable via __init__"
        c = Calculator(debug=True)
        self.assertTrue(c._debug)

    def testDebugOn(self):
        "It must be possible to turn debugging on"
        c = Calculator()
        c.toggleDebug(True)
        self.assertTrue(c._debug)

    def testDebugOff(self):
        "It must be possible to turn debugging off"
        c = Calculator()
        c.toggleDebug(False)
        self.assertFalse(c._debug)

    def testToggleDebugOn(self):
        "It must be possible to toggle debugging on"
        c = Calculator()
        # Double-check it starts out off (don't rely on above test).
        self.assertFalse(c._debug)
        c.toggleDebug()
        self.assertTrue(c._debug)

    def testToggleDebugOff(self):
        "It must be possible to toggle debugging on"
        c = Calculator()
        # Double-check it starts out off (don't rely on above test).
        c.toggleDebug()
        self.assertTrue(c._debug)
        c.toggleDebug()
        self.assertFalse(c._debug)

    def testDebugOnViaModifier(self):
        "It must be possible to turn debugging on using a modifier"
        c = Calculator()
        c.execute(':D')
        self.assertTrue(c._debug)

    def testDebugOffViaModifier(self):
        "It must be possible to turn debugging off using a modifier"
        c = Calculator(debug=True)
        c.execute(':D')
        self.assertFalse(c._debug)


class TestCalculator(TestCase):

    def testEmptyStack(self):
        "A calculator must start life with an empty stack"
        self.assertEqual([], Calculator().stack)

    def testPushNumber(self):
        "Pushing a number must work as expected"
        c = Calculator()
        c.execute('4')
        (result,) = c.stack
        self.assertEqual(4, result)

    def testPop(self):
        "Calling pop must work as expected"
        c = Calculator()
        c.execute('4 5 pop')
        (result,) = c.stack
        self.assertEqual(4, result)

    def testPop2(self):
        "Calling pop with a count of 2 must work as expected"
        c = Calculator()
        c.execute('3 4 5 pop:2')
        (result,) = c.stack
        self.assertEqual(3, result)

    def testPopAll(self):
        "Calling pop on the whole stack must work as expected"
        c = Calculator()
        c.execute('3 4 5 pop:*')
        self.assertEqual([], c.stack)

    def testPushString(self):
        "Pushing a string must work as expected"
        c = Calculator()
        c.execute("'4'")
        (result,) = c.stack
        self.assertEqual('4', result)

    def testPushAbs(self):
        "Pushing the operator.abs function must work as expected"
        c = Calculator()
        c.execute('operator.abs :!')
        (result,) = c.stack
        self.assertIs(operator.abs, result)

    def testAdd(self):
        "Adding must work"
        c = Calculator()
        c.execute('3 4 +')
        (result,) = c.stack
        self.assertEqual(7, result)

    def testAddOneArg(self):
        "Adding must give an error if there is only one thing on the stack"
        err = StringIO()
        c = Calculator(errfp=err)
        c.execute('3 +')
        self.assertEqual(
            'Not enough args on stack! (+ needs 2 args, stack has 1 item)\n',
            err.getvalue())

    def testRegisterWithArgCount(self):
        """Registering and calling a new function and passing its argument
           count must work"""
        def double(n):
            return 2 * n

        c = Calculator()
        c.register(double, name='double', nArgs=1)
        c.execute('3 double')
        (result,) = c.stack
        self.assertEqual(6, result)

    def testRegisterWithArgCount2(self):
        """Registering and calling a new function and passing its argument
           count must work"""
        def doubleMax(*args):
            return 2 * max(*args)

        c = Calculator()
        c.register(doubleMax, name='doubleMax', nArgs=3)
        c.execute('3 5 6 7 doubleMax')
        (onStack, result) = c.stack
        self.assertEqual(14, result)
        self.assertEqual(3, onStack)

    def testRegisterWithoutArgCount(self):
        """Registering and calling a new function without passing its argument
           count must work"""
        def addAndDouble(i, j):
            return 2 * (i + j)

        c = Calculator()
        c.register(addAndDouble, name='addAndDouble')
        c.execute('3 5 addAndDouble')
        (result,) = c.stack
        self.assertEqual(16, result)

    def testRegisterCelciusNoExplicitName(self):
        """Register and call a function to convert Fahrenheit to Celcius but do
        not pass a function name"""
        def celcius(f):
            return (f - 32) / 1.8

        c = Calculator()
        c.register(celcius)
        c.execute('212 celcius')
        (result,) = c.stack
        self.assertEqual(100, result)

    def testRegisterLambdaWithExplicitName(self):
        "Register and call a function defined using lambda with a passed name"
        c = Calculator()
        c.register(lambda: 3, name='xx')
        c.execute('xx')
        (result,) = c.stack
        self.assertEqual(3, result)

    def testRegisterLambdaWithNoName(self):
        "Register and call a function defined using lambda and no passed name"
        c = Calculator()
        c.register(lambda: 3)
        # The function will be called '<lambda>', which is a bit dangerous
        # (could easily be accidentally be overwritten).
        c.execute('<lambda>')
        (result,) = c.stack
        self.assertEqual(3, result)

    def testSumOneArg(self):
        "sum must take one arg by default"
        c = Calculator()
        self.assertEqual(1, c._functions['sum'].nArgs)

    def testPowTwoArgs(self):
        "pow must take two args by default"
        c = Calculator()
        self.assertEqual(2, c._functions['pow'].nArgs)

    def testSwap(self):
        "swap must work as expected"
        c = Calculator()
        c.execute('4 5')
        self.assertEqual([4, 5], c.stack)
        c.execute('swap')
        self.assertEqual([5, 4], c.stack)

    def testDup(self):
        "dup must work as expected"
        c = Calculator()
        c.execute('4 d')
        self.assertEqual([4, 4], c.stack)
        c.execute('dup')
        self.assertEqual([4, 4, 4], c.stack)

    def testIterateString(self):
        "The :i (iterate) modifier must work as expected on a string"
        c = Calculator()
        c.execute('"hello" :i')
        (result,) = c.stack
        self.assertEqual(['h', 'e', 'l', 'l', 'o'], result)

    def testIterateGenerator(self):
        "The :i (iterate) modifier must work as expected on a generator"
        c = Calculator()
        c.execute('str :!')
        c.execute('[1,2,3]')
        c.execute('map :i')
        (result,) = c.stack
        self.assertEqual(['1', '2', '3'], result)

    def testItemgetter(self):
        "itemgetter must work correctly"
        c = Calculator(splitLines=False)
        c.execute('1')
        c.execute('itemgetter')
        c.execute('[[1, 2, 3], [4, 5, 6]]')
        c.execute('map :i')
        (result,) = c.stack
        self.assertEqual([2, 5], result)

    def testList(self):
        "Converting the top of the stack to a list must work"
        c = Calculator()
        c.execute('"hey" list')
        (result,) = c.stack
        self.assertEqual(['h', 'e', 'y'], result)

    def testList2(self):
        "Converting the top two items of the stack to a list must work"
        c = Calculator()
        c.execute('4 5 list:2')
        c.printStack()
        (result,) = c.stack
        self.assertEqual([4, 5], result)

    def testListAll(self):
        "Converting all items of the stack to a list must work"
        c = Calculator()
        c.execute('4 5 6 list:*')
        c.printStack()
        (result,) = c.stack
        self.assertEqual([4, 5, 6], result)

    def testPushList(self):
        "It must be possible to push list onto the stack"
        c = Calculator()
        c.execute('list :!')
        (result,) = c.stack
        self.assertIs(list, result)

    def testPushNone(self):
        "It must be possible to push None onto the stack"
        c = Calculator()
        c.execute('None')
        (result,) = c.stack
        self.assertIs(None, result)

    def testPushTrue(self):
        "It must be possible to push True onto the stack"
        c = Calculator()
        c.execute('True')
        (result,) = c.stack
        self.assertIs(True, result)

    def testPushFalse(self):
        "It must be possible to push False onto the stack"
        c = Calculator()
        c.execute('False')
        (result,) = c.stack
        self.assertIs(False, result)

    def testBatch(self):
        "It must be possible to read from a file using the batch method"
        infp = StringIO('3\n4\n+\np\n')
        outfp = StringIO()
        c = Calculator(outfp=outfp)
        c.batch(infp)
        self.assertEqual('7\n', outfp.getvalue())

    def testREPL(self):
        "It must be possible to read input in REPL form"
        infp = StringIO('3\n4\n+\np\n')
        outfp = StringIO()
        c = Calculator(outfp=outfp)
        stdin = sys.stdin
        sys.stdin = infp
        c.repl('prompt')
        sys.stdin = stdin
        self.assertEqual('7\n', outfp.getvalue())

    def testCountConflictsWithAllModifier(self):
        "If :* and a count is given, it's an error if they don't agree"
        errfp = StringIO()
        c = Calculator(errfp=errfp)
        self.assertFalse(c.execute('4 5 6 list:*5'))
        error = ('* modifier conflicts with explicit count 5 '
                 '(stack has 3 items)\n')
        self.assertEqual(error, errfp.getvalue())

    def testFloat(self):
        "It must be possible to convert something to a float"
        c = Calculator()
        c.execute('4 float')
        (result,) = c.stack
        self.assertEqual(4.0, result)


class TestReverseModifier(TestCase):
    "Test the reverse modifier"

    def testReverseSubtractionArgs(self):
        "Reverse the args in a subtraction"
        c = Calculator()
        c.execute('5 4 -:r')
        (result,) = c.stack
        self.assertEqual(-1, result)

    def testMap(self):
        "Call map with args reversed from their normal order"
        c = Calculator()
        c.execute('[1,2,3]')
        c.execute('str :!')
        c.execute('map :ir')
        (result,) = c.stack
        self.assertEqual(['1', '2', '3'], result)


class TestReverseSpecialCommand(TestCase):
    "Test the reverse special command"

    def testReverse1(self):
        "Reversing just the top element of the stack does nothing"
        c = Calculator()
        c.execute('4 5 reverse:1')
        self.assertEqual([4, 5], c.stack)

    def testReverse(self):
        "Reverse (by default) the top two elements of the stack"
        c = Calculator()
        c.execute('4 5 reverse')
        self.assertEqual([5, 4], c.stack)

    def testReverseAll(self):
        "It must be possible to reverse all the stack"
        c = Calculator()
        c.execute('4 6 5 reverse:*')
        self.assertEqual([5, 6, 4], c.stack)

    def testReverseWithStackTooSmall(self):
        "It's an error if an attempt is made to reverse too many things"
        errfp = StringIO()
        c = Calculator(errfp=errfp)
        self.assertFalse(c.execute('4 5 6 reverse:10'))
        error = ("Could not run special command 'reverse': Cannot reverse 10 "
                 "items (stack length is 3)\n")
        self.assertEqual(error, errfp.getvalue())


class TestDecimal(TestCase):
    "Test working with Decimal instances"

    def testPush(self):
        "It must be possible to push Decimal by itself"
        c = Calculator()
        c.execute('Decimal :!')
        (result,) = c.stack
        self.assertIs(Decimal, result)

    def testPushValue(self):
        "It must be possible to push a Decimal value"
        c = Calculator()
        c.execute('Decimal(4)')
        (result,) = c.stack
        self.assertEqual(Decimal(4), result)

    def testArithmetic(self):
        "It must be possible to operate on Decimals"
        c = Calculator()
        c.execute('Decimal(4) Decimal(6) +')
        (result,) = c.stack
        self.assertEqual(Decimal(10), result)
        c.execute('int')
        (result,) = c.stack
        self.assertEqual(10, result)


class TestJoin(TestCase):
    "Test the join special function"

    def testEmptyString(self):
        "Joining on an empty string must work"
        c = Calculator()
        c.execute('"" ["4","5","6"] join')
        (result,) = c.stack
        self.assertEqual('456', result)

    def testNonEmptyString(self):
        "Joining on a non-empty string must work"
        c = Calculator()
        c.execute('"-" ["4","5","6"] join')
        (result,) = c.stack
        self.assertEqual('4-5-6', result)

    def testNonStrings(self):
        "Joining things that are not string must work"
        c = Calculator()
        c.execute('"-" [4,5,6] join')
        (result,) = c.stack
        self.assertEqual('4-5-6', result)

    def testWithCount(self):
        "Joining several stack items must work"
        c = Calculator()
        c.execute('3 "-" 4 5 6 join:3')
        self.assertEqual([3, '4-5-6'], c.stack)

    def testAllStack(self):
        "Joining the whole stack"
        c = Calculator()
        c.execute('"-" 3 4 5 6 join:*')
        (result,) = c.stack
        self.assertEqual('3-4-5-6', result)

    def testEmptyStringReversed(self):
        "Joining on an empty string must work"
        c = Calculator()
        c.execute('["4","5","6"] "" join:r')
        (result,) = c.stack
        self.assertEqual('456', result)

    def testNonEmptyStringReversed(self):
        "Joining on a non-empty string must work"
        c = Calculator()
        c.execute('["4","5","6"] "-" join:r')
        (result,) = c.stack
        self.assertEqual('4-5-6', result)

    def testNonStringsReversed(self):
        "Joining things that are not string must work"
        c = Calculator()
        c.execute('[4,5,6] "-" join:r')
        (result,) = c.stack
        self.assertEqual('4-5-6', result)

    def testWithCountReversed(self):
        "Joining several stack items must work"
        c = Calculator()
        c.execute('3 4 5 6 "-" join:3r')
        self.assertEqual([3, '4-5-6'], c.stack)

    def testAllStackReversed(self):
        "Joining the whole stack"
        c = Calculator()
        c.execute('3 4 5 6 "-" join:*r')
        (result,) = c.stack
        self.assertEqual('3-4-5-6', result)


class TestFindCallableAndArgs(TestCase):
    "Test the findCallableAndArgs function"

    def testEmptyStack(self):
        "Calling on an empty stack must return None, None"
        c = Calculator()

        error = r"^Cannot run 'cmd' \(stack has only 0 items\)$"
        self.assertRaisesRegex(StackError, error, c.findCallableAndArgs,
                               'cmd', Modifiers(), None)

    def testStackLengthOne(self):
        "Calling on a stack with only one item must return None, None"
        errfp = StringIO()
        c = Calculator(errfp=errfp)
        c.execute('4')
        error = r"^Cannot run 'cmd' \(stack has only 1 item\)$"
        self.assertRaisesRegex(StackError, error, c.findCallableAndArgs,
                               'cmd', Modifiers(), None)

    def testStackLengthTwoNoCount(self):
        """Calling on a stack with two items and no count must return
        correctly. The number of stack items is obtained from the function
        signature"""
        c = Calculator()
        c.execute('log10 :!')
        c.execute('4')
        func, args = c.findCallableAndArgs('cmd', Modifiers(), None)
        self.assertIs(math.log10, func)
        self.assertEqual([4], args)

    def testStackLengthTwoWithCount(self):
        """Calling on a stack with two items and a count must return
        correctly."""
        c = Calculator()
        c.execute('log10 :!')
        c.execute('4')
        func, args = c.findCallableAndArgs('cmd', Modifiers(), 1)
        self.assertIs(math.log10, func)
        self.assertEqual([4], args)

    def testStackLengthThreeWithCountError(self):
        """Calling on a stack with three items and a count that points to a
        non-callable must result in an error"""
        errfp = StringIO()
        c = Calculator(errfp=errfp)
        c.execute('log10 :!')
        c.execute('4')
        c.execute('5')
        error = (r"^Cannot run 'cmd' with 1 argument. Stack item \(4\) is "
                 r"not callable$")
        self.assertRaisesRegex(StackError, error, c.findCallableAndArgs,
                               'cmd', Modifiers(), 1)

    def testStackLengthThree(self):
        "Calling on a stack with three items (count=2) must return correctly"
        c = Calculator()
        c.execute('log10 :!')
        c.execute('4')
        c.execute('5')
        func, args = c.findCallableAndArgs('cmd', Modifiers(), 2)
        self.assertIs(math.log10, func)
        self.assertEqual([4, 5], args)

    def testAllStack(self):
        "Calling on all the stack must return correctly"
        c = Calculator()
        c.execute('log10 :!')
        c.execute('4')
        c.execute('5')
        c.execute('6')
        func, args = c.findCallableAndArgs('cmd', strToModifiers('*'), None)
        self.assertIs(math.log10, func)
        self.assertEqual([4, 5, 6], args)


class TestFindCallableAndArgsReversed(TestCase):
    "Test the findCallableAndArgs function when the reversed modifier is used"

    def testEmptyStack(self):
        "Calling on an empty stack must return None, None"
        c = Calculator()
        error = r"Cannot run 'cmd' \(stack has only 0 items\)$"
        self.assertRaisesRegex(StackError, error, c.findStringAndArgs,
                               'cmd', strToModifiers('r'), None)

    def testStackLengthOne(self):
        "Calling on a stack with only one item must return None, None"
        c = Calculator()
        c.execute('4')
        error = r"Cannot run 'cmd' \(stack has only 1 item\)$"
        self.assertRaisesRegex(StackError, error, c.findStringAndArgs,
                               'cmd', strToModifiers('r'), None)

    def testStackLengthTwoNoCount(self):
        """Calling on a stack with two items and no count must return
        correctly. The number of stack items is obtained from the function
        signature"""
        c = Calculator()
        c.execute('4')
        c.execute('log10 :!')
        func, args = c.findCallableAndArgs('cmd', strToModifiers('r'), None)
        self.assertIs(math.log10, func)
        self.assertEqual([4], args)

    def testStackLengthTwoWithCount(self):
        """Calling on a stack with two items and a count must return
        correctly."""
        c = Calculator()
        c.execute('4')
        c.execute('log10 :!')
        func, args = c.findCallableAndArgs('cmd', strToModifiers('r'), 1)
        self.assertIs(math.log10, func)
        self.assertEqual([4], args)

    def testStackLengthThree(self):
        "Calling on a stack with three items (count=2) must return correctly"
        c = Calculator()
        c.execute('5')
        c.execute('4')
        c.execute('log10 :!')
        func, args = c.findCallableAndArgs('cmd', strToModifiers('r'), 2)
        self.assertIs(math.log10, func)
        self.assertEqual([5, 4], args)

    def testAllStack(self):
        "Calling on all the stack must return correctly"
        c = Calculator()
        c.execute('4')
        c.execute('5')
        c.execute('6')
        c.execute('log10 :!')
        func, args = c.findCallableAndArgs('cmd', strToModifiers('*r'), None)
        self.assertIs(math.log10, func)
        self.assertEqual([4, 5, 6], args)


class TestFindStringAndArgs(TestCase):
    "Test the findStringAndArgs function"

    def testEmptyStack(self):
        "Calling on an empty stack must return None, None"
        c = Calculator()
        error = r"^Cannot run 'cmd' \(stack has only 0 items\)$"
        self.assertRaisesRegex(StackError, error, c.findStringAndArgs,
                               'cmd', Modifiers(), None)

    def testStackLengthOne(self):
        "Calling on a stack with only one item must return None, None"
        c = Calculator()
        c.execute('4')
        error = r"^Cannot run 'cmd' \(stack has only 1 item\)$"
        self.assertRaisesRegex(StackError, error, c.findStringAndArgs,
                               'cmd', Modifiers(), None)

    def testStackLengthTwoNoCount(self):
        """Calling on a stack with two items and no count must return
        correctly. The number of stack items is obtained from the function
        signature"""
        c = Calculator()
        c.execute("'string'")
        c.execute('4')
        string, args = c.findStringAndArgs('cmd', Modifiers(), None)
        self.assertEqual('string', string)
        self.assertEqual([4], args)

    def testStackLengthTwoWithCount(self):
        """Calling on a stack with two items and a count must return
        correctly."""
        c = Calculator()
        c.execute("'string'")
        c.execute('4')
        string, args = c.findStringAndArgs('cmd', Modifiers(), 1)
        self.assertEqual('string', string)
        self.assertEqual([4], args)

    def testStackLengthThreeWithCountError(self):
        """Calling on a stack with three items and a count that points to a
        non-string must result in an error"""
        c = Calculator()
        c.execute("'string'")
        c.execute('4')
        c.execute('5')
        error = (r"^Cannot run 'cmd' with 1 argument. Stack item \(4\) is "
                 r"not a string$")
        self.assertRaisesRegex(StackError, error, c.findStringAndArgs,
                               'cmd', Modifiers(), 1)

    def testStackLengthThree(self):
        "Calling on a stack with three items (count=2) must return correctly"
        c = Calculator()
        c.execute("'string'")
        c.execute('4')
        c.execute('5')
        string, args = c.findStringAndArgs('cmd', Modifiers(), 2)
        self.assertEqual('string', string)
        self.assertEqual([4, 5], args)

    def testAllStack(self):
        "Calling on all the stack must return correctly"
        c = Calculator()
        c.execute("'string'")
        c.execute('4')
        c.execute('5')
        c.execute('6')
        string, args = c.findStringAndArgs('cmd', strToModifiers('*'), None)
        self.assertEqual('string', string)
        self.assertEqual([4, 5, 6], args)


class TestFindStringAndArgsReversed(TestCase):
    "Test the findStringAndArgs function when the reversed modifier is used"

    def testEmptyStack(self):
        "Calling on an empty stack must return None, None"
        c = Calculator()
        error = r"^Cannot run 'cmd' \(stack has only 0 items\)$"
        self.assertRaisesRegex(StackError, error, c.findStringAndArgs,
                               'cmd', strToModifiers('r'), None)

    def testStackLengthOne(self):
        "Calling on a stack with only one item must result in a StackError"
        c = Calculator()
        c.execute('4')
        error = r"^Cannot run 'cmd' \(stack has only 1 item\)$"
        self.assertRaisesRegex(StackError, error, c.findStringAndArgs,
                               'cmd', strToModifiers('r'), None)

    def testStackLengthTwoNoCount(self):
        """Calling on a stack with two items and no count must return
        correctly. The number of stack items is obtained from the function
        signature"""
        c = Calculator()
        c.execute('4')
        c.execute("'string'")
        string, args = c.findStringAndArgs('cmd', strToModifiers('r'), None)
        self.assertEqual('string', string)
        self.assertEqual([4], args)

    def testStackLengthTwoWithCount(self):
        """Calling on a stack with two items and a count must return
        correctly."""
        c = Calculator()
        c.execute('4')
        c.execute("'string'")
        string, args = c.findStringAndArgs('cmd', strToModifiers('r'), 1)
        self.assertEqual('string', string)
        self.assertEqual([4], args)

    def testStackLengthThree(self):
        "Calling on a stack with three items (count=2) must return correctly"
        c = Calculator()
        c.execute('5')
        c.execute('4')
        c.execute("'string'")
        string, args = c.findStringAndArgs('cmd', strToModifiers('r'), 2)
        self.assertEqual('string', string)
        self.assertEqual([5, 4], args)

    def testAllStack(self):
        "Calling on all the stack must return correctly"
        c = Calculator()
        c.execute('4')
        c.execute('5')
        c.execute('6')
        c.execute("'string'")
        string, args = c.findStringAndArgs('cmd', strToModifiers('*r'), None)
        self.assertEqual('string', string)
        self.assertEqual([4, 5, 6], args)


class TestApply(TestCase):
    "Test the apply special function"

    def testApplyNoCount(self):
        "apply must work correctly when not given a count"
        c = Calculator()
        c.execute('abs :!')
        c.execute('-1')
        c.execute('apply')
        (result,) = c.stack
        self.assertEqual(1, result)

    def testApplyWithCount(self):
        "apply must work correctly when given a count"
        c = Calculator()
        c.execute('+ :!')
        c.execute('3 5')
        c.execute('apply :2')
        (result,) = c.stack
        self.assertEqual(8, result)


class TestReduce(TestCase):
    "Test the reduce special function"

    def testReduceAl(self):
        "Reduce must work correctly when told to operate on the whole stack"
        c = Calculator()
        c.execute('+ :!')
        c.execute('5')
        c.execute('6')
        c.execute('7')
        c.execute('reduce :*')
        (result,) = c.stack
        self.assertEqual(18, result)

    def testReduceWithCount(self):
        "Reduce must work correctly when given a count"
        c = Calculator()
        c.execute('+ :!')
        c.execute('5')
        c.execute('6')
        c.execute('reduce :2')
        (result,) = c.stack
        self.assertEqual(11, result)


class TestReduceReversed(TestCase):
    "Test the reduce special function when the reverse modifier is used"

    def testReduceAl(self):
        "Reduce must work correctly when told to operate on the whole stack"
        c = Calculator()
        c.execute('5')
        c.execute('6')
        c.execute('7')
        c.execute('+ :!')
        c.execute('reduce :*r')
        (result,) = c.stack
        self.assertEqual(18, result)

    def testReduceWithCount(self):
        "Reduce must work correctly when given a count"
        c = Calculator()
        c.execute('4')
        c.execute('5')
        c.execute('6')
        c.execute('+ :!')
        c.execute('reduce :2r')
        self.assertEqual([4, 11], c.stack)
