r"""
Interface to FriCAS

.. TODO::

    - some conversions in ``sage.functions`` are still missing and
      all should be checked and tested

FriCAS is a free GPL-compatible (modified BSD license) general
purpose computer algebra system based on Axiom.  The FriCAS
website can be found at http://fricas.sourceforge.net/.

AUTHORS:

- Mike Hansen (2009-02): Split off the FriCAS interface from
  the Axiom interface.

- Martin Rubey, Bill Page (2016-08): Completely separate from Axiom,
  implement more complete translation from FriCAS to SageMath types.


EXAMPLES::

    sage: fricas('3 * 5')                                                       # optional - fricas
    15
    sage: a = fricas(3) * fricas(5); a                                          # optional - fricas
    15

The type of a is :class:`FriCASElement`, i.e., an element of the
FriCAS interpreter::

    sage: type(a)                                                               # optional - fricas
    <class 'sage.interfaces.fricas.FriCASElement'>
    sage: a.parent()                                                            # optional - fricas
    FriCAS

The underlying FriCAS type of a is also available, via the type
method::

    sage: a.typeOf()                                                            # optional - fricas
    PositiveInteger

FriCAS objects are normally displayed using "ASCII art"::

    sage: fricas(2/3)                                                           # optional - fricas
      2
      -
      3
    sage: fricas('x^2 + 3/7')                                                   # optional - fricas
       2   3
      x  + -
           7

Functions defined in FriCAS are available as methods of the :class:`fricas<FriCAS>` object::

    sage: F = fricas.factor('x^5 - y^5'); F                                     # optional - fricas
               4      3    2 2    3     4
    - (y - x)(y  + x y  + x y  + x y + x )
    sage: type(F)                                                               # optional - fricas
    <class 'sage.interfaces.fricas.FriCASElement'>
    sage: F.typeOf()                                                            # optional - fricas
    Factored(Polynomial(Integer))

We can also create a FriCAS polynomial and apply the function
``factor`` from FriCAS.  The notation ``f.factor()`` is consistent
with how the rest of SageMath works::

    sage: f = fricas('x^5 - y^5')                                               # optional - fricas
    sage: f^2                                                                   # optional - fricas
     10      5 5    10
    y   - 2 x y  + x
    sage: f.factor()                                                            # optional - fricas
               4      3    2 2    3     4
    - (y - x)(y  + x y  + x y  + x y + x )

For many FriCAS types, translation to an appropriate SageMath type is
available::

    sage: f.factor().sage()                                                     # optional - fricas
    (y - x) * (y^4 + y^3*x + y^2*x^2 + y*x^3 + x^4)

Control-C interruption works well with the FriCAS interface. For
example, try the following sum but with a much bigger range, and hit
control-C::

    sage:  f = fricas('(x^5 - y^5)^10000')                                      # not tested - fricas
    Interrupting FriCAS...
    ...
    KeyboardInterrupt: Ctrl-c pressed while running FriCAS

Let us demonstrate some features of FriCAS.  FriCAS can guess a
differential equation for the generating function for integer
partitions::

    sage: fricas("guessADE([partition n for n in 0..40], homogeneous==4)")      # optional - fricas
    [
      [
          n
        [x ]f(x):
               2    3 (iv)           2    2 ,              3  ,,,
              x f(x) f    (x) + (20 x f(x) f (x) + 5 x f(x) )f   (x)
    <BLANKLINE>
            +
                    2    2 ,,   2
              - 39 x f(x) f  (x)
    <BLANKLINE>
            +
                   2     ,   2            2 ,            3  ,,         2 ,   4
              (12 x f(x)f (x)  - 15 x f(x) f (x) + 4 f(x) )f  (x) + 6 x f (x)
    <BLANKLINE>
            +
                        ,   3          2 ,   2
              10 x f(x)f (x)  - 16 f(x) f (x)
    <BLANKLINE>
          =
            0
        ,
                         2      3      4
       f(x) = 1 + x + 2 x  + 3 x  + O(x )]
      ]

FriCAS can solve linear ordinary differential equations::

    sage: fricas.set("y", "operator y")                                         # optional - fricas
    sage: fricas.set("deq", "x^3*D(y x, x, 3) + x^2*D(y x, x, 2) - 2*x*D(y x, x) + 2*y x - 2*x^4")  # optional - fricas
    sage: fricas.set("sol", "solve(deq, y, x)"); fricas("sol")                  # optional - fricas
                   5       3       2
                  x  - 10 x  + 20 x  + 4
    [particular = ----------------------,
                           15 x
                 3      2       3       3      2
              2 x  - 3 x  + 1  x  - 1  x  - 3 x  - 1
     basis = [---------------, ------, -------------]]
                     x            x          x

    sage: fricas("sol.particular").sage()                                       # optional - fricas
    1/15*(x^5 - 10*x^3 + 20*x^2 + 4)/x
    sage: fricas("sol.basis").sage()                                            # optional - fricas
    [(2*x^3 - 3*x^2 + 1)/x, (x^3 - 1)/x, (x^3 - 3*x^2 - 1)/x]
    sage: fricas.eval(")clear values y deq sol")                                # optional - fricas
    ''

FriCAS can expand expressions into series::

    sage: x = var('x'); ex = sqrt(cos(x)); a = fricas(ex).series(x=0); a        # optional - fricas
        1  2    1  4    19   6     559   8     29161    10      11
    1 - - x  - -- x  - ---- x  - ------ x  - --------- x   + O(x  )
        4      96      5760      645120      116121600

    sage: a.coefficients()[38].sage()                                           # optional - fricas
    -29472026335337227150423659490832640468979/274214482066329363682430667508979749984665600000000

    sage: ex = sqrt(atan(x)); a = fricas(ex).series(x=0); a                     # optional - fricas
     1      5        9
     -      -        -
     2   1  2    31  2      6
    x  - - x  + --- x  + O(x )
         6      360

    sage: a.coefficient(9/2).sage()                                             # optional - fricas
    31/360

    sage: x = fricas("x::TaylorSeries Fraction Integer")                        # optional - fricas
    sage: y = fricas("y::TaylorSeries Fraction Integer")                        # optional - fricas
    sage: 2*(1+2*x+sqrt(1-4*x)-2*x*y).recip()                                   # optional - fricas
                  2       3     2 2      3       4        4        5
      1 + (x y + x ) + 2 x  + (x y  + 2 x y + 6 x ) + (4 x y + 18 x )
    +
        3 3      4 2       5        6        5 2       6         7
      (x y  + 3 x y  + 13 x y + 57 x ) + (6 x y  + 40 x y + 186 x )
    +
        4 4      5 3       6 2        7         8
      (x y  + 4 x y  + 21 x y  + 130 x y + 622 x )
    +
          6 3       7 2        8          9
      (8 x y  + 66 x y  + 432 x y + 2120 x )
    +
        5 5      6 4       7 3        8 2         9          10
      (x y  + 5 x y  + 30 x y  + 220 x y  + 1466 x y + 7338 x  ) + O(11)

FriCAS does some limits right::

    sage: x = var('x'); ex = x^2*exp(-x)*Ei(x) - x; fricas(ex).limit(x=oo)      # optional - fricas
    1

"""

###########################################################################
#       Copyright (C) 2008 Mike Hansen <mhansen@gmail.com>
#                     2007 Bill Page
#                     2006 William Stein <wstein@gmail.com>
#
#  Distributed under the terms of the GNU General Public License (GPL)
#  The full text of the GPL is available at:
#
#                  https://www.gnu.org/licenses/
###########################################################################
from __future__ import print_function

import re
import os
from sage.interfaces.tab_completion import ExtraTabCompletion
from sage.interfaces.expect import Expect, ExpectElement, FunctionElement, ExpectFunction
from sage.misc.misc import SAGE_TMP_INTERFACE
from sage.env import DOT_SAGE, LOCAL_IDENTIFIER
from sage.docs.instancedoc import instancedoc
from sage.rings.integer_ring import ZZ
from sage.rings.rational_field import QQ
from sage.misc.lazy_import import lazy_import
lazy_import('sage.libs.pynac.pynac', ['symbol_table'])
lazy_import('sage.calculus.var', ['var', 'function'])


FRICAS_SINGLE_LINE_START = 3  # where output starts when it fits next to the line number
FRICAS_MULTI_LINE_START = 2   # and when it doesn't
FRICAS_LINE_LENGTH = 80       # length of a line, should match the line length in sage
# the following messages have, unfortunately, no markup.
FRICAS_WHAT_OPERATIONS_STRING = r"Operations whose names satisfy the above pattern\(s\):"
FRICAS_ERROR_IN_LIBRARY_CODE = ">> Error detected within library code:"

