/**************************************************************************** ** ** This file is part of GAP, a system for computational discrete algebra. ** ** Copyright of GAP belongs to its developers, whose names are too numerous ** to list here. Please refer to the COPYRIGHT file for details. ** ** SPDX-License-Identifier: GPL-2.0-or-later ** ** This file implements operating system dependent functions dealing with ** file and stream operations.
*/
// ensure we can access large files #define _FILE_OFFSET_BITS 64
#ifdef HAVE_SELECT // Only for the Hook handler calls: #include <sys/time.h> #endif
#ifdef HAVE_SIGNAL_H // signal handling functions #include <signal.h> #endif
#ifdef HAVE_SYS_IOCTL_H #include <sys/ioctl.h> // for TIOCGWINSZ #endif
#ifdef HAVE_LIBREADLINE // the following two definitions silence some compiler warnings in the // readline headers; the first one suppresses the definition of a few // deprecated (!) and unused typedefs; the second indicates that stdarg.h is // available (since compiling GAP requires C99, this is guaranteed) #define _FUNCTION_DEF #define HAVE_STDARG_H #include <readline/readline.h> #endif
#include <zlib.h>
#include <sys/utsname.h>
// 'EndLineHook' is a GAP-level variable which can be set to a function to be // called at end of each command line (i.e. after the user presses enter). // If not bound, nothing is done. static Obj EndLineHook = 0;
/**************************************************************************** ** *V syBuf . . . . . . . . . . . . . . buffer and other info for files, local ** ** 'syBuf' is an array used as buffers for file I/O to prevent the C I/O ** routines from allocating their buffers using 'malloc', which would ** otherwise confuse Gasman. ** ** ** Actually these days SyBuf just stores various file info. SyBuffers ** stores buffers for the relatively few files that need them.
*/
// The type of file stored in a 'SYS_SY_BUF' typedefenum {
unused_socket, // Socket is free
raw_socket, // Plain UNIX socket stored in 'fp'
gzip_socket // A gzFile handled by zlib stored in 'gzfp'
} GAPSocketType;
GAP_STATIC_ASSERT(unused_socket == 0, "unused_socket must be zero");
typedefstruct { // gzfp is used if type == gzip_socket
gzFile gzfp;
// file descriptor for this file (only used if type == raw_socket) int fp;
// file descriptor for the echo (only used if type == raw_socket) int echo;
// file is either a plain descriptor, pipe or gzipped
GAPSocketType type;
// set to 1 by any read operation that hits eof; reset to 0 by a // subsequent successful read BOOL ateof;
// records that last character read was \r for cygwin and other systems // that need end-of-line hackery BOOL crlast;
// if non-negative then this file has a buffer in syBuffers[bufno]; if // negative, this file may not be buffered int bufno;
// set when this fid is a *stdin* or *errin* and really is a tty BOOL isTTY;
} SYS_SY_BUF;
// and close it again
SyFclose( fid ); // Emulate a signed shift: if (crc & 0x80000000L) return (Int4) ((crc >> 4) | 0xF0000000L); else return (Int4) (crc >> 4);
}
<Description> <Index>hash function</Index> <Index>checksum</Index> This function computes a CRC (cyclic redundancy check) number from a string <A>str</A>. See also <Ref Func="CrcFile"/> and <Ref Func="HexSHA256"/>. <Example> gap> CrcString("GAP example string"); -50451670 </Example> </Description> </ManSection>
<#/GAPDoc>
*/
// And here we include a variant working on a GAP string static Obj FuncCrcString(Obj self, Obj str)
{
UInt4 crc;
UInt4 old;
UInt4 new;
UInt4 i, len; constChar *ptr;
Int4 ch; Int seen_nl;
RequireStringRep(SELF_NAME, str);
ptr = CONST_CSTR_STRING(str);
len = GET_LEN_STRING(str);
crc = 0x12345678L;
seen_nl = 0; for (i = 0; i < len; i++) {
ch = (Int4)(ptr[i]); if ( ch == '\377' || ch == '\n' || ch == '\r' )
ch = '\n'; if ( ch == '\n' ) { if ( seen_nl ) continue; else
seen_nl = 1;
} else
seen_nl = 0;
old = (crc >> 8) & 0x00FFFFFFL; new = syCcitt32[ ( (UInt4)( crc ^ ch ) ) & 0xff ];
crc = old ^ new;
} if ( crc == 0 ) {
crc = 1;
} return INTOBJ_INT(((Int4) crc) >> 4);
}
// Get OS Kernel version. Used to discover if GAP is running inside // 'Windows Subsystem for Linux'
Obj SyGetOsRelease(void)
{
Obj r = NEW_PREC(0); struct utsname buf; if (!uname(&buf)) {
AssPRec(r, RNamName("sysname"), MakeImmString(buf.sysname));
AssPRec(r, RNamName("nodename"), MakeImmString(buf.nodename));
AssPRec(r, RNamName("release"), MakeImmString(buf.release));
AssPRec(r, RNamName("version"), MakeImmString(buf.version));
AssPRec(r, RNamName("machine"), MakeImmString(buf.machine));
}
/**************************************************************************** ** *F syWinPut( <fid>, <cmd>, <str> ) . . . . send a line to the window handler ** ** 'syWinPut' send the command <cmd> and the string <str> to the window ** handler associated with the file identifier <fid>. In the string <str> ** '@' characters are duplicated, and control characters are converted to ** '@<chr>', e.g., <newline> is converted to '@J'.
*/ void syWinPut ( Int fid, constChar * cmd, constChar * str )
{ Char tmp [130]; // temporary buffer constChar * s; // pointer into the string Char * t; // pointer into the temporary
// if not running under a window handler, don't do anything if (!SyWindow || 4 <= fid || syBuf[fid].type == gzip_socket) return;
// print the cmd
echoandcheck( fid, cmd, strlen(cmd) );
/**************************************************************************** ** *F SyWinCmd( <str>, <len> ) . . . . . . . . . . . . . execute a window cmd ** ** 'SyWinCmd' send the command <str> to the window handler (<len> is ** ignored). In the string <str> '@' characters are duplicated, and control ** characters are converted to '@<chr>', e.g., <newline> is converted to ** '@J'. Then 'SyWinCmd' waits for the window handlers answer and returns ** that string.
*/ staticChar WinCmdBuffer[8000];
constChar * SyWinCmd ( constChar * str,
UInt len )
{ Char buf [130]; // temporary buffer constChar * s; // pointer into the string constChar * bb; // pointer into the temporary Char * b; // pointer into the temporary
UInt i; // loop variable #ifdef SYS_IS_CYGWIN32
UInt len1; // temporary storage for len #endif
// if not running under a window handler, don't do nothing if ( ! SyWindow ) return"I1+S52+No Window Handler Present";
// compute the length of the (expanded) string (and ignore argument)
len = 0; for ( s = str; *s != '\0'; s++ )
len += 1 + (*s == '@' || (CTR('A') <= *s && *s <= CTR('Z')));
// send the length to the window handler
b = buf; for ( ; 0 < len; len /= 10 ) {
*b++ = (len % 10) + '0';
}
*b++ = '+';
*b++ = '\0';
syWinPut( 1, "@w", buf );
// send the string to the window handler
syWinPut( 1, "", str );
// read the length of the answer
b = WinCmdBuffer;
i = 3; while ( 0 < i ) {
len = read( 0, b, i );
i -= len;
b += len;
} if ( WinCmdBuffer[0] != '@' || WinCmdBuffer[1] != 'a' ) return"I1+S41+Illegal Answer";
b = WinCmdBuffer+2; for ( i=1,len=0; '0' <= *b && *b <= '9'; i *= 10 ) {
len += (*b-'0')*i; while ( read( 0, b, 1 ) != 1 ) ;
}
// read the arguments of the answer
b = WinCmdBuffer;
i = len; #ifdef SYS_IS_CYGWIN32
len1 = len; while ( 0 < i ) {
len = read( 0, b, i );
b += len;
i -= len;
s += len;
}
len = len1; #else while ( 0 < i ) {
len = read( 0, b, i );
i -= len;
s += len;
} #endif
// Mark a member of syBuf as unused staticvoid SyBufMarkUnused(Int i)
{
GAP_ASSERT(i >= 0 && i < ARRAY_SIZE(syBuf));
memset(&(syBuf[i]), 0, sizeof(syBuf[i]));
syBuf[i].type = unused_socket;
}
// There is no explicit method to mark syBufs as used, // they are marked as used when created.
// Check if a member of syBuf is in use staticInt SyBufInUse(Int i)
{ if (i >= 0 && i < ARRAY_SIZE(syBuf)) return syBuf[i].type != unused_socket; return 0;
}
/**************************************************************************** ** *F SyFopen( <name>, <mode>, <transparent_compress> ) *F open the file with name <name> ** ** The function 'SyFopen' is called to open the file with the name <name>. ** If <mode> is "r" it is opened for reading, in this case it must exist. ** If <mode> is "w" it is opened for writing, it is created if necessary. ** If <mode> is "a" it is opened for appending, i.e., it is not truncated. ** ** 'SyFopen' returns an integer used by the scanner to identify the file. ** 'SyFopen' returns -1 if it cannot open the file. ** ** The following standard files names and file identifiers are guaranteed: ** 'SyFopen( "*stdin*", "r", ..)' returns 0, the standard input file. ** 'SyFopen( "*stdout*","w", ..)' returns 1, the standard outpt file. ** 'SyFopen( "*errin*", "r", ..)' returns 2, the brk loop input file. ** 'SyFopen( "*errout*","w", ..)' returns 3, the error messages file. ** ** If it is necessary to adjust the filename this should be done here, the ** filename convention used in GAP is that '/' is the directory separator. ** ** Right now GAP does not read nonascii files, but if this changes sometimes ** 'SyFopen' must adjust the mode argument to open the file in binary mode. ** ** If <transparent_compress> is TRUE, files with names ending '.gz' will be ** automatically compressed/decompressed using gzip.
*/
Int SyFopen(constChar * name, constChar * mode, BOOL transparent_compress)
{ Int fid; Char namegz [1024]; int flags = 0;
/**************************************************************************** ** *F SyFclose( <fid> ) . . . . . . . . . . . . . . . . . close the file <fid> ** ** 'SyFclose' closes the file with the identifier <fid> which is obtained ** from 'SyFopen'.
*/ Int SyFclose ( Int fid )
{ // check file identifier if ( ARRAY_SIZE(syBuf) <= fid || fid < 0 ) {
fputs("gap: panic 'SyFclose' asked to close illegal fid!\n",stderr); return -1;
} if ( !SyBufInUse(fid) ) {
fputs("gap: panic 'SyFclose' asked to close closed file!\n",stderr); return -1;
}
// refuse to close the standard files if ( fid == 0 || fid == 1 || fid == 2 || fid == 3 ) { return -1;
}
HashLock(&syBuf); // try to close the file if (syBuf[fid].type == raw_socket && close(syBuf[fid].fp) == EOF) {
fputs("gap: 'SyFclose' cannot close file, ",stderr);
fputs("maybe your file system is full?\n",stderr);
SyBufMarkUnused(fid);
HashUnlock(&syBuf); return -1;
}
if (syBuf[fid].type == gzip_socket) { if (gzclose(syBuf[fid].gzfp) < 0) {
fputs("gap: 'SyFclose' cannot close compressed file", stderr);
}
}
// mark the buffer as unused if (syBuf[fid].bufno >= 0)
syBuffers[syBuf[fid].bufno].inuse = FALSE;
SyBufMarkUnused(fid);
HashUnlock(&syBuf); return 0;
}
/**************************************************************************** ** *F SyIsEndOfFile( <fid> ) . . . . . . . . . . . . . . . end of file reached
*/ Int SyIsEndOfFile ( Int fid )
{ // check file identifier if ( !SyBufInUse(fid) ) { return -1;
}
// *stdin* and *errin* are never at end of file if ( fid < 4 ) return 0;
/**************************************************************************** ** *F syStartraw( <fid> ) . . . . . . start raw mode on input file <fid>, local ** ** The following four functions are the actual system dependent part of ** 'SyFgets'. ** ** 'syStartraw' tries to put the file with the file identifier <fid> into ** raw mode. I.e., disabling echo and any buffering. It also finds a ** place to put the echoing for 'syEchoch'. If 'syStartraw' succeeds it ** returns 1, otherwise, e.g., if the <fid> is not a terminal, it returns 0. ** ** 'syStopraw' stops the raw mode for the file <fid> again, switching it ** back into whatever mode the terminal had before 'syStartraw'. ** ** 'syGetch' reads one character from the file <fid>, which must have been ** turned into raw mode before, and returns it. ** ** 'syEchoch' puts the character <ch> to the file opened by 'syStartraw' for ** echoing. Note that if the user redirected 'stdout' but not 'stdin', the ** echo for 'stdin' must go to 'ttyname(fileno(stdin))' instead of 'stdout'.
*/
/**************************************************************************** ** For UNIX System V, input/output redirection and typeahead are supported. ** We turn off input buffering and canonical input editing and also echo. ** Because we leave the signals enabled we have to disable the characters ** for interrupt and quit, which are usually set to '<ctr>-C' and '<ctr>-B'. ** We also turn off the xon/xoff start and stop characters, which are ** usually set to '<ctr>-S' and '<ctr>-Q' so we can get those characters. ** We do not turn of signals 'ISIG' because we want to catch stop and ** continue signals if this particular version of UNIX supports them, so we ** can turn the terminal line back to cooked mode before stopping GAP.
*/ staticstruct termios syOld, syNew; // old and new terminal state
UInt syStartraw ( Int fid )
{ // if running under a window handler, tell it that we want to read if ( SyWindow ) { if ( fid == 0 ) { syWinPut( fid, "@i", "" ); return 1; } elseif ( fid == 2 ) { syWinPut( fid, "@e", "" ); return 1; } else { return 0; }
}
// try to get the terminal attributes, will fail if not terminal constint fd = SyBufFileno(fid);
GAP_ASSERT(fd >= 0); if ( tcgetattr( fd, &syOld) == -1 ) return 0;
// disable interrupt, quit, start and stop output characters
syNew = syOld;
syNew.c_cc[VINTR] = 0377;
syNew.c_cc[VQUIT] = 0377; /*C 27-Nov-90 martin changing '<ctr>S' and '<ctr>Q' does not work */ /*C syNew.c_iflag &= ~(IXON|INLCR|ICRNL); */
syNew.c_iflag &= ~(INLCR|ICRNL);
// disable input buffering, line editing and echo
syNew.c_cc[VMIN] = 1;
syNew.c_cc[VTIME] = 0;
syNew.c_lflag &= ~(ECHO|ICANON);
#ifdef SIGTSTP // install signal handler for stop
syFid = fid;
signal( SIGTSTP, syAnswerTstp ); #endif
// indicate success return 1;
}
/**************************************************************************** ** *F syStopraw( <fid> ) . . . . . . stop raw mode on input file <fid>, local
*/
void syStopraw ( Int fid )
{ // if running under a window handler, don't do nothing if ( SyWindow ) return;
#ifdef SIGTSTP // remove signal handler for stop
signal( SIGTSTP, SIG_DFL ); #endif
// enable input buffering, line editing and echo again constint fd = SyBufFileno(fid);
GAP_ASSERT(fd >= 0); if (tcsetattr(fd, TCSANOW, &syOld) == -1)
fputs("gap: 'tcsetattr' could not turn off raw mode!\n",stderr);
}
/**************************************************************************** ** *F SyIsIntr() . . . . . . . . . . . . . . . . check whether user hit <ctr>-C ** ** 'SyIsIntr' is called from the evaluator at regular intervals to check ** whether the user hit '<ctr>-C' to interrupt a computation. ** ** 'SyIsIntr' returns 1 if the user typed '<ctr>-C' and 0 otherwise.
*/
/**************************************************************************** ** *f SyIsIntr() ** ** For UNIX we install 'syAnswerIntr' to answer interrupt 'SIGINT'. If ** two interrupts occur within 1 second 'syAnswerIntr' exits GAP.
*/ #ifdef HAVE_SIGNAL
static UInt syLastIntr; // time of the last interrupt
#ifdef HAVE_LIBREADLINE // ignore during readline if (doingReadline) return; #endif
// get the current wall clock time
nowIntr = time(0);
// if the last '<ctr>-C' was less than a second ago, exit GAP if ( syLastIntr && nowIntr-syLastIntr < 1 ) {
fputs("gap: you hit '-C' twice in a second, goodbye.\n",stderr);
SyExit( 1 );
}
// remember time of this interrupt
syLastIntr = nowIntr;
#ifdef HAVE_SIGNAL // interrupt the executor
InterruptExecStat(); #endif
}
isIntr = (syLastIntr != 0); #ifdef HPCGAP /* The following write has to be conditional to avoid serious * performance degradation on shared memory (especially NUMA) * architectures when multiple threads all try to write to the same * location at the same time. Branch prediction can be expected to * be near perfect.
*/ if (isIntr) syLastIntr = 0; #else
syLastIntr = 0; #endif return isIntr;
}
#endif
/**************************************************************************** ** *F InitWindowSize() . . . . . . . get screen size from termcap or TIOCGWINSZ ** ** For UNIX we install 'syWindowChangeIntr' to answer 'SIGWINCH'.
*/
#ifdef USE_TERMCAP // note that if we define TERMCAP, this has to be linked with -ltermcap // maybe that is -ltermlib on some SYSV machines if (SyNrRows <= 0 || SyNrCols <= 0) { // this failed - next attempt: try to find info in TERMCAP char *sp; char bp[1024];
// write the character to the associate echo output device
ch2 = ch;
echoandcheck( fid, (char*)&ch2, 1 );
// if running under a window handler, duplicate '@' if ( SyWindow && ch == '@' ) {
ch2 = ch;
echoandcheck( fid, (char*)&ch2, 1 );
}
}
/**************************************************************************** ** *F SyEchoch( <ch>, <fid> ) . . . . . . . . . . . . . echo a char from <fid>
*/ Int SyEchoch ( Int ch, Int fid )
{ // check file identifier if ( !SyBufInUse(fid) ) { return -1;
}
syEchoch(ch,fid); return 0;
}
/**************************************************************************** ** *F syEchos( <ch>, <fid> ) . . . . . . . . . . . echo a char to <fid>, local
*/
/**************************************************************************** ** *f syEchos( <ch>, <fid> )
*/ staticvoid syEchos(constChar * str, Int fid)
{ // if running under a window handler, send the line to it if ( SyWindow && fid < 4 )
syWinPut( fid, (fid == 1 ? "@n" : "@f"), str );
// otherwise, write it to the associate echo output device else
echoandcheck(fid, str, strlen(str) );
}
/**************************************************************************** ** *F SyFputs( <line>, <fid> ) . . . . . . . . write a line to the file <fid> ** ** 'SyFputs' is called to put the <line> to the file identified by <fid>.
*/ static UInt syNrchar; // nr of chars already on the line staticChar syPrompt[MAXLENOUTPUTLINE]; // characters already on the line
/**************************************************************************** ** *F SyFtell( <fid> ) . . . . . . . . . . . . . . . . . . position of stream
*/ Int SyFtell ( Int fid )
{ // check file identifier if ( !SyBufInUse(fid) ) { return -1;
}
Int ret;
switch (syBuf[fid].type) { case raw_socket:
ret = (Int)lseek(syBuf[fid].fp, 0, SEEK_CUR); break; case gzip_socket:
ret = (Int)gzseek(syBuf[fid].gzfp, 0, SEEK_CUR); break; case unused_socket: default: return -1;
}
// Need to account for characters in buffer if (syBuf[fid].bufno >= 0) {
UInt bufno = syBuf[fid].bufno;
ret -= syBuffers[bufno].buflen - syBuffers[bufno].bufstart;
} return ret;
}
/**************************************************************************** ** *F SyFseek( <fid>, <pos> ) . . . . . . . . . . . seek a position of stream
*/ Int SyFseek ( Int fid, Int pos )
{ // check file identifier if ( !SyBufInUse(fid) ) { return -1;
}
switch (syBuf[fid].type) { case raw_socket: return (Int)lseek(syBuf[fid].fp, pos, SEEK_SET); case gzip_socket: return (Int)gzseek(syBuf[fid].gzfp, pos, SEEK_SET); case unused_socket: default: return -1;
}
}
/**************************************************************************** ** *F syGetchTerm( <fid> ) . . . . . . . . . . . . . . . . . get a char from <fid> ** ** 'SyGetchTerm' reads a character from <fid>, which is already switched ** to raw mode if it is *stdin* or *errin*.
*/
/**************************************************************************** ** *f syGetchTerm( <fid> ) . . . . . . . . . . . . . . . . . . . . . UNIX ** ** This version should be called if the input is stdin and command-line editing ** etc. is switched on. It handles possible messages from xgap and systems ** that return odd things rather than waiting for a key **
*/
/* In the cygwin environment it is not predictable if text files get the * '\r' in their line ends filtered out *before* GAP sees them. This leads * to problem with continuation of strings or integers over several lines in * GAP input. Therefore we introduce a hack which removes such '\r's * before '\n's on such a system. Add here if there are other systems with * a similar problem.
*/
// if running under a window handler, handle special characters if ( SyWindow && ch == '@' ) { do { while ( (ret = SyRead(fid, &ch, 1)) == -1 &&
errno == EAGAIN ) ; if (ret <= 0) return EOF;
} while ( ch < '@' || 'z' < ch ); if ( ch == 'y' ) { do { while ( (ret = SyRead(fid, &ch, 1)) == -1 &&
errno == EAGAIN ); if (ret <= 0) return EOF;
} while ( ch < '@' || 'z' < ch );
str[0] = ch;
str[1] = 0;
syWinPut( syBuf[fid].echo, "@s", str );
ch = syGetchTerm(fid);
} elseif ( 'A' <= ch && ch <= 'Z' )
ch = CTR(ch);
}
#ifdef LINE_END_HACK /* A hack for non ANSI-C confirming systems which deliver \r or \r\n * line ends. These are translated to \n here.
*/ if (ch == '\n') { if (syBuf[fid].crlast) {
syBuf[fid].crlast = FALSE; goto tryagain;
} else return (UChar)'\n';
} if (ch == '\r') {
syBuf[fid].crlast = TRUE; return (Int)'\n';
} // We saw a '\r' without a '\n'
syBuf[fid].crlast = FALSE; #endif// line end hack
#ifdef LINE_END_HACK /* A hack for non ANSI-C confirming systems which deliver \r or \r\n * line ends. These are translated to \n here.
*/ if (ch == '\n') { if (syBuf[fid].crlast) {
syBuf[fid].crlast = FALSE; goto tryagain;
} else return (UChar)'\n';
} if (ch == '\r') {
syBuf[fid].crlast = TRUE; return (Int)'\n';
} // We saw a '\r' without a '\n'
syBuf[fid].crlast = FALSE; #endif// line end hack
/**************************************************************************** ** *F SyGetch( <fid> ) . . . . . . . . . . . . . . . . . get a char from <fid> ** ** 'SyGetch' reads a character from <fid>, which is switch to raw mode if it ** is *stdin* or *errin*.
*/ Int SyGetch ( Int fid )
{ Int ch;
// if we are reading stdin or errin use raw mode if ( fid == 0 || fid == 2 ) {
syStartraw(fid);
}
ch = syGetch(fid); if ( fid == 0 || fid == 2 ) {
syStopraw(fid);
} return ch;
}
/**************************************************************************** ** *F SyFgets( <line>, <length>, <fid> ) . . . . . get a line from file <fid> ** ** 'SyFgets' is called to read a line from the file with identifier <fid>. ** 'SyFgets' (like 'fgets') reads characters until either <length>-1 chars ** have been read or until a <newline> or an <eof> character is encountered. ** It retains the '\n' (unlike 'gets'), if any, and appends '\0' to <line>. ** 'SyFgets' returns <line> if any char has been read, otherwise '(char*)0'. ** ** 'SyFgets' allows to edit the input line if the file <fid> refers to a ** terminal with the following commands: ** ** <ctr>-A move the cursor to the beginning of the line. ** <esc>-B move the cursor to the beginning of the previous word. ** <ctr>-B move the cursor backward one character. ** <ctr>-F move the cursor forward one character. ** <esc>-F move the cursor to the end of the next word. ** <ctr>-E move the cursor to the end of the line. ** ** <ctr>-H, <del> delete the character left of the cursor. ** <ctr>-D delete the character under the cursor. ** <ctr>-K delete up to the end of the line. ** <esc>-D delete forward to the end of the next word. ** <esc>-<del> delete backward to the beginning of the last word. ** <ctr>-X delete entire input line, and discard all pending input. ** <ctr>-Y insert (yank) a just killed text. ** ** <ctr>-T exchange (twiddle) current and previous character. ** <esc>-U uppercase next word. ** <esc>-L lowercase next word. ** <esc>-C capitalize next word. ** ** <tab> complete the identifier before the cursor. ** <ctr>-L insert last input line before current character. ** <ctr>-P redisplay the last input line, another <ctr>-P will redisplay ** the line before that, etc. If the cursor is not in the first ** column only the lines starting with the string to the left of ** the cursor are taken. The history is limited to ~8000 chars. ** <ctr>-N Like <ctr>-P but goes the other way round through the history ** <esc>-< goes to the beginning of the history. ** <esc>-> goes to the end of the history. ** <ctr>-O accept this line and perform a <ctr>-N. ** ** <ctr>-V enter next character literally. ** <ctr>-U execute the next command 4 times. ** <esc>-<num> execute the next command <num> times. ** <esc>-<ctr>-L repaint input line. ** ** Not yet implemented commands: ** ** <ctr>-S search interactive for a string forward. ** <ctr>-R search interactive for a string backward. ** <esc>-Y replace yanked string with previously killed text. ** <ctr>-_ undo a command. ** <esc>-T exchange two words.
*/
static UInt syCTRO; // number of '<ctr>-O' pending static UInt syESCN; // number of '<Esc>-N' pending
UInt FreezeStdin; // When true, ignore if any new input from stdin // This is used to stop HPC-GAP from reading stdin // while forked subprocesses are running.
#ifdef HAVE_SELECT
static Obj OnCharReadHookActive = 0; // if bound the hook is active static Obj OnCharReadHookInFds = 0; // a list of UNIX file descriptors for reading static Obj OnCharReadHookInFuncs = 0; // a list of GAP functions with 0 args static Obj OnCharReadHookOutFds = 0; // a list of UNIX file descriptors for writing static Obj OnCharReadHookOutFuncs = 0;// a list of GAP functions with 0 args static Obj OnCharReadHookExcFds = 0; // a list of UNIX file descriptors static Obj OnCharReadHookExcFuncs = 0;// a list of GAP functions with 0 args
staticvoid HandleCharReadHook(int stdinfd) /* This is called directly before a character is read from stdin in the case * of an interactive session with command line editing. We have to return * as soon as stdin is ready to read! We just use `select' and care for
* handlers for streams. */
{
fd_set infds,outfds,excfds; int n,maxfd; Int i,j;
Obj o; staticint WeAreAlreadyInHere = 0;
// Just to make sure: if (WeAreAlreadyInHere) return;
WeAreAlreadyInHere = 1;
while (1) { // breaks when fd becomes ready
FD_ZERO(&infds);
FD_ZERO(&outfds);
FD_ZERO(&excfds);
FD_SET(stdinfd,&infds);
maxfd = stdinfd; // Handle input file descriptors: if (OnCharReadHookInFds != (Obj) 0 &&
IS_PLIST(OnCharReadHookInFds) &&
OnCharReadHookInFuncs != (Obj) 0 &&
IS_PLIST(OnCharReadHookInFuncs)) { for (i = 1;i <= LEN_PLIST(OnCharReadHookInFds);i++) {
o = ELM_PLIST(OnCharReadHookInFds,i); if (o != (Obj) 0 && IS_INTOBJ(o)) {
j = INT_INTOBJ(o); // a UNIX file descriptor
FD_SET(j,&infds); if (j > maxfd) maxfd = j;
}
}
} // Handle output file descriptors: if (OnCharReadHookOutFds != (Obj) 0 &&
IS_PLIST(OnCharReadHookOutFds) &&
OnCharReadHookOutFuncs != (Obj) 0 &&
IS_PLIST(OnCharReadHookOutFuncs)) { for (i = 1;i <= LEN_PLIST(OnCharReadHookOutFds);i++) {
o = ELM_PLIST(OnCharReadHookOutFds,i); if (o != (Obj) 0 && IS_INTOBJ(o)) {
j = INT_INTOBJ(o); // a UNIX file descriptor
FD_SET(j,&outfds); if (j > maxfd) maxfd = j;
}
}
} // Handle exception file descriptors: if (OnCharReadHookExcFds != (Obj) 0 &&
IS_PLIST(OnCharReadHookExcFds) &&
OnCharReadHookExcFuncs != (Obj) 0 &&
IS_PLIST(OnCharReadHookExcFuncs)) { for (i = 1;i <= LEN_PLIST(OnCharReadHookExcFds);i++) {
o = ELM_PLIST(OnCharReadHookExcFds,i); if (o != (Obj) 0 && IS_INTOBJ(o)) {
j = INT_INTOBJ(o); // a UNIX file descriptor
FD_SET(j,&excfds); if (j > maxfd) maxfd = j;
}
}
}
n = select(maxfd+1,&infds,&outfds,&excfds,NULL); if (n >= 0) { // Now run through the lists and call functions if ready:
if (OnCharReadHookInFds != (Obj) 0 &&
IS_PLIST(OnCharReadHookInFds) &&
OnCharReadHookInFuncs != (Obj) 0 &&
IS_PLIST(OnCharReadHookInFuncs)) { for (i = 1;i <= LEN_PLIST(OnCharReadHookInFds);i++) {
o = ELM_PLIST(OnCharReadHookInFds,i); if (o != (Obj) 0 && IS_INTOBJ(o)) {
j = INT_INTOBJ(o); // a UNIX file descriptor if (FD_ISSET(j,&infds)) {
o = ELM_PLIST(OnCharReadHookInFuncs,i); if (o != (Obj) 0 && IS_FUNC(o))
Call1ArgsInNewReader(o,INTOBJ_INT(i));
}
}
}
} // Handle output file descriptors: if (OnCharReadHookOutFds != (Obj) 0 &&
IS_PLIST(OnCharReadHookOutFds) &&
OnCharReadHookOutFuncs != (Obj) 0 &&
IS_PLIST(OnCharReadHookOutFuncs)) { for (i = 1;i <= LEN_PLIST(OnCharReadHookOutFds);i++) {
o = ELM_PLIST(OnCharReadHookOutFds,i); if (o != (Obj) 0 && IS_INTOBJ(o)) {
j = INT_INTOBJ(o); // a UNIX file descriptor if (FD_ISSET(j,&outfds)) {
o = ELM_PLIST(OnCharReadHookOutFuncs,i); if (o != (Obj) 0 && IS_FUNC(o))
Call1ArgsInNewReader(o,INTOBJ_INT(i));
}
}
}
} // Handle exception file descriptors: if (OnCharReadHookExcFds != (Obj) 0 &&
IS_PLIST(OnCharReadHookExcFds) &&
OnCharReadHookExcFuncs != (Obj) 0 &&
IS_PLIST(OnCharReadHookExcFuncs)) { for (i = 1;i <= LEN_PLIST(OnCharReadHookExcFds);i++) {
o = ELM_PLIST(OnCharReadHookExcFds,i); if (o != (Obj) 0 && IS_INTOBJ(o)) {
j = INT_INTOBJ(o); // a UNIX file descriptor if (FD_ISSET(j,&excfds)) {
o = ELM_PLIST(OnCharReadHookExcFuncs,i); if (o != (Obj) 0 && IS_FUNC(o))
Call1ArgsInNewReader(o,INTOBJ_INT(i));
}
}
}
}
// Return if there is input to read from stdin, // and FreezeStdin is false. if (FD_ISSET(stdinfd, &infds) && !FreezeStdin) {
WeAreAlreadyInHere = 0; break;
}
} else break;
} // while (1)
} #endif// HAVE_SELECT
/*************************************************************************** ** *F HasAvailableBytes( <fid> ) returns positive if a subsequent read to <fid> ** will read at least one byte without blocking **
*/
Int HasAvailableBytes( UInt fid )
{
UInt bufno; if (!SyBufInUse(fid)) return -1;
if (syBuf[fid].bufno >= 0)
{
bufno = syBuf[fid].bufno; if (syBuffers[bufno].bufstart < syBuffers[bufno].buflen) return 1;
}
#ifdef HAVE_SELECT // All sockets other than raw sockets are always ready if (syBuf[fid].type == raw_socket) {
fd_set set; struct timeval tv;
FD_ZERO( &set);
FD_SET( syBuf[fid].fp, &set );
tv.tv_sec = 0;
tv.tv_usec = 0; return select( syBuf[fid].fp + 1, &set, NULL, NULL, &tv);
} #endif // best guess Int ret = SyIsEndOfFile(fid); return (ret != -1 && ret != 1);
}
staticChar * syFgetsNoEdit(Char * line, UInt length, Int fid, UInt block)
{
UInt x = 0; int ret = 0;
/* if stream is buffered, and the buffer has a full line, * grab it -- we could make more use of the buffer, but
* this covers the majority of cases simply. */ #ifndef LINE_END_HACK
UInt bufno; Char* newlinepos; Char* bufstart; int buflen; if(!syBuf[fid].isTTY && syBuf[fid].bufno >= 0) {
bufno = syBuf[fid].bufno; if (syBuffers[bufno].bufstart < syBuffers[bufno].buflen) {
bufstart = syBuffers[bufno].buf + syBuffers[bufno].bufstart;
buflen = syBuffers[bufno].buflen - syBuffers[bufno].bufstart;
newlinepos = memchr(bufstart, '\n', buflen); if(newlinepos && (newlinepos - bufstart) < length - 2) {
newlinepos++;
memcpy(line, bufstart, newlinepos - bufstart);
line[newlinepos - bufstart] = '\0';
syBuffers[bufno].bufstart += (newlinepos - bufstart); return line;
}
}
} #endif
while (x < length -1) { if (!block && x && !HasAvailableBytes( fid ))
{ break;
}
ret = syGetch(fid); if (ret == EOF) break; if ((line[x++] = ret) == '\n') break;
}
line[x] = '\0';
syBuf[fid].ateof = (ret == EOF); if (x) return line; else return NULL;
}
/* will be imported from library, first is generic function which does some checks before returning result to kernel, the second is the list of handler
functions which do the actual work. */ static Obj LineEditKeyHandler; static Obj LineEditKeyHandlers; static Obj GAPInfo;
#ifdef HAVE_LIBREADLINE
// we import GAP level functions from GAPInfo components static Obj CLEFuncs; static Obj KeyHandler;
staticint GAPMacroNumber = 0;
staticint GAP_set_macro(int count, int key)
{
GAPMacroNumber = count; return 0;
} // a generic rl_command_func_t that delegates to GAP level staticint GAP_rl_func(int count, int key)
{
Obj rldata, linestr, okey, res, obj, data, beginchange, endchange, m; Int len, n, hook, dlen, max, i;
// we shift indices 0-based on C-level and 1-based on GAP level
linestr = MakeString(rl_line_buffer);
okey = INTOBJ_INT(key + 1000*GAPMacroNumber);
GAPMacroNumber = 0;
rldata = NEW_PLIST(T_PLIST, 6); if (GAP_rl_func == rl_last_func) {
SET_LEN_PLIST(rldata, 6);
SET_ELM_PLIST(rldata, 6, True);
} else
SET_LEN_PLIST(rldata, 5);
SET_ELM_PLIST(rldata, 1, INTOBJ_INT(count));
SET_ELM_PLIST(rldata, 2, okey);
SET_ELM_PLIST(rldata, 3, linestr);
SET_ELM_PLIST(rldata, 4, INTOBJ_INT(rl_point+1));
SET_ELM_PLIST(rldata, 5, INTOBJ_INT(rl_mark+1));
res = Call1ArgsInNewReader(KeyHandler, rldata); if (!res) return 0; if (!IS_LIST(res)) return 0;
len = LEN_LIST(res); if (len == 0) return 0;
obj = ELM_LIST(res, 1); if (IsStringConv(obj)) { // insert txt
rl_insert_text(CONST_CSTR_STRING(obj));
n = 1;
} elseif ((obj == True || obj == False) && len > 2) { // kill or delete text
beginchange = ELM_LIST(res, 2); if (!IS_INTOBJ(beginchange)) return 0;
endchange = ELM_LIST(res, 3); if (!IS_INTOBJ(endchange)) return 0; if (obj == True)
rl_kill_text(INT_INTOBJ(beginchange)-1, INT_INTOBJ(endchange)-1); else
rl_delete_text(INT_INTOBJ(beginchange)-1, INT_INTOBJ(endchange)-1);
n = 3;
} elseif (IS_INTOBJ(obj) && len > 2) { // delete some text and insert
beginchange = obj;
endchange = ELM_LIST(res, 2); if (!IS_INTOBJ(endchange)) return 0;
obj = ELM_LIST(res, 3); if (!IsStringConv(obj)) return 0;
rl_begin_undo_group();
rl_delete_text(INT_INTOBJ(beginchange)-1, INT_INTOBJ(endchange)-1);
rl_point = INT_INTOBJ(beginchange)-1;
rl_insert_text(CONST_CSTR_STRING(obj));
rl_end_undo_group();
n = 3;
} elseif (IS_INTOBJ(obj) && len == 2) { // several hooks to particular rl_ functions with data
hook = INT_INTOBJ(obj);
data = ELM_LIST(res, 2); if (hook == 1) { // display matches if (!IS_LIST(data)) return 0; // -1, because first is word to be completed
dlen = LEN_LIST(data)-1; // +2, must be in 'argv' format, terminated by 0 char **strs = (char**)calloc(dlen+2, sizeof(char*));
max = 0; for (i=0; i <= dlen; i++) { if (!IsStringConv(ELM_LIST(data, i+1))) {
free(strs); return 0;
}
strs[i] = CSTR_STRING(ELM_LIST(data, i+1)); if (max < strlen(strs[i])) max = strlen(strs[i]);
}
rl_display_match_list(strs, dlen, max);
free(strs);
rl_on_new_line();
} elseif (hook == 2) { // put these characters into sequence of input keys if (!IsStringConv(data)) return 0;
dlen = strlen(CSTR_STRING(data)); for (i=0; i < dlen; i++)
rl_stuff_char(CSTR_STRING(data)[i]);
}
n = 2;
} elseif (IS_INTOBJ(obj) && len == 1) { // several hooks to particular rl_ functions with no data
hook = INT_INTOBJ(obj); // ring bell if (hook == 100) rl_ding(); // return line (execute Ctrl-m) elseif (hook == 101) rl_execute_next(13);
n = 1;
} else
n = 0;
// optionally we can return the new point, or new point and mark if (len > n) {
n++;
m = ELM_LIST(res, n); if (IS_INTOBJ(m))
rl_point = INT_INTOBJ(m) - 1;
} if (len > n) {
n++;
m = ELM_LIST(res, n); if (IS_INTOBJ(m))
rl_mark = INT_INTOBJ(m) - 1;
} return 0;
}
if (!IsStringConv(line)) returnFalse;
cline = CSTR_STRING(line);
rl_parse_and_bind(cline); returnTrue;
}
// init is needed once staticInt ISINITREADLINE = 0; // a hook function called regularly while waiting on input staticInt current_rl_fid; staticint charreadhook_rl(void)
{ #ifdef HAVE_SELECT if (OnCharReadHookActiveCheck())
HandleCharReadHook(syBuf[current_rl_fid].fp); #endif return 0;
}
staticvoid initreadline(void)
{
/* allows users to configure GAP specific settings in their ~/.inputrc like: $if GAP ....
$endif */
rl_readline_name = "GAP"; // this should pipe signals through to GAP
rl_already_prompted = 1 ;
rl_catch_signals = 0;
rl_catch_sigwinch = 1; // hook to read from other channels
rl_event_hook = 0; // give GAP_rl_func a name that can be used in .inputrc
rl_add_defun( "handled-by-GAP", GAP_rl_func, -1 );
rl_bind_keyseq("\\C-x\\C-g", GAP_set_macro);
// disable bracketed paste mode by default: it interferes with our handling // of pastes of data involving REPL prompts "gap>"
rl_variable_bind("enable-bracketed-paste", "off");
current_rl_fid = fid; if (!ISINITREADLINE) initreadline();
// read at most as much as we can buffer
rl_num_chars_to_read = length-2; #ifdef HAVE_SELECT // hook to read from other channels
rl_event_hook = (OnCharReadHookActiveCheck()) ? charreadhook_rl : 0; #endif // now do the real work
doingReadline = 1;
rlres = readline(STATE(Prompt));
doingReadline = 0; // we get a NULL pointer on EOF, say by pressing Ctr-d if (!rlres) { if (!SyCTRD) { while (!rlres)
rlres = readline(STATE(Prompt));
} else {
printf("\n");fflush(stdout);
line[0] = '\0'; return (Char*)0;
}
} // maybe add to history, we use key 0 for this function
GAP_rl_func(0, 0);
gap_strlcpy(line, rlres, length); // FIXME: handle the case where rlres contains more than length // characters better?
free(rlres);
gap_strlcat(line, "\n", length);
// send the whole line (unclipped) to the window handler
syWinPut( fid, (*line != '\0' ? "@r" : "@x"), line );
return line;
}
#endif
#ifdef HPCGAP
static GVarDescriptor GVarBeginEdit, GVarEndEdit;
staticInt syBeginEdit(Int fid)
{
Obj func = GVarFunction(&GVarBeginEdit);
Obj result; if (!func) return syStartraw(fid);
result = CALL_1ARGS(func, INTOBJ_INT(fid)); return result != False && result != Fail && result != INTOBJ_INT(0);
}
staticInt syEndEdit(Int fid)
{
Obj func = GVarFunction(&GVarEndEdit);
Obj result; if (!func) {
syStopraw(fid); return 1;
}
result = CALL_1ARGS(func, INTOBJ_INT(fid)); return result != False && result != Fail && result != INTOBJ_INT(0);
}
// no line editing if the file is not '*stdin*' or '*errin*' if ( fid != 0 && fid != 2 ) {
p = syFgetsNoEdit(line, length, fid, block);
return p;
}
/* no line editing if the user disabled it
or we can't make it into raw mode */ if ( SyLineEdit == 0 || ! syBeginEdit(fid) ) {
p = syFgetsNoEdit(line, length, fid, block ); return p;
}
#ifdef HAVE_LIBREADLINE if (SyUseReadline) { // switch back to cooked mode if ( SyLineEdit )
syEndEdit(fid);
p = readlineFgets(line, length, fid, block);
if ( EndLineHook ) Call0ArgsInNewReader( EndLineHook ); if (!p) return p; else return line;
} #endif
/* In line editing mode 'length' is not allowed bigger than the
yank buffer (= length of line buffer for input files).*/ if (length > 32768)
ErrorQuit("Cannot handle lines with more than 32768 characters in line edit mode.",0,0);
// the line starts out blank
line[0] = '\0'; p = line; for ( q = old; q < old+sizeof(old); ++q ) *q = ' ';
oldc = 0;
last = 0;
ch = 0;
rubdel=0; // do we want to east a `del' character?
while ( 1 ) {
// get a character, handle <ctr>V<chr>, <esc><num> and <ctr>U<num>
rep = 1; ch2 = 0; do {
--> --------------------
--> maximum size reached
--> --------------------
¤ Dauer der Verarbeitung: 0.46 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.