Manual
File I/O
 previous   up   next 

8. FILE INPUT AND OUTPUT

Files are used for communication in various ways. For example: To write strings on the screen we use the following statements:

write("hello world");
writeln;

The procedure write writes a given string and writeln means: Write newline. We can also write data of various types with 'write':

write("result = ");
write(number div 5);
write(" ");
writeln(not error);

The 'writeln' above writes data and then terminates the line. This is equal to a 'write' followed by a 'writeln'. Instead of multiple write statements the <& operator can be used to concatenate the elements to be written:

writeln("result = " <& number div 5 <& " " <& not error);

The <& operator is overloaded for various types and is present it three variants:

This allows chaining concatenations like in:

write(next_time <& " is " <& booleanExpression);

Several objects can be concatenated in a chain, if the first or the second object is a string.

We can also read data from the keyboard:

write("Amount? ");
read(amount);

The user is allowed to use Backspace and sends the input to the program with the Return key. To let the user respond with the Return key we can write:

writeln("Type RETURN");
readln;

To read a line of data we can use 'readln':

write("Your comment? ");
readln(user_comment_string);

In the previous examples all 'read' statements read from the file IN and all 'write' statements write to the file OUT. The files IN and OUT are initialized with STD_IN and STD_OUT which are the stdin and stdout files of the operating system. (Usually the keyboard and the screen). If we want to write to other files we use write statements with the file as first parameter. To write a line of text to the file "info.fil" we use the following statements:

info_file := open("info.fil", "w");
writeln(info_file, "This is the first line of the info file.");
close(info_file);

First the external file is opened for writing and then it is used. To read the file back in the string 'stri' we write:

info_file := open("info.fil", "r");
readln(info_file, stri);
close(info_file);

It is also possible to write values of other types to 'info_file':

writeln(info_file, number);

Here the 'number' is converted to a string which is written to the file. A 'number' is read back with:

readln(info_file, number);

For doing I/O to a window on the screen we write:

window1 := openWindow(screen, 10, 10, 5, 60);
box(window1);
setPos(window1, 3, 1);
write(window1, "hello there");

This opens the window 'window1' on the 'screen' at the position 10, 10. This window has 5 lines and 60 columns. A box (of characters: - | + ) is written to surround the 'window1' and finally the string "hello there" is written in the window 'window1' at Position 3, 1. If we want to clear the 'window1' we write:

clear(window1);

Files can be used for much more things. Here is a list of goals for an input/output system:

In the following sub-chapters we discuss each of these goals.

8.1 Conversion to strings and back

We archive the goal of doing I/O for arbitrary types with two conversion functions. In order to do I/O with a type the str and parse functions must be defined for that type. As an example we show the conversion functions for the type boolean:

const func string: str (in boolean: aBool) is func
  result
    var string: stri is "";
  begin
    if aBool then
      stri := "TRUE";
    else
      stri := "FALSE";
    end if;
  end func;

const func boolean: (attr boolean) parse (in string: stri) is func
  result
    var boolean: aBoolean is FALSE;
  begin
    if stri = "TRUE" then
      aBoolean := TRUE;
    elsif stri = "FALSE" then
      aBoolean := FALSE;
    else
      raise RANGE_ERROR;
    end if;
  end func;

The str function must deliver a corresponding string for every value of the type. The parse operator parses a string and delivers the converted value as result. If the conversion is not successful the exception RANGE_ERROR is raised. The attribute used with parse allows that it is overloaded for different types.

After defining the str and parse functions for a type the enable_io function can be called for this type as in:

enable_io(boolean);

The enable_io template declares various io functions like 'read', 'write' and others for the provided type (in this example boolean). If only output (or only input) is needed for a type it is possible to define just str (or parse) and activate just enable_output (or enable_input).

There is also a formatting operator called lpad which is based on the str function. The statements

write(12 lpad 6);
write(3 lpad 6);
writeln(45 lpad 6);
write(678 lpad 6);
write(98765 lpad 6);
writeln(4321 lpad 6);

produce the following output:

    12     3    45
   678 98765  4321

As we see the lpad operator can be used to produce right justified output. There is also rpad operator to produce left justified output. The basic definitions of the lpad and rpad operators work on strings and are as follows:

const func string: (ref string: stri) lpad (in integer: leng) is func
  result
    var string: padded is "";
  begin
    if leng > length(stri) then
      padded := " " mult leng - length(stri) & stri;
    else
      padded := stri;
    end if;
  end func;

const func string: (ref string: stri) rpad (in integer: leng) is func
  result
    var string: padded is "";
  begin
    if leng > length(stri) then
      padded := stri & " " mult leng - length(stri);
    else
      padded := stri;
    end if;
  end func;

The enable_io template contains definitions of lpad and rpad to work on the type specified with enable_io:

const func string: (in aType: aValue) lpad (in integer: leng) is
  return str(aValue) lpad leng;

const func string: (in aType: aValue) rpad (in integer: leng) is
  return str(aValue) rpad leng;

Values of type integer and bigInteger can be written in a numeral system with a radix (base) other than 10. The operators radix and RADIX can be used for this purpose. E.g. the statements

writeln(48879 radix 16);
writeln(3735928559_ RADIX 16);

produce the following output:

beef
DEADBEEF

For float values exist additional ways to convert them to strings. The digits operator allows the specification of a precision. E.g. the statements

writeln(3.1415 digits 2);
writeln(4.0 digits 2);

produce the following output:

3.14
4.00

A combination with the lpad operator as in

writeln(3.1415 digits 2 lpad 6);
writeln(99.9 digits 2 lpad 6);

is also possible and produces the following output:

  3.14
 99.90

Scientific notation for float is supported with the conversion operator sci. The statements

writeln(0.012345 sci 4);
writeln(1.2468 sci 2 );
writeln(3.1415 sci 0);
writeln(0.125 sci 1);
writeln(0.375 sci 1);

produce the following output:

1.2345e-2
1.25e+0
3e+0
1.2e-1
3.8e-1

The operator exp is used to specify the number of exponent digits. The statements

writeln(0.012345 sci 4 exp 2);
writeln(1.2468e15 sci 2 exp 1);
writeln(3.1415 sci 0 exp 3);
writeln(0.125 sci 1 exp 2);
writeln(0.375 sci 1 exp 2);

produce the following output:

1.2345e-02
1.25e+15
3e+000
1.2e-01
3.8e-01

8.2 Basic input and output operations

To allow arbitrary user defined file-types beside the operating system files we chose a model in which the I/O methods are assigned to the type of the file-value and not to the type of the file-variable. This allows a file variable to point to any file-value. The file-variables have the type file, which is the interface type for sequential files. For the operating system files and for each user defined file a file-type must be declared which has the I/O methods defined. These file-types are derived (direct or indirect) from the type null_file for which all I/O methods are defined upon a base of basic string I/O methods. So for a new user defined file-type only the basic string I/O methods must be defined.

The two basic I/O methods defined for null_file are

const proc: write (in null_file: aFile, in string: stri) is noop;

const func string: gets (in null_file: inFile, in integer: maxLength) is func
  result
    var string: striRead is "";
  begin
    if maxLength < 0 then
      raise RANGE_ERROR;
    end if;
  end func;

A write to null_file with any string has no effect. Reading any number of characters with gets from null_file delivers the empty string. When a user defined file type is declared these are the two methods, which must be redefined, for the new file-type. Based upon these two methods three more methods are defined for null_file, named getc, getwd and getln. These methods get a character, a word and a line respectively. A word is terminated by a space, a tab or a linefeed. A line is terminated by a linefeed. This methods need not to be redefined for a user defined file type but for performance reasons they can also be redefined. The definitions for getc, getwd and getln for null_file are

const func char: getc (inout null_file: aFile) is func
  result
    var char: ch is ' ';
  local
    var string: buffer is "";
  begin
    buffer := gets(aFile, 1);
    if buffer = "" then
      ch := EOF;
    else
      ch := buffer[1];
    end if;
  end func;

const func string: getwd (inout null_file: aFile) is func
  result
    var string: stri is "";
  local
    var string: buffer is "";
  begin
    repeat
      buffer := gets(aFile, 1);
    until buffer <> " " and buffer <> "\t";
    while buffer <> " " and buffer <> "\t" and
        buffer <> "\n" and buffer <> "" do
      stri &:= buffer;
      buffer := gets(aFile, 1);
    end while;
    if buffer = "" then
      aFile.bufferChar := EOF;
    else
      aFile.bufferChar := buffer[1];
    end if;
  end func;

const func string: getln (inout null_file: aFile) is func
  result
    var string: stri is "";
  local
    var string: buffer is "";
  begin
    buffer := gets(aFile, 1);
    while buffer <> "\n" and buffer <> "" do
      stri &:= buffer;
      buffer := gets(aFile, 1);
    end while;
    if buffer = "" then
      aFile.bufferChar := EOF;
    else
      aFile.bufferChar := buffer[1];
    end if;
  end func;

Note that getwd skips leading spaces and tabs while getc and getln do not. If getc, getwd or getln is not defined for a new user defined file type the declarations from the null_file are used instead. These declarations are based on the method gets which must be defined for every new user defined file-type.

Note that there is an assignment to the variable 'bufferChar'. This variable is an element of null_file and therefore also an element of all derived file types. This allows an 'eoln' function to test if the last getwd or getln reach the end of a line. Here is a definition of the 'eoln' function:

const func boolean: eoln (in null_file: inFile) is
  return inFile.bufferChar = '\n';

Besides assigning a value to 'bufferChar' in getwd and getln and using it in 'eoln' the standard file functions do nothing with 'bufferChar'. The functions of the "scanfile.s7i" library use the 'bufferChar' variable as current character in the scan process. As such all functions of the "scanfile.s7i" library assume that the first character to be processed is always in 'bufferChar'. Since the standard file functions do not have this behavior, care has to be taken if mixing scanner and file functions.

The type null_file provides default functions to write end-of-line:

const proc: writeln (inout null_file: outFile) is func
  begin
    write(outFile, "\n");
  end func;

const proc: writeln (inout null_file: outFile, in string: stri) is func
  begin
    write(outFile, stri);
    writeln(outFile);
  end func;

The next declarations allow various I/O operations for strings:

const proc: read (inout file: aFile, inout string: stri) is func
  begin
    stri := getwd(aFile);
  end func;

const proc: readln (inout file: aFile, inout string: stri) is func
  begin
    stri := getln(aFile);
  end func;

8.3 Input and output with conversion

Normally we need a combination of an I/O operation with a conversion operation. There are several functions which are based on the str and parse conversions and on the basic I/O-functions. The declaration of this functions is done by the templates enable_io, enable_input and enable_output. The templates enable_io and enable_output define the following write function:

const proc: write (in file: aFile, in aType: aValue) is func
  begin
    write(aFile, str(aValue));
  end func;

The templates enable_io and enable_input define the following read and readln functions:

const proc: read (inout file: aFile, inout aType: aValue) is func
  begin
    aValue := aType parse getwd(aFile);
  end func;

const proc: readln (inout file: aFile, inout aType: aValue) is func
  begin
    aValue := aType parse trimValue(aType, getln(aFile));
  end func;

The function trimValue is used to trim the string retrieved by getln. This adjusts the string before the parse operator is applied. There are 3 cases:

The next declaration defines 'backSpace':

const proc: backSpace (ref external_file: aFile) is func
  begin
    write(aFile, "\b \b");
  end func;

8.4 Simple read and write statements

The simple input/output for the standard I/O-files are 'read' and 'write' which are defined with enable_io. Simple I/O may look like:

write("Amount? ");
read(amount);

'read' and 'write' use the files IN and OUT, which are described in the next chapter. Here is the definition of the 'read' and 'write' procedures done with enable_io:

const proc: read (inout aType: aValue) is func
  begin
    read(IN, aValue);
  end func;

const proc: readln (inout aType: aValue) is func
  begin
    readln(IN, aValue);
  end func;

const proc: write (in aType: aValue) is func
  begin
    write(OUT, aValue);
  end func;

const proc: writeln (in aType: aValue) is func
  begin
    writeln(OUT, aValue);
  end func;

Additional procedures defined outside of enable_io are:

const proc: readln is func
  local
    var string: stri is "";
  begin
    stri := getln(IN);
  end func;

const proc: writeln is func
  begin
    writeln(OUT);
  end func;

As an example, when you call

readln(number);

the readln(number) function of the type integer calls

readln(IN, number);

which executes

number := integer parse trimValue(integer, getln(IN));

The file IN may have its own implementation of getln (e.g. as getln of external_file). The default implementation of getln (in null_file) calls gets(IN, 1) in a loop. For the type integer the function trimValue removes leading and trailing spaces. Finally the parse operator converts the string read into an integer which is assigned to 'number'.

8.5 Standard input and output files

The standard I/O files are IN for input and OUT for output. IN and OUT are file variables, which are defined as follows:

var file: IN is STD_IN;
var file: OUT is STD_OUT;

The files STD_IN and STD_OUT are the standard input and output files of the operating system (Usually the keyboard and the screen). Because IN and OUT are variables redirection of standard input or standard output can be done easily by assigning a new value to them:

IN := OTHER_FILE;

After that all 'read' statements refer to OTHER_FILE. Most operating systems have also a stderr file which can be accessed via the name STD_ERR. If you want to write error messages to the screen even if stdout is redirected elsewhere you can write:

writeln(STD_ERR, "ERROR MESSAGE");

To redirect the standard output to STD_ERR you can write:

OUT := STD_ERR;

There is also a file STD_NULL defined. Anything written to it is ignored. Reading from it does deliver empty strings. This file can be used to initialize file variables as in:

var file: MY_FILE is STD_NULL;

It is also used to represent an illegal file value, if for example an attempt to open a file fails.

8.6 Access to operating system files

The interface type file is also used to access operating system files. Usually a file variable is defined

var file: my_out is STD_NULL;

and the result of the open function is assigned to this file variable

my_out := open("my_file", "w");

The first parameter of open is the path of the file to be opened. The path must use the standard path representation. This means that a slash ('/') is used as path delimiter. A path with a backslash or a drive letter may raise the exception RANGE_ERROR. The second parameter of open specifies the mode:

Binary mode:
"r" ... Open file for reading.
"w" ... Truncate to zero length or create file for writing.
"a" ... Append; open or create file for writing at end-of-file.
"r+" ... Open file for update (reading and writing).
"w+" ... Truncate to zero length or create file for update.
"a+" ... Append; open or create file for update, writing at end-of-file.
Text mode:
"rt" ... Open file for reading.
"wt" ... Truncate to zero length or create file for writing.
"at" ... Append; open or create file for writing at end-of-file.
"rt+" ... Open file for update (reading and writing).
"wt+" ... Truncate to zero length or create file for update.
"at+" ... Append; open or create file for update, writing at end-of-file.

Note that Seed7 defines the modes "r", "w", "a", "r+", "w+" and "a+" as binary modes. If open is called, with a mode not listed in the table above, the exception RANGE_ERROR is raised. If there is not enough memory to convert 'path' to the system path type the exception MEMORY_ERROR is raised. If open fails for other reasons it returns STD_NULL. E.g.: It is not allowed to open a directory. An attempt to open a directory returns STD_NULL. It is recommended to check the file variable after opening a file:

if my_out <> STD_NULL then

After that output to 'my_out' is possible with

writeln(my_out, "hi there");

When processing of a file has finished it should be closed

close(my_out);

Writing to a file after it has been closed results in the exception FILE_ERROR. The following program writes "hi there" to the file "my_file":

$ include "seed7_05.s7i";

const proc: main is func
  local
    var file: my_out is STD_NULL;
  begin
    my_out := open("my_file", "w");
    if my_out <> STD_NULL then
      writeln(my_out, "hi there");
      close(my_out);
    end if;
  end func;

Note that open opens BYTE files. Writing a character with an ordinal >= 256 such as

writeln(my_out, "illegal char: \256;");

results in the exception RANGE_ERROR. To write Unicode characters other file types must be used. The libraries "utf8.s7i" and "utf16.s7i" provide access to UTF-8 and UTF-16 files. The function openUtf8 can be used the same way as open:

my_out := openUtf8("utf8_file", "w");

An UTF-8 file accepts all Unicode characters. That way

writeln(my_out, "Unicode char: \256;");

works without problems. UTF-8 files are byte order independent. Therefore they do not need a byte order mark (BOM). In case a BOM is required it can be written by the user program:

my_out := openUtf8("utf8_file", "w");
write("\16#feff;");

The following example expects a mandatory BOM at the beginning of an UTF-8 file:

my_out := openUtf8("utf8_file", "r");
if getc(my_file) <> '\16#feff;' then
  writeln("The BOM is missing"");
else
  ...
end if;

Accepting an optional BOM at the beginning of an UTF-8 file is done with:

my_out := openUtf8("utf8_file", "r");
if getc(my_file) <> '\16#feff;' then
  # This is a file without BOM (the first character will be read later).
  seek(my_file, 1);
end if;
...

UTF-16 comes in two flavors UTF-16LE and UTF-16BE. To support both flavors the "utf16.s7i" library defines several functions.

The function openUtf16 opens a Unicode file which uses the UTF-16LE or UTF-16BE encoding. The function openUtf16 checks for a BOM and depending on that it opens an UTF-16LE or UTF-16BE file.

The functions openUtf16le and openUtf16be open Unicode files with the UTF-16LE and UTF-16BE encoding respectively. If the file is opened with one of the modes "w", "w+", "wt" or "wt+" an appropriate BOM is created. If the file is opened with any other mode the application program is in charge to handle optional BOM markers. This way openUtf16le and openUtf16be can be used to open existing files without BOM.

External BYTE files use the implementation type external_file. The type external_file is defined as:

const type: external_file is sub null_file struct
    var clib_file: ext_file is CLIB_NULL_FILE;
    var string: name is "";
  end struct;

This means that every data item of the type external_file has the elements from null_file and additionally the elements 'ext_file' and 'name'. The type clib_file points directly to an operating system file. Objects of type clib_file can only have operating system files as values while objects of type file can also have other files as values. To allow the implementation of the type external_file several operations for the type clib_file are defined. But outside external_file the type clib_file and its operations should not be used.

There are three predefined external files STD_IN, STD_OUT and STD_ERR which have the following declarations:

const func external_file: INIT_STD_FILE (ref clib_file: primitive_file,
    in string: file_name) is func
  result
    var external_file: standardFile is external_file.value;
  begin
    standardFile.ext_file := primitive_file;
    standardFile.name := file_name;
  end func;

var external_file: STD_IN is  INIT_STD_FILE(CLIB_INPUT,  "STD_IN");
var external_file: STD_OUT is INIT_STD_FILE(CLIB_OUTPUT, "STD_OUT");
var external_file: STD_ERR is INIT_STD_FILE(CLIB_ERROR,  "STD_ERR");

It is possible to do I/O directly with them, but it is more wisely to use them only to initialize user defined file variables as in:

var file: err is STD_ERR;

In the rest of the program references to such a variable can be used:

writeln(err, "Some error occurred");

In this case redirection of the file 'err' can be done very easy. Another way to access external files is to use the function open. The modes used by open differ from those used by the 'fopen' function in the C library. The following table compares the file modes of Seed7 and C:

Seed7 'open' mode C 'fopen' mode
"r" "rb"
"w" "wb"
"a" "ab"
"r+" "rb+"
"w+" "wb+"
"a+" "ab+"
"rt" "r"
"wt" "w"
"at" "a"
"rt+" "r+"
"wt+" "w+"
"at+" "a+"

The difference between binary and text mode is as follows:

The library "utf8.s7i" defines the implementation type utf8File as

const type: utf8File is sub external_file struct
  end struct;

8.7 Keyboard file

As stated earlier STD_IN provides an interface to the keyboard which is line buffered and echoed on STD_OUT. This means that you can see everything you typed. Additionally you can correct your input with Backspace until you press Return. But sometimes an unbuffered and unechoed input is needed. This is provided in the library "keybd.s7i", which defines the type keyboard_file and the file KEYBOARD. Characters typed at the keyboard are queued (first in first out) and can be read directly from KEYBOARD without any possibility to correct. Additionally KEYBOARD does not echo the characters. Reading from KEYBOARD delivers normal Unicode characters or special codes (which may be or may not be Unicode characters) for function and cursor keys. Unicode characters and special codes both are char values. The library "keybd.s7i" defines char constants for various keys:

Key character constant Description
KEY_CTL_A to KEY_CTL_Z The control keys ctrl-a to ctrl-z
KEY_ALT_A to KEY_ALT_Z The alternate keys alt-a to alt-z
KEY_CTL_0 to KEY_CTL_9 The control keys ctrl-0 to ctrl-9
KEY_ALT_0 to KEY_ALT_9 The alternate keys alt-0 to alt-9
KEY_F1 to KEY_F10 Function keys F1 to F10
KEY_SFT_F1 to KEY_SFT_F10 Shifted function keys F1 to F10
KEY_CTL_F1 to KEY_CTL_F10 Control function keys F1 to F10
KEY_ALT_F1 to KEY_ALT_F10 Alternate function keys F1 to F10
KEY_LEFT Cursor left
KEY_RIGHT Cursor right
KEY_UP Cursor up
KEY_DOWN Cursor down
KEY_HOME Home key
KEY_END End key
KEY_PGUP Page up
KEY_PGDN Page down
KEY_INS Insert key
KEY_DEL Delete key
KEY_PAD_CENTER Numeric keypad center key
KEY_SFT_LEFT Shifted cursor left
KEY_SFT_RIGHT Shifted cursor right
KEY_SFT_UP Shifted cursor up
KEY_SFT_DOWN Shifted cursor down
KEY_SFT_HOME Shifted home key
KEY_SFT_END Shifted end key
KEY_SFT_PGUP Shifted page up
KEY_SFT_PGDN Shifted page down
KEY_SFT_INS Shifted insert key
KEY_SFT_DEL Shifted delete key
KEY_SFT_PAD_CENTER Shifted numeric keypad center key
KEY_CTL_LEFT Control cursor left
KEY_CTL_RIGHT Control cursor right
KEY_CTL_UP Control cursor up
KEY_CTL_DOWN Control cursor down
KEY_CTL_HOME Control home key
KEY_CTL_END Control end key
KEY_CTL_PGUP Control page up
KEY_CTL_PGDN Control page down
KEY_CTL_INS Control insert key
KEY_CTL_DEL Control delete key
KEY_CTL_PAD_CENTER Control numeric keypad center key
KEY_ALT_LEFT Alt cursor left
KEY_ALT_RIGHT Alt cursor right
KEY_ALT_UP Alt cursor up
KEY_ALT_DOWN Alt cursor down
KEY_ALT_HOME Alt home key
KEY_ALT_END Alt end key
KEY_ALT_PGUP Alt page up
KEY_ALT_PGDN Alt page down
KEY_ALT_INS Alt insert key
KEY_ALT_DEL Alt delete key
KEY_ALT_PAD_CENTER Alt numeric keypad center key
KEY_NL Newline/enter/return key (equal to KEY_CTL_J)
KEY_BS Backspace (equal to KEY_CTL_H)
KEY_TAB Horizontal tab (equal to KEY_CTL_H)
KEY_CR Carriage return (equal to KEY_CTL_M)
KEY_ESC Escape key
KEY_MENU Menu key
KEY_PRINT Print key
KEY_PAUSE Pause key
KEY_SFT_NL Shift newline/enter/return key
KEY_SFT_BS Shift backspace
KEY_SFT_TAB Shift tab (same as KEY_BACKTAB)
KEY_BACKTAB Shift tab (same as KEY_SFT_TAB)
KEY_SFT_ESC Shift escape
KEY_SFT_MENU Shift menu
KEY_SFT_PRINT Shift print
KEY_SFT_PAUSE Shift pause
KEY_CTL_NL Control newline/enter/return key
KEY_CTL_BS Control backspace
KEY_CTL_TAB Control tab
KEY_CTL_ESC Control escape
KEY_CTL_MENU Control menu
KEY_CTL_PRINT Control print
KEY_CTL_PAUSE Control pause
KEY_ALT_NL Alt newline/enter/return key
KEY_ALT_BS Alt backspace
KEY_ALT_TAB Alt tab
KEY_ALT_ESC Alt escape
KEY_ALT_MENU Alt menu
KEY_ALT_PRINT Alt print
KEY_ALT_PAUSE Alt pause
KEY_SCRLUP Scroll up key
KEY_SCRLDN Scroll down key
KEY_INSLN Insert line key
KEY_DELLN Delete line key
KEY_ERASE Erase key
KEY_NULCHAR Nul character key
KEY_NULLCMD Null command of window manager
KEY_REDRAW Redraw command of window manager
KEY_MOUSE1 Mouse button 1 (counted from left)
KEY_MOUSE2 Mouse button 2 (counted from left)
KEY_MOUSE3 Mouse button 3 (counted from left)
KEY_MOUSE4 Mouse wheel scroll up
KEY_MOUSE5 Mouse wheel scroll down
KEY_MOUSE_FWD Mouse forward button
KEY_MOUSE_BACK Mouse back button
KEY_SFT_MOUSE1 Shift mouse button 1 (counted from left)
KEY_SFT_MOUSE2 Shift mouse button 2 (counted from left)
KEY_SFT_MOUSE3 Shift mouse button 3 (counted from left)
KEY_SFT_MOUSE4 Shift mouse wheel scroll up
KEY_SFT_MOUSE5 Shift mouse wheel scroll down
KEY_SFT_MOUSE_FWD Shift mouse forward button
KEY_SFT_MOUSE_BACK Shift mouse back button
KEY_CTL_MOUSE1 Control mouse button 1 (counted from left)
KEY_CTL_MOUSE2 Control mouse button 2 (counted from left)
KEY_CTL_MOUSE3 Control mouse button 3 (counted from left)
KEY_CTL_MOUSE4 Control mouse wheel scroll up
KEY_CTL_MOUSE5 Control mouse wheel scroll down
KEY_CTL_MOUSE_FWD Control mouse forward button
KEY_CTL_MOUSE_BACK Control mouse back button
KEY_ALT_MOUSE1 Alt mouse button 1 (counted from left)
KEY_ALT_MOUSE2 Alt mouse button 2 (counted from left)
KEY_ALT_MOUSE3 Alt mouse button 3 (counted from left)
KEY_ALT_MOUSE4 Alt mouse wheel scroll up
KEY_ALT_MOUSE5 Alt mouse wheel scroll down
KEY_ALT_MOUSE_FWD Alt mouse forward button
KEY_ALT_MOUSE_BACK Alt mouse back button
KEY_CLOSE The close button of the window has been pressed
KEY_RESIZE The window has been resized
KEY_UNDEF Undefined key
KEY_NONE No key pressed (returned by getc(KEYBOARD, NO_WAIT))

The following example uses the char constant KEY_UP:

$ include "seed7_05.s7i";
  include "keybd.s7i";

const proc: main is func
  begin
    writeln("Please press cursor up");
    while getc(KEYBOARD) <> KEY_UP do
      writeln("This was not cursor up");
    end while;
    writeln("Cursor up was pressed");
  end func;

Programs should use the char constants defined in "keybd.s7i" to deal with function and cursor keys, since the special key codes may change in future versions of Seed7.

Note that getc(KEYBOARD) works synchronous. This means that it might wait (block) until a key has been pressed. Blocking can be avoided with the following functions:

Note that inputReady does not actually read a character. Reading must be done with with a different function (e.g. getc) after inputReady returns TRUE. The program below writes a sequence of # characters. If any key is pressed it starts a new line. Pressing Return (or Enter) terminates the program:

$ include "seed7_05.s7i";
  include "keybd.s7i";
  include "duration.s7i";

const proc: main is func
  begin
    repeat
      while not inputReady(KEYBOARD) do
        write("#");
        flush(OUT);
        wait(30000 . MICRO_SECONDS);
      end while;
      writeln;
    until getc(KEYBOARD) = KEY_NL;
  end func;

Both functions (getc(KEYBOARD, NO_WAIT) and inputReady) are useful when user input is allowed while some processing takes place. The following program uses getc(KEYBOARD, NO_WAIT) to display the time until a key is pressed:

$ include "seed7_05.s7i";
  include "time.s7i";
  include "keybd.s7i";

const proc: main is func
  begin
    writeln;
    while getc(KEYBOARD, NO_WAIT) = KEY_NONE do
      write(time(NOW) <& "\r");
      flush(OUT);
    end while;
    writeln;
    writeln;
  end func;

Seed7 programs can run in two modes:

These two modes are supported with two basic keyboard files:

The file KEYBOARD is actually a variable which refers to one of the two basic keyboard files. The declaration of the type keyboard_file and the file KEYBOARD in "keybd.s7i" is:

const type: keyboard_file is subtype file;

var keyboard_file: KEYBOARD is CONSOLE_KEYBOARD;

Graphic programs switch to to the GRAPH_KEYBOARD driver with:

KEYBOARD := GRAPH_KEYBOARD;

A GRAPH_KEYBOARD additionally provides the following functions:

Modifier keys such as shift, control, super and alt do not send key/button down or up events. The function buttonPressed can be used to determine if a modifier has been pressed. The program below displays blue and red squares depending on the state of the left and right shift keys. The program is terminated by pressing any non-modifier key:

$ include "seed7_05.s7i";
  include "keybd.s7i";
  include "draw.s7i";
  include "duration.s7i";

const proc: main is func
  begin
    screen(640, 480);
    KEYBOARD := GRAPH_KEYBOARD;
    repeat
      rect(  85, 190, 100, 100, buttonPressed(KEYBOARD, KEY_LEFT_SHIFT)  ? light_blue : light_red);
      rect( 270, 190, 100, 100, buttonPressed(KEYBOARD, KEY_SHIFT)       ? light_blue : light_red);
      rect( 455, 190, 100, 100, buttonPressed(KEYBOARD, KEY_RIGHT_SHIFT) ? light_blue : light_red);
      flushGraphic;
      wait(30000 . MICRO_SECONDS);
    until inputReady(KEYBOARD);
  end func;

Note that buttonPressed does not process key/button events. This must be done with inputReady or getc.

The library "keybd.s7i" provides definitions for modifier keys:

Key character constant Description
KEY_SHIFT Left or right shift is pressed
KEY_LEFT_SHIFT Left shift is pressed
KEY_RIGHT_SHIFT Right shift is pressed
KEY_CONTROL Left or right control is pressed
KEY_LEFT_CONTROL Left control is pressed
KEY_RIGHT_CONTROL Right control is pressed
KEY_ALT Left or right alt is pressed
KEY_LEFT_ALT Left alt is pressed
KEY_RIGHT_ALT Right alt is pressed
KEY_SUPER Left or right super is pressed
KEY_LEFT_SUPER Left super is pressed
KEY_RIGHT_SUPER Right super is pressed
KEY_SHIFT_LOCK Shift lock is pressed
KEY_SHIFT_LOCK_ON Shift lock is currently on
KEY_NUM_LOCK Num lock is pressed
KEY_NUM_LOCK_ON Num lock is currently on
KEY_SCROLL_LOCK Scroll lock is pressed
KEY_SCROLL_LOCK_ON Scroll lock is currently on

The functions clickedXPos and clickedYPos can be used to determine which position was "clicked". The program below uses clickedXPos and clickedYPos to produce a dot for each keypress.

$ include "seed7_05.s7i";
  include "keybd.s7i";
  include "draw.s7i";

const proc: main is func
  local
    var char: command is ' ';
  begin
    screen(640, 480);
    KEYBOARD := GRAPH_KEYBOARD;
    command := getc(KEYBOARD);
    while command = KEY_MOUSE1 do
      fcircle(clickedXPos(KEYBOARD), clickedYPos(KEYBOARD), 4, light_red);
      command := getc(KEYBOARD);
    end while;
  end func;

The current position of the mouse cursor, which is independent from key presses can be retrieved with the following functions:

The functions pointerXPos and pointerYPos can be used to move something with the cursor (e.g.: drag and drop). The program below uses buttonPressed to determine how long the mouse button is pressed. This is used together with pointerXPos and pointerYPos to draw along the mouse cursor while the mouse button is pressed:

$ include "seed7_05.s7i";
  include "keybd.s7i";
  include "draw.s7i";

const proc: main is func
  local
    var char: command is ' ';
  begin
    screen(640, 480);
    KEYBOARD := GRAPH_KEYBOARD;
    command := getc(KEYBOARD);
    while command = KEY_MOUSE1 do
      while buttonPressed(KEYBOARD, KEY_MOUSE1) do
        fcircle(pointerXPos(curr_win), pointerYPos(curr_win), 4, light_red);
      end while;
      command := getc(KEYBOARD);
    end while;
  end func;

Some keys are not delivered to the program by default. By default the close button of the window (often marked with X) just exits the program. By default resizing a window is also not communicated to the program. These two events can be activated with selectInput. This way a program can also receive KEY_CLOSE and KEY_RESIZE:

$ include "seed7_05.s7i";
  include "keybd.s7i";
  include "draw.s7i";

const proc: main is func
  local
    var char: command is ' ';
  begin
    screen(640, 480);
    KEYBOARD := GRAPH_KEYBOARD;
    selectInput(curr_win, KEY_CLOSE, TRUE);  # Enable the program to get KEY_CLOSE without closing the window.
    selectInput(curr_win, KEY_RESIZE, TRUE); # Enable the program to get KEY_RESIZE.
    command := getc(KEYBOARD);
    while command <> KEY_CLOSE do
      if command = KEY_RESIZE then
        lineTo(0, 0, width(curr_win), height(curr_win), white);
      end if;
      command := getc(KEYBOARD);
    end while;
  end func;

Some file types are defined to support the KEYBOARD. One such file type is echoFile, which is defined in the library "echo.s7i". An echoFile file can be used to write input characters to an output file. This is useful since KEYBOARD does not echo its input, but echoFile is not restricted to support KEYBOARD. The following program writes echoes of the keys typed and exits as soon as a '!' is encountered:

$ include "seed7_05.s7i";
  include "keybd.s7i";
  include "echo.s7i";

const proc: main is func
  local
    var char: ch is ' ';
  begin
    IN := openEcho(KEYBOARD, OUT);
    repeat
      ch := getc(IN);
    until ch = '!';
    writeln;
  end func;

An echoFile checks also for control-C (KEY_CTL_C). If control-C is typed an echoFile asks if the program should be terminated:

terminate (y/n)?

Answering 'y' or 'Y' is interpreted as 'yes' and the program is terminated with the following message:

*** PROGRAM TERMINATED BY USER

Any other input removes the question and the program continues to read input.

Another helpful file type is lineFile, which is defined in the library "line.s7i". A lineFile allows to correct the input with Backspace until a Return (represented with '\n') is encountered. In contrast to this editing feature the possibility to edit a line of STD_IN is provided by the operating system. The following program uses echoFile and lineFile to simulate input line editing:

$ include "seed7_05.s7i";
  include "keybd.s7i";
  include "echo.s7i";
  include "line.s7i";

const proc: main is func
  local
    var char: ch is ' ';
  begin
    IN := openEcho(KEYBOARD, OUT);
    IN := openLine(IN);
    repeat
      ch := getc(IN);
      write(ch);
    until ch = '!';
  end func;

This program terminates if a line containing '!' is confirmed with Return.

There is also the editLineFile, which is defined in the library "editline.s7i". An editLineFile provides more than a combination of an echoFile with a lineFile. An editLineFile allows editing with Backspace, Delete, , , Home and End. The vertical curser keys can be used to get previous input lines. Like an echoFile it checks also for control-C (KEY_CTL_C).

$ include "seed7_05.s7i";
  include "keybd.s7i";
  include "console.s7i";
  include "editline.s7i";

const proc: main is func
  local
    var string: command is "";
  begin
    OUT := STD_CONSOLE;
    IN := openEditLine(KEYBOARD, OUT);
    writeln("Use the command quit to exit the program.");
    repeat
      write("command> ");
      readln(command);
    until command = "quit";
  end func;

8.8 Files with line structure

The library "text.s7i" defines the type text, which is a subtype of file. The type text adds a line structure and other features such as scrolling and color to file. The lines and columns of a text start with 1 in the upper left corner and increase downward and rightward. The function setPos sets the current line and column of a text:

setPos(aText, 10, 20);

The functions setLine and setColumn set just the line and column respectively:

setLine(aText, 2);
setColumn(aText, 72);

The current line and column of a text file can be retrieved with line and column:

writeln("The current line is: " <& line(aText));
writeln("The current column is: " <& column(aText));

The current height and width of a text file can be retrieved with height and width:

writeln("The height is: " <& height(aText));
writeln("The width is: " <& width(aText));

To allow random access output to a text console (or text window) the library "console.s7i" defines the type console_file. The function

open(CONSOLE)

returns a console_file.

8.9 Sockets

The library "socket.s7i" defines types and functions to access sockets. The implementation type for sockets is socket. As interface type file is used:

var file: clientSocket is STD_NULL;

With openInetSocket an Internet client socket can be opened:

clientSocket := openInetSocket("www.google.com", 80);

The function openInetSocket creates and connects a socket. Opening an Internet socket at the local host is also done with a variant of openInetSocket:

clientSocket := openInetSocket(1080);

Since sockets use the file interface functions like writeln and getln can be used:

$ include "seed7_05.s7i";
  include "socket.s7i";

const proc: main is func
  local
    const string: serverName is "www.google.com";
    var file: aSocket is STD_NULL;
  begin
    aSocket := openInetSocket(serverName, 80);
    if aSocket <> STD_NULL then
      writeln(aSocket, "GET / HTTP/1.1");
      writeln(aSocket, "Host: " <& serverName);
      writeln(aSocket, "User-Agent: BlackHole");
      writeln(aSocket);
      writeln(getln(aSocket));
    end if;
  end func;

The example above sends a HTTP request to a server and gets the status code from the response. The example above consists of code from the library "gethttp.s7i".

Server sockets are supported with the type listener. A listener is defined with:

var listener: myListener is listener.value;

The library "listener.s7i" defines the function openInetListener, which opens a listener:

aListener := openInetListener(1080);

The function listen is used to listen for incoming socket connections of a listener, and to limit the incoming queue:

listen(aListener, 10);

The function accept returns the first connected socked of the listener:

serverSocket := accept(aListener);

Together the functions above can be use to process requests without sessions:

aListener := openInetListener(1080);
listen(aListener, 10);
while TRUE do
  sock := accept(aListener);
  # Read and process the request from sock.
  close(sock);
end while;

A similar loop is used in the Comanche web server (see main function). The function waitForRequest can be used to process requests with session:

aListener := openInetListener(2021);
listen(aListener, 10);
while TRUE do
  waitForRequest(aListener, existingConnection, newConnection);
  if existingConnection <> STD_NULL then
    # Read and process the request from existingConnection.
  end if;
  if newConnection <> STD_NULL then
    # Send welcome message to newConnection.
  end if;
end while;

Similar code is used in the program "ftpserv.sd7". The implementation of waitForRequest is based on pollData, which is defined in "poll.s7i".

8.10 Transport Layer Security

Transport Layer Security (TLS) is the successor of the Secure Sockets Layer (SSL). It provides secure communication over a computer network. The library "tls.s7i" defines types and functions for TLS sockets. The implementation type for sockets is tlsFile. As interface type file is used:

var file: aTlsSocket is STD_NULL;

With openTlsSocket a TLS socket can be opened:

aTlsSocket := openTlsSocket("www.google.com", 443);

The function openTlsSocket opens a TLS socket. Since TLS sockets use the file interface functions like writeln and getln can be used:

$ include "seed7_05.s7i";
  include "tls.s7i";

const proc: main is func
  local
    const string: serverName is "www.google.com";
    var file: aTlsSocket is STD_NULL;
  begin
    aTlsSocket := openTlsSocket(serverName, 443);
    if aTlsSocket <> STD_NULL then
      writeln(aTlsSocket, "GET / HTTP/1.1");
      writeln(aTlsSocket, "Host: " <& serverName);
      writeln(aTlsSocket, "User-Agent: BlackHole");
      writeln(aTlsSocket);
      writeln(getln(aTlsSocket));
    end if;
  end func;

The example above sends a HTTPS request to a server and gets the status code from the response. The example above consists of code from the library "gethttps.s7i".

The function openServerTls can be used to open a TLS socket at the server side. The library "x509cert.s7i" defines the self signed certificate stdCertificate. This certificate can be used to open a TLS server socket:

$ include "seed7_05.s7i";
  include "socket.s7i";
  include "listener.s7i";
  include "tls.s7i";

const proc: main is func
  local
    var listener: aListener is listener.value;
    var file: sock is STD_NULL;
    var file: tlsSock is STD_NULL;
    var string: line is "";
  begin
    aListener := openInetListener(11111);
    listen(aListener, 10);
    while TRUE do
      sock := accept(aListener);
      tlsSock := openServerTls(sock, stdCertificate);
      if tlsSock <> STD_NULL then
        writeln("Success");
        repeat
          line := getln(tlsSock);
          writeln(line <& " received");
        until line = "";
        close(tlsSock);
      else
        writeln(" *** Cannot open TLS connection.");
      end if;
    end while;
  end func;

8.11 User defined file types

In addition to the predefined file types it is often necessary to define a new type of file. Such a new file has several possibilities:

With the following declaration we define a new file type:

const type: my_file_type is sub null_file struct
    ...
    (* Local data *)
    ...
  end struct;

It is not necessary to derive the type my_file_type directly from null_file. The type my_file_type may also be an indirect descendant of null_file. So it is possible to create file type hierarchies. The interface implemented by the new file needs also to be specified:

type_implements_interface(my_file_type, file);

The type file is not the only interface type which can be used. There is also the type text which is derived from file. The type text describes a line oriented file which allows setPos (which moves the current position to the line and column specified) and other functions. It is also possible to define new interface types which derive from file or text.

As next an open function is needed to open a my_file_type file:

const func file: open_my_file (  (* Parameters *) ) is func
  result
    var file: newFile is STD_NULL;
  local
    var my_file_type: new_file is my_file_type.value;
  begin
    ...
    (* Initialization of the data elements of new_file *)
    newFile := toInterface(new_file);
    ...
  end func;

Note that the function 'toInterface' is used to generate a new file object. Now only the two basic I/O operations must be defined:

const proc: write (inout my_file_type: new_fil, in string: stri) is func
  begin
    ...
    (* Statements that do the output *)
    ...
  end func;

const proc: gets (inout my_file_type: new_fil, in integer: leng) is func
  result
    var string: stri is "";
  begin
    ...
    (* Statements that do the input *)
    ...
  end func;

8.12 Scanning a file

The I/O concept introduced in the previous chapters separates the input of data from its conversion. The read, readln, getwd and getln functions are designed to read whitespace separated data elements. If the data elements are not separated by whitespace characters this I/O concept is not possible. Instead the functions which read from the file need some knowledge about the type which they intend to read. Fortunately this is a well researched area. The lexical scanners used by compilers solve exactly this problem.

Lexical scanners read symbols from a file and use the concept of a current character. A symbol can be a name, a number, a string, an operator, a bracket or something else. The current character is the first character to be processed when scanning a symbol. After a scanner has read a symbol the current character contains the character just after the symbol. This character could be the first character of the next symbol or some whitespace character. If the set of symbols is chosen wisely all decisions about the type of the symbol and when to stop reading characters for a symbol can be done based on the current character.

Every file contains a 'bufferChar' variable which is used as current character by the scanner functions defined in the "scanfile.s7i" library. The "scanfile.s7i" library contains skip... and get... functions. The skip... procedures return void and are used to skip input while the get... functions return the string of characters they have read. The following basic scanner functions are defined in the "scanfile.s7i" library:

skipComment
Skips a possibly nested Seed7 comment from a file.
getComment
Reads a possibly nested Seed7 comment from a file.
skipClassicComment
Skips a classic C comment from a file.
skipLineComment
Skips a line comment from a file.
getLineComment
Reads a line comment from a file.
getDigits
Reads a sequence of digits from a file.
getInteger
Reads a decimal integer with optional sign from a file.
getNumber
Reads a numeric literal (integer, bigInteger or float literal) from a file.
getNonDigits
Reads a sequence of non digits from a file.
getQuotedText
Reads a text quoted with " or ' from a file.
getSimpleStringLiteral
Read a simple string literal from a file.
getCharLiteral
Reads a character literal from a file.
getStringLiteral
Reads a string literal from a file.
getName
Reads an alphanumeric name from a file.

Contrary to read and getwd basic scanner functions do not skip leading whitespace characters. To skip whitespace characters one of the following functions can be used:

skipSpace
Skips space characters from a file.
skipSpaceOrTab
Skips space and tab characters from a file.
skipWhiteSpace
Skips whitespace characters from a file.
getWhiteSpace
Reads whitespace characters from a file.
getWord
Reads a white space delimited word from a file.
skipLine
Skips a line from a file.
getLine
Reads a line from a file.

The advanced scanner functions do skip whitespace characters before reading a symbol:

getSymbolOrComment
Reads a symbol or a comment from a file.
getSymbol
Reads a symbol from a file.
getSymbolWithHtmlEntities
Reads a symbol, where html entities are allowed, from a file.
getHtmlTagSymbolOrComment
Reads a HTML tag, a symbol or a comment from a file.
skipXmlComment
Skips a XML comment from a file.
getXmlTagOrContent
Reads a XML/HTML tag or the XML/HTML content text from a file.
getXmlCharacterReference
Reads a predefined XML entity from a file.
getXmlCdataContent
Read the content text of a CDATA section from a file.
getXmlTagHeadOrContent
Reads a XML/HTML tag head or a XML/HTML content from a file.
getSymbolInXmlTag
Reads a symbol which can appear inside a XML/HTML tag from a file.
skipXmlTag
Skips beyond an XML Tag in a file.
getNextXmlAttribute
Reads name and value of an attribute inside a XML tag from file.
getHtmlAttributeValue
Reads a HTML tag attribute value from a file.
getNextHtmlAttribute
Reads name and value of an attribute inside a HTML tag from a file.
getSimpleSymbol
Reads a simple symbol from a file.

All scanner functions assume that the first character to be processed is in 'bufferChar' and after they are finished the next character which should be processed is also in 'bufferChar'. To use scanner functions for a new opened file it is necessary to assign the first character to the 'bufferChar' with:

myFile.bufferChar := getc(myFile);

In most cases whole files are either processed with normal I/O functions or with scanner functions. If normal I/O functions need to be combined with scanner functions care has to be taken:

Scanner functions are helpful if it is necessary to read numeric input without failing if no digits are present:

skipWhiteSpace(IN);
if eoln(IN) then
  writeln("empty input");
elsif IN.bufferChar in {'0' .. '9'} then
  number := integer parse getDigits(IN);
  skipLine(IN);
  writeln("number " <& number);
else
  stri := getLine(IN);
  writeln("command " <& literal(stri));
end if;

The function getSymbol is designed to read Seed7 symbols. When the end of the file is reached it returns "". With getSymbol name-value pairs can be read:

name := getSymbol(inFile);
while name <> "" do
  if name <> "#" and getSymbol(inFile) = "=" then
    aValue = getSymbol(inFile);
    if aValue <> "" then
      if aValue[1] = '"' then
        keyValueHash @:= [name] aValue[2 ..];
      elsif aValue[1] in {'0' .. '9'} then
        keyValueHash @:= [name] aValue;
      end if;
    end if;
  end if;
end while;

The following loop can be used to process the symbols of a Seed7 program:

inFile.bufferChar := getc(inFile);
currSymbol := getSymbol(inFile);
while currSymbol <> "" do
  ... process currSymbol ...
  currSymbol := getSymbol(inFile);
end while;

Whitespace and comments are automatically skipped with the function getSymbol. If comments should also be returned the function getSymbolOrComment can be used. Together with the function getWhiteSpace it is even possible to get the whitespace between the symbols:

const func string: processFile (in string: fileName) is func
  result
    var string: processed is "";
  local
    var file: inFile is STD_NULL;
    var string: currSymbol is "";
  begin
    inFile := open(fileName, "r");
    if inFile <> STD_NULL then
      inFile.bufferChar := getc(inFile);
      processed := getWhiteSpace(inFile);
      currSymbol := getSymbolOrComment(inFile);
      while currSymbol <> "" do
        processed &:= currSymbol;
        processed &:= getWhiteSpace(inFile);
        currSymbol := getSymbolOrComment(inFile);
      end while;
    end if;
  end func;

In the example above the function 'processFile' gathers all symbols, whitespace and comments in the string it returns. The string returned by 'processFile' is equivalent to the one returned by the function 'getf'. That way it is easy to test the scanner functionality.

The logic with getWhiteSpace and getSymbolOrComment can be used to add HTML tags to comments and literals. The following function colors comments with green, string and char literals with maroon and numeric literals with purple:

const proc: sourceToHtml (inout file: inFile, inout file: outFile) is func
  local
    var string: currSymbol is "";
  begin
    inFile.bufferChar := getc(inFile);
    write(outFile, "<pre>\n");
    write(outFile, getWhiteSpace(inFile));
    currSymbol := getSymbolOrComment(inFile);
    while currSymbol <> "" do
      currSymbol := replace(currSymbol, "&", "&amp;");
      currSymbol := replace(currSymbol, "<", "&lt;");
      if currSymbol[1] in {'"', '''} then
        write(outFile, "<font color=\"maroon\">");
        write(outFile, currSymbol);
        write(outFile, "</font>");
      elsif currSymbol[1] = '#' or startsWith(currSymbol, "(*") then
        write(outFile, "<font color=\"green\">");
        write(outFile, currSymbol);
        write(outFile, "</font>");
      elsif currSymbol[1] in digit_char then
        write(outFile, "<font color=\"purple\">");
        write(outFile, currSymbol);
        write(outFile, "</font>");
      else
        write(outFile, currSymbol);
      end if;
      write(outFile, getWhiteSpace(inFile));
      currSymbol := getSymbolOrComment(inFile);
    end while;
    write(outFile, "</pre>\n");
  end func;

The functions skipSpace and skipWhiteSpace are defined in the "scanfile.s7i" library as follows:

const proc: skipSpace (inout file: inFile) is func
  local
    var char: ch is ' ';
  begin
    ch := inFile.bufferChar;
    while ch = ' ' do
      ch := getc(inFile);
    end while;
    inFile.bufferChar := ch;
  end func;

const proc: skipWhiteSpace (inout file: inFile) is func
  begin
    while inFile.bufferChar in white_space_char do
      inFile.bufferChar := getc(inFile);
    end while;
  end func;

The functions skipComment and skipLineComment, which can be used to skip Seed7 comments, are defined as follows:

const proc: skipComment (inout file: inFile) is func
  local
    var char: character is ' ';
  begin
    character := getc(inFile);
    repeat
      repeat
        while character not in special_comment_char do
          character := getc(inFile);
        end while;
        if character = '(' then
          character := getc(inFile);
          if character = '*' then
            skipComment(inFile);
            character := getc(inFile);
          end if;
        end if;
      until character = '*' or character = EOF;
      if character <> EOF then
        character := getc(inFile);
      end if;
    until character = ')' or character = EOF;
    if character = EOF then
      inFile.bufferChar := EOF;
    else
      inFile.bufferChar := getc(inFile);
    end if;
  end func; # skipComment

const proc: skipLineComment (inout file: inFile) is func
  local
    var char: character is ' ';
  begin
    repeat
      character := getc(inFile);
    until character = '\n' or character = EOF;
    inFile.bufferChar := character;
  end func; # skipLineComment


 previous   up   next