# only the last command should be necessary to make the interface
# work, the other are optimizations.  Beware that lisp distinguishes
# between ' and ".
FRICAS_INIT_CODE = (
    ")set functions compile on",
    ")set message autoload off",
    ")set message type off",
    ")set output length " + str(FRICAS_LINE_LENGTH),
    ")lisp (setf |$ioHook|"
    "            (lambda (x &optional args)"
    "              (when (member x '(|startAlgebraOutput| |endOfAlgebraOutput|"
    "                                |startKeyedMsg|      |endOfKeyedMsg|))"
    "               (prin1 x)"
    "               (princ #\\Newline))))")
# code (one-liners!) executed after having set up the prompt
FRICAS_HELPER_CODE = (
    'sageprint(x:InputForm):String == ' +
    '(atom? x => (' +
    'float? x => return float(x)::String;' +
    'integer? x => return integer(x)::String;' +
    'string? x => return concat(["_"", string(x)::String, "_""])$String;' +
    'symbol? x => return string(symbol(x)));' +
    'S: List String := [sageprint y for y in destruct x];' +
    'R: String := new(1 + reduce(_+, [1 + #(s)$String for s in S], 0),' +
    'space()$Character);' +
    'copyInto!(R, "(", 1);' +
    'i := 2;' +
    'for s in S repeat'
    '(copyInto!(R, s, i); i := i + 1 + #(s)$String);' +
    'copyInto!(R, ")", i-1);' +
    'return R)',)

FRICAS_LINENUMBER_OFF_CODE = ")lisp (setf |$IOindex| NIL)"
FRICAS_FIRST_PROMPT = r"\(1\) -> "
FRICAS_LINENUMBER_OFF_PROMPT = r"\(NIL\) -> "


class FriCAS(ExtraTabCompletion, Expect):
    """
    Interface to a FriCAS interpreter.
    """
    def __init__(self, name='fricas', command='fricas -nosman',
                 script_subdirectory=None, logfile=None,
                 server=None, server_tmpdir=None):
        """
        Create an instance of the FriCAS interpreter.

        TESTS::

            sage: fricas == loads(dumps(fricas))                                # optional - fricas
            True

        Check that :trac:`25174` is fixed::

            sage: fricas(I)                                                     # optional - fricas
            %i

            sage: integrate(sin(x)*exp(I*x), x, -pi, 0, algorithm="fricas")     # optional - fricas
            1/2*I*pi

            sage: fricas(I*sin(x)).sage()                                       # optional - fricas
            I*sin(x)

            sage: fricas(I*x).sage()                                            # optional - fricas
            I*x
        """
        eval_using_file_cutoff = 4096-5  # magic number from Expect._eval_line (there might be a bug)
        assert max(len(c) for c in FRICAS_INIT_CODE) < eval_using_file_cutoff
        self.__eval_using_file_cutoff = eval_using_file_cutoff
        self._COMMANDS_CACHE = '%s/%s_commandlist_cache.sobj' % (DOT_SAGE, name)
        # we run the init code in _start to avoid spurious output
        Expect.__init__(self,
                        name=name,
                        prompt=FRICAS_FIRST_PROMPT,
                        command=command,
                        script_subdirectory=script_subdirectory,
                        server=server,
                        server_tmpdir=server_tmpdir,
                        restart_on_ctrlc=False,
                        verbose_start=False,
                        init_code=[],
                        logfile=logfile,
                        eval_using_file_cutoff=eval_using_file_cutoff)

    def _start(self):
        """
        Start the FriCAS interpreter and switch off the linenumbers.

        EXAMPLES::

            sage: a = FriCAS()                                                  # optional - fricas
            sage: a.is_running()                                                # optional - fricas
            False
            sage: a._start()                                                    # optional - fricas
            sage: a.is_running()                                                # optional - fricas
            True
            sage: a.quit()                                                      # optional - fricas
        """
        # setting the prompt properly is necessary for restarting FriCAS
        self._prompt = FRICAS_FIRST_PROMPT
        Expect._start(self)
        for line in FRICAS_INIT_CODE:
            self.eval(line, reformat=False)
        # switching off the line numbers also modified the prompt
        self._prompt = FRICAS_LINENUMBER_OFF_PROMPT
        self.eval(FRICAS_LINENUMBER_OFF_CODE, reformat=False)
        for line in FRICAS_HELPER_CODE:
            self.eval(line, reformat=False)

    def _install_hints(self):
        """
        Hints for installing FriCAS on your computer.

        EXAMPLES::

            sage: print(fricas._install_hints())
            In order...
        """
        return r"""
In order to use the FriCAS interface you need to have FriCAS installed.
You can either run 'sage -i fricas' to install FriCAS as an optional
package within SageMath, or install FriCAS separately, see
http://fricas.sourceforge.net.
"""

    def _quit_string(self):
        """
        Returns the string used to quit FriCAS.

        EXAMPLES::

            sage: fricas._quit_string()                                         # optional - fricas
            ')quit'
            sage: a = FriCAS()                                                  # optional - fricas
            sage: a.is_running()                                                # optional - fricas
            False
            sage: a._start()                                                    # optional - fricas
            sage: a.is_running()                                                # optional - fricas
            True
            sage: a.quit()                                                      # optional - fricas
            sage: a.is_running()                                                # optional - fricas
            False

        TESTS::

            sage: import psutil                                                 # optional - fricas
            sage: p = fricas.pid(); pr = psutil.Process(p); pr                  # optional - fricas
            <psutil.Process(pid=..., name='AXIOMsys') at ...>
            sage: pr.children()                                                 # optional - fricas
            []
        """
        return ')quit'

    def _commands(self):
        """
        Returns a list of commands available. This is done by parsing the
        result of the first section of the output of ')what things'.

        EXAMPLES::

            sage: cmds = fricas._commands()                                     # optional - fricas
            sage: len(cmds) > 100                                               # optional - fricas
            True
            sage: '<' in cmds                                                   # optional - fricas
            True
            sage: 'factor' in cmds                                              # optional - fricas
            True
        """
        output = self.eval(")what operations", reformat=False)
        m = re.search(FRICAS_WHAT_OPERATIONS_STRING + r"\n(.*)\n\|startKeyedMsg\|",
                      output, flags=re.DOTALL)
        l = m.groups()[0].split()
        return l

    def _tab_completion(self, verbose=True, use_disk_cache=True):
        """
        Returns a list of all the commands defined in Fricas and optionally
        (per default) store them to disk.

        EXAMPLES::

            sage: c = fricas._tab_completion(use_disk_cache=False, verbose=False)         # optional - fricas
            sage: len(c) > 100                                                  # optional - fricas
            True
            sage: 'factor' in c                                                 # optional - fricas
            True
            sage: '**' in c                                                     # optional - fricas
            False
            sage: 'upperCase?' in c                                             # optional - fricas
            False
            sage: 'upperCase_q' in c                                            # optional - fricas
            True
            sage: 'upperCase_e' in c                                            # optional - fricas
            True
        """
        try:
            return self.__tab_completion
        except AttributeError:
            import sage.misc.persist
            if use_disk_cache:
                try:
                    self.__tab_completion = sage.misc.persist.load(self._COMMANDS_CACHE)
                    return self.__tab_completion
                except IOError:
                    pass
            if verbose:
                print("\nBuilding %s command completion list (this takes" % self)
                print("a few seconds only the first time you do it).")
                print("To force rebuild later, delete %s." % self._COMMANDS_CACHE)
            v = self._commands()

            # process the commands to strip out things which are not
            # valid Python identifiers
            valid = re.compile('[^a-zA-Z0-9_]+')
            names = [x for x in v if valid.search(x) is None]

            # replace trailing ? with _q and trailing ! with _e
            names += [x[:-1]+"_q" for x in v if x.endswith("?")]
            names += [x[:-1]+"_e" for x in v if x.endswith("!")]

            self.__tab_completion = names
            if len(v) > 200:
                # Fricas is actually installed.
                sage.misc.persist.save(v, self._COMMANDS_CACHE)
            return names

    def _read_in_file_command(self, filename):
        """
        Return the FriCAS command to read the file ``filename``.

        INPUT:

        - ``filename``, a string ending in '.input'.

        OUTPUT:

        - a string with the command for reading filename without output.

        TESTS:

        Evaluate a rather long line::

            sage: len(fricas([i for i in range(600)]))                          # optional - fricas, indirect doctest
            600

        """
        if not filename.endswith('.input'):
            raise ValueError("the filename must end with .input")

        return ')read %s )quiet' % filename

    def _local_tmpfile(self):
        """
        Return a local tmpfile ending with ".input" used to buffer long
        command lines sent to FriCAS.

        """
        try:
            return self.__local_tmpfile
        except AttributeError:
            self.__local_tmpfile = os.path.join(SAGE_TMP_INTERFACE, 'tmp' + str(self.pid()) + '.input')
            return self.__local_tmpfile

    def _remote_tmpfile(self):
        """
        Return a remote tmpfile ending with ".input" used to buffer long
        command lines sent to FriCAS.

        """
        try:
            return self.__remote_tmpfile
        except AttributeError:
            self.__remote_tmpfile = self._remote_tmpdir() + "/interface_%s:%s.input" % (LOCAL_IDENTIFIER, self.pid())
            return self.__remote_tmpfile

