text: from __future__ import division
  from .. import Interpreter, adapter
  from ..interface import Block
  from typing import Optional
  from pyparsing import Literal, CaselessLiteral, Word, Combine, Group, Optional,ZeroOrMore, Forward, nums, alphas, oneOf
  import math
  import operator
  import re
  
  class NumericStringParser(object):
      '''
      Most of this code comes from the fourFn.py pyparsing example
  
      '''
  
      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 + "_$")
          mod = Literal("%")
          plus = Literal("+")
          minus = Literal("-")
          mult = Literal("*")
          iadd = Literal("+=")
          imult = Literal("*=")
          idiv = Literal("/=")
          isub = Literal("-=")
          div = Literal("/")
          lpar = Literal("(").suppress()
          rpar = Literal(")").suppress()
          addop = plus | minus
          multop = mult | div | mod
          iop = iadd | isub | imult | idiv
          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)
          # by defining exponentiation as "atom [ ^ factor ]..." instead of
          # "atom [ ^ atom ]...", we get right-to-left exponents, instead of left-to-right
          # that is, 2^3^2 = 2^(3^2), not (2^3)^2.
          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))
          final = expr + \
              ZeroOrMore((iop + expr).setParseAction(self.pushFirst))
          # addop_term = ( addop + term ).setParseAction( self.pushFirst )
          # general_term = term + ZeroOrMore( addop_term ) | OneOrMore( addop_term)
          # expr <<  general_term
          self.bnf = final
          # map operator symbols to corresponding arithmetic operations
          epsilon = 1e-12
          self.opn = {"+": operator.add,
                      "-": operator.sub,
                      "+=": operator.iadd,
                      "-=": operator.isub,
                      "*": operator.mul,
                      "*=": operator.imul,
                      "/": operator.truediv,
                      "/=": operator.itruediv,
                      "^": operator.pow,
                      "%": operator.mod}
          self.fn = {"sin": math.sin,
                     "cos": math.cos,
                     "tan": math.tan,
                     "sinh": math.sinh,
                     "cosh": math.cosh,
                     "tanh": math.tanh,
                     "exp": math.exp,
                     "abs": abs,
                     "trunc": lambda a: int(a),
                     "round": round,
                     "sgn": lambda a: abs(a) > epsilon and ((a>0)-(a<0)) or 0,
                     "log": lambda a: math.log(a, 10),
                     "ln": math.log,
                     "log2": math.log2}
  
      def evaluateStack(self, s):
          op = s.pop()
          if op == 'unary -':
              return -self.evaluateStack(s)
          if op in self.opn:
              op2 = self.evaluateStack(s)
              op1 = self.evaluateStack(s)
              return self.opn[op](op1, op2)
          elif op == "PI":
              return math.pi  # 3.1415926535
          elif op == "E":
              return math.e  # 2.718281828
          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
  
  NSP = NumericStringParser()
  
  class MathBlock(Block):
      def will_accept(self, ctx : Interpreter.Context) -> bool:
          dec = ctx.verb.declaration.lower()
          return any([dec == "math", dec == "m", dec == "+", dec == "calc"])
  
      def process(self, ctx : Interpreter.Context):
          try: 
              result = str(NSP.eval(ctx.verb.payload.strip(" ")))
              return result
          except:
              return None
  