r"""
Interface to GAP3

This module implements an interface to GAP3.

AUTHORS:

-  Franco Saliola (February 2010)
-  Christian Stump (March 2016)

.. WARNING::

    The experimental package for GAP3 is Jean Michel's pre-packaged GAP3,
    which is a minimal GAP3 distribution containing packages that have
    no equivalent in GAP4, see :trac:`20107` and also 

        https://webusers.imj-prg.fr/~jean.michel/gap3/

Obtaining GAP3
--------------

Instead of installing the experimental GAP3 package, one can as well install
by hand either of the following two versions of GAP3:

- Frank Luebeck maintains a GAP3 Linux executable, optimized
  for i686 and statically linked for jobs of 2 GByte or more:

    http://www.math.rwth-aachen.de/~Frank.Luebeck/gap/GAP3

- or you can download GAP3 from the GAP website below. Since GAP3
  is no longer supported, it may not be easy to install this version.

    http://www.gap-system.org/Gap3/Download3/download.html

Changing which GAP3 is used
---------------------------

.. WARNING::

    There is a bug in the pexpect module (see :trac:`8471`) that
    prevents the following from working correctly. For now, just make sure
    that ``gap3`` is in your ``PATH``.

Sage assumes that GAP3 can be launched with the command ``gap3``; that is,
Sage assumes that the command ``gap3`` is in your ``PATH``. If this is not
the case, then you can start GAP3 using the following command::

    sage: gap3 = Gap3(command='/usr/local/bin/gap3')               #not tested

Functionality and Examples
--------------------------

The interface to GAP3 offers the following functionality.

#.  ``gap3(expr)`` - Evaluation of arbitrary GAP3 expressions, with the
    result returned as a Sage object wrapping the corresponding GAP3 element::

        sage: a = gap3('3+2')                              #optional - gap3
        sage: a                                            #optional - gap3
        5
        sage: type(a)                                      #optional - gap3
        <class 'sage.interfaces.gap3.GAP3Element'>

    ::

        sage: S5 = gap3('SymmetricGroup(5)')               #optional - gap3
        sage: S5                                           #optional - gap3
        Group( (1,5), (2,5), (3,5), (4,5) )
        sage: type(S5)                                     #optional - gap3
        <class 'sage.interfaces.gap3.GAP3Record'>

    This provides a Pythonic interface to GAP3. If ``gap_function`` is the
    name of a GAP3 function, then the syntax ``gap_element.gap_function()``
    returns the ``gap_element`` obtained by evaluating the command
    ``gap_function(gap_element)`` in GAP3::

        sage: S5.Size()                                    #optional - gap3
        120
        sage: S5.CharTable()                               #optional - gap3
        CharTable( Group( (1,5), (2,5), (3,5), (4,5) ) )

    Alternatively, you can instead use the syntax
    ``gap3.gap_function(gap_element)``::

        sage: gap3.DerivedSeries(S5)                       #optional - gap3
        [ Group( (1,5), (2,5), (3,5), (4,5) ),
          Subgroup( Group( (1,5), (2,5), (3,5), (4,5) ),
                    [ (1,2,5), (1,3,5), (1,4,5) ] ) ]

    If ``gap_element`` corresponds to a GAP3 record, then
    ``gap_element.recfield`` provides a means to access the record element
    corresponding to the field ``recfield``::

        sage: S5.IsRec()                                   #optional - gap3
        true
        sage: S5.recfields()                               #optional - gap3
        ['isDomain', 'isGroup', 'identity', 'generators', 'operations',
        'isPermGroup', 'isFinite', '1', '2', '3', '4', 'degree']
        sage: S5.identity                                  #optional - gap3
        ()
        sage: S5.degree                                    #optional - gap3
        5
        sage: S5.1                                         #optional - gap3
        (1,5)
        sage: S5.2                                         #optional - gap3
        (2,5)

#.  By typing ``%gap3`` or ``gap3.interact()`` at the command-line, you can
    interact directly with the underlying GAP3 session.

    ::

        sage: gap3.interact()                              #not tested

          --> Switching to Gap3 <--

        gap3:

#.  You can start a new GAP3 session as follows::

        sage: gap3.console()                               #not tested

                     ########            Lehrstuhl D fuer Mathematik
                   ###    ####           RWTH Aachen
                  ##         ##
                 ##          #             #######            #########
                ##                        #      ##          ## #     ##
                ##           #           #       ##             #      ##
                ####        ##           ##       #             #      ##
                 #####     ###           ##      ##             ##    ##
                   ######### #            #########             #######
                             #                                  #
                            ##           Version 3              #
                           ###           Release 4.4            #
                          ## #           18 Apr 97              #
                         ##  #
                        ##   #  Alice Niemeyer, Werner Nickel,  Martin Schoenert
                       ##    #  Johannes Meier, Alex Wegner,    Thomas Bischops
                      ##     #  Frank Celler,   Juergen Mnich,  Udo Polis
                      ###   ##  Thomas Breuer,  Goetz Pfeiffer, Hans U. Besche
                       ######   Volkmar Felsch, Heiko Theissen, Alexander Hulpke
                                Ansgar Kaup,    Akos Seress,    Erzsebet Horvath
                                Bettina Eick
                                For help enter: ?<return>
        gap>

#.  The interface also has access to the GAP3 help system::

        sage: gap3.help('help', pager=False)               #not tested
        Help _______________________________________________________...

        This  section describes  together with  the following sections the   GAP
        help system.  The help system lets you read the manual interactively...

Common Pitfalls
---------------

#.  If you want to pass a string to GAP3, then you need to wrap it in
    single quotes as follows::

        sage: gap3('"This is a GAP3 string"')              #optional - gap3
        "This is a GAP3 string"

    This is particularly important when a GAP3 package is loaded via the
    ``RequirePackage`` method (note that one can instead use the
    ``load_package`` method)::

        sage: gap3.RequirePackage('"chevie"')             #optional - gap3

Examples
--------

Load a GAP3 package::

    sage: gap3.load_package("chevie")                      #optional - gap3
    sage: gap3.version() # random                          #optional - gap3
    'lib: v3r4p4 1997/04/18, src: v3r4p0 1994/07/10, sys: usg gcc ansi'

Working with GAP3 lists. Note that GAP3 lists are 1-indexed::

    sage: L = gap3([1,2,3])                                #optional - gap3
    sage: L[1]                                             #optional - gap3
    1
    sage: L[2]                                             #optional - gap3
    2
    sage: 3 in L                                           #optional - gap3
    True
    sage: 4 in L                                           #optional - gap3
    False
    sage: m = gap3([[1,2],[3,4]])                          #optional - gap3
    sage: m[2,1]                                           #optional - gap3
    3
    sage: [1,2] in m                                       #optional - gap3
    True
    sage: [3,2] in m                                       #optional - gap3
    False
    sage: gap3([1,2]) in m                                 #optional - gap3
    True

Controlling variable names used by GAP3::

    sage: gap3('2', name='x')                              #optional - gap3
    2
    sage: gap3('x')                                        #optional - gap3
    2
    sage: gap3.unbind('x')                                 #optional - gap3
    sage: gap3('x')                                        #optional - gap3
    Traceback (most recent call last):
    ...
    TypeError: Gap3 produced error output
    Error, Variable: 'x' must have a value
    ...
"""

