Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quellcode-Bibliothek io.c   Sprache: C

 
/****************************************************************************
**
**  This file is part of GAP, a system for computational discrete algebra.
**
**  Copyright of GAP belongs to its developers, whose names are too numerous
**  to list here. Please refer to the COPYRIGHT file for details.
**
**  SPDX-License-Identifier: GPL-2.0-or-later
**
**  This file contains functions responsible for input and output processing.
**
**  These provide the concept of  a current input  and output file.   In the
**  main   module   they are opened  and   closed  with the  'OpenInput'  and
**  'CloseInput' respectively  'OpenOutput' and 'CloseOutput' calls.  All the
**  other modules just read from the  current input  and write to the current
**  output file.
**
**  This module relies on the functions  provided  by  the  operating  system
**  dependent module 'system.c' for the low level input/output.
*/


#include "io.h"

#include "ariths.h"
#include "bool.h"
#include "calls.h"
#include "error.h"
#include "gapstate.h"
#include "gaputils.h"
#include "gvars.h"
#include "listfunc.h"
#include "lists.h"
#include "modules.h"
#include "plist.h"
#include "read.h"
#include "scanner.h"
#include "stringobj.h"
#include "symbols.h"
#include "sysfiles.h"
#include "sysopt.h"
#include "sysstr.h"
#include "trycatch.h"

#ifdef HPCGAP
#include "hpc/aobjects.h"
#endif

#include <limits.h>


static Char GetLine(TypInputFile * input);
static void PutLine2(TypOutputFile * output, const Char * line, UInt len);

static Obj ReadLineFunc;
static Obj WriteAllFunc;
static Obj IsInputStringStream;
static Obj IsOutputStringStream;
static Obj PositionStream;
static Obj SeekPositionStream;

// 'PrintPromptHook' is a GAP-level variable which can be set to a function
// for printing the GAP prompt. If not bound, 'STATE(Prompt)' is printed.
static Obj PrintPromptHook = 0;

static Obj PrintFormattingStatus;
static Obj SetPrintFormattingStatus;

/****************************************************************************
**
*V  FilenameCache . . . . . . . . . . . . . . . . . . list of filenames
**
**  'FilenameCache' is a list of previously opened filenames.
*/

static Obj FilenameCache;
static SymbolTable FilenameSymbolTable;

static ModuleStateOffset IOStateOffset = -1;

enum {
    MAX_OPEN_FILES = 16,
};

struct IOModuleState {

    // A pointer to the current input file
    TypInputFile * Input;

    // A pointer to the current output file. It points to the top of the
    // stack 'OutputFiles'.
    TypOutputFile * Output;

    //
    TypOutputFile * IgnoreStdoutErrout;


    // The file identifier of the current input logfile. If it is not 0 the
    // scanner echoes all input from the files '*stdin*' and '*errin*' to
    // this file.
    TypOutputFile * InputLog;

    // The file identifier of the current output logfile. If it is not 0 the
    // scanner echoes all output to the files '*stdout*' and '*errout*' to
    // this file.
    TypOutputFile * OutputLog;

    TypOutputFile InputLogFileOrStream;
    TypOutputFile OutputLogFileOrStream;

    TypOutputFile DefaultOutput;

#ifdef HPCGAP
    Obj DefaultOutputStream;
    Obj DefaultInputStream;
#endif

    Int NoSplitLine;

    BOOL PrintFormattingForStdout;
    BOOL PrintFormattingForErrout;
};

// for debugging from GDB / lldb, we mark this as extern inline
extern inline struct IOModuleState * IO(void)
{
    return (struct IOModuleState *)StateSlotsAtOffset(IOStateOffset);
}

void LockCurrentOutput(BOOL lock)
{
    IO()->IgnoreStdoutErrout = lock ? IO()->Output : NULL;
}

TypInputFile * GetCurrentInput(void)
{
    return IO()->Input;
}

/****************************************************************************
**
*F  GetNextChar() . . . . . . . . . . . . . . . get the next character, local
**
**  'GetNextChar' returns the next character from  the current input file.
*/

Char GetNextChar(TypInputFile * input)
{
    input->ptr++;

    // handle line continuation, i.e., backslash followed by new line; and
    // also the case when we run out of buffered data
    while (*input->ptr == '\\' || *input->ptr == 0) {

        // if we run out of data, get more, and try again
        if (*input->ptr == 0) {
            GetLine(input);
            continue;
        }

        // we have seen a backslash; so check now if it starts a
        // line continuation, i.e., whether it is followed by a line terminator
        if (input->ptr[1] == '\n') {
            // LF is the line terminator used in Unix and its relatives
            input->ptr += 2;
        }
        else if (input->ptr[1] == '\r' && input->ptr[2] == '\n') {
            // CR+LF is the line terminator used by Windows
            input->ptr += 3;
        }
        else {
            // if we see a backlash without a line terminator after it, stop
            break;
        }

        // if we get here, we saw a line continuation; change the prompt to a
        // partial prompt from now on
        SetPrompt("> ");
    }

    return *input->ptr;
}

// GET_NEXT_CHAR_NO_LC is like GetNextChar, but does not handle
// line continuations. This is used when skipping to the end of the
// current line, when handling comment lines.
Char GET_NEXT_CHAR_NO_LC(TypInputFile * input)
{
    char c = *(++input->ptr);
    return c ? c : GetLine(input);
}

Char PEEK_NEXT_CHAR(TypInputFile * input)
{
    // store the current character
    char c = *input->ptr;

    // read next character; this will increment input->ptr and then
    // possibly read in new line data, and so even might end up resetting
    // input->ptr to point at the start of the line buffer, which is
    // equal to Input->line+1
    char next = GetNextChar(input);

    // push back the previous character: first, return input->ptr to the
    // previous position; then, if we detect that GetNextChar read a new
    // line, also restore the previous character by placing it in the "push
    // back buffer"
    GAP_ASSERT(input->ptr > input->line);
    input->ptr--;
    if (input->ptr == input->line)
        *input->ptr = c;

    // return the next character
    return next;
}

Char PEEK_CURR_CHAR(TypInputFile * input)
{
    Char c = *input->ptr;

    // if no character is available then get one
    if (c == '\0') {
        GAP_ASSERT(input->ptr > input->line);
        input->ptr--;
        c = GetNextChar(input);
    }

    return c;
}

void SKIP_TO_END_OF_LINE(TypInputFile * input)
{
    Char c = *input->ptr;
    while (c != '\n' && c != '\r' && c != '\377')
        c = GET_NEXT_CHAR_NO_LC(input);
}


Int GetInputLineNumber(TypInputFile * input)
{
    return input ? input->number : 0;
}

const Char * GetInputLineBuffer(TypInputFile * input)
{
    GAP_ASSERT(input);
    // first byte of Input->line is reserved for the pushback buffer, so add 1
    return input->line + 1;
}

Int GetInputLinePosition(TypInputFile * input)
{
    GAP_ASSERT(input);
    return input->ptr - GetInputLineBuffer(input);
}

UInt GetInputFilenameID(TypInputFile * input)
{
    return input ? input->gapnameid : 0;
}

static void AddCachedFilename(SymbolTable * symtab, UInt id, Obj name)
{
    AssPlist(FilenameCache, id, name);
}

Obj GetCachedFilename(UInt id)
{
    return ELM_LIST(FilenameCache, id);
}


/****************************************************************************
**
*F * * * * * * * * * * * open input/output functions  * * * * * * * * * * * *
*/


#ifdef HPCGAP
static GVarDescriptor DEFAULT_INPUT_STREAM;
static GVarDescriptor DEFAULT_OUTPUT_STREAM;

static UInt OpenDefaultInput(TypInputFile * input)
{
  Obj func, stream;
  stream = IO()->DefaultInputStream;
  if (stream)
      return OpenInputStream(input, stream, FALSE);
  func = GVarOptFunction(&DEFAULT_INPUT_STREAM);
  if (!func)
    return OpenInput(input, "*stdin*");
  stream = CALL_0ARGS(func);
  if (!stream)
    ErrorQuit("DEFAULT_INPUT_STREAM() did not return a stream", 0, 0);
  if (IsStringConv(stream))
    return OpenInput(input, CONST_CSTR_STRING(stream));
  IO()->DefaultInputStream = stream;
  return OpenInputStream(input, stream, FALSE);
}

