# 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[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: ```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 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: ```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. ## 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: ```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 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: ```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 | 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: ```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.