# what I expect from FriCAS:

# 1.) in set(self, var, value)
#
# no markers:
# there could be some "debugging" output, as in fricas("guessADE([1,1,1,1], debug==true)")
#
# startKeyedMsg: an error happened
#
# 2.) in get(self, var)
#
# |startAlgebraOutput\|...|endOfAlgebraOutput\|
#
# 3.) I also need a routine to send a system command and get its output.

    def _check_errors(self, line, output):
        """
        Check whether output contains an error and, if so, raise it.

        INPUT:

        - ``line``, a string that was sent to FriCAS.

        - ``output``, a string returned by FriCAS

        OUTPUT:

        None

        TESTS::

            sage: fricas.set("x", "[i fo83r i in 0..17]")                       # optional - fricas, indirect doctest
            Traceback (most recent call last):
            ...
            RuntimeError: An error occurred when FriCAS evaluated '[i fo83r i in 0..17]':
              Line   1: x:=[i fo83r i in 0..17];
                       ...A..........B
              Error  A: Missing mate.
              Error  B: syntax error at top level
              Error  B: Possibly missing a ]
               3 error(s) parsing

            sage: fricas.set("x", "something stupid")                           # optional - fricas, indirect doctest
            Traceback (most recent call last):
            ...
            RuntimeError: An error occurred when FriCAS evaluated 'something stupid':
               There are no library operations named something
                  Use HyperDoc Browse or issue
                                           )what op something
                  to learn if there is any operation containing " something " in its
                  name.
            <BLANKLINE>
               Cannot find a definition or applicable library operation named
                  something with argument type(s)
                                            Variable(stupid)
            <BLANKLINE>
                  Perhaps you should use "@" to indicate the required return type, or
                  "$" to specify which version of the function you need.

        """
        # otherwise there might be a message
        m = re.search(r"\|startKeyedMsg\|\n(.*)\n\|endOfKeyedMsg\|",
                      output, flags=re.DOTALL)
        if m:
            replacements = [('|startKeyedMsg|\n', ''),
                            ('|endOfKeyedMsg|', '')]
            for old, new in replacements:
                output = output.replace(old, new)
            raise RuntimeError("An error occurred when FriCAS evaluated '%s':\n%s" % (line, output))

        # or even an error
        if FRICAS_ERROR_IN_LIBRARY_CODE in output:
            raise RuntimeError("An error occurred when FriCAS evaluated '%s':\n%s" % (line, output))

    def set(self, var, value):
        """
        Set a variable to a value in FriCAS.

        INPUT:

        - ``var``, ``value``: strings, the first representing a valid
          FriCAS variable identifier, the second a FriCAS expression.

        OUTPUT: None

        EXAMPLES::

            sage: fricas.set('xx', '2')                                         # optional - fricas
            sage: fricas.get('xx')                                              # optional - fricas
            '2'

        """
        cmd = '%s%s%s;' % (var, self._assign_symbol(), value)
        output = self.eval(cmd, reformat=False)
        self._check_errors(value, output)

    def get(self, var):
        r"""
        Get the string representation of the value (more precisely, the
        OutputForm) of a variable or expression in FriCAS.

        If FriCAS cannot evaluate `var` an error is raised.

        EXAMPLES::

            sage: fricas.set('xx', '2')                                         # optional - fricas
            sage: fricas.get('xx')                                              # optional - fricas
            '2'
            sage: a = fricas('(1 + sqrt(2))^5')                                 # optional - fricas
            sage: fricas.get(a.name())                                          # optional - fricas
            '    +-+\n29 \\|2  + 41'
            sage: fricas.get('(1 + sqrt(2))^5')                                 # optional - fricas
            '    +-+\n29 \\|2  + 41'
            sage: fricas.new('(1 + sqrt(2))^5')                                 # optional - fricas
                +-+
            29 \|2  + 41
        """
        output = self.eval(str(var), reformat=False)
        # if there is AlgebraOutput we ask no more
        m = re.search(r"\|startAlgebraOutput\|\n(.*)\n\|endOfAlgebraOutput\|",
                      output, flags=re.DOTALL)
        if m:
            lines = m.groups()[0].split("\n")
            if max(len(line) for line in lines) < FRICAS_LINE_LENGTH:
                return "\n".join(line[FRICAS_SINGLE_LINE_START:] for line in lines)
            else:
                return "\n".join(line[FRICAS_MULTI_LINE_START:] for line in lines)

        self._check_errors(var, output)

    def get_string(self, var):
        """
        Return the value of a FriCAS string as a string, without checking
        that it is a string.

        TESTS:

        We test that strings are returned properly::

            sage: r = fricas.get_string('concat([concat(string(i)," ") for i in 0..299])')   # optional - fricas
            sage: r == " ".join([str(i) for i in range(300)]) + ' '                          # optional - fricas
            True

            sage: fricas.get_string('concat([string(1) for i in 1..5])') == "1"*5            # optional - fricas
            True

            sage: fricas.get_string('concat([string(1) for i in 1..10000])') == "1"*10000    # optional - fricas
            True

        A problem with leading space::

            sage: s = "unparse((-1234567890123456789012345678901234567890123456789012345678901234567890*n::EXPR INT)::INFORM)"
            sage: fricas.get_string(s)                                                       # optional - fricas
            '(-1234567890123456789012345678901234567890123456789012345678901234567890)*n'

        Check that :trac:`25628` is fixed::

            sage: var("a b"); f = 1/(1+a*cos(x))                                # optional - fricas
            (a, b)
            sage: lF = integrate(f, x, algorithm="fricas")                      # optional - fricas
            sage: (diff(lF[0], x) - f).simplify_trig()                          # optional - fricas
            0
            sage: (diff(lF[1], x) - f).simplify_trig()                          # optional - fricas
            0
            sage: f = 1/(b*x^2+a); lF = integrate(f, x, algorithm="fricas"); lF # optional - fricas
            [1/2*log((2*a*b*x + (b*x^2 - a)*sqrt(-a*b))/(b*x^2 + a))/sqrt(-a*b),
             arctan(sqrt(a*b)*x/a)/sqrt(a*b)]
            sage: (diff(lF[0], x) - f).simplify_trig()                          # optional - fricas
            0
            sage: (diff(lF[1], x) - f).simplify_trig()                          # optional - fricas
            0

        """
        # strip removes leading and trailing whitespace, after that
        # we can assume that the first and the last character are
        # double quotes
        return self.get(str(var)).replace("\n", "").strip()[1:-1]

    def get_integer(self, var):
        """
        Return the value of a FriCAS integer as an integer, without
        checking that it is an integer.

        TESTS::

            sage: fricas.get_integer('factorial 1111') == factorial(1111)       # optional - fricas
            True

        """
        return int(self.get_unparsed_InputForm(str(var)))

    def get_boolean(self, var):
        """
        Return the value of a FriCAS boolean as a boolean, without checking
        that it is a boolean.

        TESTS::

            sage: fricas.get_boolean('(1=1)::Boolean') == True                  # optional - fricas
            True

            sage: fricas.get_boolean('(1=2)::Boolean') == False                 # optional - fricas
            True
        """
        return self.get(str(var)).replace("\n", "") == "true"

    def get_unparsed_InputForm(self, var):
        """
        Return the unparsed ``InputForm`` as a string.

        .. TODO::

            - catch errors, especially when InputForm is not available:

                - for example when integration returns ``"failed"``

                - ``UnivariatePolynomial``

            - should we provide workarounds, too?

        TESTS::

            sage: fricas.get_unparsed_InputForm('1..3')                         # optional - fricas
            '(1..3)$Segment(PositiveInteger())'

        """
        return self.get_string('unparse((%s)::InputForm)' % str(var))

    def get_InputForm(self, var):
        """
        Return the ``InputForm`` as a string.

        TESTS::

            sage: fricas.get_InputForm('1..3')                                  # optional - fricas
            '(($elt (Segment (PositiveInteger)) SEGMENT) 1 3)'

        """
        return self.get_string('sageprint((%s)::InputForm)' % str(var))

    def _assign_symbol(self):
        """
        Return the symbol used for setting a variable in FriCAS.

        EXAMPLES::

            sage: fricas.set("x", "1");                                         # optional - fricas, indirect doctest
            sage: fricas.get("x")                                               # optional - fricas
            '1'
            sage: fricas.eval(")cl val x")                                      # optional - fricas
            ''
        """
        return ":="

    def _equality_symbol(self):
        """
        Return the equality testing logical symbol in FriCAS.

        EXAMPLES::

            sage: a = fricas(x==6); a                                           # optional - fricas, indirect doctest
            x = 6

        A warning::

            sage: fricas.set("x", 2);                                           # optional - fricas
            sage: a = fricas(x==6); a                                           # optional - fricas
            2 = 6
            sage: fricas.eval(")cl val x")                                      # optional - fricas
            ''
        """
        return "="

    def _true_symbol(self):
        """
        Return the string used for ``True`` in FriCAS.

        EXAMPLES::

            sage: str(fricas("(1=1)@Boolean")) == fricas._true_symbol()         # optional - fricas
            True
        """
        return "true"

    def _false_symbol(self):
        """
        Return the string used for ``False`` in FriCAS.

        EXAMPLES::

            sage: str(fricas("(1~=1)@Boolean")) == fricas._false_symbol()       # optional - fricas
            True
        """
        return "false"

    def _inequality_symbol(self):
        """
        Return the string used for False in FriCAS.

        EXAMPLES::

            sage: fricas(x!=0)                                                  # optional - fricas, indirect doctest
            true
        """
        return '~='

    def _repr_(self):
        """
        EXAMPLES::

            sage: fricas                                                        # indirect doctest
            FriCAS
        """
        return "FriCAS"

    def __reduce__(self):
        """
        EXAMPLES::

            sage: fricas.__reduce__()                                           # optional - fricas
            (<function reduce_load_fricas at 0x...>, ())
            sage: f, args = _                                                   # optional - fricas
            sage: f(*args)                                                      # optional - fricas
            FriCAS
        """
        return reduce_load_fricas, tuple([])

    def eval(self, code, strip=True, synchronize=False, locals=None, allow_use_file=True,
             split_lines="nofile", reformat=True, **kwds):
        """
        Evaluate ``code`` using FriCAS.

        Except ``reformat``, all arguments are passed to
        :meth:`sage.interfaces.expect.Expect.eval`.

        INPUT:

        - ``reformat`` -- bool; remove the output markers when True.

        This can also be used to pass system commands to FriCAS.

        EXAMPLES::

            sage: fricas.set("x", "1783"); fricas("x")                          # optional - fricas
            1783
            sage: fricas.eval(")cl val x");                                     # optional - fricas
            ''
            sage: fricas("x")                                                   # optional - fricas
            x

        """
        output = Expect.eval(self, code, strip=strip,
                             synchronize=synchronize, locals=locals,
                             allow_use_file=allow_use_file, split_lines=split_lines,
                             **kwds)
        # we remove carriage returns (\r) to make parsing easier
        # they are sent depending on how fricas was invoked:
        # on linux, "fricas -nox -noclef" sends "\r\n" and "fricas -nosman" sends "\n"
        output = output.replace('\r', '')
        if reformat:
            replacements = [('|startAlgebraOutput|\n', ''),
                            ('|endOfAlgebraOutput|', ''),
                            ('|startKeyedMsg|\n', ''),
                            ('|endOfKeyedMsg|', '')]
            for old, new in replacements:
                output = output.replace(old, new)

        return output

    def _function_class(self):
        """
        Return the FriCASExpectFunction class.

        EXAMPLES::

            sage: fricas._function_class()                                      # optional - fricas
            <class 'sage.interfaces.fricas.FriCASExpectFunction'>
            sage: type(fricas.gcd)                                              # optional - fricas
            <class 'sage.interfaces.fricas.FriCASExpectFunction'>
        """
        return FriCASExpectFunction

    def _object_class(self):
        """
        EXAMPLES::

            sage: fricas._object_class()                                        # optional - fricas
            <class 'sage.interfaces.fricas.FriCASElement'>
            sage: type(fricas(2))                                               # optional - fricas
            <class 'sage.interfaces.fricas.FriCASElement'>
        """
        return FriCASElement

    def _function_element_class(self):
        """
        Returns the FriCAS function element class.

        EXAMPLES::

            sage: fricas._function_element_class()                              # optional - fricas
            <class 'sage.interfaces.fricas.FriCASFunctionElement'>
            sage: type(fricas(2).gcd)                                           # optional - fricas
            <class 'sage.interfaces.fricas.FriCASFunctionElement'>
        """
        return FriCASFunctionElement

    def console(self):
        """
        Spawn a new FriCAS command-line session.

        EXAMPLES::

            sage: fricas.console()                                              # not tested
                             FriCAS (AXIOM fork) Computer Algebra System
                                    Version: FriCAS 1.0.5
                     Timestamp: Thursday February 19, 2009 at 06:57:33
            -----------------------------------------------------------------------------
               Issue )copyright to view copyright notices.
               Issue )summary for a summary of useful system commands.
               Issue )quit to leave AXIOM and return to shell.
            -----------------------------------------------------------------------------
        """
        fricas_console()


