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