Goto Chapter: | Top | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Ind

### 4. High level functions for buffered I/O

The functions in the previous sections are intended to be a possibility for direct access to the low level I/O functions in the C library. Thus, the calling conventions are strictly as in the original.

The functionality described in this section is implemented completely in the GAP language and is intended to provide a good interface for programming in GAP. The fundamental object for I/O on the C library level is the file descriptor, which is just a non-negative integer representing an open file of the process. The basic idea is to wrap up file descriptors in GAP objects that do the buffering.

Note that considerable care has been taken to ensure that one can do I/O multiplexing with buffered I/O. That is, one always has the possibility to make sure before a read or write operation, that this read or write operation will not block. This is crucial when one wants to serve more than one I/O channel from the same (single-threaded) GAP process. This design principle sometimes made it necessary to have more than one function for a certain operation. Those functions usually differ in a subtle way with respect to their blocking behaviour.

One remark applies again to nearly all functions presented here: If an error is indicated by the returned value `fail` one can use the library function `LastSystemError` (Reference: LastSystemError) to find out more about the cause of the error. This fact is not mentioned with every single function.

#### 4.1 Types and the creation of `File` objects

The wrapped file objects are in the following category:

##### 4.1-1 IsFile

`> IsFile`( o ) | ( Category )

Returns: `true` or `false`

The category of `File` objects.

To create objects in this category, one uses the following function:

##### 4.1-2 IO_WrapFD

`> IO_WrapFD`( fd, rbufsize, wbufsize ) | ( function )

Returns: a `File` object

The argument fd must be a file descriptor (i.e. an integer) or -1 (see below).

rbufsize can either be `false` for unbuffered reading or an integer buffer size or a string. If it is an integer, a read buffer of that size is used. If it is a string, then fd must be -1 and a `File` object that reads from that string is created.

wbufsize can either be `false` for unbuffered writing or an integer buffer size or a string. If it is an integer, a write buffer of that size is used. If it is a string, then fd must be -1 and a `File` object that appends to that string is created.

The result of this function is a new `File` object.

A convenient way to do this for reading or writing of files on disk is the following function:

##### 4.1-3 IO_File

`> IO_File`( filename[, mode] ) | ( function )

`> IO_File`( filename[, bufsize] ) | ( function )

`> IO_File`( filename, mode, bufsize ) | ( function )

Returns: a `File` object or `fail`

The argument filename must be a string specifying the path name of the file to work on. mode must also be a string with possible values "r", "w", or "a", meaning read access, write access (with creating and truncating), and append access respectively. If mode is omitted, it defaults to "r". bufsize, if given, must be a positive integer or `false`, otherwise it defaults to `IO.DefaultBufSize`. Internally, the `IO_open` (3.2-31) function is used and the result file descriptor is wrapped using `IO_WrapFD` (4.1-2) with bufsize as the buffer size.

The result is either `fail` in case of an error or a `File` object in case of success.

Note that there is a similar function `IO_FilteredFile` (4.4-9) which also creates a `File` object but with additional functionality with respect to a pipeline for filtering. It is described in its section in Section 4.4. There is some more low-level functionality to acquire open file descriptors. These can be wrapped into `File` objects using `IO_WrapFD` (4.1-2).

#### 4.2 Reading and writing

Once a `File` object is created, one can use the following functions on it:

##### 4.2-1 IO_ReadUntilEOF

`> IO_ReadUntilEOF`( f ) | ( function )

Returns: a string or `fail`

This function reads all data from the file f until the end of file. The data is returned as a GAP string. If the file is already at end of file, an empty string is returned. If an error occurs, then `fail` is returned. Note that you still have to call `IO_Close` (4.2-16) on the `File` object to properly close the file later.

##### 4.2-2 IO_ReadBlock

`> IO_ReadBlock`( f, len ) | ( function )

Returns: a string or `fail`

This function gets two arguments, the first argument f must be a `File` object and the second argument len must be a positive integer. The function tries to read len bytes and returns a string of that length. If and only if the end of file is reached earlier, fewer bytes are returned. If an error occurs, `fail` is returned. Note that this function blocks until either len bytes are read, or the end of file is reached, or an error occurs. For the case of pipes or internet connections it is possible that currently no more data is available, however, by definition the end of file is only reached after the connection has been closed by the other side!