@instancedoc
class FriCASElement(ExpectElement):
    """
    Instances of this class represent objects in FriCAS.

    Using the method :meth:`sage` we can translate some of them to
    SageMath objects:

    .. automethod:: _sage_
    """
    def __len__(self):
        """
        Return the length of a list.

        EXAMPLES::

            sage: v = fricas('[x^i for i in 0..5]')                             # optional - fricas
            sage: len(v)                                                        # optional - fricas
            6
        """
        P = self._check_valid()
        l = P('#(%s)' % self._name)
        return l.sage()

    def __getitem__(self, n):
        """
        We implement the sage conventions here, translating to 0-based iterables.

        We do not check validity, since many objects in FriCAS are
        iterable, in particular Streams

        .. TODO::

            - can we somehow implement negative arguments?

        TESTS::

            sage: fricas("[1,2,3]")[0]                                          # optional - fricas
            1

            sage: fricas("[1,2,3]")[3]                                          # optional - fricas
            Traceback (most recent call last):
            ...
            TypeError: An error occurred when FriCAS evaluated 'elt(...,...)':
            <BLANKLINE>
            >> Error detected within library code:
            index out of range
        """
        n = int(n)
        if n < 0:
            raise IndexError("index out of range")
        P = self._check_valid()
        # use "elt" instead of "." here because then the error
        # message is clearer
        return P.new("elt(%s,%s)" % (self._name, n+1))

    def __int__(self):
        """
        TESTS::

            sage: int(fricas(2))                                                # optional - fricas
            2
        """
        return int(self.sage())

    def bool(self):
        """
        Coerce the expression into a boolean.

        EXAMPLES::

            sage: fricas("1=1").bool()                                          # optional - fricas
            True
            sage: fricas("1~=1").bool()                                         # optional - fricas
            False
        """
        P = self._check_valid()
        return P.new(self._name + "::Boolean").sage()

    def __bool__(self):
        """
        Check whether the expression is different from zero.

        EXAMPLES::

            sage: fricas(0).is_zero()                                           # optional - fricas, indirect doctest
            True
        """
        P = self._check_valid()
        return not P.new("zero?(%s)" % self._name).sage()

    __nonzero__ = __bool__

    def __long__(self):
        """
        TESTS::

            sage: long(fricas('1'))                                             # optional - fricas
            1L
        """
        return long(self.sage())

    def __float__(self):
        """
        TESTS::

            sage: float(fricas(2))                                              # optional - fricas
            2.0
        """
        return float(self.sage())

    def _integer_(self, ZZ=None):
        """
        EXAMPLES::

            sage: ZZ(fricas('1'))                                               # optional - fricas
            1
        """
        return ZZ(self.sage())

    def _rational_(self):
        """
        EXAMPLES::

            sage: QQ(fricas('-1/2'))                                            # optional - fricas
            -1/2
        """
        return QQ(self.sage())

    def gen(self, n):
        """
        Return an error, since the n-th generator in FriCAS is not well defined.
        """
        raise NotImplementedError

    def _latex_(self):
        r"""
        EXAMPLES::

            sage: latex(fricas("sin(x+y)/exp(z)*log(1+%e)"))                    # optional - fricas
            {{\log \left( {{e+1}} \right)} \  {\sin \left( {{y+x}} \right)}} \over {{e} ^{z}}

            sage: latex(fricas("matrix([[1,2],[3,4]])"))                        # optional - fricas
            \left[ \begin{array}{cc} 1 & 2 \\ 3 & 4\end{array} \right]

            sage: latex(fricas("integrate(sin(x+1/x),x)"))                      # optional - fricas
            \int ^{\displaystyle x} {{\sin \left( {{{{{ \%O} ^{2}}+1} \over  \%O}} \right)} \  {d \%O}}
        """
        replacements = [(r'\sp ', '^'),
                        (r'\sp{', '^{'),
                        (r'\sb ', '_'),
                        (r'\sb{', '_{')]
        P = self._check_valid()
        s = P.get_string("latex(%s)" % self._name)
        for old, new in replacements:
            s = s.replace(old, new)
        return s

    def _get_sage_type(self, domain):
        """
        INPUT:

        - ``domain``, a FriCAS SExpression

        OUTPUT:

        - a corresponding Sage type

        EXAMPLES::

            sage: m = fricas("dom(1/2)::Any")                                   # optional - fricas
            sage: fricas(0)._get_sage_type(m)                                   # optional - fricas
            Rational Field

        TESTS::

            sage: m = fricas("UP(y, UP(x, AN))::INFORM")                        # optional - fricas
            sage: fricas(0)._get_sage_type(m)                                   # optional - fricas
            Univariate Polynomial Ring in y over Univariate Polynomial Ring in x over Algebraic Field
        """
        from sage.rings.all import QQbar, RDF, PolynomialRing
        from sage.rings.fraction_field import FractionField
        from sage.rings.finite_rings.integer_mod_ring import Integers
        from sage.rings.finite_rings.finite_field_constructor import FiniteField
        from sage.rings.real_mpfr import RealField
        from sage.symbolic.ring import SR

        # first implement domains without arguments
        head = str(domain.car())
        if head in ["Integer", "NonNegativeInteger", "PositiveInteger"]:
            return ZZ
        if head == "String":
            return str
        if head == "Float":
            P = self._check_valid()
            prec = max(P.new("length mantissa(%s)" % self._name).sage(), 53)
            return RealField(prec)
        if head == "DoubleFloat":
            return RDF
        if head == "AlgebraicNumber":
            return QQbar

        # now implement "functorial" types
        if head == "OrderedCompletion" or head == "Complex":
            # this is a workaround, I don't know how translate this
            return SR

        if head == "IntegerMod":
            return Integers(domain[1].integer().sage())

        if head == "PrimeField":
            return FiniteField(domain[1].integer().sage())

        if head == "Fraction":
            return FractionField(self._get_sage_type(domain[1]))

        if head == "Expression":
            return SR

        if head == "Polynomial":
            # this is a workaround, since in sage we always have to specify the variables
            return SR

        if head == "UnivariatePolynomial":
            var = str(domain[1])
            return PolynomialRing(self._get_sage_type(domain[2]), var)

        raise NotImplementedError("the translation of FriCAS type %s to sage is not yet implemented" % domain)

    _WHITESPACE = " "
    _LEFTBRACKET = "("
    _RIGHTBRACKET = ")"
    _STRINGMARKER = '"'
    _ESCAPEMARKER = '_'  # STRINGMARKER must be escaped in strings

    @staticmethod
    def _parse_and_eval(s, start=0):
        """
        Parse and evaluate the string.

        INPUT:

        - ``s`` -- string
        - ``start`` -- integer; specifies where to start parsing

        OUTPUT:

        - a pair ``(L, end)``, where ``L`` is the parsed list and
          ``end`` is the position of the last parsed letter.

        TESTS::

            sage: from sage.interfaces.fricas import FriCASElement
            sage: FriCASElement._parse_and_eval("abc")
            (abc, 2)

            sage: FriCASElement._parse_and_eval("(asin c)")
            (arcsin(c), 7)

            sage: N(FriCASElement._parse_and_eval("(pi)")[0])                   # optional - fricas
            3.14159265358979

            sage: FriCASElement._parse_and_eval('(a "(b c)")')
            Traceback (most recent call last):
            ...
            TypeError: cannot coerce arguments: no canonical coercion from <type 'str'> to Symbolic Ring

        """
        a = start
        while s[a] in FriCASElement._WHITESPACE:
            a += 1

        if s[a] == FriCASElement._LEFTBRACKET:
            return FriCASElement._parse_list(s, start=a)
        elif s[a] == FriCASElement._STRINGMARKER:
            return FriCASElement._parse_string(s, start=a)
        else:
            return FriCASElement._parse_other(s, start=a)

    @staticmethod
    def _parse_list(s, start=0):
        """
        Parse the initial part of a string, assuming that it is a
        whitespace separated list, treating its first element as
        function and the rest as arguments.

        INPUT:

        - ``s`` -- string
        - ``start`` -- integer; specifies the position of the left
          bracket

        TESTS::

            sage: from sage.interfaces.fricas import FriCASElement
            sage: FriCASElement._parse_list("()")
            Traceback (most recent call last):
            ...
            TypeError: 'tuple' object is not callable

            sage: FriCASElement._parse_list("(a b c)")
            (a(b, c), 6)

            sage: FriCASElement._parse_list('(bcd)')
            (bcd(), 4)

        """
        a = start
        assert s[a] == FriCASElement._LEFTBRACKET
        a += 1
        # the first element of a list must be a function call
        fun, a = FriCASElement._parse_other(s, start=a, make_fun=True)
        a += 1
        args = []
        while s[a] != FriCASElement._RIGHTBRACKET:
            e, a = FriCASElement._parse_and_eval(s, start=a)
            args.append(e)
            a += 1
        return fun(*args), a

    @staticmethod
    def _parse_other(s, start=0, make_fun=False):
        """
        Parse the initial part of a string, assuming that it is an
        atom, but not a string.

        Symbols and numbers must not contain ``FriCASElement._WHITESPACE`` and
        ``FriCASElement._RIGHTBRACKET``.

        INPUT:

        - ``s`` -- string
        - ``start`` -- integer; specifies where the symbol begins
        - ``make_fun`` -- (default: ``False``) a Boolean; specifying
          whether the atom should be interpreted as a function call

        TESTS::

            sage: from sage.interfaces.fricas import FriCASElement
            sage: FriCASElement._parse_other("abc")
            (abc, 2)
            sage: FriCASElement._parse_other("123 xyz")
            (123, 2)
            sage: FriCASElement._parse_other("abc -1.23", 4)
            (-1.23, 8)

        This function uses the symbol table to translate symbols
        which are not function calls.  At least ``%pi`` is an
        example showing that this may be necessary::

            sage: FriCASElement._parse_other("%pi")
            (pi, 2)

        """
        a = start
        b = len(s)
        while a < b and s[a] not in FriCASElement._WHITESPACE and s[a] != FriCASElement._RIGHTBRACKET:
            a += 1

        e = s[start:a]
        if make_fun:
            try:
                e = symbol_table["fricas"][e]
            except KeyError:
                e = function(e)
        else:
            try:
                e = ZZ(e)
            except TypeError:
                try:
                    e = float(e)
                except ValueError:
                    try:
                        e = symbol_table["fricas"][e]
                    except KeyError:
                        e = var(e.replace("%", "_"))
        return e, a-1

    @staticmethod
    def _parse_string(s, start=0):
        r"""
        Parse the initial part of a string, assuming that it represents a
        string.

        INPUT:

        - ``s`` -- string
        - ``start`` -- integer; specifies the position of the left
          quote

        TESTS::

            sage: from sage.interfaces.fricas import FriCASElement
            sage: FriCASElement._parse_string('"abc" 123')
            ('abc', 4)

            sage: FriCASElement._parse_string('"" 123')
            ('', 1)

            sage: FriCASElement._parse_string('"____" 123')
            ('__', 5)

            sage: FriCASElement._parse_string('"_a" 123')
            ('a', 3)

            sage: FriCASElement._parse_string('"_" _"" 123')
            ('" "', 6)

            sage: FriCASElement._parse_string('"(b c)"')
            ('(b c)', 6)

        """
        a = start
        assert s[a] == FriCASElement._STRINGMARKER
        b = a + 1
        a = b
        S = []
        while s[b] != FriCASElement._STRINGMARKER:
            if s[b] == FriCASElement._ESCAPEMARKER:
                S.append(s[a:b])
                b += 1
                a = b
            b += 1
        S.append(s[a:b])
        return "".join(S), b

    @staticmethod
    def _sage_expression(fricas_InputForm):
        r"""
        Convert an expression to an element of the Symbolic Ring.

        INPUT:

        - fricas_InputForm, a string, the InputForm of a FriCAS expression.

        TESTS::

            sage: f = fricas('integrate(sin(x^2), x)'); f                       # optional - fricas
                       +---+
                       | 2
            fresnelS(x |--- )
                      \|%pi
            -----------------
                   +---+
                   | 2
                   |---
                  \|%pi
            sage: s = fricas.get_InputForm(f._name); s                          # optional - fricas
            '(/ (fresnelS (* x (^ (/ 2 (pi)) (/ 1 2)))) (^ (/ 2 (pi)) (/ 1 2)))'
            sage: from sage.interfaces.fricas import FriCASElement
            sage: FriCASElement._sage_expression(s)                             # optional - fricas
            1/2*sqrt(2)*sqrt(pi)*fresnel_sin(sqrt(2)*x/sqrt(pi))

        Check that :trac:`22525` is fixed::

            sage: l = [sin, cos, sec, csc, cot, tan, asin, acos, atan, acot, acsc, asec, arcsin, arccos, arctan, arccot, arccsc, arcsec]
            sage: [f(x)._fricas_().sage().subs(x=0.9) for f in l]               # optional - fricas
            [0.783326909627483,
             0.621609968270664,
             1.60872581046605,
             1.27660621345890,
             0.793551147842317,
             1.26015821755034,
             1.11976951499863,
             0.451026811796262,
             0.732815101786507,
             0.837981225008390,
             1.57079632679490 - 0.467145308103262*I,
             0.467145308103262*I,
             1.11976951499863,
             0.451026811796262,
             0.732815101786507,
             0.837981225008390,
             1.57079632679490 - 0.467145308103262*I,
             0.467145308103262*I]
            sage: l = [tanh, sinh, cosh, coth, sech, csch, asinh, acosh, atanh, acoth, asech, acsch, arcsinh, arccosh, arctanh, arccoth, arcsech, arccsch]
            sage: [f(x)._fricas_().sage().subs(x=0.9) for f in l]               # optional - fricas
            [0.716297870199024,
             1.02651672570818,
             1.43308638544877,
             1.39606725303001,
             0.697794641100332,
             0.974168247780004,
             0.808866935652782,
             0.451026811796262*I,
             1.47221948958322,
             1.47221948958322 - 1.57079632679490*I,
             0.467145308103262,
             0.957800449200672,
             0.808866935652782,
             0.451026811796262*I,
             1.47221948958322,
             1.47221948958322 - 1.57079632679490*I,
             0.467145308103262,
             0.957800449200672]

        Check that :trac:`23782` is fixed::

            sage: s = '((3*n^10-25*n^9+50*n^8+62*n^7-229*n^6-25*n^5+320*n^4-12*n^3-144*n^2)/11520)::EXPR INT'
            sage: fricas(s).sage()                                              # optional - fricas
            1/3840*n^10 - 5/2304*n^9 + 5/1152*n^8 + 31/5760*n^7 - 229/11520*n^6 - 5/2304*n^5 + 1/36*n^4 - 1/960*n^3 - 1/80*n^2


        Check that :trac:`25224` is fixed::

            sage: integrate(log(x)/(1-x),x,algorithm='fricas')                  # optional - fricas
            dilog(-x + 1)
            sage: fricas(dilog(-x + 1))                                         # optional - fricas
            dilog(x)
            sage: dilog._fricas_()(1.0)                                         # optional - fricas
            1.6449340668_4822643647_24152
            sage: dilog(1.0)
            1.64493406684823

        Check that :trac:`25987` is fixed::

            sage: integrate(lambert_w(x), x, algorithm="fricas")                # optional - fricas
            (x*lambert_w(x)^2 - x*lambert_w(x) + x)/lambert_w(x)

        Check that :trac:`25838` is fixed::

            sage: F = function('f'); f = SR.var('f')
            sage: FF = fricas(F(f)); FF                                         # optional - fricas
            f(f)
            sage: FF.D(f).sage()                                                # optional - fricas
            diff(f(f), f)
            sage: bool(FF.D(f).integrate(f).sage() == F(f))                     # optional - fricas
            True

        Check that :trac:`25602` is fixed::

            sage: r = fricas.integrate(72000/(1+x^5),x).sage()                  # optional - fricas
            sage: n(r.subs(x=5)-r.subs(x=3))                                    # optional - fricas tol 0.1
            193.020947266210

            sage: var("a"); r = fricas.integrate(72000*a^8/(a^5+x^5),x).sage()  # optional - fricas
            a
            sage: n(r.subs(a=1, x=5)-r.subs(a=1, x=3))                          # optional - fricas tol 0.1
            193.020947266268 - 8.73114913702011e-11*I

        Check conversions of sums and products::

            sage: var("k, m, n")                                                # optional - fricas
            (k, m, n)
            sage: fricas("sum(1/factorial(k), k=1..n)").sage()                  # optional - fricas
            sum(1/factorial(_...), _..., 1, n)
            sage: fricas("eval(sum(x/k, k=1..n), x=k)").sage()                  # optional - fricas
            k*harmonic_number(n)
            sage: fricas("product(1/factorial(k), k=1..n)").sage()              # optional - fricas
            1/product(factorial(_...), _..., 1, n)

            sage: f = fricas.guess([sum(1/k, k,1,n) for n in range(10)])[0]; f  # optional - fricas
             n - 1
              --+       1
              >      -------
              --+    s   + 1
            s   = 0   10
             10

            sage: f.sage()                                                      # optional - fricas
            harmonic_number(n)

            sage: f = fricas.guess([0, 1, 3, 9, 33])[0]; f                      # optional - fricas
                    s  - 1
            n - 1    5
             --+    ++-++
             >       | |    p  + 2
             --+     | |     4
            s  = 0  p  = 0
             5       4

            sage: f.sage()                                                      # optional - fricas
            sum(factorial(_... + 1), _..., 0, n - 1)

        Check that :trac:`26746` is fixed::

            sage: _ = var('x, y, z')
            sage: f = sin(x^2) + y^z
            sage: f.integrate(x, algorithm='fricas')                            # optional - fricas
            1/2*sqrt(2)*sqrt(pi)*(sqrt(2)*x*y^z/sqrt(pi) + fresnel_sin(sqrt(2)*x/sqrt(pi)))

            sage: fricas(fresnel_sin(1))                                        # optional - fricas
            fresnelS(1)
            sage: fricas("fresnelS(1.0)")                                       # optional - fricas
            0.4382591473_9035476607_676

            sage: fricas(fresnel_cos(1))                                        # optional - fricas
            fresnelC(1)
            sage: fricas("fresnelC(1.0)")                                       # optional - fricas
            0.7798934003_7682282947_42

        Check that :trac:`17908` is fixed::

            sage: fricas(abs(x)).sage().subs(x=-1783)                           # optional - fricas
            1783

        """
        from sage.libs.pynac.pynac import register_symbol
        from sage.symbolic.all import I
        from sage.symbolic.constants import e, pi
        from sage.calculus.functional import diff
        from sage.functions.log import dilog, lambert_w
        from sage.functions.trig import sin, cos, tan, cot, sec, csc
        from sage.functions.hyperbolic import tanh, sinh, cosh, coth, sech, csch
        from sage.misc.functional import symbolic_sum, symbolic_prod
        from sage.rings.infinity import infinity
        register_symbol(I, {'fricas': '%i'})
        register_symbol(e, {'fricas': '%e'})
        register_symbol(pi, {'fricas': 'pi'})  # fricas uses both pi and %pi
        register_symbol(lambda: infinity, {'fricas': 'infinity'})
        register_symbol(lambda: infinity, {'fricas': 'plusInfinity'})
        register_symbol(lambda: -infinity, {'fricas': 'minusInfinity'})
        register_symbol(cos, {'fricas': 'cos'})
        register_symbol(sin, {'fricas': 'sin'})
        register_symbol(tan, {'fricas': 'tan'})
        register_symbol(cot, {'fricas': 'cot'})
        register_symbol(sec, {'fricas': 'sec'})
        register_symbol(csc, {'fricas': 'csc'})
        register_symbol(tanh, {'fricas': 'tanh'})
        register_symbol(sinh, {'fricas': 'sinh'})
        register_symbol(cosh, {'fricas': 'cosh'})
        register_symbol(coth, {'fricas': 'coth'})
        register_symbol(sech, {'fricas': 'sech'})
        register_symbol(csch, {'fricas': 'csch'})
        register_symbol(lambda x, y: x + y, {'fricas': '+'})
        register_symbol(lambda x, y: x - y, {'fricas': '-'})
        register_symbol(lambda x, y: x * y, {'fricas': '*'})
        register_symbol(lambda x, y: x / y, {'fricas': '/'})
        register_symbol(lambda x, y: x ** y, {'fricas': '^'})
        register_symbol(lambda f, x: diff(f, x), {'fricas': 'D'})
        register_symbol(lambda x, y: x + y*I, {'fricas': 'complex'})
        register_symbol(lambda x: dilog(1-x), {'fricas': 'dilog'})
        register_symbol(lambda z: lambert_w(z), {'fricas': 'lambertW'})
        # the following is a hack to deal with
        # integrate(sin((x^2+1)/x),x)::INFORM giving
        # (integral (sin (/ (+ (^ x 2) 1) x)) (:: x Symbol))
        register_symbol(lambda x, y: x, {'fricas': '::'})

        def _convert_sum(x, y):
            v, seg = y.operands()
            a, b = seg.operands()
            return symbolic_sum(x, v, a, b)

        def _convert_prod(x, y):
            v, seg = y.operands()
            a, b = seg.operands()
            return symbolic_prod(x, v, a, b)

        register_symbol(_convert_sum, {'fricas': 'sum'})
        register_symbol(_convert_prod, {'fricas': 'product'})

        def explicitly_not_implemented(*args):
            raise NotImplementedError("the translation of the FriCAS Expression '%s' to sage is not yet implemented" % args)

        register_symbol(lambda *args: explicitly_not_implemented("rootOfADE"), {'fricas': 'rootOfADE'})
        register_symbol(lambda *args: explicitly_not_implemented("rootOfRec"), {'fricas': 'rootOfRec'})

        rootOf = dict()  # (variable, polynomial)
        rootOf_ev = dict()  # variable -> (complex) algebraic number

        def convert_rootOf(x, y):
            if y in rootOf:
                assert rootOf[y] == x
            else:
                rootOf[y] = x
            return y

        register_symbol(convert_rootOf, {'fricas': 'rootOf'})

        ex, _ = FriCASElement._parse_and_eval(fricas_InputForm)
        # postprocessing of rootOf
        from sage.rings.all import QQbar, PolynomialRing
        while rootOf:
            for var, poly in rootOf.items():
                pvars = poly.variables()
                rvars = [v for v in pvars if v not in rootOf_ev]  # remaining variables
                uvars = [v for v in rvars if v in rootOf]  # variables to evaluate
                if len(uvars) == 1:
                    assert uvars[0] == var, "the only variable in uvars should be %s but is %s" % (var, uvars[0])
                    break
            else:
                assert False, "circular dependency in rootOf expression"
            # substitute known roots
            poly = poly.subs(rootOf_ev)
            evars = [v for v in rvars if v not in rootOf]  # extraneous variables
            assert set(evars) == set(poly.variables()).difference([var])
            del rootOf[var]
            if evars:
                # we just need any root per FriCAS specification
                rootOf_ev[var] = poly.roots(var, multiplicities=False)[0]
            else:
                R = PolynomialRing(QQbar, "x")
                # PolynomialRing does not accept variable names with leading underscores
                poly = R(poly.subs({var: R.gen()}))
                # we just need any root per FriCAS specification
                rootOf_ev[var] = poly.roots(multiplicities=False)[0].radical_expression()

        return ex.subs(rootOf_ev)

    def _sage_(self):
        r"""
        Convert ``self`` to a Sage object.

        EXAMPLES:

        Floats::

            sage: fricas(2.1234).sage()                                         # optional - fricas
            2.12340000000000
            sage: _.parent()                                                    # optional - fricas
            Real Field with 53 bits of precision
            sage: a = RealField(100)(pi)                                        # optional - fricas
            sage: fricas(a).sage()                                              # optional - fricas
            3.1415926535897932384626433833
            sage: _.parent()                                                    # optional - fricas
            Real Field with 100 bits of precision
            sage: fricas(a).sage() == a                                         # optional - fricas
            True
            sage: fricas(2.0).sage()                                            # optional - fricas
            2.00000000000000
            sage: _.parent()                                                    # optional - fricas
            Real Field with 53 bits of precision

        Algebraic numbers::

            sage: a = fricas('(1 + sqrt(2))^5'); a                              # optional - fricas
                +-+
            29 \|2  + 41
            sage: b = a.sage(); b                                               # optional - fricas
            82.0121933088198?
            sage: b.radical_expression()                                        # optional - fricas
            29*sqrt(2) + 41

        Integers modulo n::

            sage: fricas("((42^17)^1783)::IntegerMod(5^(5^5))").sage() == Integers(5^(5^5))((42^17)^1783) # optional - fricas
            True

        Matrices over a prime field::

            sage: fricas("matrix [[1::PF 3, 2],[2, 0]]").sage().parent()        # optional - fricas
            Full MatrixSpace of 2 by 2 dense matrices over Finite Field of size 3

        We can also convert FriCAS's polynomials to Sage polynomials::

            sage: a = fricas(x^2 + 1); a.typeOf()                               # optional - fricas
            Polynomial(Integer)
            sage: a.sage()                                                      # optional - fricas
            x^2 + 1
            sage: _.parent()                                                    # optional - fricas
            Univariate Polynomial Ring in x over Integer Ring
            sage: fricas('x^2 + y^2 + 1/2').sage()                              # optional - fricas
            y^2 + x^2 + 1/2
            sage: _.parent()                                                    # optional - fricas
            Multivariate Polynomial Ring in y, x over Rational Field

            sage: fricas("1$Polynomial Integer").sage()                         # optional - fricas
            1

            sage: fricas("x^2/2").sage()                                        # optional - fricas
            1/2*x^2

            sage: x = polygen(QQ, 'x')
            sage: fricas(x+3).sage()                                            # optional - fricas
            x + 3
            sage: fricas(x+3).domainOf()                                        # optional - fricas
            Polynomial(Integer())

            sage: fricas(matrix([[2,3],[4,x+5]])).diagonal().sage()             # optional - fricas
            (2, x + 5)

            sage: f = fricas("(y^2+3)::UP(y, INT)").sage(); f                   # optional - fricas
            y^2 + 3
            sage: f.parent()                                                    # optional - fricas
            Univariate Polynomial Ring in y over Integer Ring

            sage: fricas("(y^2+sqrt 3)::UP(y, AN)").sage()                      # optional - fricas
            y^2 + 1.732050807568878?

        Rational functions::

            sage: fricas("x^2 + 1/z").sage()                                    # optional - fricas
            x^2 + 1/z

        Expressions::

            sage: fricas(pi).sage()                                             # optional - fricas
            pi

            sage: fricas("sin(x+y)/exp(z)*log(1+%e)").sage()                    # optional - fricas
            e^(-z)*log(e + 1)*sin(x + y)

            sage: fricas("factorial(n)").sage()                                 # optional - fricas
            factorial(n)

            sage: fricas("integrate(sin(x+y), x=0..1)").sage()                  # optional - fricas
            -cos(y + 1) + cos(y)

            sage: fricas("integrate(x*sin(1/x), x=0..1)").sage()                # optional - fricas
            'failed'

            sage: fricas("integrate(sin((x^2+1)/x),x)").sage()                  # optional - fricas
            integral(sin((x^2 + 1)/x), x)

        .. TODO::

            - Converting matrices and lists takes much too long.

        Matrices::

            sage: fricas("matrix [[x^n/2^m for n in 0..5] for m in 0..3]").sage()         # optional - fricas, long time
            [      1       x     x^2     x^3     x^4     x^5]
            [    1/2   1/2*x 1/2*x^2 1/2*x^3 1/2*x^4 1/2*x^5]
            [    1/4   1/4*x 1/4*x^2 1/4*x^3 1/4*x^4 1/4*x^5]
            [    1/8   1/8*x 1/8*x^2 1/8*x^3 1/8*x^4 1/8*x^5]

        Lists::

            sage: fricas("[2^n/x^n for n in 0..5]").sage()                      # optional - fricas, long time
            [1, 2/x, 4/x^2, 8/x^3, 16/x^4, 32/x^5]

            sage: fricas("[matrix [[i for i in 1..n]] for n in 0..5]").sage()   # optional - fricas, long time
            [[], [1], [1 2], [1 2 3], [1 2 3 4], [1 2 3 4 5]]

        Error handling::

            sage: s = fricas.guessPade("[fibonacci i for i in 0..10]"); s       # optional - fricas
                n        x
            [[[x ]- ----------]]
                     2
                    x  + x - 1
            sage: s.sage()                                                      # optional - fricas
            Traceback (most recent call last):
            ...
            NotImplementedError: the translation of the FriCAS Expression 'rootOfADE' to sage is not yet implemented

            sage: s = fricas("series(sqrt(1+x), x=0)"); s                       # optional - fricas
                  1     1  2    1  3    5   4    7   5    21   6    33   7    429   8
              1 + - x - - x  + -- x  - --- x  + --- x  - ---- x  + ---- x  - ----- x
                  2     8      16      128      256      1024      2048      32768
            +
               715   9    2431   10      11
              ----- x  - ------ x   + O(x  )
              65536      262144

            sage: s.sage()                                                      # optional - fricas
            Traceback (most recent call last):
            ...
            NotImplementedError: the translation of the FriCAS object
            <BLANKLINE>
                  1     1  2    1  3    5   4    7   5    21   6    33   7    429   8
              1 + - x - - x  + -- x  - --- x  + --- x  - ---- x  + ---- x  - ----- x
                  2     8      16      128      256      1024      2048      32768
            +
               715   9    2431   10      11
              ----- x  - ------ x   + O(x  )
              65536      262144
            <BLANKLINE>
            to sage is not yet implemented:
            An error occurred when FriCAS evaluated 'unparse(...::InputForm)':
            <BLANKLINE>
               Cannot convert the value from type Any to InputForm .
        """
        from sage.rings.all import PolynomialRing, RDF
        from sage.rings.real_mpfr import RealField
        from sage.symbolic.ring import SR
        from sage.symbolic.all import I
        from sage.matrix.constructor import matrix
        from sage.modules.free_module_element import vector
        from sage.structure.factorization import Factorization
        from sage.misc.sage_eval import sage_eval

        # TODO: perhaps we should translate the type first?
        # TODO: perhaps we should get the InputForm as SExpression?

        # remember: fricas.new gives a FriCASElement

        # the coercion to Any gets rid of the Union domain
        P = self._check_valid()
        domain = P.new("dom((%s)::Any)" % self._name)  # domain is now a fricas SExpression

        # first translate dummy domains such as "failed".  We must
        # not recurse here!
        if P.get_boolean("string?(%s)" % domain._name):
            return P.get_string("string(%s)" % domain._name)

        # now translate domains which cannot be coerced to InputForm,
        # or where we do not need it.
        head = str(domain.car())
        if head == "Record":
            fields = fricas("[string symbol(e.2) for e in rest destruct %s]" % domain._name).sage()
            return {field: self.elt(field).sage() for field in fields}

        if head == "List":
            n = P.get_integer('#(%s)' % self._name)
            return [self.elt(k).sage() for k in range(1, n + 1)]

        if head == "Vector" or head == "DirectProduct":
            n = P.get_integer('#(%s)' % self._name)
            return vector([self.elt(k).sage() for k in range(1, n + 1)])

        if head == "Matrix":
            base_ring = self._get_sage_type(domain[1])
            rows = self.listOfLists().sage()
            return matrix(base_ring, rows)

        if head == "Fraction":
            return self.numer().sage() / self.denom().sage()

        if head == "Complex":
            return self.real().sage() + self.imag().sage()*I

        if head == "Factored":
            l = P.new('[[f.factor, f.exponent] for f in factors(%s)]' % self._name).sage()
            return Factorization([(p, e) for p, e in l])

        if head == "UnivariatePolynomial":
            base_ring = self._get_sage_type(domain[2])
            vars = str(domain[1])
            R = PolynomialRing(base_ring, vars)
            return R([self.coefficient(i).sage()
                      for i in range(ZZ(self.degree()) + 1)])

        # finally translate domains with InputForm
        try:
            unparsed_InputForm = P.get_unparsed_InputForm(self._name)
        except RuntimeError as error:
            raise NotImplementedError("the translation of the FriCAS object\n\n%s\n\nto sage is not yet implemented:\n%s" % (self, error))
        if head == "Boolean":
            return unparsed_InputForm == "true"

        if head in ["Integer", "NonNegativeInteger", "PositiveInteger"]:
            return ZZ(unparsed_InputForm)

        if head == "String":
            return unparsed_InputForm

        if head == "Float":
            # Warning: precision$Float gives the current precision,
            # whereas length(mantissa(self)) gives the precision of
            # self.
            prec = max(P.new("length mantissa(%s)" % self._name).sage(), 53)
            R = RealField(prec)
            x, e, b = unparsed_InputForm.lstrip('float(').rstrip(')').split(',')
            return R(ZZ(x)*ZZ(b)**ZZ(e))

        if head == "DoubleFloat":
            return RDF(unparsed_InputForm)

        if head == "AlgebraicNumber":
            s = unparsed_InputForm[:-len("::AlgebraicNumber()")]
            return sage_eval("QQbar(" + s + ")")

        if head == "IntegerMod" or head == "PrimeField":
            # one might be tempted not to go via InputForm here, but
            # it turns out to be safer to do it.
            n = unparsed_InputForm[len("index("):]
            n = n[:n.find(")")]
            return self._get_sage_type(domain)(n)

        if head == "Polynomial":
            base_ring = self._get_sage_type(domain[1])
            # Polynomial Complex is translated into SR
            if base_ring is SR:
                return FriCASElement._sage_expression(P.get_InputForm(self._name))

            # the following is a bad hack, we should be getting a list here
            vars = P.get_unparsed_InputForm("variables(%s)" % self._name)[1:-1]
            if vars == "":
                return base_ring(unparsed_InputForm)
            else:
                R = PolynomialRing(base_ring, vars)
                return R(unparsed_InputForm)

        if head in ["OrderedCompletion", "OnePointCompletion"]:
            # it would be more correct to get the type parameter
            # (which might not be Expression Integer) and recurse
            return FriCASElement._sage_expression(P.get_InputForm(self._name))

        if head == "Expression" or head == "Pi":
            # we treat Expression Integer and Expression Complex
            # Integer just the same
            return FriCASElement._sage_expression(P.get_InputForm(self._name))

        if head == 'DistributedMultivariatePolynomial':
            base_ring = self._get_sage_type(domain[2])
            vars = domain[1].car()
            R = PolynomialRing(base_ring, vars)
            return R(unparsed_InputForm)

        raise NotImplementedError("the translation of the FriCAS object %s to sage is not yet implemented" % (unparsed_InputForm))


