Advertisement
Guest User

calculator.py v1.fixeder

a guest
Nov 29th, 2012
107
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 8.31 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*\.)?\d+([eE][+\-]?\d+)?|[nN]a[nN]|-?[iI]nfinity" # scary huh?
  74. regexstraybrackets = r"[()]"
  75.  
  76. regexbinaryinfix = r"(?P<num1>"+regexdouble+")\s*{}\s*(?P<num2>"+regexdouble+")" # notice {}
  77. regexunaryprefix = r"(?<!\d){}(?P<num>"+regexdouble+")" # lookbehind to prevent two numbers from getting mashed together
  78. regexunarypostfix = r"(?P<num>"+regexdouble+"){}(?![\d.])" # lookahead, similar reason
  79. 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
  80.    
  81. # calculatethis should take a dictionary of variables -> values
  82.  
  83. def sanitize(s = ""):
  84.     # [\^$.|?*+()
  85.     return re.sub(r"([\[\\\^\$\.\|\?\*\+\(\)])", r"\\\1", s)
  86.  
  87. def calculatethis(evalstring = "", variablesdict = {}):
  88.     for (k, v) in variablesdict.iteritems():
  89.         variables[string.lower(k)] = v
  90.     ans = float(calculatethiscore(evalstring)) # try/catch exceptions?
  91.     variables['ans'] = ans
  92.     return ans
  93.  
  94. def calculatethiscore(evalstring = "", functionname = ""):
  95.     functionname = string.lower(functionname)
  96.     # step 1.5: split by ,s if we're in a function?
  97.     expectedcommanumber = 0
  98.     if len(functionname) > 0:
  99.         if functionname in [x[0] for x in binaryoperators.itervalues()]:
  100.             expectedcommanumber = 1
  101.             operatorentry = [x for x in binaryoperators.itervalues() if x[0] == functionname][0]
  102.         elif functionname in [x[0] for x in unaryoperators.itervalues()]:
  103.             expectedcommanumber = 0
  104.             operatorentry = [x for x in unaryoperators.itervalues() if x[0] == functionname][0]
  105.         else:
  106.             raise ArithmeticError("no definition for function name {}".format(functionname))
  107.    
  108.     if expectedcommanumber == 1:
  109.         splitstrings = evalstring.split(",")
  110.         if len(splitstrings) < 2:
  111.             raise ArithmeticError("Passed too few arguments to two valued function: {}({})".format(functionname, evalstring))
  112.         elif len(splitstrings) > 2:
  113.             raise ArithmeticError("Passed too many arguments to two valued function: {}({})".format(functionname, evalstring))
  114.         return operatorentry[3](float(calculatethiscore(splitstrings[0])), float(calculatethiscore(splitstrings[1])))
  115.    
  116.     # step 2: recurse on brackets + do functions, substring and paste back into evalstring
  117.     while True:
  118.         mo = re.search(regexbrackets, evalstring)
  119.         if mo is None:
  120.             bracketsmo = re.search(regexstraybrackets, evalstring)
  121.             if bracketsmo is not None:
  122.                 raise ArithmeticError("Unbalanced brackets: Too many {}s", bracketsmo.group(0))
  123.             else:
  124.                 break
  125.         else:
  126.             result = calculatethiscore(mo.group('bracketed'), string.lower(mo.group('funcname')))
  127.             evalstring = evalstring[0:mo.start(0)] + str(result) + evalstring[mo.end(0):len(evalstring)]
  128.    
  129.     # step 2.5: replace variables with values
  130.     for (k, v) in sorted(variables.iteritems(), lambda x, y: cmp(len(x[0]), len(y[0])), reverse = True):
  131.         evalstring = re.sub(r"\b{}\b".format(k), str(v), evalstring)
  132.    
  133.     # step 3: do all unary operators
  134.     # gotta handle precedence, fixity...
  135.     for i in range(100, -1, -1):
  136.         for (k, v) in filter(lambda x: x[1][1] == i, unaryoperators.iteritems()):
  137.             while True:
  138.                 if v[2] & EnumNotation.FunctionOnly != 0:
  139.                     break
  140.                 if v[2] & EnumNotation.Prefix != 0:
  141.                     mo = re.search(regexunaryprefix.format(sanitize(k)), evalstring, re.IGNORECASE)
  142.                     if mo is None:
  143.                         break
  144.                     else:
  145.                         result = v[3](float(mo.group('num')))
  146.                         evalstring = evalstring[0:mo.start(0)] + str(result) + evalstring[mo.end(0):len(evalstring)]
  147.                 if v[2] & EnumNotation.Postfix != 0:
  148.                     mo = re.search(regexunarypostfix.format(sanitize(k)), evalstring, re.IGNORECASE)
  149.                     if mo is None:
  150.                         break
  151.                     else:
  152.                         result = v[3](float(mo.group('num')))
  153.                         evalstring = evalstring[0:mo.start(0)] + str(result) + evalstring[mo.end(0):len(evalstring)]
  154.    
  155.     # step 4: do all binary operators
  156.     # gotta handle precedence, fixity...
  157.     # 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
  158.    
  159.     # NEW VERSION
  160.     for i in range(100, -1, -1):
  161.         somethinghappened = True
  162.         while somethinghappened:
  163.             bestmatch = None
  164.             bestoperator = None
  165.             somethinghappened = False
  166.             for (k, v) in filter(lambda x: x[1][1] == i, binaryoperators.iteritems()):
  167.                 if v[2] & EnumNotation.FunctionOnly != 0:
  168.                     continue
  169.                 elif v[2] & EnumNotation.Infix != 0:
  170.                     mo = re.search(regexbinaryinfix.format(sanitize(k)), evalstring, re.IGNORECASE)
  171.                     if mo is None:
  172.                         continue
  173.                     elif bestmatch is None or mo.start(0) < bestmatch.start(0):
  174.                         somethinghappened = True
  175.                         bestmatch = mo
  176.                         bestoperator = v
  177.             if somethinghappened:
  178.                 result = bestoperator[3](float(bestmatch.group('num1')), float(bestmatch.group('num2')))
  179.                 evalstring = evalstring[0:bestmatch.start(0)] + str(result) + evalstring[bestmatch.end(0):len(evalstring)]
  180.    
  181.     # step 4.5: if I implement assignment operations, do 'em here
  182.    
  183.     # step 5: apply function if we have one
  184.     if len(functionname) > 0:
  185.         return operatorentry[3](float(evalstring))
  186.    
  187.     #step 5.5: return result
  188.     return evalstring
  189.  
  190. # use by doing:
  191. calculatethis("2d8")
  192. # 9.0
  193. calculatethis("1+1")
  194. # 2.0
  195. calculatethis("abs(-1)")
  196. # 1.0
  197. calculatethis("pi*e")
  198. # 8.53973422268
  199. calculatethis("8*(floor(sin(max(3,2d4))))%27")
  200. # 19.0
  201. calculatethis("6-3*4/5+2^2")
  202. # 7.6
  203. calculatethis("1+2-3*4/5%(6-4)")
  204. # 2.6
  205. calculatethis("max(sub(2,pi),mod(3,4))")
  206. # 3.0
  207. calculatethis("1e+03+1e-03")
  208. # 1000.001
  209. calculatethis("4-5*-5")
  210. # 29.0
  211.  
  212. # etc
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement