calc.py

changeset 129
8aa03b5c6e47
parent 128
bd949c554dd2
child 130
c82cef747008
equal deleted inserted replaced
128:bd949c554dd2 129:8aa03b5c6e47
231 self.preferred_base = None 231 self.preferred_base = None
232 232
233 def set_preferred_base (self, x): 233 def set_preferred_base (self, x):
234 self.preferred_base = x 234 self.preferred_base = x
235 235
236 def trim_spaces (self, expr): 236 def trim_spaces (self):
237 return re.sub ('\s+', '', expr.strip()) 237 self.expr = re.sub ('\s+', '', self.expr.strip())
238 238
239 def parse_attributes (self, expr): 239 def parse_attributes (self):
240 if expr[0] != '<': 240 if self.expr[0] != '<':
241 return expr 241 return
242 242
243 i = 1 243 i = 1
244 while expr[i] != '>': 244 while self.expr[i] != '>':
245 if expr[i] == '|': 245 if self.expr[i] == '|':
246 i += 1 246 i += 1
247 247
248 for name in AttributeNames: 248 for name in AttributeNames:
249 if expr[i:i + len(name)] == name: 249 if self.expr[i:i + len(name)] == name:
250 Attributes[name] (self) 250 Attributes[name] (self)
251 i += len(name) 251 i += len(name)
252 252
253 if expr[i] == '>': 253 if self.expr[i] == '>':
254 break 254 break
255 255
256 if expr[i] != '|': 256 if self.expr[i] != '|':
257 raise ValueError ('malformed attributes') 257 raise ValueError ('malformed attributes')
258 break 258 break
259 else: 259 else:
260 raise ValueError ('bad attributes: %s' % expr[i:]) 260 raise ValueError ('bad attributes: %s' % self.expr[i:])
261 261
262 return expr[i + 1:] 262 return self.expr[i + 1:]
263 263
264 def parse_number (self, expr): 264 def parse_number (self):
265 """Tries to parse a number from the expression. Returns (value, length) on success.""" 265 """Tries to parse a number from the expression. Returns value on success."""
266 i = 0 266 i = 0
267 value = None 267 value = None
268 base = 10 268 base = 10
269 269
270 # Possibly it's hexadecimal 270 # Possibly it's hexadecimal
271 if expr[0:2].lower() == '0x': 271 if self.expr[0:2].lower() == '0x':
272 base = 0x10 272 base = 0x10
273 digits = string.hexdigits 273 digits = string.hexdigits
274 digitname = 'hexit' 274 digitname = 'hexit'
275 i = 2 275 i = 2
276 elif expr[0:2].lower() == '0b': 276 elif self.expr[0:2].lower() == '0b':
277 base = 0b10 277 base = 0b10
278 digits = ['0', '1'] 278 digits = ['0', '1']
279 digitname = 'bit' 279 digitname = 'bit'
280 i = 2 280 i = 2
281 281
282 if base != 10: 282 if base != 10:
283 if not self.preferred_base: 283 if not self.preferred_base:
284 self.preferred_base = base 284 self.preferred_base = base
285 285
286 # Skip leading zeroes 286 # Skip leading zeroes
287 while i < len (expr) and expr[i] == '0': 287 while i < len (self.expr) and self.expr[i] == '0':
288 i += 1 288 i += 1
289 289
290 startingIndex = i 290 startingIndex = i
291 while i < len (expr) and expr[i] in digits: 291 while i < len (self.expr) and self.expr[i] in digits:
292 i += 1 292 i += 1
293 293
294 if i < len(expr) and expr[i] == '.': 294 if i < len(self.expr) and self.expr[i] == '.':
295 raise ValueError ('non-decimal floating point numbers are not supported') 295 raise ValueError ('non-decimal floating point numbers are not supported')
296 296
297 if i == startingIndex: 297 if i == startingIndex:
298 if i < len (expr): 298 if i < len (self.expr):
299 raise ValueError ('not a valid %s "%s" in %s' % (digitname, expr[i], expr[0:i+1])) 299 raise ValueError ('not a valid %s "%s" in %s' % (digitname, self.expr[i], self.expr[0:i+1]))
300 else: 300 else:
301 raise ValueError ('end of expression encountered while parsing ' 301 raise ValueError ('end of expression encountered while parsing '
302 'base-%d literal' % base) 302 'base-%d literal' % base)
303 303
304 return (complex (int (expr[startingIndex:i], base), 0), i) 304 return (complex (int (self.expr[startingIndex:i], base), 0), i)
305 305
306 if expr[0] == 'i': 306 if self.expr[0] == 'i':
307 # Special case, we just have 'i' -- need special handling here because otherwise this would 307 # Special case, we just have 'i' -- need special handling here because otherwise this would
308 # call float('') in the complex number routine, which throws an error. 308 # call float('') in the complex number routine, which throws an error.
309 # TODO: maybe 'i' can be a symbol instead? 309 # TODO: maybe 'i' can be a symbol instead?
310 return (1j, 1) 310 self.expr = self.expr[1:]
311 return 1j
311 312
312 # Try parse increasingly long substrings until we are unable to create a float or complex number 313 # Try parse increasingly long substrings until we are unable to create a float or complex number
313 # from it. 314 # from it.
314 try: 315 try:
315 while i < len (expr): 316 while i < len (self.expr):
316 if expr[i] == 'i': 317 if self.expr[i] == 'i':
317 value = complex (0.0, float (expr[:i])) 318 value = complex (0.0, float (self.expr[:i]))
318 else: 319 else:
319 value = complex (float (expr [:i + 1]), 0.0) 320 value = complex (float (self.expr [:i + 1]), 0.0)
320 i += 1 321 i += 1
321 322
322 return (value, i) 323 self.expr = self.expr[i:]
324 return value
323 except ValueError: 325 except ValueError:
324 if i != 0: 326 if i != 0:
325 # Got a number (the error happened when we tried to parse past the number) 327 # Got a number (the error happened when we tried to parse past the number)
326 return (value, i) 328 self.expr = self.expr[i:]
329 return value
327 else: 330 else:
328 # The error happened on the first character. So this is not a number. 331 # The error happened on the first character. So this is not a number.
329 return None 332 return None
330 333
331 def parse_symbol (self, expr): 334 def parse_symbol (self):
332 for sym in Symbols: 335 for sym in Symbols:
333 if expr[:len (sym)] == sym: 336 if self.expr[:len (sym)] == sym:
337 self.expr = self.expr[len (sym):]
334 return sym 338 return sym
335 339
336 return None 340 return None
337 341
338 def tokenize (self, expr): 342 def tokenize (self):
339 i=0 343 self.tokens = []
340 tokens = [] 344
341 345 while self.expr:
342 while i < len(expr): 346 sym = self.parse_symbol()
343 sym = self.parse_symbol (expr[i:])
344 347
345 if sym: 348 if sym:
346 symtype = SymbolTypes[sym] 349 symtype = SymbolTypes[sym]
347 if symtype == SymbolType.CONSTANT: 350 if symtype == SymbolType.CONSTANT:
348 tokens.append (Constants[sym]) 351 self.tokens.append (Constants[sym])
349 else: 352 else:
350 tokens.append (sym) 353 self.tokens.append (sym)
351
352 i += len(sym)
353 continue 354 continue
354 355
355 result = self.parse_number (expr[i:]) 356 result = self.parse_number()
356 if result: 357 if result:
357 num, length = result 358 self.tokens.append (result)
358 tokens.append (num)
359 i += length
360 continue 359 continue
361 360
362 raise ValueError ("""bad expression, couldn't parse: %s""" % expr[i:]) 361 raise ValueError ("""bad expression, couldn't parse: %s""" % self.expr[i:])
363 362
364 return tokens 363 def process_parens (self):
365 364 """Processes parentheses of self.tokens into sublists in-place.
366 def process_parens (self, expr):
367 """Processes parentheses of expr into sublists in-place.
368 [1.0, '*', '(', 3.5, '+', 1j, ')'] 365 [1.0, '*', '(', 3.5, '+', 1j, ')']
369 -> [1.0, '*', [3.5, '+', 1j]]""" 366 -> [1.0, '*', [3.5, '+', 1j]]"""
370 if '(' not in expr and ')' not in expr: 367 if '(' not in self.tokens and ')' not in self.tokens:
371 return 368 return
372 369
373 try: 370 try:
374 parenStart = rindex (expr, '(') 371 parenStart = rindex (self.tokens, '(')
375 parenEnd = expr.index (')', parenStart) 372 parenEnd = self.tokens.index (')', parenStart)
376 except ValueError: 373 except ValueError:
377 raise ValueError ("""mismatched parentheses in expression: %s""" % expr) 374 raise ValueError ("""mismatched parentheses in expression: %s""" % self.tokens)
378 375
379 subexpr = expr[parenStart + 1:parenEnd] 376 subexpr = self.tokens[parenStart + 1:parenEnd]
380 del expr[parenStart + 1:parenEnd + 1] 377 del self.tokens[parenStart + 1:parenEnd + 1]
381 expr[parenStart] = subexpr 378 self.tokens[parenStart] = subexpr
382 self.process_parens (expr) 379 self.process_parens (self.tokens)
383 380
384 def process_functions (self, expr): 381 def process_functions (self):
385 """Processes functions in-place""" 382 """Processes functions in-place"""
386 i = 0 383 i = 0
387 while i < len (expr): 384 while i < len (self.tokens):
388 if type (expr[i]) is list: 385 if type (self.tokens[i]) is list:
389 self.process_functions (expr[i]) 386 self.process_functions (self.tokens[i])
390 387
391 if (type(expr[i]) is not str) or (expr[i] not in Functions): 388 if (type(self.tokens[i]) is not str) or (self.tokens[i] not in Functions):
392 i += 1 389 i += 1
393 continue 390 continue
394 391
395 # Ensure that arguments follow 392 # Ensure that arguments follow
396 if (i + 1 >= len (expr)) or (type (expr[i + 1]) is not list): 393 if (i + 1 >= len (self.tokens)) or (type (self.tokens[i + 1]) is not list):
397 raise ValueError ("""function %s used without arguments""" % expr[i]) 394 raise ValueError ("""function %s used without arguments""" % self.tokens[i])
398 395
399 args = expr[i + 1] 396 args = self.tokens[i + 1]
400 del expr[i + 1] 397 del self.tokens[i + 1]
401 expr[i] = FunctionCall (expr[i], args) 398 self.tokens[i] = FunctionCall (self.tokens[i], args)
402 i += 1 399 i += 1
403 400
404 def is_operand (self, x): 401 def is_operand (self, x):
405 # Operands can be either lists (which also mean parens, thus can be single-expressions) 402 # Operands can be either lists (which also mean parens, thus can be single-expressions)
406 # or complex numbers 403 # or complex numbers
420 if op.symbol == sym: 417 if op.symbol == sym:
421 return op 418 return op
422 419
423 raise ValueError ('''unknown operator %s!''' % sym) 420 raise ValueError ('''unknown operator %s!''' % sym)
424 421
425 def process_operators (self, expr): 422 def process_operators (self):
426 """Processes operators""" 423 """Processes operators"""
427 i = 0 424 i = 0
428 425
429 # Find all operators in this expression 426 # Find all operators in this expression
430 while i < len (expr): 427 while i < len (self.tokens):
431 if type (expr[i]) is list: 428 if type (self.tokens[i]) is list:
432 self.process_functions (expr[i]) 429 self.process_functions (self.tokens[i])
433 self.process_operators (expr[i]) 430 self.process_operators (self.tokens[i])
434 431
435 if type (expr[i]) is FunctionCall: 432 if type (self.tokens[i]) is FunctionCall:
436 self.process_functions (expr[i].args) 433 self.process_functions (self.tokens[i].args)
437 self.process_operators (expr[i].args) 434 self.process_operators (self.tokens[i].args)
438 435
439 if (type(expr[i]) is not str) or (expr[i] not in OperatorSymbols): 436 if (type(self.tokens[i]) is not str) or (self.tokens[i] not in OperatorSymbols):
440 i += 1 437 i += 1
441 continue 438 continue
442 439
443 args = [] 440 args = []
444 argIndices = [] 441 argIndices = []
445 if i > 0 and self.is_operand (expr[i - 1]): 442 if i > 0 and self.is_operand (self.tokens[i - 1]):
446 args.append (expr[i - 1]) 443 args.append (self.tokens[i - 1])
447 argIndices.append (i - 1) 444 argIndices.append (i - 1)
448 445
449 if i - 1 < len(expr) and self.is_operand (expr[i + 1]): 446 if i - 1 < len(self.tokens) and self.is_operand (self.tokens[i + 1]):
450 args.append (expr[i + 1]) 447 args.append (self.tokens[i + 1])
451 argIndices.append (i + 1) 448 argIndices.append (i + 1)
452 449
453 # Resolve operators with the same symbol based on operand count 450 # Resolve operators with the same symbol based on operand count
454 numOperands = 0 451 numOperands = 0
455 for arg in args: 452 for arg in args:
456 if self.is_operand (arg): 453 if self.is_operand (arg):
457 numOperands += 1 454 numOperands += 1
458 455
459 expr[i] = self.find_fitting_operator (expr[i], numOperands) 456 self.tokens[i] = self.find_fitting_operator (self.tokens[i], numOperands)
460 i += 1 457 i += 1
461 458
462 def find_priority_operator (self, expr): 459 def find_priority_operator (self):
463 """Finds the operator with most priority in the expression""" 460 """Finds the operator with most priority in the expression"""
464 bestOp = None 461 bestOp = None
465 bestOpIndex = -1 462 bestOpIndex = -1
466 463
467 for i in range (0, len (expr)): 464 for i in range (0, len (self.tokens)):
468 sym = expr[i] 465 sym = self.tokens[i]
469 466
470 if type (sym) is not Operator: 467 if type (sym) is not Operator:
471 continue 468 continue
472 469
473 if not bestOp or sym.priority < bestOp.priority: 470 if not bestOp or sym.priority < bestOp.priority:
474 bestOp = sym 471 bestOp = sym
475 bestOpIndex = i 472 bestOpIndex = i
476 473
477 return (bestOp, bestOpIndex) 474 return (bestOp, bestOpIndex)
478 475
479 def evaluate (self, expr, verbose=False): 476 def evaluate (self, verbose=False):
480 printFunc = realPrint if verbose else lambda x:None 477 printFunc = realPrint if verbose else lambda x:None
481 printFunc (self.tabs + 'Preprocess: %s' % expr) 478 printFunc (self.tabs + 'Preprocess: %s' % self.tokens)
482 479
483 # If there are sub-expressions in here, those need to be evaluated first 480 # If there are sub-expressions in here, those need to be evaluated first
484 i = 0 481 i = 0
485 while i < len (expr): 482 while i < len (self.tokens):
486 sym = expr[i] 483 sym = self.tokens[i]
487 484
488 if type (sym) is list and sym: 485 if type (sym) is list and sym:
489 printFunc (self.tabs + 'Evaluating sub-expression: %s' % (sym)) 486 printFunc (self.tabs + 'Evaluating sub-expression: %s' % (sym))
490 self.tabs += '\t' 487 self.tabs += '\t'
491 expr[i] = self.evaluate (list (sym), verbose) 488 self.tokens[i] = self.evaluate (list (sym), verbose)
492 self.tabs = self.tabs[:-1] 489 self.tabs = self.tabs[:-1]
493 printFunc (self.tabs + '-> %s' % expr[i]) 490 printFunc (self.tabs + '-> %s' % self.tokens[i])
494 491
495 # If there are function calls, evaluate those 492 # If there are function calls, evaluate those
496 if type (sym) is FunctionCall: 493 if type (sym) is FunctionCall:
497 self.tabs += '\t' 494 self.tabs += '\t'
498 if sym.args: 495 if sym.args:
499 sym.args = [self.evaluate (sym.args, verbose)] 496 sym.args = [self.evaluate (sym.args, verbose)]
500 self.tabs = self.tabs[:-1] 497 self.tabs = self.tabs[:-1]
501 498
502 printFunc (self.tabs + 'Evaluating function call: %s' % (sym)) 499 printFunc (self.tabs + 'Evaluating function call: %s' % (sym))
503 expr[i] = Functions[sym.funcname]['function'] (*sym.args) 500 self.tokens[i] = Functions[sym.funcname]['function'] (*sym.args)
504 printFunc (self.tabs + '-> %s' % expr[i]) 501 printFunc (self.tabs + '-> %s' % self.tokens[i])
505 502
506 i += 1 503 i += 1
507 504
508 printFunc (self.tabs + 'Evaluate: %s' % expr) 505 printFunc (self.tabs + 'Evaluate: %s' % self.tokens)
509 runaway = 0 506 runaway = 0
510 while True: 507 while True:
511 runaway += 1 508 runaway += 1
512 if runaway > 1000: 509 if runaway > 1000:
513 raise ValueError ('infinite loop detected') 510 raise ValueError ('infinite loop detected')
514 511
515 op, i = self.find_priority_operator (expr) 512 op, i = self.find_priority_operator()
516 if not op: 513 if not op:
517 break 514 break
518 515
519 if op.operands == 2: 516 if op.operands == 2:
520 argIndices = [i - 1, i + 1] 517 argIndices = [i - 1, i + 1]
521 else: 518 else:
522 argIndices = [i + 1] 519 argIndices = [i + 1]
523 520
524 args = [expr[x] for x in argIndices] 521 args = [self.tokens[x] for x in argIndices]
525 argIndices = sorted (argIndices, reverse=True) 522 argIndices = sorted (argIndices, reverse=True)
526 printFunc (self.tabs + 'Processing: (%s, %d) with args %s' % (op, i, args)) 523 printFunc (self.tabs + 'Processing: (%s, %d) with args %s' % (op, i, args))
527 expr[i] = op.function (*args) 524 self.tokens[i] = op.function (*args)
528 printFunc (self.tabs + '-> %s' % expr[i]) 525 printFunc (self.tabs + '-> %s' % self.tokens[i])
529 526
530 for i2 in argIndices: 527 for i2 in argIndices:
531 del expr[i2] 528 del self.tokens[i2]
532 529
533 printFunc (self.tabs + 'Result: %s' % expr[0]) 530 printFunc (self.tabs + 'Result: %s' % self.tokens[0])
534 531
535 if len(expr) != 1: 532 if len (self.tokens) != 1:
536 printFunc (self.tabs + 'Bad expression detected, tokens: %s' % expr) 533 printFunc (self.tabs + 'Bad expression detected, tokens: %s' % self.tokens)
537 raise ValueError ('malformed expression') 534 raise ValueError ('malformed expression')
538 535
539 return expr[0] 536 return self.tokens[0]
540 537
541 def repr_number (self, x): 538 def repr_number (self, x):
542 """Returns a string representation for a real number""" 539 """Returns a string representation for a real number"""
543 base = self.preferred_base if self.preferred_base else 10 540 base = self.preferred_base if self.preferred_base else 10
544 if math.fabs (x - math.floor(x)) < epsilon and base != 10: 541 if math.fabs (x - math.floor(x)) < epsilon and base != 10:
601 # Real number 598 # Real number
602 return self.repr_number (x.real) 599 return self.repr_number (x.real)
603 600
604 def calc (self, expr, verbose=False): 601 def calc (self, expr, verbose=False):
605 self.state = {} 602 self.state = {}
603 self.expr = expr
606 self.tabs = '' 604 self.tabs = ''
607 expr = self.trim_spaces (expr) 605
608 expr = self.parse_attributes (expr) 606 self.trim_spaces()
609 expr = self.tokenize (expr) 607 self.parse_attributes()
610 self.process_parens (expr) 608 self.tokenize()
611 self.process_functions (expr) 609 self.process_parens()
612 self.process_operators (expr) 610 self.process_functions()
613 result = self.evaluate (expr, verbose) 611 self.process_operators()
612 result = self.evaluate (verbose)
614 return self.represent (result) 613 return self.represent (result)

mercurial