#*****************************************************************************
#       Copyright (C) 2010 Franco Saliola <saliola@gmail.com>
#
#  Distributed under the terms of the GNU General Public License (GPL)
#
#    This code is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    General Public License for more details.
#
#  The full text of the GPL is available at:
#
#                  http://www.gnu.org/licenses/
#*****************************************************************************
from __future__ import print_function

from sage.misc.cachefunc import cached_method
from sage.interfaces.expect import Expect
from sage.interfaces.gap import Gap_generic, GapElement_generic

# gap3_cmd should point to the gap3 executable
gap3_cmd = 'gap3'

class Gap3(Gap_generic):
    r"""
    A simple Expect interface to GAP3.

    EXAMPLES::

        sage: from sage.interfaces.gap3 import Gap3
        sage: gap3 = Gap3(command='gap3')

    TESTS::

        sage: gap3(2) == gap3(3)                           #optional - gap3
        False
        sage: gap3(2) == gap3(2)                           #optional - gap3
        True
        sage: gap3._tab_completion()                       #optional - gap3
        []

    We test the interface behaves correctly after a keyboard interrupt::

        sage: gap3(2)                                      #optional - gap3
        2
        sage: try:
        ....:     gap3._keyboard_interrupt()
        ....: except KeyboardInterrupt:
        ....:     pass
        sage: gap3(2)                                      #optional - gap3
        2

    We test that the interface busts out of GAP3's break loop correctly::

        sage: f = gap3('function(L) return L[0]; end;;')   #optional - gap3
        sage: f([1,2,3])                                   #optional - gap3
        Traceback (most recent call last):
        ...
        RuntimeError: Gap3 produced error output
        Error, List Element: <position> must be a positive integer at
        return L[0] ...

    AUTHORS:

    - Franco Saliola (Feb 2010)
    """
    _identical_function = "IsIdentical"

    def __init__(self, command=gap3_cmd):
        r"""
        Initialize the GAP3 interface and start a session.

        INPUT:

        -  command - string (default "gap3"); points to the gap3
           executable on your system; by default, it is assumed the
           executable is in your path.

        EXAMPLES::

            sage: gap3 = Gap3()                            #optional - gap3
            sage: gap3.is_running()
            False
            sage: gap3._start()                            #optional - gap3
            sage: gap3.is_running()                        #optional - gap3
            True
        """
        self.__gap3_command_string = command
        # Explanation of additional command-line options passed to gap3:
        #
        #     -p invokes the internal programmatic interace, which is how Sage
        #     talks to GAP4. This allows reuse some of the GAP4 interface code.
        #
        #     -y -- sets the number of lines of the terminal; controls how many
        #     lines of text are output by GAP3 before the pager is invoked.
        #     This option is useful in dealing with the GAP3 help system.
        Expect.__init__(self,
             name='gap3',
             prompt='gap> ',
             command=self.__gap3_command_string + " -p -b -y 500",
             server=None,
             ulimit=None,
             script_subdirectory=None,
             restart_on_ctrlc=True,
             verbose_start=False,
             init_code=[],
             max_startup_time=None,
             logfile=None,
             eval_using_file_cutoff=100,
             do_cleaner=True,
             remote_cleaner=False,
             path=None)

    def _start(self):
        r"""
        Initialize the interface and start gap3.

        EXAMPLES::

            sage: gap3 = Gap3()                            #optional - gap3
            sage: gap3.is_running()
            False
            sage: gap3._start()                            #optional - gap3
            sage: gap3.is_running()                        #optional - gap3
            True

        Check that :trac:`23142` is fixed::

            sage: gap3.eval("1+1")                         #optional - gap3
            '2'
            sage: gap3.quit()                              #optional - gap3
        """
        Expect._start(self)
        # The -p command-line option to GAP3 produces the following
        # funny-looking patterns in the interface. We compile the patterns
        # now, and use them later for interpreting interface messages.
        self._compiled_full_pattern = self._expect.compile_pattern_list([
            '@p\d+\.','@@','@[A-Z]','@[123456!"#$%&][^+]*\+', '@e','@c',
            '@f','@h','@i','@m','@n','@r','@s\d','@w.*\+','@x','@z'])
        self._compiled_small_pattern = self._expect.compile_pattern_list('@J')
        self._expect.expect("@i")

    def _object_class(self):
        r"""
        Return the class used for constructing GAP3 elements.

        TESTS::

            sage: gap3._object_class()
            <class 'sage.interfaces.gap3.GAP3Element'>
        """
        return GAP3Element

    def _execute_line(self, line, wait_for_prompt=True, expect_eof=False):
        r"""
        Execute a line of code in GAP3 and parse the output.

        TESTS:

        Test that syntax errors are handled correctly::

            sage: gap3('syntax error', name='x')               #optional - gap3
            Traceback (most recent call last):
            ...
            TypeError: Gap3 produced error output
            Syntax error: ; expected
            x:=syntax error;
                          ^
            gap>
            ...

        Test that error messages are detected and reported correctly::

            sage: gap3.SymmetricGroup(5,3)                     #optional - gap3
            Traceback (most recent call last):
            ...
            RuntimeError: Gap3 produced error output
            Error, Record: left operand must be a record at
            return arg[1].operations.SymmetricGroup( arg[1], arg[2] ) ... in
            SymmetricGroup( ..., ... ) called from
            main loop
            brk> quit;
            ...

        Test that break loops are detected and exited properly::

            sage: f = gap3('function(L) return L[0]; end;;')   #optional - gap3
            sage: f([1,2,3])                                   #optional - gap3
            Traceback (most recent call last):
            ...
            RuntimeError: Gap3 produced error output
            Error, List Element: <position> must be a positive integer at
            return L[0] ... in
            ... called from
            main loop
            brk> quit;
            ...
        """
        # It seems that GAP3 does not classify syntax errors as regular error
        # messages, so the generic GAP interface processing code does not
        # detect it. So we test for a syntax error explicitly.
        normal_output, error_output = \
            super(Gap3, self)._execute_line(line, wait_for_prompt=True, expect_eof=False)
        if normal_output.startswith("Syntax error:"):
            normal_output, error_output = "", normal_output
        return (normal_output, error_output)

    def help(self, topic, pager=True):
        r"""
        Print help on the given topic.

        INPUT:

        - ``topic`` -- string

        EXAMPLES::

            sage: gap3.help('help', pager=False)           #optional - gap3
            Help _______________________________________________________...
            <BLANKLINE>
            This  section describes  together with  the following sectio...
            help system.  The help system lets you read the manual inter...

        ::

            sage: gap3.help('SymmetricGroup', pager=False) #optional - gap3
            no section with this name was found

        TESTS::

            sage: m = gap3([[1,2,3],[4,5,6]]); m           #optional - gap3
            [ [ 1, 2, 3 ], [ 4, 5, 6 ] ]
            sage: gap3.help('help', pager=False)           #optional - gap3
            Help _______________________________________________________...
            sage: m                                        #optional - gap3
            [ [ 1, 2, 3 ], [ 4, 5, 6 ] ]
            sage: m.Print()                                #optional - gap3
            [ [ 1, 2, 3 ], [ 4, 5, 6 ] ]
            sage: gap3.help('Group', pager=False)          #optional - gap3
            Group ______________________________________________________...
            sage: m                                        #optional - gap3
            [ [ 1, 2, 3 ], [ 4, 5, 6 ] ]
            sage: m.Print()                                #optional - gap3
            [ [ 1, 2, 3 ], [ 4, 5, 6 ] ]
        """

        import pexpect
        if self._expect is None:
            self._start()
        E = self._expect
        helptext = []

        # we request the help document
        E.sendline("? %s" % topic)
        # it seems necessary to skip TWO echoes (as is done in the GAP4 interface)
        E.expect("\r\n")
        E.expect("\r\n")

        # next we process the help document; translating special characters, etc.
        while True:
            try:
                x = E.expect_list(self._compiled_full_pattern, timeout=2)
            except pexpect.TIMEOUT:
                break
            if x == 1:
                # matched @@; replace with @
                helptext.append('@')
                helptext.append(E.before)
            elif x == 2:
                # matched a special char; convert and insert
                helptext.append(chr(ord(E.after[1:2])-ord('A')+1))
                helptext.append(E.before)
            elif x == 10:
                # matched @n (normal input mode); it seems we're done
                break
            elif x==11:
                # matched @r (echoing input); skip to end of line
                E.expect_list(self._compiled_small_pattern)

        # merge the help text into one string and print it.
        helptext = "".join(helptext).strip()
        if pager is True:
            from sage.misc.pager import pager as pag
            pag()(helptext)
        else:
            print(helptext)

    def cputime(self, t=None):
        r"""
        Returns the amount of CPU time that the GAP session has used in
        seconds. If ``t`` is not None, then it returns the difference
        between the current CPU time and ``t``.

        EXAMPLES::

            sage: t = gap3.cputime()                       #optional - gap3
            sage: t  #random                               #optional - gap3
            0.02
            sage: gap3.SymmetricGroup(5).Size()            #optional - gap3
            120
            sage: gap3.cputime()  #random                  #optional - gap3
            0.14999999999999999
            sage: gap3.cputime(t)  #random                 #optional - gap3
            0.13
        """
        if t is not None:
            return self.cputime() - t
        else:
            return eval(self.eval('Runtime();'))/1000.0

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

        EXAMPLES::

            sage: gap3.console()                               #not tested

                         ########            Lehrstuhl D fuer Mathematik
                       ###    ####           RWTH Aachen
                      ##         ##
                     ##          #             #######            #########
                    ##                        #      ##          ## #     ##
                    ##           #           #       ##             #      ##
                    ####        ##           ##       #             #      ##
                     #####     ###           ##      ##             ##    ##
                       ######### #            #########             #######
                                 #                                  #
                                ##           Version 3              #
                               ###           Release 4.4            #
                              ## #           18 Apr 97              #
                             ##  #
                            ##   #  Alice Niemeyer, Werner Nickel,  Martin Schoenert
                           ##    #  Johannes Meier, Alex Wegner,    Thomas Bischops
                          ##     #  Frank Celler,   Juergen Mnich,  Udo Polis
                          ###   ##  Thomas Breuer,  Goetz Pfeiffer, Hans U. Besche
                           ######   Volkmar Felsch, Heiko Theissen, Alexander Hulpke
                                    Ansgar Kaup,    Akos Seress,    Erzsebet Horvath
                                    Bettina Eick
                                    For help enter: ?<return>
            gap>
        """
        os.system(self.__gap3_command_string)

    def _install_hints(self):
        r"""

        TESTS::

            sage: gap3 = Gap3(command='/wrongpath/gap3')
            sage: gap3('3+2')
            Traceback (most recent call last):
            ...
            TypeError: unable to start gap3 because the command '/wrongpath/gap3 ...' failed: The command was not found or was not executable: /wrongpath/gap3.
            <BLANKLINE>
                Your attempt to start GAP3 failed, either because you do not have
                have GAP3 installed, or because it is not configured correctly.
            <BLANKLINE>
                - If you do not have GAP3 installed, then you must either...
            sage: print(gap3._install_hints())
            <BLANKLINE>
                Your attempt to start GAP3 failed, either because you do not have
                have GAP3 installed, or because it is not configured correctly.
            <BLANKLINE>
                - If you do not have GAP3 installed, then you must either...
        """
        return r"""
    Your attempt to start GAP3 failed, either because you do not have
    have GAP3 installed, or because it is not configured correctly.

    - If you do not have GAP3 installed, then you must either install
      the optional package, see :trac:`20107`, or you download and
      install it yourself.
      Here are two other ways to obtain GAP3:

        - Frank Luebeck maintains a GAP3 Linux executable, optimized
          for i686 and statically linked for jobs of 2 GByte or more:
            http://www.math.rwth-aachen.de/~Frank.Luebeck/gap/GAP3

        - Finally, you can download GAP3 from the GAP website below. Since
          GAP3 is no longer an officially supported distribution of GAP, it
          may not be easy to install this version.
            http://www.gap-system.org/Gap3/Download3/download.html

    - If you have GAP3 installed, then perhaps it is not configured
      correctly. Sage assumes that you can start GAP3 with the command
      %s. Alternatively, you can use the following command
      to point Sage to the correct command for your system.

          gap3 = Gap3(command='/usr/local/bin/gap3')
        """ % self.__gap3_command_string

    @cached_method
    def _tab_completion(self):
        """
        Return additional tab completion entries

        Currently this is empty

        OUTPUT:

        List of strings

        EXAMPLES::

            sage: gap3._tab_completion()
            []
        """
        return []

    
gap3 = Gap3()

class GAP3Element(GapElement_generic):
    r"""
    A GAP3 element

    .. NOTE::

        If the corresponding GAP3 element is a GAP3 record,
        then the class is changed to a ``GAP3Record``.

    INPUT:

    - ``parent`` -- the GAP3 session

    - ``value`` -- the GAP3 command as a string

    - ``is_name`` -- bool (default: False); if True, then ``value`` is
      the variable name for the object

    - ``name`` -- str (default: ``None``); the variable name to use for the
      object. If ``None``, then a variable name is generated.

    .. NOTE::

        If you pass ``E``, ``X`` or ``Z`` for ``name``, then an error is
        raised because these are sacred variable names in GAP3 that should
        never be redefined. Sage raises an error because GAP3 does not!

    EXAMPLES::

        sage: from sage.interfaces.gap3 import GAP3Element   #optional - gap3
        sage: gap3 = Gap3()                                  #optional - gap3
        sage: GAP3Element(gap3, value='3+2')                 #optional - gap3
        5
        sage: GAP3Element(gap3, value='sage0', is_name=True) #optional - gap3
        5

    TESTS::

        sage: GAP3Element(gap3, value='3+2', is_name=False, name='X') #optional - gap3
        Traceback (most recent call last):
        ...
        ValueError: you are attempting to redefine X; but you should never redefine E, X or Z in gap3 (because things will break!)

    AUTHORS:

    - Franco Saliola (Feb 2010)
    """
    def __init__(self, parent, value, is_name=False, name=None):
        r"""
        See ``GAP3Element`` for full documentation.

        EXAMPLES::

            sage: from sage.interfaces.gap3 import GAP3Element   #optional - gap3
            sage: gap3 = Gap3()                                  #optional - gap3
            sage: GAP3Element(gap3, value='3+2')                 #optional - gap3
            5
            sage: GAP3Element(gap3, value='sage0', is_name=True) #optional - gap3
            5

        TESTS::

            sage: GAP3Element(gap3, value='3+2', is_name=False, name='X') #optional - gap3
            Traceback (most recent call last):
            ...
            ValueError: you are attempting to redefine X; but you should never redefine E, X or Z in gap3 (because things will break!)
        """
        # Warning: One should not redefine E, X or Z in gap3, because
        # things will break, but gap3 raises no errors if one does this!
        if name in ["E","X","Z"]:
            raise ValueError("you are attempting to redefine %s; but you should never redefine E, X or Z in gap3 (because things will break!)" % name)

        # initialize the superclass
        super(GAP3Element, self).__init__(parent, value, is_name, name)

        # check for a GAP record; if so then change the class
        parent._synchronize()
        if parent.eval("IsRec(%s)" % self._name) == "true":
            self.__class__ = GAP3Record

    def __getitem__(self, n):
        r"""
        EXAMPLES::

            sage: l = gap3('[1,2,3]')                      #optional - gap3
            sage: l[1]                                     #optional - gap3
            1
            sage: a = gap3([1,2,3])                        #optional - gap3
            sage: a[1]                                     #optional - gap3
            1
            sage: m = gap3([[1,2,3],[4,5,6],[7,8,9]])      #optional - gap3
            sage: m[1,3]                                   #optional - gap3
            3
            sage: m[2][1]                                  #optional - gap3
            4
        """
        gap3_session = self._check_valid()
        if not isinstance(n, tuple):
            return gap3_session.new('%s[%s]'%(self.name(), n))
        else:
            return gap3_session.new('%s%s'%(self.name(), ''.join(['[%s]'%x for x in n])))

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

            sage: s = gap("[[1,2], [3/4, 5/6]]")
            sage: s._latex_()
            '\\left(\\begin{array}{rr} 1&2\\\\ 3/4&\\frac{5}{6}\\\\ \\end{array}\\right)'
            sage: latex(s)
            \left(\begin{array}{rr} 1&2\\ 3/4&\frac{5}{6}\\ \end{array}\right)
        """
        gap3_session = self._check_valid()
        try:
            s = gap3_session.eval('FormatLaTeX(%s)'%self.name())
            s = s.replace('\\\\','\\').replace('"','')
            s = s.replace('%\\n',' ')
            return s
        except RuntimeError:
            return str(self)