@instancedoc
class FriCASFunctionElement(FunctionElement):
    def __init__(self, object, name):
        """
        Make FriCAS operation names valid python function identifiers.

        TESTS::

            sage: a = fricas('"Hello"')                                         # optional - fricas
            sage: a.upperCase_q                                                 # optional - fricas
            upperCase?
            sage: a.upperCase_e                                                 # optional - fricas
            upperCase!
            sage: a.upperCase_e()                                               # optional - fricas
            "HELLO"

        """
        if name.endswith("_q"):
            name = name[:-2] + "?"
        elif name.endswith("_e"):
            name = name[:-2] + "!"
        FunctionElement.__init__(self, object, name)


@instancedoc
class FriCASExpectFunction(ExpectFunction):
    def __init__(self, parent, name):
        """
        Translate the pythonized function identifier back to a FriCAS
        operation name.

        TESTS::

            sage: fricas.upperCase_q                                            # optional - fricas
            upperCase?
            sage: fricas.upperCase_e                                            # optional - fricas
            upperCase!

        """
        if name.endswith("_q"):
            name = name[:-2] + "?"
        elif name.endswith("_e"):
            name = name[:-2] + "!"
        ExpectFunction.__init__(self, parent, name)


def is_FriCASElement(x):
    """
    Return ``True`` if ``x`` is of type :class:`FriCASElement`.

    EXAMPLES::

        sage: from sage.interfaces.fricas import is_FriCASElement               # optional - fricas
        sage: is_FriCASElement(fricas(2))                                       # optional - fricas
        True
        sage: is_FriCASElement(2)                                               # optional - fricas
        False
    """
    return isinstance(x, FriCASElement)


