# Titan Robotics Team 2022: Expression submodule
# Written by Arthur Lu
# Notes:
#    this should be imported as a python module using 'from tra_analysis import equation'
# 	 adapted from https://github.com/pyparsing/pyparsing/blob/master/examples/fourFn.py
# setup:

from __future__ import division

__version__ = "0.0.2-alpha"

__changelog__ = """changelog:
	0.0.2-alpha:
		- wrote BNF using pyparsing and uses a BNF metasyntax
		- renamed this submodule parser
	0.0.1-alpha:
		- took items from equation.ipynb and ported here
"""

__author__ = (
	"Arthur Lu <learthurgo@gmail.com>",
)

from pyparsing import (Literal, CaselessLiteral, Word, Combine, Group, Optional, ZeroOrMore, Forward, nums, alphas, oneOf)
from . import py2
import math
import operator

class BNF(object):

	def pushFirst(self, strg, loc, toks):
		self.exprStack.append(toks[0])

	def pushUMinus(self, strg, loc, toks):
		if toks and toks[0] == '-':
			self.exprStack.append('unary -')

	def __init__(self):
		"""
		expop   :: '^'
		multop  :: '*' | '/'
		addop   :: '+' | '-'
		integer :: ['+' | '-'] '0'..'9'+
		atom    :: PI | E | real | fn '(' expr ')' | '(' expr ')'
		factor  :: atom [ expop factor ]*
		term    :: factor [ multop factor ]*
		expr    :: term [ addop term ]*
		"""
		point = Literal(".")
		e = CaselessLiteral("E")
		fnumber = Combine(Word("+-" + nums, nums) +
						  Optional(point + Optional(Word(nums))) +
						  Optional(e + Word("+-" + nums, nums)))
		ident = Word(alphas, alphas + nums + "_$")
		plus = Literal("+")
		minus = Literal("-")
		mult = Literal("*")
		div = Literal("/")
		lpar = Literal("(").suppress()
		rpar = Literal(")").suppress()
		addop = plus | minus
		multop = mult | div
		expop = Literal("^")
		pi = CaselessLiteral("PI")
		expr = Forward()
		atom = ((Optional(oneOf("- +")) +
				 (ident + lpar + expr + rpar | pi | e | fnumber).setParseAction(self.pushFirst))
				| Optional(oneOf("- +")) + Group(lpar + expr + rpar)
				).setParseAction(self.pushUMinus)
		factor = Forward()
		factor << atom + \
			ZeroOrMore((expop + factor).setParseAction(self.pushFirst))
		term = factor + \
			ZeroOrMore((multop + factor).setParseAction(self.pushFirst))
		expr << term + \
			ZeroOrMore((addop + term).setParseAction(self.pushFirst))

		self.bnf = expr

		epsilon = 1e-12
		
		self.opn = {"+": operator.add,
					"-": operator.sub,
					"*": operator.mul,
					"/": operator.truediv,
					"^": operator.pow}
		self.fn = {"sin": math.sin,
				   "cos": math.cos,
				   "tan": math.tan,
				   "exp": math.exp,
				   "abs": abs,
				   "trunc": lambda a: int(a),
				   "round": round,
				   "sgn": lambda a: abs(a) > epsilon and py2.cmp(a, 0) or 0}

	def evaluateStack(self, s):
		op = s.pop()
		if op == 'unary -':
			return -self.evaluateStack(s)
		if op in "+-*/^":
			op2 = self.evaluateStack(s)
			op1 = self.evaluateStack(s)
			return self.opn[op](op1, op2)
		elif op == "PI":
			return math.pi
		elif op == "E":
			return math.e
		elif op in self.fn:
			return self.fn[op](self.evaluateStack(s))
		elif op[0].isalpha():
			return 0
		else:
			return float(op)

	def eval(self, num_string, parseAll=True):
		self.exprStack = []
		results = self.bnf.parseString(num_string, parseAll)
		val = self.evaluateStack(self.exprStack[:])
		return val