class GAP3Record(GAP3Element):
    r"""
    A GAP3 record

    .. NOTE::

        This class should not be called directly, use GAP3Element instead.
        If the corresponding GAP3 element is a GAP3 record, then the class
        is changed to a ``GAP3Record``.

    AUTHORS:

    - Franco Saliola (Feb 2010)
    """
    def recfields(self):
        r"""
        Return a list of the fields for the record. (Record fields are akin
        to object attributes in Sage.)

        OUTPUT:

        - list of strings - the field records

        EXAMPLES::

            sage: S5 = gap3.SymmetricGroup(5)              #optional - gap3
            sage: S5.recfields()                           #optional - gap3
            ['isDomain', 'isGroup', 'identity', 'generators',
             'operations', 'isPermGroup', 'isFinite', '1', '2',
             '3', '4', 'degree']
            sage: S5.degree                                      #optional - gap3
            5
        """
        gap3_session = self._check_valid()
        if not hasattr(self, "_gap_recfields"):
            s = str(gap3_session.eval("RecFields(%s)" % self._name))
            s = s.strip('[] ').replace('\n','')
            self._gap_recfields = [ss.strip('" ') for ss in s.split(',')]
        return getattr(self,"_gap_recfields")

    def operations(self):
        r"""
        Return a list of the GAP3 operations for the record.

        OUTPUT:

        - list of strings - operations of the record

        EXAMPLES::

            sage: S5 = gap3.SymmetricGroup(5)              #optional - gap3
            sage: S5.operations()                          #optional - gap3
            [..., 'NormalClosure', 'NormalIntersection', 'Normalizer',
            'NumberConjugacyClasses', 'PCore', 'Radical', 'SylowSubgroup',
            'TrivialSubgroup', 'FusionConjugacyClasses', 'DerivedSeries', ...]
            sage: S5.DerivedSeries()                       #optional - gap3
            [ Group( (1,5), (2,5), (3,5), (4,5) ),
              Subgroup( Group( (1,5), (2,5), (3,5), (4,5) ),
                        [ (1,2,5), (1,3,5), (1,4,5) ] ) ]
        """
        gap3_session = self._check_valid()
        if not hasattr(self,"_gap_operations"):
            s = str(gap3_session.eval("RecFields(%s.operations)" % self._name))
            s = s.strip('[] ').replace('\n','')
            self._gap_operations = [ss.strip('" ') for ss in s.split(',')]
        return getattr(self,"_gap_operations")

    def __getattr__(self, attrname):
        r"""
        OUTPUT:

        - ``GAP3Record`` -- if ``attrname`` is a field of the GAP record

        - ``ExpectFunction`` -- if ``attrname`` is the name of a GAP3 function

        EXAMPLES::

            sage: S5 = gap3.SymmetricGroup(5)              #optional - gap3
            sage: S5.__getattr__('Size')                   #optional - gap3
            Size
            sage: gap3.IsFunc(S5.__getattr__('Size'))      #optional - gap3
            true
            sage: S5.__getattr__('generators')             #optional - gap3
            [ (1,5), (2,5), (3,5), (4,5) ]
        """
        gap3_session = self._check_valid()
        if attrname[:1] == "_":
            raise AttributeError
        if attrname in self.recfields():
            return gap3_session.new('%s.%s' % (self.name(), attrname))
        return gap3_session._function_element_class()(self, attrname)

    def _tab_completion(self):
        r"""
        Defines the list of methods and attributes that will appear for tab
        completion.

        OUTPUT:

        - list of strings -- the available fields and operations of the
          record

        EXAMPLES::

            sage: S5 = gap3.SymmetricGroup(5)              #optional - gap3
            sage: S5._tab_completion()                     #optional - gap3
            [..., 'ConjugacyClassesTry', 'ConjugateSubgroup', 'ConjugateSubgroups',
            'Core', 'DegreeOperation', 'DerivedSeries', 'DerivedSubgroup',
            'Difference', 'DimensionsLoewyFactors', 'DirectProduct', ...]
        """
        names = self.recfields() + self.operations()
        names.sort()
        return names


