text: """
  Parser for FullForm[Downvalues[]] of Mathematica rules.
  
  This parser is customised to parse the output in MatchPy rules format. Multiple
  `Constraints` are divided into individual `Constraints` because it helps the
  MatchPy's `ManyToOneReplacer` to backtrack earlier and improve the speed.
  
  Parsed output is formatted into readable format by using `sympify` and print the
  expression using `sstr`. This replaces `And`, `Mul`, 'Pow' by their respective
  symbols.
  
  Mathematica
  ===========
  
  To get the full form from Wolfram Mathematica, type:
  ```
  ShowSteps = False
  Import["RubiLoader.m"]
  Export["output.txt", ToString@FullForm@DownValues@Int]
  ```
  
  The file ``output.txt`` will then contain the rules in parseable format.
  
  References
  ==========
  [1] http://reference.wolfram.com/language/ref/FullForm.html
  [2] http://reference.wolfram.com/language/ref/DownValues.html
  [3] https://gist.github.com/Upabjojr/bc07c49262944f9c1eb0
  """
  import re
  import os
  import inspect
  from sympy.core.function import Function
  from sympy.core.symbol import Symbol
  from sympy.core.sympify import sympify
  from sympy.sets.sets import Set
  from sympy.printing import StrPrinter
  from sympy.utilities.misc import debug
  
  
  class RubiStrPrinter(StrPrinter):
      def _print_Not(self, expr):
          return "Not(%s)" % self._print(expr.args[0])
  
  
  def rubi_printer(expr, **settings):
      return RubiStrPrinter(settings).doprint(expr)
  
  
  replacements = dict(  # Mathematica equivalent functions in SymPy
          Times="Mul",
          Plus="Add",
          Power="Pow",
          Log='log',
          Exp='exp',
          Sqrt='sqrt',
          Cos='cos',
          Sin='sin',
          Tan='tan',
          Cot='1/tan',
          cot='1/tan',
          Sec='1/cos',
          sec='1/cos',
          Csc='1/sin',
          csc='1/sin',
          ArcSin='asin',
          ArcCos='acos',
          # ArcTan='atan',
          ArcCot='acot',
          ArcSec='asec',
          ArcCsc='acsc',
          Sinh='sinh',
          Cosh='cosh',
          Tanh='tanh',
          Coth='1/tanh',
          coth='1/tanh',
          Sech='1/cosh',
          sech='1/cosh',
          Csch='1/sinh',
          csch='1/sinh',
          ArcSinh='asinh',
          ArcCosh='acosh',
          ArcTanh='atanh',
          ArcCoth='acoth',
          ArcSech='asech',
          ArcCsch='acsch',
          Expand='expand',
          Im='im',
          Re='re',
          Flatten='flatten',
          Polylog='polylog',
          Cancel='cancel',
          #Gamma='gamma',
          TrigExpand='expand_trig',
          Sign='sign',
          Simplify='simplify',
          Defer='UnevaluatedExpr',
          Identity = 'S',
          Sum = 'Sum_doit',
          Module = 'With',
          Block = 'With',
          Null = 'None'
  )
  
  temporary_variable_replacement = {  # Temporarily rename because it can raise errors while sympifying
      'gcd' : "_gcd",
      'jn' : "_jn",
  }
  
  permanent_variable_replacement = {  # Permamenely rename these variables
      r"\[ImaginaryI]" : 'ImaginaryI',
      "$UseGamma": '_UseGamma',
  }
  
  # These functions have different return type in different cases. So better to use a try and except in the constraints, when any of these appear
  f_diff_return_type = ['BinomialParts', 'BinomialDegree', 'TrinomialParts', 'GeneralizedBinomialParts', 'GeneralizedTrinomialParts', 'PseudoBinomialParts', 'PerfectPowerTest',
      'SquareFreeFactorTest', 'SubstForFractionalPowerOfQuotientOfLinears', 'FractionalPowerOfQuotientOfLinears', 'InverseFunctionOfQuotientOfLinears',
      'FractionalPowerOfSquareQ', 'FunctionOfLinear', 'FunctionOfInverseLinear', 'FunctionOfTrig', 'FindTrigFactor', 'FunctionOfLog',
      'PowerVariableExpn', 'FunctionOfSquareRootOfQuadratic', 'SubstForFractionalPowerOfLinear', 'FractionalPowerOfLinear', 'InverseFunctionOfLinear',
      'Divides', 'DerivativeDivides', 'TrigSquare', 'SplitProduct', 'SubstForFractionalPowerOfQuotientOfLinears', 'InverseFunctionOfQuotientOfLinears',
      'FunctionOfHyperbolic', 'SplitSum']
  
  
  def contains_diff_return_type(a):
      """
      This function returns whether an expression contains functions which have different return types in
      diiferent cases.
      """
      if isinstance(a, list):
          for i in a:
              if contains_diff_return_type(i):
                  return True
      elif type(a) == Function('With') or type(a) == Function('Module'):
          for i in f_diff_return_type:
              if a.has(Function(i)):
                  return True
      else:
          if a in f_diff_return_type:
              return True
      return False
  
  
  def parse_full_form(wmexpr):
      """
      Parses FullForm[Downvalues[]] generated by Mathematica
      """
      out = []
      stack = [out]
      generator = re.finditer(r'[\[\],]', wmexpr)
      last_pos = 0
      for match in generator:
          if match is None:
              break
          position = match.start()
          last_expr = wmexpr[last_pos:position].replace(',', '').replace(']', '').replace('[', '').strip()
  
          if match.group() == ',':
              if last_expr != '':
                  stack[-1].append(last_expr)
          elif match.group() == ']':
              if last_expr != '':
                  stack[-1].append(last_expr)
              stack.pop()
          elif match.group() == '[':
              stack[-1].append([last_expr])
              stack.append(stack[-1][-1])
          last_pos = match.end()
      return out[0]
  
  
  def get_default_values(parsed, default_values={}):
      """
      Returns Optional variables and their values in the pattern
      """
      if not isinstance(parsed, list):
          return default_values
  
      if parsed[0] == "Times":  # find Default arguments for "Times"
          for i in parsed[1:]:
              if i[0] == "Optional":
                  default_values[(i[1][1])] = 1
  
      if parsed[0] == "Plus":  # find Default arguments for "Plus"
          for i in parsed[1:]:
              if i[0] == "Optional":
                  default_values[(i[1][1])] = 0
  
      if parsed[0] == "Power":  # find Default arguments for "Power"
          for i in parsed[1:]:
              if i[0] == "Optional":
                  default_values[(i[1][1])] = 1
  
      if len(parsed) == 1:
          return default_values
  
      for i in parsed:
          default_values = get_default_values(i, default_values)
  
      return default_values
  
  
  def add_wildcards(string, optional={}):
      """
      Replaces `Pattern(variable)` by `variable` in `string`.
      Returns the free symbols present in the string.
      """
      symbols = []  # stores symbols present in the expression
  
      p = r'(Optional\(Pattern\((\w+), Blank\)\))'
      matches = re.findall(p, string)
      for i in matches:
          string = string.replace(i[0], "WC('{}', S({}))".format(i[1], optional[i[1]]))
          symbols.append(i[1])
  
      p = r'(Pattern\((\w+), Blank\))'
      matches = re.findall(p, string)
      for i in matches:
          string = string.replace(i[0], i[1] + '_')
          symbols.append(i[1])
  
      p = r'(Pattern\((\w+), Blank\(Symbol\)\))'
      matches = re.findall(p, string)
      for i in matches:
          string = string.replace(i[0], i[1] + '_')
          symbols.append(i[1])
  
      return string, symbols
  
  
  def seperate_freeq(s, variables=[], x=None):
      """
      Returns list of symbols in FreeQ.
      """
      if s[0] == 'FreeQ':
          if len(s[1]) == 1:
              variables = [s[1]]
          else:
              variables = s[1][1:]
          x = s[2]
      else:
          for i in s[1:]:
              variables, x = seperate_freeq(i, variables, x)
              return variables, x
      return variables, x
  
  
  def parse_freeq(l, x, cons_index, cons_dict, cons_import, symbols=None):
      """
      Converts FreeQ constraints into MatchPy constraint
      """
      res = []
      cons = ''
      for i in l:
          if isinstance(i, str):
              r = '        return FreeQ({}, {})'.format(i, x)
              # First it checks if a constraint is already present in `cons_dict`, If yes, use it else create a new one.
              if r not in cons_dict.values():
                  cons_index += 1
                  c = '\n    def cons_f{}({}, {}):\n'.format(cons_index, i, x)
                  c += r
                  c += '\n\n    cons{} = CustomConstraint({})\n'.format(cons_index, 'cons_f{}'.format(cons_index))
                  cons_name = 'cons{}'.format(cons_index)
                  cons_dict[cons_name] = r
              else:
                  c = ''
                  cons_name = next(key for key, value in sorted(cons_dict.items()) if value == r)
  
          elif isinstance(i, list):
              s = sorted(set(get_free_symbols(i, symbols)))
              s = ', '.join(s)
              r = '        return FreeQ({}, {})'.format(generate_sympy_from_parsed(i), x)
              if r not in cons_dict.values():
                  cons_index += 1
                  c = '\n    def cons_f{}({}):\n'.format(cons_index, s)
                  c += r
                  c += '\n\n    cons{} = CustomConstraint({})\n'.format(cons_index, 'cons_f{}'.format(cons_index))
                  cons_name = 'cons{}'.format(cons_index)
                  cons_dict[cons_name] = r
              else:
                  c = ''
                  cons_name = next(key for key, value in cons_dict.items() if value == r)
  
          if cons_name not in cons_import:
              cons_import.append(cons_name)
  
          res.append(cons_name)
          cons += c
  
      if res != []:
          return ', ' + ', '.join(res), cons, cons_index
      return '', cons, cons_index
  
  
  def generate_sympy_from_parsed(parsed, wild=False, symbols=(), replace_Int=False):
      """
      Parses list into Python syntax.
  
      Parameters
      ==========
      wild : When set to True, the symbols are replaced as wild symbols.
      symbols : Symbols already present in the pattern.
      replace_Int: when set to True, `Int` is replaced by `Integral`(used to parse pattern).
      """
      out = ""
  
      if not isinstance(parsed, list):
          try:  # return S(number) if parsed is Number
              float(parsed)
              return "S({})".format(parsed)
          except:
              pass
          if parsed in symbols:
              if wild:
                  return parsed + '_'
          return parsed
  
      if parsed[0] == 'Rational':
          return 'S({})/S({})'.format(generate_sympy_from_parsed(parsed[1], wild=wild, symbols=symbols, replace_Int=replace_Int), generate_sympy_from_parsed(parsed[2], wild=wild, symbols=symbols, replace_Int=replace_Int))
  
      if parsed[0] in replacements:
          out += replacements[parsed[0]]
      elif parsed[0] == 'Int' and replace_Int:
          out += 'Integral'
      else:
          out += parsed[0]
  
      if len(parsed) == 1:
          return out
  
      result = [generate_sympy_from_parsed(i, wild=wild, symbols=symbols, replace_Int=replace_Int) for i in parsed[1:]]
      if '' in result:
          result.remove('')
  
      out += "("
      out += ", ".join(result)
      out += ")"
  
      return out
  
  
  def get_free_symbols(s, symbols, free_symbols=None):
      """
      Returns free_symbols present in `s`.
      """
      free_symbols = free_symbols or []
      if not isinstance(s, list):
          if s in symbols:
              free_symbols.append(s)
          return free_symbols
  
      for i in s:
          free_symbols = get_free_symbols(i, symbols, free_symbols)
  
      return free_symbols
  
  
  def set_matchq_in_constraint(a, cons_index):
      """
      Takes care of the case, when a pattern matching has to be done inside a constraint.
      """
      lst = []
      res = ''
      if isinstance(a, list):
          if a[0] == 'MatchQ':
              s = a
              optional = get_default_values(s, {})
              r = generate_sympy_from_parsed(s, replace_Int=True)
              r, free_symbols = add_wildcards(r, optional=optional)
              free_symbols = sorted(set(free_symbols))  # remove common symbols
              r = sympify(r, locals={"Or": Function("Or"), "And": Function("And"), "Not":Function("Not")})
              pattern = r.args[1].args[0]
              cons = r.args[1].args[1]
              pattern = rubi_printer(pattern, sympy_integers=True)
              pattern = setWC(pattern)
              res = '        def _cons_f_{}({}):\n            return {}\n'.format(cons_index, ', '.join(free_symbols), cons)
              res += '        _cons_{} = CustomConstraint(_cons_f_{})\n'.format(cons_index, cons_index)
              res += '        pat = Pattern(UtilityOperator({}, x), _cons_{})\n'.format(pattern, cons_index)
              res += '        result_matchq = is_match(UtilityOperator({}, x), pat)'.format(r.args[0])
              return "result_matchq", res
  
          else:
              for i in a:
                  if isinstance(i, list):
                      r = set_matchq_in_constraint(i, cons_index)
                      lst.append(r[0])
                      res = r[1]
                  else:
                      lst.append(i)
      return lst, res
  
  
  def _divide_constriant(s, symbols, cons_index, cons_dict, cons_import):
      # Creates a CustomConstraint of the form `CustomConstraint(lambda a, x: FreeQ(a, x))`
      lambda_symbols = sorted(set(get_free_symbols(s, symbols, [])))
      r = generate_sympy_from_parsed(s)
      r = sympify(r, locals={"Or": Function("Or"), "And": Function("And"), "Not":Function("Not")})
      if r.has(Function('MatchQ')):
          match_res = set_matchq_in_constraint(s, cons_index)
          res = match_res[1]
          res += '\n        return {}'.format(rubi_printer(sympify(generate_sympy_from_parsed(match_res[0]), locals={"Or": Function("Or"), "And": Function("And"), "Not":Function("Not")}), sympy_integers = True))
  
      elif contains_diff_return_type(s):
          res = '        try:\n            return {}\n        except (TypeError, AttributeError):\n            return False'.format(rubi_printer(r, sympy_integers=True))
      else:
          res = '        return {}'.format(rubi_printer(r, sympy_integers=True))
  
      # First it checks if a constraint is already present in `cons_dict`, If yes, use it else create a new one.
      if not res in cons_dict.values():
          cons_index += 1
          cons = '\n    def cons_f{}({}):\n'.format(cons_index, ', '.join(lambda_symbols))
          if 'x' in lambda_symbols:
              cons += '        if isinstance(x, (int, Integer, float, Float)):\n            return False\n'
          cons += res
          cons += '\n\n    cons{} = CustomConstraint({})\n'.format(cons_index, 'cons_f{}'.format(cons_index))
          cons_name = 'cons{}'.format(cons_index)
          cons_dict[cons_name] = res
      else:
          cons = ''
          cons_name = next(key for key, value in cons_dict.items() if value == res)
  
      if cons_name not in cons_import:
          cons_import.append(cons_name)
      return cons_name, cons, cons_index
  
  
  def divide_constraint(s, symbols, cons_index, cons_dict, cons_import):
      """
      Divides multiple constraints into smaller constraints.
  
      Parameters
      ==========
      s : constraint as list
      symbols : all the symbols present in the expression
      """
      result =[]
      cons = ''
      if s[0] == 'And':
          for i in s[1:]:
              if i[0]!= 'FreeQ':
                  a = _divide_constriant(i, symbols, cons_index, cons_dict, cons_import)
                  result.append(a[0])
                  cons += a[1]
                  cons_index = a[2]
      else:
          a = _divide_constriant(s, symbols, cons_index, cons_dict, cons_import)
          result.append(a[0])
          cons += a[1]
          cons_index = a[2]
  
      r = ['']
      for i in result:
          if i != '':
              r.append(i)
  
      return ', '.join(r),cons, cons_index
  
  
  def setWC(string):
      """
      Replaces `WC(a, b)` by `WC('a', S(b))`
      """
      p = r'(WC\((\w+), S\(([-+]?\d)\)\))'
      matches = re.findall(p, string)
      for i in matches:
          string = string.replace(i[0], "WC('{}', S({}))".format(i[1], i[2]))
  
      return string
  
  
  def process_return_type(a1, L):
      """
      Functions like `Set`, `With` and `CompoundExpression` has to be taken special care.
      """
      a = sympify(a1[1])
      x = ''
      processed = False
      return_value = ''
      if type(a) == Function('With') or type(a) == Function('Module'):
          for i in a.args:
              for s in i.args:
                  if isinstance(s, Set) and not s in L:
                      x += '\n        {} = {}'.format(s.args[0], rubi_printer(s.args[1], sympy_integers=True))
  
              if not type(i) in (Function('List'),  Function('CompoundExpression')) and not i.has(Function('CompoundExpression')):
                  return_value = i
                  processed = True
  
              elif type(i) == Function('CompoundExpression'):
                  return_value = i.args[-1]
                  processed = True
  
              elif type(i.args[0]) == Function('CompoundExpression'):
                  C = i.args[0]
                  return_value = '{}({}, {})'.format(i.func, C.args[-1], i.args[1])
                  processed = True
      return x, return_value, processed
  
  
  def extract_set(s, L):
      """
      this function extracts all `Set` functions
      """
      lst = []
      if isinstance(s, Set) and not s in L:
          lst.append(s)
      else:
          try:
              for i in s.args:
                  lst += extract_set(i, L)
          except:  # when s has no attribute args (like `bool`)
              pass
      return lst
  
  
  def replaceWith(s, symbols, index):
      """
      Replaces `With` and `Module by python functions`
      """
      return_type = None
      with_value = ''
      if type(s) == Function('With') or type(s) == Function('Module'):
          constraints = ' '
          result = '\n\n\ndef With{}({}):'.format(index, ', '.join(symbols))
          if type(s.args[0]) == Function('List'):  # get all local variables of With and Module
              L = list(s.args[0].args)
          else:
              L = [s.args[0]]
          lst = []
          for i in s.args[1:]:
              lst += extract_set(i, L)
  
          L += lst
          for i in L:  # define local variables
              if isinstance(i, Set):
                  with_value += '\n    {} = {}'.format(i.args[0], rubi_printer(i.args[1], sympy_integers=True))
  
              elif isinstance(i, Symbol):
                  with_value += "\n    {} = Symbol('{}')".format(i, i)
          #result += with_value
          if type(s.args[1]) == Function('CompoundExpression'):  # Expand CompoundExpression
              C = s.args[1]
              result += with_value
              if isinstance(C.args[0], Set):
                  result += '\n    {} = {}'.format(C.args[0].args[0], C.args[0].args[1])
              result += '\n    return {}'.format(rubi_printer(C.args[1], sympy_integers=True))
              return result, constraints, return_type
  
          elif type(s.args[1]) == Function('Condition'):
              C = s.args[1]
              if len(C.args) == 2:
                  if all(j in symbols for j in [str(i) for i in C.free_symbols]):
                      result += with_value
                      #constraints += 'CustomConstraint(lambda {}: {})'.format(', '.join([str(i) for i in C.free_symbols]), sstr(C.args[1], sympy_integers=True))
                      result += '\n    return {}'.format(rubi_printer(C.args[0], sympy_integers=True))
                  else:
                      if 'x' in symbols:
                          result += '\n    if isinstance(x, (int, Integer, float, Float)):\n        return False'
  
                      if contains_diff_return_type(s):
                          n_with_value = with_value.replace('\n', '\n    ')
                          result += '\n    try:{}\n        res = {}'.format(n_with_value, rubi_printer(C.args[1], sympy_integers=True))
                          result += '\n    except (TypeError, AttributeError):\n        return False'
                          result += '\n    if res:'
  
                      else:
                          result+=with_value
                          result += '\n    if {}:'.format(rubi_printer(C.args[1], sympy_integers=True))
                      return_type = (with_value, rubi_printer(C.args[0], sympy_integers=True))
                      return_type1 = process_return_type(return_type, L)
                      if return_type1[2]:
                          return_type = (with_value+return_type1[0], rubi_printer(return_type1[1]))
                      result += '\n        return True'
                      result += '\n    return False'
              constraints = ', CustomConstraint(With{})'.format(index)
              return result, constraints, return_type
  
          elif type(s.args[1]) == Function('Module') or type(s.args[1]) == Function('With'):
              C = s.args[1]
              result += with_value
              return_type = (with_value, rubi_printer(C, sympy_integers=True))
              return_type1 = process_return_type(return_type, L)
              if return_type1[2]:
                  return_type = (with_value+return_type1[0], rubi_printer(return_type1[1]))
              result += return_type1[0]
              result += '\n    return {}'.format(rubi_printer(return_type1[1]))
              return result, constraints, None
  
          elif s.args[1].has(Function("CompoundExpression")):
              C = s.args[1].args[0]
              result += with_value
              if isinstance(C.args[0], Set):
                  result += '\n    {} = {}'.format(C.args[0].args[0], C.args[0].args[1])
              result += '\n    return {}({}, {})'.format(s.args[1].func, C.args[-1], s.args[1].args[1])
              return result, constraints, None
  
          result += with_value
          result += '\n    return {}'.format(rubi_printer(s.args[1], sympy_integers=True))
          return result, constraints, return_type
      else:
          return rubi_printer(s, sympy_integers=True), '', return_type
  
  
  def downvalues_rules(r, header, cons_dict, cons_index, index):
      """
      Function which generates parsed rules by substituting all possible
      combinations of default values.
      """
      rules = '['
      parsed = '\n\n'
      repl_funcs = '\n\n'
      cons = ''
      cons_import = [] # it contains name of constraints that need to be imported for rules.
      for i in r:
          debug('parsing rule {}'.format(r.index(i) + 1))
          # Parse Pattern
          if i[1][1][0] == 'Condition':
              p = i[1][1][1].copy()
          else:
              p = i[1][1].copy()
  
          optional = get_default_values(p, {})
          pattern = generate_sympy_from_parsed(p.copy(), replace_Int=True)
          pattern, free_symbols = add_wildcards(pattern, optional=optional)
          free_symbols = sorted(set(free_symbols)) #remove common symbols
          # Parse Transformed Expression and Constraints
          if i[2][0] == 'Condition':  # parse rules without constraints separately
              constriant, constraint_def, cons_index = divide_constraint(i[2][2], free_symbols, cons_index, cons_dict, cons_import) # separate And constraints into individual constraints
              FreeQ_vars, FreeQ_x = seperate_freeq(i[2][2].copy()) # separate FreeQ into individual constraints
              transformed = generate_sympy_from_parsed(i[2][1].copy(), symbols=free_symbols)
          else:
              constriant = ''
              constraint_def = ''
              FreeQ_vars, FreeQ_x = [], []
              transformed = generate_sympy_from_parsed(i[2].copy(), symbols=free_symbols)
          FreeQ_constraint, free_cons_def, cons_index = parse_freeq(FreeQ_vars, FreeQ_x, cons_index, cons_dict, cons_import, free_symbols)
          pattern = sympify(pattern, locals={"Or": Function("Or"), "And": Function("And"), "Not":Function("Not") })
          pattern = rubi_printer(pattern, sympy_integers=True)
  
          pattern = setWC(pattern)
          transformed = sympify(transformed, locals={"Or": Function("Or"), "And": Function("And"), "Not":Function("Not") })
          constraint_def = constraint_def + free_cons_def
          cons += constraint_def
          index += 1
  
          # below are certain if - else condition depending on various situation that may be encountered
          if type(transformed) == Function('With') or type(transformed) == Function('Module'): # define separate function when With appears
              transformed, With_constraints, return_type = replaceWith(transformed, free_symbols, index)
              if return_type is None:
                  repl_funcs += '{}'.format(transformed)
                  parsed += '\n    pattern' + str(index) + ' = Pattern(' + pattern + '' + FreeQ_constraint + '' + constriant + ')'
                  parsed += '\n    ' + 'rule' + str(index) + ' = ReplacementRule(' + 'pattern' + rubi_printer(index, sympy_integers=True) + ', With{}'.format(index) + ')\n'
              else:
                  repl_funcs += '{}'.format(transformed)
                  parsed += '\n    pattern' + str(index) + ' = Pattern(' + pattern + '' + FreeQ_constraint + '' + constriant + With_constraints + ')'
                  repl_funcs += '\n\n\ndef replacement{}({}):\n'.format(
                      index, ', '.join(free_symbols)
                  ) + return_type[0] + '\n    return '.format(index) + return_type[1]
                  parsed += '\n    ' + 'rule' + str(index) + ' = ReplacementRule(' + 'pattern' + rubi_printer(index, sympy_integers=True) + ', replacement{}'.format(index) + ')\n'
  
          else:
              transformed = rubi_printer(transformed, sympy_integers=True)
              parsed += '\n    pattern' + str(index) + ' = Pattern(' + pattern + '' + FreeQ_constraint + '' + constriant + ')'
              repl_funcs += '\n\n\ndef replacement{}({}):\n    return '.format(index, ', '.join(free_symbols), index) + transformed
              parsed += '\n    ' + 'rule' + str(index) + ' = ReplacementRule(' + 'pattern' + rubi_printer(index, sympy_integers=True) + ', replacement{}'.format(index) + ')\n'
          rules += 'rule{}, '.format(index)
      rules += ']'
      parsed += '    return ' + rules +'\n'
  
      header += '    from sympy.integrals.rubi.constraints import ' + ', '.join(word for word in cons_import)
      parsed = header + parsed + repl_funcs
      return parsed, cons_index, cons, index
  
  
  def rubi_rule_parser(fullform, header=None, module_name='rubi_object'):
      """
      Parses rules in MatchPy format.
  
      Parameters
      ==========
      fullform : FullForm of the rule as string.
      header : Header imports for the file. Uses default imports if None.
      module_name : name of RUBI module
  
      References
      ==========
      [1] http://reference.wolfram.com/language/ref/FullForm.html
      [2] http://reference.wolfram.com/language/ref/DownValues.html
      [3] https://gist.github.com/Upabjojr/bc07c49262944f9c1eb0
      """
  
      if header is None:  # use default header values
          path_header = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
          header = open(os.path.join(path_header, "header.py.txt")).read()
          header = header.format(module_name)
  
      cons_dict = {}  # dict keeps track of constraints that has been encountered, thus avoids repetition of constraints.
      cons_index = 0  # for index of a constraint
      index = 0  # indicates the number of a rule.
      cons = ''
  
      # Temporarily rename these variables because it
      # can raise errors while sympifying
      for i in temporary_variable_replacement:
          fullform = fullform.replace(i, temporary_variable_replacement[i])
      # Permanently rename these variables
      for i in permanent_variable_replacement:
          fullform = fullform.replace(i, permanent_variable_replacement[i])
  
      rules = []
      for i in parse_full_form(fullform): # separate all rules
          if i[0] == 'RuleDelayed':
              rules.append(i)
      parsed = downvalues_rules(rules, header, cons_dict, cons_index, index)
      result = parsed[0].strip() + '\n'
      cons += parsed[2]
      # Replace temporary variables by actual values
      for i in temporary_variable_replacement:
          cons = cons.replace(temporary_variable_replacement[i], i)
          result = result.replace(temporary_variable_replacement[i], i)
      cons = "\n".join(header.split("\n")[:-2]) + '\n' + cons
      return result, cons
  