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

Subscribers

People subscribed via source and target branches