1 /****************************************************************************
2 **
3 **  This file is part of GAP, a system for computational discrete algebra.
4 **
5 **  Copyright of GAP belongs to its developers, whose names are too numerous
6 **  to list here. Please refer to the COPYRIGHT file for details.
7 **
8 **  SPDX-License-Identifier: GPL-2.0-or-later
9 **
10 **  This file contains functions responsible for input and output processing.
11 **
12 **  These provide the concept of  a current input  and output file.   In the
13 **  main   module   they are opened  and   closed  with the  'OpenInput'  and
14 **  'CloseInput' respectively  'OpenOutput' and 'CloseOutput' calls.  All the
15 **  other modules just read from the  current input  and write to the current
16 **  output file.
17 **
18 **  This module relies on the functions  provided  by  the  operating  system
19 **  dependent module 'system.c' for the low level input/output.
20 */
21 
22 #include "io.h"
23 
24 #include "bool.h"
25 #include "calls.h"
26 #include "error.h"
27 #include "gapstate.h"
28 #include "gaputils.h"
29 #include "gvars.h"
30 #include "lists.h"
31 #include "modules.h"
32 #include "plist.h"
33 #include "read.h"
34 #include "scanner.h"
35 #include "stringobj.h"
36 #include "sysfiles.h"
37 #include "sysopt.h"
38 
39 #ifdef HPCGAP
40 #include "hpc/aobjects.h"
41 #endif
42 
43 
44 /****************************************************************************
45 **
46 *T  TypInputFile  . . . . . . . . . .  structure of an open input file, local
47 **
48 **  'TypInputFile' describes the  information stored  for  open input  files:
49 **
50 **  'isstream' is 'true' if input come from a stream.
51 **
52 **  'file'  holds the  file identifier  which  is received from 'SyFopen' and
53 **  which is passed to 'SyFgets' and 'SyFclose' to identify this file.
54 **
55 **  'name' is the name of the file, this is only used in error messages.
56 **
57 **  'line' is a  buffer that holds the  current input  line.  This is  always
58 **  terminated by the character '\0'.  Because 'line' holds  only part of the
59 **  line for very long lines the last character need not be a <newline>.
60 **
61 **  'stream' is non-zero if the input points to a stream.
62 **
63 **  'sline' contains the next line from the stream as GAP string.
64 **
65 **  The following variables are used to store the state of the interpreter
66 **  and stream when another input file is opened:
67 **
68 **  'ptr' points to the current character within that line.  This is not used
69 **  for the current input file, where 'In' points to the  current  character.
70 **
71 **  'number' is the number of the current line, is used in error messages.
72 **
73 **  'interpreterStartLine' is the number of the line where the fragment of
74 **  code currently being interpreted started. This is used for profiling
75 **
76 **
77 */
78 typedef struct {
79     UInt   isstream;
80     Int    file;
81     Char   name[256];
82     UInt   gapnameid;
83     Char   line[32768];
84     Char * ptr;
85     UInt   symbol;
86     Int    interpreterStartLine;
87     Int    number;
88     Obj    stream;
89     UInt   isstringstream;
90     Obj    sline;
91     Int    spos;
92     UInt   echo;
93 } TypInputFile;
94 
95 
96 /****************************************************************************
97 **
98 *T  TypOutputFiles  . . . . . . . . . structure of an open output file, local
99 **
100 **  'TypOutputFile' describes the information stored for open  output  files:
101 **  'file' holds the file identifier which is  received  from  'SyFopen'  and
102 **  which is passed to  'SyFputs'  and  'SyFclose'  to  identify  this  file.
103 **  'line' is a buffer that holds the current output line.
104 **  'pos' is the position of the current character on that line.
105 */
106 /* the maximal number of used line break hints */
107 #define MAXHINTS 100
108 typedef struct {
109     UInt isstream;
110     UInt isstringstream;
111     Int  file;
112     Char line[MAXLENOUTPUTLINE];
113     Int  pos;
114     Int  format;
115     Int  indent;
116 
117     /* each hint is a tripel (position, value, indent) */
118     Int hints[3 * MAXHINTS + 1];
119     Obj stream;
120 } TypOutputFile;
121 
122 
123 static Char GetLine(void);
124 static void PutLine2(TypOutputFile * output, const Char * line, UInt len);
125 
126 static Obj ReadLineFunc;
127 static Obj WriteAllFunc;
128 static Obj IsStringStream;
129 static Obj PrintPromptHook = 0;
130 Obj EndLineHook = 0;
131 static Obj PrintFormattingStatus;
132 
133 /****************************************************************************
134 **
135 *V  FilenameCache . . . . . . . . . . . . . . . . . . list of filenames
136 **
137 **  'FilenameCache' is a list of previously opened filenames.
138 */
139 static Obj FilenameCache;
140 
141 /* TODO: Eliminate race condition in HPC-GAP */
142 static Char promptBuf[81];
143 
144 static ModuleStateOffset IOStateOffset = -1;
145 
146 enum {
147     MAX_OPEN_FILES = 16,
148 };
149 
150 struct IOModuleState {
151 
152     // The stack of the open input files
153     TypInputFile * InputStack[MAX_OPEN_FILES];
154     int            InputStackPointer;
155 
156     // The stack of open output files
157     TypOutputFile * OutputStack[MAX_OPEN_FILES];
158     int             OutputStackPointer;
159 
160     // A pointer to the current input file. It points to the top of the stack
161     // 'InputFiles'.
162     TypInputFile * Input;
163 
164     // A pointer to the current output file. It points to the top of the
165     // stack 'OutputFiles'.
166     TypOutputFile * Output;
167 
168     //
169     TypOutputFile * IgnoreStdoutErrout;
170 
171 
172     // The file identifier of the current input logfile. If it is not 0 the
173     // scanner echoes all input from the files '*stdin*' and '*errin*' to
174     // this file.
175     TypOutputFile * InputLog;
176 
177     // The file identifier of the current output logfile. If it is not 0 the
178     // scanner echoes all output to the files '*stdout*' and '*errout*' to
179     // this file.
180     TypOutputFile * OutputLog;
181 
182     TypOutputFile InputLogFileOrStream;
183     TypOutputFile OutputLogFileOrStream;
184 
185     Int NoSplitLine;
186 
187     Char   Pushback;
188     Char * RealIn;
189 };
190 
191 // for debugging from GDB / lldb, we mark this as extern inline
IO(void)192 extern inline struct IOModuleState * IO(void)
193 {
194     return (struct IOModuleState *)StateSlotsAtOffset(IOStateOffset);
195 }
196 
LockCurrentOutput(Int lock)197 void LockCurrentOutput(Int lock)
198 {
199     IO()->IgnoreStdoutErrout = lock ? IO()->Output : NULL;
200 }
201 
202 
203 /****************************************************************************
204 **
205 *F  GET_NEXT_CHAR()  . . . . . . . . . . . . .  get the next character, local
206 **
207 **  'GET_NEXT_CHAR' returns the next character from  the current input file.
208 **  This character is afterwards also available as '*In'.
209 */
210 
211 
IS_CHAR_PUSHBACK_EMPTY(void)212 static inline Int IS_CHAR_PUSHBACK_EMPTY(void)
213 {
214     return STATE(In) != &IO()->Pushback;
215 }
216 
GET_NEXT_CHAR(void)217 Char GET_NEXT_CHAR(void)
218 {
219     if (STATE(In) == &IO()->Pushback) {
220         STATE(In) = IO()->RealIn;
221     }
222     else
223         STATE(In)++;
224 
225     // handle line continuation, i.e., backslash followed by new line; and
226     // also the case when we run out of buffered data
227     while (*STATE(In) == '\\' || *STATE(In) == 0) {
228 
229         // if we run out of data, get more, and try again
230         if (*STATE(In) == 0) {
231             GetLine();
232             continue;
233         }
234 
235         // we have seen a backslash; so check now if it starts a
236         // line continuation, i.e., whether it is followed by a line terminator
237         if (STATE(In)[1] == '\n') {
238             // LF is the line terminator used in Unix and its relatives
239             STATE(In) += 2;
240         }
241         else if (STATE(In)[1] == '\r' && STATE(In)[2] == '\n') {
242             // CR+LF is the line terminator used by Windows
243             STATE(In) += 3;
244         }
245         else {
246             // if we see a backlash without a line terminator after it, stop
247             break;
248         }
249 
250         // if we get here, we saw a line continuation; change the prompt to a
251         // partial prompt from now on
252         STATE(Prompt) = SyQuiet ? "" : "> ";
253     }
254 
255     return *STATE(In);
256 }
257 
258 // GET_NEXT_CHAR_NO_LC is like GET_NEXT_CHAR, but does not handle
259 // line continuations. This is used when skipping to the end of the
260 // current line, when handling comment lines.
GET_NEXT_CHAR_NO_LC(void)261 Char GET_NEXT_CHAR_NO_LC(void)
262 {
263     if (STATE(In) == &IO()->Pushback) {
264         STATE(In) = IO()->RealIn;
265     }
266     else
267         STATE(In)++;
268 
269     if (!*STATE(In))
270         GetLine();
271 
272     return *STATE(In);
273 }
274 
PEEK_NEXT_CHAR(void)275 Char PEEK_NEXT_CHAR(void)
276 {
277     assert(IS_CHAR_PUSHBACK_EMPTY());
278 
279     // store the current character
280     IO()->Pushback = *STATE(In);
281 
282     // read next character
283     GET_NEXT_CHAR();
284 
285     // fake insert the previous character
286     IO()->RealIn = STATE(In);
287     STATE(In) = &IO()->Pushback;
288     return *IO()->RealIn;
289 }
290 
PEEK_CURR_CHAR(void)291 Char PEEK_CURR_CHAR(void)
292 {
293     return *STATE(In);
294 }
295 
SKIP_TO_END_OF_LINE(void)296 void SKIP_TO_END_OF_LINE(void)
297 {
298     Char c = *STATE(In);
299     while (c != '\n' && c != '\r' && c != '\377')
300         c = GET_NEXT_CHAR_NO_LC();
301 }
302 
303 
GetInputFilename(void)304 const Char * GetInputFilename(void)
305 {
306     GAP_ASSERT(IO()->Input);
307     return IO()->Input->name;
308 }
309 
GetInputLineNumber(void)310 Int GetInputLineNumber(void)
311 {
312     GAP_ASSERT(IO()->Input);
313     return IO()->Input->number;
314 }
315 
GetInputLineBuffer(void)316 const Char * GetInputLineBuffer(void)
317 {
318     GAP_ASSERT(IO()->Input);
319     return IO()->Input->line;
320 }
321 
322 // Get current line position. In the case where we pushed back the last
323 // character on the previous line we return the first character of the
324 // current line, as we cannot retrieve the previous line.
GetInputLinePosition(void)325 Int GetInputLinePosition(void)
326 {
327     if (STATE(In) == &IO()->Pushback) {
328         // Subtract 2 as a value was pushed back
329         Int pos = IO()->RealIn - IO()->Input->line - 2;
330         if (pos < 0)
331             pos = 0;
332         return pos;
333     }
334     else {
335         return STATE(In) - IO()->Input->line - 1;
336     }
337 }
338 
GetInputFilenameID(void)339 UInt GetInputFilenameID(void)
340 {
341     GAP_ASSERT(IO()->Input);
342     UInt gapnameid = IO()->Input->gapnameid;
343     if (gapnameid == 0) {
344         Obj filename = MakeImmString(GetInputFilename());
345 #ifdef HPCGAP
346         // TODO/FIXME: adjust this code to work more like the corresponding
347         // code below for GAP?!?
348         gapnameid = AddAList(FilenameCache, filename);
349 #else
350         Obj pos = POS_LIST(FilenameCache, filename, INTOBJ_INT(1));
351         if (pos == Fail) {
352             gapnameid = PushPlist(FilenameCache, filename);
353         }
354         else {
355             gapnameid = INT_INTOBJ(pos);
356         }
357 #endif
358         IO()->Input->gapnameid = gapnameid;
359     }
360     return gapnameid;
361 }
362 
GetCachedFilename(UInt id)363 Obj GetCachedFilename(UInt id)
364 {
365     return ELM_LIST(FilenameCache, id);
366 }
367 
368 
369 /****************************************************************************
370 **
371 *F * * * * * * * * * * * open input/output functions  * * * * * * * * * * * *
372 */
373 
374 #if !defined(HPCGAP)
375 static TypInputFile  InputFiles[MAX_OPEN_FILES];
376 static TypOutputFile OutputFiles[MAX_OPEN_FILES];
377 #endif
378 
PushNewInput(void)379 static TypInputFile * PushNewInput(void)
380 {
381     GAP_ASSERT(IO()->InputStackPointer < MAX_OPEN_FILES);
382     const int sp = IO()->InputStackPointer++;
383 #ifdef HPCGAP
384     if (!IO()->InputStack[sp]) {
385         IO()->InputStack[sp] = AllocateMemoryBlock(sizeof(TypInputFile));
386     }
387 #endif
388     GAP_ASSERT(IO()->InputStack[sp]);
389     return IO()->InputStack[sp];
390 }
391 
PushNewOutput(void)392 static TypOutputFile * PushNewOutput(void)
393 {
394     GAP_ASSERT(IO()->OutputStackPointer < MAX_OPEN_FILES);
395     const int sp = IO()->OutputStackPointer++;
396 #ifdef HPCGAP
397     if (!IO()->OutputStack[sp]) {
398         IO()->OutputStack[sp] = AllocateMemoryBlock(sizeof(TypOutputFile));
399     }
400 #endif
401     GAP_ASSERT(IO()->OutputStack[sp]);
402     return IO()->OutputStack[sp];
403 }
404 
405 #ifdef HPCGAP
406 static GVarDescriptor DEFAULT_INPUT_STREAM;
407 static GVarDescriptor DEFAULT_OUTPUT_STREAM;
408 
OpenDefaultInput(void)409 static UInt OpenDefaultInput(void)
410 {
411   Obj func, stream;
412   stream = TLS(DefaultInput);
413   if (stream)
414       return OpenInputStream(stream, 0);
415   func = GVarOptFunction(&DEFAULT_INPUT_STREAM);
416   if (!func)
417     return OpenInput("*stdin*");
418   stream = CALL_0ARGS(func);
419   if (!stream)
420     ErrorQuit("DEFAULT_INPUT_STREAM() did not return a stream", 0L, 0L);
421   if (IsStringConv(stream))
422     return OpenInput(CONST_CSTR_STRING(stream));
423   TLS(DefaultInput) = stream;
424   return OpenInputStream(stream, 0);
425 }
426 
OpenDefaultOutput(void)427 static UInt OpenDefaultOutput(void)
428 {
429   Obj func, stream;
430   stream = TLS(DefaultOutput);
431   if (stream)
432     return OpenOutputStream(stream);
433   func = GVarOptFunction(&DEFAULT_OUTPUT_STREAM);
434   if (!func)
435     return OpenOutput("*stdout*");
436   stream = CALL_0ARGS(func);
437   if (!stream)
438     ErrorQuit("DEFAULT_OUTPUT_STREAM() did not return a stream", 0L, 0L);
439   if (IsStringConv(stream))
440     return OpenOutput(CONST_CSTR_STRING(stream));
441   TLS(DefaultOutput) = stream;
442   return OpenOutputStream(stream);
443 }
444 #endif
445 
446 
447 /****************************************************************************
448 **
449 *F  OpenInput( <filename> ) . . . . . . . . . .  open a file as current input
450 **
451 **  'OpenInput' opens  the file with  the name <filename>  as  current input.
452 **  All  subsequent input will  be taken from that  file, until it is  closed
453 **  again  with 'CloseInput'  or  another file  is opened  with  'OpenInput'.
454 **  'OpenInput'  will not  close the  current  file, i.e., if  <filename>  is
455 **  closed again, input will again be taken from the current input file.
456 **
457 **  'OpenInput'  returns 1 if  it   could  successfully open  <filename>  for
458 **  reading and 0  to indicate  failure.   'OpenInput' will fail if  the file
459 **  does not exist or if you do not have permissions to read it.  'OpenInput'
460 **  may  also fail if  you have too  many files open at once.   It  is system
461 **  dependent how many are  too many, but  16  files should  work everywhere.
462 **
463 **  Directely after the 'OpenInput' call the variable  'Symbol' has the value
464 **  'S_ILLEGAL' to indicate that no symbol has yet been  read from this file.
465 **  The first symbol is read by 'Read' in the first call to 'Match' call.
466 **
467 **  You can open  '*stdin*' to  read  from the standard  input file, which is
468 **  usually the terminal, or '*errin*' to  read from the standard error file,
469 **  which  is  the  terminal  even if '*stdin*'  is  redirected from  a file.
470 **  'OpenInput' passes those  file names  to  'SyFopen' like any other  name,
471 **  they are  just  a  convention between the  main  and the system  package.
472 **  'SyFopen' and thus 'OpenInput' will  fail to open  '*errin*' if the  file
473 **  'stderr'  (Unix file  descriptor  2)  is  not a  terminal,  because  of a
474 **  redirection say, to avoid that break loops take their input from a file.
475 **
476 **  It is not neccessary to open the initial input  file, 'InitScanner' opens
477 **  '*stdin*' for  that purpose.  This  file on   the other   hand  cannot be
478 **  closed by 'CloseInput'.
479 */
OpenInput(const Char * filename)480 UInt OpenInput (
481     const Char *        filename )
482 {
483     Int                 file;
484 
485     /* fail if we can not handle another open input file                   */
486     if (IO()->InputStackPointer == MAX_OPEN_FILES)
487         return 0;
488 
489 #ifdef HPCGAP
490     /* Handle *defin*; redirect *errin* to *defin* if the default
491      * channel is already open. */
492     if (! strcmp(filename, "*defin*") ||
493         (! strcmp(filename, "*errin*") && TLS(DefaultInput)) )
494         return OpenDefaultInput();
495 #endif
496 
497     /* try to open the input file                                          */
498     file = SyFopen( filename, "r" );
499     if ( file == -1 )
500         return 0;
501 
502     /* remember the current position in the current file                   */
503     if (IO()->InputStackPointer > 0) {
504         GAP_ASSERT(IS_CHAR_PUSHBACK_EMPTY());
505         IO()->Input->ptr = STATE(In);
506         IO()->Input->symbol = STATE(Scanner).Symbol;
507         IO()->Input->interpreterStartLine = STATE(InterpreterStartLine);
508     }
509 
510     /* enter the file identifier and the file name                         */
511     IO()->Input = PushNewInput();
512     IO()->Input->isstream = 0;
513     IO()->Input->file = file;
514     IO()->Input->name[0] = '\0';
515 
516     // enable echo for stdin and errin
517     if (!strcmp("*errin*", filename) || !strcmp("*stdin*", filename))
518         IO()->Input->echo = 1;
519     else
520         IO()->Input->echo = 0;
521 
522     strlcpy(IO()->Input->name, filename, sizeof(IO()->Input->name));
523     IO()->Input->gapnameid = 0;
524 
525     /* start with an empty line and no symbol                              */
526     STATE(In) = IO()->Input->line;
527     STATE(In)[0] = STATE(In)[1] = '\0';
528     STATE(Scanner).Symbol = S_ILLEGAL;
529     STATE(InterpreterStartLine) = 0;
530     IO()->Input->number = 1;
531 
532     /* indicate success                                                    */
533     return 1;
534 }
535 
536 
537 /****************************************************************************
538 **
539 *F  OpenInputStream( <stream>, <echo> ) . . .  open a stream as current input
540 **
541 **  The same as 'OpenInput' but for streams.
542 */
OpenInputStream(Obj stream,UInt echo)543 UInt OpenInputStream(Obj stream, UInt echo)
544 {
545     /* fail if we can not handle another open input file                   */
546     if (IO()->InputStackPointer == MAX_OPEN_FILES)
547         return 0;
548 
549     /* remember the current position in the current file                   */
550     if (IO()->InputStackPointer > 0) {
551         GAP_ASSERT(IS_CHAR_PUSHBACK_EMPTY());
552         IO()->Input->ptr = STATE(In);
553         IO()->Input->symbol = STATE(Scanner).Symbol;
554         IO()->Input->interpreterStartLine = STATE(InterpreterStartLine);
555     }
556 
557     /* enter the file identifier and the file name                         */
558     IO()->Input = PushNewInput();
559     IO()->Input->isstream = 1;
560     IO()->Input->stream = stream;
561     IO()->Input->isstringstream =
562         (CALL_1ARGS(IsStringStream, stream) == True);
563     if (IO()->Input->isstringstream) {
564         IO()->Input->sline = CONST_ADDR_OBJ(stream)[2];
565         IO()->Input->spos = INT_INTOBJ(CONST_ADDR_OBJ(stream)[1]);
566     }
567     else {
568         IO()->Input->sline = 0;
569     }
570     IO()->Input->file = -1;
571     IO()->Input->echo = echo;
572     strlcpy(IO()->Input->name, "stream", sizeof(IO()->Input->name));
573     IO()->Input->gapnameid = 0;
574 
575     /* start with an empty line and no symbol                              */
576     STATE(In) = IO()->Input->line;
577     STATE(In)[0] = STATE(In)[1] = '\0';
578     STATE(Scanner).Symbol = S_ILLEGAL;
579     STATE(InterpreterStartLine) = 0;
580     IO()->Input->number = 1;
581 
582     /* indicate success                                                    */
583     return 1;
584 }
585 
586 
587 /****************************************************************************
588 **
589 *F  CloseInput()  . . . . . . . . . . . . . . . . .  close current input file
590 **
591 **  'CloseInput'  will close the  current input file.   Subsequent input will
592 **  again be taken from the previous input file.   'CloseInput' will return 1
593 **  to indicate success.
594 **
595 **  'CloseInput' will not close the initial input file '*stdin*', and returns
596 **  0  if such  an  attempt is made.   This is  used in  'Error'  which calls
597 **  'CloseInput' until it returns 0, therebye closing all open input files.
598 **
599 **  Calling 'CloseInput' if the  corresponding  'OpenInput' call failed  will
600 **  close the current output file, which will lead to very strange behaviour.
601 */
CloseInput(void)602 UInt CloseInput ( void )
603 {
604     /* refuse to close the initial input file                              */
605 #ifdef HPCGAP
606     // In HPC-GAP, only for the main thread.
607     if (TLS(threadID) != 0) {
608         if (IO()->InputStackPointer <= 0)
609             return 0;
610     } else
611 #else
612     if (IO()->InputStackPointer <= 1)
613         return 0;
614 #endif
615 
616     /* close the input file                                                */
617     if (!IO()->Input->isstream) {
618         SyFclose(IO()->Input->file);
619     }
620 
621     /* don't keep GAP objects alive unnecessarily */
622     memset(IO()->Input, 0, sizeof(TypInputFile));
623 
624     /* revert to last file                                                 */
625     const int sp = --IO()->InputStackPointer;
626 #ifdef HPCGAP
627     if (sp == 0) {
628         IO()->Input = NULL;
629         return 1;
630     }
631 #endif
632     IO()->Input = IO()->InputStack[sp - 1];
633     STATE(In) = IO()->Input->ptr;
634     STATE(Scanner).Symbol = IO()->Input->symbol;
635     STATE(InterpreterStartLine) = IO()->Input->interpreterStartLine;
636 
637     /* indicate success                                                    */
638     return 1;
639 }
640 
641 /****************************************************************************
642 **
643 *F  FlushRestOfInputLine()  . . . . . . . . . . . . discard remainder of line
644 */
645 
FlushRestOfInputLine(void)646 void FlushRestOfInputLine( void )
647 {
648   STATE(In)[0] = STATE(In)[1] = '\0';
649   /* IO()->Input->number = 1; */
650   STATE(Scanner).Symbol = S_ILLEGAL;
651 }
652 
653 /****************************************************************************
654 **
655 *F  OpenLog( <filename> ) . . . . . . . . . . . . . log interaction to a file
656 **
657 **  'OpenLog'  instructs  the scanner to   echo  all  input   from  the files
658 **  '*stdin*' and  '*errin*'  and  all  output to  the  files '*stdout*'  and
659 **  '*errout*' to the file with  name <filename>.  The  file is truncated  to
660 **  size 0 if it existed, otherwise it is created.
661 **
662 **  'OpenLog' returns 1 if it could  successfully open <filename> for writing
663 **  and 0  to indicate failure.   'OpenLog' will  fail if  you do  not   have
664 **  permissions  to create the file or   write to  it.  'OpenOutput' may also
665 **  fail if you have too many files open at once.  It is system dependent how
666 **  many   are too   many, but  16   files should  work everywhere.   Finally
667 **  'OpenLog' will fail if there is already a current logfile.
668 */
OpenLog(const Char * filename)669 UInt OpenLog (
670     const Char *        filename )
671 {
672 
673     /* refuse to open a logfile if we already log to one                   */
674     if (IO()->InputLog != 0 || IO()->OutputLog != 0)
675         return 0;
676 
677     /* try to open the file                                                */
678     IO()->OutputLogFileOrStream.file = SyFopen(filename, "w");
679     IO()->OutputLogFileOrStream.isstream = 0;
680     if (IO()->OutputLogFileOrStream.file == -1)
681         return 0;
682 
683     IO()->InputLog = &IO()->OutputLogFileOrStream;
684     IO()->OutputLog = &IO()->OutputLogFileOrStream;
685 
686     /* otherwise indicate success                                          */
687     return 1;
688 }
689 
690 
691 /****************************************************************************
692 **
693 *F  OpenLogStream( <stream> ) . . . . . . . . . . log interaction to a stream
694 **
695 **  The same as 'OpenLog' but for streams.
696 */
OpenLogStream(Obj stream)697 UInt OpenLogStream (
698     Obj             stream )
699 {
700 
701     /* refuse to open a logfile if we already log to one                   */
702     if (IO()->InputLog != 0 || IO()->OutputLog != 0)
703         return 0;
704 
705     /* try to open the file                                                */
706     IO()->OutputLogFileOrStream.isstream = 1;
707     IO()->OutputLogFileOrStream.stream = stream;
708     IO()->OutputLogFileOrStream.file = -1;
709 
710     IO()->InputLog = &IO()->OutputLogFileOrStream;
711     IO()->OutputLog = &IO()->OutputLogFileOrStream;
712 
713     /* otherwise indicate success                                          */
714     return 1;
715 }
716 
717 
718 /****************************************************************************
719 **
720 *F  CloseLog()  . . . . . . . . . . . . . . . . . . close the current logfile
721 **
722 **  'CloseLog' closes the current logfile again, so that input from '*stdin*'
723 **  and '*errin*' and output to '*stdout*' and '*errout*' will no  longer  be
724 **  echoed to a file.  'CloseLog' will return 1 to indicate success.
725 **
726 **  'CloseLog' will fail if there is no logfile active and will return  0  in
727 **  this case.
728 */
CloseLog(void)729 UInt CloseLog ( void )
730 {
731     /* refuse to close a non existent logfile                              */
732     if (IO()->InputLog == 0 || IO()->OutputLog == 0 ||
733         IO()->InputLog != IO()->OutputLog)
734         return 0;
735 
736     /* close the logfile                                                   */
737     if (!IO()->InputLog->isstream) {
738         SyFclose(IO()->InputLog->file);
739     }
740     IO()->InputLog = 0;
741     IO()->OutputLog = 0;
742 
743     /* indicate success                                                    */
744     return 1;
745 }
746 
747 
748 /****************************************************************************
749 **
750 *F  OpenInputLog( <filename> )  . . . . . . . . . . . . . log input to a file
751 **
752 **  'OpenInputLog'  instructs the  scanner  to echo  all input from the files
753 **  '*stdin*' and  '*errin*' to the file  with  name <filename>.  The file is
754 **  truncated to size 0 if it existed, otherwise it is created.
755 **
756 **  'OpenInputLog' returns 1  if it  could successfully open  <filename>  for
757 **  writing  and  0 to indicate failure.  'OpenInputLog' will fail  if you do
758 **  not have  permissions to create the file  or write to it.  'OpenInputLog'
759 **  may also fail  if you  have  too many  files open  at once.  It is system
760 **  dependent  how many are too many,  but 16 files  should work  everywhere.
761 **  Finally 'OpenInputLog' will fail if there is already a current logfile.
762 */
OpenInputLog(const Char * filename)763 UInt OpenInputLog (
764     const Char *        filename )
765 {
766 
767     /* refuse to open a logfile if we already log to one                   */
768     if (IO()->InputLog != 0)
769         return 0;
770 
771     /* try to open the file                                                */
772     IO()->InputLogFileOrStream.file = SyFopen(filename, "w");
773     IO()->InputLogFileOrStream.isstream = 0;
774     if (IO()->InputLogFileOrStream.file == -1)
775         return 0;
776 
777     IO()->InputLog = &IO()->InputLogFileOrStream;
778 
779     /* otherwise indicate success                                          */
780     return 1;
781 }
782 
783 
784 /****************************************************************************
785 **
786 *F  OpenInputLogStream( <stream> )  . . . . . . . . . . log input to a stream
787 **
788 **  The same as 'OpenInputLog' but for streams.
789 */
OpenInputLogStream(Obj stream)790 UInt OpenInputLogStream (
791     Obj                 stream )
792 {
793 
794     /* refuse to open a logfile if we already log to one                   */
795     if (IO()->InputLog != 0)
796         return 0;
797 
798     /* try to open the file                                                */
799     IO()->InputLogFileOrStream.isstream = 1;
800     IO()->InputLogFileOrStream.stream = stream;
801     IO()->InputLogFileOrStream.file = -1;
802 
803     IO()->InputLog = &IO()->InputLogFileOrStream;
804 
805     /* otherwise indicate success                                          */
806     return 1;
807 }
808 
809 
810 /****************************************************************************
811 **
812 *F  CloseInputLog() . . . . . . . . . . . . . . . . close the current logfile
813 **
814 **  'CloseInputLog'  closes  the current  logfile again,  so  that input from
815 **  '*stdin*'  and   '*errin*'  will  no  longer   be  echoed   to  a   file.
816 **  'CloseInputLog' will return 1 to indicate success.
817 **
818 **  'CloseInputLog' will fail if there is no logfile active and will return 0
819 **  in this case.
820 */
CloseInputLog(void)821 UInt CloseInputLog ( void )
822 {
823     /* refuse to close a non existent logfile                              */
824     if (IO()->InputLog == 0)
825         return 0;
826 
827     /* refuse to close a log opened with LogTo */
828     if (IO()->InputLog == IO()->OutputLog)
829         return 0;
830 
831     /* close the logfile                                                   */
832     if (!IO()->InputLog->isstream) {
833         SyFclose(IO()->InputLog->file);
834     }
835 
836     IO()->InputLog = 0;
837 
838     /* indicate success                                                    */
839     return 1;
840 }
841 
842 
843 /****************************************************************************
844 **
845 *F  OpenOutputLog( <filename> )  . . . . . . . . . . .  log output to a file
846 **
847 **  'OpenInputLog'  instructs the  scanner to echo   all output to  the files
848 **  '*stdout*' and '*errout*' to the file with name  <filename>.  The file is
849 **  truncated to size 0 if it existed, otherwise it is created.
850 **
851 **  'OpenOutputLog'  returns 1 if it  could  successfully open <filename> for
852 **  writing and 0 to  indicate failure.  'OpenOutputLog'  will fail if you do
853 **  not have permissions to create the file  or write to it.  'OpenOutputLog'
854 **  may also  fail if you have  too many  files  open at  once.  It is system
855 **  dependent how many are  too many,  but  16 files should  work everywhere.
856 **  Finally 'OpenOutputLog' will fail if there is already a current logfile.
857 */
OpenOutputLog(const Char * filename)858 UInt OpenOutputLog (
859     const Char *        filename )
860 {
861 
862     /* refuse to open a logfile if we already log to one                   */
863     if (IO()->OutputLog != 0)
864         return 0;
865 
866     /* try to open the file                                                */
867     memset(&IO()->OutputLogFileOrStream, 0, sizeof(TypOutputFile));
868     IO()->OutputLogFileOrStream.isstream = 0;
869     IO()->OutputLogFileOrStream.file = SyFopen(filename, "w");
870     if (IO()->OutputLogFileOrStream.file == -1)
871         return 0;
872 
873     IO()->OutputLog = &IO()->OutputLogFileOrStream;
874 
875     /* otherwise indicate success                                          */
876     return 1;
877 }
878 
879 
880 /****************************************************************************
881 **
882 *F  OpenOutputLogStream( <stream> )  . . . . . . . .  log output to a stream
883 **
884 **  The same as 'OpenOutputLog' but for streams.
885 */
OpenOutputLogStream(Obj stream)886 UInt OpenOutputLogStream (
887     Obj                 stream )
888 {
889 
890     /* refuse to open a logfile if we already log to one                   */
891     if (IO()->OutputLog != 0)
892         return 0;
893 
894     /* try to open the file                                                */
895     memset(&IO()->OutputLogFileOrStream, 0, sizeof(TypOutputFile));
896     IO()->OutputLogFileOrStream.isstream = 1;
897     IO()->OutputLogFileOrStream.stream = stream;
898     IO()->OutputLogFileOrStream.file = -1;
899 
900     IO()->OutputLog = &IO()->OutputLogFileOrStream;
901 
902     /* otherwise indicate success                                          */
903     return 1;
904 }
905 
906 
907 /****************************************************************************
908 **
909 *F  CloseOutputLog()  . . . . . . . . . . . . . . . close the current logfile
910 **
911 **  'CloseInputLog' closes   the current logfile   again, so  that output  to
912 **  '*stdout*'  and    '*errout*'  will no   longer  be   echoed to  a  file.
913 **  'CloseOutputLog' will return 1 to indicate success.
914 **
915 **  'CloseOutputLog' will fail if there is  no logfile active and will return
916 **  0 in this case.
917 */
CloseOutputLog(void)918 UInt CloseOutputLog ( void )
919 {
920     /* refuse to close a non existent logfile                              */
921     if (IO()->OutputLog == 0)
922         return 0;
923 
924     /* refuse to close a log opened with LogTo */
925     if (IO()->OutputLog == IO()->InputLog)
926         return 0;
927 
928     /* close the logfile                                                   */
929     if (!IO()->OutputLog->isstream) {
930         SyFclose(IO()->OutputLog->file);
931     }
932 
933     IO()->OutputLog = 0;
934 
935     /* indicate success                                                    */
936     return 1;
937 }
938 
939 /****************************************************************************
940 **
941 *F  OpenOutput( <filename> )  . . . . . . . . . open a file as current output
942 **
943 **  'OpenOutput' opens the file  with the name  <filename> as current output.
944 **  All subsequent output will go  to that file, until either   it is  closed
945 **  again  with 'CloseOutput' or  another  file is  opened with 'OpenOutput'.
946 **  The file is truncated to size 0 if it existed, otherwise it  is  created.
947 **  'OpenOutput' does not  close  the  current file, i.e., if  <filename>  is
948 **  closed again, output will go again to the current output file.
949 **
950 **  'OpenOutput'  returns  1 if it  could  successfully  open  <filename> for
951 **  writing and 0 to indicate failure.  'OpenOutput' will fail if  you do not
952 **  have  permissions to create the  file or write   to it.  'OpenOutput' may
953 **  also   fail if you   have  too many files   open  at once.   It is system
954 **  dependent how many are too many, but 16 files should work everywhere.
955 **
956 **  You can open '*stdout*'  to write  to the standard output  file, which is
957 **  usually the terminal, or '*errout*' to write  to the standard error file,
958 **  which is the terminal  even   if '*stdout*'  is  redirected to   a  file.
959 **  'OpenOutput' passes  those  file names to 'SyFopen'  like any other name,
960 **  they are just a convention between the main and the system package.
961 **
962 **  It is not neccessary to open the initial output file, 'InitScanner' opens
963 **  '*stdout*' for that purpose.  This  file  on the other hand   can not  be
964 **  closed by 'CloseOutput'.
965 */
OpenOutput(const Char * filename)966 UInt OpenOutput (
967     const Char *        filename )
968 {
969     Int                 file;
970 
971     // do nothing for stdout and errout if caught
972     if (IO()->Output != NULL && IO()->IgnoreStdoutErrout == IO()->Output &&
973         (strcmp(filename, "*errout*") == 0 ||
974          strcmp(filename, "*stdout*") == 0)) {
975         return 1;
976     }
977 
978     /* fail if we can not handle another open output file                  */
979     if (IO()->OutputStackPointer == MAX_OPEN_FILES)
980         return 0;
981 
982 #ifdef HPCGAP
983     /* Handle *defout* specially; also, redirect *errout* if we already
984      * have a default channel open. */
985     if ( ! strcmp( filename, "*defout*" ) ||
986          (! strcmp( filename, "*errout*" ) && TLS(threadID) != 0) )
987         return OpenDefaultOutput();
988 #endif
989 
990     /* try to open the file                                                */
991     file = SyFopen( filename, "w" );
992     if ( file == -1 )
993         return 0;
994 
995     /* put the file on the stack, start at position 0 on an empty line     */
996     IO()->Output = PushNewOutput();
997     IO()->Output->file = file;
998     IO()->Output->line[0] = '\0';
999     IO()->Output->pos = 0;
1000     IO()->Output->indent = 0;
1001     IO()->Output->isstream = 0;
1002     IO()->Output->format = 1;
1003 
1004     /* variables related to line splitting, very bad place to split        */
1005     IO()->Output->hints[0] = -1;
1006 
1007     /* indicate success                                                    */
1008     return 1;
1009 }
1010 
1011 
1012 /****************************************************************************
1013 **
1014 *F  OpenOutputStream( <stream> )  . . . . . . open a stream as current output
1015 **
1016 **  The same as 'OpenOutput' (and also 'OpenAppend') but for streams.
1017 */
1018 
1019 
OpenOutputStream(Obj stream)1020 UInt OpenOutputStream (
1021     Obj                 stream )
1022 {
1023     /* fail if we can not handle another open output file                  */
1024     if (IO()->OutputStackPointer == MAX_OPEN_FILES)
1025         return 0;
1026 
1027     /* put the file on the stack, start at position 0 on an empty line     */
1028     IO()->Output = PushNewOutput();
1029     IO()->Output->stream = stream;
1030     IO()->Output->isstringstream =
1031         (CALL_1ARGS(IsStringStream, stream) == True);
1032     IO()->Output->format =
1033         (CALL_1ARGS(PrintFormattingStatus, stream) == True);
1034     IO()->Output->line[0] = '\0';
1035     IO()->Output->pos = 0;
1036     IO()->Output->indent = 0;
1037     IO()->Output->isstream = 1;
1038 
1039     /* variables related to line splitting, very bad place to split        */
1040     IO()->Output->hints[0] = -1;
1041 
1042     /* indicate success                                                    */
1043     return 1;
1044 }
1045 
1046 
1047 /****************************************************************************
1048 **
1049 *F  CloseOutput() . . . . . . . . . . . . . . . . . close current output file
1050 **
1051 **  'CloseOutput' will  first flush all   pending output and  then  close the
1052 **  current  output  file.   Subsequent output will  again go to the previous
1053 **  output file.  'CloseOutput' returns 1 to indicate success.
1054 **
1055 **  'CloseOutput' will  not  close the  initial output file   '*stdout*', and
1056 **  returns 0 if such attempt is made.  This  is  used in 'Error' which calls
1057 **  'CloseOutput' until it returns 0, thereby closing all open output files.
1058 **
1059 **  Calling 'CloseOutput' if the corresponding 'OpenOutput' call failed  will
1060 **  close the current output file, which will lead to very strange behaviour.
1061 **  On the other  hand if you  forget  to call  'CloseOutput' at the end of a
1062 **  'PrintTo' call or an error will not yield much better results.
1063 */
CloseOutput(void)1064 UInt CloseOutput ( void )
1065 {
1066     // silently refuse to close the test output file; this is probably an
1067     // attempt to close *errout* which is silently not opened, so let's
1068     // silently not close it
1069     if (IO()->IgnoreStdoutErrout == IO()->Output)
1070         return 1;
1071 
1072     /* refuse to close the initial output file '*stdout*'                  */
1073 #ifdef HPCGAP
1074     if (IO()->OutputStackPointer <= 1 && IO()->Output->isstream &&
1075         TLS(DefaultOutput) == IO()->Output->stream)
1076         return 0;
1077 #else
1078     if (IO()->OutputStackPointer <= 1)
1079         return 0;
1080 #endif
1081 
1082     /* flush output and close the file                                     */
1083     Pr( "%c", (Int)'\03', 0L );
1084     if (!IO()->Output->isstream) {
1085         SyFclose(IO()->Output->file);
1086     }
1087 
1088     /* revert to previous output file and indicate success                 */
1089     const int sp = --IO()->OutputStackPointer;
1090     IO()->Output = sp ? IO()->OutputStack[sp - 1] : 0;
1091 
1092     return 1;
1093 }
1094 
1095 
1096 /****************************************************************************
1097 **
1098 *F  OpenAppend( <filename> )  . . open a file as current output for appending
1099 **
1100 **  'OpenAppend' opens the file  with the name  <filename> as current output.
1101 **  All subsequent output will go  to that file, until either   it is  closed
1102 **  again  with 'CloseOutput' or  another  file is  opened with 'OpenOutput'.
1103 **  Unlike 'OpenOutput' 'OpenAppend' does not truncate the file to size 0  if
1104 **  it exists.  Appart from that 'OpenAppend' is equal to 'OpenOutput' so its
1105 **  description applies to 'OpenAppend' too.
1106 */
OpenAppend(const Char * filename)1107 UInt OpenAppend (
1108     const Char *        filename )
1109 {
1110     Int                 file;
1111 
1112     /* fail if we can not handle another open output file                  */
1113     if (IO()->OutputStackPointer == MAX_OPEN_FILES)
1114         return 0;
1115 
1116 #ifdef HPCGAP
1117     if ( ! strcmp( filename, "*defout*") )
1118         return OpenDefaultOutput();
1119 #endif
1120 
1121     /* try to open the file                                                */
1122     file = SyFopen( filename, "a" );
1123     if ( file == -1 )
1124         return 0;
1125 
1126     /* put the file on the stack, start at position 0 on an empty line     */
1127     IO()->Output = PushNewOutput();
1128     IO()->Output->file = file;
1129     IO()->Output->line[0] = '\0';
1130     IO()->Output->pos = 0;
1131     IO()->Output->indent = 0;
1132     IO()->Output->isstream = 0;
1133 
1134     /* variables related to line splitting, very bad place to split        */
1135     IO()->Output->hints[0] = -1;
1136 
1137     /* indicate success                                                    */
1138     return 1;
1139 }
1140 
1141 
1142 /****************************************************************************
1143 **
1144 *F * * * * * * * * * * * * * * input functions  * * * * * * * * * * * * * * *
1145 */
1146 
1147 
1148 /****************************************************************************
1149 **
1150 *F  GetLine2( <input>, <buffer>, <length> ) . . . . . . . . get a line, local
1151 */
GetLine2(TypInputFile * input,Char * buffer,UInt length)1152 static Int GetLine2 (
1153     TypInputFile *          input,
1154     Char *                  buffer,
1155     UInt                    length )
1156 {
1157 #ifdef HPCGAP
1158     if ( ! input ) {
1159         input = IO()->Input;
1160         if (!input)
1161             OpenDefaultInput();
1162         input = IO()->Input;
1163     }
1164 #endif
1165 
1166     if ( input->isstream ) {
1167         if (input->sline == 0 ||
1168             (IS_STRING(input->sline) &&
1169              GET_LEN_STRING(input->sline) <= input->spos)) {
1170             input->sline = CALL_1ARGS( ReadLineFunc, input->stream );
1171             input->spos  = 0;
1172         }
1173         if ( input->sline == Fail || ! IS_STRING(input->sline) ) {
1174             return 0;
1175         }
1176 
1177         ConvString(input->sline);
1178         /* we now allow that input->sline actually contains several lines,
1179            e.g., it can be a  string from a string stream  */
1180 
1181         /* start position in buffer */
1182         Char *bptr = buffer;
1183         while (*bptr)
1184             bptr++;
1185 
1186         /* copy piece of input->sline into buffer and adjust counters */
1187         const Char *ptr = CONST_CSTR_STRING(input->sline) + input->spos;
1188         const Char * const end = CONST_CSTR_STRING(input->sline) + GET_LEN_STRING(input->sline);
1189         const Char * const bend = buffer + length - 2;
1190         while (bptr < bend && ptr < end) {
1191             Char c = *ptr++;
1192 
1193             // ignore CR, so that a Window CR+LF line terminator looks
1194             // to us the same as a Unix LF line terminator
1195             if (c == '\r')
1196                 continue;
1197 
1198             *bptr++ = c;
1199 
1200             // check for line end
1201             if (c == '\n')
1202                 break;
1203         }
1204         *bptr = '\0';
1205         input->spos = ptr - CONST_CSTR_STRING(input->sline);
1206 
1207         /* if input->stream is a string stream, we have to adjust the
1208            position counter in the stream object as well */
1209         if (input->isstringstream) {
1210             ADDR_OBJ(input->stream)[1] = INTOBJ_INT(input->spos);
1211         }
1212     }
1213     else {
1214         if ( ! SyFgets( buffer, length, input->file ) ) {
1215             return 0;
1216         }
1217     }
1218     return 1;
1219 }
1220 
1221 
1222 /****************************************************************************
1223 **
1224 *F  GetLine() . . . . . . . . . . . . . . . . . . . . . . . get a line, local
1225 **
1226 **  'GetLine'  fetches another  line from  the  input 'Input' into the buffer
1227 **  'Input->line', sets the pointer 'In' to  the beginning of this buffer and
1228 **  returns the first character from the line.
1229 **
1230 **  If   the input file is  '*stdin*'   or '*errin*' 'GetLine'  first  prints
1231 **  'Prompt', unless it is '*stdin*' and GAP was called with option '-q'.
1232 **
1233 **  If there is an  input logfile in use  and the input  file is '*stdin*' or
1234 **  '*errin*' 'GetLine' echoes the new line to the logfile.
1235 */
GetLine(void)1236 static Char GetLine(void)
1237 {
1238     /* if file is '*stdin*' or '*errin*' print the prompt and flush it     */
1239     /* if the GAP function `PrintPromptHook' is defined then it is called  */
1240     /* for printing the prompt, see also `EndLineHook'                     */
1241     if (!IO()->Input->isstream) {
1242         if (IO()->Input->file == 0) {
1243             if ( ! SyQuiet ) {
1244                 if (IO()->Output->pos > 0)
1245                     Pr("\n", 0L, 0L);
1246                 if ( PrintPromptHook )
1247                      Call0ArgsInNewReader( PrintPromptHook );
1248                 else
1249                      Pr( "%s%c", (Int)STATE(Prompt), (Int)'\03' );
1250             } else
1251                 Pr( "%c", (Int)'\03', 0L );
1252         }
1253         else if (IO()->Input->file == 2) {
1254             if (IO()->Output->pos > 0)
1255                 Pr("\n", 0L, 0L);
1256             if ( PrintPromptHook )
1257                  Call0ArgsInNewReader( PrintPromptHook );
1258             else
1259                  Pr( "%s%c", (Int)STATE(Prompt), (Int)'\03' );
1260         }
1261     }
1262 
1263     /* bump the line number                                                */
1264     if (IO()->Input->line < STATE(In) && *(STATE(In) - 1) == '\n') {
1265         IO()->Input->number++;
1266     }
1267 
1268     /* initialize 'STATE(In)', no errors on this line so far                      */
1269     STATE(In) = IO()->Input->line;
1270     STATE(In)[0] = '\0';
1271     STATE(NrErrLine) = 0;
1272 
1273     /* try to read a line                                              */
1274     if (!GetLine2(IO()->Input, IO()->Input->line,
1275                   sizeof(IO()->Input->line))) {
1276         STATE(In)[0] = '\377';  STATE(In)[1] = '\0';
1277     }
1278 
1279     /* if necessary echo the line to the logfile                      */
1280     if (IO()->InputLog != 0 && IO()->Input->echo == 1)
1281         if ( !(STATE(In)[0] == '\377' && STATE(In)[1] == '\0') )
1282             PutLine2(IO()->InputLog, STATE(In), strlen(STATE(In)));
1283 
1284     /* return the current character                                        */
1285     return *STATE(In);
1286 }
1287 
1288 
1289 /****************************************************************************
1290 **
1291 *F * * * * * * * * * * * * *  output functions  * * * * * * * * * * * * * * *
1292 */
1293 
1294 
1295 /****************************************************************************
1296 **
1297 *F  PutLine2( <output>, <line>, <len> )  . . . . . . . . .print a line, local
1298 **
1299 **  Introduced <len> argument. Actually in all cases where this is called one
1300 **  knows the length of <line>, so it is not necessary to compute it again
1301 **  with the inefficient C- strlen.  (FL)
1302 */
1303 
PutLine2(TypOutputFile * output,const Char * line,UInt len)1304 static void PutLine2(TypOutputFile * output, const Char * line, UInt len)
1305 {
1306   Obj                     str;
1307   UInt                    lstr;
1308   if ( output->isstream ) {
1309     /* special handling of string streams, where we can copy directly */
1310     if (output->isstringstream) {
1311       str = CONST_ADDR_OBJ(output->stream)[1];
1312       lstr = GET_LEN_STRING(str);
1313       GROW_STRING(str, lstr+len);
1314       memcpy(CHARS_STRING(str) + lstr, line, len);
1315       SET_LEN_STRING(str, lstr + len);
1316       *(CHARS_STRING(str) + lstr + len) = '\0';
1317       CHANGED_BAG(str);
1318       return;
1319     }
1320 
1321     /* Space for the null is allowed for in GAP strings */
1322     str = MakeImmStringWithLen(line, len);
1323 
1324     /* now delegate to library level */
1325     CALL_2ARGS( WriteAllFunc, output->stream, str );
1326   }
1327   else {
1328     SyFputs( line, output->file );
1329   }
1330 }
1331 
1332 
1333 /****************************************************************************
1334 **
1335 *F  PutLineTo ( stream, len ) . . . . . . . . . . . . . . print a line, local
1336 **
1337 **  'PutLineTo'  prints the first len characters of the current output
1338 **  line   'stream->line' to <stream>
1339 **  It  is  called from 'PutChrTo'.
1340 **
1341 **  'PutLineTo' also echoes the output line to the logfile 'OutputLog' if
1342 **  'OutputLog' is not 0 and the output file is '*stdout*' or '*errout*'.
1343 **
1344 */
PutLineTo(TypOutputFile * stream,UInt len)1345 static void PutLineTo(TypOutputFile * stream, UInt len)
1346 {
1347   PutLine2( stream, stream->line, len );
1348 
1349   /* if neccessary echo it to the logfile                                */
1350   if (IO()->OutputLog != 0 && !stream->isstream) {
1351       if (stream->file == 1 || stream->file == 3) {
1352           PutLine2(IO()->OutputLog, stream->line, len);
1353       }
1354   }
1355 }
1356 
1357 
1358 /****************************************************************************
1359 **
1360 *F  PutChrTo( <stream>, <ch> )  . . . . . . . . . print character <ch>, local
1361 **
1362 **  'PutChrTo' prints the single character <ch> to the stream <stream>
1363 **
1364 **  'PutChrTo' buffers the output characters until either <ch> is <newline>,
1365 **  <ch> is '\03' (<flush>) or the buffer fills up.
1366 **
1367 **  In the later case 'PutChrTo' has to decide where to split the output
1368 **  line. It takes the point at which $linelength - pos + 8 * indent$ is
1369 **  minimal.
1370 */
1371 
1372 /* helper function to add a hint about a possible line break;
1373    a triple (pos, value, indent), such that the minimal (value-pos) wins */
1374 static void
addLineBreakHint(TypOutputFile * stream,Int pos,Int val,Int indentdiff)1375 addLineBreakHint(TypOutputFile * stream, Int pos, Int val, Int indentdiff)
1376 {
1377   Int nr, i;
1378   /* find next free slot */
1379   for (nr = 0; nr < MAXHINTS && stream->hints[3*nr] != -1; nr++);
1380   if (nr == MAXHINTS) {
1381     /* forget the first stored hint */
1382     for (i = 0; i < 3*MAXHINTS - 3; i++)
1383        stream->hints[i] =  stream->hints[i+3];
1384     nr--;
1385   }
1386   /* if pos is same as before only relevant if new entry has higher
1387      priority */
1388   if ( nr > 0 && stream->hints[3*(nr-1)] == pos )
1389     nr--;
1390 
1391   if ( stream->indent < pos &&
1392        (stream->hints[3*nr] == -1 || val < stream->hints[3*(nr)+1]) ) {
1393     stream->hints[3*nr] = pos;
1394     stream->hints[3*nr+1] = val;
1395     stream->hints[3*nr+2] = stream->indent;
1396     stream->hints[3*nr+3] = -1;
1397   }
1398   stream->indent += indentdiff;
1399 }
1400 /* helper function to find line break position,
1401    returns position nr in stream[hints] or -1 if none found */
nrLineBreak(TypOutputFile * stream)1402 static Int nrLineBreak(TypOutputFile * stream)
1403 {
1404   Int nr=-1, min, i;
1405   for (i = 0, min = INT_MAX; stream->hints[3*i] != -1; i++)
1406   {
1407     if (stream->hints[3*i] > 0 &&
1408         stream->hints[3*i+1] - stream->hints[3*i] <= min)
1409     {
1410       nr = i;
1411       min = stream->hints[3*i+1] - stream->hints[3*i];
1412     }
1413   }
1414   if (min < INT_MAX)
1415     return nr;
1416   else
1417     return -1;
1418 }
1419 
1420 
PutChrTo(TypOutputFile * stream,Char ch)1421 static void PutChrTo(TypOutputFile * stream, Char ch)
1422 {
1423   Int                 i, hint, spos;
1424   Char                str [MAXLENOUTPUTLINE];
1425 
1426 
1427   /* '\01', increment indentation level                                  */
1428   if ( ch == '\01' ) {
1429 
1430     if (!stream->format)
1431       return;
1432 
1433     /* add hint to break line  */
1434     addLineBreakHint(stream, stream->pos, 16*stream->indent, 1);
1435   }
1436 
1437   /* '\02', decrement indentation level                                  */
1438   else if ( ch == '\02' ) {
1439 
1440     if (!stream->format)
1441       return;
1442 
1443     /* if this is a better place to split the line remember it         */
1444     addLineBreakHint(stream, stream->pos, 16*stream->indent, -1);
1445   }
1446 
1447   /* '\03', print line                                                   */
1448   else if ( ch == '\03' ) {
1449 
1450     /* print the line                                                  */
1451     if (stream->pos != 0)
1452       {
1453         stream->line[ stream->pos ] = '\0';
1454         PutLineTo(stream, stream->pos );
1455 
1456         /* start the next line                                         */
1457         stream->pos      = 0;
1458       }
1459     /* reset line break hints                                          */
1460     stream->hints[0] = -1;
1461 
1462   }
1463 
1464   /* <newline> or <return>, print line, indent next                      */
1465   else if ( ch == '\n' || ch == '\r' ) {
1466 
1467     /* put the character on the line and terminate it                  */
1468     stream->line[ stream->pos++ ] = ch;
1469     stream->line[ stream->pos   ] = '\0';
1470 
1471     /* print the line                                                  */
1472     PutLineTo( stream, stream->pos );
1473 
1474     /* and dump it from the buffer */
1475     stream->pos = 0;
1476     if (stream->format)
1477       {
1478         /* indent for next line                                         */
1479         for ( i = 0;  i < stream->indent; i++ )
1480           stream->line[ stream->pos++ ] = ' ';
1481       }
1482     /* reset line break hints                                       */
1483     stream->hints[0] = -1;
1484 
1485   }
1486 
1487   /* normal character, room on the current line                          */
1488 #ifdef HPCGAP
1489   /* TODO: For threads other than the main thread, reserve some extra
1490      space for the thread id indicator. See issue #136. */
1491   else if (stream->pos <
1492            SyNrCols - 2 - 6 * (TLS(threadID) != 0) - IO()->NoSplitLine) {
1493 #else
1494   else if (stream->pos < SyNrCols - 2 - IO()->NoSplitLine) {
1495 #endif
1496 
1497     /* put the character on this line                                  */
1498     stream->line[ stream->pos++ ] = ch;
1499 
1500   }
1501 
1502   else
1503     {
1504       /* position to split                                              */
1505       if ( (hint = nrLineBreak(stream)) != -1 )
1506         spos = stream->hints[3*hint];
1507       else
1508         spos = 0;
1509 
1510       /* if we are going to split at the end of the line, and we are
1511          formatting discard blanks */
1512       if ( stream->format && spos == stream->pos && ch == ' ' ) {
1513         ;
1514       }
1515 
1516       /* full line, acceptable split position                              */
1517       else if ( stream->format && spos != 0 ) {
1518 
1519         /* add character to the line, terminate it                         */
1520         stream->line[ stream->pos++ ] = ch;
1521         stream->line[ stream->pos++ ] = '\0';
1522 
1523         /* copy the rest after the best split position to a safe place     */
1524         for ( i = spos; i < stream->pos; i++ )
1525           str[ i-spos ] = stream->line[ i ];
1526         str[ i-spos] = '\0';
1527 
1528         /* print line up to the best split position                        */
1529         stream->line[ spos++ ] = '\n';
1530         stream->line[ spos   ] = '\0';
1531         PutLineTo( stream, spos );
1532         spos--;
1533 
1534         /* indent for the rest                                             */
1535         stream->pos = 0;
1536         for ( i = 0; i < stream->hints[3*hint+2]; i++ )
1537           stream->line[ stream->pos++ ] = ' ';
1538         spos -= stream->hints[3*hint+2];
1539 
1540         /* copy the rest onto the next line                                */
1541         for ( i = 0; str[ i ] != '\0'; i++ )
1542           stream->line[ stream->pos++ ] = str[ i ];
1543         /* recover line break hints for copied rest                      */
1544         for ( i = hint+1; stream->hints[3*i] != -1; i++ )
1545         {
1546           stream->hints[3*(i-hint-1)] = stream->hints[3*i]-spos;
1547           stream->hints[3*(i-hint-1)+1] = stream->hints[3*i+1];
1548           stream->hints[3*(i-hint-1)+2] = stream->hints[3*i+2];
1549         }
1550         stream->hints[3*(i-hint-1)] = -1;
1551       }
1552 
1553       /* full line, no split position                                       */
1554       else {
1555 
1556         if (stream->format)
1557           {
1558             /* append a '\',*/
1559             stream->line[ stream->pos++ ] = '\\';
1560             stream->line[ stream->pos++ ] = '\n';
1561           }
1562         /* and print the line                                */
1563         stream->line[ stream->pos   ] = '\0';
1564         PutLineTo( stream, stream->pos );
1565 
1566         /* add the character to the next line                              */
1567         stream->pos = 0;
1568         stream->line[ stream->pos++ ] = ch;
1569 
1570         if (stream->format)
1571           stream->hints[0] = -1;
1572       }
1573 
1574     }
1575 }
1576 
1577 /****************************************************************************
1578 **
1579 *F  FuncToggleEcho( )
1580 **
1581 */
1582 
1583 static Obj FuncToggleEcho(Obj self)
1584 {
1585     IO()->Input->echo = 1 - IO()->Input->echo;
1586     return (Obj)0;
1587 }
1588 
1589 /****************************************************************************
1590 **
1591 *F  FuncCPROMPT( )
1592 **
1593 **  returns the current `Prompt' as GAP string.
1594 */
1595 static Obj FuncCPROMPT(Obj self)
1596 {
1597   Obj p;
1598   p = MakeString(STATE(Prompt));
1599   return p;
1600 }
1601 
1602 /****************************************************************************
1603 **
1604 *F  FuncPRINT_CPROMPT( <prompt> )
1605 **
1606 **  prints current `Prompt' if argument <prompt> is not in StringRep, otherwise
1607 **  uses the content of <prompt> as `Prompt' (at most 80 characters).
1608 **  (important is the flush character without resetting the cursor column)
1609 */
1610 
1611 static Obj FuncPRINT_CPROMPT(Obj self, Obj prompt)
1612 {
1613   if (IS_STRING_REP(prompt)) {
1614     /* by assigning to Prompt we also tell readline (if used) what the
1615        current prompt is  */
1616     strlcpy(promptBuf, CONST_CSTR_STRING(prompt), sizeof(promptBuf));
1617     STATE(Prompt) = promptBuf;
1618   }
1619   Pr("%s%c", (Int)STATE(Prompt), (Int)'\03' );
1620   return (Obj) 0;
1621 }
1622 
1623 void ResetOutputIndent(void)
1624 {
1625     GAP_ASSERT(IO()->Output);
1626     IO()->Output->indent = 0;
1627 }
1628 
1629 /****************************************************************************
1630 **
1631 *F  Pr( <format>, <arg1>, <arg2> )  . . . . . . . . .  print formatted output
1632 *F  PrTo( <stream>, <format>, <arg1>, <arg2> )  . . .  print formatted output
1633 **
1634 **  'Pr' is the output function. The first argument is a 'printf' like format
1635 **  string containing   up   to 2  '%'  format   fields,   specifing  how the
1636 **  corresponding arguments are to be  printed.  The two arguments are passed
1637 **  as  'Int'   integers.   This  is possible  since every  C object  ('int',
1638 **  'char', pointers) except 'float' or 'double', which are not used  in GAP,
1639 **  can be converted to a 'Int' without loss of information.
1640 **
1641 **  The function 'Pr' currently support the following '%' format  fields:
1642 **  '%c'    the corresponding argument represents a character,  usually it is
1643 **          its ASCII or EBCDIC code, and this character is printed.
1644 **  '%s'    the corresponding argument is the address of  a  null  terminated
1645 **          character string which is printed.
1646 **  '%S'    the corresponding argument is the address of  a  null  terminated
1647 **          character string which is printed with escapes.
1648 **  '%g'    the corresponding argument is the address of an Obj which points
1649 **          to a string in STRING_REP format which is printed in '%s' format
1650 **  '%G'    the corresponding argument is the address of an Obj which points
1651 **          to a string in STRING_REP format which is printed in '%S' format
1652 **  '%C'    the corresponding argument is the address of an Obj which points
1653 **          to a string in STRING_REP format which is printed with C escapes
1654 **  '%d'    the corresponding argument is a signed integer, which is printed.
1655 **          Between the '%' and the 'd' an integer might be used  to  specify
1656 **          the width of a field in which the integer is right justified.  If
1657 **          the first character is '0' 'Pr' pads with '0' instead of <space>.
1658 **  '%i'    is a synonym of %d, in line with recent C library developements
1659 **  '%I'    print an identifier, given as a null terminated character string.
1660 **  '%H'    print an identifier, given as GAP string in STRING_REP
1661 **  '%>'    increment the indentation level.
1662 **  '%<'    decrement the indentation level.
1663 **  '%%'    can be used to print a single '%' character. No argument is used.
1664 **
1665 **  You must always  cast the arguments to  '(Int)'  to avoid  problems  with
1666 **  those compilers with a default integer size of 16 instead of 32 bit.  You
1667 **  must pass 0L if you don't make use of an argument to please lint.
1668 */
1669 static inline void FormatOutput(
1670     void (*put_a_char)(void *state, Char c),
1671     void *state, const Char *format, Int arg1, Int arg2 )
1672 {
1673   const Char *        p;
1674   Obj                 arg1obj;
1675   Int                 prec,  n;
1676   Char                fill;
1677 
1678   /* loop over the characters of the <format> string                     */
1679   for ( p = format; *p != '\0'; p++ ) {
1680 
1681     /* not a '%' character, simply print it                            */
1682     if ( *p != '%' ) {
1683       put_a_char(state, *p);
1684       continue;
1685     }
1686 
1687     /* if the character is '%' do something special                    */
1688 
1689     /* first look for a precision field                            */
1690     p++;
1691     prec = 0;
1692     fill = (*p == '0' ? '0' : ' ');
1693     while ( IsDigit(*p) ) {
1694       prec = 10 * prec + *p - '0';
1695       p++;
1696     }
1697 
1698     /* handle the case of a missing argument                     */
1699     if (arg1 == 0 && (*p == 's' || *p == 'S' || *p == 'C' || *p == 'I')) {
1700       put_a_char(state, '<');
1701       put_a_char(state, 'n');
1702       put_a_char(state, 'u');
1703       put_a_char(state, 'l');
1704       put_a_char(state, 'l');
1705       put_a_char(state, '>');
1706 
1707       /* on to the next argument                                 */
1708       arg1 = arg2;
1709     }
1710 
1711     /* '%d' print an integer                                       */
1712     else if ( *p == 'd'|| *p == 'i' ) {
1713       int is_neg = (arg1 < 0);
1714       if ( is_neg ) {
1715         arg1 = -arg1;
1716         prec--; // we loose one digit of output precision for the minus sign
1717       }
1718 
1719       /* compute how many characters this number requires    */
1720       for ( n = 1; n <= arg1/10; n*=10 ) {
1721         prec--;
1722       }
1723       while ( --prec > 0 )  put_a_char(state, fill);
1724 
1725       if ( is_neg ) {
1726         put_a_char(state, '-');
1727       }
1728 
1729       for ( ; n > 0; n /= 10 )
1730         put_a_char(state, (Char)(((arg1/n)%10) + '0') );
1731 
1732       /* on to the next argument                                 */
1733       arg1 = arg2;
1734     }
1735 
1736     /* '%s' or '%g' print a string                               */
1737     else if ( *p == 's' || *p == 'g') {
1738 
1739       // If arg is a GAP obj, get out the contained string, and
1740       // set arg1obj so we can re-evaluate after any possible GC
1741       // which occurs in put_a_char
1742       if (*p == 'g') {
1743         arg1obj = (Obj)arg1;
1744         arg1 = (Int)CONST_CSTR_STRING(arg1obj);
1745       }
1746       else {
1747         arg1obj = 0;
1748       }
1749 
1750       /* compute how many characters this identifier requires    */
1751       for ( const Char * q = (const Char *)arg1; *q != '\0' && prec > 0; q++ ) {
1752         prec--;
1753       }
1754 
1755       /* if wanted push an appropriate number of <space>-s       */
1756       while ( prec-- > 0 )  put_a_char(state, ' ');
1757 
1758       if (arg1obj) {
1759           arg1 = (Int)CONST_CSTR_STRING(arg1obj);
1760       }
1761 
1762       /* print the string                                        */
1763       /* must be careful that line breaks don't go inside
1764          escaped sequences \n or \123 or similar */
1765       for ( Int i = 0; ((const Char *)arg1)[i] != '\0'; i++ ) {
1766           const Char* q = ((const Char *)arg1) + i;
1767           if (*q == '\\' && IO()->NoSplitLine == 0) {
1768               if (*(q + 1) < '8' && *(q + 1) >= '0')
1769                   IO()->NoSplitLine = 3;
1770               else
1771                   IO()->NoSplitLine = 1;
1772         }
1773         else if (IO()->NoSplitLine > 0)
1774             IO()->NoSplitLine--;
1775         put_a_char(state, *q);
1776 
1777         if (arg1obj) {
1778           arg1 = (Int)CONST_CSTR_STRING(arg1obj);
1779         }
1780       }
1781 
1782       /* on to the next argument                                 */
1783       arg1 = arg2;
1784     }
1785 
1786     /* '%S' or '%G' print a string with the necessary escapes    */
1787     else if ( *p == 'S' || *p == 'G' ) {
1788 
1789       // If arg is a GAP obj, get out the contained string, and
1790       // set arg1obj so we can re-evaluate after any possible GC
1791       // which occurs in put_a_char
1792       if (*p == 'G') {
1793         arg1obj = (Obj)arg1;
1794         arg1 = (Int)CONST_CSTR_STRING(arg1obj);
1795       }
1796       else {
1797         arg1obj = 0;
1798       }
1799 
1800 
1801       /* compute how many characters this identifier requires    */
1802       for ( const Char * q = (const Char *)arg1; *q != '\0' && prec > 0; q++ ) {
1803         if      ( *q == '\n'  ) { prec -= 2; }
1804         else if ( *q == '\t'  ) { prec -= 2; }
1805         else if ( *q == '\r'  ) { prec -= 2; }
1806         else if ( *q == '\b'  ) { prec -= 2; }
1807         else if ( *q == '\01' ) { prec -= 2; }
1808         else if ( *q == '\02' ) { prec -= 2; }
1809         else if ( *q == '\03' ) { prec -= 2; }
1810         else if ( *q == '"'   ) { prec -= 2; }
1811         else if ( *q == '\\'  ) { prec -= 2; }
1812         else                    { prec -= 1; }
1813       }
1814 
1815       /* if wanted push an appropriate number of <space>-s       */
1816       while ( prec-- > 0 )  put_a_char(state, ' ');
1817 
1818       if (arg1obj) {
1819           arg1 = (Int)CONST_CSTR_STRING(arg1obj);
1820       }
1821 
1822       /* print the string                                        */
1823       for ( Int i = 0; ((const Char *)arg1)[i] != '\0'; i++ ) {
1824         const Char* q = ((const Char *)arg1) + i;
1825         if      ( *q == '\n'  ) { put_a_char(state, '\\'); put_a_char(state, 'n');  }
1826         else if ( *q == '\t'  ) { put_a_char(state, '\\'); put_a_char(state, 't');  }
1827         else if ( *q == '\r'  ) { put_a_char(state, '\\'); put_a_char(state, 'r');  }
1828         else if ( *q == '\b'  ) { put_a_char(state, '\\'); put_a_char(state, 'b');  }
1829         else if ( *q == '\01' ) { put_a_char(state, '\\'); put_a_char(state, '>');  }
1830         else if ( *q == '\02' ) { put_a_char(state, '\\'); put_a_char(state, '<');  }
1831         else if ( *q == '\03' ) { put_a_char(state, '\\'); put_a_char(state, 'c');  }
1832         else if ( *q == '"'   ) { put_a_char(state, '\\'); put_a_char(state, '"');  }
1833         else if ( *q == '\\'  ) { put_a_char(state, '\\'); put_a_char(state, '\\'); }
1834         else                    { put_a_char(state, *q);               }
1835 
1836         if (arg1obj) {
1837           arg1 = (Int)CONST_CSTR_STRING(arg1obj);
1838         }
1839       }
1840 
1841       /* on to the next argument                                 */
1842       arg1 = arg2;
1843     }
1844 
1845     /* '%C' print a string with the necessary C escapes            */
1846     else if ( *p == 'C' ) {
1847 
1848       arg1obj = (Obj)arg1;
1849       arg1 = (Int)CONST_CSTR_STRING(arg1obj);
1850 
1851       /* compute how many characters this identifier requires    */
1852       for ( const Char * q = (const Char *)arg1; *q != '\0' && prec > 0; q++ ) {
1853         if      ( *q == '\n'  ) { prec -= 2; }
1854         else if ( *q == '\t'  ) { prec -= 2; }
1855         else if ( *q == '\r'  ) { prec -= 2; }
1856         else if ( *q == '\b'  ) { prec -= 2; }
1857         else if ( *q == '\01' ) { prec -= 3; }
1858         else if ( *q == '\02' ) { prec -= 3; }
1859         else if ( *q == '\03' ) { prec -= 3; }
1860         else if ( *q == '"'   ) { prec -= 2; }
1861         else if ( *q == '\\'  ) { prec -= 2; }
1862         else                    { prec -= 1; }
1863       }
1864 
1865       /* if wanted push an appropriate number of <space>-s       */
1866       while ( prec-- > 0 )  put_a_char(state, ' ');
1867 
1868       /* print the string                                        */
1869       Int i = 0;
1870       while (1) {
1871         const Char* q = CONST_CSTR_STRING(arg1obj) + i++;
1872         if (*q == 0)
1873             break;
1874 
1875         if      ( *q == '\n'  ) { put_a_char(state, '\\'); put_a_char(state, 'n');  }
1876         else if ( *q == '\t'  ) { put_a_char(state, '\\'); put_a_char(state, 't');  }
1877         else if ( *q == '\r'  ) { put_a_char(state, '\\'); put_a_char(state, 'r');  }
1878         else if ( *q == '\b'  ) { put_a_char(state, '\\'); put_a_char(state, 'b');  }
1879         else if ( *q == '\01' ) { put_a_char(state, '\\'); put_a_char(state, '0');
1880                                   put_a_char(state, '1');                }
1881         else if ( *q == '\02' ) { put_a_char(state, '\\'); put_a_char(state, '0');
1882                                   put_a_char(state, '2');                }
1883         else if ( *q == '\03' ) { put_a_char(state, '\\'); put_a_char(state, '0');
1884                                   put_a_char(state, '3');                }
1885         else if ( *q == '"'   ) { put_a_char(state, '\\'); put_a_char(state, '"');  }
1886         else if ( *q == '\\'  ) { put_a_char(state, '\\'); put_a_char(state, '\\'); }
1887         else                    { put_a_char(state, *q);               }
1888       }
1889 
1890       /* on to the next argument                                 */
1891       arg1 = arg2;
1892     }
1893 
1894     /* '%I' print an identifier                                    */
1895     else if ( *p == 'I' || *p =='H' ) {
1896       int found_keyword = 0;
1897 
1898       // If arg is a GAP obj, get out the contained string, and
1899       // set arg1obj so we can re-evaluate after any possible GC
1900       // which occurs in put_a_char
1901       if (*p == 'H') {
1902         arg1obj = (Obj)arg1;
1903         arg1 = (Int)CONST_CSTR_STRING(arg1obj);
1904       }
1905       else {
1906         arg1obj = 0;
1907       }
1908 
1909       /* check if q matches a keyword    */
1910       found_keyword = IsKeyword((const Char *)arg1);
1911 
1912       /* compute how many characters this identifier requires    */
1913       if (found_keyword) {
1914         prec--;
1915       }
1916       for ( const Char * q = (const Char *)arg1; *q != '\0'; q++ ) {
1917         if ( !IsIdent(*q) && !IsDigit(*q) ) {
1918           prec--;
1919         }
1920         prec--;
1921       }
1922 
1923       /* if wanted push an appropriate number of <space>-s       */
1924       while ( prec-- > 0 ) { put_a_char(state, ' '); }
1925 
1926       /* print the identifier                                    */
1927       if ( found_keyword ) {
1928         put_a_char(state, '\\');
1929       }
1930 
1931       for ( Int i = 0; ((const Char *)arg1)[i] != '\0'; i++ ) {
1932         Char c = ((const Char *)arg1)[i];
1933 
1934         if ( !IsIdent(c) && !IsDigit(c) ) {
1935           put_a_char(state, '\\');
1936         }
1937         put_a_char(state, c);
1938         if (arg1obj) {
1939           arg1 = (Int)CONST_CSTR_STRING(arg1obj);
1940         }
1941       }
1942 
1943       /* on to the next argument                                 */
1944       arg1 = arg2;
1945     }
1946 
1947     /* '%c' print a character                                      */
1948     else if ( *p == 'c' ) {
1949       put_a_char(state, (Char)arg1);
1950       arg1 = arg2;
1951     }
1952 
1953     /* '%%' print a '%' character                                  */
1954     else if ( *p == '%' ) {
1955       put_a_char(state, '%');
1956     }
1957 
1958     /* '%>' increment the indentation level                        */
1959     else if ( *p == '>' ) {
1960       put_a_char(state, '\01');
1961       while ( --prec > 0 )
1962         put_a_char(state, '\01');
1963     }
1964 
1965     /* '%<' decrement the indentation level                        */
1966     else if ( *p == '<' ) {
1967       put_a_char(state, '\02');
1968       while ( --prec > 0 )
1969         put_a_char(state, '\02');
1970     }
1971 
1972     /* else raise an error                                         */
1973     else {
1974       for ( p = "%format error"; *p != '\0'; p++ )
1975         put_a_char(state, *p);
1976     }
1977 
1978   }
1979 
1980 }
1981 
1982 
1983 static void putToTheStream(void *state, Char c) {
1984     PutChrTo((TypOutputFile *)state, c);
1985 }
1986 
1987 static void
1988 PrTo(TypOutputFile * stream, const Char * format, Int arg1, Int arg2)
1989 {
1990   FormatOutput( putToTheStream, stream, format, arg1, arg2);
1991 }
1992 
1993 void Pr (
1994          const Char *      format,
1995          Int                 arg1,
1996          Int                 arg2 )
1997 {
1998 #ifdef HPCGAP
1999     if (!IO()->Output) {
2000         OpenDefaultOutput();
2001     }
2002 #endif
2003     PrTo(IO()->Output, format, arg1, arg2);
2004 }
2005 
2006 typedef struct {
2007     Char * TheBuffer;
2008     UInt   TheCount;
2009     UInt   TheLimit;
2010 } BufferState;
2011 
2012 static void putToTheBuffer(void *state, Char c)
2013 {
2014   BufferState *buf = (BufferState *)state;
2015   if (buf->TheCount < buf->TheLimit)
2016     buf->TheBuffer[buf->TheCount++] = c;
2017 }
2018 
2019 void SPrTo(Char *buffer, UInt maxlen, const Char *format, Int arg1, Int arg2)
2020 {
2021   BufferState buf = { buffer, 0, maxlen };
2022   FormatOutput(putToTheBuffer, &buf, format, arg1, arg2);
2023   putToTheBuffer(&buf, '\0');
2024 }
2025 
2026 
2027 static Obj FuncINPUT_FILENAME(Obj self)
2028 {
2029     if (IO()->Input == 0)
2030         return MakeImmString("*defin*");
2031 
2032     UInt gapnameid = GetInputFilenameID();
2033     return GetCachedFilename(gapnameid);
2034 }
2035 
2036 static Obj FuncINPUT_LINENUMBER(Obj self)
2037 {
2038     return INTOBJ_INT(IO()->Input ? IO()->Input->number : 0);
2039 }
2040 
2041 static Obj FuncSET_PRINT_FORMATTING_STDOUT(Obj self, Obj val)
2042 {
2043     IO()->OutputStack[1]->format = (val != False);
2044     return val;
2045 }
2046 
2047 static Obj FuncIS_INPUT_TTY(Obj self)
2048 {
2049     GAP_ASSERT(IO()->Input);
2050     if (IO()->Input->isstream)
2051         return False;
2052     return SyBufIsTTY(IO()->Input->file) ? True : False;
2053 }
2054 
2055 static Obj FuncIS_OUTPUT_TTY(Obj self)
2056 {
2057     GAP_ASSERT(IO()->Output);
2058     if (IO()->Output->isstream)
2059         return False;
2060     return SyBufIsTTY(IO()->Output->file) ? True : False;
2061 }
2062 
2063 static Obj FuncGET_FILENAME_CACHE(Obj self)
2064 {
2065   return CopyObj(FilenameCache, 1);
2066 }
2067 
2068 static StructGVarFunc GVarFuncs [] = {
2069 
2070     GVAR_FUNC(ToggleEcho, 0, ""),
2071     GVAR_FUNC(CPROMPT, 0, ""),
2072     GVAR_FUNC(PRINT_CPROMPT, 1, "prompt"),
2073     GVAR_FUNC(INPUT_FILENAME, 0, ""),
2074     GVAR_FUNC(INPUT_LINENUMBER, 0, ""),
2075     GVAR_FUNC(SET_PRINT_FORMATTING_STDOUT, 1, "format"),
2076     GVAR_FUNC(IS_INPUT_TTY, 0, ""),
2077     GVAR_FUNC(IS_OUTPUT_TTY, 0, ""),
2078     GVAR_FUNC(GET_FILENAME_CACHE, 0, ""),
2079     { 0, 0, 0, 0, 0 }
2080 
2081 };
2082 
2083 /****************************************************************************
2084 **
2085 *F  InitLibrary( <module> ) . . . . . . .  initialise library data structures
2086 */
2087 static Int InitLibrary (
2088     StructInitInfo *    module )
2089 {
2090 #ifdef HPCGAP
2091     FilenameCache = NewAtomicList(T_ALIST, 0);
2092 #else
2093     FilenameCache = NEW_PLIST(T_PLIST, 0);
2094 #endif
2095 
2096     /* init filters and functions                                          */
2097     InitGVarFuncsFromTable( GVarFuncs );
2098 
2099     /* return success                                                      */
2100     return 0;
2101 }
2102 
2103 #if !defined(HPCGAP)
2104 static Char OutputFilesStreamCookie[MAX_OPEN_FILES][9];
2105 static Char InputFilesStreamCookie[MAX_OPEN_FILES][9];
2106 static Char InputFilesSlineCookie[MAX_OPEN_FILES][9];
2107 #endif
2108 
2109 static Int InitKernel (
2110     StructInitInfo *    module )
2111 {
2112     IO()->Input = 0;
2113     IO()->Output = 0;
2114     IO()->InputLog = 0;
2115     IO()->OutputLog = 0;
2116 
2117 #if !defined(HPCGAP)
2118     for (Int i = 0; i < MAX_OPEN_FILES; i++) {
2119         IO()->InputStack[i] = &InputFiles[i];
2120         IO()->OutputStack[i] = &OutputFiles[i];
2121     }
2122 #endif
2123 
2124     OpenInput("*stdin*");
2125     OpenOutput("*stdout*");
2126 
2127     InitGlobalBag( &FilenameCache, "FilenameCache" );
2128 
2129 #ifdef HPCGAP
2130     /* Initialize default stream functions */
2131     DeclareGVar(&DEFAULT_INPUT_STREAM, "DEFAULT_INPUT_STREAM");
2132     DeclareGVar(&DEFAULT_OUTPUT_STREAM, "DEFAULT_OUTPUT_STREAM");
2133 
2134 #else
2135     // Initialize cookies for streams. Also initialize the cookies for the
2136     // GAP strings which hold the latest lines read from the streams  and the
2137     // name of the current input file. For HPC-GAP we don't need the cookies
2138     // anymore, since the data got moved to thread-local storage.
2139     for (Int i = 0; i < MAX_OPEN_FILES; i++) {
2140         strxcpy(OutputFilesStreamCookie[i], "ostream0", sizeof(OutputFilesStreamCookie[i]));
2141         OutputFilesStreamCookie[i][7] = '0' + i;
2142         InitGlobalBag(&(OutputFiles[i].stream), &(OutputFilesStreamCookie[i][0]));
2143 
2144         strxcpy(InputFilesStreamCookie[i], "istream0", sizeof(InputFilesStreamCookie[i]));
2145         InputFilesStreamCookie[i][7] = '0' + i;
2146         InitGlobalBag(&(InputFiles[i].stream), &(InputFilesStreamCookie[i][0]));
2147 
2148         strxcpy(InputFilesSlineCookie[i], "isline 0", sizeof(InputFilesSlineCookie[i]));
2149         InputFilesSlineCookie[i][7] = '0' + i;
2150         InitGlobalBag(&(InputFiles[i].sline), &(InputFilesSlineCookie[i][0]));
2151     }
2152 
2153     /* tell GASMAN about the global bags                                   */
2154     InitGlobalBag(&(IO()->InputLogFileOrStream.stream),
2155                   "src/scanner.c:InputLogFileOrStream");
2156     InitGlobalBag(&(IO()->OutputLogFileOrStream.stream),
2157                   "src/scanner.c:OutputLogFileOrStream");
2158 #endif
2159 
2160     /* import functions from the library                                   */
2161     ImportFuncFromLibrary( "ReadLine", &ReadLineFunc );
2162     ImportFuncFromLibrary( "WriteAll", &WriteAllFunc );
2163     ImportFuncFromLibrary( "IsInputTextStringRep", &IsStringStream );
2164     InitCopyGVar( "PrintPromptHook", &PrintPromptHook );
2165     InitCopyGVar( "EndLineHook", &EndLineHook );
2166     InitFopyGVar( "PrintFormattingStatus", &PrintFormattingStatus);
2167 
2168     InitHdlrFuncsFromTable( GVarFuncs );
2169     /* return success                                                      */
2170     return 0;
2171 }
2172 
2173 /****************************************************************************
2174 **
2175 *F  InitInfoIO() . . . . . . . . . . . . . . . . table of init functions
2176 */
2177 static StructInitInfo module = {
2178     // init struct using C99 designated initializers; for a full list of
2179     // fields, please refer to the definition of StructInitInfo
2180     .type = MODULE_BUILTIN,
2181     .name = "scanner",
2182     .initKernel = InitKernel,
2183     .initLibrary = InitLibrary,
2184 
2185     .moduleStateSize = sizeof(struct IOModuleState),
2186     .moduleStateOffsetPtr = &IOStateOffset,
2187 };
2188 
2189 StructInitInfo * InitInfoIO ( void )
2190 {
2191     return &module;
2192 }
2193