Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- # # # calculator.py
- import math
- import random
- import string
- import re
- # # # enum of fixities (before, after, in the middle or a function only of its arguments)
- class EnumNotation:
- Prefix = 1
- Postfix = 2
- Infix = 4
- FunctionOnly = 8
- # symbol, [0] function name, [1] precedence (higher the more precedence), [2] fixity, [3] what it does to its operators
- binaryoperators = {
- 'log': ('log', 1, EnumNotation.FunctionOnly, lambda x, y: math.log(x, y)),
- 'atan2': ('atan2', 1, EnumNotation.FunctionOnly, lambda x, y: math.atan2(x, y)),
- 'gauss': ('gauss', 1, EnumNotation.FunctionOnly, lambda x, y: random.gauss(x, y)),
- '+': ('add', 10, EnumNotation.Infix, lambda x, y: x + y),
- '-': ('sub', 10, EnumNotation.Infix, lambda x, y: x - y),
- '*': ('mul', 20, EnumNotation.Infix, lambda x, y: x * y),
- '/': ('div', 20, EnumNotation.Infix, lambda x, y: x / y),
- '%': ('mod', 20, EnumNotation.Infix, lambda x, y: x % y),
- '^': ('pow', 30, EnumNotation.Infix, lambda x, y: x ** y),
- 'max': ('max', 40, EnumNotation.Infix, lambda x, y: max(x, y)),
- 'min': ('min', 40, EnumNotation.Infix, lambda x, y: min(x, y)),
- 'avg': ('avg', 40, EnumNotation.Infix, lambda x, y: (x + y)/ 2),
- '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))])),
- }
- unaryoperators = {
- 'floor': ('floor', 1, EnumNotation.FunctionOnly, lambda x: math.floor(x)),
- 'ceil': ('ceil', 1, EnumNotation.FunctionOnly, lambda x: math.ceil(x)),
- 'round': ('round', 1, EnumNotation.FunctionOnly, lambda x: round(x)),
- 'V': ('sqrt', 10, EnumNotation.Prefix, lambda x: math.sqrt(x)),
- 'ln': ('ln', 15, EnumNotation.Prefix, lambda x: math.log(x)),
- 'abs': ('abs', 15, EnumNotation.Prefix, lambda x: abs(x)),
- 'sin': ('sin', 15, EnumNotation.Prefix, lambda x: math.sin(x)),
- 'cos': ('cos', 15, EnumNotation.Prefix, lambda x: math.cos(x)),
- 'tan': ('tan', 15, EnumNotation.Prefix, lambda x: math.tan(x)),
- 'asin': ('asin', 15, EnumNotation.Prefix, lambda x: math.asin(x)),
- 'acos': ('acos', 15, EnumNotation.Prefix, lambda x: math.acos(x)),
- 'atan': ('atan', 15, EnumNotation.Prefix, lambda x: math.atan(x)),
- 'sinh': ('sinh', 15, EnumNotation.Prefix, lambda x: math.sinh(x)),
- 'cosh': ('cosh', 15, EnumNotation.Prefix, lambda x: math.cosh(x)),
- 'tanh': ('tanh', 15, EnumNotation.Prefix, lambda x: math.tanh(x)),
- 'asinh': ('asinh', 15, EnumNotation.Prefix, lambda x: math.asinh(x)),
- 'acosh': ('acosh', 15, EnumNotation.Prefix, lambda x: math.acosh(x)),
- 'atanh': ('atanh', 15, EnumNotation.Prefix, lambda x: math.atanh(x)),
- '!': ('factorial', 20, EnumNotation.Postfix, lambda x: math.gamma(x+1)),
- 'rand': ('rand', 25, EnumNotation.Prefix, lambda x: random.uniform(0, x)),
- }
- variables = {
- 'pi': math.pi,
- 'e': math.e,
- 'ans': 0,
- }
- # call this when the variables you're calculating with go 'out of scope'
- def resetvariables():
- variables = {
- 'pi': math.pi,
- 'e': math.e,
- 'ans': 0,
- }
- # regular expressions
- # new rule for leading minus signs - they only bind if
- 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
- regexstraybrackets = r"[()]"
- regexbinaryinfix = r"(?P<num1>"+regexdouble+")\s*{}\s*(?P<num2>"+regexdouble+")" # {} is where operator is substituted
- regexunaryprefix = r"(?<!\d){}(?P<num>"+regexdouble+")" # lookbehind to prevent two numbers from getting mashed together
- regexunarypostfix = r"(?P<num>"+regexdouble+"){}(?![\d.])" # lookahead, similar reason
- 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
- # sanitizes all regex metacharacters to be treated as literal
- def sanitize(s = ""):
- # [\^$.|?*+()
- return re.sub(r"([\[\\\^\$\.\|\?\*\+\(\)])", r"\\\1", s)
- # evalstring: expression to evaluate e.g. 1+2*3
- # variablesdict: dictionary of strings (variable names) to numbers (variable values)
- # returns: float value of expression or throws exception
- def calc(evalstring = "", variablesdict = {}):
- for (k, v) in variablesdict.iteritems():
- variables[string.lower(k)] = v
- ans = float(calccore(evalstring)) # try/catch exceptions?
- variables['ans'] = ans
- return ans
- # internal function
- def calccore(evalstring = "", functionname = ""):
- functionname = string.lower(functionname)
- # step 1.5: split by ,s if we're in a function?
- expectedcommanumber = 0
- if len(functionname) > 0:
- if functionname in [x[0] for x in binaryoperators.itervalues()]:
- expectedcommanumber = 1
- operatorentry = [x for x in binaryoperators.itervalues() if x[0] == functionname][0]
- elif functionname in [x[0] for x in unaryoperators.itervalues()]:
- expectedcommanumber = 0
- operatorentry = [x for x in unaryoperators.itervalues() if x[0] == functionname][0]
- else:
- raise ArithmeticError("no definition for function name {}".format(functionname))
- if expectedcommanumber == 1:
- splitstrings = evalstring.split(",")
- if len(splitstrings) < 2:
- raise ArithmeticError("Passed too few arguments to two valued function: {}({})".format(functionname, evalstring))
- elif len(splitstrings) > 2:
- raise ArithmeticError("Passed too many arguments to two valued function: {}({})".format(functionname, evalstring))
- return operatorentry[3](float(calccore(splitstrings[0])), float(calccore(splitstrings[1])))
- # step 2: recurse on brackets + do functions, substring and paste back into evalstring
- while True:
- mo = re.search(regexbrackets, evalstring)
- if mo is None:
- bracketsmo = re.search(regexstraybrackets, evalstring)
- if bracketsmo is not None:
- raise ArithmeticError("Unbalanced brackets: Too many {}s", bracketsmo.group(0))
- else:
- break
- else:
- result = calccore(mo.group('bracketed'), string.lower(mo.group('funcname')))
- evalstring = evalstring[0:mo.start(0)] + str(result) + evalstring[mo.end(0):len(evalstring)]
- # step 2.5: replace variables with values
- for (k, v) in sorted(variables.iteritems(), lambda x, y: cmp(len(x[0]), len(y[0])), reverse = True):
- evalstring = re.sub(r"\b{}\b".format(k), str(v), evalstring)
- # step 3: do all unary operators
- # gotta handle precedence, fixity...
- for i in range(100, -1, -1):
- for (k, v) in filter(lambda x: x[1][1] == i, unaryoperators.iteritems()):
- while True:
- if v[2] & EnumNotation.FunctionOnly != 0:
- break
- if v[2] & EnumNotation.Prefix != 0:
- mo = re.search(regexunaryprefix.format(sanitize(k)), evalstring, re.IGNORECASE)
- if mo is None:
- break
- else:
- result = v[3](float(mo.group('num')))
- evalstring = evalstring[0:mo.start(0)] + str(result) + evalstring[mo.end(0):len(evalstring)]
- if v[2] & EnumNotation.Postfix != 0:
- mo = re.search(regexunarypostfix.format(sanitize(k)), evalstring, re.IGNORECASE)
- if mo is None:
- break
- else:
- result = v[3](float(mo.group('num')))
- evalstring = evalstring[0:mo.start(0)] + str(result) + evalstring[mo.end(0):len(evalstring)]
- # step 4: do all binary operators
- # gotta handle precedence, fixity...
- # 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
- # NEW VERSION
- for i in range(100, -1, -1):
- somethinghappened = True
- while somethinghappened:
- bestmatch = None
- bestoperator = None
- somethinghappened = False
- for (k, v) in filter(lambda x: x[1][1] == i, binaryoperators.iteritems()):
- if v[2] & EnumNotation.FunctionOnly != 0:
- continue
- elif v[2] & EnumNotation.Infix != 0:
- mo = re.search(regexbinaryinfix.format(sanitize(k)), evalstring, re.IGNORECASE)
- if mo is None:
- continue
- elif bestmatch is None or mo.start(0) < bestmatch.start(0):
- somethinghappened = True
- bestmatch = mo
- bestoperator = v
- if somethinghappened:
- result = bestoperator[3](float(bestmatch.group('num1')), float(bestmatch.group('num2')))
- evalstring = evalstring[0:bestmatch.start(0)] + str(result) + evalstring[bestmatch.end(0):len(evalstring)]
- # step 4.5: if I implement assignment operations, do 'em here
- # step 5: apply function if we have one
- if len(functionname) > 0:
- return operatorentry[3](float(evalstring))
- #step 5.5: return result
- return evalstring
- # use by doing:
- calc("2d8")
- # 9.0
- calc("1+1")
- # 2.0
- calc("abs(-1)")
- # 1.0
- calc("pi*e")
- # 8.53973422268
- calc("8*(floor(sin(max(3,2d4))))%27")
- # 19.0
- calc("6-3*4/5+2^2")
- # 7.6
- calc("1+2-3*4/5%(6-4)")
- # 2.6
- calc("max(sub(2,pi),mod(3,4))")
- # 3.0
- calc("1e+03+1e-03")
- # 1000.001
- calc("4-5*-5")
- # 29.0
- calc("-5-5-5-5+5-5")
- # -20.0
- calc("5--5")
- # 10.0
- # etc
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement