Merge lp:~stefanor/ibid/calc-336195 into lp:~ibid-core/ibid/old-trunk-pack-0.92

Proposed by Stefano Rivera
Status: Merged
Approved by: Jonathan Hitchcock
Approved revision: 599
Merged at revision: 600
Proposed branch: lp:~stefanor/ibid/calc-336195
Merge into: lp:~ibid-core/ibid/old-trunk-pack-0.92
Diff against target: None lines
To merge this branch: bzr merge lp:~stefanor/ibid/calc-336195
Reviewer Review Type Date Requested Status
Jonathan Hitchcock Approve
Michael Gorven Approve
Review via email: mp+5686@code.launchpad.net

This proposal supersedes a proposal from 2009-04-16.

To post a comment you must log in.
Revision history for this message
Stefano Rivera (stefanor) wrote : Posted in a previous version of this proposal

Rip it to shreds! (Or accept)

Revision history for this message
Stefano Rivera (stefanor) wrote : Posted in a previous version of this proposal

Come on Vhata, don't be afraid

Revision history for this message
Stefano Rivera (stefanor) wrote :

OK, I think this is ready to go in. Thanks for the pointers, Hodgestar

Revision history for this message
Michael Gorven (mgorven) wrote :

 review approve

review: Approve
Revision history for this message
Jonathan Hitchcock (vhata) wrote :

The code looks fine and all.