static UInt OpenDefaultOutput(TypOutputFile * output)
{
  Obj func, stream;
  stream = IO()->DefaultOutputStream;
  if (stream)
    return OpenOutputStream(output, stream);
  func = GVarOptFunction(&DEFAULT_OUTPUT_STREAM);
  if (!func)
    return OpenOutput(output, "*stdout*"FALSE);
  stream = CALL_0ARGS(func);
  if (!stream)
    ErrorQuit("DEFAULT_OUTPUT_STREAM() did not return a stream", 0, 0);
  if (IsStringConv(stream))
    return OpenOutput(output, CONST_CSTR_STRING(stream), FALSE);
  IO()->DefaultOutputStream = stream;
  return OpenOutputStream(output, stream);
}
#endif


/****************************************************************************
**
*F  OpenInput( <filename> ) . . . . . . . . . .  open a file as current input
**
**  'OpenInput' opens  the file with  the name <filename>  as  current input.
**  All  subsequent input will  be taken from that  file, until it is  closed
**  again  with 'CloseInput'  or  another file  is opened  with  'OpenInput'.
**  'OpenInput'  will not  close the  current  file, i.e., if  <filename>  is
**  closed again, input will again be taken from the current input file.
**
**  'OpenInput'  returns 1 if  it   could  successfully open  <filename>  for
**  reading and 0  to indicate  failure.   'OpenInput' will fail if  the file
**  does not exist or if you do not have permissions to read it.  'OpenInput'
**  may  also fail if  you have too  many files open at once.   It  is system
**  dependent how many are  too many, but  16  files should  work everywhere.
**
**  You can open  '*stdin*' to  read  from the standard  input file, which is
**  usually the terminal, or '*errin*' to  read from the standard error file,
**  which  is  the  terminal  even if '*stdin*'  is  redirected from  a file.
**  'OpenInput' passes those  file names  to  'SyFopen' like any other  name,
**  they are  just  a  convention between the  main  and the system  package.
**  'SyFopen' and thus 'OpenInput' will  fail to open  '*errin*' if the  file
**  'stderr'  (Unix file  descriptor  2)  is  not a  terminal,  because  of a
**  redirection say, to avoid that break loops take their input from a file.
**
**  It is not necessary to open the initial input  file, 'InitScanner' opens
**  '*stdin*' for  that purpose.  This  file on   the other   hand  cannot be
**  closed by 'CloseInput'.
*/

UInt OpenInput(TypInputFile * input, const Char * filename)
{
    GAP_ASSERT(input);

    Int file;

#ifdef HPCGAP
    /* Handle *defin*; redirect *errin* to *defin* if the default
     * channel is already open. */

    if (streq(filename, "*defin*") ||
        (streq(filename, "*errin*") && IO()->DefaultInputStream))
        return OpenDefaultInput(input);
#endif

    // try to open the input file
    file = SyFopen(filename, "r"TRUE);
    if ( file == -1 )
        return 0;

    // enter the file identifier and the file name
#ifdef GAP_KERNEL_DEBUG
    // paranoia: fill with garbage, to verify we initialize everything
    memset(input, 0x47, sizeof(TypInputFile));
#endif
    input->prev = IO()->Input;
    input->stream = 0;
    input->file = file;

    // enable echo for stdin and errin
    if (streq("*errin*", filename) || streq("*stdin*", filename))
        input->echo = TRUE;
    else
        input->echo = FALSE;

    input->gapnameid = LookupSymbol(&FilenameSymbolTable, filename);

    // start with an empty line
    input->line[0] = '\0';    // init the pushback buffer
    input->line[1] = '\0';    // empty line buffer
    input->ptr = input->line + 1;
    input->number = 1;
    input->lastErrorLine = 0;

    IO()->Input = input;

    // indicate success
    return 1;
}


/****************************************************************************
**
*F  OpenInputStream( <stream>, <echo> ) . . .  open a stream as current input
**
**  The same as 'OpenInput' but for streams.
*/

UInt OpenInputStream(TypInputFile * input, Obj stream, BOOL echo)
{
    GAP_ASSERT(input);

    // enter the file identifier and the file name
#ifdef GAP_KERNEL_DEBUG
    // paranoia: fill with garbage, to verify we initialize everything
    memset(input, 0x47, sizeof(TypInputFile));
#endif
    input->prev = IO()->Input;
    input->stream = stream;
    input->file = -1;
    input->isstringstream = (CALL_1ARGS(IsInputStringStream, stream) == True);
    if (input->isstringstream) {
        input->sline = CONST_ADDR_OBJ(stream)[2];
        input->spos = INT_INTOBJ(CONST_ADDR_OBJ(stream)[1]);
    }
    else {
        input->sline = 0;
    }
    input->echo = echo;
    input->gapnameid = LookupSymbol(&FilenameSymbolTable, "stream");

    // start with an empty line
    input->line[0] = '\0';    // init the pushback buffer
    input->line[1] = '\0';    // empty line buffer
    input->ptr = input->line + 1;
    input->number = 1;
    input->lastErrorLine = 0;

    IO()->Input = input;

    // indicate success
    return 1;
}


/****************************************************************************
**
*F  CloseInput()  . . . . . . . . . . . . . . . . .  close current input file
**
**  'CloseInput'  will close the  current input file.   Subsequent input will
**  again be taken from the previous input file.   'CloseInput' will return 1
**  to indicate success.
**
**  'CloseInput' will not close the initial input file '*stdin*', and returns
**  0  if such  an  attempt is made.   This is  used in  'Error'  which calls
**  'CloseInput' until it returns 0, thereby closing all open input files.
**
**  Calling 'CloseInput' if the  corresponding  'OpenInput' call failed  will
**  close the current output file, which will lead to very strange behaviour.
*/

UInt CloseInput(TypInputFile * input)
{
    GAP_ASSERT(input);
    GAP_ASSERT(input == IO()->Input);

    // revert to previous input
    IO()->Input = input->prev;

    if (input->stream) {
        // if the input stream supports seeking, update its position to
        // reflect the actual state of things: we may have read and buffered
        // more bytes than we actually processed
        int offset = strlen(input->ptr);
        // check for EOF
        if (input->ptr[0] == '\377' && input->ptr[1] == '\0')
            offset = 0;
        if (offset) {
            Obj pos = CALL_1ARGS(PositionStream, input->stream);
            C_DIFF_FIA(pos, pos, INTOBJ_INT(offset));
            CALL_2ARGS(SeekPositionStream, input->stream, pos);
        }
    } else {
        // close the input file
        SyFclose(input->file);
    }

    // don't keep GAP objects alive unnecessarily
    input->stream = 0;
    input->sline = 0;

    return 1;
}

/****************************************************************************
**
*F  FlushRestOfInputLine()  . . . . . . . . . . . . discard remainder of line
*/


void FlushRestOfInputLine(TypInputFile * input)
{
    input->ptr[0] = input->ptr[1] = '\0';
}

/****************************************************************************
**
*F  OpenLog( <filename> ) . . . . . . . . . . . . . log interaction to a file
**
**  'OpenLog'  instructs  the scanner to   echo  all  input   from  the files
**  '*stdin*' and  '*errin*'  and  all  output to  the  files '*stdout*'  and
**  '*errout*' to the file with  name <filename>.  The  file is truncated  to
**  size 0 if it existed, otherwise it is created.
**
**  'OpenLog' returns 1 if it could  successfully open <filename> for writing
**  and 0  to indicate failure.   'OpenLog' will  fail if  you do  not   have
**  permissions  to create the file or   write to  it.  'OpenOutput' may also
**  fail if you have too many files open at once.  It is system dependent how
**  many   are too   many, but  16   files should  work everywhere.   Finally
**  'OpenLog' will fail if there is already a current logfile.
*/

UInt OpenLog (
    const Char *        filename )
{

    // refuse to open a logfile if we already log to one
    if (IO()->InputLog != 0 || IO()->OutputLog != 0)
        return 0;

    // try to open the file
    IO()->OutputLogFileOrStream.file = SyFopen(filename, "w"FALSE);
    IO()->OutputLogFileOrStream.stream = 0;
    if (IO()->OutputLogFileOrStream.file == -1)
        return 0;

    IO()->InputLog = &IO()->OutputLogFileOrStream;
    IO()->OutputLog = &IO()->OutputLogFileOrStream;

    // otherwise indicate success
    return 1;
}


/****************************************************************************
**
*F  OpenLogStream( <stream> ) . . . . . . . . . . log interaction to a stream
**
**  The same as 'OpenLog' but for streams.
*/

UInt OpenLogStream (
    Obj             stream )
{

    // refuse to open a logfile if we already log to one
    if (IO()->InputLog != 0 || IO()->OutputLog != 0)
        return 0;

    // try to open the file
    IO()->OutputLogFileOrStream.stream = stream;
    IO()->OutputLogFileOrStream.file = -1;

    IO()->InputLog = &IO()->OutputLogFileOrStream;
    IO()->OutputLog = &IO()->OutputLogFileOrStream;

    // otherwise indicate success
    return 1;
}


/****************************************************************************
**
*F  CloseLog()  . . . . . . . . . . . . . . . . . . close the current logfile
**
**  'CloseLog' closes the current logfile again, so that input from '*stdin*'
**  and '*errin*' and output to '*stdout*' and '*errout*' will no  longer  be
**  echoed to a file.  'CloseLog' will return 1 to indicate success.
**
**  'CloseLog' will fail if there is no logfile active and will return  0  in
**  this case.
*/

UInt CloseLog ( void )
{
    // refuse to close a non existent logfile
    if (IO()->InputLog == 0 || IO()->OutputLog == 0 ||
        IO()->InputLog != IO()->OutputLog)
        return 0;

    // close the logfile
    if (!IO()->InputLog->stream) {
        SyFclose(IO()->InputLog->file);
    }
    IO()->InputLog = 0;
    IO()->OutputLog = 0;

    // indicate success
    return 1;
}


/****************************************************************************
**
*F  OpenInputLog( <filename> )  . . . . . . . . . . . . . log input to a file
**
**  'OpenInputLog'  instructs the  scanner  to echo  all input from the files
**  '*stdin*' and  '*errin*' to the file  with  name <filename>.  The file is
**  truncated to size 0 if it existed, otherwise it is created.
**
**  'OpenInputLog' returns 1  if it  could successfully open  <filename>  for
**  writing  and  0 to indicate failure.  'OpenInputLog' will fail  if you do
**  not have  permissions to create the file  or write to it.  'OpenInputLog'
**  may also fail  if you  have  too many  files open  at once.  It is system
**  dependent  how many are too many,  but 16 files  should work  everywhere.
**  Finally 'OpenInputLog' will fail if there is already a current logfile.
*/

UInt OpenInputLog (
    const Char *        filename )
{

    // refuse to open a logfile if we already log to one
    if (IO()->InputLog != 0)
        return 0;

    // try to open the file
    IO()->InputLogFileOrStream.file = SyFopen(filename, "w"FALSE);
    IO()->InputLogFileOrStream.stream = 0;
    if (IO()->InputLogFileOrStream.file == -1)
        return 0;

    IO()->InputLog = &IO()->InputLogFileOrStream;

    // otherwise indicate success
    return 1;
}


/****************************************************************************
**
*F  OpenInputLogStream( <stream> )  . . . . . . . . . . log input to a stream
**
**  The same as 'OpenInputLog' but for streams.
*/

UInt OpenInputLogStream (
    Obj                 stream )
{

    // refuse to open a logfile if we already log to one
    if (IO()->InputLog != 0)
        return 0;

    // try to open the file
    IO()->InputLogFileOrStream.stream = stream;
    IO()->InputLogFileOrStream.file = -1;

    IO()->InputLog = &IO()->InputLogFileOrStream;

    // otherwise indicate success
    return 1;
}


/****************************************************************************
**
*F  CloseInputLog() . . . . . . . . . . . . . . . . close the current logfile
**
**  'CloseInputLog'  closes  the current  logfile again,  so  that input from
**  '*stdin*'  and   '*errin*'  will  no  longer   be  echoed   to  a   file.
**  'CloseInputLog' will return 1 to indicate success.
**
**  'CloseInputLog' will fail if there is no logfile active and will return 0
**  in this case.
*/

UInt CloseInputLog ( void )
{
    // refuse to close a non existent logfile
    if (IO()->InputLog == 0)
        return 0;

    // refuse to close a log opened with LogTo
    if (IO()->InputLog == IO()->OutputLog)
        return 0;

    // close the logfile
    if (!IO()->InputLog->stream) {
        SyFclose(IO()->InputLog->file);
    }

    IO()->InputLog = 0;

    // indicate success
    return 1;
}


/****************************************************************************
**
*F  OpenOutputLog( <filename> )  . . . . . . . . . . .  log output to a file
**
**  'OpenOutputLog'  instructs the  scanner to echo  all output to  the files
**  '*stdout*' and '*errout*' to the file with name  <filename>.  The file is
**  truncated to size 0 if it existed, otherwise it is created.
**
**  'OpenOutputLog'  returns 1 if it  could  successfully open <filename> for
**  writing and 0 to  indicate failure.  'OpenOutputLog'  will fail if you do
**  not have permissions to create the file  or write to it.  'OpenOutputLog'
**  may also  fail if you have  too many  files  open at  once.  It is system
**  dependent how many are  too many,  but  16 files should  work everywhere.
**  Finally 'OpenOutputLog' will fail if there is already a current logfile.
*/

UInt OpenOutputLog (
    const Char *        filename )
{

    // refuse to open a logfile if we already log to one
    if (IO()->OutputLog != 0)
        return 0;

    // try to open the file
    memset(&IO()->OutputLogFileOrStream, 0, sizeof(TypOutputFile));
    IO()->OutputLogFileOrStream.stream = 0;
    IO()->OutputLogFileOrStream.file = SyFopen(filename, "w"FALSE);
    if (IO()->OutputLogFileOrStream.file == -1)
        return 0;

    IO()->OutputLog = &IO()->OutputLogFileOrStream;

    // otherwise indicate success
    return 1;
}


/****************************************************************************
**
*F  OpenOutputLogStream( <stream> )  . . . . . . . .  log output to a stream
**
**  The same as 'OpenOutputLog' but for streams.
*/

UInt OpenOutputLogStream (
    Obj                 stream )
{

    // refuse to open a logfile if we already log to one
    if (IO()->OutputLog != 0)
        return 0;

    // try to open the file
    memset(&IO()->OutputLogFileOrStream, 0, sizeof(TypOutputFile));
    IO()->OutputLogFileOrStream.stream = stream;
    IO()->OutputLogFileOrStream.file = -1;

    IO()->OutputLog = &IO()->OutputLogFileOrStream;

    // otherwise indicate success
    return 1;
}


/****************************************************************************
**
*F  CloseOutputLog()  . . . . . . . . . . . . . . . close the current logfile
**
**  'CloseOutputLog' closes  the  current logfile  again, so  that output  to
**  '*stdout*'  and    '*errout*'  will no   longer  be   echoed to  a  file.
**  'CloseOutputLog' will return 1 to indicate success.
**
**  'CloseOutputLog' will fail if there is  no logfile active and will return
**  0 in this case.
*/

UInt CloseOutputLog ( void )
{
    // refuse to close a non existent logfile
    if (IO()->OutputLog == 0)
        return 0;

    // refuse to close a log opened with LogTo
    if (IO()->OutputLog == IO()->InputLog)
        return 0;

    // close the logfile
    if (!IO()->OutputLog->stream) {
        SyFclose(IO()->OutputLog->file);
    }

    IO()->OutputLog = 0;

    // indicate success
    return 1;
}

/****************************************************************************
**
*F  OpenOutput( <filename> )  . . . . . . . . . open a file as current output
**
**  'OpenOutput' opens the file  with the name  <filename> as current output.
**  All subsequent output will go  to that file, until either   it is  closed
**  again  with 'CloseOutput' or  another  file is  opened with 'OpenOutput'.
**  The file is truncated to size 0 if it existed, otherwise it  is  created.
**  'OpenOutput' does not  close  the  current file, i.e., if  <filename>  is
**  closed again, output will go again to the current output file.
**
**  'OpenOutput'  returns  1 if it  could  successfully  open  <filename> for
**  writing and 0 to indicate failure.  'OpenOutput' will fail if  you do not
**  have  permissions to create the  file or write   to it.  'OpenOutput' may
**  also   fail if you   have  too many files   open  at once.   It is system
**  dependent how many are too many, but 16 files should work everywhere.
**
**  You can open '*stdout*'  to write  to the standard output  file, which is
**  usually the terminal, or '*errout*' to write  to the standard error file,
**  which is the terminal  even   if '*stdout*'  is  redirected to   a  file.
**  'OpenOutput' passes  those  file names to 'SyFopen'  like any other name,
**  they are just a convention between the main and the system package.
**
**  The function does nothing and returns success for '*stdout*' and
**  '*errout*' when 'LockCurrentOutput(1)' is in effect (used for testing
**  purposes).
**
**  It is not necessary to open the initial output file; '*stdout'* is
**  opened for that purpose during startup. This file on the other hand  can
**  not be closed by 'CloseOutput'.
**
**  If <append> is set to true, then 'OpenOutput' does not truncate the file
**  to size 0 if it exists.
*/

UInt OpenOutput(TypOutputFile * output, const Char * filename, BOOL append)
{
    GAP_ASSERT(output);

    // do nothing for stdout and errout if caught
    if (IO()->Output != NULL && IO()->IgnoreStdoutErrout == IO()->Output &&
        (streq(filename, "*errout*") || streq(filename, "*stdout*"))) {
        return 1;
    }

#ifdef HPCGAP
    /* Handle *defout* specially; also, redirect *errout* if we already
     * have a default channel open. */

    if (streq(filename, "*defout*") ||
        (streq(filename, "*errout*") && TLS(threadID) != 0))
        return OpenDefaultOutput(output);
#endif

    // try to open the file
    Int file = SyFopen(filename, append ? "a" : "w"FALSE);
    if ( file == -1 )
        return 0;

    // put the file on the stack, start at position 0 on an empty line
#ifdef GAP_KERNEL_DEBUG
    // paranoia: fill with garbage, to verify we initialize everything
    memset(output, 0x47, sizeof(TypOutputFile));
#endif
    output->prev = IO()->Output;
    IO()->Output = output;
    output->isstringstream = FALSE;
    output->stream = 0;
    output->file = file;
    output->line[0] = '\0';
    output->pos = 0;
    if (streq(filename, "*stdout*"))
        output->format = IO()->PrintFormattingForStdout;
    else if (streq(filename, "*errout*"))
        output->format = IO()->PrintFormattingForErrout;
    else
        output->format = TRUE;
    output->indent = 0;

    // variables related to line splitting, very bad place to split
    output->hints[0] = -1;

    // indicate success
    return 1;
}


/****************************************************************************
**
*F  OpenOutputStream( <stream> )  . . . . . . open a stream as current output
**
**  The same as 'OpenOutput' (and also 'OpenAppend') but for streams.
*/



UInt OpenOutputStream(TypOutputFile * output, Obj stream)
{
    GAP_ASSERT(output);

    // put the file on the stack, start at position 0 on an empty line
#ifdef GAP_KERNEL_DEBUG
    // paranoia: fill with garbage, to verify we initialize everything
    memset(output, 0x47, sizeof(TypOutputFile));
#endif
    output->prev = IO()->Output;
    IO()->Output = output;
    output->isstringstream = (CALL_1ARGS(IsOutputStringStream, stream) == True);
    output->stream = stream;
    output->file = -1;
    output->line[0] = '\0';
    output->pos = 0;
    output->format = (CALL_1ARGS(PrintFormattingStatus, stream) == True);
    output->indent = 0;

    // variables related to line splitting, very bad place to split
    output->hints[0] = -1;

    // indicate success
    return 1;
}


/****************************************************************************
**
*F  CloseOutput() . . . . . . . . . . . . . . . . . close current output file
**
**  'CloseOutput' will  first flush all   pending output and  then  close the
**  current  output  file.   Subsequent output will  again go to the previous
**  output file.  'CloseOutput' returns 1 to indicate success.
**
**  'CloseOutput' will  not  close the  initial output file   '*stdout*', and
**  returns 0 if such attempt is made.  This  is  used in 'Error' which calls
**  'CloseOutput' until it returns 0, thereby closing all open output files.
**
**  Calling 'CloseOutput' if the corresponding 'OpenOutput' call failed  will
**  close the current output file, which will lead to very strange behaviour.
**  On the other  hand if you  forget  to call  'CloseOutput' at the end of a
**  'PrintTo' call or an error will not yield much better results.
*/

UInt CloseOutput(TypOutputFile * output)
{
    GAP_ASSERT(output);

    // silently refuse to close the test output file; this is probably an
    // attempt to close *errout* which is silently not opened, so let's
    // silently not close it
    if (IO()->IgnoreStdoutErrout == IO()->Output)
        return 1;

    GAP_ASSERT(output == IO()->Output);

    // refuse to close the initial output file '*stdout*'
#ifdef HPCGAP
    if (output->prev == 0 && output->stream &&
        IO()->DefaultOutputStream == output->stream)
        return 0;
#else
    if (output->prev == 0)
        return 0;
#endif

    // flush output and close the file
    Pr("%c", (Int)'\03', 0);
    if (!output->stream) {
        SyFclose(output->file);
    }

    // revert to previous output file and indicate success
    IO()->Output = output->prev;

    // don't keep GAP objects alive unnecessarily
    output->stream = 0;

    return 1;
}


/****************************************************************************
**
*F * * * * * * * * * * * * * * input functions  * * * * * * * * * * * * * * *
*/



/****************************************************************************
**
*F  SetPrompt( <prompt> ) . . . . . . . . . . . . . set the user input prompt
*/

void SetPrompt(const char * prompt)
{
    if (SyQuiet)
        prompt = "";
    gap_strlcpy(STATE(Prompt), prompt, sizeof(STATE(Prompt)));
}


/****************************************************************************
**
*F  GetLine2( <input>, <buffer>, <length> ) . . . . . . . . get a line, local
*/

static Int GetLine2(TypInputFile * input)
{
    Char * buffer = input->line + 1;
    UInt   length = sizeof(input->line) - 1;

    if ( input->stream ) {
        if (input->sline == 0 ||
            (IS_STRING_REP(input->sline) &&
             GET_LEN_STRING(input->sline) <= input->spos)) {
            // If we are in the process of quitting, we cannot call
            // GAP functions, so we just return EOF.
            if (STATE(UserHasQuit) || STATE(UserHasQUIT)) {
                input->sline = Fail;
            }
            else {
                input->sline = CALL_1ARGS(ReadLineFunc, input->stream);
            }
            input->spos  = 0;
        }
        if ( input->sline == Fail || ! IS_STRING(input->sline) ) {
            return 0;
        }

        ConvString(input->sline);
        /* we now allow that input->sline actually contains several lines,
           e.g., it can be a  string from a string stream  */


        // start position in buffer
        Char *bptr = buffer;
        while (*bptr)
            bptr++;

        // copy piece of input->sline into buffer and adjust counters
        const Char *ptr = CONST_CSTR_STRING(input->sline) + input->spos;
        const Char * const end = CONST_CSTR_STRING(input->sline) + GET_LEN_STRING(input->sline);
        const Char * const bend = buffer + length - 2;
        while (bptr < bend && ptr < end) {
            Char c = *ptr++;

            // ignore CR, so that a Window CR+LF line terminator looks
            // to us the same as a Unix LF line terminator
            if (c == '\r')
                continue;

            *bptr++ = c;

            // check for line end
            if (c == '\n')
                break;
        }
        *bptr = '\0';
        input->spos = ptr - CONST_CSTR_STRING(input->sline);

        /* if input->stream is a string stream, we have to adjust the
           position counter in the stream object as well */

        if (input->isstringstream) {
            ADDR_OBJ(input->stream)[1] = INTOBJ_INT(input->spos);
        }
    }
    else {
        if ( ! SyFgets( buffer, length, input->file ) ) {
            return 0;
        }
    }
    return 1;
}


/****************************************************************************
**
*F  GetLine( <input> ) . . . . . . . . . . . . . . . . . .  get a line, local
**
**  'GetLine'  fetches another  line from  the  input 'Input' into the buffer
**  'Input->line', sets the pointer 'Input->ptr' to the beginning of this
**  buffer and returns the first character from the line.
**
**  If   the input file is  '*stdin*'   or '*errin*' 'GetLine'  first  prints
**  'Prompt', unless it is '*stdin*' and GAP was called with option '-q'.
**
**  If there is an  input logfile in use  and the input  file is '*stdin*' or
**  '*errin*' 'GetLine' echoes the new line to the logfile.
*/

static Char GetLine(TypInputFile * input)
{
    GAP_ASSERT(input);

    // if file is '*stdin*' or '*errin*' print the prompt and flush it
    // if the GAP function `PrintPromptHook' is defined then it is called
    // for printing the prompt
    if (!input->stream) {
        if (input->file == 0 && SyQuiet) {
            Pr("%c", (Int)'\03', 0);
        }
        else if (input->file == 0 || input->file == 2) {
            if (IO()->Output->pos > 0)
                Pr("\n", 0, 0);
            if ( PrintPromptHook )
                 Call0ArgsInNewReader( PrintPromptHook );
            else
                 Pr( "%s%c", (Int)STATE(Prompt), (Int)'\03' );
        }
    }

    // bump the line number
    if (input->ptr > input->line && input->ptr[-1] == '\n') {
        input->number++;
    }

    // initialize 'input->ptr', no errors on this line so far
    input->line[0] = '\0';    // init the pushback buffer
    input->line[1] = '\0';    // empty line buffer
    input->ptr = input->line + 1;
    input->lastErrorLine = 0;

    // try to read a line
    if (!GetLine2(input)) {
        input->ptr[0] = '\377';
        input->ptr[1] = '\0';
    }

    // if necessary echo the line to the logfile
    if (IO()->InputLog != 0 && input->echo == 1)
        if (!(input->ptr[0] == '\377' && input->ptr[1] == '\0'))
            PutLine2(IO()->InputLog, input->ptr, strlen(input->ptr));

    // return the current character
    return *input->ptr;
}


/****************************************************************************
**
*F * * * * * * * * * * * * *  output functions  * * * * * * * * * * * * * * *
*/



/****************************************************************************
**
*F  PutLine2( <output>, <line>, <len> )  . . . . . . . . .print a line, local
**
**  Introduced <len> argument. Actually in all cases where this is called one
**  knows the length of <line>, so it is not necessary to compute it again
**  with the inefficient C- strlen.  (FL)
*/

static void PutLine2(TypOutputFile * output, const Char * line, UInt len)
{
    Obj str;

    if (output->isstringstream) {
        // special handling of string streams, where we can copy directly
        str = CONST_ADDR_OBJ(output->stream)[1];
        ConvString(str);
        AppendCStr(str, line, len);
    }
    else if (output->stream) {
        // delegate to library level
        str = MakeImmStringWithLen(line, len);
        CALL_2ARGS(WriteAllFunc, output->stream, str);
    }
    else {
        SyFputs(line, output->file);
    }
}


/****************************************************************************
**
*F  PutLineTo ( stream, len ) . . . . . . . . . . . . . . print a line, local
**
**  'PutLineTo'  prints the first len characters of the current output
**  line   'stream->line' to <stream>
**  It  is  called from 'PutChrTo'.
**
**  'PutLineTo' also echoes the output line to the logfile 'OutputLog' if
**  'OutputLog' is not 0 and the output file is '*stdout*' or '*errout*'.
**
*/

static void PutLineTo(TypOutputFile * stream, UInt len)
{
  PutLine2( stream, stream->line, len );

  // if necessary echo it to the logfile
  if (IO()->OutputLog != 0 && !stream->stream) {
      if (stream->file == 1 || stream->file == 3) {
          PutLine2(IO()->OutputLog, stream->line, len);
      }
  }
}


/****************************************************************************
**
*F  PutChrTo( <stream>, <ch> )  . . . . . . . . . print character <ch>, local
**
**  'PutChrTo' prints the single character <ch> to the stream <stream>
**
**  'PutChrTo' buffers the output characters until either <ch> is <newline>,
**  <ch> is '\03' (<flush>) or the buffer fills up.
**
**  In the later case 'PutChrTo' has to decide where to split the output
**  line. It takes the point at which $linelength - pos + 8 * indent$ is
**  minimal.
*/


/* helper function to add a hint about a possible line break;
   a triple (pos, value, indent), such that the minimal (value-pos) wins */

static void
addLineBreakHint(TypOutputFile * stream, Int pos, Int val, Int indentdiff)
{
  Int nr, i;
  // find next free slot
  for (nr = 0; nr < MAXHINTS && stream->hints[3*nr] != -1; nr++);
  if (nr == MAXHINTS) {
    // forget the first stored hint
    for (i = 0; i < 3*MAXHINTS - 3; i++)
       stream->hints[i] =  stream->hints[i+3];
    nr--;
  }
  /* if pos is same as before only relevant if new entry has higher
     priority */

  if ( nr > 0 && stream->hints[3*(nr-1)] == pos )
    nr--;

  if ( stream->indent < pos &&
       (stream->hints[3*nr] == -1 || val < stream->hints[3*(nr)+1]) ) {
    stream->hints[3*nr] = pos;
    stream->hints[3*nr+1] = val;
    stream->hints[3*nr+2] = stream->indent;
    stream->hints[3*nr+3] = -1;
  }
  stream->indent += indentdiff;
}
/* helper function to find line break position,
   returns position nr in stream[hints] or -1 if none found */

static Int nrLineBreak(TypOutputFile * stream)
{
  Int nr=-1, min, i;
  for (i = 0, min = INT_MAX; stream->hints[3*i] != -1; i++)
  {
    if (stream->hints[3*i] > 0 &&
        stream->hints[3*i+1] - stream->hints[3*i] <= min)
    {
      nr = i;
      min = stream->hints[3*i+1] - stream->hints[3*i];
    }
  }
  if (min < INT_MAX)
    return nr;
  else
    return -1;
}


static void PutChrTo(TypOutputFile * stream, Char ch)
{
  Int                 i, hint, spos;
  Char                str [MAXLENOUTPUTLINE];


  // '\01', increment indentation level
  if ( ch == '\01' ) {

    if (!stream->format)
      return;

    // add hint to break line
    addLineBreakHint(stream, stream->pos, 16*stream->indent, 1);
  }

  // '\02', decrement indentation level
  else if ( ch == '\02' ) {

    if (!stream->format)
      return;

    // if this is a better place to split the line remember it
    addLineBreakHint(stream, stream->pos, 16*stream->indent, -1);
  }

  // '\03', print line
  else if ( ch == '\03' ) {

    // print the line
    if (stream->pos != 0)
      {
        stream->line[ stream->pos ] = '\0';
        PutLineTo(stream, stream->pos );

        // start the next line
        stream->pos      = 0;
      }
    // reset line break hints
    stream->hints[0] = -1;

  }

  // <newline> or <return>, print line, indent next
  else if ( ch == '\n' || ch == '\r' ) {

    // put the character on the line and terminate it
    stream->line[ stream->pos++ ] = ch;
    stream->line[ stream->pos   ] = '\0';

    // print the line
    PutLineTo( stream, stream->pos );

    // and dump it from the buffer
    stream->pos = 0;
    if (stream->format)
      {
        // indent for next line
        for ( i = 0;  i < stream->indent; i++ )
          stream->line[ stream->pos++ ] = ' ';
      }
    // reset line break hints
    stream->hints[0] = -1;

  }

  // normal character, room on the current line
#ifdef HPCGAP
  /* TODO: For threads other than the main thread, reserve some extra
     space for the thread id indicator. See issue #136. */

  else if (stream->pos <
           SyNrCols - 2 - 6 * (TLS(threadID) != 0) - IO()->NoSplitLine) {
#else
  else if (stream->pos < SyNrCols - 2 - IO()->NoSplitLine) {
#endif

    // put the character on this line
    stream->line[ stream->pos++ ] = ch;

  }

  else
    {
      // position to split
      if ( (hint = nrLineBreak(stream)) != -1 )
        spos = stream->hints[3*hint];
      else
        spos = 0;

      /* if we are going to split at the end of the line, and we are
         formatting discard blanks */

      if ( stream->format && spos == stream->pos && ch == ' ' ) {
        ;
      }

      // full line, acceptable split position
      else if ( stream->format && spos != 0 ) {

        // add character to the line, terminate it
        stream->line[ stream->pos++ ] = ch;
        stream->line[ stream->pos++ ] = '\0';

        // copy the rest after the best split position to a safe place
        for ( i = spos; i < stream->pos; i++ )
          str[ i-spos ] = stream->line[ i ];
        str[ i-spos] = '\0';

        // print line up to the best split position
        stream->line[ spos++ ] = '\n';
        stream->line[ spos   ] = '\0';
        PutLineTo( stream, spos );
        spos--;

        // indent for the rest
        stream->pos = 0;
        for ( i = 0; i < stream->hints[3*hint+2]; i++ )
          stream->line[ stream->pos++ ] = ' ';
        spos -= stream->hints[3*hint+2];

        // copy the rest onto the next line
        for ( i = 0; str[ i ] != '\0'; i++ )
          stream->line[ stream->pos++ ] = str[ i ];
        // recover line break hints for copied rest
        for ( i = hint+1; stream->hints[3*i] != -1; i++ )
        {
          stream->hints[3*(i-hint-1)] = stream->hints[3*i]-spos;
          stream->hints[3*(i-hint-1)+1] = stream->hints[3*i+1];
          stream->hints[3*(i-hint-1)+2] = stream->hints[3*i+2];
        }
        stream->hints[3*(i-hint-1)] = -1;
      }

      // full line, no split position
      else {

        if (stream->format)
          {
            /* append a '\',*/
            stream->line[ stream->pos++ ] = '\\';
            stream->line[ stream->pos++ ] = '\n';
          }
        // and print the line
        stream->line[ stream->pos   ] = '\0';
        PutLineTo( stream, stream->pos );

        // add the character to the next line
        stream->pos = 0;
        stream->line[ stream->pos++ ] = ch;

        if (stream->format)
          stream->hints[0] = -1;
      }

    }
}

/****************************************************************************
**
*F  FuncToggleEcho( )
**
*/

static Obj FuncToggleEcho(Obj self)
{
    IO()->Input->echo = !IO()->Input->echo;
    return (Obj)0;
}

/****************************************************************************
**
*F  FuncCPROMPT( )
**
**  returns the current `Prompt' as GAP string.
*/

static Obj FuncCPROMPT(Obj self)
{
  Obj p;
  p = MakeString(STATE(Prompt));
  return p;
}

/****************************************************************************
**
*F  FuncPRINT_CPROMPT( <prompt> )
**
**  prints current `Prompt' if argument <prompt> is not in StringRep, otherwise
**  uses the content of <prompt> as `Prompt' (at most 80 characters).
**  (important is the flush character without resetting the cursor column)
*/

static Obj FuncPRINT_CPROMPT(Obj self, Obj prompt)
{
  if (IS_STRING_REP(prompt)) {
    /* by assigning to Prompt we also tell readline (if used) what the
       current prompt is  */

    SetPrompt(CONST_CSTR_STRING(prompt));
  }
  Pr("%s%c", (Int)STATE(Prompt), (Int)'\03' );
  return (Obj) 0;
}

void ResetOutputIndent(void)
{
    GAP_ASSERT(IO()->Output);
    IO()->Output->indent = 0;
}



/****************************************************************************
**
*V  AllKeywords
**
*/

static const char * AllKeywords[] = {
    "and",     "atomic",   "break",         "continue""do",     "elif",
    "else",    "end",      "false",         "fi",       "for",    "function",
    "if",      "in",       "local",         "mod",      "not",    "od",
    "or",      "readonly""readwrite",     "rec",      "repeat""return",
    "then",    "true",     "until",         "while",    "quit",   "QUIT",
    "IsBound""Unbind",   "TryNextMethod""Info",     "Assert",
};


/****************************************************************************
**
*F  IsKeyword( )
**
*/

static BOOL IsKeyword(const char * str)
{
    for (UInt i = 0; i < ARRAY_SIZE(AllKeywords); i++) {
        if (streq(str, AllKeywords[i])) {
            return TRUE;
        }
    }
    return FALSE;
}


/****************************************************************************
**
*F  FuncALL_KEYWORDS( )
**
*/

static Obj FuncALL_KEYWORDS(Obj self)
{
    Obj l = NewEmptyPlist();
    for (UInt i = 0; i < ARRAY_SIZE(AllKeywords); i++) {
        Obj s = MakeImmString(AllKeywords[i]);
        ASS_LIST(l, i+1, s);
    }
    SortDensePlist(l);
    SET_FILT_LIST(l, FN_IS_HOMOG);
    SET_FILT_LIST(l, FN_IS_SSORT);
    MakeImmutable(l);
    return l;
}


/****************************************************************************
**
*F  Pr( <format>, <arg1>, <arg2> )  . . . . . . . . .  print formatted output
*F  PrTo( <stream>, <format>, <arg1>, <arg2> )  . . .  print formatted output
**
**  'Pr' is the output function. The first argument is a 'printf' like format
**  string containing   up   to 2  '%'  format   fields,  specifying  how the
**  corresponding arguments are to be  printed.  The two arguments are passed
**  as  'Int'  integers. This assumes that GAP is built on an architecture
**  where each of the C types possibly passed as argument to 'Pr' ('int',
**  'char', pointers) can be converted to 'Int' without loss of information.
**
**  The function 'Pr' currently support the following '%' format  fields:
**  '%c'    the corresponding argument represents a character,  usually it is
**          its ASCII code, and this character is printed.
**  '%s'    the corresponding argument is the address of  a  null  terminated
**          character string which is printed.
**  '%g'    the corresponding argument is the address of a T_STRING string
**          object which is printed. This is similar to using '%s' to print
**          CSTR_STRING(arg), but is safe during garbage collection.
**  '%G'    the corresponding argument is the address of an Obj which points
**          to a string in STRING_REP format which is printed in '%S' format
**  '%C'    the corresponding argument is the address of an Obj which points
**          to a string in STRING_REP format which is printed with C escapes
**  '%d'    the corresponding argument is a signed integer, which is printed.
**          Between the '%' and the 'd' an integer might be used  to  specify
**          the width of a field in which the integer is right justified.  If
**          the first character is '0' 'Pr' pads with '0' instead of <space>.
**  '%I'    print an identifier, given as GAP string in STRING_REP.
**  '%>'    increment the indentation level.
**  '%<'    decrement the indentation level.
**  '%%'    can be used to print a single '%' character. No argument is used.
*/

static inline void FormatOutput(
    void (*put_a_char)(void *state, Char c),
    void *state, const Char *format, Int arg1, Int arg2 )
{
  const Char *        p;
  Obj                 arg1obj;
  Int                 prec,  n;
  Char                fill;

  // loop over the characters of the <format> string
  for ( p = format; *p != '\0'; p++ ) {

    // not a '%' character, simply print it
    if ( *p != '%' ) {
      put_a_char(state, *p);
      continue;
    }

    // if the character is '%' do something special

    // first look for a precision field
    p++;
    prec = 0;
    fill = (*p == '0' ? '0' : ' ');
    while ( IsDigit(*p) ) {
      prec = 10 * prec + *p - '0';
      p++;
    }

    // handle the case of a missing argument
    if (arg1 == 0 && (*p == 's' || *p == 'S' || *p == 'C' || *p == 'I')) {
      put_a_char(state, '<');
      put_a_char(state, 'n');
      put_a_char(state, 'u');
      put_a_char(state, 'l');
      put_a_char(state, 'l');
      put_a_char(state, '>');

      // on to the next argument
      arg1 = arg2;
    }

    // '%d' print an integer
    else if (*p == 'd') {
      int is_neg = (arg1 < 0);
      if ( is_neg ) {
        arg1 = -arg1;
        prec--; // we loose one digit of output precision for the minus sign
      }

      // compute how many characters this number requires
      for ( n = 1; n <= arg1/10; n*=10 ) {
        prec--;
      }
      while ( --prec > 0 )  put_a_char(state, fill);

      if ( is_neg ) {
        put_a_char(state, '-');
      }

      for ( ; n > 0; n /= 10 )
        put_a_char(state, (Char)(((arg1/n)%10) + '0') );

      // on to the next argument
      arg1 = arg2;
    }

    // '%s' or '%g' print a string
    else if ( *p == 's' || *p == 'g') {
      UInt len;

      // If arg is a GAP obj, get out the contained string, and
      // set arg1obj so we can re-evaluate after any possible GC
      // which occurs in put_a_char
      if (*p == 'g') {
        arg1obj = (Obj)arg1;
        arg1 = (Int)CONST_CSTR_STRING(arg1obj);
        len = GET_LEN_STRING(arg1obj);
      }
      else {
        arg1obj = 0;
        len = strlen((const Char *)arg1);
      }

      // compute how many characters this identifier requires
      if (len >= prec)
        prec = 0;
      else
        prec -= len;

      // if wanted push an appropriate number of <space>-s
      while ( prec-- > 0 )  put_a_char(state, ' ');

      if (arg1obj) {
        arg1 = (Int)CONST_CSTR_STRING(arg1obj);
      }

      // print the string
      /* must be careful that line breaks don't go inside
         escaped sequences \n or \123 or similar */

      for (UInt i = 0; i < len; i++) {
        const Char* q = ((const Char *)arg1) + i;
        // skip null bytes; this means 'prec' is off in this case. Fixing this
        // is not worth the effort. So instead, new rule: if you print strings
        // with null bytes, behavior of 'prec' is undefined.
        if (*q == '\0')
          continue;
        if (*q == '\\' && IO()->NoSplitLine == 0) {
          if (*(q + 1) < '8' && *(q + 1) >= '0')
            IO()->NoSplitLine = 3;
          else
            IO()->NoSplitLine = 1;
        }
        else if (IO()->NoSplitLine > 0)
          IO()->NoSplitLine--;
        put_a_char(state, *q);

        if (arg1obj) {
          arg1 = (Int)CONST_CSTR_STRING(arg1obj);
        }
      }

      // on to the next argument
      arg1 = arg2;
    }

    // '%C' print a string with the necessary C escapes
    else if ( *p == 'C' ) {

      arg1obj = (Obj)arg1;
      arg1 = (Int)CONST_CSTR_STRING(arg1obj);

      // compute how many characters this identifier requires
      for ( const Char * q = (const Char *)arg1; *q != '\0' && prec > 0; q++ ) {
        if      ( *q == '\n'  ) { prec -= 2; }
        else if ( *q == '\t'  ) { prec -= 2; }
        else if ( *q == '\r'  ) { prec -= 2; }
        else if ( *q == '\b'  ) { prec -= 2; }
        else if ( *q == '\01' ) { prec -= 3; }
        else if ( *q == '\02' ) { prec -= 3; }
        else if ( *q == '\03' ) { prec -= 3; }
        else if ( *q == '"'   ) { prec -= 2; }
        else if ( *q == '\\'  ) { prec -= 2; }
        else                    { prec -= 1; }
      }

      // if wanted push an appropriate number of <space>-s
      while ( prec-- > 0 )  put_a_char(state, ' ');

      // print the string
      Int i = 0;
      while (1) {
        const Char* q = CONST_CSTR_STRING(arg1obj) + i++;
        if (*q == 0)
            break;

        if      ( *q == '\n'  ) { put_a_char(state, '\\'); put_a_char(state, 'n');  }
        else if ( *q == '\t'  ) { put_a_char(state, '\\'); put_a_char(state, 't');  }
        else if ( *q == '\r'  ) { put_a_char(state, '\\'); put_a_char(state, 'r');  }
        else if ( *q == '\b'  ) { put_a_char(state, '\\'); put_a_char(state, 'b');  }
        else if ( *q == '\01' ) { put_a_char(state, '\\'); put_a_char(state, '0');
                                  put_a_char(state, '1');                }
        else if ( *q == '\02' ) { put_a_char(state, '\\'); put_a_char(state, '0');
                                  put_a_char(state, '2');                }
        else if ( *q == '\03' ) { put_a_char(state, '\\'); put_a_char(state, '0');
                                  put_a_char(state, '3');                }
        else if ( *q == '"'   ) { put_a_char(state, '\\'); put_a_char(state, '"');  }
        else if ( *q == '\\'  ) { put_a_char(state, '\\'); put_a_char(state, '\\'); }
        else                    { put_a_char(state, *q);               }
      }

      // on to the next argument
      arg1 = arg2;
    }

    // '%I' print an identifier
    else if (*p == 'I') {
      BOOL found_keyword = FALSE;

      arg1obj = (Obj)arg1;
      arg1 = (Int)CONST_CSTR_STRING(arg1obj);

      // check if q matches a keyword
      found_keyword = IsKeyword((const Char *)arg1);

      // compute how many characters this identifier requires
      if (found_keyword) {
        prec--;
      }
      for ( const Char * q = (const Char *)arg1; *q != '\0'; q++ ) {
        if ( !IsIdent(*q) ) {
          prec--;
        }
        prec--;
      }

      // if wanted push an appropriate number of <space>-s
      while ( prec-- > 0 ) { put_a_char(state, ' '); }

      // print the identifier
      if ( found_keyword ) {
        put_a_char(state, '\\');
      }

      for ( Int i = 0; ((const Char *)arg1)[i] != '\0'; i++ ) {
        Char c = ((const Char *)arg1)[i];

        if ( !IsIdent(c) ) {
          put_a_char(state, '\\');
        }
        put_a_char(state, c);
        arg1 = (Int)CONST_CSTR_STRING(arg1obj);
      }

      // on to the next argument
      arg1 = arg2;
    }

    // '%c' print a character
    else if ( *p == 'c' ) {
      put_a_char(state, (Char)arg1);
      arg1 = arg2;
    }

    // '%%' print a '%' character
    else if ( *p == '%' ) {
      put_a_char(state, '%');
    }

    // '%>' increment the indentation level
    else if ( *p == '>' ) {
      put_a_char(state, '\01');
      while ( --prec > 0 )
        put_a_char(state, '\01');
    }

    // '%<' decrement the indentation level
    else if ( *p == '<' ) {
      put_a_char(state, '\02');
      while ( --prec > 0 )
        put_a_char(state, '\02');
    }

    // else raise an error
    else {
      for ( p = "%format error"; *p != '\0'; p++ )
        put_a_char(state, *p);
    }

  }

}

static void putToTheStream(void *state, Char c)
{
    PutChrTo((TypOutputFile *)state, c);
}

static void
PrTo(TypOutputFile * stream, const Char * format, Int arg1, Int arg2)
{
    FormatOutput( putToTheStream, stream, format, arg1, arg2);
}

void Pr (
         const Char *      format,
         Int                 arg1,
         Int                 arg2 )
{
#ifdef HPCGAP
    if (!IO()->Output) {
        OpenDefaultOutput(&IO()->DefaultOutput);
    }
#endif
    PrTo(IO()->Output, format, arg1, arg2);
}

typedef struct {
    Char * TheBuffer;
    UInt   TheCount;
    UInt   TheLimit;
} BufferState;

static void putToTheBuffer(void *state, Char c)
{
  BufferState *buf = (BufferState *)state;
  if (buf->TheCount < buf->TheLimit)
    buf->TheBuffer[buf->TheCount++] = c;
}

void SPrTo(Char *buffer, UInt maxlen, const Char *format, Int arg1, Int arg2)
{
  BufferState buf = { buffer, 0, maxlen };
  FormatOutput(putToTheBuffer, &buf, format, arg1, arg2);
  putToTheBuffer(&buf, '\0');
}


static Obj FuncINPUT_FILENAME(Obj self)
{
    if (IO()->Input == 0)
        return MakeImmString("*defin*");

    UInt gapnameid = GetInputFilenameID(IO()->Input);
    return GetCachedFilename(gapnameid);
}

static Obj FuncINPUT_LINENUMBER(Obj self)
{
    return INTOBJ_INT(IO()->Input ? IO()->Input->number : 0);
}

static Obj FuncSET_PRINT_FORMATTING_STDOUT(Obj self, Obj val)
{
    BOOL format = (val != False);
    TypOutputFile * output = IO()->Output;
    while (output) {
        if (!output->stream && output->file == 1)
            output->format = format;
        output = output->prev;
    }
    IO()->PrintFormattingForStdout = format;
    return 0;
}

static Obj FuncPRINT_FORMATTING_STDOUT(Obj self)
{
    return IO()->PrintFormattingForStdout ? True : False;
}

static Obj FuncSET_PRINT_FORMATTING_ERROUT(Obj self, Obj val)
{
    BOOL format = (val != False);
    TypOutputFile * output = IO()->Output;
    while (output) {
        if (!output->stream && output->file == 3)
            output->format = format;
        output = output->prev;
    }
    IO()->PrintFormattingForErrout = format;
    return 0;
}

static Obj FuncPRINT_FORMATTING_ERROUT(Obj self)
{
    return IO()->PrintFormattingForErrout ? True : False;
}

/****************************************************************************
**
*F  FuncCALL_WITH_FORMATTING_STATUS( <status>, <func>, <args> )
**
**  Temporarily set the formatting status of the active output stream to
**  <status>, then call the function <func> with the arguments in <args>.
*/

static Obj FuncCALL_WITH_FORMATTING_STATUS(Obj self, Obj status, Obj func, Obj args)
{
    RequireTrueOrFalse(SELF_NAME, status);
    RequireSmallList(SELF_NAME, args);

    TypOutputFile * output = IO()->Output;
    if (!output)
        ErrorMayQuit("CALL_WITH_FORMATTING_STATUS called while no output is open", 0, 0);

    BOOL old = output->format;
    output->format = (status != False);

    Obj result;
    GAP_TRY
    {
        result = CallFuncList(func, args);
    }
    GAP_CATCH
    {
        output->format = old;
        GAP_THROW();
    }

    output->format = old;
    return result;
}

static Obj FuncIS_INPUT_TTY(Obj self)
{
    GAP_ASSERT(IO()->Input);
    if (IO()->Input->stream)
        return False;
    return SyBufIsTTY(IO()->Input->file) ? True : False;
}

static Obj FuncIS_OUTPUT_TTY(Obj self)
{
    GAP_ASSERT(IO()->Output);
    if (IO()->Output->stream)
        return False;
    return SyBufIsTTY(IO()->Output->file) ? True : False;
}

static Obj FuncGET_FILENAME_CACHE(Obj self)
{
  return CopyObj(FilenameCache, 1);
}

static StructGVarFunc GVarFuncs [] = {

    GVAR_FUNC_0ARGS(ToggleEcho),
    GVAR_FUNC_0ARGS(CPROMPT),
    GVAR_FUNC_1ARGS(PRINT_CPROMPT, prompt),
    GVAR_FUNC_0ARGS(ALL_KEYWORDS),
    GVAR_FUNC_0ARGS(INPUT_FILENAME),
    GVAR_FUNC_0ARGS(INPUT_LINENUMBER),
    GVAR_FUNC_1ARGS(SET_PRINT_FORMATTING_STDOUT, format),
    GVAR_FUNC_0ARGS(PRINT_FORMATTING_STDOUT),
    GVAR_FUNC_1ARGS(SET_PRINT_FORMATTING_ERROUT, format),
    GVAR_FUNC_0ARGS(PRINT_FORMATTING_ERROUT),
    GVAR_FUNC_3ARGS(CALL_WITH_FORMATTING_STATUS, status, func, args),
    GVAR_FUNC_0ARGS(IS_INPUT_TTY),
    GVAR_FUNC_0ARGS(IS_OUTPUT_TTY),
    GVAR_FUNC_0ARGS(GET_FILENAME_CACHE),
    { 0, 0, 0, 0, 0 }

};

/****************************************************************************
**
*F  InitLibrary( <module> ) . . . . . . .  initialise library data structures
*/

static Int InitLibrary (
    StructInitInfo *    module )
{
    InitSymbolTableLibrary(&FilenameSymbolTable, 7079);
    FilenameCache = NEW_PLIST(T_PLIST, 0);

    // init filters and functions
    InitGVarFuncsFromTable( GVarFuncs );

    return 0;
}

static Int InitKernel (
    StructInitInfo *    module )
{
    IO()->Input = 0;
    IO()->Output = 0;
    IO()->InputLog = 0;
    IO()->OutputLog = 0;
    IO()->PrintFormattingForStdout = TRUE;
    IO()->PrintFormattingForErrout = TRUE;

    OpenOutput(&IO()->DefaultOutput, "*stdout*"FALSE);

    InitSymbolTableKernel(&FilenameSymbolTable, "FilenameSymbolCount",
                          "FilenameSymbolTable", GetCachedFilename,
                          AddCachedFilename);

    InitGlobalBag( &FilenameCache, "FilenameCache" );

#ifdef HPCGAP
    // Initialize default stream functions
    DeclareGVar(&DEFAULT_INPUT_STREAM, "DEFAULT_INPUT_STREAM");
    DeclareGVar(&DEFAULT_OUTPUT_STREAM, "DEFAULT_OUTPUT_STREAM");

#else
    // register global bags with the garbage collector
    InitGlobalBag(&(IO()->InputLogFileOrStream.stream),
                  "src/io.c:InputLogFileOrStream");
    InitGlobalBag(&(IO()->OutputLogFileOrStream.stream),
                  "src/io.c:OutputLogFileOrStream");
#endif

    // import functions from the library
    ImportFuncFromLibrary( "ReadLine", &ReadLineFunc );
    ImportFuncFromLibrary( "WriteAll", &WriteAllFunc );
    ImportFuncFromLibrary( "IsInputTextStringRep", &IsInputStringStream );
    ImportFuncFromLibrary( "IsOutputTextStringRep", &IsOutputStringStream );
    ImportFuncFromLibrary( "PositionStream", &PositionStream );
    ImportFuncFromLibrary( "SeekPositionStream", &SeekPositionStream );
    InitCopyGVar( "PrintPromptHook", &PrintPromptHook );
    InitFopyGVar( "PrintFormattingStatus", &PrintFormattingStatus);
    InitFopyGVar( "SetPrintFormattingStatus", &SetPrintFormattingStatus);

    InitHdlrFuncsFromTable( GVarFuncs );
    return 0;
}

/****************************************************************************
**
*F  InitInfoIO() . . . . . . . . . . . . . . . . table of init functions
*/

static StructInitInfo module = {
    // init struct using C99 designated initializers; for a full list of
    // fields, please refer to the definition of StructInitInfo
    .type = MODULE_BUILTIN,
    .name = "io",
    .initKernel = InitKernel,
    .initLibrary = InitLibrary,

    .moduleStateSize = sizeof(struct IOModuleState),
    .moduleStateOffsetPtr = &IOStateOffset,
};

StructInitInfo * InitInfoIO ( void )
{
    return &module;
}

90%


¤ Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.0.38Bemerkung:  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung ist noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge