287 lines
11 KiB
Markdown
287 lines
11 KiB
Markdown
# 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:
|
|
```pascal
|
|
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:
|
|
```pascal
|
|
var s:string;
|
|
c:char;
|
|
|
|
...
|
|
|
|
s := 'Test';
|
|
|
|
for c in s do
|
|
writeln(ord(c));
|
|
|
|
...
|
|
```
|
|
|
|
Array Example:
|
|
```pascal
|
|
var a:array [1..3] of integer;
|
|
i:integer;
|
|
|
|
...
|
|
|
|
a[1] := 3; a[3] := 2; a[5] := 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:
|
|
```pascal
|
|
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 _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:
|
|
```pascal
|
|
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.
|
|
|
|
## 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_ and _seek_
|
|
- 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:
|
|
```pascal
|
|
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, 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.
|
|
|
|
The function _ErrorStr_ from the standard library takes an error code as an argument and returns the corresponding textual description as a string.
|
|
|
|
Example:
|
|
```pascal
|
|
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 | 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:
|
|
```pascal
|
|
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:
|
|
```pascal
|
|
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:
|
|
```pascal
|
|
{$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:
|
|
```pascal
|
|
{$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:
|
|
```pascal
|
|
{$!}uses math,crt;
|
|
```
|
|
In this example, the line is ignored by the Tridora compiler but FPC will parse it.
|