diff --git a/plugins/FunCommands.py b/plugins/FunCommands.py index ce31dcf70..e64dc416a 100644 --- a/plugins/FunCommands.py +++ b/plugins/FunCommands.py @@ -73,7 +73,7 @@ import callbacks example = utils.wrapLines(""" @list FunCommands - base, binary, calc, chr, coin, cpustats, decode, dice, dns, encode, hexlify, kernel, last, lastfrom, leet, levenshtein, lithp, md5, mimetype, netstats, objects, ord, pydoc, rot13, rpn, sha, soundex, unhexlify, uptime, urlquote, urlunquote, xor + base, binary, chr, coin, cpustats, decode, dice, dns, encode, hexlify, kernel, last, lastfrom, leet, levenshtein, lithp, md5, mimetype, netstats, objects, ord, pydoc, rot13, sha, soundex, unhexlify, uptime, urlquote, urlunquote, xor @netstats I have received 211 messages for a total of 19535 bytes. I have sent 109 messages for a total of 8672 bytes. @ord A @@ -129,12 +129,6 @@ example = utils.wrapLines(""" I have taken 5.4 seconds of user time and 0.29 seconds of system time, for a total of 5.69 seconds of CPU time. My children have taken 0.0 seconds of user time and 0.0 seconds of system time for a total of 0.0 seconds of CPU time. I've taken a total of 0.00329929635973% of this computer's time. Out of 2 spawned threads, I have 1 active. @uptime I have been running for 28 minutes and 47 seconds. - @calc e**(i*pi) + 1 - 0 - @calc 1+2+3 - 6 - @rpn 1 2 3 + + - 6 @objects I have 24941 objects: 234 modules, 716 classes, 5489 functions, 1656 dictionaries, 827 lists, and 14874 tuples (and a few other different types). I have a total of 119242 references. @levenshtein supybot supbot @@ -487,128 +481,6 @@ class FunCommands(callbacks.Privmsg): irc.reply(msg, response) - ### - # So this is how the 'calc' command works: - # First, we make a nice little safe environment for evaluation; basically, - # the names in the 'math' and 'cmath' modules. Then, we remove the ability - # of a random user to get ints evaluated: this means we have to turn all - # int literals (even octal numbers and hexadecimal numbers) into floats. - # Then we delete all square brackets, underscores, and whitespace, so no - # one can do list comprehensions or call __...__ functions. - ### - _mathEnv = {'__builtins__': new.module('__builtins__'), 'i': 1j} - _mathEnv.update(math.__dict__) - _mathEnv.update(cmath.__dict__) - _mathRe = re.compile(r'((?:(? 0: - imag = '+%si' % imag - if real == 0: - return imag.lstrip('+') - else: - return '%s%s' % (real, imag) - - def calc(self, irc, msg, args): - """ - - Returns the value of the evaluted . The syntax is - Python syntax; the type of arithmetic is floating point. - """ - text = privmsgs.getArgs(args) - text = text.translate(string.ascii, '_[] \t') - text = text.replace('lambda', '') - def handleMatch(m): - s = m.group(1) - if s.startswith('0x'): - i = int(s, 16) - elif s.startswith('0') and '.' not in s: - try: - i = int(s, 8) - except ValueError: - i = int(s) - else: - i = float(s) - return str(complex(i)) - text = self._mathRe.sub(handleMatch, text) - try: - x = complex(eval(text, self._mathEnv, self._mathEnv)) - irc.reply(msg, self._complexToString(x)) - except OverflowError: - irc.error(msg, 'Go get scanez, this is a *real* math problem!') - except TypeError: - irc.error(msg, 'Something in there wasn\'t a valid number.') - except Exception, e: - irc.error(msg, debug.exnToString(e)) - - _rpnEnv = { - 'dup': lambda s: s.extend([s.pop()]*2), - } - def rpn(self, irc, msg, args): - """ - - Returns the value of an RPN expression. - """ - stack = [] - for arg in args: - try: - stack.append(complex(arg)) - except ValueError: # Not a float. - if arg in self._mathEnv: - f = self._mathEnv[arg] - if callable(f): - called = False - arguments = [] - while not called and stack: - arguments.append(stack.pop()) - try: - stack.append(f(*arguments)) - called = True - except TypeError: - pass - if not called: - irc.error(msg, 'Not enough arguments for %s' % arg) - return - else: - stack.append(f) - elif arg in self._rpnEnv: - self._rpnEnv[arg](stack) - else: - arg2 = stack.pop() - arg1 = stack.pop() - s = '%s%s%s' % (arg1, arg, arg2) - stack.append(eval(s, self._mathEnv, self._mathEnv)) - if len(stack) == 1: - irc.reply(msg, str(self._complexToString(complex(stack[0])))) - else: - s = ', '.join(imap(self._complexToString, imap(complex, stack))) - irc.reply(msg, 'Stack: [%s]' % s) - def objects(self, irc, msg, args): """takes no arguments. diff --git a/plugins/Math.py b/plugins/Math.py new file mode 100644 index 000000000..92f672333 --- /dev/null +++ b/plugins/Math.py @@ -0,0 +1,193 @@ +#!/usr/bin/env python + +### +# Copyright (c) 2002, Jeremiah Fincher +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions, and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions, and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the author of this software nor the name of +# contributors to this software may be used to endorse or promote products +# derived from this software without specific prior written consent. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +### + +""" +Various math-related commands. +""" + +from baseplugin import * + +import re +import new +import math +import cmath +from itertools import imap + +import utils +import privmsgs +import callbacks + + +def configure(onStart, afterConnect, advanced): + # This will be called by setup.py to configure this module. onStart and + # afterConnect are both lists. Append to onStart the commands you would + # like to be run when the bot is started; append to afterConnect the + # commands you would like to be run when the bot has finished connecting. + from questions import expect, anything, something, yn + onStart.append('load Math') + +example = utils.wrapLines(""" + @calc e**(i*pi) + 1 + 0 + @calc 1+2+3 + 6 + @rpn 1 2 3 + + + 6 +""") + +class Math(callbacks.Privmsg): + ### + # So this is how the 'calc' command works: + # First, we make a nice little safe environment for evaluation; basically, + # the names in the 'math' and 'cmath' modules. Then, we remove the ability + # of a random user to get ints evaluated: this means we have to turn all + # int literals (even octal numbers and hexadecimal numbers) into floats. + # Then we delete all square brackets, underscores, and whitespace, so no + # one can do list comprehensions or call __...__ functions. + ### + _mathEnv = {'__builtins__': new.module('__builtins__'), 'i': 1j} + _mathEnv.update(math.__dict__) + _mathEnv.update(cmath.__dict__) + _mathRe = re.compile(r'((?:(? 0: + imag = '+%si' % imag + if real == 0: + return imag.lstrip('+') + else: + return '%s%s' % (real, imag) + + def calc(self, irc, msg, args): + """ + + Returns the value of the evaluted . The syntax is + Python syntax; the type of arithmetic is floating point. + """ + text = privmsgs.getArgs(args) + text = text.translate(string.ascii, '_[] \t') + text = text.replace('lambda', '') + def handleMatch(m): + s = m.group(1) + if s.startswith('0x'): + i = int(s, 16) + elif s.startswith('0') and '.' not in s: + try: + i = int(s, 8) + except ValueError: + i = int(s) + else: + i = float(s) + return str(complex(i)) + text = self._mathRe.sub(handleMatch, text) + try: + x = complex(eval(text, self._mathEnv, self._mathEnv)) + irc.reply(msg, self._complexToString(x)) + except OverflowError: + irc.error(msg, 'Go get scanez, this is a *real* math problem!') + except TypeError: + irc.error(msg, 'Something in there wasn\'t a valid number.') + except Exception, e: + irc.error(msg, debug.exnToString(e)) + + _rpnEnv = { + 'dup': lambda s: s.extend([s.pop()]*2), + } + def rpn(self, irc, msg, args): + """ + + Returns the value of an RPN expression. + """ + stack = [] + for arg in args: + try: + stack.append(complex(arg)) + except ValueError: # Not a float. + if arg in self._mathEnv: + f = self._mathEnv[arg] + if callable(f): + called = False + arguments = [] + while not called and stack: + arguments.append(stack.pop()) + try: + stack.append(f(*arguments)) + called = True + except TypeError: + pass + if not called: + irc.error(msg, 'Not enough arguments for %s' % arg) + return + else: + stack.append(f) + elif arg in self._rpnEnv: + self._rpnEnv[arg](stack) + else: + arg2 = stack.pop() + arg1 = stack.pop() + s = '%s%s%s' % (arg1, arg, arg2) + stack.append(eval(s, self._mathEnv, self._mathEnv)) + if len(stack) == 1: + irc.reply(msg, str(self._complexToString(complex(stack[0])))) + else: + s = ', '.join(imap(self._complexToString, imap(complex, stack))) + irc.reply(msg, 'Stack: [%s]' % s) + + + +Class = Math + +# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: diff --git a/test/test_FunCommands.py b/test/test_FunCommands.py index ceee8814d..487043e88 100644 --- a/test/test_FunCommands.py +++ b/test/test_FunCommands.py @@ -54,16 +54,6 @@ class FunCommandsTest(PluginTestCase, PluginDocumentation): for s in nicks[:10]: # 10 is probably enough. self.assertResponse('rot13 [rot13 %s]' % s, s) - def testCalc(self): - self.assertResponse('calc 5*0.06', str(5*0.06)) - self.assertResponse('calc 2.0-7.0', str(2-7)) - self.assertResponse('calc (-1)**.5', 'i') - self.assertResponse('calc e**(i*pi)+1', '0') - self.assertResponse('calc (-5)**.5', '2.2360679775i') - self.assertResponse('calc -((-5)**.5)', '-2.2360679775i') - self.assertNotRegexp('calc [9, 5] + [9, 10]', 'TypeError') - self.assertError('calc [9, 5] + [9, 10]') - def testChr(self): for i in range(256): c = chr(i) @@ -105,11 +95,5 @@ class FunCommandsTest(PluginTestCase, PluginDocumentation): self.assertNotError('whois ohio-state.edu') self.assertError('whois slashdot.org') - def testRpn(self): - self.assertResponse('rpn 5 2 +', '7') - self.assertResponse('rpn 1 2 3 +', 'Stack: [1, 5]') - self.assertResponse('rpn 1 dup', 'Stack: [1, 1]') - self.assertResponse('rpn 2 3 4 + -', str(2-7)) - # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: diff --git a/test/test_Math.py b/test/test_Math.py new file mode 100644 index 000000000..8649b48ee --- /dev/null +++ b/test/test_Math.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python + +### +# Copyright (c) 2002, Jeremiah Fincher +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions, and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions, and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the author of this software nor the name of +# contributors to this software may be used to endorse or promote products +# derived from this software without specific prior written consent. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +### + +from test import * + +class MathTestCase(PluginTestCase, PluginDocumentation): + plugins = ('Math',) + def testCalc(self): + self.assertResponse('calc 5*0.06', str(5*0.06)) + self.assertResponse('calc 2.0-7.0', str(2-7)) + self.assertResponse('calc (-1)**.5', 'i') + self.assertResponse('calc e**(i*pi)+1', '0') + self.assertResponse('calc (-5)**.5', '2.2360679775i') + self.assertResponse('calc -((-5)**.5)', '-2.2360679775i') + self.assertNotRegexp('calc [9, 5] + [9, 10]', 'TypeError') + self.assertError('calc [9, 5] + [9, 10]') + + def testRpn(self): + self.assertResponse('rpn 5 2 +', '7') + self.assertResponse('rpn 1 2 3 +', 'Stack: [1, 5]') + self.assertResponse('rpn 1 dup', 'Stack: [1, 1]') + self.assertResponse('rpn 2 3 4 + -', str(2-7)) + + +# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: +