fricas = FriCAS()


def reduce_load_fricas():
    """
    Returns the FriCAS interface object defined in
    :sage.interfaces.fricas.

    EXAMPLES::

        sage: from sage.interfaces.fricas import reduce_load_fricas             # optional - fricas
        sage: reduce_load_fricas()                                              # optional - fricas
        FriCAS
    """
    return fricas


def fricas_console():
    """
    Spawn a new FriCAS command-line session.

    EXAMPLES::

        sage: fricas_console()                                                  # not tested
                         FriCAS (AXIOM fork) Computer Algebra System
                                    Version: FriCAS 1.0.5
                     Timestamp: Thursday February 19, 2009 at 06:57:33
        -----------------------------------------------------------------------------
           Issue )copyright to view copyright notices.
           Issue )summary for a summary of useful system commands.
           Issue )quit to leave AXIOM and return to shell.
        -----------------------------------------------------------------------------
    """
    from sage.repl.rich_output.display_manager import get_display_manager
    if not get_display_manager().is_in_terminal():
        raise RuntimeError('Can use the console only in the terminal. Try %%fricas magics instead.')
    os.system('fricas -nox')


def __doctest_cleanup():
    """
    EXAMPLES::

        sage: from sage.interfaces.fricas import __doctest_cleanup              # optional - fricas
        sage: a = FriCAS()                                                      # optional - fricas
        sage: two = a(2)                                                        # optional - fricas
        sage: a.is_running()                                                    # optional - fricas
        True
        sage: __doctest_cleanup()                                               # optional - fricas
        sage: a.is_running()                                                    # optional - fricas
        False
    """
    import sage.interfaces.quit
    sage.interfaces.quit.expect_quitall()
