44
44
_ = PluginInternationalization ('Math' )
45
45
46
46
from .local import convertcore
47
+ from .evaluator import safe_eval , InvalidNode , SAFE_ENV
47
48
48
49
baseArg = ('int' , 'base' , lambda i : i <= 36 )
49
50
@@ -97,36 +98,6 @@ def _convertBaseToBase(self, number, toBase, fromBase):
97
98
return str (number )
98
99
return self ._convertDecimalToBase (number , toBase )
99
100
100
- _mathEnv = {'__builtins__' : types .ModuleType ('__builtins__' ), 'i' : 1j }
101
- _mathEnv .update (math .__dict__ )
102
- _mathEnv .update (cmath .__dict__ )
103
- def _sqrt (x ):
104
- if isinstance (x , complex ) or x < 0 :
105
- return cmath .sqrt (x )
106
- else :
107
- return math .sqrt (x )
108
- def _cbrt (x ):
109
- return math .pow (x , 1.0 / 3 )
110
- def _factorial (x ):
111
- if x <= 10000 :
112
- return float (math .factorial (x ))
113
- else :
114
- raise Exception ('factorial argument too large' )
115
- _mathEnv ['sqrt' ] = _sqrt
116
- _mathEnv ['cbrt' ] = _cbrt
117
- _mathEnv ['abs' ] = abs
118
- _mathEnv ['max' ] = max
119
- _mathEnv ['min' ] = min
120
- _mathEnv ['round' ] = lambda x , y = 0 : round (x , int (y ))
121
- _mathSafeEnv = dict ([(x ,y ) for x ,y in _mathEnv .items ()])
122
- _mathSafeEnv ['factorial' ] = _factorial
123
- _mathRe = re .compile (r'((?:(?<![A-Fa-f\d)])-)?'
124
- r'(?:0x[A-Fa-f\d]+|'
125
- r'0[0-7]+|'
126
- r'\d+\.\d+|'
127
- r'\.\d+|'
128
- r'\d+\.|'
129
- r'\d+))' )
130
101
def _floatToString (self , x ):
131
102
if - 1e-10 < x < 1e-10 :
132
103
return '0'
@@ -157,17 +128,6 @@ def _complexToString(self, x):
157
128
else :
158
129
return '%s%s' % (realS , imagS )
159
130
160
- _calc_match_forbidden_chars = re .compile ('[_\[\]]' )
161
- _calc_remover = utils .str .MultipleRemover ('_[] \t ' )
162
- ###
163
- # So this is how the 'calc' command works:
164
- # First, we make a nice little safe environment for evaluation; basically,
165
- # the names in the 'math' and 'cmath' modules. Then, we remove the ability
166
- # of a random user to get ints evaluated: this means we have to turn all
167
- # int literals (even octal numbers and hexadecimal numbers) into floats.
168
- # Then we delete all square brackets, underscores, and whitespace, so no
169
- # one can do list comprehensions or call __...__ functions.
170
- ###
171
131
@internationalizeDocstring
172
132
def calc (self , irc , msg , args , text ):
173
133
"""<math expression>
@@ -178,57 +138,17 @@ def calc(self, irc, msg, args, text):
178
138
crash to the bot with something like '10**10**10**10'. One consequence
179
139
is that large values such as '10**24' might not be exact.
180
140
"""
181
- try :
182
- text = str (text )
183
- except UnicodeEncodeError :
184
- irc .error (_ ("There's no reason you should have fancy non-ASCII "
185
- "characters in your mathematical expression. "
186
- "Please remove them." ))
187
- return
188
- if self ._calc_match_forbidden_chars .match (text ):
189
- # Note: this is important to keep this to forbid usage of
190
- # __builtins__
191
- irc .error (_ ('There\' s really no reason why you should have '
192
- 'underscores or brackets in your mathematical '
193
- 'expression. Please remove them.' ))
194
- return
195
- text = self ._calc_remover (text )
196
- if 'lambda' in text :
197
- irc .error (_ ('You can\' t use lambda in this command.' ))
198
- return
199
- text = text .lower ()
200
- def handleMatch (m ):
201
- s = m .group (1 )
202
- if s .startswith ('0x' ):
203
- i = int (s , 16 )
204
- elif s .startswith ('0' ) and '.' not in s :
205
- try :
206
- i = int (s , 8 )
207
- except ValueError :
208
- i = int (s )
209
- else :
210
- i = float (s )
211
- x = complex (i )
212
- if x .imag == 0 :
213
- x = x .real
214
- # Need to use string-formatting here instead of str() because
215
- # use of str() on large numbers loses information:
216
- # str(float(33333333333333)) => '3.33333333333e+13'
217
- # float('3.33333333333e+13') => 33333333333300.0
218
- return '%.16f' % x
219
- return str (x )
220
- text = self ._mathRe .sub (handleMatch , text )
221
141
try :
222
142
self .log .info ('evaluating %q from %s' , text , msg .prefix )
223
- x = complex (eval (text , self . _mathSafeEnv , self . _mathSafeEnv ))
143
+ x = complex (safe_eval (text , allow_ints = False ))
224
144
irc .reply (self ._complexToString (x ))
225
145
except OverflowError :
226
146
maxFloat = math .ldexp (0.9999999999999999 , 1024 )
227
147
irc .error (_ ('The answer exceeded %s or so.' ) % maxFloat )
228
- except TypeError :
229
- irc .error (_ ('Something in there wasn \' t a valid number.' ) )
148
+ except InvalidNode as e :
149
+ irc .error (_ ('Invalid syntax: %s' ) % e . args [ 0 ] )
230
150
except NameError as e :
231
- irc .error (_ ('%s is not a defined function.' ) % str ( e ). split ()[ 1 ])
151
+ irc .error (_ ('%s is not a defined function.' ) % e . args [ 0 ])
232
152
except Exception as e :
233
153
irc .error (str (e ))
234
154
calc = wrap (calc , ['text' ])
@@ -241,28 +161,15 @@ def icalc(self, irc, msg, args, text):
241
161
math, and can thus cause the bot to suck up CPU. Hence it requires
242
162
the 'trusted' capability to use.
243
163
"""
244
- if self ._calc_match_forbidden_chars .match (text ):
245
- # Note: this is important to keep this to forbid usage of
246
- # __builtins__
247
- irc .error (_ ('There\' s really no reason why you should have '
248
- 'underscores or brackets in your mathematical '
249
- 'expression. Please remove them.' ))
250
- return
251
- # This removes spaces, too, but we'll leave the removal of _[] for
252
- # safety's sake.
253
- text = self ._calc_remover (text )
254
- if 'lambda' in text :
255
- irc .error (_ ('You can\' t use lambda in this command.' ))
256
- return
257
- text = text .replace ('lambda' , '' )
258
164
try :
259
165
self .log .info ('evaluating %q from %s' , text , msg .prefix )
260
- irc .reply (str (eval (text , self ._mathEnv , self ._mathEnv )))
166
+ x = safe_eval (text , allow_ints = True )
167
+ irc .reply (str (x ))
261
168
except OverflowError :
262
169
maxFloat = math .ldexp (0.9999999999999999 , 1024 )
263
170
irc .error (_ ('The answer exceeded %s or so.' ) % maxFloat )
264
- except TypeError :
265
- irc .error (_ ('Something in there wasn \' t a valid number.' ) )
171
+ except InvalidNode as e :
172
+ irc .error (_ ('Invalid syntax: %s' ) % e . args [ 0 ] )
266
173
except NameError as e :
267
174
irc .error (_ ('%s is not a defined function.' ) % str (e ).split ()[1 ])
268
175
except Exception as e :
@@ -286,8 +193,8 @@ def rpn(self, irc, msg, args):
286
193
x = abs (x )
287
194
stack .append (x )
288
195
except ValueError : # Not a float.
289
- if arg in self . _mathSafeEnv :
290
- f = self . _mathSafeEnv [arg ]
196
+ if arg in SAFE_ENV :
197
+ f = SAFE_ENV [arg ]
291
198
if callable (f ):
292
199
called = False
293
200
arguments = []
@@ -310,7 +217,7 @@ def rpn(self, irc, msg, args):
310
217
arg1 = stack .pop ()
311
218
s = '%s%s%s' % (arg1 , arg , arg2 )
312
219
try :
313
- stack .append (eval (s , self . _mathSafeEnv , self . _mathSafeEnv ))
220
+ stack .append (safe_eval (s , allow_ints = False ))
314
221
except SyntaxError :
315
222
irc .error (format (_ ('%q is not a defined function.' ),
316
223
arg ))
0 commit comments