I just can't help but feel that weird hacks like this for simple things like maths are why ibid uses 1.21 jiggawatts of memory though ;-)

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'ibid/plugins/math.py'
--- ibid/plugins/math.py 2009-03-11 11:49:44 +0000
+++ ibid/plugins/math.py 2009-04-18 08:57:41 +0000
@@ -1,12 +1,27 @@
1import logging
2from os import kill
1import re3import re
4from signal import SIGTERM
2from subprocess import Popen, PIPE5from subprocess import Popen, PIPE
6from time import time, sleep
37
4import ibid8import ibid
5from ibid.plugins import Processor, match, handler9from ibid.plugins import Processor, match, handler
6from ibid.config import Option10from ibid.config import Option, FloatOption
7from ibid.utils import file_in_path, unicode_output11from ibid.utils import file_in_path, unicode_output
812
13try:
14 from ast import NodeTransformer, Pow, Name, Load, Call, copy_location, parse
15 transform_method='ast'
16
17except ImportError:
18 from compiler import ast, pycodegen, parse, misc, walk
19 class NodeTransformer(object):
20 pass
21 transform_method='compiler'
22
9help = {}23help = {}
24log = logging.getLogger('math')
1025
11help['bc'] = u'Calculate mathematical expressions using bc'26help['bc'] = u'Calculate mathematical expressions using bc'
12class BC(Processor):27class BC(Processor):
@@ -15,6 +30,7 @@
15 feature = 'bc'30 feature = 'bc'
1631
17 bc = Option('bc', 'Path to bc executable', 'bc')32 bc = Option('bc', 'Path to bc executable', 'bc')
33 bc_timeout = FloatOption('bc_timeout', 'Maximum BC execution time (sec)', 2.0)
1834
19 def setup(self):35 def setup(self):
20 if not file_in_path(self.bc):36 if not file_in_path(self.bc):
@@ -23,7 +39,21 @@
23 @match(r'^bc\s+(.+)$')39 @match(r'^bc\s+(.+)$')
24 def calculate(self, event, expression):40 def calculate(self, event, expression):
25 bc = Popen([self.bc, '-l'], stdin=PIPE, stdout=PIPE, stderr=PIPE)41 bc = Popen([self.bc, '-l'], stdin=PIPE, stdout=PIPE, stderr=PIPE)
26 output, error = bc.communicate(expression.encode('utf-8') + '\n')42 start_time = time()
43 bc.stdin.write(expression.encode('utf-8') + '\n')
44 bc.stdin.close()
45
46 while bc.poll() is None and time() - start_time < self.bc_timeout:
47 sleep(0.1)
48
49 if bc.poll() is None:
50 kill(bc.pid, SIGTERM)
51 event.addresponse(u'Sorry, that took too long. I stopped waiting')
52 return
53
54 output = bc.stdout.read()
55 error = bc.stderr.read()
56
27 code = bc.wait()57 code = bc.wait()
2858
29 if code == 0:59 if code == 0:
@@ -34,21 +64,52 @@
34 else:64 else:
35 error = unicode_output(error.strip())65 error = unicode_output(error.strip())
36 error = error.split(":", 1)[1].strip()66 error = error.split(":", 1)[1].strip()
37 error = error[0].lower() + error[1:]67 error = error[0].lower() + error[1:].split('\n')[0]
38 event.addresponse(u"You can't %s", error)68 event.addresponse(u"I'm sorry, I couldn't deal with the %s", error)
39 else:69 else:
40 event.addresponse(u"Error running bc")70 event.addresponse(u"Error running bc")
41 error = unicode_output(error.strip())71 error = unicode_output(error.strip())
42 raise Exception("BC Error: %s" % error)72 raise Exception("BC Error: %s" % error)
4373
44help['calc'] = u'Returns the anwser to mathematical expressions'74help['calc'] = u'Returns the anwser to mathematical expressions'
75class LimitException(Exception):
76 pass
77
78def limited_pow(*args):
79 for arg, limit in zip(args, (1e100, 200)):
80 if isinstance(arg, int) and (arg > limit or arg < -limit):
81 raise LimitException
82 return pow(*args)
83
84# ast method
85class PowSubstitutionTransformer(NodeTransformer):
86 def visit_BinOp(self, node):
87 self.generic_visit(node)
88 if isinstance(node.op, Pow):
89 fnode = Name('pow', Load())
90 copy_location(fnode, node)
91 cnode = Call(fnode, [node.left, node.right], [], None, None)
92 copy_location(cnode, node)
93 return cnode
94 return node
95
96# compiler method
97class PowSubstitutionWalker(object):
98 def visitPower(self, node, *args):
99 walk(node.left, self)
100 walk(node.right, self)
101 cnode = ast.CallFunc(ast.Name('pow'), [node.left, node.right], None, None)
102 node.left = cnode
103 # Little hack: instead of trying to turn node into a CallFunc, we just do pow(left, right)**1
104 node.right = ast.Const(1)
105
45class Calc(Processor):106class Calc(Processor):
46 u"""[calc] <expression>"""107 u"""[calc] <expression>"""
47 feature = 'calc'108 feature = 'calc'
48109
49 priority = 500110 priority = 500
50111
51 extras = ('abs', 'pow', 'round', 'min', 'max')112 extras = ('abs', 'round', 'min', 'max')
52 banned = ('for', 'yield', 'lambda')113 banned = ('for', 'yield', 'lambda')
53114
54 # Create a safe dict to pass to eval() as locals115 # Create a safe dict to pass to eval() as locals
@@ -57,6 +118,7 @@
57 del safe['__builtins__']118 del safe['__builtins__']
58 for function in extras:119 for function in extras:
59 safe[function] = eval(function)120 safe[function] = eval(function)
121 safe['pow'] = limited_pow
60122
61 @match(r'^(?:calc\s+)?(.+?)$')123 @match(r'^(?:calc\s+)?(.+?)$')
62 def calculate(self, event, expression):124 def calculate(self, event, expression):
@@ -65,7 +127,19 @@
65 return127 return
66128
67 try:129 try:
68 result = eval(expression, {'__builtins__': None}, self.safe)130 # We need to remove all power operators and replace with our limited pow
131 # ast is the new method (Python >=2.6) compiler is the old
132 ast = parse(expression, mode='eval')
133 if transform_method == 'ast':
134 ast = PowSubstitutionTransformer().visit(ast)
135 code = compile(ast, '<string>', 'eval')
136 else:
137 misc.set_filename('<string>', ast)
138 walk(ast, PowSubstitutionWalker())
139 code = pycodegen.ExpressionCodeGenerator(ast).getCode()
140
141 result = eval(code, {'__builtins__': None}, self.safe)
142
69 except ZeroDivisionError, e:143 except ZeroDivisionError, e:
70 event.addresponse(u"I can't divide by zero.")144 event.addresponse(u"I can't divide by zero.")
71 return145 return
@@ -76,6 +150,9 @@
76 if unicode(e) == u"math domain error":150 if unicode(e) == u"math domain error":
77 event.addresponse(u"I can't do that: %s", unicode(e))151 event.addresponse(u"I can't do that: %s", unicode(e))
78 return152 return
153 except LimitException, e:
154 event.addresponse(u"I'm afraid I'm not allowed to play with big numbers")
155 return
79 except Exception, e:156 except Exception, e:
80 return157 return
81158

Subscribers

People subscribed via source and target branches