/**************************************************************************** ** ** This file is part of GAP, a system for computational discrete algebra. ** ** Copyright of GAP belongs to its developers, whose names are too numerous ** to list here. Please refer to the COPYRIGHT file for details. ** ** SPDX-License-Identifier: GPL-2.0-or-later ** ** This file contains the various read-eval-print loops and related stuff.
*/
/**************************************************************************** ** *V SyLoadSystemInitFile . . . . . . should GAP load 'lib/init.g' at startup ** ** TODO: this variable could be made static or even deleted. However for ** now the GAP.jl Julia package is accessing it, so we have to keep it. ** See also issue #5890 for the reasons behind this.
*/ Int SyLoadSystemInitFile = 1;
/**************************************************************************** ** *V Last . . . . . . . . . . . . . . . . . . . . . . global variable 'last' ** ** 'Last', 'Last2', and 'Last3' are the global variables 'last', 'last2', ** and 'last3', which are automatically assigned the result values in the ** main read-eval-print loop.
*/ static UInt Last;
/**************************************************************************** ** *V Time . . . . . . . . . . . . . . . . . . . . . . global variable 'time' ** ** 'Time' is the global variable 'time', which is automatically assigned the ** time the last command took.
*/ static UInt Time;
/**************************************************************************** ** *V MemoryAllocated . . . . . . . . . . . global variable 'memory_allocated' ** ** 'MemoryAllocated' is the global variable 'memory_allocated', ** which is automatically assigned the amount of memory allocated while ** executing the last command.
*/ static UInt MemoryAllocated;
#ifndef HPCGAP
GAPState MainGAPState; #endif
/**************************************************************************** ** *F ViewObjHandler . . . . . . . . . handler to view object and catch errors ** ** This is the function actually called in Read-Eval-View loops. ** We might be in trouble if the library has not (yet) loaded and so ViewObj ** is not yet defined, or the fallback methods not yet installed. To avoid ** this problem, we check, and use PrintObj if there is a problem ** ** This function also supplies the \n after viewing.
*/
UInt ViewObjGVar;
void ViewObjHandler ( Obj obj )
{ // save some values in case view runs into error volatile Bag currLVars = STATE(CurrLVars);
// if non-zero use this function, otherwise use `PrintObj'
GAP_TRY {
Obj func = ValAutoGVar(ViewObjGVar); if ( func != 0 && TNUM_OBJ(func) == T_FUNCTION ) {
ViewObj(obj);
} else {
PrintObj( obj );
}
Pr("\n", 0, 0);
GAP_ASSERT(currLVars == STATE(CurrLVars));
}
GAP_CATCH {
SWITCH_TO_OLD_LVARS(currLVars);
}
}
static Obj FuncSHELL(Obj self,
Obj context,
Obj canReturnVoid,
Obj canReturnObj,
Obj breakLoop,
Obj prompt,
Obj preCommandHook)
{ // // validate all arguments // if (!IS_LVARS_OR_HVARS(context))
RequireArgument(SELF_NAME, context, "must be a local variables bag");
RequireTrueOrFalse(SELF_NAME, canReturnVoid);
RequireTrueOrFalse(SELF_NAME, canReturnObj);
RequireTrueOrFalse(SELF_NAME, breakLoop);
RequireStringRep(SELF_NAME, prompt); if (GET_LEN_STRING(prompt) > 80)
ErrorMayQuit("SHELL: must be a string of length at most 80",
0, 0);
if (preCommandHook == False)
preCommandHook = 0; elseif (!IS_FUNC(preCommandHook))
RequireArgument(SELF_NAME, preCommandHook, "must be function or false");
// // open input and output streams // constChar * inFile; constChar * outFile;
TypOutputFile output; if (!OpenOutput(&output, outFile, FALSE))
ErrorQuit("SHELL: can't open outfile %s", (Int)outFile, 0);
TypInputFile input; if (!OpenInput(&input, inFile)) {
CloseOutput(&output);
ErrorQuit("SHELL: can't open infile %s", (Int)inFile, 0);
}
// // save some state // Int oldErrorLLevel = STATE(ErrorLLevel); Int oldRecursionDepth = GetRecursionDepth();
UInt oldPrintObjState = SetPrintObjState(0);
// // start the REPL (read-eval-print loop) //
STATE(ErrorLLevel) = 0; while (1) {
UInt time = 0;
UInt8 mem = 0;
// start the stopwatch if (breakLoop == False) {
time = SyTime();
mem = SizeAllBags;
}
// read and evaluate one command
SetPrompt(CONST_CSTR_STRING(prompt));
SetPrintObjState(0);
ResetOutputIndent();
SetRecursionDepth(0);
// here is a hook: if (preCommandHook) {
Call0ArgsInNewReader(preCommandHook); // Recover from a potential break loop:
SetPrompt(CONST_CSTR_STRING(prompt));
}
// update ErrorLVars based on ErrorLLevel // // It is slightly wasteful to do this every time, but that's OK since // this code is only used for interactive input, and the time it takes // a user to press the return key is something like a thousand times // greater than the time it takes to execute this loop. Int depth = STATE(ErrorLLevel);
Obj errorLVars = context;
STATE(ErrorLLevel) = 0; while (0 < depth && !IsBottomLVars(errorLVars) &&
!IsBottomLVars(PARENT_LVARS(errorLVars))) {
errorLVars = PARENT_LVARS(errorLVars);
STATE(ErrorLLevel)++;
depth--;
}
STATE(ErrorLVars) = errorLVars;
// read and evaluate one command (statement or expression) BOOL dualSemicolon;
status =
ReadEvalCommand(errorLVars, &input, &evalResult, &dualSemicolon);
// if the input we just processed *indirectly* executed a `QUIT` statement // (e.g. by reading a file via `READ`) then bail out if (STATE(UserHasQUIT)) break;
// if the statement we just processed itself was `QUIT`, also bail out if (status == STATUS_QQUIT) {
STATE(UserHasQUIT) = TRUE; break;
}
// handle ordinary command if (status == STATUS_END && evalResult != 0) {
UpdateLast(evalResult); if (!dualSemicolon) {
ViewObjHandler(evalResult);
}
}
// handle return-value or return-void command elseif (status == STATUS_RETURN && evalResult != 0) { if (canReturnObj == True) break;
Pr("'return "loop\n",
0, 0);
} elseif (status == STATUS_RETURN && evalResult == 0) { if (canReturnVoid == True) break;
Pr("'return' cannot be used in this read-eval-print loop\n", 0,
0);
} // handle quit command or <end-of-file> elseif (status == STATUS_EOF || status == STATUS_QUIT) { break;
}
// stop the stopwatch if (breakLoop == False) {
UpdateTime(time);
AssGVarWithoutReadOnlyCheck(MemoryAllocated,
ObjInt_Int8(SizeAllBags - mem));
}
if (STATE(UserHasQuit)) { // If we get here, then some code invoked indirectly by the // command we just processed was aborted via `quit` (most likely: // `quit` was entered in a break loop). Stop processing any // further input in the current line of input. Thus if the input // is `f(); g();` and executing `f()` triggers a break loop that // the user aborts via `quit`, then we won't try to execute `g()` // anymore. // // So in a sense we are (ab)using `UserHasQuit` to see if an error // occurred.
FlushRestOfInputLine(&input);
STATE(UserHasQuit) = FALSE;
}
}
// // handle QUIT // if (STATE(UserHasQUIT)) { // If we are in a break loop, throw so that the next higher up // read&eval loop can process the QUIT if (breakLoop == True)
GAP_THROW();
// If we are the topmost REPL, then indicating we are QUITing to the // GAP language level, and simply end the loop. This implicitly // assumes that the only places using SHELL() are the primary REPL and // break loops.
STATE(UserHasQuit) = FALSE;
STATE(UserHasQUIT) = FALSE;
AssGVarWithoutReadOnlyCheck(QUITTINGGVar, True); return Fail;
}
// // handle the remaining status codes; note that `STATUS_QQUIT` is handled // above, as part of the `UserHasQUIT` handling // if (status == STATUS_EOF || status == STATUS_QUIT) { return Fail;
} if (status == STATUS_RETURN) { return evalResult ? NewPlistFromArgs(evalResult) : NewEmptyPlist();
}
Panic("SHELL: unhandled status %d, this code should never be reached",
(int)status); return (Obj)0;
}
int realmain(int argc, constchar * argv[])
{
UInt type; // result of compile
Obj func; // function (compiler)
Int4 crc; // crc of file to compile
// initialize everything and read init.g which runs the GAP session
InitializeGap(&argc, argc, argv, 1); if (!STATE(UserHasQUIT)) { /* maybe the user QUIT from the initial
read of init.g somehow*/ // maybe compile in which case init.g got skipped if ( SyCompilePlease ) {
TypInputFile input; if ( ! OpenInput(&input, SyCompileInput) ) { return 1;
}
func = READ_AS_FUNC(&input); if (!CloseInput(&input)) { return 2;
}
crc = SyGAPCRC(SyCompileInput);
type = CompileFunc(
MakeImmString(SyCompileOutput),
func,
MakeImmString(SyCompileName),
crc,
MakeImmString(SyCompileMagic1) ); return ( type == 0 ) ? 1 : 0;
}
} return SystemErrorCode;
}
/**************************************************************************** ** *F FuncSizeScreen( <self>, <args> ) . . . . internal function 'SizeScreen' ** ** 'FuncSizeScreen' implements the internal function 'SizeScreen' to get ** or set the actual screen size. ** ** 'SizeScreen()' ** ** In this form 'SizeScreen' returns the size of the screen as a list with ** two entries. The first is the length of each line, the second is the ** number of lines. ** ** 'SizeScreen( [ <x>, <y> ] )' ** ** In this form 'SizeScreen' sets the size of the screen. <x> is the length ** of each line, <y> is the number of lines. Either value may be missing, ** to leave this value unaffected. Note that those parameters can also be ** set with the command line options '-x <x>' and '-y <y>'.
*/ static Obj FuncSizeScreen(Obj self, Obj args)
{
Obj size; // argument and result list
Obj elm; // one entry from size
UInt len; // length of lines on the screen
UInt nr; // number of lines on the screen
RequireSmallList(SELF_NAME, args); if (1 < LEN_LIST(args)) {
ErrorMayQuit("SizeScreen: number of arguments must be 0 or 1 (not %d)",
LEN_LIST(args), 0);
}
// get the arguments if ( LEN_LIST(args) == 0 ) {
size = NEW_PLIST( T_PLIST, 0 );
}
// otherwise check the argument else {
size = ELM_LIST( args, 1 ); if (!IS_SMALL_LIST(size) || 2 < LEN_LIST(size)) {
ErrorMayQuit("SizeScreen: must be a list of length at most 2",
0, 0);
}
}
// extract the length if ( LEN_LIST(size) < 1 || ELM0_LIST(size,1) == 0 ) {
len = 0;
} else {
elm = ELMW_LIST(size,1);
len = GetSmallIntEx(SELF_NAME, elm, ""); if ( len < 20 ) len = 20; if ( MAXLENOUTPUTLINE < len ) len = MAXLENOUTPUTLINE;
}
// extract the number
elm = ELM0_LIST(size, 2); if ( elm == 0 ) {
nr = 0;
} else {
nr = GetSmallIntEx(SELF_NAME, elm, ""); if ( nr < 10 ) nr = 10;
}
// set length and number if (len != 0)
{
SyNrCols = len;
SyNrColsLocked = 1;
} if (nr != 0)
{
SyNrRows = nr;
SyNrRowsLocked = 1;
}
// make and return the size of the screen
size = NEW_PLIST( T_PLIST, 2 );
PushPlist(size, ObjInt_UInt(SyNrCols));
PushPlist(size, ObjInt_UInt(SyNrRows)); return size;
static Obj FuncWindowCmd(Obj self, Obj args)
{
Obj tmp;
Obj list; Int len; Int n, m; Int i; Char * ptr; constChar * inptr; constChar * qtr;
RequireSmallList(SELF_NAME, args);
tmp = ELM_LIST(args, 1); if (!IsStringConv(tmp)) {
RequireArgumentEx(SELF_NAME, tmp, "", "must be a string");
} if ( 3 != LEN_LIST(tmp) ) {
ErrorMayQuit("WindowCmd: must be a string of length 3", 0, 0);
}
// compute size needed to store argument string
len = 13; for ( i = 2; i <= LEN_LIST(args); i++ )
{
tmp = ELM_LIST( args, i ); if (!IS_INTOBJ(tmp) && !IsStringConv(tmp)) {
ErrorMayQuit("WindowCmd: the argument in position %d must be a " "string or integer (not a %s)",
i, (Int)TNAM_OBJ(tmp));
SET_ELM_PLIST(args, i, tmp);
} if ( IS_INTOBJ(tmp) )
len += 12; else
len += 12 + LEN_LIST(tmp);
} if ( SIZE_OBJ(WindowCmdString) <= len ) {
ResizeBag( WindowCmdString, 2*len+1 );
}
// convert <args> into an argument string
ptr = (Char*) CSTR_STRING(WindowCmdString);
// first the command name
memcpy( ptr, CONST_CSTR_STRING( ELM_LIST(args,1) ), 3 + 1 );
ptr += 3;
// and now the arguments for ( i = 2; i <= LEN_LIST(args); i++ )
{
tmp = ELM_LIST(args,i);
if ( IS_INTOBJ(tmp) ) {
*ptr++ = 'I';
m = INT_INTOBJ(tmp); for ( m = (m<0)?-m:m; 0 < m; m /= 10 )
*ptr++ = (m%10) + '0'; if ( INT_INTOBJ(tmp) < 0 )
*ptr++ = '-'; else
*ptr++ = '+';
} else {
*ptr++ = 'S';
m = LEN_LIST(tmp); for ( ; 0 < m; m/= 10 )
*ptr++ = (m%10) + '0';
*ptr++ = '+';
qtr = CONST_CSTR_STRING(tmp); for ( m = LEN_LIST(tmp); 0 < m; m-- )
*ptr++ = *qtr++;
}
}
*ptr = 0;
// now call the window front end with the argument string
qtr = CONST_CSTR_STRING(WindowCmdString);
inptr = SyWinCmd( qtr, strlen(qtr) );
len = strlen(inptr);
// now convert result back into a list
list = NEW_PLIST( T_PLIST, 11 );
i = 1; while ( 0 < len ) { if ( *inptr == 'I' ) {
inptr++; for ( n=0,m=1; '0' <= *inptr && *inptr <= '9'; inptr++,m *= 10,len-- )
n += (*inptr-'0') * m; if ( *inptr++ == '-' )
n *= -1;
len -= 2;
AssPlist( list, i, INTOBJ_INT(n) );
} elseif ( *inptr == 'S' ) {
inptr++; for ( n=0,m=1; '0' <= *inptr && *inptr <= '9'; inptr++,m *= 10,len-- )
n += (*inptr-'0') * m;
inptr++; // ignore the '+'
tmp = MakeImmStringWithLen(inptr, n);
inptr += n;
len -= n+2;
AssPlist( list, i, tmp );
} else {
ErrorQuit( "unknown return value '%s'", (Int)inptr, 0 );
}
i++;
}
// if the first entry is one signal an error if ( ELM_LIST(list,1) == INTOBJ_INT(1) ) {
tmp = MakeString("window system: ");
SET_ELM_PLIST(list, 1, tmp);
SET_LEN_PLIST(list, i - 1); return CALL_XARGS(Error, list);
} else { for ( m = 1; m <= i-2; m++ )
SET_ELM_PLIST( list, m, ELM_PLIST(list,m+1) );
SET_LEN_PLIST( list, i-2 ); return list;
}
}
// otherwise complain else {
ErrorMayQuit("GASMAN: must be " "\"display\" or \"clear\" or \"global\" or " "\"collect\" or \"partial\" or \"message\"", 0, 0);
} #endif// USE_GASMAN
}
return 0;
}
#ifdef USE_GASMAN static Obj FuncGASMAN_STATS(Obj self)
{
Obj res;
Obj row;
UInt i,j; Int x;
res = NEW_PLIST_IMM(T_PLIST_TAB_RECT, 2);
SET_LEN_PLIST(res, 2); for (i = 1; i <= 2; i++)
{
row = NEW_PLIST_IMM(T_PLIST_CYC, 9);
SET_ELM_PLIST(res, i, row);
CHANGED_BAG(res);
SET_LEN_PLIST(row, 9); for (j = 1; j <= 8; j++)
{
x = SyGasmanNumbers[i-1][j];
SET_ELM_PLIST(row, j, ObjInt_Int(x));
}
SET_ELM_PLIST(row, 9, INTOBJ_INT(SyGasmanNumbers[i-1][0]));
} return res;
}
/**************************************************************************** ** *F FuncSIZE_OBJ( <self>, <obj> ) . . . . expert function 'SIZE_OBJ' ** ** 'SIZE_OBJ( <obj> )' returns 0 for immediate objects, and otherwise ** returns the bag size of the object. This does not include the size of ** sub-objects.
*/ static Obj FuncSIZE_OBJ(Obj self, Obj obj)
{ if (IS_INTOBJ(obj) || IS_FFE(obj)) return INTOBJ_INT(0); return ObjInt_UInt(SIZE_OBJ(obj));
}
/**************************************************************************** ** *F FuncOBJ_HANDLE( <self>, <handle> ) . . . . . expert function 'OBJ_HANDLE'
*/ static Obj FuncOBJ_HANDLE(Obj self, Obj handle)
{ if (handle != INTOBJ_INT(0) && !IS_POS_INT(handle))
RequireArgument(SELF_NAME, handle, "must be a non-negative integer"); return (Obj)UInt_ObjInt(handle);
}
/**************************************************************************** ** *F FuncHANDLE_OBJ( <self>, <obj> ) . . . . . . expert function 'HANDLE_OBJ' ** ** This is a very quick function which returns a unique integer for each ** object non-identical objects will have different handles. The integers ** may be large.
*/ static Obj FuncHANDLE_OBJ(Obj self, Obj obj)
{ return ObjInt_UInt((UInt) obj);
}
/* This function does quite a similar job to HANDLE_OBJ, but (a) returns 0 for all immediate objects (small integers or ffes) and (b) returns reasonably small results (roughly in the range from 1 to the max number of objects that have existed in this session. In HPC-GAP it returns almost the same value as HANDLE_OBJ for non-immediate objects, but divided by sizeof(Obj), which gets rid of a few zero bits and thus increases the chance of the result value
fitting into an immediate integer. */
// Common code in the next 3 methods. staticint SetExitValue(Obj code)
{ if (code == False || code == Fail)
SystemErrorCode = 1; elseif (code == True)
SystemErrorCode = 0; elseif (IS_INTOBJ(code))
SystemErrorCode = INT_INTOBJ(code); else return 0; return 1;
}
/**************************************************************************** ** *F FuncGapExitCode() . . . . . . . . Set the code with which GAP exits. **
*/
/**************************************************************************** ** *F KERNEL_INFO() ......................record of information from the kernel ** ** The general idea is to put all kernel-specific info in here, and clean up ** the assortment of global variables previously used
*/ static Obj KernelArgs;
staticvoid InitKernelArgs(int argc, constchar * argv[])
{ // make command line available to GAP level
KernelArgs = NEW_PLIST_IMM(T_PLIST, argc); for (int i = 0; i < argc; i++) {
PushPlist(KernelArgs, MakeImmString(argv[i]));
}
}
// Get OS Kernel Release info
AssPRec(res, RNamName("uname"), SyGetOsRelease());
// make command line available to GAP level
AssPRec(res, RNamName("COMMAND_LINE"), KernelArgs);
// make environment available to GAP level
tmp = NEW_PREC(0); for (int i = 0; environ[i]; i++) { Char * p = strchr(environ[i], '='); if (!p) { // should never happen... // FIXME: should we print a warning here? continue;
}
Obj name = MakeStringWithLen(environ[i], p - environ[i]);
r = RNamName(CONST_CSTR_STRING(name));
p++; // Move pointer behind = character
AssPRec(tmp, r, MakeImmString(p));
}
AssPRec(res, RNamName("ENVIRONMENT"), tmp);
/**************************************************************************** ** *F FuncBREAKPOINT . . . . . . . . . . . . Mark kernel breakpoints in GAP code ** ** The purpose behind this function is to mark positions in the GAP code ** and to transfer a stage flag to the kernel in a way that facilitates ** the use of kernel breakpoints depending on GAP state. ** ** One use case is to simply insert BREAKPOINT(0) at a specific GAP ** source location and then set a breakpoint on FuncBREAKPOINT in the ** kernel. ** ** The function accepts any argument; if it is a small integer, it will be ** converted and stored in the global variable BreakPointValue. This ** also allows the encoding of GAP state in that value and to attach a ** condition based on the value of BreakPointValue to other breakpoints.
*/
/**************************************************************************** ** *F FuncSINGLE_THREAD_STARTUP . . whether to start up in single-threaded mode **
*/
// UPDATE_STAT lets code assign the special variables which GAP // automatically sets in interactive sessions. This is for demonstration // code which wants to look like interactive usage of GAP. Using this // function will not stop GAP automatically changing these variables as // usual. static Obj FuncUPDATE_STAT(Obj self, Obj name, Obj newStat)
{
RequireStringRep(SELF_NAME, name);
// ensure any legacy code which directly tries to set the former GAP // global 'CurrentAssertionLevel' or tries to compare to an integer, // runs into an error.
AssConstantGVar(GVarName("CurrentAssertionLevel"), False);
return PostRestore( module );
}
/**************************************************************************** ** *F InitInfoGap() . . . . . . . . . . . . . . . . . . table of init functions
*/ static StructInitInfo module = { // init struct using C99 designated initializers; for a full list of // fields, please refer to the definition of StructInitInfo
.type = MODULE_BUILTIN,
.name = "gap",
.initKernel = InitKernel,
.initLibrary = InitLibrary,
.postRestore = PostRestore
};
/**************************************************************************** ** *F InitializeGap() . . . . . . . . . . . . . . . . . . . . . initialize GAP ** ** Each module (builtin or compiled) exports a structure which contains ** information about the name, version, crc, init function, save and restore ** functions. ** ** The init process is split into three different functions: ** ** `InitKernel': This function setups the internal data structures and ** tables, registers the global bags and functions handlers, copies and ** fopies. It is not allowed to create objects, gvar or rnam numbers. This ** function is used for both starting and restoring. ** ** `InitLibrary': This function creates objects, gvar and rnam number, and ** does assignments of auxiliary C variables (for example, pointers from ** objects, length of hash lists). This function is only used for starting. ** ** `PostRestore': Everything in `InitLibrary' execpt creating objects. In ** general `InitLibrary' will create all objects and then calls ** `PostRestore'. This function is only used when restoring. ** ** Note that this function does not take the usual argc, argv pair for a ** list of arguments, but instead a pointer to argc is passed. This is a ** trick to get a pointer to the execution stack on the level of the calling ** function. We use the resulting pointer as a hint to the garbage collector ** as to where the execution stack (might) start.
*/ void InitializeGap(void * stackBottom, int argc, constchar * argv[], BOOL handleSignals)
{ // initialize the basic system and gasman
InitSystem(argc, argv, handleSignals);
// Initialise memory -- have to do this here to make sure we are at top of C stack
Bag * alignedStackBottom = (Bag *)(((UInt)stackBottom / C_STACK_ALIGN) * C_STACK_ALIGN); #ifdefined(USE_GASMAN)
InitBags(SyStorMin, alignedStackBottom); #else
InitBags(0, alignedStackBottom); #endif
// we are restoring, load the workspace and call the post restore
ModulesInitModuleState();
LoadWorkspace(SyRestoring);
SyRestoring = NULL;
// make command line available to GAP level
InitKernelArgs(argc, argv);
// Call POST_RESTORE which is a GAP function that now takes control, // calls the post restore functions and then runs a GAP session
Obj POST_RESTORE = ValGVar(GVarName("POST_RESTORE")); if (POST_RESTORE != 0 && IS_FUNC(POST_RESTORE)) {
Call0ArgsInNewReader(POST_RESTORE);
}
// make command line available to GAP level
InitKernelArgs(argc, argv);
// should GAP load 'lib/init.g' on initialization? if (SyCompilePlease) {
SyLoadSystemInitFile = 0;
} #ifdef GAP_ENABLE_SAVELOAD elseif (SyRestoring) {
SyLoadSystemInitFile = 0;
} #endif
if (SyLoadSystemInitFile) { // read the init files // depending on the command line this now actually runs the GAP // session, we only get past here when we're about to exit.
GAP_TRY
{ if (READ_GAP_ROOT("lib/init.g") == 0) {
Pr("gap: hmm, I cannot find 'lib/init.g' maybe" " use option '-l '?\n",
0, 0);
SystemErrorCode = 1;
}
}
GAP_CATCH
{
Panic("Caught error at top-most level, probably quit from " "library loading");
}
}
}
¤ Dauer der Verarbeitung: 0.28 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.