r"""Read results from FORM.

        Wait for a response of FORM to obtain the results specified by
        the given names and return a corresponding string or (nested) list of
        strings. Objects to be read from FORM are expressions, $-variables and
        preprocessor variables.

        ========== =============================
          name       meaning
        ========== =============================
          "F"        expression F
          "$x"       $-variable $x
          "$x[]"     factorized $-variable $x
          "\`A'"     preprocessor variable A
        ========== =============================

        Note that the communication for the reading is performed within the
        preprocessor of FORM (i.e., at compile-time), so one may need to write
        ".sort" to get the correct result.

        If non-string objects are passed, they are considered as sequences, and
        the return value becomes a list corresponding to the arguments. If
        a sequence is passed as the argument to this method, it is guaranteed
        that the return value is always a list:

        >>> import form
        >>> f = form.open()
        >>> f.write('''
        ...     S a1,...,a3;
        ...     L F1 = a1;
        ...     L F2 = a2;
        ...     L F3 = a3;
        ...     .sort
        ... ''')

        >>> f.read(['F1'])
        ['a1']
        >>> f.read(['F1', 'F2'])
        ['a1', 'a2']
        >>> f.read(['F1', 'F2', 'F3'])
        ['a1', 'a2', 'a3']

        A more complicated example, which returns a nested list, is

        >>> f.read('F1', ['F2', 'F3'])
        ['a1', ['a2', 'a3']]

        >>> f.close()

def read(self, *names):  # type: ignore  # noqa: F811
        # type: (Any) -> Any
        r"""Read results from FORM.

        Wait for a response of FORM to obtain the results specified by
        the given names and return a corresponding string or (nested) list of
        strings. Objects to be read from FORM are expressions, $-variables and
        preprocessor variables.

        ========== =============================
          name       meaning
        ========== =============================
          "F"        expression F
          "$x"       $-variable $x
          "$x[]"     factorized $-variable $x
          "\`A'"     preprocessor variable A
        ========== =============================

        Note that the communication for the reading is performed within the
        preprocessor of FORM (i.e., at compile-time), so one may need to write
        ".sort" to get the correct result.

        If non-string objects are passed, they are considered as sequences, and
        the return value becomes a list corresponding to the arguments. If
        a sequence is passed as the argument to this method, it is guaranteed
        that the return value is always a list:

        >>> import form
        >>> f = form.open()
        >>> f.write('''
        ...     S a1,...,a3;
        ...     L F1 = a1;
        ...     L F2 = a2;
        ...     L F3 = a3;
        ...     .sort
        ... ''')

        >>> f.read(['F1'])
        ['a1']
        >>> f.read(['F1', 'F2'])
        ['a1', 'a2']
        >>> f.read(['F1', 'F2', 'F3'])
        ['a1', 'a2', 'a3']

        A more complicated example, which returns a nested list, is

        >>> f.read('F1', ['F2', 'F3'])
        ['a1', ['a2', 'a3']]

        >>> f.close()
        """
        if self._closed:
            raise IOError('tried to read from closed connection')

        if len(names) == 1 and not isinstance(names[0], string_types):
            names = tuple(names[0])
            if len(names) == 1:
                return [self.read(*names)]  # Guarantee to return a list.
            else:
                return self.read(*names)

        if any(not isinstance(x, string_types) for x in names):
            return [self.read(x) for x in names]

        assert self._parentin is not None
        assert self._parentout is not None
        assert self._loggingin is not None

        for e in names:
            if len(e) >= 2 and e[0] == '`' and e[-1] == "'":
                self._parentout.write(
                    '#toexternal "{0}{1}"\n'.format(e, self._END_MARK))
            elif len(e) >= 3 and e[0] == '$' and e[-2:] == '[]':
                # Special syntax "$x[]" for factorized $-variables.
                # NOTE: (1) isfactorized($x) is zero when $x is 0 or $x has
                #           only one factor even after FactArg is performed.
                #       (2) `$x[0]' is accessible even if FactArg has not been
                #           performed. Use `$x[0]' rather than
                #           `isfactorized($x)`.
                #       (3) `$x[1]' is not accessible (segfault) with versions
                #           before Sep  3 2015, if $x has only one factor and
                #           `$x[0]' gives 1.
                self._parentout.write((
                    "#if `${0}[0]'\n"
                    "#toexternal \"(%$)\",${0}[1]\n"
                    "#do i=2,`${0}[0]'\n"
                    "#toexternal \"*(%$)\",${0}[`i']\n"
                    "#enddo\n"
                    "#else\n"
                    "#if termsin(${0})\n"
                    "#toexternal \"%$\",${0}\n"
                    "#else\n"
                    "#toexternal \"(0)\"\n"
                    "#endif\n"
                    "#endif\n"
                    "#toexternal \"{1}\"\n"
                ).format(e[1:-2], self._END_MARK))
            elif len(e) >= 1 and e[0] == '$':
                self._parentout.write(
                    '#toexternal "%${1}",{0}\n'.format(e, self._END_MARK))
            else:
                self._parentout.write(
                    '#toexternal "%E{1}",{0}\n'.format(e, self._END_MARK))
        self._parentout.write('#redefine FORMLINKLOOPVAR "0"')
        self._parentout.write(self._PROMPT)
        self._parentout.flush()

        result = []
        out = self._parentin.read0()
        out_start = 0  # start position for searching _END_MARK.
        for _e in names:
            while True:
                i = out.find(self._END_MARK, out_start)
                if i >= 0:
                    result.append(out[:i])
                    out = out[i + self._END_MARK_LEN:]
                    out_start = 0
                    break
                out_start = max(len(out) - self._END_MARK_LEN, 0)

                r, _, _ = select.select((self._parentin, self._loggingin),
                                        (), ())
                if self._loggingin in r:
                    s = self._loggingin.read()
                    if s:
                        i = s.rfind('\n')
                        if i >= 0:
                            msgs = s[:i].split('\n')
                            if self._log is not None:
                                self._log.extend(msgs)
                            for msg in msgs:
                                if (msg.find('-->') >= 0 or
                                        msg.find('==>') >= 0):
                                    if self._log:
                                        msg += '\n'
                                        msg += '\n'.join(self._log)
                                    self.close()
                                    raise FormError(msg)
                        self._loggingin.unread(s[i + 1:])
                if self._parentin in r:
                    out += (self._parentin.read()
                            .replace('\n', '')
                            .replace('\\', '')
                            .replace(' ', ''))

        self._parentin.unread(out)

        if len(names) == 0:
            return None
        elif len(names) == 1:
            return result[0]
        else:
            return result