I have been developing a graphical calculator, and to allow for natural user input I have written a function which converts their input into a python readable form.
This is my first time using regex, and I believe I managed to get each of the expressions to do what I intended by them.
However, as can been seen there are quite a few different cases to consider, and so the function is relatively long considering what it does.
I was wondering if there was a better solution than this to solve my problem, but if not can I improve my implementation at all?
def edit_function_string(func):
"""Converts the input function into a form executable by Python"""
func = re.sub(r'\s+', '', func) # Strip whitespace
func = re.sub(r'\^', r'**', func) # replaces '^' with '**'
func = re.sub(r'/(([\w()]+(\*\*)?)*)', r'/(\1)', func) # replaces '/nf(x)' with '/(nf(x))'
func = re.sub(r'\*\*(([\w()]+(\*\*)?)*)', r'**(\1)', func) # replaces '**nf(x)' with '**(nf(x))'
func = re.sub(r'(\d+)x', r'\1*x', func) # replaces 'nx' with 'n*x'
func = re.sub(r'(math\.)?ceil(ing)?', 'math.ceil', func) # replaces 'ceil(ing)' with 'math.ceil'
func = re.sub(r'(math\.)?floor', 'math.floor', func) # replaces 'floor' with 'math.floor'
func = re.sub(r'(math\.f)?abs(olute)?|modulus', 'math.fabs', func) # replaces 'abs(olute)' with 'math.fabs'
func = re.sub(r'(math\.)?sqrt|root', 'math.sqrt', func) # replaces 'sqrt' or 'root' with 'math.sqrt'
func = re.sub(r'(math\.)?log|ln', 'math.log', func) # replaces 'log' or 'ln' with 'math.log'
func = re.sub(r'(math\.)?exp', 'math.exp', func) # replaces 'exp' with 'math.exp'
func = re.sub(r'\|(.+?)\|', r'math.fabs(\1)', func) # replaces '|x|' with 'math.fabs(x)'
func = re.sub(r'([\w]+)!|\((.+?)\)!', r'math.factorial(\1\2)', func) # replaces 'x!' with 'math.factorial(x)'
for f in ('sin', 'cos', 'tan', 'sinh', 'cosh', 'tanh'):
# replaces trigonometric or hyperbolic functions with the correct syntax
func = re.sub(r'^(\d*){f}\(|([+\-*/()])(\d*){f}\('.format(f=f),
r'\1\2\3math.{f}('.format(f=f), func)
# replaces inverse trigonometric or hyperbolic functions with the correct syntax
func = re.sub(r'^(\d*)a(rc?)?{f}\(|([+\-*/()])(\d*)a(rc?)?{f}\('.format(f=f),
r'\3\1\4math.a{f}('.format(f=f), func)
for f, reciprocal in (('sec', 'cos'), ('cosec', 'sin'), ('csc', 'sin'), ('cot', 'tan'),
('sech', 'cosh'), ('cosech', 'sinh'), ('csch', 'sinh'), ('coth', 'tanh')):
# replaces reciprocal trigonometric or hyperbolic functions with the correct syntax
func = re.sub(r'^(\d*){f}\((.+?)\)|([+\-*/()])(\d*){f}\((.+?)\)'.format(f=f),
r'\3\1\4(1/math.{reciprocal}(\2\5))'.format(reciprocal=reciprocal), func)
# replaces inverse reciprocal trigonometric or hyperbolic functions with the correct syntax
func = re.sub(r'^(\d*)a(rc?)?{f}\((.+?)\)|([+\-*/()])(\d*)a(rc?)?{f}\((.+?)\)'.format(f=f),
r'\4\1\5(1/math.a{reciprocal}(\3\7))'.format(reciprocal=reciprocal), func)
for i in range(2): # Runs twice in order to deal with overlapping matches
for constant in ('e', 'pi', 'tau'):
# replaces 'e', 'pi', or 'tau' with 'math.e', 'math.pi', or 'math.tau' respectfully
# unless in another function such as: 'math.ceil'
func = re.sub(r'^(\d*){constant}(x?)$|^(\d*){constant}(x?)([+\-*/(])|'
r'([+\-*/()])(\d*){constant}(x?)([+\-*/()])|'
r'([+\-*/)])(\d*){constant}(x?)$'.format(constant=constant),
r'\6\10\1\3\7\11math.{constant}\2\4\8\12\5\9'.format(constant=constant), func)
# replaces 'math.ex', 'math.pix', or 'math.tau' with 'math.e*x', 'math.pi*x', or 'math.tau*x' respectfully
# unless part of another function
func = re.sub(r'math\.{constant}x([+\-*/()])|math.ex$'.format(constant=constant),
r'math.{constant}*x\1'.format(constant=constant), func)
func = re.sub(r'([\dx])math\.', r'\1*math.', func) # replaces 'nmath.' with 'n*math.'
func = re.sub(r'([\dx])\(', r'\1*(', func) # replaces 'n(' with 'n*('
return func