Advertisement
Guest User

calculator.py v1.fixed

a guest
Nov 29th, 2012
106
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 8.19 KB | None | 0 0
  1. # # # calculator.py
  2.  
  3. import math
  4. import random
  5. import string
  6. import re
  7.  
  8. # # # enum
  9.  
  10. class EnumNotation:
  11.     Prefix = 1
  12.     Postfix = 2
  13.     Infix = 4
  14.     FunctionOnly = 8
  15.  
  16. # symbol, function name, precedence (higher the more precedence), fixity, what it does to its operators
  17.    
  18. binaryoperators = {
  19.     'log': ('log', 1, EnumNotation.FunctionOnly, lambda x, y: math.log(x, y)),
  20.     'atan2': ('atan2', 1, EnumNotation.FunctionOnly, lambda x, y: math.atan2(x, y)),
  21.     'gauss': ('gauss', 1, EnumNotation.FunctionOnly, lambda x, y: random.gauss(x, y)),
  22.     '+': ('add', 10, EnumNotation.Infix, lambda x, y: x + y),
  23.     '-': ('sub', 10, EnumNotation.Infix, lambda x, y: x - y),
  24.     '*': ('mul', 20, EnumNotation.Infix, lambda x, y: x * y),
  25.     '/': ('div', 20, EnumNotation.Infix, lambda x, y: x / y),
  26.     '%': ('mod', 20, EnumNotation.Infix, lambda x, y: x % y),
  27.     '^': ('pow', 30, EnumNotation.Infix, lambda x, y: x ** y),
  28.     'max': ('max', 40, EnumNotation.Infix, lambda x, y: max(x, y)),
  29.     'min': ('min', 40, EnumNotation.Infix, lambda x, y: min(x, y)),
  30.     'avg': ('avg', 40, EnumNotation.Infix, lambda x, y: (x + y)/ 2),
  31.     'd': ('roll', 50, EnumNotation.Infix, lambda x, y: reduce (lambda x, y: x + y, [random.randint(1, int(y)) for i in range(int(x))])),
  32. }
  33.  
  34. unaryoperators = {
  35.     'floor': ('floor', 1, EnumNotation.FunctionOnly, lambda x: math.floor(x)),
  36.     'ceil': ('ceil', 1, EnumNotation.FunctionOnly, lambda x: math.ceil(x)),
  37.     'round': ('round', 1, EnumNotation.FunctionOnly, lambda x: round(x)),
  38.     'V': ('sqrt', 10, EnumNotation.Prefix, lambda x: math.sqrt(x)),
  39.     'ln': ('ln', 15, EnumNotation.Prefix, lambda x: math.log(x)),
  40.     'abs': ('abs', 15, EnumNotation.Prefix, lambda x: abs(x)),
  41.     'sin': ('sin', 15, EnumNotation.Prefix, lambda x: math.sin(x)),
  42.     'cos': ('cos', 15, EnumNotation.Prefix, lambda x: math.cos(x)),
  43.     'tan': ('tan', 15, EnumNotation.Prefix, lambda x: math.tan(x)),
  44.     'asin': ('asin', 15, EnumNotation.Prefix, lambda x: math.asin(x)),
  45.     'acos': ('acos', 15, EnumNotation.Prefix, lambda x: math.acos(x)),
  46.     'atan': ('atan', 15, EnumNotation.Prefix, lambda x: math.atan(x)),
  47.     'sinh': ('sinh', 15, EnumNotation.Prefix, lambda x: math.sinh(x)),
  48.     'cosh': ('cosh', 15, EnumNotation.Prefix, lambda x: math.cosh(x)),
  49.     'tanh': ('tanh', 15, EnumNotation.Prefix, lambda x: math.tanh(x)),
  50.     'asinh': ('asinh', 15, EnumNotation.Prefix, lambda x: math.asinh(x)),
  51.     'acosh': ('acosh', 15, EnumNotation.Prefix, lambda x: math.acosh(x)),
  52.     'atanh': ('atanh', 15, EnumNotation.Prefix, lambda x: math.atanh(x)),
  53.     '!': ('factorial', 20, EnumNotation.Postfix, lambda x: math.gamma(x+1)),
  54.     'rand': ('rand', 25, EnumNotation.Prefix, lambda x: random.uniform(0, x)),
  55. }
  56.  
  57. variables = {
  58.     'pi': math.pi,
  59.     'e': math.e,
  60.     'ans': 0,
  61. }
  62.  
  63. def resetvariables():
  64.     variables = {
  65.         'pi': math.pi,
  66.         'e': math.e,
  67.         'ans': 0,
  68.     }
  69.  
  70. # regular expressions
  71.  
  72. # new rule for leading minus signs - they only bind if
  73. regexdouble = r"-?(\d*\.)?\d+([eE][+\-]?\d+)?" # scary huh?
  74. regexinteger = r"(\d+)"
  75. regexstraybrackets = r"[()]"
  76.  
  77. regexbinaryinfix = r"(?P<num1>"+regexdouble+")\s*{}\s*(?P<num2>"+regexdouble+")" # notice {}
  78. regexunaryprefix = r"(?<!\d){}(?P<num>"+regexdouble+")" # lookbehind to prevent two numbers from getting mashed together
  79. regexunarypostfix = r"(?P<num>"+regexdouble+"){}(?!\d)" # lookahead, similar reason
  80. regexbrackets = r"(?P<funcname>(\b[a-zA-Z]\w*)?)\((?P<bracketed>[^(]*?)\)" # brackets with an optional function name. function name must start with a letter than proceed with letters/numbers
  81.    
  82. # calculatethis should take a dictionary of variables -> values
  83.  
  84. def sanitize(s = ""):
  85.     # [\^$.|?*+()
  86.     return re.sub(r"([\[\\\^\$\.\|\?\*\+\(\)])", r"\\\1", s)
  87.  
  88. def calculatethis(evalstring = "", variablesdict = {}):
  89.     for (k, v) in variablesdict.iteritems():
  90.         variables[string.lower(k)] = v
  91.     ans = float(calculatethiscore(evalstring)) # try/catch exceptions?
  92.     variables['ans'] = ans
  93.     return ans
  94.  
  95. def calculatethiscore(evalstring = "", functionname = ""):
  96.     functionname = string.lower(functionname)
  97.     # step 1.5: split by ,s if we're in a function?
  98.     expectedcommanumber = 0
  99.     if len(functionname) > 0:
  100.         if functionname in [x[0] for x in binaryoperators.itervalues()]:
  101.             expectedcommanumber = 1
  102.             operatorentry = [x for x in binaryoperators.itervalues() if x[0] == functionname][0]
  103.         elif functionname in [x[0] for x in unaryoperators.itervalues()]:
  104.             expectedcommanumber = 0
  105.             operatorentry = [x for x in unaryoperators.itervalues() if x[0] == functionname][0]
  106.         else:
  107.             raise ArithmeticError("no definition for function name {}".format(functionname))
  108.    
  109.     if expectedcommanumber == 1:
  110.         splitstrings = evalstring.split(",")
  111.         if len(splitstrings) < 2:
  112.             raise ArithmeticError("Passed too few arguments to two valued function: {}({})".format(functionname, evalstring))
  113.         elif len(splitstrings) > 2:
  114.             raise ArithmeticError("Passed too many arguments to two valued function: {}({})".format(functionname, evalstring))
  115.         return operatorentry[3](float(calculatethiscore(splitstrings[0])), float(calculatethiscore(splitstrings[1])))
  116.    
  117.     # step 2: recurse on brackets + do functions, substring and paste back into evalstring
  118.     while True:
  119.         mo = re.search(regexbrackets, evalstring)
  120.         if mo is None:
  121.             bracketsmo = re.search(regexstraybrackets, evalstring)
  122.             if bracketsmo is not None:
  123.                 raise ArithmeticError("Unbalanced brackets: Too many {}s", bracketsmo.group(0))
  124.             else:
  125.                 break
  126.         else:
  127.             result = calculatethiscore(mo.group('bracketed'), string.lower(mo.group('funcname')))
  128.             evalstring = evalstring[0:mo.start(0)] + str(result) + evalstring[mo.end(0):len(evalstring)]
  129.    
  130.     # step 2.5: replace variables with values
  131.     for (k, v) in sorted(variables.iteritems(), lambda x, y: cmp(len(x[0]), len(y[0])), reverse = True):
  132.         evalstring = re.sub(r"\b{}\b".format(k), str(v), evalstring)
  133.    
  134.     # step 3: do all unary operators
  135.     # gotta handle precedence, fixity...
  136.     for i in range(100, -1, -1):
  137.         for (k, v) in filter(lambda x: x[1][1] == i, unaryoperators.iteritems()):
  138.             while True:
  139.                 if v[2] & EnumNotation.FunctionOnly != 0:
  140.                     break
  141.                 if v[2] & EnumNotation.Prefix != 0:
  142.                     mo = re.search(regexunaryprefix.format(sanitize(k)), evalstring, re.IGNORECASE)
  143.                     if mo is None:
  144.                         break
  145.                     else:
  146.                         result = v[3](float(mo.group('num')))
  147.                         evalstring = evalstring[0:mo.start(0)] + str(result) + evalstring[mo.end(0):len(evalstring)]
  148.                 if v[2] & EnumNotation.Postfix != 0:
  149.                     mo = re.search(regexunarypostfix.format(sanitize(k)), evalstring, re.IGNORECASE)
  150.                     if mo is None:
  151.                         break
  152.                     else:
  153.                         result = v[3](float(mo.group('num')))
  154.                         evalstring = evalstring[0:mo.start(0)] + str(result) + evalstring[mo.end(0):len(evalstring)]
  155.    
  156.     # step 4: do all binary operators
  157.     # gotta handle precedence, fixity...
  158.     # while at a precedence level, we have to do all operators from leftmost to rightmost until we run out - to preserve correct order of operation
  159.    
  160.     # NEW VERSION
  161.     for i in range(100, -1, -1):
  162.         somethinghappened = True
  163.         while somethinghappened:
  164.             bestmatch = None
  165.             bestoperator = None
  166.             somethinghappened = False
  167.             for (k, v) in filter(lambda x: x[1][1] == i, binaryoperators.iteritems()):
  168.                 if v[2] & EnumNotation.FunctionOnly != 0:
  169.                     continue
  170.                 elif v[2] & EnumNotation.Infix != 0:
  171.                     mo = re.search(regexbinaryinfix.format(sanitize(k)), evalstring, re.IGNORECASE)
  172.                     if mo is None:
  173.                         continue
  174.                     elif bestmatch is None or mo.start(0) < bestmatch.start(0):
  175.                         somethinghappened = True
  176.                         bestmatch = mo
  177.                         bestoperator = v
  178.             if somethinghappened:
  179.                 result = bestoperator[3](float(bestmatch.group('num1')), float(bestmatch.group('num2')))
  180.                 evalstring = evalstring[0:bestmatch.start(0)] + str(result) + evalstring[bestmatch.end(0):len(evalstring)]
  181.    
  182.     # step 4.5: if I implement assignment operations, do 'em here
  183.    
  184.     # step 5: apply function if we have one
  185.     if len(functionname) > 0:
  186.         return operatorentry[3](float(evalstring))
  187.    
  188.     #step 5.5: return result
  189.     return evalstring
  190.  
  191. # use by doing:
  192. calculatethis("2d8")
  193. calculatethis("1+1")
  194. calculatethis("abs(-1)")
  195. calculatethis("pi*e")
  196. calculatethis("8*(floor(sin(max(3,2d4))))%27")
  197. calculatethis("6-3*4/5+2^2")
  198. calculatethis("1+2-3*4/5%(6-4)")
  199. calculatethis("max(sub(2,pi),mod(3,4))")
  200. calculatethis("1e+03+1e-03")
  201.  
  202. # etc
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement