/**************************************************************************** ** ** 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.
*/
// '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;
/**************************************************************************** ** *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;
// for debugging from GDB / lldb, we mark this as extern inline externinlinestruct IOModuleState * IO(void)
{ return (struct IOModuleState *)StateSlotsAtOffset(IOStateOffset);
}
/**************************************************************************** ** *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;
} elseif (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);
}
constChar * GetInputLineBuffer(TypInputFile * input)
{
GAP_ASSERT(input); // first byte of Input->line is reserved for the pushback buffer, so add 1 return input->line + 1;
}
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, constChar * 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;
// 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);
}
/**************************************************************************** ** *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 ( constChar * 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;
/**************************************************************************** ** *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 ( constChar * 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 ( constChar * 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, constChar * 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; elseif (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.
*/
// 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;
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 constChar *ptr = CONST_CSTR_STRING(input->sline) + input->spos; constChar * const end = CONST_CSTR_STRING(input->sline) + GET_LEN_STRING(input->sline); constChar * 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.
*/ staticChar 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);
} elseif (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 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)
*/ staticvoid PutLine2(TypOutputFile * output, constChar * 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);
} elseif (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*'. **
*/ staticvoid 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 */ staticvoid
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 */ staticInt 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;
}
staticvoid PutChrTo(TypOutputFile * stream, Char ch)
{ Int i, hint, spos; Char str [MAXLENOUTPUTLINE];
// if this is a better place to split the line remember it
addLineBreakHint(stream, stream->pos, 16*stream->indent, -1);
}
// '\03', print line elseif ( 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 elseif ( 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. */ elseif (stream->pos <
SyNrCols - 2 - 6 * (TLS(threadID) != 0) - IO()->NoSplitLine) { #else elseif (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 elseif ( 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;
/**************************************************************************** ** *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;
}
/**************************************************************************** ** *F IsKeyword( ) **
*/ staticBOOL IsKeyword(constchar * str)
{ for (UInt i = 0; i < ARRAY_SIZE(AllKeywords); i++) { if (streq(str, AllKeywords[i])) { returnTRUE;
}
} returnFALSE;
}
/**************************************************************************** ** *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.
*/ staticinlinevoid FormatOutput( void (*put_a_char)(void *state, Char c), void *state, constChar *format, Int arg1, Int arg2 )
{ constChar * 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 elseif (*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 elseif ( *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((constChar *)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++) { constChar* q = ((constChar *)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;
} elseif (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 elseif ( *p == 'C' ) {
// else raise an error else { for ( p = "%format error"; *p != '\0'; p++ )
put_a_char(state, *p);
}
}
}
staticvoid putToTheStream(void *state, Char c)
{
PutChrTo((TypOutputFile *)state, c);
}
staticvoid
PrTo(TypOutputFile * stream, constChar * format, Int arg1, Int arg2)
{
FormatOutput( putToTheStream, stream, format, arg1, arg2);
}
void Pr ( constChar * format, Int arg1, Int arg2 )
{ #ifdef HPCGAP if (!IO()->Output) {
OpenDefaultOutput(&IO()->DefaultOutput);
} #endif
PrTo(IO()->Output, format, arg1, arg2);
}
/**************************************************************************** ** *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);
#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
¤ 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.45Bemerkung:
(vorverarbeitet)
¤
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.