Manual |
|
File I/O |
|
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:
- string <& otherType: In this case the right operand is converted to a string before the concatenation takes place.
- otherType <& string: In this case the left operand is converted to a string before the concatenation takes place.
- string <& string: In this case the two operands are concatenated.
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:
- A concept which provides conversions from arbitrary types to strings and back.
- Basic input and output operations to process a file character wise, word wise or line wise.
- Input and output statements which combine input with conversion respectively conversion with output.
- Simple read and write statements for standard input and output for arbitrary types.
- Standard input and output files and the possibility to route the standard I/O to any file.
- Access to operating system files and devices.
- An interface which allows the user to define his own file types.
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:
- Trimming of a string with trimValue(string, aString): This leaves the string unchanged.
- Trimming of a char with trimValue(char, aString): For "" it returns "". In all other cases it returns a trimmed string of length 1.
- Trimming of all other types with trimValue(aType, aString): This removes leading and trailing spaces.
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:
- Binary mode provides an implementation independent behavior on all operating systems. In binary mode no conversion to and from the line end character ('\n') is done. This has the advantage that an external_file written in binary mode is identical on all operating systems. Reading files with different line endings ("\n" and "\r\n") is supported by every external_file: The functions getwd, getln, read and readln, of external_file skip a carriage return ('\r') if it is just before a linefeed ('\n'). The rest of the external_file functions like getc and gets deliver line endings unchanged.
- The behavior of an external_file in text mode is implementation dependent. Under Unix/Linux/Bsd text and binary modes are identical. Other operating systems prefer to do some line end conversions in text mode: When reading a file all occurrences of "\r\n" are converted to '\n'. When writing to a file all occurrences of '\n' are converted to "\r\n". Note that text mode cannot be used to automatically create files with "\r\n" line endings under Unix/Linux/Bsd.
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:
- inputReady, which returns TRUE if a character can be read without blocking and FALSE otherwise.
- getc(KEYBOARD, NO_WAIT), which delivers the next character from the keyboard or KEY_NONE if no key has been pressed.
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:
- Console mode, where the program runs in a console/terminal window (the default).
- Graphics mode, where the program has its own graphic window.
These two modes are supported with two basic keyboard files:
- CONSOLE_KEYBOARD, which uses a terminfo or console driver.
- GRAPH_KEYBOARD, which uses a X11 or GDI driver.
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:
- buttonPressed, which determines if a given button is currently pressed.
- clickedXPos, which returns the X position of the mouse cursor when a button was pressed.
- clickedYPos, which returns the Y position of the mouse cursor when a button was pressed.
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:
- pointerXPos, which returns the actual X position of the mouse pointer.
- pointerYPos, which returns the actual Y position of the mouse pointer.
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:
- It could store its contents in a string (not only to be faster but also to provide additional file operations)
- The information can be processed (e.g. to upper case) and sent to another file.
- It could work just like an Unix utility (Think of more, sort, tee, uniq ...)
- It could provide a file-like interface for something with an other interface. (e.g. The contents of a directory, or random access I/O to the screen)
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:
- If the last function which read from a file was one of read, readln, getwd or getln the 'bufferChar' already contains the character which should be processed next and therefore subsequent scanner functions can be used.
- Other I/O functions like getc and gets do not assign something to 'bufferChar'. In this case something should be assigned to 'bufferChar'.
- Switching back from scanner functions to normal I/O functions is best done when the content of 'bufferChar' is known. For example at the end of the line.
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, "&", "&"); currSymbol := replace(currSymbol, "<", "<"); 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
|
|