Manual |
|
Tutorial |
|
2. TUTORIAL
2.1 Hello world
Below is the hello world program of Seed7:
$ include "seed7_05.s7i"; const proc: main is func begin writeln("hello world"); end func;
Save this program to the file hello.sd7 and start it in a console with:
s7 hello
The Seed7 interpreter writes something like
SEED7 INTERPRETER Version 5.1.790 Copyright (c) 1990-2023 Thomas Mertes hello world
You get information about the Seed7 interpreter and the output of the hello.sd7 program:
hello world
The option -q can be used to suppress the information line of the Seed7 interpreter:
s7 -q hello
The first line of the program
$ include "seed7_05.s7i";
includes all definitions of the standard library. In contrast to other libraries the seed7_05.s7i library contains not only function declarations but also declarations of statements and operators. Additionally the seed7_05.s7i library defines the main function as entry point for a Seed7 program.
In the example above main is declared as constant and proc is the type of main. Declaring main with the type proc makes a procedure out of it. The object main gets a
func ... end func
construct as value. The 'func' construct is similar to begin ... end in PASCAL and { ... } in C. Inside the 'func' is a writeln statement with the "hello world" string. The writeln statement is used to write a string followed by a newline character.
2.2 Greeting
The program below starts a little dialog:
$ include "seed7_05.s7i"; const proc: main is func local var string: name is ""; begin write("What's your name? "); readln(name); writeln("Hi " <& name <& "!"); end func;
Save this program to the file greeting.sd7 and start it in a console with:
s7 greeting
The program asks you for your name with:
What's your name?
After you entered your name and pressed enter it will greet you. This program uses the variable 'name' to store the name you entered. Variables must be defined. The place to to define and initialize all variables of a function is after the keyword local.
var string: name is "";
This defines the string variable 'name'. This definition assigns also the initial value "" to 'name'. The value "" is the empty string (it contains no characters). In Seed7 variables must be defined and always get an initial value.
The write statement is similar to writeln, but it does not write a newline character. The readln statement reads a line from the standard input file and assigns this line to the given variable. This function allows the usage of backspace to correct the input. By pressing enter the line is sent to the program. The final writeln statement contains the operator <& to concatenate strings. If necessary the <& operator converts values to string.
The greeting program above has a problem. If someone refuses to type his name and just presses enter the program writes:
Hi !
To avoid this we improve the program to check for special cases:
$ include "seed7_05.s7i"; const proc: main is func local var string: name is ""; begin write("What's your name? "); readln(name); if name = "" then writeln("Greetings to the person who pressed enter!"); elsif name = "name" then writeln("Interesting, your name is name."); else writeln("Hi " <& name <& "!"); end if; end func;
There can be zero or more elsif parts, and the else part is optional. As you can see the equality of strings is checked with =. The conditions of an if-statement decide what to do. By chance both conditions in the example above use the variable name. This special case opens the oportunity to use a case-statement instead:
$ include "seed7_05.s7i"; const proc: main is func local var string: name is ""; begin write("What's your name? "); readln(name); case name of when {""}: writeln("Greetings to the person who pressed enter!"); when {"name"}: writeln("Interesting, your name is name."); when {"Linus", "Torvalds"}: writeln("Are you the inventor of Linux?"); otherwise: writeln("Hi " <& name <& "!"); end case; end func;
As it can be seen, the keyword when is followed by a comma-separated list of values surrounded by braces. This is the set of values covered by this when-part. Depending on the value of 'name' one when-part is executed. If no when-part fits the otherwise-part is executed.
This is not the only way to improve the greeting program. Alternatively we can use a loop to insist on entering a name:
$ include "seed7_05.s7i"; const proc: main is func local var string: name is ""; begin repeat write("What's your name? "); readln(name); until name <> ""; writeln("Hi " <& name <& "!"); end func;
The repeat ... until loop repeats the statements between the two keywords until the condition name <> "" is TRUE. Note that the statements in the repeat loop are executed at least once. A solution with a while loop is:
$ include "seed7_05.s7i"; const proc: main is func local var string: name is ""; begin write("What's your name? "); readln(name); while name = "" do write("Just pressing enter is not okay. What's your name? "); readln(name); end while; writeln("Hi " <& name <& "!"); end func;
The while loop repeats the statements between the keywords do and end as long as the condition name = "" is TRUE. Note that the statements in a while loop might not be executed at all. In the example above this happens if a non-empty name is entered after the question: What's your name?
2.3 Assignment
The following program writes the numbers from 1 to 10:
$ include "seed7_05.s7i"; const proc: main is func local var integer: count is 1; begin while count <= 10 do writeln(count); count := count + 1; end while; end func;
This program declares a local variable of the type integer:
var integer: count is 1;
The variable count above is initialized with 1. The value of a variable can be changed with an assignment (:=):
count := count + 1;
The expression at the right of the := symbol is evaluated and assigned to the variable left of the := symbol. In the case above the variable count is incremented by one. For this special case there is a shortcut that could be used instead of the assignment:
incr(count);
The output produced by this program is
1 2 3 4 5 6 7 8 9 10
What changes are necessary to count down from 10 to 0 instead?
2.4 Constants
To write a Fahrenheit to Celsius conversion table we use the following program:
(* Print a Fahrenheit-Celsius table for Fahrenheit values between 0 and 300 *) $ include "seed7_05.s7i"; const proc: main is func local const integer: upper is 300; const integer: increment is 20; var integer: fahr is 0; var integer: celsius is 0; begin while fahr <= upper do celsius := 5 * (fahr - 32) div 9; writeln(fahr <& " " <& celsius); fahr +:= increment; end while; end func;
Everything between (* and *) is a comment, which is ignored. This program contains local constants and variables of the type integer. Constant declarations are introduced with const:
const integer: upper is 300; const integer: increment is 20;
Like variables constants must be initialized with a value that is specified after the keyword is. Constants like upper cannot be changed. All constants just keep the initialization value. An attempt to change a constant results in a parsing error:
*** tst352.sd7(14):53: Variable expected in {lower := 10 } found constant integer: lower lower := 10;
Note that the error is reported before the program execution starts.
The program contains a while-statement and the expression to compute the 'celsius' value. The variable 'fahr' is incremented with the +:= operator. The statement:
fahr +:= increment;
is a shortcut for the statement:
fahr := fahr + increment;
The expression to compute the 'celsius' value uses an integer division (div). The output produced by this program is
0 -17 20 -6 40 4 60 15 80 26 100 37 120 48 140 60 160 71 180 82 200 93 220 104 240 115 260 126 280 137 300 148
2.5 For loop and float expressions
An improved version of the program to write the Fahrenheit to Celsius conversion table is:
# Print a Fahrenheit-Celsius table with floating point numbers. $ include "seed7_05.s7i"; # This must be included first. include "float.s7i"; # Subsequent includes do not need a $. const proc: main is func local const integer: lower is 0; const integer: upper is 300; const integer: increment is 20; var integer: fahr is 0; var float: celsius is 0.0; begin for fahr range lower to upper step increment do celsius := flt(5 * (fahr - 32)) / 9.0; writeln(fahr lpad 3 <& " " <& celsius digits 2 lpad 6); end for; end func;
Everything between # the end of the line is a line comment, which is ignored. To use the type float it is necessary to include float.s7i. The float variable 'celsius' must be initialized with 0.0 (instead of 0). The for-loop executes the loop body with different values of fahr (0, 20, 40 .. 280, 300). Omitting the step part corresponds to a step of 1:
for fahr range lower to upper do celsius := flt(5 * (fahr - 32)) / 9.0; writeln(fahr lpad 3 <& " " <& celsius digits 2 lpad 6); end for;
The keyword downto can be used to run the for-loop backward:
for fahr range upper downto lower do celsius := flt(5 * (fahr - 32)) / 9.0; writeln(fahr lpad 3 <& " " <& celsius digits 2 lpad 6); end for;
Since Seed7 is strong typed integer and float values cannot be mixed in expressions. Therefore the integer expression '5 * (fahr - 32)' is converted to float with the function flt. For the same reason a '/' division and the value '9.0' must be used. The <& operator is used to concatenate elements before writing. If the right operand of the <& operator has not the type string it is converted to a string using the 'str' function. The lpad operator converts the value of 'fahr' to a string and pads spaces to the left until the string has length 3. The digits operator converts the value of 'celsius' to a string with 2 decimal digits. The resulting string is padded left up to a length of 6.
2.6 Arrays
Arrays allow a variable to contain several values. E.g.: An array can be used to store a mapping from a day number to a day name:
$ include "seed7_05.s7i"; const proc: main is func local var array string: weekdayName is 7 times ""; var integer: number is 0; begin weekdayName[1] := "monday"; weekdayName[2] := "tuesday"; weekdayName[3] := "wednesday"; weekdayName[4] := "thursday"; weekdayName[5] := "friday"; weekdayName[6] := "saturday"; weekdayName[7] := "sunday"; for number range 1 to 7 do writeln(weekdayName[number]); end for; end func;
Since weekdayName is not changed after its values have been assigned, it can be declared as constant that is initialized with an array literal:
$ include "seed7_05.s7i"; const proc: main is func local const array string: weekdayName is [] ("monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"); var integer: number is 0; begin for number range 1 to 7 do writeln(weekdayName[number]); end for; end func;
The example above uses the array literal
[] ("monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday")
This array literal has the type array string and it is indexed beginning from 1. A corresponding array literal indexed beginning from 0 would be:
[0] ("monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday")
The for-loop above uses the literal 7 as upper bound. The function length can be used instead:
for number range 1 to length(weekdayName) do
This works as long as the array weekdayName is indexed beginning from 1. If this is not the case the functions minIdx and maxIdx can be used:
for number range minIdx(weekdayName) to maxIdx(weekdayName) do
For this case there is a convenient for-key-loop:
for key number range weekdayName do writeln(weekdayName[number]); end for;
This for-key-loop loops over the indices of the array weekdayName (from 1 to 7). In the loop body the index is just used to access an element from the array. For this case there is a convenient for-each-loop:
$ include "seed7_05.s7i"; const proc: main is func local const array string: weekdayName is [] ("monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"); var string: name is ""; begin for name range weekdayName do writeln(name); end for; end func;
A for-each-loop iterates over the elements of an array. Sometimes the elements and a corresponding index are needed. This is supported by the for-each-key-loop:
$ include "seed7_05.s7i"; const proc: main is func local const array string: weekdayName is [] ("monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"); var string: name is ""; var integer: index is 0; begin for name key index range weekdayName do writeln("day " <& index <& ": " <& name); end for; end func;
Another example of a for-each-loop is:
$ include "seed7_05.s7i"; const proc: main is func local var integer: number is 0; begin for number range [] (0, 1, 2, 3, 5, 8, 13, 20, 40, 100) do write(number <& " "); end for; writeln; end func;
The example above uses the array literal
[] (0, 1, 2, 3, 5, 8, 13, 20, 40, 100)
This array literal has the type array integer. The index type of an array is not restricted to integers. A type like char, that has a 1:1 mapping to integer, can also be used as index:
$ include "seed7_05.s7i"; const proc: main is func local const array [char] string: digitName is ['0'] ("zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"); var integer: number is 0; var char: ch is ' '; begin number := rand(1, 9999999); write(number <& ": "); for ch range str(number) do write(digitName[ch] <& " "); end for; writeln; end func;
The example above uses the array literal
['0'] ("zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine")
This array literal has the type array [char] string. The minimum index of this array is the character '0'.
2.7 Hashes
A hash is similar to an array with the difference that the index can be any type (not just one that can be converted to integer). The type hash [string] integer defines a hash with a string as index and an integer as value. This type can be used in a type declaration:
const type: nameToDigitType is hash [string] integer;
A hash literal can be used to initialize hash constants or variables:
const nameToDigitType: nameToDigit is [] ( ["zero" : 0], ["one" : 1], ["two" : 2], ["three" : 3], ["four" : 4], ["five" : 5], ["six" : 6], ["seven" : 7], ["eight" : 8], ["nine" : 9]);
Like with arrays an element in the hash can be accessed with
nameToDigit[name]
The following example asks for digit names and writes the corresponding digit:
$ include "seed7_05.s7i"; const type: nameToDigitType is hash [string] integer; const proc: main is func local const nameToDigitType: nameToDigit is [] ( ["zero" : 0], ["one" : 1], ["two" : 2], ["three" : 3], ["four" : 4], ["five" : 5], ["six" : 6], ["seven" : 7], ["eight" : 8], ["nine" : 9]); var string: name is ""; begin write("Enter the name of a digit: "); readln(name); if name in nameToDigit then writeln("The value of " <& name <& " is " <& nameToDigit[name]); else writeln("You entered " <& name <& ", which is not the name of a digit."); end if; end func;
In the example above
name in nameToDigit
checks if the key name is in the hash table nameToDigit. This assures that getting the corresponding value with
nameToDigit[name]
succeeds. For-each loops can be used with hash tables as well. The example below uses keyDescription which is defined as hash [char] string in "keydescr.s7i". It contains descriptive texts for keyboard keys. A for-loop can loop over the values of a hash:
$ include "seed7_05.s7i"; include "keydescr.s7i"; const proc: main is func local var string: description is ""; begin for description range keyDescription do write(description <& " "); end for; writeln; end func;
A for-loop can also loop over the keys (indices) and values of a hash:
$ include "seed7_05.s7i"; include "keydescr.s7i"; const proc: main is func local var char: aChar is ' '; var string: description is ""; begin for description key aChar range keyDescription do writeln("const char: " <& description <& " is " <& literal(aChar)); end for; end func;
2.8 For loop and containers
For-loops can also iterate over the elements of a set:
$ include "seed7_05.s7i"; const proc: main is func local var string: innerPlanet is ""; begin for innerPlanet range {"Mercury", "Venus", "Earth", "Mars"} do write(innerPlanet <& " "); end for; writeln; end func;
In the example above {"Mercury", "Venus", "Earth", "Mars"} is a set literal. The type of this literal is set of string. Other set literals are:
{1, 2} {'a', 'e', 'i', 'o', 'u'}
For-loops can iterate over the characters of a string:
$ include "seed7_05.s7i"; const proc: main is func local const set of char: vowels is {'a', 'e', 'i', 'o', 'u'}; var char: letter is ' '; begin for letter range "the quick brown fox jumps over the lazy dog" do if letter not in vowels then write(letter); end if; end for; writeln; end func;
A for-loop can loop over the keys (indices) and values of a array:
$ include "seed7_05.s7i"; const proc: main is func local var integer: number is 0; var string: name is ""; begin for name key number range [0] ("zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine") do writeln(number <& ": " <& name); end for; end func;
All loops that iterate over a container can be combined with an until condition:
$ include "seed7_05.s7i"; const proc: main is func local var string: testText is ""; var char: ch is ' '; var boolean: controlCharFound is FALSE; begin write("Enter text: "); readln(testText); for ch range testText until controlCharFound do controlCharFound := ord(ch) < 32; end for; if controlCharFound then writeln("The text contains control chars."); end if; end func;
2.9 Functions
The program below uses the function flipCoin to flip a coin until the result is heads:
$ include "seed7_05.s7i"; const func boolean: flipCoin is return rand(FALSE, TRUE); const proc: main is func local var boolean: heads is FALSE; begin repeat write("Press enter to flip the coin. "); readln; heads := flipCoin; writeln(heads ? "heads" : "tails"); until heads; end func;
In the example above, the function flipCoin is declared as constant. As const the code of flipCoin will not change during run-time. The type func boolean determines that the function returns a boolean value. The task of the function flipCoin is to return a random boolean value. This is done with:
return rand(FALSE, TRUE);
The actual random value is computed with the expression
rand(FALSE, TRUE);
The statement
heads := flipCoin;
assigns the result of flipCoin to heads. Afterwards heads is written with the ternary operator (?:). If heads is TRUE, it writes "heads" otherwise "tails".
The program below reads a multi-line paragraph:
$ include "seed7_05.s7i"; const func string: readMultiLineParagraph is func result var string: multiLineParagraph is ""; local var string: line is ""; begin repeat write("line: "); line := getln(IN); multiLineParagraph &:= line & "\n"; until line = ""; end func; const proc: main is func begin writeln("Enter a multi line paragraph. An empty line ends the paragraph."); writeln("\nThe paragraph is:\n" <& readMultiLineParagraph); end func;
The declaration of readMultiLineParagraph above contains the construct:
func ... end func
The function readMultiLineParagraph gets the func ... end func construct as value. This construct can be used to initialize functions and procedures. Note that proc is just a shortcut for func void and that void describes the empty type. Inside of the func ... end func construct are the keywords result, local and begin:
- The keyword result introduces the declaration of the result variable. When the function is finished, it will return the current value of the result variable.
- The keyword local introduces constant and variable declarations that are local to the function.
- The keyword begin introduces a sequence of statements that is executed when the function is called.
As can be seen from the function main above, the result and local parts are optional. If the function type is proc, the result part must be omitted. For all other functions the result part is mandatory.
2.10 Parameters
Most parameters are not changed inside a function. Seed7 uses 'in' parameters to describe this situation:
const func integer: negate (in integer: num1) is return -num1; const func integer: fib (in integer: num1) is func result var integer: fib is 1; begin if num1 <> 1 and num1 <> 2 then fib := fib(pred(num1)) + fib(num1 - 2); end if; end func;
The functions above use 'in' parameters named 'num1'. An assignment to 'num1' is not allowed. A formal 'in' parameter like 'num1' behaves like a constant. Trying to change a formal 'in' parameter:
const proc: wrong (in integer: num2) is func begin num2 := 0; end func;
results in a parsing error:
*** tst77.sd7(5):53: Variable expected in {num2 := 0 } found parameter (in integer: num2) num2 := 0;
When a function wants to change the value of the actual parameter it can use an 'inout' parameter:
const proc: reset (inout integer: num2) is func begin num2 := 0; end func;
If you call this function with
reset(number);
the variable 'number' has the value 0 afterwards. Calling 'reset' with a constant instead of a variable:
reset(8);
results in a parsing error:
*** tst77.sd7(12):53: Variable expected in {8 reset } found constant integer: 8 reset(8);
Sometimes an 'in' parameter is needed, but you need to change the formal parameter in the function without affecting the actual parameter. In this case we use the 'in var' parameter:
const func string: oct_str (in var integer: number) is func result var string: stri is ""; begin if number >= 0 then repeat stri := str(number mod 8) & stri; number := number mdiv 8; until number = 0; end if; end func;
As you can see this works like a combination of an 'in' parameter with a local 'var'.
Conventionally there are two kinds of parameters: 'call by value' and 'call by reference'. When taking the access right (constant or variable) into account we get the following table:
parameter | call by | access right |
---|---|---|
val | value | const |
ref | reference | const |
in | val / ref | const |
in var | value | var |
inout | reference | var |
Additionally to the parameters we already know this table describes also 'val' and 'ref' parameters which use 'call by value' and 'call by reference' and have a constant formal parameter. The 'in' parameter is called by 'val / ref' in this table which is easily explained:
The parameter
in integer: number
is a 'val' parameter which could also be declared as
val integer: number
while the parameter
in string: stri
is a 'ref' parameter which could also be declared as
ref string: stri
The meaning of the 'in' parameter is predefined for most types. Usually types with small amounts of data use 'val' as 'in' parameter while types with bigger data amounts use 'ref'. Most of the time it is not necessary to care if an 'in' parameter is really a 'val' or 'ref' parameter.
In rare cases a 'ref' parameter would have undesired side effects with global variables or other 'ref' parameters. In these cases an explicit 'val' parameter instead of an 'in' parameter makes sense.
In all normal cases an 'in' parameter should be preferred over an explicit 'val' and 'ref' parameter.
2.11 Overloading
Functions are not only identified by identifiers but also via the types of their parameters. So several versions of a function can be defined:
$ include "seed7_05.s7i"; include "float.s7i"; include "bigint.s7i"; include "bigrat.s7i"; const func float: tenPercent (in float: amount) is return amount / 10.0; const func float: tenPercent (in integer: amount) is return float(amount) / 10.0; const func bigRational: tenPercent (in bigInteger: amount) is return amount / 10_; const proc: main is func begin writeln(tenPercent(123)); writeln(tenPercent(123.0)); writeln(tenPercent(123_)); end func;
The example above defines the function tenPercent for the types float, integer and bigInteger. Two of these functions return a float and one returns a bigRational. This reuse of the same function name is called overloading. The literals 123, 123.0 and 123_ have the types float, integer and bigInteger respectively. This allows an easy identification of the tenPercent function used. Note that writeln is also overloaded. Otherwise writeln would not be able to accept float and bigRational arguments.
Overloading does not consider the result of a function. The following example attempts to overload addOne with the same parameter type and a different result type:
const func integer: addOne (in integer: num) is return num + 1; const func float: addOne (in integer: num) is return float(num) + 1.0;
Return type overloading is not supported. Therefore the attempt above results in the compile-time error:
*** tst344.sd7(5):34: Redeclaration of "addOne (val integer: num)" const func float: addOne (in integer: num) is return float(num) + 1.0; ------------------------------------------------------------------------^ *** tst344.sd7(4):35: Previous declaration of "addOne (val integer: num)" const func integer: addOne (in integer: num) is return num + 1;
The absence of return type overloading improves readability, since:
The type of every expression (and sub expression) is independent of the context.
You don't need to look where an expression is used. The expression alone gives you enough information to determine the type of the expression.
Operators like + can be overloaded with:
$ include "seed7_05.s7i"; include "float.s7i"; const func float: (in integer: summand1) + (in float: summand2) is return float(summand1) + summand2; const func float: (in float: summand1) + (in integer: summand2) is return summand1 + float(summand2); const proc: main is func begin writeln(123 + 123.456); writeln(123.456 + 123); end func;
The definitions of the + operator above allow mixing of integer and float arguments. The overloaded + operators above are taken from the mixarith.s7i library.
2.12 Templates
Templates allow the declaration of functions where the actual types are specified later. The function declarations are done inside a procedure that has a type as parameter. The library integer.s7i defines the template DECLARE_MIN_MAX as:
const proc: DECLARE_MIN_MAX (in type: aType) is func begin const func aType: min (in aType: value1, in aType: value2) is return value1 < value2 ? value1 : value2; const func aType: max (in aType: value1, in aType: value2) is return value1 > value2 ? value1 : value2; end func;
The template procedure DECLARE_MIN_MAX uses the type parameter aType to declare the functions min and max. The template is instantiated for every actual type which needs min and max. E.g.:
DECLARE_MIN_MAX(integer); # Instantiated in the integer.s7i library DECLARE_MIN_MAX(bigInteger); # Instantiated in the bigint.s7i library DECLARE_MIN_MAX(float); # Instantiated in the float.s7i library
This allows for expressions like min(2, 5) or min(PI, E).
2.13 Declare a statement
This example program writes its arguments
$ include "seed7_05.s7i"; # Standard Seed7 library const proc: main is func local var string: stri is ""; begin for stri range argv(PROGRAM) do write(stri <& " "); end for; writeln; end func;
The for-statement iterates over argv(PROGRAM). The function argv(PROGRAM) returns an array string (=array of string elements). The for-statement is overloaded for various collection types. In the standard Seed7 library seed7_05.s7i the for-statement for arrays is declared as follows:
const proc: for (inout baseType: variable) range (in arrayType: arr_obj) do (in proc: statements) end for is func local var integer: number is 0; begin for number range 1 to length(arr_obj) do variable := arr_obj[number]; statements; end for; end func;
The syntax of this for-statement is declared as:
$ syntax expr: .for.().range.().to.().do.().end.for is -> 25;
Additionally everybody can overload the for-statement also. Because of these powerful features Seed7 does not need iterators.
2.14 Template declaring a statement
Templates are just normal functions with types as parameters. The following template function declares for-statements:
const proc: FOR_DECLS (in type: aType) is func begin const proc: for (inout aType: variable) range (in aType: low) to (in aType: high) do (in proc: statements) end for is func begin variable := low; if variable <= high then statements; while variable < high do incr(variable); statements; end while; end if; end func; end func; FOR_DECLS(char); FOR_DECLS(boolean);
The body of the 'FOR_DECLS' function contains a declaration of the for-statement for the type aType. Calling 'FOR_DECLS' with char and boolean as parameter creates corresponding declarations of for-statements. The example above is a simplified part of the library forloop.s7i.
|
|