Because Seed7 has several features which are not found in other programming languages:
The design principles are:
An extensible programming language supports mechanisms to extend the programming language, compiler/interpreter and runtime environment. The programmer is allowed to define new language constructs such as statements, declaration constructs and operators syntactically and semantically. Most programming languages allow user defined variables, functions and types, but they also use constructs which are hard-coded in the compiler/interpreter. An extensible programming language tries to avoid such hard-coded constructs in normal programs.
Extensible programming was an area of active research in the 1960s, but in the 1970s the extensibility movement was displaced by the abstraction movement. Today's software history gives almost no hint that the extensible languages movement had ever occurred. In the historical movement an extensible programming language consisted of a base language providing elementary computing facilities, and a meta-language capable of modifying the base language. A program then consisted of meta-language modifications and code in the modified base language. A popular approach to do language extension was the use of macro definitions. The constructs of the base language were hard-coded.
The design and development of Seed7 is based on independent research, which was done without knowing that the historic extensible programming language movement existed. Although Seed7 has different roots it reaches many of the original extensible programming language goals. Contrary to the historic movement Seed7 does not have a meta-language. In Seed7 a language extension is formulated in Seed7 itself. Seed7 differentiates between syntactic and semantic extensions. Syntactic extensions are described in Chapter 9 (Structured syntax definition) of the manual. The semantic extensions of Seed7 are done by declaring statements and operators as functions. For the body of loops and similar needs statically typed call-by-name parameters are used.
Yes. Seed7 spares no effort to support source code portability. No changes are necessary, if programs are moved between different processors, between 32- and 64-bit systems or between little- and big-endian machines. Seed7 source code can also be moved between different operating systems. Several driver libraries assure that the access to operating system resources such as files, directories, network, clock, keyboard, console and graphics is done in a portable way. The libraries of Seed7 cover many areas. The goal is: There should be no need to call foreign C functions, or to execute shell (respectively cmd.exe) commands.
Seed7 is "Free as in Freedom" and not only "Free as in Free Beer". The s7 interpreter and the example programs (extension .sd7) are under the GPL (General Public License, seen in the file COPYING).
The Seed7 runtime library is under the LGPL (Lesser General Public License, seen in the file LGPL). The Seed7 include files (extension .s7i) are a part of the Seed7 runtime library.
Seed7 allows the interpretation and compilation of programs with any license. There is no restriction on the license of your Seed7 programs.
For the development of the Seed7 compiler it will be necessary to move some source code from the s7 interpreter (under GPL) to the Seed7 runtime library (under LGPL). This will only be done to for the Seed7 runtime library and only as far as necessary to make no restriction on the license of compiled Seed7 programs.
If you send me patches (I would be very pleased), it is assumed that you accept license changes from GPL to LGPL for parts of code which need to be in the runtime library to support compilation of Seed7 programs.
No, not really. The keywords and statements remind people of Pascal, but behind the surface there is much difference. Don't judge a book by its cover. Seed7 is neither limited to Pascal's features, nor is it implemented like Pascal. Notable differences are:
Feature | Standard Pascal | Seed7 |
---|---|---|
syntax | hard-coded in the compiler | defined in a library |
statements | hard-coded in the compiler | defined in a library |
operators | hard-coded in the compiler | defined in a library |
array | hard-coded in the compiler | defined as abstract data type array |
record / struct | hard-coded in the compiler | defined as abstract data type |
hash table | not in the standard library | defined as abstract data type hash |
compiler target | machine code or P-code | C, compiled to machine code afterwards |
template | none | function with type parameters |
abstract data type | none | function with type result |
object orientation | none | interfaces and multiple dispatch |
Except for LL(1) parsing, no technology used by classical Pascal compilers could be used to implement Seed7.
Several features of Seed7 are missing in Java:
Features missing in Java | Comment |
---|---|
Stand alone functions | Singletons must be used instead |
Call-by-reference parameters | All parameters are call-by-value |
Call-by-name parameters | All parameters are call-by-value |
Operator overloading | In Java it is necessary to write a.add(b.multiply(c)) instead of a + b * c. |
User defined operators | - |
User defined statements | - |
User defined syntax | - |
One operator to check for equality | For POD types Java uses == and for strings name.equals(""). |
Elegant way to express data structures | Property files and XML must be used instead |
User defined functions to initialize data | - |
Multiple dispatch | - |
Checking for integer overflow | - |
Escape sequences only as part of literals | Unicode escapes can be everywhere. That can cause unexpected effects |
Seed7 can be used in various application areas:
The Seed7 package contains more than 100000 lines of C and more than 400000 lines of Seed7. For version 2023-09-13 the number of lines is:
177503 | C source files (*.c) | |
13190 | C header files (*.h) | |
244554 | Seed7 source files (*.sd7) | |
177770 | Seed7 library/include files (*.s7i) |
C code (*.c and *.h files) can be divided into the following areas:
0.3% | Interpreter main | |
11.6% | Parser | |
2.8% | Interpreter core | |
24.7% | Primitive action functions | |
7.4% | General helper functions | |
48.5% | Runtime library | |
4.7% | Compiler data library |
Details about these files can be found in the file
Seed7 runs on the following operating systems:
For other operating systems it might be necessary to write driver modules for
screen (=text console), graphics, time or other aspects of Seed7. The package
contains various older driver modules which are not up to date, but can be used
as base to write such driver modules. For more detailed information look at the
files
A Seed7 installer for Windows can be downloaded from:
This directory contains the latest installer and older ones. Installers have names with the following pattern:
seed7_05_yyyymmdd_win.exe
Just download the installer with the latest date (yyyy-mm-dd). It is not a problem, if the installer is older than the latest source release of Seed7. The installer is capable to download the latest source release. After you have downloaded the installer you can start it (either from the console (cmd.exe) or from the Windows Explorer).
The installer leads through the installation process with a dialog. It determines the latest source release of Seed7 and downloads it. If the latest release cannot be downloaded a manually downloaded source release can be used instead. The installer can also use a built-in release of Seed7. This built-in release is the one with the date of the installer.
The installer asks for an installation directory for Seed7. Afterwards it compiles Seed7 with the makefile seed7/src/mk_mingc.mak. The installer uses a built-in make utility and an encapsulated gcc. These tools do not interfere with another make or gcc, which might be installed on your computer.
Finally the installer adds the directory with the Seed7 executables to the search path (PATH variable). Therefore it needs administrator rights. The program to change the path is setwpath.exe. The name setwpath.exe will show up, when you are asked to allow administrative rights for the installation.
The installer can be used to update an existing Seed7 installation. The installer checks the version of an existing installation of Seed7 and offers the possibility to update. Update means that all files in the Seed7 installation directory are replaced. Therefore it makes sense to place your own Seed7 programs and libraries at a different place.
The latest source code release of Seed7 can be downloaded from:
Just click on the button Download Latest Version .
Other source code releases can be found in the directory seed7. It is strongly recommended to use the latest version. An installer for Windows can be found in the directory bin. Other executables are also in the bin directory.
Seed7 is now available at GitHub as well. You can use the command:
git clone https://github.com/ThomasMertes/seed7.git
to clone the Seed7 repository.
After downloading the Seed7 source code the interpreter can be compiled.
If you have a gnu 'tar' program available you can just do
$ tar -xvzf seed7_05_yyyymmdd.tgz
If your 'tar' command does not accept the 'z' option you need to uncompress the file first with 'gunzip':
$ gunzip seed7_05_yyyymmdd.tgz $ tar -xvf seed7_05_yyyymmdd.tar
Sometimes the browser downloads a *.gz file instead of a *.tgz file. In that case, you could also use 'gunzip' as shown above. As an alternative, you can also use 'zcat':
$ zcat seed7_05_yyyymmdd.gz > seed7.tar $ tar -xvf seed7.tar
Under windows you can use the 7-Zip compression/decompression utility (there is no relationship to Seed7). 7-Zip is open source software and is available at: www.7-zip.org.
There is a detailed description how to build Seed7 in
The way to compile the interpreter depends on the operating system and the development tools used. You need a stand-alone C compiler and a make utility to compile the interpreter. A C compiler, which is only usable from an IDE, is not so useful, since some Seed7 programs (e.g. The Seed7 compiler s7c) need to call the C compiler as well.
In case a make utility is missing the program make7 can be used instead. You can download make7.exe, which is a binary version of make7 for Windows.
To compile the interpreter under Linux just go to the
make depend make
For other cases several makefiles are prepared for various combinations of operating system, make utility, C compiler and shell:
makefile name | operating system | make prog | C compiler | shell |
---|---|---|---|---|
mk_linux.mak | Linux/Unix/BSD | (g)make | gcc | sh |
mk_clang.mak | Linux/Unix/BSD | (g)make | clang | sh |
mk_icc.mak | Linux/Unix/BSD | (g)make | icc | sh |
mk_tcc_l.mak | Linux/Unix/BSD | (g)make | tcc | sh |
mk_cygw.mak | Windows (Cygwin) | (g)make | gcc | sh |
mk_msys.mak | Windows (MSYS) | mingw32-make | gcc | sh |
mk_mingw.mak | Windows (MinGW) | mingw32-make | gcc | cmd.exe |
mk_nmake.mak | Windows (MinGW) | nmake | gcc | cmd.exe |
mk_msvc.mak | Windows (MSVC) | nmake | cl | cmd.exe |
mk_bcc32.mak | Windows (bcc32) | make | bcc32 | cmd.exe |
mk_bccv5.mak | Windows (bcc32) | make | bcc32 V5.5 | cmd.exe |
mk_clangw.mak | Windows (clang) | (g)make | clang | cmd.exe |
mk_tcc_w.mak | Windows (tcc) | (g)make | tcc | cmd.exe |
mk_djgpp.mak | DOS | (g)make | gcc | cmd.exe |
mk_osx.mak | Mac OS X | make | gcc | sh |
mk_osxcl.mak | Mac OS X | make | clang | sh |
mk_freebsd.mk | FreeBSD | make | clang/gcc | sh |
mk_emccl.mak | Linux/Unix/BSD | make | emcc + gcc | sh |
mk_emccw.mak | Windows (emcc) | mingw32-make | emcc + gcc | cmd.exe |
In the optimal case you just copy one makefile from above to 'makefile' and do (with the corresponding make program):
make depend make
When the interpreter is compiled successfully the executable and the libraries are placed in the 'bin' directory. Additionally a symbolic link to the executable is placed in the 'prg' directory (Under Windows symbolic links are not supported, so a copy of the executable is placed in the 'prg' directory). The Seed7 compiler (s7c) is compiled with:
make s7c
The compiler executable is copied to the 'bin' directory. If you do several compilation attempts in succession you need to do
make clean
before you start a new attempt.
In most cases errors indicate that some development package of your distribution is missing. If your operating system is Linux, BSD or Unix not all development packages with header files might be installed. In this case you get some errors after typing 'make depend'. Errors such as
chkccomp.c:56:20: fatal error: stdlib.h: No such file or directory s7.c:30:20: fatal error: stdlib.h: No such file or directory
indicate that the development package of the C library is missing. I don't know the name of this package in your distribution (under Ubuntu it has the name libc6-dev), but you can search for C development libraries and header files.
Errors such as
con_inf.c:54:18: error: term.h: No such file or directory kbd_inf.c:53:18: error: term.h: No such file or directory trm_inf.c:47:18: error: term.h: No such file or directory
indicate that the curses or ncurses development package is missing. I don't know the name of this package in your distribution (under Ubuntu it has the name libncurses5-dev), but you can search in your package manager for a curses/ncurses package which mentions that it contains the header files. To execute programs you also need to install the non-developer package of curses/ncurses (in most cases it will already be installed because it is needed by other packages).
Errors such as
drw_x11.c:38:19: error: X11/X.h: No such file or directory drw_x11.c:39:22: error: X11/Xlib.h: No such file or directory drw_x11.c:40:23: error: X11/Xutil.h: No such file or directory drw_x11.c:45:24: error: X11/keysym.h: No such file or directory
indicate that the X11 development package is missing. Under Ubuntu this package has the name libx11-dev and is described as: X11 client-side library (development headers) Note that under X11 'client' means: The program which wants to draw. A X11 'server' is the place where the drawings are displayed. So you have to search for a X11 client developer package with headers. If you use X11 in some way (you don't do everything from the text console) the non-developer package of X11 will already be installed.
Errors such as
gcc chkccomp.c -o chkccomp chkccomp.c:28:10: fatal error: base.h: No such file or directory compilation terminated.
or
del version.h process_begin: CreateProcess(NULL, del version.h, ...) failed. make (e=2): The system cannot find the file specified. mingw32-make: *** [clean] Error 2
indicate that your makefile contains commands for the cmd.exe
(or command.com) windows console, but your 'make' program uses
a Unix shell (
Errors such as
s7.c:28:21: error: version.h: No such file or directory
indicate that you forgot to run 'make depend' before running 'make'. Since such an attempt produces several unneeded files it is necessary now to run 'make clean', 'make depend' and 'make'.
If you got other errors I would like to know about. Please send a mail with detailed information (name and version) of your operating system, distribution, C compiler, the version of Seed7 you wanted to compile and the complete log of error messages to seed7-users@lists.sourceforge.net .
A comprehensive test of the s7 interpreter and the s7c compiler can be done in the directory prg with the command:
./s7 chk_all
Under windows using ./ might not work. Just omit the ./ and type:
s7 chk_all
The program chk_all uses several check programs to do its work. First a check program is interpreted and the output is compared to a reference. Then the program is compiled and executed, and this output is also checked. Finally, the C code generated by the compiled compiler is checked against the C code generated by the interpreted compiler. The checks of the compiler are repeated with several compiler options. If everything works correctly the output is (after the usual information from the interpreter):
compiling the compiler - okay chkint ........... okay chkovf ........... okay chkflt ........... okay chkbin ........... okay chkchr ........... okay chkstr ........... okay chkidx ........... okay chkbst ........... okay chkarr ........... okay chkprc ........... okay chkbig ........... okay chkbool ........... okay chkenum ........... okay chkbitdata ........... okay chkset ........... okay chkhsh ........... okay chkfil ........... okay chkexc ........... okay
This verifies that interpreter and compiler work correctly.
After Seed7 interpreter and compiler have been compiled and verified they can be installed. The makefiles support the target install. You need appropriate privileges to do the installation. Depending on the operating system there are different strategies to get the privileges:
Unix-like operating systems
Just go to the directory
sudo make install
With the make command of your computer. The sudo command
will ask you for your password. If your permissions are
sufficient the command creates symbolic links in the
directory
Windows
You need to open a console as administrator. Then you can
go to the directory
make install
With the make command of your computer. This adds the
directory
More details can be found in the file
The s7 interpreter is called with the command
s7 [options] sourcefile [parameters]
Note that the 'options' must be written before the 'sourcefile'. If the 'sourcefile' is not found .sd7 is appended to the 'sourcefile' and searched for that file.
The following options are recognized by s7:
In the program the 'parameters' can be accessed via argv(PROGRAM). The function argv(PROGRAM) delivers an array of strings. The number of parameters is 'length(argv(PROGRAM))' and 'argv(PROGRAM)[1]' returns the first parameter.
Generally Seed7 is designed to allow the compilation to machine code. The Seed7 compiler (s7c) is written in Seed7. It uses the analyze phase of the interpreter to convert a program to call-code and then generates a corresponding C program. This C program is compiled and linked afterwards. So Seed7 compiles to efficient machine code via C. The intermediate C code is viewed as portable assembler. It is not intended for human readers. The Seed7 compiler can be called with:
s7c [ options ] source
Possible options are
Syntax highlighting is available for several editors:
mkdir -p ~/.vim/syntax mkdir -p ~/.vim/ftdetect cp seed7.vim ~/.vim/syntax cp sd7.vim ~/.vim/ftdetectOn some computers syntax highlighting is turned off by default. In this case it is necessary to create a .vimrc file in the home directory and enter this line:
syntax onFor the Mac platform this is described here.
The file
Yes, Eclipse can be easily configured to work with Seed7:
You can use tools that profile executables such as Valgrind. Additionally the Seed7 compiler supports simple function profiling. You just need to compile a program with the option -p. If you execute this program it writes profiling data to the file profile_out, when it is finished. In "profile_out" you find a tab-separated table with microseconds, number of calls, place of the function and function name.
In Seed7 there are no reserved words. Instead there are keywords which are used at various places. Some keywords introduce statements or other constructs (such as declarations). E.g.: The keywords if, while, repeat, for, and some others introduce statements. Other keywords like do, then, range, result, etc. are used in the middle of statements (or other constructs). Finally there are also keywords like div, rem, lpad, times, etc. which are used as operator symbols.
Seed7 uses syntax declarations to specify the syntax of statements. A keyword is a name which is used somewhere in a syntax declaration. Syntax declarations reduce the possibilities to use a keyword out of context. E.g.: After the keyword if the parser expects always an expression. This makes if unusable as variable name. This way you get error messages when you try to use if or other keywords as variable name. That behavior is just the same as in other languages which have reserved words. It can be summarized that Seed7 reaches the goal of avoiding the misuse of keywords in other ways and not by reserving them altogether.
In a classic compiler (e.g. a Pascal compiler), there is a distinction between reserved words and identifiers. Pascal compilers and possibly Ada, C/C++, Java and C# compilers use an enumeration type to represent the reserved words. Since Seed7 allows user defined statements (which may introduce new keywords) it is not possible to hard code reserved words in the compiler as it is done in Pascal, Ada, C/C++, Java and many other compilers.
The syntax of Seed7 is described with the Seed7 Structured Syntax Description (S7SSD). The S7SSD is similar to an Extended Backus-Naur Form (EBNF), but there are important differences. S7SSD does not distinguish between different non-terminal symbols. Instead it only knows one non-terminal symbol: () . S7SSD syntax rules do not define named non-terminal symbols (EBNF rules define named non-terminal symbols). S7SSD syntax rules are introduced with:
$ syntax
S7SSD syntax rules define a pattern of terminal and non-terminal symbols separated by dots. A S7SSD syntax rule also defines a priority and associativity. The syntax of the + operator is:
$ syntax expr: .(). + .() is -> 7;
The syntax of statements and other constructs is defined as if they were operators:
$ syntax expr: .while.().do.().end.while is -> 25;
S7SSD is a simple syntax description that can be used by humans and compilers respectively interpreters. The syntax of a Seed7 program is defined in the library "syntax.s7i". When a Seed7 program is interpreted or compiled the syntax definitions are read from "syntax.s7i".
The C statements have some weaknesses which are avoided with the Seed7 statements:
The C if-statement
if (condition)
statement;
allows just one statement after the condition. By using the compound statement it is possible to have several statements after the condition
if (condition) {
statement1;
statement2;
}
Adding or removing a statement in the second if-statement is always possible. In the first if-statement you must add braces if you add a statement otherwise you get an undesired effect. Adding statements to an if-statement is quite common.
Since both forms are legal and adding a statement to the first form can lead to errors Seed7 closes this possible source of errors with its if-statement:
if condition then statement end if;
The following switch statement is formally correct but probably wrong
switch (number) { case 1: case 2: result = 5; case 3: case 4: result = 8; break; default: result = 0; }
Forgetting break statements in a switch is another possible source of errors which is avoided with the case-statement of Seed7:
case number of when {1, 2}: result = 5; when {3, 4}: result = 8; otherwise: result = 0; end case;
Many languages of the ALGOL and C family define variables this way:
int x
If a variable is initialized something like
int x = 888;
is used. Constant declarations often look like this:
const int x = 888;
The Seed7 declaration syntax is oriented towards these examples. Variable declarations are introduced with var and constant declarations are introduced with const. The equals sign is replaced with is and a colon is used as separator between type and name. This leads to:
const integer: x is 42;
Writing the type before the name offers the opportunity to have a type declaration that is easy to recognize and uses the same syntax:
const type: stack is array integer;
It is also easy to recognize function declarations. They use the same syntax:
const func integer: computeSomething is ...
In a block with several declarations the type can be recognized easily:
var integer: index is 1; var integer: paramValue is 0; var bigInteger: bigSum is 0_;
Programmers used to dynamically typed languages may be confused by the colon. Those languages use something like name: type with the type annotation being optional. In Seed7 the specification of the type is mandatory.
People often mistake familiarity with a certain kind of syntax for good readability. E.g.: If you prefer statements with braces it is harder to read statements using keywords instead and vice versa. But you can accustom to such syntactic things and then they don't hinder readability any more.
On the other hand there are things that lead to spaghetti code and being accustomed to the syntax does not help. Generally the term spaghetti code can be used for code where information is scattered and the reader needs considerable time to gather this information. E.g.:
Beyond that, reduced complexity also helps readability:
There are lots of possibilities to write unreadable code without using the extension features of Seed7. The programmer is (as always) responsible to write readable programs. The variable/type/function names and other things chosen by the programmer can always lead to obfuscated code.
Defining new statements and operators is a feature which should not be used in every program by every programmer. It is a feature which allows experienced programmers, to write libraries which use statement or operator syntax instead of function syntax, in areas where such a notation is already accepted practice.
Statements to access a database or operators for vector arithmetic would be such an example. Another example is a construct which can be used in the definition of text adventure games.
The possibility to define statements also allows a more precise language definition. The for/while/if statements of C++ are described in the C++ manuals with BNF and an English description. Seed7 statements can be defined in Seed7. For example:
$ syntax expr: .while.().do.().end.while is -> 25; const proc: while (in func boolean: condition) do (in proc: statement) end while is func begin if condition then statement; while condition do statement; end while; end if; end func;
The syntax and semantic of a while-statement is described using an if-statement and recursion. For performance reasons the implementation will usually use a different approach to implement a while-loop, but this example shows the expressive power of Seed7.
Defining the semantic of a new 'statement' in Lisp is a classic example. Normally such 'statements' still use the list notation with lots of parentheses. The read macros of Lisp could be used to define the syntax of a statement, but read macros make no type checks at compile time. Any type checking must be written by the programmer and is not mandated by Lisp. The type checks will be performed at runtime. Depending on the implementation there might be warnings issued at compile time. In general: Lisp 'statement' declarations do not force compile time checks and look less elegant. Seed7 statement declarations force a type check at compile time.
While Lisp allows new and overloaded functions, the Lisp 'operators' are functions which use the prefix notation (with lots of parentheses). Again read macros could be used to support infix operators with priority and associativity. This read macros would have the same problems as above. Although Lisp fanatics would never admit it, infix operators with priority and associativity are not really supported by Lisp. If somebody tells you that everything can be done in Lisp, send him to the next advocacy group. In general: Seed7 supports user definable infix operators with priority and associativity. Such operators can be overloaded and the type checks are done at compile time. In Lisp all this would be a hack.
With static type checking all type checks are performed during compile time. Type errors, such as an attempt to divide an integer by a string, can be caught earlier (unless this unusual operation has been defined). The key point is that type errors are found without the need to execute the program. Some type errors can be hidden in rarely executed code paths. Static type checking can find such errors easily. With dynamic type checking extensive tests are necessary to find all type errors. Even tests with 100% code coverage are not enough since the combination of all places where values are created and all places where these values are used must be taken into account. That means that testing cannot guarantee to find all type errors that a static type checker can find. Additionally it would be necessary to repeat all tests every time the program is changed. Naturally there are doubts that enough tests are done and that the tests are adjusted and repeated for every change in the program. Therefore it can be said that compile time type checks increase the reliability of the program.
Seed7 makes sure that the object values always have the type of the object. This goal is reached with mechanisms like mandatory initialization, runtime checks and the impossibility to change arbitrary places in memory. If the generation of garbage values is avoided, it can be guaranteed that only legal values of the correct type are used as object values. This way runtime type checks are unnecessary and the program execution can be more efficient.
Type declarations can also serve as a form of documentation because they can illustrate the intent of the programmer. Although static type checking is very helpful in finding type errors, it cannot replace a careful program design. Some operations allowed by the static type system can still be wrong because of different measurement units or other reasons. In the end, there are also other possible sources of errors such as range violations.
Interface types can be used if an object can have several types at runtime. In this case the interface type of the object can be determined at compile time and the type of the object value (implementation type) can vary at runtime. The static type checking can still check the interface type and the presence of interface functions. Additionally the compiler can also check that all functions granted by the interface type are defined for the implementation type.
No, especially if the time spent to debug a program is taken into account. Except for artificial corner cases all type errors found by a "nitpicking" compiler correspond to runtime type errors that can happen in a dynamically typed language under some circumstances. That way the compile time type checks save the time necessary to find and debug those errors. The time that a compiler needs to find and flag type errors is so small that it can be ignored in this comparison.
Some people claim, that adding type information to a program is a time consuming process. This is only true if the type information is added afterwards, but it is wrong if type considerations take place during the program development. Every good programmer has some concepts about what values will be hold by variables or parameters and what values will be returned by functions. A good type system helps to formalize the type concepts which are already in the mind of the programmer. That way, the ideas of the programmer are documented as well. This type documentation helps when reading the code. The maintenance costs are also reduced, since code is more often read that written.
When comparing compile time and runtime type checking it can be concluded that dynamic typed languages save some programming time by omitting type declarations, but this time must be paid back with massive interest rates to do the debugging and when the code needs to be maintained.
Definitely yes. Static type checking can guarantee that only legal values of the correct type are used. This way run-time type checks are unnecessary and the program execution can be more efficient. A statically typed language can be compiled to efficient machine code. In a dynamically typed language the type checks take place at run-time. It might be necessary to do type checks many times for the same expression and not just once at compile-time. In this case there is an overhead every time a dynamically typed value is used.
Dynamically typed languages often introduce compilation and type annotations as afterthought. This works worse than in a statically typed language. Since type annotations are optional they are not considered when the program is written, but at a later time (maybe by someone else). If the type annotations are not 100% correct unnecessary conversions might take place, which slow down the program.
This question refers to something which seems paradox: If Seed7 types are created at runtime how can they be checked at compile time. The simple answer is that a type created at runtime cannot be used to define something in the program that is currently running.
Seed7 declarations are not executed at runtime. Functions with type parameters and type result are executed at compile time. This is done in templates and abstract data types (both are executed at compile time). It is possible to have type variables and type expressions at runtime but is not possible to declare objects with such a variable type for the program which currently runs. Such type variables and type expressions are used in the Seed7 compiler.
Seed7 has a basic principle that would break if type inference would be used:
The type of every expression (and sub expression) is independent of the context. |
To explain this principle consider the expression:
a + b
If the types of a and b are known and the definition of + applies, then the type of the expression a + b is also known. In this example a and b may be constants, variables or even sub-expressions. If one of the types of a or b is not known then the type of a + b cannot be determined. Now assume that a + b is part of a bigger expression:
c = (a + b)
In theory you could deduce the type of a + b if you know the type of c and how = works. But in this case the context of a + b would have to be taken into account. The basic principle mentioned above rules that out. According to it the context around an expression has no influence on the type of the expression. In Seed7 the type information moves inside out from sub-expressions to expressions. Within the syntax tree it moves from the bottom to the top. This rule simplifies type checking a lot. This is one of the reasons why the interpreter is able to process several hundred thousand lines per second.
For type inference, it would be necessary that type information also moves into the other direction. You can see: This would violate the basic principle mentioned above. As long as this principle holds you need to know the global and local declarations to find out the result type of an expression. With type inference it would be necessary to take other expressions in the local function and even expressions in other functions into account. I do not say that this is not possible (for sure it is an interesting challenge to invent an algorithm to do this). But a human reader would also need to apply this algorithm when reading the program. You have to consider that a program is more often read than written.
A local declaration block is parsed completely before it is executed. This causes that type declarations inside of a local declaration block are not defined during the parsing. If the locally defined type is used in the same declaration block an error like
*** tst249.sd7(6):52: Match for {var intArrayType : {arr } is {[ ] {1 , 2 } } } failed
var intArrayType: arr is [](1, 2);
will be triggered. This errors are avoided, if all type declarations are made at the top level. E.g.:
$ include "seed7_05.s7i"; const type: intArrayType is array integer; const proc: main is func local var intArrayType: arr is [](1, 2); begin writeln(length(arr)); end func;
A function declaration like:
const func array array string: test (in string: value) is func result var array array string: data is 0 times 0 times ""; ...
triggers the error:
*** tst309.sd7(4):52: Match for {func result var func type: ({array type: ({array string }) }) : {data } is {0 times {0 times "" } } begin {data := {3 times {2 times value } } } end func } failed
const func array array string: test (in string: value) is func
The reason is: The two occurrences of array array string are considered as two different types. This error can be avoided by defining a named type for the two dimensional array at the top level:
const type: stringArray2D is array array string; const func stringArray2D: test (in string: value) is func result var stringArray2D: data is 0 times 0 times ""; ...
Seed7 cannot read the mind of the programmer. It is hard to find out what the programmer considers as "right type". A conversion can lose information. Seemingly safe conversions may also lose information. E.g. Not all 64-bit integer values can be represented as 64-bit float values. It can also lead to unplanned behavior if the programmer is not aware of an automatic conversion. Readability is improved if conversions are done explicitly. Seed7 is strong typed and uses explicit conversions. E.g.: The conversion from integer to float is done with the functions flt and float. Conversions from float to integer are done with round or trunc. Explicit conversions have more advantages than disadvantages:
The phrase "do what I mean" (DWIM) is used when computer systems attempt to anticipate what users intend to do. To put it bluntly: A program tries to read a humans mind. Since this is not possible DWIM languages use heuristics to interpret illegal or ambiguous code towards an interpretation that makes sense. Well, it makes sense for the one who invented the heuristic, which does not imply that it makes sense for everybody else.
Heuristics usually do not work for 100%. There are always corner cases where heuristics fail in an unexpected way. In this case something in your program is misinterpreted and you don't know about it. This means: Your program contains a bug which you are not aware of.
Not all human programmers have the same background. What one programmer sees as correct reinterpretation of his intentions another programmer might consider as totally stupid. The second programmer would certainly prefer to get an error message instead of a silent reinterpretation of his program. Of course, this silent reinterpretation also means that there is a hidden bug in the program.
There is a reason natural languages are not used for programming. They are just too ambiguous. Established programming languages try to be unambiguous. Usually they are more strict than the languages used in mathematics or physics. Being strict has proven to facilitate the maintenance of large programs. It is a bad idea to allow ambiguities, even when they are resolved by "do what I mean" heuristics. Therefore Seed7 is not a "do what I mean" language.
No, everything must be declared before it is used. The possibility to declare new statements and new operators on one side and the static typing requirements with compile time checks of the parameters on the other side would make the job of analyzing expressions with undeclared functions very complex.
Forward declarations help, if something needs to be used before it can be declared fully:
const func string: concatenate (inout file: inFile) is forward; const func string: getString (inout file: inFile) is func result var string: stri is ""; begin if inFile.bufferChar = '(' then ignore(getc(inFile)); stri := concatenate(inFile); # Call of forward declared function ignore(getc(inFile)); elsif inFile.bufferChar = '"' then stri := getQuotedText(inFile); end if; end func; const func string: concatenate (inout file: inFile) is func result var string: stri is ""; begin stri := getString(inFile); while inFile.bufferChar = '&' do ignore(getc(inFile)); stri &:= getString(inFile); end while; end func;
Yes, functions, operators and statements can be overloaded. E.g.:
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_;
These functions can be used with:
writeln(tenPercent(123)); writeln(tenPercent(123.0)); writeln(tenPercent(123_));
Existing operators like + can be overloaded with:
const func float: (in integer: summand1) + (in float: summand2) is return float(summand1) + summand2;
Note that the declaration above identifies the + operator with:
(in integer: summand1) + (in float: summand2)
This is the same syntactic pattern as the one used, when the + operator is invoked:
8 + 3.9
This syntactic pattern is defined in the file "syntax.s7i" with the syntax declaraton:
$ syntax expr: .() . + .() is -> 7;
The actual syntax is described with:
. () . + . ()
The dots are used to create a list of elements. If we leave out the dots we get the actual syntactic pattern:
() + ()
The place of parameters is specified with (). In declarations () is the place of a parameter declaration and in calls () is the place of an actual parameter.
To introduce new operator symbols like inProduct it is necessary to define the syntax before defining the semantic.
No, return type overloading is not supported. Otherwise the type of an expression would depend on its context. Assume the function f is overloaded like this:
f(x) -> integer f(x) -> string
In this case the compiler cannot determine the type of f(x) unless it analyzes the context of the call. Thus type checking expressions may become arbitrarily complex. Remember that the human reader has to do the same. Omitting return type overloading simplifies type checking a lot. This way the following holds:
The type of every expression (and sub expression) is independent of the context. |
Consider this expression with operators (also applying to functions):
a = b + c * d
Converting into a tree representation gives:
= / \ a + / \ b * / \ c d
The type information goes strictly upward (or inside out).
This bottom up approach simplifies type checking a lot for both the compiler and the human reader. With return type overloading there would be ambiguous sub expressions. For the whole expression these ambiguities might be resolved or they might stay unresolved. It is obvious that return type overloading would complicate type checking considerable (for the human reader too).
No, because functions with variable parameter list as the C printf function have some problems:
Instead Seed7 has array aggregates and allows functions with arrays as parameters. So you could declare a function
const proc: print_list (in array integer: arr) is func local var integer: number is 0; begin for number range arr do writeln(number); end for; end func;
and call it with
print_list([](1, 1, 2, 3, 5, 8, 13, 21, 34, 55));
The decision to use the keyword is relates to the Structured Syntax Definition of Seed7. A variable declaration in Seed7 looks like:
var integer: number is 42
The syntax of this variable declaration is defined in the file syntax.s7i. A variable declaration with := would need a different syntax definition. An attempt to define this new syntax leads to an error:
*** tst356.sd7(3):42: ":=" redeclared with infix priority 127 not 20
syntax expr: .var.(). : .(expr). := .(expr) is -> 40;
---------------------------------------------------------^
The error rejects the desired syntax pattern:
var () : () := ()
In syntax patterns () denotes the place for any expression (=parameter). The other symbols in a syntax pattern should appear as expected. The error above highlights a conflict between an existing syntax pattern an the new one. The use of := in this pattern conflicts with the one in the assignment syntax pattern. The assignment operator (:=) is already defined as infix operator with the priority 20. The syntax pattern of the := operator is:
() := ()
In both patterns there is a parameter left of the := symbol (marked in green). In case of the assignment, the priority of the left parameter must be less than 20. In case of the variable declaration, this parameter is situated in between : and :=. Such a middle parameter allows the much weaker priority 127. Code like
var integer : number := 42; begin
would be interpreted as
var integer : (number := 42;) begin
Since the syntax pattern would expect a := after number := 42; this would lead to the error:
*** tst356.sd7(8):47: ":=" expected found "begin"
begin
-------^
To avoid this misinterpretation, the syntax of a variable declaration with := is rejected beforehand with:
*** tst356.sd7(3):42: ":=" redeclared with infix priority 127 not 20
Languages with hard-coded syntax analysis use a trick to allow the same symbol for assignments and initialization. They read the parameter between : and := with special parsing code to read an identifier. Afterwards, they check if the identifier is followed by an assignment symbol. The Structured Syntax Definition of Seed7 does not support such tricks. Instead it provides a systematic approach for the syntax description. This general concept to define syntax is available to all programmers.
Most languages allow that a constant is initialized with a constant expression. This usually rules out user defined functions (or it is restricted in other ways). Seed7 allows arbitrary expressions (including user defined functions) in initializations of constants and variables:
const integer: limit is 1000 ** 2 * 10; var string: s7Page is getHttp("seed7.sourceforge.net"); const func array string: getWords (in string: fileName) is return split(lower(getf(fileName)), "\n"); var array string: dict is getWords("unixdict.txt"); const set of integer: primes is eratosthenes(limit); const PRIMITIVE_WINDOW: pic is readBmp("head3.bmp"); const array integer: someData is [](1, 1, 2, 3, 5, 8, 13, 21, 34, 55);
A nice example is the initialization of the table stars with the function genStarDescr in the library stars.s7i.
Forgetting to initialize a variable is a common source of errors. In some programming languages uninitialized variables have a random value which could lead to errors. To avoid errors caused by uninitialized variables in Seed7 each variable must be initialized when it is declared.
Seed7 follows the "one size fits all" principle for fixed size integers. The type integer is 64-bit signed, smaller integer types do not exist. Today's computers have 64-bit processors. Some processors do not have instructions for all the smaller integer types. On such computers, smaller integers must be converted into larger integers in order to do computations. So programs that use smaller integers might actually be slower because of this. Today computers' memory covers many gigabytes, so the pressure to save memory is also gone. If you prefer arrays with smaller integers, because they fit into the cache, you should probably stick with C or some other lower level language. Seed7 tries to stay above this low level thinking.
Support for shorter integers is only needed, when reading or writing files that contain binary integers of smaller sizes. In C it is possible to write or read data structures directly to or from a file. Such C code is unportable, as it assumes that the file format uses the same endianness (little- or big-endian) as the processor. Seed7 does not support writing or reading structures directly to or from a file. Instead the library bytedata.s7i defines several functions to convert integers into and from signed and unsigned representations of various sizes. These functions also allow that the endianness is specified explicit.
Message digest and compression algorithms do bitwise operations on 32- or 64-bit data. Bitwise operations are not supported by integer. To do that the types bin32 and bin64 have been introduced. These types support bitwise AND, OR and XOR operations, but no integer arithmetic. Hence bin32 and bin64 are not integer types but types that describe bit-patterns with 32 and 64 bits. Conversions between integer and bin32 (respectively bin64) cause no additional costs in compiled programs.
If the 64-bit signed integer type is not sufficient the type bigInteger can be used.
Seed7 characters and strings support Unicode. Unicode values are encoded with UTF-32. Functions which exchange strings with the operating system automatically convert the strings from and to UTF-32. It is possible to read and write files with Latin-1, UTF-8 and UTF-16 encoding. Functions to deal with code pages and functions to convert between different Unicode encodings are also available.
The usage of UTF-32 for strings in a program has several advantages:
Sometimes it is argued that UTF-32 encodes code points and that an actual character might consist of several code points (e.g. by using combining characters). This is not only a problem of UTF-32. Practically all programs that use Unicode assume that a code point is a character. Unicode contains many precomposed characters, so that most of the time a code point is in fact a character. Most of the programs have no problem with that simplification. If a program needs to handle combining characters it must check for that, independent of the code point encoding.
In a Seed7 program all operations with strings can be done with the type string. Having just one string type simplifies things.
Conversions to upper and to lower case use the default Unicode case mapping, where each character is considered in isolation. Characters without case mapping are left unchanged. The mapping is independent from the locale. Individual character case mappings cannot be reversed, because some characters have multiple characters that map to them.
Seed7 source code allows Unicode in char literals, string literals, block comments and line comments. Interpreter and compiler assume that a Seed7 program is written with UTF-8 encoding. Therefore a program editor with UTF-8 encoding should be used.
Unicode names are supported as well. The support for Unicode names is switched off by default and must be activated with the pragma:
$ names unicode;
This pragma allows variables with e.g. German umlauts or Cyrillic letters. This way beginners can use variable and function names from their native language.
Java, C# and several other languages use immutable strings which allow for simple and quick assignments (just a pointer is assigned). But they also have disadvantages. Almost everything else besides assignments becomes more expensive. Every time immutable strings are changed, the whole string content must be copied. If you want to change a string often, this becomes very costly. For that reason Java introduced the mutable string class StringBuffer (and later StringBuilder). Maintaining the string data of immutable strings is also an overhead that costs time as it requires bookkeeping and garbage collection.
The string handling of mutable strings can be optimized, such that copying the string content can be avoided in many cases. This is done by the Seed7 interpreter and compiler. You get cheap string parameter passing, string slicing and assignment without being bothered with immutable and mutable string types (which is essentially an implementation detail). Mutable strings also give us consistent language semantics (strings are not handled differently than other objects).
Here is a little example to explain that. Please read the second line from the following list:
It should be obvious: The number one has been invented as starting point to count something. The first character in this sentence is T not h. So the question is: Why does everybody believe that in computer science the first character has the index 0? Basically this origins in the language C. Arrays and strings in C are viewed as pointer + offset. So it is natural that the first offset is 0. From C this concept spread to many other programming languages. Seed7 breaks with this tradition as it uses the number one again for the purpose it has been invented, thousands of years ago, long before zero has been introduced.
In Seed7 the operators = (equal) and <> (not equal) are defined for all types. Additionally many types also define the operators < (less than), <= (less than or equal to), > (greater than) and >= (greater than or equal to). These operators do exactly what the corresponding type considers as the correct comparison.
In Java and other languages you are discouraged to use the normal equality comparison operator (==) for string comparisons. Instead you need to use an expression like name.equals(""). The == operator just compares references, which is almost never the desired operation. Seed7 is much more consistent in this regard, because the = operator is generally used to check for equality. It is just not necessary to tell every newcomer that == is used to compare integers, but that it should never be used to compare strings.
Most types of Seed7 define the function compare(A, B), which returns -1 (if A is less than B), 0 (if A equals B) or 1 (if A is greater than B). This function defines a total order over the values of a type even if < has not been defined or if < does not define a total order. E.g.:
type | comparisons | compare | comment |
---|---|---|---|
float | = <> < <= > >= | compare | According to IEEE 754 a NaN is neither less than, equal to, nor greater than any value, including itself. Float compare(A, B) considers all NaN values as greater than Infinity. |
complex | = <> | compare | Compares real and imaginary part. |
bitset | = <> < <= > >= | compare | The comparisons < <= > >= check for subsets and supersets. Bitset compare(A, B) compares by determining the biggest element that is not present or absent in both sets. |
Hash tables use compare(A, B) to manage their elements.
A database library provides a database independent API, which defines how a client may access a database. Seed7 accomplishes database independence by using database drivers as abstraction layers between the application and the database. There are database drivers for MySQL, MariaDB, SQLLite, PostgreSQL, Oracle, Firebird, Interbase, Db2, Informix and SQL Server databases. Databases can also be accessed via the ODBC interface. How the database independent API of Seed7 works can be seen in the following example:
const proc: dbDemo is func local var database: currDb is database.value; var sqlStatement: statement is sqlStatement.value; var integer: index is 0; begin currDb := openDatabase(DB_MYSQL, "testDb", "testUser", "testPassword"); if currDb <> database.value then statement := prepare(currDb, "select * from testTable"); execute(statement); while fetch(statement) do for index range 1 to columnCount(statement) do write(column(statement, index, string) <& ", "); end for; writeln; end while; close(currDb); end if; end func;
In the manual there is a chapter about the database abstraction API.
Regular expressions are a powerful feature. Unfortunately they also lead to code that is hard to maintain. The regular expression language is usually embedded in a surrounding programming language. As with other language-in-language features, this leads to maintenance problems. There are other difficulties too. Regular expressions work typeless but Seed7 does not. For this reasons regular expressions are currently not supported, but there is an alternative (see below).
Seed7 has support for lexical scanner functions. Scanner functions use the LL(1) approach, which is used in compilers. Practically no compiler uses regular expressions to parse a program. The example below uses scanner functions to read a key-value pair from a file:
const proc: getKeyValuePair (inout file: inFile, inout string: propertyName, inout string: propertyValue) is func begin skipWhiteSpace(inFile); propertyName := getName(inFile); skipWhiteSpace(inFile); if inFile.bufferChar = '=' then inFile.bufferChar := getc(inFile); propertyValue := getLine(inFile); else propertyValue := ""; skipLine(inFile); end if; end func;
Scanner functions work strictly from left to right. They examine one character and do decisions based on this character. How scanner functions work is described in the manual. Scanner functions for strings are defined in scanstri.s7i and scanner functions for files are defined in scanfile.s7i.
In Pascal and Ada the keyword div is used as integer division operator. Other languages like C and its descendants use / for integer division. Using div has some advantages:
The chapter about the type integer in the manual describes properties of integer divisions and contains tables that show their behavior.
The operators & and <& both concatenate strings, but they have different purposes.
The & operator is intended for string concatenations in normal expressions. The & operator does not convert an integer (or some other value) to a string.
The priority of & is defined to execute the concatenation before doing a comparison. E.g.:
name & extension = check
has the meaning
(name & extension) = check
So the & operator can be used like + - * (the expression is evaluated and its result can be compared).
The <& operator is intended for write statements. It is overloaded for many types. As long as the first or the second parameter is a string it does convert the other parameter to a string (with the function str) and does the concatenation afterwards.
The priority of <& is defined to also allow the output of boolean expressions. E.g.:
name <& extension = check
has the meaning
name <& (extension = check)
Note that extension and check could be e.g. integers. The result of 'extension = check' is converted to string with the function str. So
writeln(name <& extension = check)
would write (if name is "asdf: " and extension is not equal to check):
asdf: FALSE
The <& operator can be defined for new types with enable_io respectively enable_output. The description of the Seed7 file API also contains a chapter about the conversion to strings and back.
There are call-by-value and call-by-reference parameters. The formal parameter can be constant or variable. The combination of these features allows four types of parameters:
parameter | evaluation strategy | access right |
---|---|---|
val | call-by-value | const |
ref | call-by-reference | const |
in var | call-by-value | var |
inout | call-by-reference | var |
For call-by-value parameters (val and in var) the actual parameter value is copied, when the function is called. For call-by-refererence parameters (ref and inout) the function uses a reference to the actual parameter value. Since a call-by-reference parameter is not copied it can provide better performance for structured types like strings, arrays, structs and hashes.
An in parameter describes, that the actual parameter value is going into the function. Inside the function an in parameter cannot be changed. In parameters are the most commonly used evaluation strategy for parameters.
An in parameter is either a val (call-by-value) parameter or a ref (call-by-reference) parameter. Every type defines an in parameter:
Usually it is not necessary to care, if an in parameter uses call-by-value or call-by-reference. A programmer can just use in parameters to specify, that the actual parameter value is going into the function. A programmer can use val or ref to overrule this behavior in cases, where the default in parameter specified by a type is not desired.
Normally val and ref parameters behave the same. Only in corner cases their behavior differs. This is shown with the following example:
$ include "seed7_05.s7i"; var integer: aGlobal is 1; const proc: aFunc (val integer: valParam, ref integer: refParam) is func begin writeln(valParam <& " " <& refParam); aGlobal := 2; writeln(valParam <& " " <& refParam); end func; const proc: main is func begin aFunc(aGlobal, aGlobal); end func;
The program above writes:
1 1 1 2
The different behavior is triggered when 2 is assigned to the global variable aGlobal:
The effect happens for any type, not just for integer parameters. The same effect also happens, when an additional inout parameter is used instead of a global variable and when the function is called with the same variable as actual parameter for all three parameters.
If a programmer has to deal with such corner cases it is necessary to explicitly use val or ref.
Call-by-name is an evaluation strategy for parameters. The actual call-by-name parameter is not evaluated before the function is called. When the function is executed the call-by-name parameter might be executed once, many times or not at all. Examples of call-by-name parameters are:
As can be seen, call-by-name parameters are used all the time, without realizing it. A call-by-name parameter is a function without parameters. Function types such as proc or func boolean are used as type of formal call-by-name parameters. An expression with the correct type is allowed as actual call-by-name parameter. This actual parameter expression is not evaluated when the function is called. Instead the call-by-name expression is evaluated every time the formal call-by-name parameter is used. A 'conditional' function (similar to the ?: ternary operator) is defined with:
const func integer: conditional (in boolean: condition, ref func integer: trueValue, ref func integer: falseValue) is func result var integer: conditionalResult is 0; begin if condition then conditionalResult := trueValue; else conditionalResult := falseValue; end if; end func;
Seed7 does not require a special notation (like brackets) for actual call-by-name parameters, therefore the 'conditional' function can be called with:
conditional(a >= 0, sqrt(a), a ** 2)
Depending on the condition 'a >= 0' only one of the expressions 'sqrt(a)' and 'a ** 2' is evaluated. This evaluation takes place when 'trueValue' or 'falseValue' is assigned to 'result'.
A function declaration like
const func boolean: isZero (in integer: number) is return number = 0;
uses const, because the body of the function
return number = 0;
will not change at run-time. So isZero will not suddenly compute something else. For the same reason, procedures are also defined with const:
const proc: procedureName ...
There are function declarations that are not introduced with const. Below is a call-by-name parameter declared with in:
const proc: whileTrueDo (in func boolean: callByNameParam) is func begin while callByNameParam do noop; end while; end func;
The call-by-name parameter callByNameParam refers to the function provided as an actual parameter when whileTrueDo is called:
whileTrueDo(getc(IN) <> '\n');
Inside whileTrueDo, the call-by-name parameter cannot be changed, but depending on the actual parameter, it can refer to different functions in different invocations.
For functions without parameters, there is support to declare functions with var. Except for test programs, this feature is not used, since object orientation provides a much better mechanism to execute different functions at run-time.
An integer overflow occurs if a calculation produces a result that cannot be stored in an integer variable. E.g.:
1234567890 * 9876543210
The correct result is 12193263111263526900 but this value does not fit into a 64-bit signed integer variable. The lowest 64 bits of the result correspond to -6253480962446024716 which is obviously wrong. Very popular languages such as C, C++, Java, Objective-C and Go do not care about integer overflow. Programs in these languages continue to execute with a wrong value instead of the correct result. This wrong value can then trigger dangerous things. A program can make wrong decisions or produce wrong output, without any hint that an integer overflow occurred. In Seed7 the exception OVERFLOW_ERROR is raised if an overflow occurs. If performance is important the overflow checking can be switched off with the compiler option -so.
In some languages, an integer expression that overflows is promoted to bigInteger and the correct result is returned as bigInteger. Seed7 does not follow this approach because it costs significant performance.
The approach that promotes integers requires a new encoding for integers. A new combined integer type that can encode a fixed size integer and a big integer in one memory location. Information about the representation that is used must also be encoded in the memory location of the combined integer.
An encoding for a combined integer that is actually used by integer promoting languages is: The lowest bit of a 64-bit value decides if a fixed size integer or a bigInteger is encoded in the remaining 63 bits. If the lowest bit is zero, the higher 63 bits would be the actual integer in a twos complement representation. If the lowest bit is one, the higher 63 bits would encode a reference to the actual bigInteger value. This combined integer encoding (with the lowest bit as decision-maker) considers memory consumption and run-time overhead.
For performance consideration the most common situation is used: An addition of two small integers (that fit into the fixed size part of a combined integer) and no overflow occurs.
add jump on overflow
slow = (a | b) & 1; res = a + b; if (!slow && !overflow_ocurred) { return res; } else { return bignum_add(a, b); }The happy path requires approximately 5 machine instructions for an addition:
| & + !slow !overflow_occurredBesides that, there is more overhead:
The automatic promotion to big integer (with the combined integer type) reduces performance. The approach that only checks for integer overflow needs two instructions for an addition. It needs no additional overhead for assignments, at the end of a variable scope, or in other situations. The possibility of optimizations applies to both approaches that check for integer overflow, but not everything can be optimized away.
For these reasons, Seed7 does not support the automatic promotion of integer expressions that overflow. Instead, it checks for integer overflow and raises the exception OVERFLOW_ERROR if necessary.
There is an automatic memory management, but there is no garbage collection process, that interrupts normal processing. There is no situation, where a garbage collection needs to "stop the world". The automatic memory management of Seed7 uses different mechanisms. Memory usage can be categorized and for every category a specific strategy of automatic memory management is used:
Yes, but object orientation is organized different compared to other object oriented languages. In a nutshell: It is based on interfaces and allows multiple dispatch. Chapter 7 (Object orientation) of the manual contains a detailed description of the Seed7 object orientation.
An example of an object oriented type is file. A file describes references to values with some other type. A value of a file can have one of the following types: null_file, external_file, echoFile, lineFile, etc. Each of this file value types acts differently to the same requests.
For the type file two kinds of functions are defined:
Compared to Java the type file can be seen as interface or abstract class, while the type of the file value can be seen as the class implementing the interface.
There can be several base types, each with their own hierarchy. In many object oriented languages the class object is used as element of all container classes. Abstract data types provide a better and type safe solution for containers and other uses of the root class object. Therefore a single rooted hierarchy is not needed.
Overloading is resolved at compile time while object orientation uses dynamic dispatch which decides at runtime which method should be called. Overloading resolution uses static types to decide. Dynamic dispatch uses the implementation type, which is only known at runtime, to decide. Besides this difference overloading resolution and dynamic dispatch both use the same approach to do the work: The types and the access rights of all parameters are used in the decision process.
An abstract data type defines, like every other type, a set of functions to handle data. An abstract data type leaves, like an interface type from OO, the details of the data representation open. The difference between the two is:
Usually an abstract data type uses parameters to resolve to a concrete type. Examples of abstract data types are arrays, structs and hashes. An abstract array type needs the element type as parameter. E.g.:
array string
This array has string elements and uses integer indices. An abstract array, were the index type is also specified as parameters is:
array [char] string
This array has string elements and uses char indices. Arrays are present in many programming languages, but they are usually hard-coded into the compiler / interpreter. Seed7 does not follow this direction. Instead it introduces abstract data types as common concept behind arrays, structs, hashes and other types. Like templates abstract data types are implemented with functions that are executed at compile time. In contrast to templates abstract data types return a type as result.
Multiple dispatch means that a function or method is connected to more than one type. The decision which method is called at runtime is done based on more than one of its arguments. The classic object orientation is a special case where a method is connected to one class and the dispatch decision is done based on the type of the 'self' or 'this' parameter. The classic object orientation is a single dispatch system.
In a multiple dispatch system the methods cannot be grouped to one class and it makes no sense to have a 'self' or 'this' parameter. All parameters are taken into account when the dispatch decision is done. In the following example the interface type Number uses multiple dispatch:
const type: Number is sub object interface; const func Number: (in Number: a) + (in Number: b) is DYNAMIC;
The DYNAMIC declaration creates an interface function for the '+' operator. The interface type Number can represent an Integer or a Float:
const type: Integer is new struct var integer: data is 0; end struct; type_implements_interface(Integer, Number); const type: Float is new struct var float: data is 0.0; end struct; type_implements_interface(Float, Number);
The declarations of the converting '+' operators are:
const func Float: (in Integer: a) + (in Float: b) is func result var Float: sum is Float.value; begin sum.data := flt(a.data) + b.data; end func; const func Float: (in Float: a) + (in Integer: b) is func result var Float: sum is Float.value; begin sum.data := a.data + flt(b.data); end func;
The declarations of the normal '+' operators (which do not convert) are:
const func Integer: (in Integer: a) + (in Integer: b) is func result var Integer: sum is Integer.value; begin sum.data := a.data + b.data; end func; const func Float: (in Float: a) + (in Float: b) is func result var Float: sum is Float.value; begin sum.data := a.data + b.data; end func;
The decision which '+' operator should be called at runtime is based on the implementation type (Integer or a Float) of both arguments of the '+'.
Abstract data types are used to replace container classes. When using an abstract data type as container you have to specify the type of the element in the type declaration. Therefore abstract data types are always type safe. Typeless container classes with object elements do not exist. The only thing which comes near to this is the ref_list which is used in the reflection. A ref_list should not be misused as container class. Predefined abstract data types are:
Usage examples of abstract data types are:
array string array [boolean] string hash [string] boolean hash [string] array array string set of char set of integer
As in C++, Java, C# and other hybrid object oriented languages there are predefined primitive types in Seed7. These are integer, char, boolean, string, float, rational, time, duration and others. In addition to the predefined primitive types, there is also the possibility to declare new primitive types.
Variables with object types contain references to object values. This means that after
a := b
the variable 'a' refers to the same object as variable 'b'. Therefore changes of the object value that 'a' refers to, will effect variable 'b' as well (and vice versa) because both variables refer to the same object.
For primitive types a different logic is used. Variables with primitive types contain the value itself. This means that after
a := b
both variables are still distinct and changing one variable has no effect on the other.
If 'a' and 'b' are declared to have type 'aType' which contains the integer field 'property' you can do the following:
b.property := 1; a := b; b.property := 2;
Everything boils down to the question: What value does 'a.property' have now.
You should declare a new primitive type if you don't need the object oriented paradigm that a variable (and a constant) is just a reference to the object. Another indication is: If you don't need two concepts of what is equal (An == operator and an equal method).
For object types just the reference to the object value is copied. For primitive types the value itself is copied. Since values can be very big (think of arrays of structs with string elements) value copies can be time consuming.
In pure object oriented languages the effect of independent objects after the assignment is reached in a different way: Every change to an object creates a new object and therefore the time consuming copy takes place with every change. Because usually changes to an object are more frequent than assignments this approach can be even more time consuming than the approach using value copies for the assignment.
Seed7 has an approach for the assignment where practical arguments count more than the classic object oriented principles. In Seed7 every type has its own logic for the assignment where sometimes a value copy and sometimes a reference copy is the right thing to do. Exactly speaking there are many forms of assignment since every type can define its own assignment. If a value copy works like a deep or a shallow copy, it can also be defined depending on the type.
For example: For integer, char and string variables a value copy is what most people expect. For files you don't expect the whole file to be copied with an assignment, therefore a reference copy seems appropriate.
And by the way: Although it is always stated that in object oriented languages everything is done with methods, this is just not true. Besides statements and operators in C++ and Java which are special even Smalltalk treats the assignment and the comparison special. Seed7 does not have such special treatment for the assignment and the comparison operators.
Seed7 does not need constructors, but you can define normal functions which create a new value in a similar way as constructors do it.
Seed7 uses a special create statement ( ::= ) to initialize objects. Explicit calls of the create statement are not needed.
The lifetime of an object goes like this:
The first three steps are usually hidden in the declaration statement.
Seed7 allows defining functions (procedures and statements) without corresponding class. If this is not desired Seed7 uses a special parameter, the 'attr' (attribute) parameter, to archive the functionality of static methods (elsewhere named class methods) in a more general way. How a static method is declared is shown in the following example:
const func integer: convert_to (attr integer, in char: ch) is func result var integer: converted is 0; begin converted := ord(ch); end func;
The function 'convert_to' can be called as
number := convert_to(integer, 'a');
Since the result of a function is not used to determine an overloaded function, this is sometimes the only way to use the same function name for different purposes as in:
ch := convert_to(char, 1); stri := convert_to(string, 1); ok := convert_to(boolean, 1); num := convert_to(typeof(num), 1);
Attribute parameters allow a function to be attached to a certain type. But this concept is much more flexible than static methods (or class methods). A function can also have several 'attr' parameters and 'attr' parameters can be at any parameter position (not just the first parameter). Furthermore the type can be the result of a function as for example typeof(num).
The generics (templates) of Ada, C++ and Java use special syntax. In Seed7 you get this functionality for free without special syntax or other magic.
Generally all Seed7 functions can be executed at compile time or at runtime. The time of the function execution depends on the place of the call. Declarations are just a form of statement and statements are a form of expression. A Seed7 program consists of a sequence of declarations (expressions), which are executed one by one at compile time. This expressions can also invoke user defined functions.
A function body can contain declaration statements. When such a function is executed at compile time, it defines things that are part of the program. It is an error to execute such a function at runtime.
Seed7 uses the word template to describe a function which is executed at compile time and declares some things while executing (at compile time). Naturally a template function can have parameters. Especially types as parameters are useful with template functions. That way a template function can declare objects with the type value of a parameter.
It is necessary to call template functions explicit. They are not invoked implicit as the C++ template functions. The explicit calls of template functions make it obvious what it is going on. This way the program is easier to read.
Yes, the library progs.s7i defines the type program, which describes a Seed7 program. The functions parseFile, and parseStri can be used to parse a file respectively string. The function execute can be used to execute a program. E.g.:
$ include "seed7_05.s7i"; include "progs.s7i"; const proc: main is func local var program: aProg is program.value; begin if length(argv(PROGRAM)) >= 1 then aProg := parseFile(argv(PROGRAM)[1]); if aProg <> program.value then execute(aProg, argv(PROGRAM)[2 ..]); end if; end if; end func;
Yes, but you cannot access the AST of the program that currently runs. Instead you can parse a program and access its AST. The functions parseFile, and parseStri return a program object. The type program provides access to an enriched AST, the call-code. You can get the list of globally declared objects as ref_list. A ref_list is a list of references to objects. The type reference describes a reference to an object. The program below writes the names of all global objects in the program panic.sd7:
$ include "seed7_05.s7i"; include "progs.s7i"; const proc: main is func local var program: aProg is program.value; var reference: aRef is NIL; begin aProg := parseFile("panic.sd7"); if aProg <> program.value then for aRef range globalObjects(aProg) do writeln(str(aRef)); end for; end if; end func;
Historic compilers used fixed size memory areas to store the data of the compiled program. Limitations like source line length, identifier length, string length or number of nesting levels can be found in language manuals. If you reach such a limit an otherwise correct program will not compile. In Seed7 restrictions of other languages have been removed:
Undefined behavior is a term used in the language specification of C and in other programming languages. Undefined behavior usually means that the behavior of the program is unpredictable. In C dividing by zero, accessing an array out of bounds, dereferencing NULL or a signed integer overflow all triggers undefined behavior. Seed7 has a well defined behavior in all situations. Even in situations where the language specification of C would refer to undefined behavior.
Memory safety is the state of being protected from various software bugs and security vulnerabilities when dealing with memory access. This means that in all possible executions of a program, there is no access to invalid memory. The violations include:
In Seed7 there is no possibility to access memory outside of the defined datatypes. For all accesses to containers like array and string the indices are checked to be inside the allowed range. In Seed7 there are no pointers that can access arbitrary memory areas. All computations of memory sizes are protected against integer overflow.
Yes, Seed7 has exceptions which are similar to Ada exceptions. In chapter 16.3 (Exceptions) of the manual you will find a detailed description of the Seed7 exceptions. The use of exceptions also improves readability. E.g.:
doA(); doB(); doC();
In this example the normal flow of control can be seen easily. If doA(), doB() or doC() trigger an exception the program is terminated. The program is safe without the need to do something.
Let's assume that exceptions are not supported and that the functions doA(), doB() and doC() will return error codes. In C you can ignore function results, so this would be legal C code. But in this case the code is unsafe since the error codes get ignored. In a language without exceptions, it is necessary to change the code to check for errors. E.g.:
if (doA() == ERRORVALUE_A) { ... handling of errors triggered by doA() ... } else if (doB() == ERRORVALUE_B) { ... handling of errors triggered by doB() ... } else if ((errorVar = doC()) == ERROR_X || errorVar == ERROR_Y) { ... handling of errors triggered by doC() ... } else { ... code that follows doC() ... }
This can lead to horrible code where it is easy to overlook a bug.
If an exception is not caught the program is terminated and the s7 interpreter writes a stack trace:
*** Uncaught exception NUMERIC_ERROR raised with {integer: <SYMBOLOBJECT> *NULL_ENTITY_OBJECT* div fuel_max } Stack: in (val integer: dividend) div (val integer: divisor) at integer.s7i(95) in init_display at lander.sd7(840) in setup at lander.sd7(909) in main at lander.sd7(1541)
This stack trace shows that a div operation causes a NUMERIC_ERROR (probably a division by zero) in line 840 of the file lander.sd7. A short examination in lander.sd7 shows that an assignment to 'fuel_max' was commented out to show how stack traces work.
A compiled program creates a much shorter crash message:
*** Uncaught exception NUMERIC_ERROR raised at tmp_lander.c(764)
In this case the mentioned file name and line number refers to the temporary C file or the Seed7 runtime library. To get useful information there are two possibilities:
If s7c is called with the option -g it instructs the C compiler to generate debugging information. This way a debugger like gdb can run the program and provide information. The option -e tells the compiler to generate code which sends a signal, if an uncaught exception occurs. This option allows debuggers to handle uncaught Seed7 exceptions. Note that -e sends the signal SIGFPE. This is done even if the exception is not related to floating point operations.
Chapter 16.5 (Stack trace) of the manual contains a detailed description how to debug compiled Seed7 programs.
There is no return statement in Seed7. Instead there is a return construct that can be used to declare a function:
const func boolean: flipCoin is return rand(FALSE, TRUE);
This is a shortcut for a function declaration with a result variable:
const func boolean: flipCoin is func result var boolean: coinState is FALSE; begin coinState := rand(FALSE, TRUE); end func;
The return above is not a statement but an alternate possibility to declare a function. This alternate function declaration differs from the normal function declaration that uses:
func ... end func;
Using return as statement triggers an error.
const func boolean: flipCoin is func result var boolean: coinState is FALSE; begin return rand(FALSE, TRUE); # THIS WILL NOT WORK end func;
The return construct of Seed7 is not comparable to the return statements of other programming languages. Since no return statement exists, it is not possible to leave a function from the middle of a loop (except when exceptions are used).
Just like goto statements, break and continue violate the concept of structured programming. A programmer should imagine loops as:
while primaryCondition and loopCount <= 1000 and seconds <= 3600 do if data <> "undefined" and not doSkip then do_something_useful; end if; end while;
instead of:
while primaryCondition do if loopCount > 1000 then break; if seconds > 3600 then break; if someData = "undefined" then continue; if doSkip then continue; do_something_useful; end while;
A goto or anything like it compromises readability. Non-structured statements are frequently used as shortcuts to avoid restructuring the program's flow. Programmers should overcome the temptation to introduce break or continue. Usually, this is a sign that the code is too complex and should be refactored. Seed7 provides many loops that help in this regard.
Seed7 can define the syntax and semantics of all structured statements easily. E.g.: It is possible to define a structured loop statement with an exit in the middle. This can be used with:
loop ch := getc(inFile); until ch = '\n' do stri &:= ch; end loop;
Instead of insisting on break and continue, it would make sense to propose structured statements that can replace them.
The break and continue statements are often seen as a trick to get more performance at the expense of readability. These performance wins are questionable. Given todays optimization techniques, the compiler might generate the same machine code from clean source code without break and continue.
For the reasons stated above, and to promote structured programming, break, continue and goto are not supported.
The context of break and continue determines what they do. In this regard, they are not statements on their own but part of a surrounding statement. The surrounding statement defines what break and continue do. Every time a programmer defines a new statement, it would be necessary to specify the behavior of break and continue. This could be done by specifying labels (which do not exist in Seed7):
const proc: loop (in proc: statements) end loop is func begin repeat continueLabel: statements; until FALSE; breakLabel: end func;
So labels and goto would be needed to introduce break and continue. Beyond that, all user defined loops would need to consider them.
Since break and continue are not structured statements, there is no straightforward way to implement them. However, they could be implemented with exceptions. These exceptions must be caught by the loop statement. In the example below, a special loop statement is introduced to catch these exceptions:
syntax expr: .loop.().end.loop is -> 25; const EXCEPTION: DO_BREAK is enumlit; const EXCEPTION: DO_CONTINUE is enumlit; const proc: break is return raise DO_BREAK; const proc: continue is return raise DO_CONTINUE; const proc: loop (in proc: statements) end loop is func local var boolean: exitLoop is FALSE; begin repeat block statements; exception catch DO_BREAK: exitLoop := TRUE; catch DO_CONTINUE: noop; end block; until exitLoop; end func;
The scanner (tokenizer) uses simple hard coded rules to read tokens. Whitespace and comments are skipped by the scanner and identifiers are looked up in a table of defined symbols.
Based on the scanner the syntax analysis uses a recursive descent LL(1) parser. This means that a lookup of one symbol is used to do syntactic decisions. The rules for parsing parentheses, call expressions and dot expressions are hard coded. For all other expressions the recursive descent parser is data driven. The data which drives the parser is actually a syntax description tree. Syntax descriptions like
$ syntax expr: .while.().do.().end.while is -> 25;
are used to create the syntax description tree. The result of the syntax analysis is an abstract syntax tree (AST).
The AST is processed again to add semantic information. All things of the program that have been defined and that are currently available are maintained in a dictionary. For overloaded functions and statements this dictionary has the form of a tree. The expressions from the AST are matched with the dictionary. If a match fails, because a corresponding declaration is not found, you will get an error like:
*** chkloop.sd7(35):52: Match for {while "X" do {1 + 2 } end while } failed
If all expressions are found in the dictionary the matching process leads to an enriched AST, the call-code. Call-code can be executed by the interpreter. Alternatively the compiler can generate C code from it.
Traditionally C source files are compiled separately into object files. These object files are later linked together into one executable file. Optimizations regarding two object files cannot be done. Link time optimization (LTO) also allows these optimizations. Gcc and clang support LTO, by writing their intermediate representations to the object files. This way interprocedural optimizations can be done when the object files are linked. C compiler, linker and archiver need to support LTO.
When Seed7 is compiled the program chkccomp.c checks if all involved components (C compiler, linker and archiver) support LTO. Currently this check is only done for gcc and clang.
The Seed7 compiler supports the option -flto, which triggers the necessary steps to do LTO. If LTO is not supported the option -flto has no effect.
Basically compiling to a dll/so would be possible. But there are obstacles. Regarding libraries, the approach used by Seed7 and other languages differs considerably. Seed7 libraries can be included directly, while the usual dll/so needs an additional header file which desribes the interfaces of the dll/so. The Seed7 overloading does not use name mangling and a possible name mangling of Seed7 would be incompatible to the ones used by other languages. Fundamental types like string, bigInteger, array, hash and file could not be used directly from other languages. When a Seed7 dll/so is only used by Seed7, there would still be the name mangling and the header file issue. Compiling to a dll/so is not high on the priority list, but if someone implements it, it will be merged.
Include libraries with absolute path (an absolute path starts with a forward slash) are only searched at the specified place. All other include libraries are searched in several directories. This is done according to a list of library directories (a library search path). The directories of the list are checked one after another for the requested include file. As soon as the include file is found the search is stopped and the file is included. The following directories are in the list of library directories:
Seed7 interpreter and compiler (s7c) use the same list of library
directories (the same library search path). When Seed7 is compiled from
source code both (interpreter and compiler) will find the Seed7 include
files automatically. Interpreter and compiler from the binary release will
only find library include files when the path
The directory of the predefined include libraries is hard-coded in the interpreter.
This information is determined when the Seed7 interpreter is compiled. The command
make depend writes a line, which defines the C preprocessor variable
SEED7_LIBRARY, to the file
#define SEED7_LIBRARY "/home/abc/seed7/lib"
The preprocessor macro SEED7_LIBRARY is used by the function init_lib_path(), which
is defined in
Interpreter and compiler use the same strategy to determine the directory with predefined include libraries.
The instructions how to compile the interpreter state that you need a makefile that is specific for your combination of operating system, make utility, C compiler and shell. When you use the command
make depend
your specific makefile writes three configuration files:
config file | included by | copied to |
---|---|---|
chkccomp.h | chkccomp.c | |
base.h | chkccomp.c | version.h |
settings.h | version.h |
These files contain C preprocessor macros with configuration values that are specific for the OS and the C compiler. The command make depend also compiles chkccomp.c. This program includes base.h and chkccomp.h. After the compilation chkccomp is executed with:
./chkccomp version.h
When chkccomp runs it copies the files base.h and settings.h to version.h. Then it tests the properties of the OS and the C compiler with various small test programs. The results of these tests are also written to version.h.
Afterwards the program setpaths.c is compiled and then executed with:
./setpaths "S7_LIB_DIR=$(S7_LIB_DIR)" "SEED7_LIBRARY=$(SEED7_LIBRARY)" >> version.h
This appends further information to version.h (which includes the absolute path to the seed7 directory). The environment variables S7_LIB_DIR and SEED7_LIBRARY allow the specification of the final path to the Seed7 directory. If the seed7 directory will not move afterwards these variables can be left empty.
The Seed7 compiler needs detailed information about the C compiler and its runtime library. This information is created when the Seed7 interpreter is compiled. The command make depend compiles and executes the program "chkccomp.c", which writes configuration values as C preprocessor macros to version.h. E.g.:
#define CC_SOURCE_UTF8 #define SEED7_LIB "seed7_05.a" #define DRAW_LIB "s7_draw.a" #define CONSOLE_LIB "s7_con.a" #define DATABASE_LIB "s7_db.a" #define COMP_DATA_LIB "s7_data.a" #define COMPILER_LIB "s7_comp.a" #define S7_LIB_DIR "/home/abc/seed7/bin"
Many of the preprocessor macros of "version.h" are determined with test programs. E.g.:
#define RSHIFT_DOES_SIGN_EXTEND 1 #define TWOS_COMPLEMENT_INTTYPE 1 #define ONES_COMPLEMENT_INTTYPE 0 #define LITTLE_ENDIAN_INTTYPE 1
The preprocessor macros used by
The Seed7 compiler uses the runtime libraries SEED7_LIB, CONSOLE_LIB, DRAW_LIB,
COMP_DATA_LIB and COMPILER_LIB in the directory S7_LIB_DIR when it links object
files to an executable. Config values like RSHIFT_DOES_SIGN_EXTEND,
TWOS_COMPLEMENT_INTTYPE and LITTLE_ENDIAN_INTTYPE are used to control the kind
of C code produced by the Seed7 compiler. The library cc_conf.s7i also
provides access to config values that do not come from "version.h", but are
defined in
#define WITH_STRI_CAPACITY 1 #define ALLOW_STRITYPE_SLICES 1
This configuration values describe data structures and implementation strategies used by the Seed7 runtime library. They do not depend on the C compiler and its runtime library, but they may change between releases of Seed7.
A binary Seed7 package needs to install four groups of files:
The table below shows the suggested directories for Linux/Unix/BSD:
Directory | Macro | Group of files |
---|---|---|
/usr/bin | - | Executables (s7 + s7c) |
/usr/lib/seed7/lib | SEED7_LIBRARY | Seed7 include libraries |
/usr/lib/seed7/bin | S7_LIB_DIR | Static libraries |
The macros must be defined, when the interpreter is compiled. This can be done by calling make depend with:
make S7_LIB_DIR=/usr/lib/seed7/bin SEED7_LIBRARY=/usr/lib/seed7/lib depend
Afterwards the interpreter can be compiled with 'make' and the Seed7 compiler can be compiled with 'make s7c'. This three make commands can be combined to
make S7_LIB_DIR=/usr/lib/seed7/bin SEED7_LIBRARY=/usr/lib/seed7/lib depend s7 s7c
Alternatively the Seed7 compiler can be compiled as post-install step.
This requires that
s7 s7c -O2 s7c
It is also possible to compile the Seed7 compiler in the build directory. In this case it is necessary to specify the directories SEED7_LIBRARY and S7_LIB_DIR with the options -l and -b:
./s7 -l ../lib s7c -l ../lib -b ../bin -O2 s7c
Compiling s7c with a make command should be preferred.
The Seed7 runtime library provides the possibility to connect to several databases. During the compilation of the Seed7 interpreter the program "chkccomp.c" searches for the availability of database connector libraries and the corresponding database include files (*.h header files). The connector libraries are provided by the database and can be static or dynamic. Often a connector library also provides a database include file (column DB include below). If the database include file is missing Seed7 uses its own database include file (the one from the column Other *.h below). The names of the connector libraries can be specified in the makefile (macro definitions can be written to "chkccomp.h"). The names of the macros for the connector library names are provided in the columns Static lib macro and Dynamic lib macro below. The list below lists the currently supported databases:
Database | DB include | Other *.h | DB driver | Static lib macro | Dynamic lib macro |
---|---|---|---|---|---|
MySQL | mysql.h | db_my.h | sql_my.c | MYSQL_LIBS | MYSQL_DLL |
MariaDB | mysql.h | db_my.h | sql_my.c | MYSQL_LIBS | MYSQL_DLL |
SQLLite | sqlite3.h | db_lite.h | sql_lite.c | SQLITE_LIBS | SQLITE_DLL |
PostgreSQL | libpq-fe.h | db_post.h | sql_post.c | POSTGRESQL_LIBS | POSTGRESQL_DLL |
Oracle | oci.h | db_oci.h | sql_oci.c | OCI_LIBS | OCI_DLL |
Firebird | ibase.h | db_fire.h | sql_fire.c | FIRE_LIBS | FIRE_DLL |
Interbase | ibase.h | db_fire.h | sql_fire.c | FIRE_LIBS | FIRE_DLL |
DB2 | sqlcli1.h | db_odbc.h | sql_db2.c | DB2_LIBS | DB2_DLL |
Informix | infxcli.h | db_odbc.h | sql_ifx.c | INFORMIX_LIBS | INFORMIX_DLL |
SQL Server | sql.h | db_odbc.h | sql_srv.c | SQL_SERVER_LIBS | SQL_SERVER_DLL |
ODBC | sql.h | db_odbc.h | sql_odbc.c | ODBC_LIBS | ODBC_DLL |
TDS | sybdb.h | db_tds.h | sql_tds.c | TDS_DLL |
If no static library is provided in the makefile (by writing it to "chkccomp.h") a default value is used by "chkccomp.c". This default value differs between Linux, macOS and Windows:
Static lib macro | Linux connector lib | macOS connector lib | Windows connector lib |
---|---|---|---|
MYSQL_LIBS | -lmysqlclient | -lmysqlclient | mariadbclient.lib or mysqlclient.lib |
SQLITE_LIBS | -lsqlite3 | -lsqlite3 | sqlite3.lib |
POSTGRESQL_LIBS | -lpq | -lpq | libpq.lib |
OCI_LIBS | -lclntsh | -lclntsh | |
FIRE_LIBS | -lfbclient | -lfbclient | fbclient.dll or gds32.dll |
DB2_LIBS | libdb2.a | libdb2.a | db2cli.lib |
INFORMIX_LIBS | iclit09b.a | iclit09b.a | iclit09b.lib |
SQL_SERVER_LIBS | |||
ODBC_LIBS | -lodbc | -liodbc | -lodbc32 or odbc32.lib |
If no dynamic library is provided in the makefile (by writing it to "chkccomp.h") a default value is used by "chkccomp.c". This default value differs between Linux, macOS and Windows:
Dynamic lib macro | Linux connector lib | macOS connector lib | Windows connector lib |
---|---|---|---|
MYSQL_DLL | libmysqlclient.so | libmysqlclient.dylib | libmariadb.dll or libmysql.dll |
SQLITE_DLL | libsqlite3.so | libsqlite3.dylib | sqlite3.dll |
POSTGRESQL_DLL | libpq.so or libpq.so.5 | libpq.dylib | libpq.dll |
OCI_DLL | libclntsh.so | libclntsh.dylib | oci.dll |
FIRE_DLL | libfbclient.so | libfbclient.dylib | fbclient.dll or gds32.dll |
DB2_DLL | libdb2.so | libdb2.dylib | db2cli.dll |
INFORMIX_DLL | iclit09b.so | iclit09b.dylib | iclit09b.dll |
SQL_SERVER_DLL | libtdsodbc.so | libtdsodbc.dylib | sqlsrv32.dll |
ODBC_DLL | libodbc.so | libiodbc.dylib | odbc32.dll |
TDS_DLL | libsybdb.so | libsybdb.dylib | sybdb.dll |
For Oracle it is assumed that the environment variable ORACLE_HOME has been set. Static libraries are preferred over dynamic libraries. When no connector library can be found a dynamic library is expected. This way the database can be connected if a dynamic database connector library is installed later.
For a Seed7 package this means: During the compilation of Seed7 the development packages of all supported databases should be installed. This way the original headers are used instead of the headers provided by Seed7. When dynamic database connector libraries are used the Seed7 package must require this packages.
Depending on the configuration the database connector library is linked statically or dynamically. If a dynamic database connector library cannot be found at runtime the function openDatabase raises the exception DATABASE_ERROR.
No, the analyze phase of the Seed7 interpreter produces call-code which consists of values and function calls. This call-code is just handled in memory and never written to a file. After the analyze phase the call-code is interpreted.
The analyzer reads successive expressions. The expressions are read with a table-driven LL(1) recursive descent parser. The parser is controlled by Seed7 syntax definitions. The parser calls a scanner, which skips whitespace and reads identifiers and literals. Each parsed expression is searched in the internal database of defined objects. This search process is called matching. The matching resolves overloaded functions and generates call-code for the parsed expression. Call-code uses a data structure which is similar to S-Expressions. The analyzer executes the call-code of the parsed and matched expressions. Normally parsed and matched expressions represents declaration statements. Executing a declaration statement adds new defined objects to the internal database.
Every function with call-by-name parameters is searched for recursive calls. If no recursive call of the function is present it can be implemented with code inlining. In this case every call of the function is inlined and the actual call-by-name parameters replace all occurrences of the formal call-by-name parameter in the function body.
If a function cannot be implemented with code inlining (recursive calls occur) pointers to a closure structure are used as formal call-by-name parameters. This closure structure contains a function pointer and a structure which represents the environment of the closure. If a formal call-by-name parameter is used, the function of the closure structure is called with a pointer to the closure environment as parameter.
When a function with call-by-name parameters is called the following things are done: For every actual call-by-name parameter a closure structure with the function pointer and the closure environment structure is generated. An actual function representing the closure code is generated as well. Before a function with a call-by-name parameter is called a closure structure variable is initialized. This includes initializing the function pointer and the environment data of the closure structure variable. Finally a pointer to the closure structure variable is used as actual call-by-name parameter.
Actions are used to call a corresponding C function in the interpreter. For example:
The action "INT_ADD" corresponds to the function 'int_add' in the file
Chapter 14 (Primitive actions) of the manual contains a detailed description of the primitive actions. In the interpreter all action functions get the parameters as list. The action functions take the parameters they need from the list, perform the action and deliver a result.
The syntax and semantics of Seed7 are defined in the library seed7_05.s7i. So when the interpreter or compiler starts reading a Seed7 program, it knows almost nothing about Seed7. No statements, functions, operators, types or variables are predefined. All these things come from the seed7_05.s7i library. Without seed7_05.s7i, there are just a few hard-coded things such as comments and literals. Among the hard-coded things are include statements which are introduced with a dollar sign. For that reason a Seed7 program needs a
$ include "seed7_05.s7i";
at the beginning. The seed7_05.s7i library starts with including the syntax.s7i library. The file syntax.s7i contains many $ commands. First the type type is defined. Declarations of other types, system variables and syntax descriptions of operators and statements follow. At several places the $ is used to force the analyzer to use a hard-coded expression recognition instead of the configurable one. After finishing the inclusion of syntax.s7i, the file seed7_05.s7i contains some $ declarations until the const declaration statement is established. From that point onward almost no $ statements are needed.
Among other things the seed7_05.s7i library defines include statements and syntax statements, that work without $.
The number 05 is actually a 'branch info'. As if C had headers like
<stdlib_c78.h> /* For K&R C programs */ <stdlib_c89.h> /* For ANSI C */ <stdlib_c99.h> /* For C99 */
and your program must include one of these three headers as first include file (Other include files have no version/branch info in the name). That way nobody is forced to upgrade an old program (to get no warnings or to make it compile). You can leave your old K&R program from 1980 as is. If you decide to rewrite your K&R program to use prototypes, you change the <stdlib...> include file as well.
Programming languages change over long time periods. This results in different language standards. Seed7 tries to address this problem from the beginning. Since most of the Seed7's constructs (statements, operators, types, ... ) are defined in seed7_05.s7i this is the right place to do it.
Theoretically yes. In practice there would be several problems. For example:
But basically booting various languages was one of the goals of the extensible programming language Seed7 and the s7 interpreter.
In practice it turned out to be a better approach to steal concepts from other programming languages and to integrate them in Seed7 than to split the development in different branches.
The capability to boot a language can be used to allow slightly different future versions of Seed7 to coexist with the current version. This is also the reason why the file seed7_05.s7i contains a version number (05).