##### 4.2-3 IO_ReadLine

`> IO_ReadLine`( f ) | ( function )

Returns: a string or `fail`

This function gets exactly one argument, which must be a `File` object f. It reads one line of data, where the definition of line is operating system dependent. The line end character(s) are included in the result. The function returns a string with the line in case of success and `fail` in case of an error. In the latter case, one can query the error with `LastSystemError` (Reference: LastSystemError).

Note that the reading is done via the buffer of f, such that this function will be quite fast also for large amounts of data.

If the end of file is hit without a line end, the rest of the file is returned. If the file is already at end of file before the call, then a string of length 0 is returned. Note that this is not an error but the standard end of file convention!

##### 4.2-4 IO_ReadLines

`> IO_ReadLines`( f[, max] ) | ( function )

Returns: a list of strings or `fail`

This function gets one or two arguments, the first of which must always be a `File` object f. It reads lines of data (where the definition of line is operating system dependent) either until end of file (without a second argument) or up to max lines (with a second argument max. A list of strings with the result is returned, if everything went well and `fail` oterwise. In the latter case, one can query the error with `LastSystemError` (Reference: LastSystemError).

Note that the reading is done via the buffer of f, such that this function will be quite fast also for large amounts of data.

If the file is already at the end of file, the function returns a list of length 0. Note that this is not an error but the standard end of file convention!

##### 4.2-5 IO_HasData

`> IO_HasData`( f ) | ( function )

Returns: `true` or `false`

This function takes one argument f which must be a `File` object. It returns `true` or `false` according to whether there is data to read available in the file f. A return value of `true` guarantees that the next call to `IO_Read` (4.2-6) on that file will succeed without blocking and return at least one byte or an empty string to indicate the end of file.

##### 4.2-6 IO_Read

`> IO_Read`( f, len ) | ( function )

Returns: a string or `fail`

The function gets two arguments, the first of which must be a `File` object f. The second argument must be a positive integer. The function reads data up to len bytes. A string with the result is returned, if everything went well and `fail` otherwise. In the latter case, one can query the error with `LastSystemError` (Reference: LastSystemError).

Note that the reading is done via the buffer of f, such that this function will be quite fast also for large amounts of data.

If the file is already at the end of the file, the function returns a string of length 0. Note that this is not an error!

If a previous call to `IO_HasData` (4.2-5) or to `IO_Select` (4.3-3) indicated that there is data available to read, then it is guaranteed that the function `IO_Read` does not block and returns at least one byte if the file is not yet at end of file and an empty string otherwise.

##### 4.2-7 IO_Write

`> IO_Write`( f[, things, ...] ) | ( function )

Returns: an integer or `fail`

This function can get an arbitrary number of arguments, the first of which must be a `File` object f. All the other arguments are just written to f if they are strings. Otherwise, the `String` function is called on them and the result is written out to f.

Note that the writing is done buffered. That is, the data is first written to the buffer and only really written out after the buffer is full or after the user explicitly calls `IO_Flush` (4.2-10) on f.

The result is either the number of bytes written in case of success or `fail` in case of an error. In the latter case the error can be queried with `LastSystemError` (Reference: LastSystemError).

Note that this function blocks until all data is at least written into the buffer and might block until data can be sent again if the buffer is full.

##### 4.2-8 IO_WriteLine

`> IO_WriteLine`( f, line ) | ( function )

Returns: an integer or `fail`

Behaves like `IO_Write` (4.2-7) but works on a single string line and sends an (operating system dependent) end of line string afterwards. Also `IO_Flush` (4.2-10) is called automatically after the operation, such that one can be sure, that the data is actually written out after the function has completed.

##### 4.2-9 IO_WriteLines

`> IO_WriteLines`( f, list ) | ( function )

Returns: an integer or `fail`

Behaves like `IO_Write` (4.2-7) but works on a list of strings list and sends an (operating system dependent) end of line string after each string in the list. Also `IO_Flush` (4.2-10) is called automatically after the operation, such that one can be sure, that the data is actually written out after the function has completed.

##### 4.2-10 IO_Flush

`> IO_Flush`( f ) | ( function )

Returns: `true` or `fail`

This function gets one argument f, which must be a `File` object. It writes out all the data that is in the write buffer. This is not necessary before the call to the function `IO_Close` (4.2-16), since that function calls `IO_Flush` automatically. However, it is necessary to call `IO_Flush` after calls to `IO_Write` (4.2-7) to be sure that the data is really sent out. The function returns `true` if everything goes well and `fail` if an error occurs.

Remember that the functions `IO_WriteLine` (4.2-8) and `IO_WriteLines` (4.2-9) implicitly call `IO_Flush` after they are done.

Note that this function might block until all data is actually written to the file descriptor.

##### 4.2-11 IO_WriteFlush

`> IO_WriteFlush`( f[, things] ) | ( function )

Returns: an integer or `fail`

This function behaves like `IO_Write` (4.2-7) followed by a call to `IO_Flush` (4.2-10). It returns either the number of bytes written or `fail` if an error occurs.

##### 4.2-12 IO_ReadyForWrite

`> IO_ReadyForWrite`( f ) | ( function )

Returns: `true` or `false`

This function takes one argument f which must be a `File` object. It returns `true` or `false` according to whether the file f is ready to write. A return value of `true` guarantees that the next call to `IO_WriteNonBlocking` (4.2-13) on that file will succeed without blocking and accept at least one byte.

##### 4.2-13 IO_WriteNonBlocking

`> IO_WriteNonBlocking`( f, st, pos, len ) | ( function )

Returns: an integer or `fail`

This function takes four arguments. The first one f must be a `File` object, the second st a string, and the arguments pos and len must be integers, such that positions pos+1 until pos+len are bound in st. The function tries to write up to len bytes from st from position pos+1 to the file f. If a previous call to `IO_ReadyForWrite` (4.2-12) or to `IO_Select` (4.3-3) indicates that f is writable, then it is guaranteed that the following call to `IO_WriteNonBlocking` will not block and accept at least one byte of data. Note that it is not guaranteed that all len bytes are written. The function returns the number of bytes written or `fail` if an error occurs.

##### 4.2-14 IO_ReadyForFlush

`> IO_ReadyForFlush`( f ) | ( function )

Returns: `true` or `false`

This function takes one argument f which must be a `File` object. It returns `true` or `false` according to whether the file f is ready to flush. A return value of `true` guarantees that the next call to `IO_FlushNonBlocking` (4.2-15) on that file will succeed without blocking and flush out at least one byte. Note that this does not guarantee, that this call succeeds to flush out the whole content of the buffer!

##### 4.2-15 IO_FlushNonBlocking

`> IO_FlushNonBlocking`( f ) | ( function )

Returns: `true`, `false`, or `fail`

This function takes one argument f which must be a `File` object. It tries to write all data in the writing buffer to the file descriptor. If this succeeds, the function returns `true` and `false` otherwise. If an error occurs, `fail` is returned. If a previous call to `IO_ReadyForFlush` (4.2-14) or `IO_Select` (4.3-3) indicated that f is flushable, then it is guaranteed that the following call to `IO_FlushNonBlocking` does not block. However, it is not guaranteed that `true` is returned from that call.

##### 4.2-16 IO_Close

`> IO_Close`( f ) | ( function )

Returns: `true` or `fail`

This function closes the `File` object f after writing all data in the write buffer out and closing the file descriptor. All buffers are freed. In case of an error, the function returns `fail` and otherwise `true`. Note that for pipes to other processes this function collects data about the terminated processes using `IO_WaitPid` (3.2-52).

#### 4.3 Other functions

##### 4.3-1 IO_GetFD

`> IO_GetFD`( f ) | ( function )

Returns: an integer

This function returns the real file descriptor that is behind the `File` object f.

##### 4.3-2 IO_GetWBuf

`> IO_GetWBuf`( f ) | ( function )

Returns: a string or `false`

This function gets one argument f which must be a `File` object and returns the writing buffer of that `File` object. This is necessary for `File` objects, that are not associated to a real file descriptor but just collect everything that was written in their writing buffer. Remember to use this function before closing the `File` object.

##### 4.3-3 IO_Select

`> IO_Select`( r, w, f, e, t1, t2 ) | ( function )

Returns: an integer or `fail`

This function is the corresponding function to `IO_select` (3.2-43) for buffered file access. It behaves similarly to that function. The differences are the following: There are four lists of files r, w, f, and e. They all can contain either integers (standing for file descriptors) or `File` objects. The list r is for checking, whether files or file descriptors are ready to read, the list w is for checking whether they are ready to write, the list f is for checking whether they are ready to flush, and the list e is for checking whether they have exceptions.

For `File` objects it is always first checked, whether there is either data available in a reading buffer or space in a writing buffer. If so, they are immediately reported to be ready (this feature makes the list of `File` objects to test for flushability necessary). For the remaining files and for all specified file descriptors, the function `IO_select` (3.2-43) is called to get an overview about readiness. The timeout values t1 and t2 are set to zero for immediate returning if one of the requested buffers were ready.

`IO_Select` returns the number of files or file descriptors that are ready to serve or `fail` if an error occurs.

The following function is a convenience function for directory access:

##### 4.3-4 IO_ListDir

`> IO_ListDir`( pathname ) | ( function )

Returns: a list of strings or `fail`

This function gets a string containing a path name as single argument and returns a list of strings that are the names of the files in that directory, or `fail`, if an error occurred.

The following function is used to create strings describing a pair of an IP address and a port number in a binary way. These strings can be used in connection with the C library functions `connect`, `bind`, `recvfrom`, and `sendto` for the arguments needing such address pairs.

##### 4.3-5 IO_MakeIPAddressPort

`> IO_MakeIPAddressPort`( ipstring, portnr ) | ( function )

Returns: a string

This function gets a string ipstring containing an IP address in dot notation, i.e. four numbers in the range from 0 to 255 separated by dots ".", and an integer portnr, which is a port number. The result is a string of the correct length to be used for the low level C library functions, wherever IP address port number pairs are needed.

##### 4.3-6 IO_Environment

`> IO_Environment`( ) | ( function )

Returns: a record or `fail`

Takes no arguments, uses `IO_environ` (3.3-2) to get the environment and returns a record in which the component names are the names of the environment variables and the values are the values. This can then be changed and the changed record can be given to `IO_MakeEnvList` (4.3-7) to produce again a list which can be used for `IO_execve` (3.2-13) as third argument.

##### 4.3-7 IO_MakeEnvList

`> IO_MakeEnvList`( r ) | ( function )

Returns: a list of strings

Takes a record as returned by `IO_Environment` (4.3-6) and turns it into a list of strings as needed by `IO_execve` (3.2-13) as third argument.

#### 4.4 Inter process communication

##### 4.4-1 IO_FindExecutable

`> IO_FindExecutable`( path ) | ( function )

Returns: `fail` or the path to an executable

If the path name path contains a slash, this function simply checks whether the string path refers to an executable file. If so, path is returned as is. Otherwise, `fail` is returned. If the path name path does not contain a slash, all directories in the environment variable `PATH` are searched for an executable with name path. If so, the full path to that executable is returned, otherwise `fail`.

This function is used whenever one of the following functions gets an argument that should refer to an executable.

##### 4.4-2 IO_CloseAllFDs

`> IO_CloseAllFDs`( exceptions ) | ( function )

Returns: nothing

Closes all file descriptors except those listed in exceptions, which must be a list of integers.

##### 4.4-3 IO_Popen

`> IO_Popen`( path, argv, mode ) | ( function )

Returns: a `File` object or `fail`

The argument path must refer to an executable file in the sense of `IO_FindExecutable` (4.4-1).

Starts a child process using the executable in path with either stdout or stdin being a pipe. The argument mode must be either the string "`r`" or the string "`w`".

In the first case, the standard output of the child process will be the writing end of a pipe. A `File` object for reading connected to the reading end of the pipe is returned. The standard input and standard error of the child process will be the same than the calling GAP process.

In the second case, the standard input of the child process will be the reading end of a pipe. A `File` object for writing connected to the writing end of the pipe is returned. The standard output and standard error of the child process will be the same than the calling GAP process.

In case of an error, `fail` is returned.

The process will usually die, when the pipe is closed, but can also do so without that. The `File` object remembers the process ID of the started process and the `IO_Close` (4.2-16) function then calls `IO_WaitPid` (3.2-52) for it to acquire information about the terminated process.

Note that `IO_Popen` activates our SIGCHLD handler (see `IO_InstallSIGCHLDHandler` (3.3-3)).

In either case the `File` object will have the attribute "`ProcessID`" set to the process ID of the child process.

##### 4.4-4 IO_Popen2

`> IO_Popen2`( path, argv ) | ( function )

Returns: a record or `fail`

The argument path must refer to an executable file in the sense of `IO_FindExecutable` (4.4-1).

A new child process is started using the executable in path The standard input and standard output of it are pipes. The writing end of the input pipe and the reading end of the output pipe are returned as `File` objects bound to two components "`stdin`" and "`stdout`" (resp.) of the returned record. This means, you have to write to "`stdin`" and read from "`stdout`" in the calling GAP process. The standard error of the child process will be the same as the one of the calling GAP process.

Returns `fail` if an error occurred.

The process will usually die, when one of the pipes is closed. The `File` objects remember the process ID of the called process and the function call to `IO_Close` (4.2-16) for the `stdout` object will call `IO_WaitPid` (3.2-52) for it to acquire information about the terminated process.

Note that `IO_Popen2` activates our SIGCHLD handler (see `IO_InstallSIGCHLDHandler` (3.3-3)).

Both `File` objects will have the attribute "`ProcessID`" set to the process ID of the child process, which will also be bound to the "`pid`" component of the returned record.

##### 4.4-5 IO_Popen3

`> IO_Popen3`( path, argv ) | ( function )

Returns: a record or `fail`

The argument path must refer to an executable file in the sense of `IO_FindExecutable` (4.4-1).

A new child process is started using the executable in path The standard input, standard output, and standard error of it are pipes. The writing end of the input pipe, the reading end of the output pipe and the reading end of the error pipe are returned as `File` objects bound to two components "`stdin`", "`stdout`", and "`stderr`" (resp.) of the returned record. This means, you have to write to "`stdin`" and read from "`stdout`" and "`stderr`" in the calling GAP process.

Returns `fail` if an error occurred.

The process will usually die, when one of the pipes is closed. All three `File` objects will remember the process ID of the newly created process and the call to the `IO_Close` (4.2-16) function for the `stdout` object will call `IO_WaitPid` (3.2-52) for it to acquire information about the terminated child process.

Note that `IO_Popen3` activates our SIGCHLD handler (see `IO_InstallSIGCHLDHandler` (3.3-3)).

All three `File` objects will have the attribute "`ProcessID`" set to the process ID of the child process, which will also be bound to the "`pid`" component of the returned record.

##### 4.4-6 IO_StartPipeline

`> IO_StartPipeline`( progs, infd, outfd, switcherror ) | ( function )

Returns: a record or `fail`

The argument progs is a list of pairs, the first entry being a path to an executable (in the sense of `IO_FindExecutable` (4.4-1)), the second an argument list, the argument infd is an open file descriptor for reading, outfd is an open file descriptor for writing, both can be replaced by the string "`open`" in which case a new pipe will be opened. The argument switcherror is a boolean indicating whether standard error channels are also switched to the corresponding output channels.

This function starts up all processes and connects them with pipes. The input of the first is switched to infd and the output of the last to outfd.

Returns a record with the following components: `pids` is a list of process ids if everything worked. For each process for which some error occurred the corresponding pid is replaced by `fail`. The `stdin` component is equal to `false`, or to the file descriptor of the writing end of the newly created pipe which is connected to the standard input of the first of the new processes if infd was "`open`". The `stdout` component is equal to `false` or to the file descriptor of the reading end of the newly created pipe which is connected to the standard output of the last of the new processes if outfd was "`open`".

Note that the SIGCHLD handler of the IO package is installed by this function (see `IO_InstallSIGCHLDHandler` (3.3-3)) and that it lies in the responsibility of the caller to use `IO_WaitPid` (3.2-52) to ask for the status information of all child processes after their termination.

##### 4.4-7 IO_StringFilterFile

`> IO_StringFilterFile`( progs, filename ) | ( function )

Returns: a string or `fail`

Reads the file with the name filename, however, a pipeline is created by the processes described by progs (see `IO_StartPipeline` (4.4-6)) to filter the content of the file through the pipeline. The result is put into a GAP string and returned. If something goes wrong, `fail` is returned.

##### 4.4-8 IO_StringFilterFile

`> IO_StringFilterFile`( filename, progs, st[, append] ) | ( function )

Returns: a string or `fail`

Writes the content of the string st to the file with the name filename, however, a pipeline is created by the processes described by progs (see `IO_StartPipeline` (4.4-6)) to filter the content of the string through the pipeline. The result is put into the file. If the boolean value append is given and equal to `true`, then the data will be appended to the already existing file. If something goes wrong, `fail` is returned.

##### 4.4-9 IO_FilteredFile

`> IO_FilteredFile`( progs, filename[, mode][,][bufsize] ) | ( function )

Returns: a `File` object or `fail`

This function is similar to `IO_File` (4.1-3) and behaves nearly identically. The only difference is that a filtering pipeline is switched between the file and the `File` object such that all things read or written respectively are filtered through this pipeline of processes.

The `File` object remembers the started processes and upon the final call to `IO_Close` (4.2-16) automatically uses the `IO_WaitPid` (3.2-52) function to acquire information from the terminated processes in the pipeline after their termination. This means that you do not have to call `IO_WaitPid` (3.2-52) any more after the call to `IO_Close` (4.2-16).

Note that `IO_FilteredFile` activates our SIGCHLD handler (see `IO_InstallSIGCHLDHandler` (3.3-3)).

The `File` object will have the attribute "`ProcessID`" set to the list of process IDs of the child processes.

##### 4.4-10 IO_SendStringBackground

`> IO_SendStringBackground`( f, st ) | ( function )

This functions uses `IO_Write` (4.2-7) to write the whole string st to the `File` object f. However, this is done by forking off a child process identical to the calling GAP process that does the sending. The calling GAP process returns immediately, even before anything has been sent away with the result `true`. The forked off sender process terminates itself immediately after it has sent all data away.

The reason for having this function available is the following: If one uses `IO_Popen2` (4.4-4) or `IO_Popen3` (4.4-5) to start up a child process with standard input and standard output being a pipe, then one usually has the problem, that the child process starts reading some data, but then wants to write data, before it received all data coming. If the calling GAP process would first try to write all data and only start to read the output of the child process after sending away all data, a deadlock situation would occur. This is avoided with the forking and backgrounding approach.

Remember to close the writing end of the standard input pipe in the calling GAP process directly after `IO_SendStringBackground` has returned, because otherwise the child process might not notice that all data has arrived, because the pipe persists! See the file `popen2.g` in the `example` directory for an example.

Note that with most modern operating systems the forking off of an identical child process does in fact not mean a duplication of the total main memory used by both processes, because the operating system kernel will use "copy on write". However, if a garbage collection happens to become necessary during the sending of the data in the forked off sending process, this might trigger doubled memory usage.

##### 4.4-11 IO_PipeThrough

`> IO_PipeThrough`( cmd, args, input ) | ( function )

Returns: a string or `fail`

Starts the process with the executable given by the file name cmd (in the sense of `IO_FindExecutable` (4.4-1)) with arguments in the argument list args (a list of strings). The standard input and output of the started process are connected via pipes to the calling process. The content of the string input is written to the standard input of the called process and its standard output is read and returned as a string.

All the necessary I/O multiplexing and non-blocking I/O to avoid deadlocks is done in this function.

This function properly does `IO_WaitPid` (3.2-52) to wait for the termination of the child process but does not restore the original GAP SIGCHLD signal handler (see `IO_InstallSIGCHLDHandler` (3.3-3)).

##### 4.4-12 IO_PipeThroughWithError

`> IO_PipeThroughWithError`( cmd, args, input ) | ( function )

Returns: a record or `fail`

Starts the process with the executable given by the file name cmd (in the sense of `IO_FindExecutable` (4.4-1)) with arguments in the argument list args (a list of strings). The standard input, output and error of the started process are connected via pipes to the calling process. The content of the string input is written to the standard input of the called process and its standard output and error are read and returned as a record with components `out` and `err`, which are strings.

All the necessary I/O multiplexing and non-blocking I/O to avoid deadlocks is done in this function.

This function properly does `IO_WaitPid` (3.2-52) to wait for the termination of the child process but does not restore the original GAP SIGCHLD signal handler (see `IO_InstallSIGCHLDHandler` (3.3-3)).

The functions returns either `fail` if an error occurred, or otherwise a record with components `out` and `err` which are bound to strings containing the full standard output and standard error of the called process.

Top of Book | Previous Chapter | Next Chapter

Goto Chapter: | Top | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Ind

generated by GAPDoc2HTML