12 KiB
The Mostly Missing Tridora Pascal Programming Guide
Strings
Strings are dynamically sized arrays of bytes. They are declared with a maximum size and carry runtime information about the current size and the maximum size. Passing strings of different maximum sizes as parameters is possible and safe because of the maximum size information. The size field uses the integer type, so a string theoretically can be 2GB in size.
When allocating a pointer to a string with new, you can specify a maximum size that is being allocated, so you can allocate less memory than the string type specifies. The string header fields will reflect the allocated size.
Example:
type strPtrType: ^string[1024];
var p: ^strPtrType;
new(strPtr, 80);
When using new without the additional parameter, this would always allocate memory for 1024 bytes. If you at some point know that a string will be smaller, you can save memory by allocating only the amount needed.
Note that the char type is a 32-bit type, and strings contain bytes. Non-ASCII characters in strings are expected to be UTF-8-encoded. Although a char variable could in theory hold a 32-bit Unicode character, this is not supported by the standard library.
When indexing a string, the result is a char type with bits 31 to 8 set to zero. When assigning a char to an indexed string element, the destination byte is set to bits 7 to 0 from the char variable. Bits 31 to 8 are ignored.
For-in Loop
The for-in-loop is supported, which allows iterating over a string or a linear array of scalar variables. It is more efficient than using a for loop for indexing a string or an array, because no bounds checks are needed on each iteration.
String Example:
var s:string;
c:char;
...
s := 'Test';
for c in s do
writeln(ord(c));
...
Array Example:
var a:array [1..3] of integer;
i:integer;
...
a[1] := 3; a[2] := 2; a[3] := 1;
for i in a do
writeln(i);
...
Sets
The maximum number of elements in a set is 32. This makes a SET OF CHAR impossible.
When using a SET OF CHAR in other Pascal dialects, it is most often used with a set literal like this:
var c:char;
read(c);
if c in [ 'y', 'n' ] then
....
In Tridora Pascal, this syntax also works because [ 'y', 'n' ] will not be treated as a set literal, but as an array literal.
The in operator also works for linear arrays, so the above if statement will have the same result.
Note that the array in operator will be more inefficient for larger ranges (i.e. 'A'..'z'), but more efficient for sparse sets (i.e. 'A','z').
A set literal can only appear on the right side of an assignment to a set variable, or when passing an argument of a set type to a procedure/function.
Example:
program settest;
type weekday = (Mon,Tue,Wed,Thu,Fri,Sat,Sun);
days = set of weekday;
var s:days;
d:weekday;
begin
s := [Sat,Sun]; (* set literal *)
d := Sun;
if d in [Sat,Sun] then (* array literal, IN operator for arrays *)
writeln('weekend');
if d in s then (* IN operator for sets *)
writeln('also weekend');
end.
Break and Continue Statements
Wirth Pascal has no statement to exit a loop other than the loop condition. Other Pascal dialects have the break and continue statements to exit the loop or skip to the next iteration.
Tridora-Pascal only supports the break statement at the moment.
Exit Statement
The exit statement can be used to exit the current procedure or function. If it is a function, the return value of the function is undefined if exit is used before a return value is assigned.
Dynamic Memory Allocation
Memory allocation generally works as expected with the new and dispose special procedures. The variant of new with two parameters that is specified in Wirth Pascal is not supported (partial allocation of a variant record). Instead, there is a variant of new that has a second parameter for allocating strings (see above).
If heap allocation fails, new does not return and instead causes a runtime error. To avoid this, a different special procedure called newOrNil can be used. This procedure sets the pointer variable to niL if heap allocation fails.
The function MemAvail returns the number of free bytes on the heap. It does not guarantee that this amount of memory can be allocated with new, because heap space can be fragmented. The function MaxAvail, which exists in some versions of Turbo Pascal and returns the size of the largest contiguous block of available heap memory, is not (yet) implemented.
I/O
I/O handling in Tridora Pascal is mostly compatible with other Pascal dialects when reading/writing simple variables from/to the console. There are big differences when opening/reading/writing files explicitly.
Tridora's Solution
The original Wirth Pascal has very limited file I/O capabilities. Therefore, most Pascal dialects have their own, incompatible extensions for handling file I/O.
In this tradition, Tridora Pascal obviously needs to have its own incompatible file handling.
The goal is to mostly ignore the stuff from other Pascal dialects and instead use something more intuitive and more recognizable from other programming languages:
- files are sequences of bytes
- files are used with open, read/write, close
- files can be opened in different modes
The implementation also has the following properties:
- file variables have the type file
- the open procedure has the file variable, the file name and the mode as parameters
- read/write do ASCII conversion on scalar variables, records and arrays are processed as binary
- enums and booleans are treated as integers
- readln/writeln operate as expected, that is, they perform read/write and then wait for/write a newline sequence
- other file operations available are eof, eoln, seek and filepos
- for error handling there is a function IOResult
- terminating the program without calling close on open files will lose data
Differences from other Pascal dialects are:
- there are no FILE OF types
- no get, put, reset, rewrite, assign
Opening Files
There are five modes for opening files which (hopefully) cover all common use cases:
- ModeReadonly: file is opened only for reading, file must exist
- ModeCreate: file is opened for reading and writing, file must not exist
- ModeModify: file is opened for reading and writing, file must exist
- ModeOverwrite: file is opened for reading and writing, file will be truncated
- ModeAppend: file is opened for writing, data is always written to the end of the file
Example:
var f:file;
c:char;
a,b:integer;
open(f, 'myfile.text', ModeReadonly);
read(a,b,c);
close(f);
Error Handling
When an I/O error occurs, the IOResult function can be called to get the error code. Unlike TP, the IOResult function requires a file variable as a parameter. When you call IOResult, an error that may have occurred is considered to be acknowledged. If an error is not ackowledged and you do another I/O operation on that file, a runtime error is thrown.
That means you can either write programs without checking for I/O errors, while resting assured that the program will exit if an I/O error occurs. You can also choose to check for errors with IOResult if you want to avoid having runtime errors.
If an I/O error occurs on a file, it is then considered closed. Closing a file in this state, or a file that has been closed normally, will cause a runtime error.
The function ErrorStr from the standard library takes an error code as an argument and returns the corresponding textual description as a string.
Example:
procedure tryToReadFile;
var f:file;
begin
open(f, 'myfile.text', ModeReadonly);
if IOResult(f) <> IONoError then
begin
writeln('Could not open file: ', ErrorStr(IOResult(f)));
exit;
end;
...
close(f);
end;
Possible error codes from IOResult are:
| Value | Constant Identifier | Description | Notes |
|---|---|---|---|
| 0 | IONoError | no error | |
| 1 | IOFileNotFound | file not found | |
| 2 | IOVolNotFound | volume not found | |
| 3 | IOPathInvalid | path invalid | |
| 4 | IOFileExists | file already exists | |
| 5 | IOFileClosed | file closed | |
| 6 | IOSeekInvalid | seek invalid | |
| 7 | IONoSpace | no space | |
| 8 | IOReadOnly | file is readonly | |
| 9 | IOInvalidOp | invalid operation | |
| 10 | IOInvalidFormat | invalid format | when parsing numbers with read |
| 11 | IONoMem | not enough memory | heap allocation failed inside the standard library, e.g. open() |
| 12 | IOUserIntr | interrupted by user | program terminated by ^C, not visible from IOResult |
Read, Readln and Line Input
In Turbo Pascal, using read (and readln) from the console always waits until a complete line has been entered. Tridora-Pascal handles this like UCSD-Pascal, that is, it immediately returns as soon as the input data is enough to read that variable.
This means, for example:
- for char, read returns after reading one character (i.e. typing a single key)
- for integer or real, read returns after the first non-parsable character, which will then stay in the input buffer
- for string, read returns after a newline (CR) has been entered, which will not be part of the string
So to wait for a single keystroke, you can use:
var c:char;
...
writeln('Press any key...');
read(c);
...
Labels
In Wirth Pascal, labels must be numbers. Other Pascal dialects also allow normal identifiers (i.e. start with letters then letters and digits).
Tridora-Pascal only allows identifiers as labels.
Compiler Directives
Tridora-Pascal understands a small number of compiler directives which are introduced as usual with a comment and a dollar-sign. Both comment styles can be used. All directives use a single, case-sensitive letter.
Include (I)
The Include directive reads another source file at compile time. Includes can be nested (that is, an included file can include other files). After the directive letter I, a string must be specified that indicates the file name.
Example:
program includetest;
{$I 'morestuff.pas'}
...
The compiler looks for the file first on the default volume, then on the SYSTEM volume. The file name must be enclosed in single quotes.
If the file name contains a + character, the string 'tdr' is inserted after that + character. This is used for compiling the cross-compiler. FPC will use the original filename, Tridora Pascal will use the name with 'tdr' inserted.
Set Heap Size (H)
Sets the heap size for the compiled program. After the directive letter H, an integer must be specified that represents the heap size in kilobytes. The default value is 256.
The directive must appear before the program keyword to have an effect.
Example:
{$H 800}
program bigHeap;
...
Set Stack Size (S)
Sets the stack size for the compiled program. After the directive letter S, an integer must be specified that represents the stack size in kilobytes. The default value is 16.
The directive must appear before the program keyword to have an effect.
Example:
{$S 128}
program deepRecursion;
...
Ignore Rest of Line (!)
This directive is a hack to make the Tridora compiler ignore directives meant for other compilers. Used for compiling the cross-compiler.
This directive uses the character !. After this directive, the rest of the line is not parsed. The closing comment is still required.
Example:
{$!}uses math,crt;
In this example, the line is ignored by the Tridora compiler but FPC will parse it.