Advertisement
Guest User

calculator.py v.fixeder

a guest
Nov 29th, 2012
135
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 8.84 KB | None | 0 0
  1. # # # calculator.py
  2.  
  3. import math
  4. import random
  5. import string
  6. import re
  7.  
  8. # # # enum of fixities (before, after, in the middle or a function only of its arguments)
  9.  
  10. class EnumNotation:
  11.     Prefix = 1
  12.     Postfix = 2
  13.     Infix = 4
  14.     FunctionOnly = 8
  15.  
  16. # symbol, [0] function name, [1] precedence (higher the more precedence), [2] fixity, [3] 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. # call this when the variables you're calculating with go 'out of scope'
  64. def resetvariables():
  65.     variables = {
  66.         'pi': math.pi,
  67.         'e': math.e,
  68.         'ans': 0,
  69.     }
  70.  
  71. # regular expressions
  72.  
  73. # new rule for leading minus signs - they only bind if
  74. regexdouble = r"(?<!\d)-?(\d*\.)?\d+([eE][+\-]?\d+)?|[nN]a[nN]|-?[iI]nfinity" # two noteworthy things - a look behind so that the first -5 in 5-5*-5 can't be matched (and instead 5 will be matched for 5*-5 and later a subtraction will be performed of 5--25) and the allowance for scientific notation, nans and +/- infinity
  75. regexstraybrackets = r"[()]"
  76.  
  77. regexbinaryinfix = r"(?P<num1>"+regexdouble+")\s*{}\s*(?P<num2>"+regexdouble+")" # {} is where operator is substituted
  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. # sanitizes all regex metacharacters to be treated as literal
  83. def sanitize(s = ""):
  84.     # [\^$.|?*+()
  85.     return re.sub(r"([\[\\\^\$\.\|\?\*\+\(\)])", r"\\\1", s)
  86.  
  87. # evalstring: expression to evaluate e.g. 1+2*3
  88. # variablesdict: dictionary of strings (variable names) to numbers (variable values)
  89. # returns: float value of expression or throws exception
  90. def calc(evalstring = "", variablesdict = {}):
  91.     for (k, v) in variablesdict.iteritems():
  92.         variables[string.lower(k)] = v
  93.     ans = float(calccore(evalstring)) # try/catch exceptions?
  94.     variables['ans'] = ans
  95.     return ans
  96.  
  97. # internal function
  98. def calccore(evalstring = "", functionname = ""):
  99.     functionname = string.lower(functionname)
  100.     # step 1.5: split by ,s if we're in a function?
  101.     expectedcommanumber = 0
  102.     if len(functionname) > 0:
  103.         if functionname in [x[0] for x in binaryoperators.itervalues()]:
  104.             expectedcommanumber = 1
  105.             operatorentry = [x for x in binaryoperators.itervalues() if x[0] == functionname][0]
  106.         elif functionname in [x[0] for x in unaryoperators.itervalues()]:
  107.             expectedcommanumber = 0
  108.             operatorentry = [x for x in unaryoperators.itervalues() if x[0] == functionname][0]
  109.         else:
  110.             raise ArithmeticError("no definition for function name {}".format(functionname))
  111.    
  112.     if expectedcommanumber == 1:
  113.         splitstrings = evalstring.split(",")
  114.         if len(splitstrings) < 2:
  115.             raise ArithmeticError("Passed too few arguments to two valued function: {}({})".format(functionname, evalstring))
  116.         elif len(splitstrings) > 2:
  117.             raise ArithmeticError("Passed too many arguments to two valued function: {}({})".format(functionname, evalstring))
  118.         return operatorentry[3](float(calccore(splitstrings[0])), float(calccore(splitstrings[1])))
  119.    
  120.     # step 2: recurse on brackets + do functions, substring and paste back into evalstring
  121.     while True:
  122.         mo = re.search(regexbrackets, evalstring)
  123.         if mo is None:
  124.             bracketsmo = re.search(regexstraybrackets, evalstring)
  125.             if bracketsmo is not None:
  126.                 raise ArithmeticError("Unbalanced brackets: Too many {}s", bracketsmo.group(0))
  127.             else:
  128.                 break
  129.         else:
  130.             result = calccore(mo.group('bracketed'), string.lower(mo.group('funcname')))
  131.             evalstring = evalstring[0:mo.start(0)] + str(result) + evalstring[mo.end(0):len(evalstring)]
  132.    
  133.     # step 2.5: replace variables with values
  134.     for (k, v) in sorted(variables.iteritems(), lambda x, y: cmp(len(x[0]), len(y[0])), reverse = True):
  135.         evalstring = re.sub(r"\b{}\b".format(k), str(v), evalstring)
  136.    
  137.     # step 3: do all unary operators
  138.     # gotta handle precedence, fixity...
  139.     for i in range(100, -1, -1):
  140.         for (k, v) in filter(lambda x: x[1][1] == i, unaryoperators.iteritems()):
  141.             while True:
  142.                 if v[2] & EnumNotation.FunctionOnly != 0:
  143.                     break
  144.                 if v[2] & EnumNotation.Prefix != 0:
  145.                     mo = re.search(regexunaryprefix.format(sanitize(k)), evalstring, re.IGNORECASE)
  146.                     if mo is None:
  147.                         break
  148.                     else:
  149.                         result = v[3](float(mo.group('num')))
  150.                         evalstring = evalstring[0:mo.start(0)] + str(result) + evalstring[mo.end(0):len(evalstring)]
  151.                 if v[2] & EnumNotation.Postfix != 0:
  152.                     mo = re.search(regexunarypostfix.format(sanitize(k)), evalstring, re.IGNORECASE)
  153.                     if mo is None:
  154.                         break
  155.                     else:
  156.                         result = v[3](float(mo.group('num')))
  157.                         evalstring = evalstring[0:mo.start(0)] + str(result) + evalstring[mo.end(0):len(evalstring)]
  158.    
  159.     # step 4: do all binary operators
  160.     # gotta handle precedence, fixity...
  161.     # 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
  162.    
  163.     # NEW VERSION
  164.     for i in range(100, -1, -1):
  165.         somethinghappened = True
  166.         while somethinghappened:
  167.             bestmatch = None
  168.             bestoperator = None
  169.             somethinghappened = False
  170.             for (k, v) in filter(lambda x: x[1][1] == i, binaryoperators.iteritems()):
  171.                 if v[2] & EnumNotation.FunctionOnly != 0:
  172.                     continue
  173.                 elif v[2] & EnumNotation.Infix != 0:
  174.                     mo = re.search(regexbinaryinfix.format(sanitize(k)), evalstring, re.IGNORECASE)
  175.                     if mo is None:
  176.                         continue
  177.                     elif bestmatch is None or mo.start(0) < bestmatch.start(0):
  178.                         somethinghappened = True
  179.                         bestmatch = mo
  180.                         bestoperator = v
  181.             if somethinghappened:
  182.                 result = bestoperator[3](float(bestmatch.group('num1')), float(bestmatch.group('num2')))
  183.                 evalstring = evalstring[0:bestmatch.start(0)] + str(result) + evalstring[bestmatch.end(0):len(evalstring)]
  184.    
  185.     # step 4.5: if I implement assignment operations, do 'em here
  186.    
  187.     # step 5: apply function if we have one
  188.     if len(functionname) > 0:
  189.         return operatorentry[3](float(evalstring))
  190.    
  191.     #step 5.5: return result
  192.     return evalstring
  193.  
  194. # use by doing:
  195. calc("2d8")
  196. # 9.0
  197. calc("1+1")
  198. # 2.0
  199. calc("abs(-1)")
  200. # 1.0
  201. calc("pi*e")
  202. # 8.53973422268
  203. calc("8*(floor(sin(max(3,2d4))))%27")
  204. # 19.0
  205. calc("6-3*4/5+2^2")
  206. # 7.6
  207. calc("1+2-3*4/5%(6-4)")
  208. # 2.6
  209. calc("max(sub(2,pi),mod(3,4))")
  210. # 3.0
  211. calc("1e+03+1e-03")
  212. # 1000.001
  213. calc("4-5*-5")
  214. # 29.0
  215. calc("-5-5-5-5+5-5")
  216. # -20.0
  217. calc("5--5")
  218. # 10.0
  219.  
  220. # etc
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement