I wrote this equation parser and solver, I feel like it is well documented and tested:
import doctest def swap_sign(num): """ Swaps the sign of a number-representing string. >>> swap_sign('9') '-9' >>> swap_sign('-4x') '4x' """ if num.startswith('-'): return ''.join(num[1:]) elif num.startswith('+'): return '-'+''.join(num[1:]) else: return '-'+num def add_one_before_x(expr): """ It is a common usage to omit the one before the x saying 'x' when you really mean '1x'. >>> add_one_before_x('x +2x = 9 -x^2') '1x +2x = 9 -1x^2' """ if expr.startswith('x'): expr = '1'+expr return expr.replace(' x',' 1x').replace(' -x',' -1x').replace(' +x',' +1x') def move_operators(expr): """ Sticks the operators the next term for easier further parsing. >>> move_operators('3 + 9x = 3 - 3x^2 + 4') '3 +9x = 3 -3x^2 +4' """ return expr.replace('+ ','+').replace('- ','-').replace('--','-') def move_to_the_same_side(expr): """ Moves all the numbers and x-s to one side, changing sign when needed. >>> move_to_the_same_side("3x -4x^2 +5 -2 = 2x") ['3x', '-4x^2', '+5', '-2', '-2x'] >>> move_to_the_same_side("9x = 1x^2 -4 +2 -1 +7x") ['9x', '-1x^2', '4', '-2', '1', '-7x'] """ if ' = ' not in expr: raise ValueError("There is no second term, remember spaces around the equal sign.") left,right = expr.split(' = ') return left.split(' ') + [swap_sign(token) for token in right.split(' ')] def to_normal_form(expr): """ Performs further parsing on all the coefficients on one side and return the coefficients (a,b,c). >>> to_normal_form(['3x', '-4x^2', '+5', '-2', '-2x']) (-4, 1, 3) """ bare_nums = [i for i in expr if 'x' not in i] xes = [i for i in expr if 'x' in i and '^2' not in i] two_pow_xes = [i for i in expr if 'x' in i and '^2' in i] final_num = sum([int(n) for n in bare_nums]) final_x = sum([int(x.replace('x','')) for x in xes]) final_pow_x = sum([int(x.replace('x^2','')) for x in two_pow_xes]) return final_pow_x, final_x, final_num def first_grade_solve(coeffs): """ Solves the first grade equations using the trivial first grade formula. Also solves equations of grade 0. >>> first_grade_solve((0,0,3)) # 0x + 3 = 0 'Never' >>> first_grade_solve((0,2,3)) # 2x + 3 = 0 (-1.5,) """ _,a,b = coeffs if a == 0: return "Every time" if b == 0 else "Never" return ((-b) / a,) def second_grade_solve(coeffs): """ Solves second grade equations using the well known second grade formula. >>> second_grade_solve((1,2,1)) # 1x^2 + 2x + 1 = 0 (-1.0, -1.0) >>> second_grade_solve((1,5,6)) # 1x^2 + 5x + 6 = 0 (-2.0, -3.0) """ a,b,c = coeffs delta = b**2 - 4*a*c return (((-b)+delta**.5)/(2*a), ((-b)-delta**.5)/(2*a)) def solve(coefficients): """ Dispatches solving to the correct method or aborts if the equation grade is too high. >>> solve((1,5,6)) (-2.0, -3.0) >>> solve((0,2,4)) (-2.0,) """ if coefficients[0] == 0: return first_grade_solve(coefficients) elif len(coefficients) == 3: return second_grade_solve(coefficients) raise NotImplementedError("Only 0th, 1st and 2nd grade equations can be solved") def parse_and_solve(expr): """ Connects the other functions to provide full equation solving. >>> parse_and_solve("2x - 4 = 0") (2.0,) >>> parse_and_solve("1 = 2") 'Never' >>> parse_and_solve("x^2 + 2x = -1") (-1.0, -1.0) """ simpler = add_one_before_x(move_operators(expr)) same_sided = move_to_the_same_side(simpler) normal_form = to_normal_form(same_sided) return solve(normal_form) def interface(): print("Welcome to the symbolic equation solver.") print("Please enter equations in the form:") print("3x - 4x^2 + 5 - 2 = 2x + x^2\n") while True: expr = input("> ") try: result = parse_and_solve(expr) if result in ("Every time","Never"): print(result) else: print('\n'.join(['x = '+str(x) for x in result])) except Exception as E: print("Invalid Expression:", E) if __name__ == "__main__": doctest.testmod() interface()