import os
def gap3_console():
    r"""
    Spawn a new GAP3 command-line session.

    EXAMPLES::

        sage: gap3.console()                               #not tested

                     ########            Lehrstuhl D fuer Mathematik
                   ###    ####           RWTH Aachen
                  ##         ##
                 ##          #             #######            #########
                ##                        #      ##          ## #     ##
                ##           #           #       ##             #      ##
                ####        ##           ##       #             #      ##
                 #####     ###           ##      ##             ##    ##
                   ######### #            #########             #######
                             #                                  #
                            ##           Version 3              #
                           ###           Release 4.4            #
                          ## #           18 Apr 97              #
                         ##  #
                        ##   #  Alice Niemeyer, Werner Nickel,  Martin Schoenert
                       ##    #  Johannes Meier, Alex Wegner,    Thomas Bischops
                      ##     #  Frank Celler,   Juergen Mnich,  Udo Polis
                      ###   ##  Thomas Breuer,  Goetz Pfeiffer, Hans U. Besche
                       ######   Volkmar Felsch, Heiko Theissen, Alexander Hulpke
                                Ansgar Kaup,    Akos Seress,    Erzsebet Horvath
                                Bettina Eick
                                For help enter: ?<return>
        gap>
    """
    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 %%gap3 magics instead.')
    os.system(gap3_cmd)

def gap3_version():
    r"""
    Return the version of GAP3 that you have in your PATH on your computer.

    EXAMPLES::

        sage: gap3_version()                                           # random, optional - gap3
        'lib: v3r4p4 1997/04/18, src: v3r4p0 1994/07/10, sys: usg gcc ansi'
    """
    return gap3.eval('VERSION')[1:-1]
