Demo IP Pascal Version

Copyright 2005 S. A. Moore


Demo "on one page"

This page contains all you need to download, install and run the current demo. This file also appears in the demo itself.


The windows XP demo self-executable:

ipdemo.exe

 


What's new

A new release was done, 1.12.00. This fixes an inadvertent problem introduced with version 1.11.00, in gralib. Please update your demo.

A major bug scrub was completed on 2005/07/12. This addresses many outstanding issues from different sources, including customer feedback, our internal lists, internal tests, and other material.

Several features were enabled or added in the new version.

1. Header file associations do the correct thing. Formerly, if the order of reset or rewrite on header files was wrong, the filenames associated would also be incorrect:

program copy(infile, outfile);

 

var infile, outfile: text;

    c:               char;

 

begin

 

   rewrite(outfile);

   reset(infile);

   while not eof(infile) do begin

 

      while not eoln(infile) do begin

 

         read(infile, c);

         write(outfile, c)

 

      end;

      readln(infile);

      writeln(outfile);

 

   end

 

end.

This program would not work because the rewrite on the second file was performed first, so:

> copy myfile yourfile

Would assign the name "myfile" to outfile, and "yourfile" to infile. Now, the order of names on the command line always match the order in the header.

2. The "update" procedure was added. This is used instead of rewrite to allow updating a non-text file. See details in the "language" section below.

3. Case statements now have an else clause.

program copy(input, output);

 

var n: integer;

 

begin

 

   repeat

 

      write('Enter number: ');

      readln(n);

      case n of

 

         1: writeln('one');

         2: writeln('two');

         3: writeln('three');

         else writeln('What was that ?')

 

      end

 

   until n < 0

 

end.

4. There now exists a shorthand for standard base 1 integer index arrays:

program test(output);

 

var s: packed array 10 of char;

 

begin

 

   s := 'hi there  ';

   write(s)

 

end.

 

This is part of an ongoing effort to align Pascal extensions with Oberon, which uses 1 based integer index arrays. The notation is the same as writing:

 

program test(output);

 

var s: packed array [1..10] of char;

 

begin

 

   s := 'hi there  ';

   write(s)

 

end.

 

The number used in the declaration is the number of elements N, which are then numbered 1..N, and the index type is integer, without subranges.

 

As with standard array index notation, any number of dimensions can be specified:

 

program test(output);

 

var s: packed array 20, 10 of char;

 

begin

 

   s[1] := 'hi there  ';

   write(s[1])

 

end.

 

5. The standard function "refer".

 

program test;

 

procedure stub(a, b: integer);

 

begin

 

   refer(a, b)

 

end;

 

begin

end.

 

Refer takes any number of parameters and marks them as referenced, so that they will not we announced as unreferenced symbols. Its is useful in various cases where you don't wish to use a symbol in the code, such as a "dummy" procedure used to placehold for later use.

 


License


This software is provided "as is," without warranty of any kind, express or implied.  In no event shall Moore/CAD be held liable for any direct, indirect, incidental, special or consequential damages arising out of the use of or inability to use this software.

No guarantee is made that we will not change the documentation, input language for IP Pascal, or methods used to run the program. Nothing in this or other documents should be construed to mean that we will update this demonstration, or insure that it is correct, or even continue to provide it in the future. Our provision of the demonstration is completely at will.

Permission is granted to anyone to use this software for any purpose, including commercial applications.

This software is not for use in life-critical applications, nor for use in building life-critical applications.

Permission is given to individuals to download and use the software. The software can be redistributed without charge, provided the software is not altered, subsetted or modified in any manner.

The user of this software agrees not to reverse engineer the product, nor modify it.

This license applies to the demonstration version of IP Pascal only, and no permission is granted for any use or distribution of the full, unlimited IP Pascal product.


Support


Please use the email address demo@moorecad.com. We get a lot of mail, using this address will allow your email to be handled faster.

All support for the demo versions is on a time permitting basis. Please note that we handle bugs with IP Pascal, problems using IP Pascal, and questions about extended features and libraries in IP Pascal. If you need help constructing a program, we recommend the Pascal newsgroups:

comp.lang.pascal.ansi-iso

comp.lang.pascal.misc

The first is for standard Pascal related questions (of which IP Pascal is certainly a standard Pascal). The second is for all Pascals, including non-standard ones. When posting there, we recommend you clearly state which Pascal you have a question about (as in "question on IP Pascal...").

We encourage student project use of IP Pascal, and the demo should be sufficient for most student projects. Please note, however, that we don't help with student projects or help with program construction. The newsgroups above are best used for this purpose.


Please Read These Notes for the Current Demo.


1.    This is a command line compiler demo. The system will generate graphical programs, but there is no IDE available at this time. You need to compile programs using a command line window, listed in Windows XP under the start menu in:

Start->All programs->Accessories->Command prompt

2.    The demo version is limited to:

200 lines of program text

10,000 total characters per file

This should be sufficient to compile small test programs, and class assignments. There is no restriction on the output from the compiler. Your code will be fully compiled to machine code for the I80386 processor.

3.    The demo version cannot compile more than one file at a time.

4.    The demo version will only compile program files. There is no ability to compile modules to be used by other programs.

5.    Several IP Pascal standard features were not ready in time for this demo. The following items are not implemented, or out of service:

Note that all of these are IP Pascal extensions, and none affect the 7185 standard status of the system, it is fully ISO 7185 compliant.

These items will be added to the demo as they become available.


Issues


The following issues exist with the current version.


Fixes


If you have a demo version that is before the date of a fix below, it is recommended that you update.

Date: 2005/07/28

New version: 1.12.00

Fault classification: Serious

Description:

The procedure and function parameter calling method was changed in version 1.11, but the overload shim for syslib wasn't changed, leading to errors when running standard file operations under trmlib and gralib.

Please note: IP Pascal won't be released component by component any more. Its assumed that you will update everything. There will be several minor changes that won't be listed in the fixes column.

Date: 2005/07/12

Component: all

New version: 1.11.00

Fault classification: Medium

Description:

 

Across the board code scrub. Header file associativity was corrected. The "update" procedure was added. Other new features. UPDATE YOUR ENTIRE IPDEMO DIRECTORY PLEASE.

 

Date: 2005/05/22

Component: parse (parser front end)

New version: 1.02

Fault classification: Medium

Description:

Due to library change, side effect was induced in parser that caused it to handle upper case incorrectly.

Date: 2005/05/01

Component: pc (compiler shell)

New version: 1.10

Fault classification: Serious

Description:

When including multiple libraries, pc can place the standard I/O library somewhere besides the front of the link list. Because the startup code is packaged into the standard I/O, this will cause a crash on running such a binary.

A word about versions

The version numbering system was changed. With all of the changes occuring here, the minor version numbers were rolling over too fast. We have implemented a new version format that includes a "build number", as follows:

1.00.00

^  ^  ^

|  |  |

|  |  +--- Build number.

|  +------ Minor version number.

+--------- Major version number.

The major version number indicates entirely new issues of software and documentation. The minor version number indicates fixes, or collections of fixes, released externally. The build number is the internal number that is incremented for each fix.

You should not see anything but zeros in the build number. When we release code, we "roll" the version numbers by incrementing the minor version number if the build numbers are non-zero.

The new format gives 100 minor version numbers before a major release is done, and gives us 100 fix numbers internally.

In addition, the version numbers have been "synced" between all components of IP. You will now see the same version on all components of the IP Pascal system. This should make it easier to report problems.


Installation Instructions


The following installation steps are needed for the demo. The release product will use an installer. Thank you for your patience.

This demo is for Windows XP only. The demo may do anything from totally fail to partially work on other versions of windows.

1.    Place ipdemo.exe  in the directory you wish to install it in. This can be the root ("\"), or any other directory. It will create a directory called "ipdemo", which contains the entire program tree. The following installation instructions will be simpler if you use root as the location.

2.    Execute the demo file. If you have virus worries, you may scan the file for viruses using your virus scanning software. This is typically done by opening the explorer view of the ipdemo.exe file, right clicking the file, and selecting "Scan for Viruses" from the menu. The demo file is (currently) a .zip format file, and so will be usable with most current .zip format programs.

2.    Add the IP Demo binary directory to your path. There are two ways to do this.

Method #1 (preferred) used to permanently put IP Pascal on your execution path:

Add the executable directory to your path. From the start button, perform:

Start->Control Panel

This will display the control panel window. At this time, you will see either a list of icons (classic folder display), or a list of common tasks (common task display). If you see icons,  double click the "system" icon at this time. If you see common tasks, double click "Performance and Maintenance" on the right side (under "pick a category"). After "Performance and Maintenance" is clicked, you should see the "system" icon at the bottom right (under "pick a task").

Now you should be in the "system Properties" menu. Click the "advanced" tab at the top. You should now see a button marked "Environment Variables" near the bottom of the window. Click that button.

Now you should see "User variables for John Doe" (with your user name), with "System variables" at the bottom. Use the scroll bar on the "System variables", and find the variable "path". Double click that, and add the following text at the END of the "Variable value" line:

;c:\ipdemo\windows\i80386\bin

Note the ";" separating the new directory from the other entries.

Click "ok" and leave the control panel windows.

You can also place the entry at the front of the path, or anywhere in the middle, if you need to have the IP programs override other programs of the same name.

Method #2 (simpler, but not permanent) used to temporarily execute IP Pascal services:

Open a command window:

Start->All programs->Accessories->Command prompt

Execute:

> cd c:\ipdemo

(Using whatever directory you installed IP into). Execute:

> setpath

This will execute setpath.bat, which will add the IP binary to your path. This will allow you to execute IP Pascal services while the command window is open.

If you have installed the IP demo in another directory besides c:\ipdemo, you will need to edit the "setpath.bat" file to reflect that.

3.    If you have installed the IP Demo in any location besides "c:\ipdemo", you will need to edit the pc control file. Edit:

c:\ipdemo\windows\i80386\bin\pc.ins

Using whatever path you installed the IP Demo to. Edit the lines for "usespath" and "exclude", and change the "c:\ipdemo" section of the paths to reflect your actual installation location.


Using the IP Pascal Compiler


Open a command window (using instructions from the installation instructions above if required).

IP Pascal uses a typical collection of compiler and linker programs. However, you can control all of the aspects of creating an executable with the program "pc", executed as follows:

> pc myprogram

To compile "myprogram.pas".

This will create the program "myprogram.exe", which can then be executed. That's all there is to it.

Compiling a program will create several files in the current directory. These files are as follows:

myprogram.int    The intermediate code file.

myprogram.obj    The object code.

myprogram.sym    The symbols.

myprogram.exe    The executable.

Any of these files can be deleted if you require.

pc is a compiler "shell", and actually executes other programs to perform the compilation, link and executable generation. You will see these other programs execute as the compilation proceeds. pc also understands the structure of the program, similar to the Unix program "make" (but without the need for a make file), and knows if the program needs to be recompiled, and what parts do, and what parts don't. Therefore, don't be surprised if you don't see it execute all phases of the compilation/link process.

If you require that pc completely recompile the program without question, execute:

pc myprogram/r

(for "/rebuild").


Using IP Pascal as a Strict ISO 7185 Level 0 Compiler


Execute the command:

> pc myprogram/s

(for "/standard"). This passthrough option will cause the compilation to accept only the original ISO 7185 language at Level 0.

If you choose to compile ISO 7185 programs WITHOUT the standard flag, this can be done with fairly low impact. You will need to observe the following extra restrictions on program source:

IP Pascal defines additional reserved words. You will need to avoid use of the following reserved words:

module     process     monitor    share      uses

private    external    forward    xor        view

fixed      class       atom       construct  destruct

is         overload    override

Note that "forward", and "external" are promoted in the general IP Pascal language from the status of ISO 7185 "directive" to "word-symbol" or reserved word. Unlike ISO 7185 Pascal, they cannot be used for other identifiers.

There exist reserved words in the IP Pascal language that are not currently used. These have no particular meaning. They may simply indicate reserved words that we wanted to make sure did not appear in user programs. IP Pascal may have additional reserved words defined in the future, or have current reserved words removed. There is no guarantee that your programs will always work unmodified, with IP Pascal.


Lets Create "hello, world"


Ok, lets take a moment and run through a sample program, using ISO 7185 standard Pascal, using some typical I/O modes in Windows. In other words, let's get you started building standard Pascal programs.

The program will be "hello.pas", and looks, of course, like:

program hello(output);

 

begin

 

   writeln('hello, world')

 

end.

Remember, being ISO 7185, "output" in the header is required for standard output.

Ok, lets edit the program:

And compile for normal command line (serial) mode:

As detailed above, several program files were created, including the final executable, hello.exe.

And it runs.

Now that was serial mode. But there are three total modes you can target in windows:

Serial mode is the old fashioned Pascal mode. You write lines of text, they appear one after the other. Terminal mode gives you the ability to control character placement on the screen in x and x, like an old fashioned glass terminal. Graphical mode is full, windowed graphical mode, with line drawing, the works.

In windows, serial programs write to the console file, which is opened by name ("con:"). Terminal mode files are done via the console interface, a set of advanced calls that manage a console buffer and its display to a console window. Graphical mode is done via the Windows GDI, which is a VERY complex windowed graphical environment.

That three very different modes, and three VERY different APIs that windows supplies. But IP Pascal has paved over these differences for you. You can send an ISO 7185 standard Pascal program to any mode Windows has with a single option. Lets try the other two modes, starting with terminal mode.

The option "/dt" means to "default to terminal mode". Because we already built the program for serial mode, and we want to rebuild it for terminal mode, we specify the "/r" or "rebuild" switch.

We compiled and ran it, but it appears no different than the other run. Well, its actually not supposed to be different, even though it uses a very different API to output to the console. Terminal mode is completely compatible with the Windows console mode programming model, so it behaves just like any other console program. To see the difference, we have to do something that only a terminal can do, such as move the cursor, place different colored text, etc.

Finally, lets do the same thing for graphical mode.

Ok, this time, we built and executed "hello", but it came up in another window. That's a graphical window, there is no little "c:\" icon up in the top left, and no "console" in the title. The font is also different. IP Pascal created a full graphical window for you, and painted the text "hello, world" in it for you.

Also note the "finished - hello" in the title bar. What is this ? Well, hello does not know its running in a Window. If we terminated the window when hello finished, the window would be put up, written to, then closed almost immediately. You would only see a flash on the screen as the window came and went.

To prevent this, IP Pascal automatically "holds" the window until you have had a chance to read the output. IP Pascal knows to hold the window, because the program hello did nothing to demonstrate that it was aware that it was running as an advanced, windowed program. It didn't execute a call to check for any of the advanced input that IP Pascal provides. So IP Pascal assumes that it is an older program, and holds the Window until you can read the result. It is closed by you in the standard way, by clicking on the "X" in the upper right hand corner. It can also be terminated by hitting Ctrl-C on the keyboard while the window is selected. Ctrl-C is a valid termination character.

That's it. To truly take advantage of each of the new modes, terminal and graphical, you would need to use some of the calls we will introduce later in this document. But until then, realize that any ISO 7185 standard Pascal program can be brought forward into the advanced modes that IP Pascal provides, and do so without changing the program source.

Oh, by the way. If you happen to notice that we used some unusual commands like "list" and "ed", these are all programs built with IP Pascal. "ed" is a small editor that runs in terminal mode.


The IP Pascal Language


 IP Pascal complies with the complete ISO 7185 Pascal language at level 0. In order to understand the basic ISO 7185 language, see the file:

Rules of ANSI-ISO 7185 Pascal, text document

and/or the ISO 7185 standard itself:

The ISO Pascal standards, documents in various formats

Also we highly recommend Doug Cooper's book:

Standard Pascal User Reference Manual

And the original Pascal User Manual and Report, now in it's 4th edition:

Pascal User Manual and Report

We won't repeat the standard language rules in this demo document. However, note that the full IP Pascal release contains all the rules of Pascal.

The following pages of this demo document contain a short explanation of IP Pascal extended language features.


Implementation Defined Behavior


ASCII  control characters (characters with values between 0 and 32 inclusive) are treated as spaces by the compiler and its utilities.

The type "file of char" is NOT equivalent to type "text", and none of the rules for end of line or end of file interpretation apply to "file of char" (as is allowed by the ISO 7185 standard). Specifically, this allows a character file to be read with all of the control, line ending and other special characters intact.


IP Pascal Language Extensions


Identifiers

The '_' character is allowed anywhere in identifiers. For example

my_identifier

Newwave_

Are valid identifiers. Avoid use of identifiers with '_' at the front, for example:

_other

The reason for this is that they can be system names.

Goto Labels

Standard Pascal identifiers are allowed as goto labels (including '_'):

label terminate;

 

...

 

goto terminate;

 

Numeric Format

Numbers can have a radix specifier:

$feeb  Hexadecimal

&76    Octal

%0110  Binary

String Format

Strings can have "force escapes":

writeln('hello\sub\ff\0\$a5');

Unlike typical C implementations, however, the original ISO 646 (ASCII) mnemonics for the control codes are used:

\NUL \DLE \SOH \DC1 \STX \DC2 \ETX \DC3 \EOT \DC4 \ENQ \NAK
\ACK \SYN \BEL \ETB \BS  \CAN \HT  \EM  \LF  \SUB \VT  \ESC
\FF  \FS  \CR  \GS  \SO  \RS  \SI  \US

So instead of C's convention of:

'string\r'

IP Pascal uses:

'string\cr'

Numeric equivalents for characters can also be specified, using any base. The force character can also be used to terminate "run on" sequences:

'this is\$a\fun'

Keeps the string from being interpreted as:

'this is\$afun'

Which would make the inserted force an $af valued character.

Constants

Sets are allowed to appear in const declarations:

const hexset = ['a'..'z', '0'..'9'];

Constant expressions can appear anywhere a constant can. This includes const declarations, case selectors, variant selectors, and ordinal declarations.

Constant expressions have their own syntax and rules, apart from normal expressions. Only constants can be operands in constant expressions. The following operators can appear, in priority order:

Priority One

not x

For boolean operands, finds the logical "not" of the boolean value. For integer operands, finds the inversion of all bits. It's an error for the operand to be negative.

Priority Two

x * y

Multiplication.

x div y

Division.

x / y

x mod y

Integer modulo.

x and y

For boolean operands, finds the boolean "and". For integer operands, finds the boolean "and" of all bits. It's and error for either operand to be negative.

Priority Three

-x

Negation.

+x

Positivition.

Priority Four

a + b

Addition.

a - b

Subtraction.

a or b

For boolean operands, finds the boolean "or". For integer operands, finds the boolean "or" of all bits. It's and error for either operand to be negative.

a xor b

For boolean operands, finds the boolean "xor". For integer operands, finds the boolean "xor" of all bits. It's and error for either operand to be negative.

Boolean Operations

A new operator for booleans, "xor", is available:

type a, b: boolean;

a := a xor b;

The boolean operators "and", "or", "xor" and "not" function bitwise on unsigned integers:

type a: integer;

writeln(a or $a5);

Use of boolean operators on signed integers is an error.

Procedures and functions

The parameter list for procedures and functions can be duplicated when they are forwarded: 

procedure wrtout(a: integer; c: char); forward;

 

...

 

procedure wrtout(a: integer; c: char);

 

begin

 

...

 

end;

This extension makes it easier to create a forwarded procedure or function by cut and paste, and also makes it easier to read the program, because the parameters for a procedure or function are right there at the actual body of the procedures or function.

When a routine is declared this way, the two parameter lists are checked for congruence, or equality of parameter numbers and types. The identifiers used to declare the parameters can be different. If they are, then the identifier used in the routine is that of the first identifier (the one that appears in the forward declaration).

Procedures and functions can be declared external. This has several uses. First, if the procedure or function is written in assembly language, this directive passes the reference to that. Additionally, external can be used in "abbreviated modules" or modules that simply exist as descriptions of a module interface, in order not to give errors such as references, and lack of returns for functions. See the section below on modules for more information.

procedure x; external;

function y(i: integer): boolean; external;

Procedures and functions can be "overloaded" for both parameter lists, and function vs. procedure status. Overloading means to use the same name for several procedures or functions that differ in the types and numbers of their parameter lists:

procedure writeout(var f: text; i: integer);

 

begin

 

   write(f, i)

 

end;

 

overload procedure writeout(var f: text; c: char);

 

begin

 

   write(f, c)

 

end;

Overloads allow flexibility in defining procedures and functions that is very useful in a strict typing language such as Pascal. They allow the construction of library routines to be simplified, and allow "default" parameter values to be set up.

In IP Pascal, there are no ambiguous overloads, that is, there are never two or more procedures and functions that might be acceptable for a given call. To accomplish this, IP Pascal has fairly strict rules about the specification of overloads.

Overloads bind a group of procedures or functions together, referred to as an overload group. The first member of the group is the prime procedure or function. The prime function or procedure of an overload group appears as a normal procedure or function. Each succeeding procedure or function in the group must be specified as an overload by the overload keyword. In addition, the overload group must be completed in the same block as it starts. Overloads cannot cross from one procedure or function to another, or one module to another.

All of the members of an overload group must be unique vs. the others. This means that one of the number of parameters, the types of the parameters, or the procedure or function status must be different from the others. The return type of a function is not part of its uniqueness. IP will not attempt to determine, by context, which two otherwise identical functions is meant by its return type.

When parameters are considered for whether their types match another procedure or function's parameters, the var, view or value status of the parameter is not considered, only the actual type of the parameter. Parameters which are assignment compatible with each other are considered identical, under the ISO 7185 rules for assignment compatibility. Note that this means that reals and integers are not unique compared to each other, and subranges of integer are also not unique to each other, or to the type integer itself. Sets are only unique if they have different base types. Finally, general array string types are not unique vs. char types, because a general array can be a single character in length, and appear alike in context (see the section on general arrays for more information).

The most difficult rule in IP Pascal is the rule of "convergence". Convergence is defined as two members of an overload group with non-unique left hand side parameter lists. Thus:

procedure x(i: integer; c: char; d: bolean); ...

 

overload procedure x(i: integer; c: char; z: myset); ...

These members of group x are convergent in the first two parameters.

If two or more members converge, they must not specify different modes in the converging sections. A mode is the var, value or view status of the parameter. For example:

procedure x(i: integer; c: char; d: bolean); ...

 

overload procedure x(i: integer; view c: char; z: myset); ...

is an illegal example, because the second parameter has two different modes in the convergent parameter lists.

Convergence is a difficult rule. The reason it exists is that the compiler must generate code to handle the mode. A var parameter requires different handling than a value parameter. When evaluating a parameter list from left to right, the compiler must either know exactly what procedure or function it is working on, or the two "candidates" of the overload group must agree in mode.

Case statements

An "else" clause is supported for case statements:

program numbers(input, output);

 

var n: integer;

 

begin

 

   repeat

 

      writeln('Enter number: ');

      readln(n);

      case n of

 

         1: writeln('One');

         2: writeln('Two');

         3: writeln('Three');

         else writeln('Don''t know that number')

 

      end

 

   until n < 0

 

 

end.

The else clause will take the place of the error that occurs when the case selector does not match any of the case constants.

Halt Procedure

The system defined procedure halt causes the program to terminate immediately. Most operating systems also define an error value to be returned by a program to indicate the success or failure of the program. See how to set that in the description of extlib below.

procedure error;

 

begin

 

   writeln('bad input');

   halt

 

end;

halt is actually one of the first extensions to Pascal, and was in the original CDC 6000 compiler. halt allows the creation of very simple error handling procedures, without the use of a long goto back to the main program block.

Program Header Files

In IP Pascal, the header files are both required, and have extensive functions. This is essentially as original Wirth Pascal. The identifiers "input" and "output" must appear to use standard input and standard output, respectively. In addition, several other standard header files are declared. This is a complete list:

input

The standard input file.

output

The standard output file.

error

As in the C language, the error file is connected to the console, but can be used to insure that error messages are routed directly to the console. For example, if the standard output is redirected to a file, writes to error will still go to the console. Also, if the program is compiled in graphical mode, errors still go back to the original console window used to start the program, provided it is not detached. See gralib below for more details.

list

An output file, is routed to the printer. In many graphical operating systems, it simply goes to the console.

command

Reads from the command line used to execute the program. Typically, this will be a file, containing a single line, which has all of the text following the command word used to execute the program.

program echo(command, output);

 

var c: char;

 

begin

 

   while not eoln(command) do begin

 

      read(command, c);

      write(command, c)

 

   end;

   writeln

 

end.

Would appear as executed:

> echo hi there !

hi there !

The advantage of having the command line as a file instead of a string passed to the program is that all of the standard Pascal parsing procedures work on it, such as reading and converting a number, etc.

Header files can also be used to automatically pass any number of files into the program.

 

program copy(infile, outfile, output);

 

type byte = 0..255;

 

var infile, outfile: file of byte;

    b: byte;

 

begin

 

   reset(infile);

   rewrite(outfile);

   while not eof(infile) do begin

 

      read(infile, b);

      write(outfile, b)

 

   end

 

end.

Each header file that does not match a predefined name ("input", "output", "list", etc. from the list above) is assigned a name from the command line in turn:

> copy myfile yourfile

So infile gets "myfile", outfile gets "yourfile", etc.

Note that the files must have a type declaration (as required by ISO 7185), and are reset or rewritten by the program manually, instead of letting IP Pascal do it. This effectively allows you to set the read or write mode of the file.

File Handling

Files under ISO 7185 Pascal are anonymous by default, and will cease to exist after the program ends. IP Pascal can give files a name, and so cause them to exist outside the program run. The name can be a program that existed before the program started, or that persists after the program run, or any combination.

The procedure to give a file a name is assign, and comes from the Borland language series:

type f: text;

 

begin

 

   assign(f, 'myfile.txt');

   reset(f);

 

   ...

 

Note that the named file must still be reset or rewritten. All assign does is give the file a name. The file can be any type, and the other handling procedures for the file are as in standard Pascal.

The close procedure allows a file to be reused any number of times:

repeat

 

   assign(f, name);

   reset(f);

 

   ...

 

   close(f)

 

until done;

Its considered good practice to specifically close each file used before the program ends. However, IP Pascal does close each open file automatically when the program ends. If a file was never given a name (anonymous), then it is deleted when the program ends.

Non-text files have a length in number of elements, and the number of elements in a file can be determined by the function length.

l := length(f);

The current location, in number of elements from the beginning of the file, numbered from 1 to n, can be determined by:

location(f);

Note that IP Pascal files do not start at 0 as in some other implementations and languages, the numbering is consistent with arrays.

The position of reading and writing can be set by position:

position(f, p);

Which again, uses the 1..n notation. The position of a file can be set to 1 element beyond the end. In the case of reading, this causes the eof (End Of file) to become true. In the case of writing, this means that the next element past the end will be added.

If a file is to be updated by changing or adding to its contents, update is used in place of rewrite:

update(f);

Update acts just as rewrite, and places the file in write mode, at element 1. However, unlike rewrite, it does not clear the contents of the file first. This allows the contents of the file to be updated. If the file is to be appended, then executing:

position(f, length(f)+1);

After update will cause new data to be written to the end of the file.

update is not valid on a text type file.

As mentioned, text files have no element length, and cannot be the target of any of the above procedures and functions. This is because text files have a special relationship with the character format of their files. In particular, the eoln format is undefined, and the ISO 7185 standard requires the addition of an eoln at the end of the file if none exists.

If positioning is required on a file containing characters, this can be done by using the type:

type f: file of char;

Which is a plain file of character values, with no special interpretations of line endings.

A file can be checked for existence by name, by the function exists:

if exists('myfile.txt') then ...

The function delete removes a file, by name:

delete('oldfile');

Delete gives an error on an attempt to delete a non-existent file. This error can be prevented by using exists to check the file before deleting it.

Filenames can be changed by change:

change('newname', 'oldname');

Its an error if the old name does not exist, while again the exists function can be used to check for.

When a file based on a subrange of integer is declared, the element size on the external medium is only the size needed by the subrange. Specifically, this fact can be used to access a file of byte:

type byte = 0..255;

     bytfil = file of byte;

 

var bf: bytfil;

 

begin

 

   write(bf, $a5);

 

   ...

Text File I/O

The field specifications of text file writes have two extended modes. First, it is possible to specify a negative field:

write(i: -10)

Negative fields come from the C language, and mean to align the output to the left, instead of the standard Pascal right side. For example:

i := 123;

write(i: 10);

write(i: -10);

Would produce  the output:

       123

123

Second, a special field value, 0, can be used to specify padded string output:

var s: packed array [1..20] of char;

s := 'the time is now     ';

write('*', s: 0, '*');

Would produce  the output:

*The time is now*

The field of 0 indicates that the actual field should be determined by the right padded string length. The right padded string length is the index of the rightmost non-blank character, or 0 if there isn't one.

Enumerated Type Conversion

The reverse of the "ord" function can be performed on enumerated types via a "type converter":

 

type number = (one, two, three);

 

var n: number;

 

begin

 

   n := number(1);

 

   ...

Type converters under IP Pascal cannot convert any type to any other type (like a C language "type cast"), but instead have limited uses.

Subrange Constraints

The rules of ISO 7185 Pascal dictate that calculations of integer subranges are automatically promoted to full integer. In some cases, the compiler can determine that less precision is required. However, it is not always possible. Subrange constraints allow the arithmetic precision of any part of an expression to be fully controlled by the programmer using a type converter construct:

type byte = 0..255;

 

var a, b: integer;

 

begin

 

   a := byte(b+1);

 

   ...

 

The ability to specify exactly the precision needed helps greatly in embedded applications on small processors (8 or 16 bit) where it would be inefficient to perform all expression operations at full integer precision. This is equivalent in function to the C language use of different size types such as "char", "int" and "long" using rules of promotion that only promote to the largest element in the expression.

Protected Parameters

In addition to "value" and variable or var parameters, IP can have a protected parameter type view: Like a value parameter, view parameters can accept full expressions:

var x: integer;

 

procedure a(view b: integer);

 

begin

 

   ...

 

end;

 

begin

 

   a(x*5)

 

A view parameter has all of the efficiency of a var parameter, but uses value passing rules. For example:

 

type string10: packed array [1..10] of char;

 

procedure a(view s: string10);

 

...

 

Normally, a value parameter of an array such as "string10" would be inefficient because of the need to copy the entire array to the value parameter. However, typing the parameter as view leaves it up to the compiler how to most efficiently pass the parameter, by value or reference. The effect is the same, since the parameter cannot be modified, just as in a value parameter.

view parameters are also useful for cases where we want to be sure that a parameter is not modified at all within a procedure.

Any attempt to modify a view parameter within the routine is flagged as an error by the compiler, using the same rules as "threatening" of "for" index variables under ISO 7185.

Refer procedure

The system defined procedure "refer" causes any number of its parameter symbols to be considered "referenced" by the compiler:

program test;

 

procedure stub(a, b: integer);

 

begin

 

   refer(a, b) { these parameters are unused }

 

end;

 

begin

end.

 

The refer procedure allows unused identifiers to be used in the program without the need to turn all reference checking off in the compiler. This is commonly needed in the case of dummy/stub procedures and other cases.

Fixed Type

A fixed type is a cross between a variable type and a constant. They can have structured types, but are preinitalized like a constant. Fixed types can be all of the structured types, arrays, records, and combinations thereof, plus all of the normal types that can be constants, such as integers, characters and sets. The only excluded types are files, pointers and variant records, because those are inherently dynamic.

fixed a: integer = 1;

      b: array [1..10] of integer = array 5, 6, 8, 2, 3, 5, 9, 1, 12, 85 end;

      c: record a: integer; b: char end = record 1, 'a' end;

      d: array [1..5] of packed array [1..5] of char = array

  

         'tooth',

         'trap ',

         'same ',

         'fall ',

         'radio'

 

      end;

 

Fixed types have a similar declaration syntax to var declared types, except for the appearance of the initializer (= ...). Fixed types can be used anywhere a var type can, but fixed types cannot be modified or threatened (ISO 7185 contains the definition of a "threatened" variable, including appearing as a for index, var parameter, etc.).

When a structured fixed type appears, it must be in "structure image" format. The constants for an array must be contained within array and end keywords. The constants of a record must be contained in record and end keywords. This makes it clear just what is being defined in the intalizer, which otherwise is a fairly undifferentiated list of constants.

array <const> [,<const>] end

record <const> [,<const>] end

The exception to this rule is constant strings, which can be simply quoted strings of characters.

Fixed types are not preinitialized variables. They may not be modified during the run of the program. To get the same effect as preinitialized variables, a fixed type can be assigned to a variable type at startup using a single assignment statement. However, in many cases, the fixed type can be directly used, so fixed types are actually more efficient than preinitialized variables would be, since they simply hide this copy step.

Declaration Order

In IP Pascal, the declarations can appear in any order, with the only restriction being that symbols be declared before their use. Additionally, pointer declarations must define what they point to within the same type statement. Relaxed declaration order is a better match for the modular environment of IP Pascal, in which declarations are divided into modular groups.

program test;

 

var a: integer;

 

label 99;

 

procedure x; begin end;

 

const a = 5;

 

...

 

A view parameter has all of the efficiency of a var parameter, but uses value passing rules. For example

Include Operator

IP Pascal has a C type line escape, of which include is the only one currently implemented.:

program test;

 

#include myfile.pas

 

The include operator is limited in function, it must be flush left (1st character on line must be "#"), and only whole lines can be inserted at a line boundary. The include file mechanism is not the preferred way to accomplish breaking programs into files, that is the purpose of modularity. However, the include operator has occasional uses.

Integer index arrays

A special notation exists for the case where you want an array with an integer index starting with 1:

program test;

 

var a: array 10 of integer;

    i: integer;

 

begin

 

   for i := 1 to 10 do a[i] := 0

 

end.

The number in place of the index specification is the length of the array in elements. The index is then calculated as 1..N, where N is the number of elements. The following declarations are equivalent:

type a = array 10 of integer;

 

type a = array [1..10] of integer;

Any number of dimensions can be specified:

type a = array 20, 10 of integer;

Standard Pascal strings are declared as:

type a = packed array 10 of char;

General Arrays

IP Pascal has an array model that allows the length of the array to be specified at runtime:

type genint = array of integer;

The array is a "prototype" of the actual array, because it does not specify the length of the array. General arrays cannot be statically allocated. They cannot appear in a variable declaration or as a value parameter, which would require allocation of an array of unknown length. Instead, general arrays are allocated as pointers:

var myarr: ^genint;

    i: integer;

 

begin

 

   new(genint, 100);

   for i := 1 to max(genint) do genint^[i] := 0;

The call for new in this case has an extra parameter, that is the number of array elements in the general array to be created. Once a general array is allocated, it becomes equivalent to a normal array of the same length. In fact, general arrays are completely compatible with standard Pascal arrays.

The system defined function max returns the length of the array.

General arrays don't specify an index type in their declaration. The index type for them is always integer, and the starting index is 1. So a general array has indexes of:

1..max(a);

General arrays can be either packed or not packed, and the ISO 7185 idea of a "string" applies to general arrays as well. A general array of base type char, is a string:

 Two arrays are assignment compatible if:

  1. One or both of the arrays are a general array.
  2. The arrays have compatible base types.
  3. Both arrays have the same packed/unpacked status.

So, for example, the following declarations are compatible:

 

type a = packed array [1..100] of char;

     b = packed array of char;

 

The following declarations are not compatible:

 

type a: packed array [1..10] of integer;

     b: packed array of boolean;

 

The following code demonstrates this:

type a = packed array [(one, two, three, four, five)] of char;

     b = packed array of char;

 

var c: a;

    d: ^b;

 

begin

 

   c := 'prime';

   new(d, 5);

   d^ := c;

   write(c[three]);

   d^[3] := 'x';

   c := d^;

   ...

   

Note that c and d are compatible, and that d maintains its index of 1..max(d) even though it was assigned from an array whose index type was different. When assigning between general and normal arrays, the assignment occurs by paring the first element of the arrays, then the second, then the next, etc. The general array "image" of any array always starts at 1, regardless of its original index. IP Pascal considers a general array to be compatible with any array, regardless of its index type. Put another way, IP Pascal cares only about the elements of the array, and the method used to index them is not important. This is why general arrays are considered a "container" type for other array types.

type a = array [-100..100] of integer;

     b = array of integer;

 

var c: a;

    d: ^b;

 

begin

 

   c[-100] := 12;

   new(d, 201);

   d^ := c;

   write(d^[1]);

   ...

 Note that we had to allocate an array of 201 to allow for the zero crossing in the array type a. In this example, element -100 of the array c becomes element 1 of the array d.

Since general arrays don't have an index type, how are mult-dimensional arrays constructed ? The answer is, you can do it yourself.

type a = array of integer;

     b = ^a;

     c = array of b;

 

var d: ^c;

 

begin

 

   new(d, 100); { create the y dimension }

   for i := 1 to 100 do new(d^[i]); { create all x dimensions }

   d[50]^[70] := 65;

   ...

Not all general arrays are dynamically allocated. A parameter can also be a general array. The general array acts as a "template" for the actual array that is passed:

type a = array of integer;

 

var b: array [1..10] of integer;

 

procedure addone(var x: a);

 

var i: integer;

 

begin

 

   for i := 1 to max(a) do a[i] := a[i]+1

 

end;

 

begin

 

   for i := 1 to 10 do b[i] := 10-i+1;

   addone(b);

A parameter whose type is a general array must be a var or view parameter. It cannot be a value parameter, because that would be allocating a copy of the input parameter, and that can't be done unless we know its length.

As mentioned above, general arrays can be Pascal strings if they have a base type of char, and are packed. So, for example, we can create a general array string output routine:

 

type string = packed array of char;

 

var mystring: packed array [1..8] of char;

 

procedure wrtstr(var s: string);

 

var i: integer;

 

begin

 

   for i := 1 to max(s) do write(s[i]);

   writeln

 

end;

 

begin

 

   mystring := 'hi there';

   wrtstr(mystring);

 

end.

More interesting is the example:

 

type string = packed array of char;

 

procedure wrtstr(view s: string);

 

var i: integer;

 

begin

 

   for i := 1 to max(s) do write(s[i]);

   writeln

 

end;

begin

 

   wrtstr('hi there');

 

end.

 

The first thing that is different with this example is that the view declaration is used for the general array. This allows it to be a reference parameter, but still use value passing rules (because the view parameter cannot be modified). Since value passing rules are in effect for the general string parameter, it can be passed a constant string.

Modularity

Programs in IP Pascal can be broken up into modules, each of which can contain its own declarations, variables, procedures and functions. IP Pascal differs from many modular implementations in that there is no separate interface and implementation parts. Each module represents its own declarations, procedures and functions to its users. This means that they don't have to appear twice in either the same file, or in different files. This makes it easier to modify them, since two parallel copies don't have to be maintained.

The basic module construct is:

 

module mymod(input, output);

 

const one = 1;

 

type string = packed array of char;

 

procedure wrtstr(view s: string);

 

private

 

var s: string;

 

procedure wrtstr(view s: string);

 

begin

end;

 

begin { initialization section for mymod }

end;

 

begin { finalization section for mymod }

end.

 

A module appears very much like a program, which in IP Pascal is just considered a special type of module. A module can have header files. Modules can get access to the input, output, error, list and command files just as a program can. For a module to use any of the files, or perform default input and output, the file must appear in the header, just as in a program. A module can use the command file, or have automatic filename header files. However, such uses of the command line should be avoided, since they create order dependencies.

Declarations appear in a module just as in a program. IP Pascal implements a new keyword private that demarcates between declarations that other modules can use, and declarations that are only to appear within the module. Procedures and functions can be forwarded from the public area to the private area. The private keyword is not restricted to modules, it can also be used in programs.

Modules have two main body blocks, known as the initialization and finalization blocks. The normal main block that you are familiar with from the program construct is the initialization block. The second, optional block is the finalization block. When multiple modules appear in a program image, each of the initialization blocks in the image are executed before the program module gains control. After the program module exits, each module has its finalization section executed. The order in which the initialization and finalization sections are executed is specified at link time, and is set automatically by the compiler shell program pc, which considers which module calls which, and orders the modules so that each module is initialized before the modules that call it. Creating order dependencies in module initialization and finalization sections should be avoided.

A module is compiled on its on as a unit. A separate module that wants to use the module includes it in a "uses" statement, which originated with UCSD Pascal:

 

program test;

 

uses extlib, gralib;

 

...

 

Appearance in a uses statement effectively includes all of the non-private elements of the used module into the using module. Either a program or a module can have a uses statement. IP Pascal automatically keeps track of used modules to prevent duplicate declarations, and recursive or circular uses references are valid. IP Pascal knows how to extract declaration information from either the source code or the compiled code, and will automatically choose whichever is available and most recently modified. The compilation proceeds faster if the precompiled declarations are used.


Standard Modules


IP Pascal is centered around its modules. Everything in the IP system is made up of modules. The support library is made up of modules. Modules can override each other, and thus can take over functions from the low level support code so the fundamental language constructs such as the standard input and output files can be redefined.

Each operating system that IP Pascal runs on usually has one or more modules that is collectively referred to as the wrapper (but usually named something specific to the OS involved, like "windows.pas"). On top of that, a series of modules adapt the operating specific calls needed by programs to an OS independent format referred to as the IP Basic Portability Layer. It is to this system independent layer that most IP Pascal programs will be written.

The portability layer is a series of modules that each adapt a particular function the program will need. These are further classified as I/O vs. non-I/O modules. I/O modules override the standard I/O system, and become a virtual system for the program to model its I/O on. Non-I/O modules simply extend the functions available of IP Pascal. Some of these modules may rely on the wrapper, but some may only be useful extensions to the language.

strlib

Implements a set of string handling functions.

extlib

Handle searching directories, time and date, paths and filenames, file permissions and modes, environment strings, and execution of other programs.

sndlib

Implements sound via MIDIi and waveform devices.

serlib

Implements the basic serial I/O model.

trmlib

Implements I/O on a terminal, or x-y character surface.

gralib

Implements I/O on a graphical surface.

Each modules calls except serlib will be covered in turn. serlib does not need to be covered, because it implements the basic Pascal I/O model, the reads and writes you are already used to.


strlib - Standard String library


The string library implements various useful string functions. Strings are built out of arrays of characters in Pascal, and extended in IP Pascal. There are many ways to represent a string. Space padded right, from standard Pascal, leaves the right side of a fixed length string blank. Borland strings use a character array beginning with 0, then use the zero'th element to describe the string length, from 0 to 255. Dynamic strings are character arrays that are allocated for each size needed, then returned to dynamic storage when no longer needed. And there are many others.

strlib unifies two schemes. The first is the old space padded right scheme familiar from standard Pascal. The second is dynamic strings. Dynamic strings are almost the ideal string type. They are unlimited in length, they can be returned from a function, because they are pointers. And because their storage is recycled, they are space efficient. They can also be fairly speed efficient by managing when and if they are recycled.

The two string schemes complement each other. There are functions, like finding the lower case equivalent of a string, or finding a substring, that don't care which format is used, so the same function serves both string types. Using IP Pascal's compatible general arrays, dynamic arrays can be copied from standard Pascal fixed arrays, operations performed on those strings, then the result returned to a standard fixed array. Also, using general array parameters, even working with padded strings only is easier and more efficient.

The result is strlib is an easy upgrade from standard padded strings. A program can be completely converted to dynamic strings, or left as a mix of the string types.

The declarations for padded or fixed, and dynamic strings are in the module stddef, and appear as:

type string = packed array of char;

     pstring = ^string;

The string functions are often overloaded so that they take both types of strings. This allows their use in nested functions.

Conventions

In some cases, it is ambiguous whether a padded or dynamic string argument is meant. For example, the string compare facility does not know which type its operands are, and it makes a difference to the result, since length plays a part in string comparison. For these situations, a "p" is appended to the name of the procedure or function.

Many functions and procedures must know if case matters. For example, string compare can be with case, or caseless. For these procedures and functions, we append a "c" to the ones that case does matter.

A few functions and procedures perform different actions on strings vs. characters. For the string versions, an "s" is appended to the name.

Words

Some of the functions and procedures treat the strings as a series of words. Words are a series of non-space characters surrounded by one or more spaces. For example:

'      hi          there george'

Has three words, 'hi', 'there', and 'george'. Such words can be counted, indexed and extracted.

Format Strings

Some routines accept a "format string" to output numbers with. The format is an image of the output string the number is converted into. The string will contain a series of format  characters. The entire format string is copied to the result string, but the special format characters are replaced with parts of the number to be converted. The format characters are:

9

Represents a digit. This is replaced with a digit from the number, which is paired with the same digit, counting from the rightmost digit position in the format. If there is no significant digit in the number at that position, a space replaces the specifier. In the fraction, if the digit position is non-zero, or there are non-zero digits to the right, the digit replaces the format character, otherwise space.

0

As "9" above, but "0" replaces the digit if no significant digit is found, instead of space. In the fraction, if the digit position is non-zero, then it replaces the format character, otherwise remains 0.

-

Represents the sign. If the number is negative, it is left alone. If the number is positive, it is replaced by a space. The entire number must be to the right of this.

+

As "-", but "+" appears instead of space on a positive number.

$, &, %

These characters are used to indicate if the number is hex (or USA dollars), octal or binary. If a significant digit can be matched to this position, that is output, otherwise, either the character, or a space is placed. The space is placed if the format character has already appeared.

,

If the comma appears to the right of significant digits, it is output as is. Otherwise, it is either replaced by space, or if a "$", "&" or "%" character appears to the left of it, it will be replaced by that.

.

May only appear on real numbers. This format character is always printed, but specifies where the mantissa of a number appears, and where its fraction appears. The appearance of the decimal point will enable or disable the fraction. If present, fractional digits are output, otherwise, the fraction is discarded.

Recycling

By default, strlib leaves it up to the programmer to determine when dynamic strings should be recycled. To keep from losing space ("memory leaks"), the program must be careful to keep track of all dynamic strings created, and return them to free storage via dispose. However, when performing complex string calculations, especially where strings are returned from functions and then those appear as arguments to other procedures and functions, it can be very tedious to keep track of all such strings.

To answer such problems, strlib can use a nested blocking system to automatically dispose of strings for you. The openstring call begins a new string block, and closestring ends it. When a new block is opened, any dynamic strings allocated are recorded in the block. Then, each of them are disposed of when the block ends. Any number of block levels can exist. With no blocks in effect, the automatic recycling system is off, which is the state strlib starts in. When all blocks are closed, strlib reverts to this state. Because the dynamic strings are returned as a group, this system can be more efficient than returning the strings one at a time.

strlib has a call exportstring to completely remove a given dynamic string from the automated recycling system. This is good for when you wish to keep a particular string and recycle it yourself, while still enjoying the ability to automatically recycle other strings. There is also an upstring call which takes a given string, and moves it to the surrounding string block. This feature is good for when you want to return a result from a calculation inside a block, to be used in the upper block.

It's important to understand that the strlib block system has nothing to do with blocks in Pascal. strlib blocks can cross blocks, functions and even whole modules of Pascal. The two are entirely unrelated.

There is a limit of 100 on how many strings can be allocated at one time, in any string block.

Functions

function lcase(c: char): char;

overload function lcase(view s: pstring): pstring;

function lcases(view s: string): pstring;

overload procedure lcases(var s: string);

lcase finds the lower case version of a character or string. This is either returned, or converted in place.

function ucase(c: char): char;

overload function ucase(view s: pstring): pstring;

function ucases(view s: string): pstring;

overload procedure ucases(var s: string);

ucase finds the upper case version of a character or string. This is either returned, or converted in place.

procedure clears(var s: string);

Clears a string to all spaces. To set an entire string to another character value besides space, use rep below.

function len(view s: string): integer;

overload function len(view s: pstring): integer;

Finds the padded length of a string. This is equivalent to the index of the last non-space character, or zero if there is none. Note that dynamic string lengths are found via the system function max.

procedure copy(var d: string; view s:string);

overload procedure copy(var d: string; s: pstring);

overload function copy(view s: string): pstring;

overload function copy(view s: pstring): pstring;

overload procedure copy(var d: pstring; view s: string);

Create a copy of the source string in the destination. If the destination is a fixed string, then it will be padded on the right with spaces to fill it out. If the destination or return type is dynamic, then a new string will be created of the same length as the source, and the source copied to that. In the case of the last procedure, the source is taken to be a padded string, and the length is without padding. This procedure is used to copy from a padded string to a dynamic string.

procedure cat(var d: string; view s: string);

overload function cat(view sa, sb: string): pstring;

overload function cat(s1, s2: pstring): pstring;

overload function cat(view s1: string; s2: pstring): pstring;

overload function cat(s1: pstring; view s2: string): pstring;

Concatenate two strings. For the first procedure, the padded destination and source are concatenated into the padded destination. The functions all concatenate lengthed strings to a dynamic result.

When concatenating two padded strings, it is not possible using cat to concatenate with a space or spaces between strings. For this case, use a padded string insert instead, with the source string inserted after the desired number of spaces.

function compc(view d, s: string): boolean;

overload function compc(d, s: pstring): boolean;

overload function compc(view d: string; s: pstring): boolean;

overload function compc(d: pstring; view s: string): boolean;

function compcp(view d, s: string): boolean;

function comp(view d, s: string): boolean;

overload function comp(d, s: pstring): boolean;

overload function comp(view d: string; s: pstring): boolean;

overload function comp(d: pstring; view s: string): boolean;

function compp(view d, s: string): boolean;

Compare strings. Returns true if the strings are equal. Compares case or caseless, and padded or fixed length. Strings are equal only if they have the same length and content.

function gtrc(view d, s: string): boolean;

overload function gtrc(d, s: pstring): boolean;

overload function gtrc(view d: string; s: pstring): boolean;

overload function gtrc(d: pstring; view s: string): boolean;

function gtrcp(view d, s: string): boolean;

function gtr(view d, s: string): boolean;

overload function gtr(d, s: pstring): boolean;

overload function gtr(view d: string; s: pstring): boolean;

overload function gtr(d: pstring; view s: string): boolean;

function gtrp(view d, s: string): boolean;

Compare strings greater than. Returns true if the second string is greater than the first. Compares case or caseless, and padded or fixed length. Strings are compared from left to right, until the first difference is found. Then, the second string is greater than the first if the ord of its character is greater than the first. In case one string is longer than the other, but otherwise equal, the shorter string is less than the longer string.

This function can be used to find less than, greater than or equal, and less than or equal by arranging the operands:

gtr(a, b)         a < b

gtr(b, a)         a > b

not gtr(a, b)     a >= b

not gtr(b, a)     a <= b

 

 

function indexc(view d, s: string): integer;

overload function indexc(d, s: pstring): integer;

overload function indexc(view d: string; s: pstring): integer;

overload function indexc(d: pstring; view s: string): integer;

function indexcp(view d, s: string): integer;

function index(view d, s: string): integer;

overload function index(d, s: pstring): integer;

overload function index(view d: string; s: pstring): integer;

overload function index(d: pstring; view s: string): integer;

function indexp(view d, s: string): integer;

Finds the incidence of the source string in the destination string. If the source string is found within the destination, the index of its first character is returned, otherwise 0. The comparison may be case or caseless. The padded versions are for when the source string is padded.

procedure extract(var d: string; view s: string; l, r: integer);

overload function extract(view s: pstring; l, r: integer): pstring;

overload function extract(view s: string; l, r: integer): pstring;

Extract a substring. The source string from the left index to the right index is extracted, and either placed in the destination, or returned. It is an error if either index is out of range, but indexes l > r simply result in a null string. If the result is too large for the destination, an error results. The procedure version is for padded strings.

Common equivalents to extract are:

right(s, l)   extract(s, max(s)-l+1, max(s))   Get right string

left(s, l)    extract(s, 1, l)                 Get left string

mid(s, l, r)  extract(s, l, l+r-1)             Get mid string

 

 

procedure insert(var d: string; view s: string; p: integer);

overload function insert(view sa, sb: string; p: integer): pstring;

overload function insert(sa, sb: pstring; p: integer): pstring;

overload function insert(view sa: string; sb: pstring; p: integer): pstring;

overload function insert(sa: pstring; view sb: string; p: integer): pstring;

Inserts a substring from the source into the destination. The procedure version is for padded strings. If the source string is too long for the destination, an error results.

function rep(view s: string; r: integer): pstring;

overload function rep(s: pstring; r: integer): pstring;

overload procedure rep(var d: string; view s: string; r: integer);

Repeats the source string r times into the destination. The procedure version is for padded strings. If the resulting string is too long for the destination, an error results.

function trim(view s: string): pstring;

overload function trim(s: pstring): pstring;

overload procedure trim(var d: string; view s: string);

Trim leading and trailing spaces from a string. The procedure version is for padded strings. For padded strings, only the leading spaces are affected.

function words(view s: string): integer;

overload function words(s: pstring): integer;

Returns a count of the number of space delimited words in the string.

function extwords(view s: string; l, r: integer): pstring;

overload function extwords(s: pstring; l, r: integer): pstring;

overload procedure extwords(var d: string; view s: string; l, r: integer);

Extracts the space delimited words from the left to the right index.

procedure reads(var f: text; var s: pstring; var ovf: boolean);

overload procedure reads(var f: text; var s: string; var ovf: boolean);

overload procedure reads(var f: text; var s: pstring);

overload procedure reads(var f: text; var s: string);

Read string from text file. The versions of this procedure that have an ovf flag return it true if the input line overflowed the string. In this case, the string is returned truncated to the left. The non-ovf versions of the procedure halt with error on overflow.

procedure ints(var s: string; i: integer; f: integer);

overload function ints(i: integer; f: integer): pstring;

overload procedure ints(var s: string; i: integer);

overload function ints(i: integer): pstring;

overload procedure ints(var s: string; i: integer; view fmt: string);

overload function ints(i: integer; view fmt: string): pstring;

Convert integer to string. The versions with a field format the number using the field using standard Pascal rules for output using a field. Negative (left justified) fields are also allowed.

The versions with a fmt string copy the format string into the result string, then replace each of the format characters with parts of the number. See "format strings" above.

procedure reals(var s: string; r: real; f: integer);

overload function reals(r: real; f: integer): pstring;

overload procedure reals(var s: string; r: real);

overload function reals(r: real): pstring;

overload procedure reals(var s: string; r: real; fl: integer; fr: integer);

overload function reals(r: real; fl: integer; fr: integer): pstring;

overload procedure reals(var s: string; r: real; view fmt: string);

overload function reals(r: real; view fmt: string): pstring;

Convert real to string. The versions with a field format the number using the field using standard Pascal rules for output using a field. Negative (left justified) fields are also allowed.

The versions with a fmt string copy the format string into the result string, then replace each of the format characters with parts of the number. See "format strings" above.

procedure reales(var s: string; r: real; f: integer);

overload function reales(r: real; f: integer): pstring;

overload procedure reales(var s: string; r: real);

overload function reales(r: real): pstring;

Convert real to sring using "economy" format. The real is printed in the minimum number of characters possible. If the decimal position can be placed into the number, then it is, and the exponent is removed. All insignificant leading and trailing zeros are removed, and if there are no digits to the right or left of the decimal point, then that is removed.

If the decimal point cannot be placed into the number with less total characters than a full ISO 7185 floating point number format, then the standard floating point format is used.

The versions of this call with a field fit the result into the field by padding it out with blanks, either on the left for positive fields, or on the right for negative fields. If the number does not fit into the specified field, then all of the required parts of the number are printed. Note that if the ISO 7185 floating point number is chosen as the shortest length number, then the rules for fields are identical to that of normal real output formats.

procedure hexs(var s: string; w: integer; f: integer);

overload function hexs(w: integer; f: integer): pstring;

overload procedure hexs(var s: string; w: integer);

overload function hexs(w: integer): pstring;

overload procedure hexs(var s: string; i: integer; view fmt: string);

overload function hexs(i: integer; view fmt: string): pstring;

Convert integer to string, using the hexadecimal radix system. The versions with a field format the number using the field using standard Pascal rules for output using a field. Negative (left justified) fields are also allowed.

The versions with a fmt string copy the format string into the result string, then replace each of the format characters with parts of the number. See "format strings" above.

It is an error if the integer is negative.

procedure octs(var s: string; w: integer; f: integer);

overload function octs(w: integer; f: integer): pstring;

overload procedure octs(var s: string; w: integer);

overload function octs(w: integer): pstring;

overload procedure octs(var s: string; i: integer; view fmt: string);

overload function octs(i: integer; view fmt: string): pstring;

Convert integer to string, using the octal radix system. The versions with a field format the number using the field using standard Pascal rules for output using a field. Negative (left justified) fields are also allowed.

The versions with a fmt string copy the format string into the result string, then replace each of the format characters with parts of the number. See "format strings" above.

It is an error if the integer is negative.

procedure bins(var s: string; w: integer; f: integer);

overload function bins(w: integer; f: integer): pstring;

overload procedure bins(var s: string; w: integer);

overload function bins(w: integer): pstring;

overload procedure bins(var s: string; i: integer; view fmt: string);

overload function bins(i: integer; view fmt: string): pstring;

Convert integer to string, using the binary radix system. The versions with a field format the number using the field using standard Pascal rules for output using a field. Negative (left justified) fields are also allowed.

The versions with a fmt string copy the format string into the result string, then replace each of the format characters with parts of the number. See "format strings" above.

It is an error if the integer is negative.

procedure writed(var f: text; i: integer; view fmt: string);

overload procedure writed(i: integer; view fmt: string);

Convert integer to string, using the decimal radix system. Decimal numbers are well covered by the normal ISO 7185 standard write formats. However, these routines add image formatting to them.

If the output file is not specified, then the standard output file is used.

procedure writer(var f: text; r: real; view fmt: string);

overload procedure writer(r: real; view fmt: string);

Convert real to string. Real numbers are well covered by the normal ISO 7185 standard write formats. However, these routines add image formatting to them.

If the output file is not specified, then the standard output file is used.

procedure writere(var f: text; r: real; fl: integer);

overload procedure writere(r: real; fl: integer);

overload procedure writere(var f: text; r: real);

overload procedure writere(r: real);

Write the "economy real" format to either the specified output file, or to the standard output. The economy real format is explained in the reales routines descriptions.

The versions with a field fit the output to the field using standard Pascal rules, including negative fields.

procedure writeh(var f: text; i: integer; fl: integer);

overload procedure writeh(i: integer; fl: integer);

overload procedure writeh(var f: text; i: integer);

overload procedure writeh(i: integer);

overload procedure writeh(var f: text; i: integer; view fmt: string);

overload procedure writeh(i: integer; view fmt: string);

Write integer to either the specified file, or the standard output file, using the hexadecimal radix system. The versions with a field format the number using the field using standard Pascal rules for output using a field. Negative (left justified) fields are also allowed.

The versions with a fmt string copy the format string into the result string, then replace each of the format characters with parts of the number. See "format strings" above.

It is an error if the integer is negative.

procedure writeo(var f: text; i: integer; fl: integer);

overload procedure writeo(i: integer; fl: integer);

overload procedure writeo(var f: text; i: integer);

overload procedure writeo(i: integer);

overload procedure writeo(var f: text; i: integer; view fmt: string);

overload procedure writeo(i: integer; view fmt: string);

Write integer to either the specified file, or the standard output file, using the octal radix system. The versions with a field format the number using the field using standard Pascal rules for output using a field. Negative (left justified) fields are also allowed.

The versions with a fmt string copy the format string into the result string, then replace each of the format characters with parts of the number. See "format strings" above.

It is an error if the integer is negative.

procedure writeb(var f: text; i: integer; fl: integer);

overload procedure writeb(i: integer; fl: integer);

overload procedure writeb(var f: text; i: integer);

overload procedure writeb(i: integer);

overload procedure writeb(var f: text; i: integer; view fmt: string);

overload procedure writeb(i: integer; view fmt: string);

Write integer to either the specified file, or the standard output file, using the binary radix system. The versions with a field format the number using the field using standard Pascal rules for output using a field. Negative (left justified) fields are also allowed.

The versions with a fmt string copy the format string into the result string, then replace each of the format characters with parts of the number. See "format strings" above.

It is an error if the integer is negative.

function intv(view s: string): integer;

overload function intv(s: pstring): integer;

overload function intv(view s: string; var ovf: boolean): integer;

overload function intv(s: pstring; var ovf: boolean): integer;

Find value of string with default decimal format. The number contained in the string, in valid Pascal signed number format, is parsed and the value returned. The number can be signed. Leading and trailing spaces are ignored, but extra characters past a valid number result in an error.

If the number is unsigned, and has a valid radix specifier, one of "$", "&" or "%" prepended to it, then that radix overrides the default decimal radix. If this override effect is not desired, then the number should be checked for such radix specifications using index, and an error generated.

The versions of this call that feature the ovf flag do not produce an error if the number is too large to convert, but instead set the ovf flag, and then the result is undefined (and probably is garbage).

function hexv(view s: string): integer;

overload function hexv(s: pstring): integer;

overload function hexv(view s: string; var ovf: boolean): integer;

overload function hexv(s: pstring; var ovf: boolean): integer;

Find value of string with default hexadecimal format. The number contained in the string, in valid Pascal signed number format, is parsed and the value returned. The number may not be signed. Leading and trailing spaces are ignored, but extra characters past a valid number result in an error.

If the number has a valid radix specifier, one of "$", "&" or "%" prepended to it, then that radix overrides the default hexadecimal radix. If this override effect is not desired, then the number should be checked for such radix specifications using index, and an error generated.

The versions of this call that feature the ovf flag do not produce an error if the number is too large to convert, but instead set the ovf flag, and then the result is undefined (and probably is garbage).

function octv(view s: string): integer;

overload function octv(s: pstring): integer;

overload function octv(view s: string; var ovf: boolean): integer;

overload function octv(s: pstring; var ovf: boolean): integer;

Find value of string with default octal format. The number contained in the string, in valid Pascal signed number format, is parsed and the value returned. The number may not be signed. Leading and trailing spaces are ignored, but extra characters past a valid number result in an error.

If the number has a valid radix specifier, one of "$", "&" or "%" prepended to it, then that radix overrides the default octal radix. If this override effect is not desired, then the number should be checked for such radix specifications using index, and an error generated.

The versions of this call that feature the ovf flag do not produce an error if the number is too large to convert, but instead set the ovf flag, and then the result is undefined (and probably is garbage).

function binv(view s: string): integer;

overload function binv(s: pstring): integer;

overload function binv(view s: string; var ovf: boolean): integer;

overload function binv(s: pstring; var ovf: boolean): integer;

Find value of string with default binary format. The number contained in the string, in valid Pascal signed number format, is parsed and the value returned. The number may not be signed. Leading and trailing spaces are ignored, but extra characters past a valid number result in an error.

If the number has a valid radix specifier, one of "$", "&" or "%" prepended to it, then that radix overrides the default binary radix. If this override effect is not desired, then the number should be checked for such radix specifications using index, and an error generated.

The versions of this call that feature the ovf flag do not produce an error if the number is too large to convert, but instead set the ovf flag, and then the result is undefined (and probably is garbage).

function realv(view s: string): real;

overload function realv(s: pstring): real;

Find value of real number string. The string must contain a valid Pascal format real number. Leading and trailing spaces are ignored, but extra characters past a valid number result in an error.

procedure openstring;

Causes a new string block level to be created. All strings that were dynamically allocated within strlib will be recorded within the new block, and disposed of automatically on closestring. Note that there is a limit of 100 strings that can be allocated per block.

procedure closestring;

Removes the current string block level. Each string that was allocated and recorded in the current block is disposed of, and the surrounding string block, if it exists, is restored. All strlib dynamic allocations will then be recorded in that block.

It is an error if no block exists to close.

procedure exportstring(p: pstring);

Removes the indicated string completely from the current string block. Only the current block is searched. It is only possible to export a string from the block it was created in. If the string does not exist in the current block, no error results.

procedure upstring(p: pstring);

Moves the indicated string from the current block to the surrounding block. The string is removed from the current block, and recorded in the surrounding block, if it exists. If the string does not exist in the current block, or a surrounding block does not exist, no error results. If the surrounding block is full, an error results.

procedure subst(var  s: string; view m: string; view r: string);

overload function subst(view s: string; view m: string; view r: string): pstring;

overload function subst(view s: string; view m: string; r: pstring): pstring;

overload function subst(view s: string; m: pstring; view r: string): pstring;

overload function subst(view s: string; m: pstring; r: pstring): pstring;

overload function subst(s: pstring; view m: string; view r: string): pstring;

overload function subst(s: pstring; view m: string; r: pstring): pstring;

overload function subst(s: pstring; m: pstring; view r: string): pstring;

overload function subst(s: pstring; m: pstring; r: pstring): pstring;

procedure substc(var s: string; view m: string; view r: string);

overload function substc(view s: string; view m: string; view r: string): pstring;

overload function substc(view s: string; view m: string; r: pstring): pstring;

overload function substc(view s: string; m: pstring; view r: string): pstring;

overload function substc(view s: string; m: pstring; r: pstring): pstring;

overload function substc(s: pstring; view m: string; view r: string): pstring;

overload function substc(s: pstring; view m: string; r: pstring): pstring;

overload function substc(s: pstring; m: pstring; view r: string): pstring;

overload function substc(s: pstring; m: pstring; r: pstring): pstring;

procedure substall(var s: string; view m: string; view r: string);

overload function substall(view s: string; view m: string; view r: string): pstring;

overload function substall(view  s: string; view m: string; r: pstring): pstring;

overload function substall(view  s: string; m: pstring; view r: string): pstring;

overload function substall(view  s: string; m: pstring; r: pstring): pstring;

overload function substall(s: pstring; view m: string; view r: string): pstring;

overload function substall(s: pstring; view m: string; r: pstring): pstring;

overload function substall(s: pstring; m: pstring; view r: string): pstring;

overload function substall(s: pstring; m: pstring; r: pstring): pstring;

procedure substcall(var s: string; view m: string; view r: string);

overload function substcall(view s: string; view m: string; view r: string): pstring;

overload function substcall(view  s: string; view m: string; r: pstring): pstring;

overload function substcall(view  s: string; m: pstring; view r: string): pstring;

overload function substcall(view  s: string; m: pstring; r: pstring): pstring;

overload function substcall(s: pstring; view m: string; view r: string): pstring;

overload function substcall(s: pstring; view m: string; r: pstring): pstring;

overload function substcall(s: pstring; m: pstring; view r: string): pstring;

overload function substcall(s: pstring; m: pstring; r: pstring): pstring;

Substitutes a string within another string. The source string s is searched for the match string m. If found, the match string is removed, and the replacement string r takes its place. The string lengths and positions are readjusted for any size difference between the match and replacement strings.

Several variations exist. The matching process can be case sensitive, or not. In normal subst calls, only the first match is replaced. In the "all" variations, all matching substrings are replaced. Replacement strings are not searched in the "all" variations. The match process skips over the replacement string.


extlib - Operating Systems Extention Library


extlib is designed to contain the essential operating system functions every serial level program might need, including directory listing, time and date, files and paths, file attributes, environment strings, and execution of external programs.

extlib tries to do that in a non-operating specific format. However, the user will recognize that it is heavily influenced by the Unix operating system. Certainly this makes sense when most computers are running variants of Unix, or systems that have themselves been heavily influenced by Unix.

Filenames and Paths

A file specification is composed of a path, name and extention:

<path><name><ext>

The exact format of a file specification changes with the operating system, and it turns out we don't need to know much about it to work with files. The routine brknam takes a file specification and breaks it down into its path, name and extention components. The routine maknam does the opposite, creating a composite name from the three components. When a file specification has no path, it means that it refers to the default path. When a file specification needs to be printed or saved in an absolute format, with the path always specified, the fulnam routine is used to "normalize" the file specification.

In many cases we need to parse file specifications from the user, which could be in a string, or a file. But the exact format differs with the implementation. To allow for this, the routine filchar returns a character set of all possible characters in a filename. This can be used to get a sequence of characters from a string or file that can constitute a file specification. Once the potential file specification has been loaded into a string, it can be checked for validity as a file specification by validfile, and as a path by validpath. If a file specification contains wildcards, this can be determined by wild.

To find common objects that a program needs, three predefined paths are provided:

Program path

Is the path that the program was executed from. This is used to find data that accompanied the program, and system wide option files. getpgm is used to read the path, setpgm is used to set it.

User Path

This is the path for the current user's home directory. This is used to store options that only apply to the current user. getusr is used to read the path.

Current Path

The default path is used to find options that apply only to the current file being worked on. getcur is used to read the path.

Time and Date

Time is kept in two different formats in extlib. The first is seconds time, and the second is "clock" time. Seconds time is literally a count of the number of seconds since a fixed reference time. Clock is a free running clock that ticks every 100 Microseconds.

Seconds time is returned by the time function. It is in a format called "S2000", and it's the number of seconds relative to midnight on the morning of January 1, year 2000. This means that S2000 has a negative value for years before 2000, and a positive value after that. This places a limit on the number of years that can be represented, from 1932 to 2068 in 32 bits. This represents a reasonable range of time, and by the year 2068 it is likely that the time will be 64 bits or better. Note that it does not matter how many bits are returned by time, so that transition will occur seamlessly.

When an S2000 time is not available, it is customary to set it to -maxint, which makes it clear that the time was not set.

Clock time is typically derived from a free running counter kept in the host computer. It may, in fact, be derived from the same timebase as time is, and on a 64 bit machine, clock could well contain enough time to determine the time and date as well. Its not a good idea to count on it, because some implementations might not have a large clock counter, and in any case it may not be synchronized to the actual time and date. Free running clocks counting off ticks from a crystal in the computer have very small differences between them and the actual time, which nowadays is determined by very accurate atomic clocks. These differences, although small, add up over time and create large discrepancies between clock and time.

Clock time is treated differently from time. Since it free runs, you must be prepared for it to "wrap", or suddenly start counting up from zero. This can be determined by if any stored time is greater, or later in time, than the current clock value. The function elapsed takes a reference time, and determines how much time has passed from that, including compensation for wraparound.

The total amount of time that can be represented with clock is determined not only by the bit size of the clock return value, but also by the size of the counter the host computer maintains. All that is guaranteed is that clock will be able to keep a unique time for at least 24 hours.

The actual increment of time for each tick of the clock is determined by the host computer. If the host cannot time to 100 microsecond accuracy, then the clock time will increment in multiples > 1.

The time returned by time is in GMT or "universal" time. To convert to local time, the function local is used. It takes the given GMT S2000 time, and offsets it by the local time zone offset, and by daylight savings time, and returns the adjusted local time.

The time can be placed, in character format, to a string by times, and written to either an output file, or the standard output by writetime. The date can be placed, in character format, to a string by dates, and written to either an output file, or the standard output by writedate.

Directory Structures

The list procedure takes a file specification, including wildcards, and returns a linked list of all of the matching directory entries:

{ attributes }

attribute = (atexec,  { is an executable file type }

             atarc,   { has been archived since last modification }

             atsys,   { is a system special file }

             atdir,   { is a directory special file }

             atloop); { contains heriarchy loop }

attrset = set of attribute; { attributes in a set }

{ permissions }

permission = (pmread,  { may be read }

              pmwrite, { may be written }

              pmexec,  { may be executed }

              pmdel,   { may be deleted }

              pmvis,   { may be seen in directory listings }

              pmcopy,  { may be copied }

              pmren);  { may be renamed/moved }

permset = set of permission; { permissions in a set }

{ standard directory format }

filptr = ^filrec; { pointer to file records }

filrec = record

 

   name:   pstring; { name of file }

   size:   integer; { size of file }

   alloc:  integer; { allocation of file }

   attr:   attrset; { attributes }

   create: integer; { time of creation }

   modify: integer; { time of last modification }

   access: integer; { time of last access }

   backup: integer; { time of last backup }

   user:   permset; { user permissions }

   group:  permset; { group permissions }

   other:  permset; { other permissions }

   next:   filptr   { next entry in list }

 

end;

Each directory file entry has the name of the file, along with a series of descriptive data for the file. These are divided in to attributes and permissions. An attribute is a characteristic of the file, and generally does not change. Permissions indicate what can be done with the file, and are divided into the user, group and other permissions from Unix.

Not all attributes nor all permissions are available on every operating system. If a particular permission or attribute is not implemented on a given operating system, then setting it will have no effect, and reading it will always return unset.

The size of the file is its size in bytes. The allocation is the total space it occupies on the storage medium, which may be different from its size for several reasons. The blocking may be such that the size is rounded up to the nearest block. The operating system may have the ability to reserve space for the file beyond what it is currently using, or may not release space back to the free space pool if the file is truncated.

The times of interesting events in the files life are available, in "S2000" format (discussed below). If a particular time is not available, then it is set to -maxint.

The file structure is a collection of items that may be implemented on any given operating system The way to prevent the need to decide what is and what is not implemented on a particular system is to focus on what is essential for all systems. For example, the size of a file is usually present, as well as the last modification time. The last modification time can be used to determine when to back up files, by comparing it to the date of the backup copy of the same file, or to the modification date of the archive containing the file. Similarly, a "make" style program can determine when to remake a file by looking at the modification time.

File Attributes and Permissions

The attributes of a file can be set by name with the setatr command. It takes a set of attributes and the filename, and sets all the given attributes on the file. The routine resatr resets attributes. The user permissions for a file are set and reset by setuper and resuper. The group permissions are set and reset by setgper and resgper. The other permissions for a file are set and reset by setoper and resoper.

Environment Strings

The environment is a collection of strings that is kept by the executive, and passed to programs when they are started. Each string has a name and a value, both of which are arbitrary strings. An environment string can be retrieved by name by getenv, and set by setenv. The entire environment string set can be retrieved at one time by the allenv routine, which uses a linked list to represent the environment strings:

{ environment strings }

envptr = ^envrec; { pointer to environment record }

envrec = packed record

 

   name: pstring; { name of string }

   data: pstring; { data in string }

   next: envptr { next entry in list }

 

end;

The reason for retrieving the entire environment is to pass it on to other programs in an exec statement.

Although virtually all operating systems implement the environment string concept, it has fallen out of favor in modern programs. Placing the needed configuration strings for a particular program into a place where the executive will use it both requires special calls, and places individual program data into a pool where it can be deleted or corrupted. A better method is to use a file containing the configuration information for the program in its startup directory, then optionally another version in the user directory. This allows options that affect all runs on the current machine to be kept in one file, while the options for a particular user are kept in another file.

Executing Other Programs

An external program can be executed by exec, which takes the command line for the program, including the program name, and all of its parameters and other options on the same line after one or more spaces:

cmd parameter parameter... parameter

This is passed as a string to the exec routine. When programs are executed this way, the executing program does not need to await the finish of the program, nor can it find out if the program ran correctly. If this is required, the routine execw is used. execw will wait for the program to complete, then place the error return for the program in a variable. This variable will be 0 if the program ran correctly, or non-zero if it didn't. The exact numerical meaning of the error is up to the program executed.

If the environment is to be set for the executed program, the call exece can be used, which takes an environment list. This allows the environment to be retrieved from the current environment or created as new, modified or added to as needed, then passed to the executed program. execew does the same thing, but waits for the program to finish, and returns an error code.

Error Return Code

Each program, when it completes, returns an error code. By convention (which comes from Unix), if the error code is 0, then no error occurred. If the code is not 0, then an error occurred, and the meaning of the number is defined by the application.

When a program under IP Pascal exits, it returns a code that was set to 0 by default when the program started. The procedure seterr can be used to set a non-zero code, which will be returned when the program exits. Note that it does NOT cause the current program to exit, it simply sets the code that will be returned when it does. It does not matter how the program exits, from the main program block, or a halt statement.

Creating or Removing Paths

A file path, or directory, is created by makpth, and removed by rempth. If the directory has files in it, then it cannot be removed until all the files (and directories) under it have been removed.

Option Character

When parsing commands, the option character for the current operating system is found with optchr.

Functions

procedure list(view f: string; var l: filptr);

overload procedure list(view f: pstring; var  l: filptr);

Form a file list. The filename f may contain wildcards, and may be fully pathed, or refer to the current directory. If no files are found, then the list pointer is returned nil.

procedure times(var s: string; t: integer);

overload function times(t: integer): pstring;

Place S2000 time in string.

procedure dates(var s: string; t: integer);

overload function dates(t: integer): pstring;

Place S2000 date in string.

procedure writetime(var f: text; t: integer);

overload procedure writetime(t: integer);

Write S2000 time to a text file, or by default, the standard output file.

procedure writedate(var f: text; t: integer);

overload procedure writedate(t: integer);

Write S2000 date to a text file, or by default, the standard output file.

function time: integer;

Returns current S2000 time in GMT

function local(t: integer): integer;

Converts the given GMT S2000 time to local time, using the time zone offset and daylight savings status in the host computer.

function clock: integer;

Returns 100 microsecond, free running time.

function elapsed(r: integer): integer;

Given a clock time, will check the current clock time and find the total number of 100 microsecond ticks since the given time. Accounts for timer wraparound.

function validfile(view s: string): boolean;

overload function validfile(view s: pstring): boolean;

Parses and checks the given file specification for a valid filename on the current system. Returns true if valid.

function validpath(view s: string): boolean;

overload function validpath(view s: pstring): boolean;

Parses and checks the given file specification for a valid path on the current system. Returns true if valid.

function wild(view s: string): boolean;

overload function wild(view s: pstring): boolean;

Checks if the current file specification contains wildcards. Returns true if so.

procedure getenv(view ls: string; var ds: string);

overload function getenv(view ls: string): pstring;

Finds and returns an environment string. ls contains the name of the string to look up, ds or the return value contains the resulting string as found. If there is no environment string by that name, the return is either all blanks, or nil.

procedure setenv(view sn, sd: string);

overload procedure setenv(sn: pstring; view sd: string);

overload procedure setenv(view sn: string; sd: pstring);

overload procedure setenv(sn, sd: pstring);

Finds and sets an environment string. sn contains the string name to set, and sd contains the contents to set it to. If there is no string by that name, it is created, otherwise the old string is replaced.

procedure allenv(var el: envptr);

Returns a complete list of the strings in the environment.

procedure remenv(view sn: string);

overload procedure remenv(view sn: pstring);

Remove a string from the environment. The string is found by name, and removed from the environment. No error results if the string does not exist.

procedure exec(view cmd: string);

overload procedure exec(cmd: pstring);

Execute external program, with parameters. Does not wait for the program to finish, and cannot detect if it finished with an error.

procedure exece(view cmd: string; el: envptr);

overload procedure exece(cmd: pstring; el: envptr);

Execute external program, with parameters and full environment. The environment is passed as a list in el. Does not wait for the program to finish, and cannot detect if it finished with an error.

procedure execw(view cmd: string; var e: integer);

overload procedure execw(cmd: pstring; var e: integer);

Execute external program, with parameters. Waits for the program to finish, and returns its error code in e. The error code is 0 for no error, otherwise the error is a code specified by the program executed.

procedure execew(view cmd: string; el: envptr; var e: integer);

overload procedure execew(cmd: pstring; el: envptr; var e: integer);

Execute external program, with parameters and full environment. The environment is passed as a list in el. Waits for the program to finish, and returns its error code in e. The error code is 0 for no error, otherwise the error is a code specified by the program executed.

procedure getcur(var fn: string);

overload function getcur: pstring;

Get current path. Returns the current directory path.

procedure setcur(view fn: string);

overload procedure setcur(fn: pstring);

Set current path. Sets the default path for all file specifications.

procedure brknam(view fn: string; var p, n, e: string);

overload procedure brknam(view fn: string; var p, n, e: pstring);

overload procedure brknam(fn: pstring; var p, n, e: pstring);

Break down file specification. Breaks the file specification fn down into path p, name n, and extention e. Note that any one of the resulting components could be blank, if it does not exist in the name.

procedure maknam(var fn: string; view p, n, e: string);

overload function maknam(view p, n, e: string): pstring;

overload function maknam(view p: string; view n: string; e: pstring):                          pstring;

overload function maknam(view p: string; n: pstring; view e: string):                          pstring;

overload function maknam(view p: string; n: pstring; e: pstring): pstring;

overload function maknam(p: pstring; view n: string; view e: string):                          pstring;

overload function maknam(p: pstring; view n: string; e: pstring): pstring;

overload function maknam(p: pstring; n: pstring; view e: string): pstring;

overload function maknam(p: pstring; n: pstring; e: pstring): pstring;

Create file specification from components. Creates file specification fn from path p, name n, and extention e. Components may be blank, but the path and the name cannot both be blank.

procedure fulnam(var fn: string);

overload function fulnam(view fn: string): pstring;

Create full file specification from partial. Given a file specification with an incomplete path (either by using the default path, or mnemonic shortcuts for things like parent directory), creates a fully pathed name of standard form. This can "normalize" file specifications, for comparisons, and to store the complete path for the file.

procedure getpgm(var p: string);

overload function getpgm: pstring;

Get the program path. Returns the program path, which is the path the program running was loaded from.

procedure getusr(var fn: string);

overload function getusr: pstring;

Get user path. Return the user path, which is a path specific to each user.

procedure setatr(view fn: string; a: attrset);

overload procedure setatr(fn: pstring; a: attrset);

Set attributes. Given a file by name fn, the attributes in the set a are set true for the file.

procedure resatr(view fn: string; a: attrset);

overload procedure resatr(fn: pstring; a: attrset);

Reset attributes. Given a file by name fn, the attributes in the set a are set false for the file.

procedure bakupd(view fn: string);

overload procedure bakupd(fn: pstring);

Set backup time current. Given a file by name fn, sets the backup time for the file as current. Backup programs should also reset the archive bit to show that backup has occurred.

procedure setuper(view fn: string; p: permset);

overload procedure setuper(fn: pstring; p: permset);

Set user permissions. Given a file by name fn, the permissions in the set p are set true for the file.

procedure resuper(view fn: string; p: permset);

overload procedure resuper(fn: pstring; p: permset);

Reset user permissions. Given a file by name fn, the permissions in the set p are set false for the file.

procedure setgper(view fn: string; p: permset);

overload procedure setgper(fn: pstring; p: permset);

Set group permissions. Given a file by name fn, the permissions in the set p are set true for the file.

procedure resgper(view fn: string; p: permset);

overload procedure resgper(fn: pstring; p: permset);

Reset group permissions. Given a file by name fn, the permissions in the set p are set false for the file.

procedure setoper(view fn: string; p: permset);

overload procedure setoper(fn: pstring; p: permset);

Set other permissions. Given a file by name fn, the permissions in the set p are set true for the file.

procedure resoper(view fn: string; p: permset);

overload procedure resoper(fn: pstring; p: permset);

Reset group permissions. Given a file by name fn, the permissions in the set p are set false for the file.

procedure seterr(c: integer);

Set program return error. The error code returned by the current program is set. This has no effect until the program exits.

procedure makpth(view fn: string);

overload procedure makpth(fn: pstring);

Make path. Creates a new path or directory. If the path already exists, it's an error.

procedure rempth(view fn: string);

overload procedure rempth(fn: pstring);

Remove path. Removes a path, or directory. The directory must exist, and must be empty of any files or other directories, or an error results.

procedure filchr(var fc: chrset); external;

function optchr: char; external;

Find file characters. Returns a set with the allowable characters in a file. This is used to parse file specification characters into a string.


sndlib - Sound Library


The addition of sound to programs can be dramatic, especially for games. Sound is divided into playback of prerecorded sounds on command, and syntheses of new sounds on the fly. In the wonderful world of computer music, these two types of sounds play off against each other. Synthesizers commonly have large memories that can play back real instrument notes on command. And completely computer based synthesizers have the ability to make computer models of instruments to increase realism.

Although sndlib is fairly agnostic about sound file formats, in synthesizers a clear winner has emerged in the form of the MIDI or Musical Instrument Digital Interface, originally by Dave Smith of Sequential Circuits, which was later further improved to General MIDI by the MIDI manufacturers association. MIDI is not only attractive for its general availability, but its flexibility. It can serve as an internal control channel just to the sound output card in the computer, and can also leave the computer via a simple plug, and control a series of instruments connected to it.

sndlib is based on MIDI, but also provides the sequencing that MIDI lacks, and smoothes over several very hardware dependent features of it. It also unifies MIDI output with waveform output, so that the two can be used together seamlessly.

Because the MIDI standard is very capable, we haven't included the typical frequency-duration interface that a lot of game makers have used. MIDI is capable of generating virtually any frequency within hearing, and controlling it in a much more advanced way as well. In short, the future is learning how to make MIDI do what you want.

Ports

A port is the basic MIDI output device. Typically, a computer has two of them, the sound card internal to the computer, and the external MIDI jack. These ports are labeled synth_out and synth_ext, respectively. A synthesizer output is opened with opensynthout(p), where p is the logical port number, from 1 to n. It can be closed with closesynthout(p). All synthesizer ports are automatically closed when the program closes.

synth_out = 1; { the default output synth for host }

synth_ext = 2; { the default output to external synth }

The total number of synthesizer output ports is found by synthout.

Notes

type note = 1..128; { note number for midi }

The basic work of making music is playing notes. MIDI can play 128 notes, numbered from 1 to 128. This ranges in frequency from 8 Hertz, or cycles per second, to 12 Kilohertz. This is approximately the range of human hearing. MIDI can also change each note in frequency enough to move it to the note next to it (and then some), so MIDI is able to reach any frequency desired.

Humans perceive a frequency that is 4 times higher as being only twice as high. If a musical note is doubled in frequency, it will be perceived as the same note one octave higher. There are twelve notes in an octave. In the lowest octave, they are:

{ the notes in the lowest octave }

 

note_c       = 1;

note_c_sharp = 2;

note_d_flat  = 2;

note_d       = 3;

note_d_sharp = 4;

note_e_flat  = 4;

note_e       = 5;

note_f       = 6;

note_f_sharp = 7;

note_g_flat  = 7;

note_g       = 8;

note_g_sharp = 9;

note_a_flat  = 9;

note_a       = 10;

note_a_sharp = 11;

note_b_flat  = 11;

note_b       = 12;

The bases of the octaves are:

{ the octaves of midi, add to note to place in that octave }

 

octave_1  = 0;   

octave_2  = 12;  

octave_3  = 24;  

octave_4  = 36;  

octave_5  = 48;  

octave_6  = 60;  

octave_7  = 72;  

octave_8  = 84;  

octave_9  = 96;  

octave_10 = 108;

octave_11 = 120;

So any note in any octave can be found by:

note+octave

For example, C in the 6th Octave:

note_c+octave_6

Notes are activated in MIDI by the noteon(p, t, c, n, v) procedure, and deactivated by noteoff(p, t, c, n, v). For both calls, the parameters mean:

p

The logical port number to output to.

t

The time to output it.

c

The channel to play it to.

n

The note to play.

v

The volume to play the note at.

We will go over each of these parameters separately. The time to play will be discussed below. For now, it can be zero, which means "now". The channel is the instrument type to play it to, for instance, a piano, or an organ, or a tuba. The note is the logical note number we saw above, one of the 128 MIDI notes. The volume gives the volume the particular note is to be played at. This might seem odd that we can control each note, but this is like a real instrument. A piano note can be louder if hit harder.

A note can either last forever, until we turn it off with noteoff, or it can stop on its own. For example, an organ plays as long as you hold the key down, but a string instrument plays a note when you pluck the string, then dies away. noteoff need not be used for these instruments, but can still be used to cause the note to be "clipped" off early, much as if the player put a hand on the string to stop it. Similarly, a noteon can be used to restart the note, even while it is playing.

Channels and Instruments

type channel    = 1..16;  { channel number }

     instrument = 1..128; { instrument number }

MIDI has from 1 to 16 logical channels, indexed by a logical channel number. Although there are 128 instruments, you can only play 16 of them at any one time. To play an instrument, it must be assigned to a channel. This is done with instchange(p, t, c, i), where p is the port, t is the time, c is the logical channel number, and i is the instrument. The instruments we can choose from are:

{ Standard GM instruments }

 

{ Piano }

 

inst_acoustic_grand        = 1;

inst_bright_acoustic       = 2;

inst_electric_grand        = 3;

inst_honky_tonk            = 4;

inst_electric_piano_1      = 5;

inst_electric_piano_2      = 6;

inst_harpsichord           = 7;

inst_clavinet              = 8;

 

{ Chromatic percussion }

 

inst_celesta               = 9;

inst_glockenspiel          = 10;

inst_music_box             = 11;

inst_vibraphone            = 12;

inst_marimba               = 13;

inst_xylophone             = 14;

inst_tubular_bells         = 15;

inst_dulcimer              = 16;

 

{ Organ }

                         

inst_drawbar_organ         = 17;

inst_percussive_organ      = 18;

inst_rock_organ            = 19;

inst_church_organ          = 20;

inst_reed_organ            = 21;

inst_accoridan             = 22;

inst_harmonica             = 23;

inst_tango_accordian       = 24;

 

{ Guitar }

 

inst_nylon_string_guitar   = 25;

inst_steel_string_guitar   = 26;

inst_electric_jazz_guitar  = 27;

inst_electric_clean_guitar = 28;

inst_electric_muted_guitar = 29;

inst_overdriven_guitar     = 30;

inst_distortion_guitar     = 31;

inst_guitar_harmonics      = 32;

 

{ Bass }

 

inst_acoustic_bass         = 33;

inst_electric_bass_finger  = 34;

inst_electric_bass_pick    = 35;

inst_fretless_bass         = 36;

inst_slap_bass_1           = 37;

inst_slap_bass_2           = 38;

inst_synth_bass_1          = 39;

inst_synth_bass_2          = 40;

 

{ Solo strings }

 

inst_violin                = 41;

inst_viola                 = 42;

inst_cello                 = 43;

inst_contrabass            = 44;

inst_tremolo_strings       = 45;

inst_pizzicato_strings     = 46;

inst_orchestral_strings    = 47;

inst_timpani               = 48;

 

{ Ensemble }

 

inst_string_ensemble_1     = 49;

inst_string_ensemble_2     = 50;

inst_synthstrings_1        = 51;

inst_synthstrings_2        = 52;

inst_choir_aahs            = 53;

inst_voice_oohs            = 54;

inst_synth_voice           = 55;

inst_orchestra_hit         = 56;

 

{ Brass }

 

inst_trumpet               = 57;

inst_trombone              = 58;

inst_tuba                  = 59;

inst_muted_trumpet         = 60;

inst_french_horn           = 61;

inst_brass_section         = 62;

inst_synthbrass_1          = 63;

inst_synthbrass_2          = 64;

 

{ Reed }

                        

inst_soprano_sax           = 65;

inst_alto_sax              = 66;

inst_tenor_sax             = 67;

inst_baritone_sax          = 68;

inst_oboe                  = 69;

inst_english_horn          = 70;

inst_bassoon               = 71;

inst_clarinet              = 72;

 

{ Pipe }

 

inst_piccolo               = 73;

inst_flute                 = 74;

inst_recorder              = 75;

inst_pan_flute             = 76;

inst_blown_bottle          = 77;

inst_skakuhachi            = 78;

inst_whistle               = 79;

inst_ocarina               = 80;

 

{ Synth lead }

                   

inst_lead_1_square         = 81;

inst_lead_2_sawtooth       = 82;

inst_lead_3_calliope       = 83;

inst_lead_4_chiff          = 84;

inst_lead_5_charang        = 85;

inst_lead_6_voice          = 86;

inst_lead_7_fifths         = 87;

inst_lead_8_bass_lead      = 88;

 

{ Synth pad }

 

inst_pad_1_new_age         = 89;

inst_pad_2_warm            = 90;

inst_pad_3_polysynth       = 91;

inst_pad_4_choir           = 92;

inst_pad_5_bowed           = 93;

inst_pad_6_metallic        = 94;

inst_pad_7_halo            = 95;

inst_pad_8_sweep           = 96;

 

{ Synth effects }

 

inst_fx_1_rain             = 97;

inst_fx_2_soundtrack       = 98;

inst_fx_3_crystal          = 99;

inst_fx_4_atmosphere       = 100;

inst_fx_5_brightness       = 101;

inst_fx_6_goblins          = 102;

inst_fx_7_echoes           = 103;

inst_fx_8_sci_fi           = 104;

 

{ Ethnic }

 

inst_sitar                 = 105;

inst_banjo                 = 106;

inst_shamisen              = 107;

inst_koto                  = 108;

inst_kalimba               = 109;

inst_bagpipe               = 110;

inst_fiddle                = 111;

inst_shanai                = 112;

 

{ Percussive }

 

inst_tinkle_bell           = 113;

inst_agogo                 = 114;

inst_steel_drums           = 115;

inst_woodblock             = 116;

inst_taiko_drum            = 117;

inst_melodic_tom           = 118;

inst_synth_drum            = 119;

inst_reverse_cymbal        = 120;

 

{ Sound effects }

 

inst_guitar_fret_noise     = 121;

inst_breath_noise          = 122;

inst_seashore              = 123;

inst_bird_tweet            = 124;

inst_telephone_ring        = 125;

inst_helicopter            = 126;

inst_applause              = 127;

inst_gunshot               = 128;

When MIDI starts up, all channels are assigned logical instrument number 1, an acoustical grand piano, with the exception of channel 10. However, remember that you might be driving MIDI outward through an external chain of MIDI cables and synthesizers, so the best practice is to specifically set instruments you are going to use.

The MIDI channel system is like a band. You make an "arrangement" based on the different instruments you are going to need, so you configure each channel with an instrument, then you can start playing. Unlike a real band, the computer can quickly change instruments during the music, and start an entirely different kind of music without skipping a beat. However, it is good practice not to count on an instrument being able to complete a note that is playing if it is swapped out of its channel for another instrument. Think of it like the Saxophone player being kicked out of the band mid note. You wouldn't expect that last note to turn out right !

We mentioned that channel 10 was an exception. This channel is always reserved for percussion (or drum) sounds. Just like a band must always have a drummer. In this channel, the notes sent have a special meaning. In fact, each note selects a different instrument:

chan_drum = 10; { the GM drum channel } 

 

{ Drum sounds, activated as notes to drum instruments }

 

note_acoustic_bass_drum = 35;      

note_bass_drum_1        = 36;     

note_side_stick         = 37;      

note_acoustic_snare     = 38;      

note_hand_clap          = 39;     

note_electric_snare     = 40;      

note_low_floor_tom      = 41;     

note_closed_hi_hat      = 42;     

note_high_floor_tom     = 43;     

note_pedal_hi_hat       = 44;     

note_low_tom            = 45;     

note_open_hi_hat        = 46;     

note_low_mid_tom        = 47;     

note_hi_mid_tom         = 48;     

note_crash_cymbal_1     = 49;     

note_high_tom           = 50;     

note_ride_cymbal_1      = 51;     

note_chinese_cymbal     = 52;     

note_ride_bell          = 53;     

note_tambourine         = 54;     

note_splash_cymbal      = 55;     

note_cowbell            = 56;     

note_crash_cymbal_2     = 57;     

note_vibraslap          = 58;   

note_ride_cymbal_2      = 59;   

note_hi_bongo           = 60;   

note_low_bongo          = 61;   

note_mute_hi_conga      = 62;   

note_open_hi_conga      = 63;   

note_low_conga          = 64;   

note_high_timbale       = 65;   

note_low_timbale        = 66;   

note_high_agogo         = 67;   

note_low_agogo          = 68;   

note_cabasa             = 69;   

note_maracas            = 70;   

note_short_whistle      = 71;   

note_long_whistle       = 72;   

note_short_guiro        = 73;   

note_long_guiro         = 74;   

note_claves             = 75;   

note_hi_wood_block      = 76;   

note_low_wood_block     = 77;   

note_mute_cuica         = 78;   

note_open_cuica         = 79;   

note_mute_triangle      = 80;   

note_open_triangle      = 81;   

Percussion instruments always stop themselves.

The same instrument can be assigned to multiple channels. Why would that be a good idea ? Well, that allows you to have an instrument harmonize with itself, playing overlapping notes.

Volume

The volume, as mentioned, can be set for each individual note. It can also be set for the entire synthesizer port by volsynth(p, t, v), where p is port, t is time (which again, can be 0 for "now"), and v is volume. Volume can even be set for each channel by volsynthchan(p, t, c, v), where p is port, t is time, c is the logical channel number, and v is volume.

The volume is "maxint ratioed". This means we don't care about the absolute value of the highest volume (it isn't the famous "10" on your stereo). Instead, it is ratioed to maxint so that 0 is off (no volume) and maxint is full on. Is maxint div 2 half volume ? Actually, no it isn't, because human ears don't work that way. But you can worry about the concept of Decibels later.

Balance between left and right can be set for each channel with balance(p, t, c, v), where p is port, t is that soon to be explained parameter time, c is channel, and v is, well, somewhat like volume. Its still maxint ratioed, except that 0 means middle, maxint means full right, and -maxint means full left.

So what does stereo sound mean in MIDI ? Well, the MIDI synthesizer makers will do their best to make the sound of the instrument just slightly different between the right and left channels. That enhances the realism for the listener. However, the rest is up to you. Along with arranging the instruments to their channels, you also will use the balance procedure to set where they end up on the virtual stage, from left to right or somewhere in between.

Time and the Sequencer

We have been talking about time, and you know that setting it to zero means "do it now". If you know a little MIDI (or a lot), you know that MIDI has no concept of time. Actually, this is the job of a sequencer. The sequencer takes all the notes, instrument changes, etc., and knows exactly when to send them out to the MIDI interface.

Lets say you want to play two notes separated by one second. You call noteon to turn on the first, then activate a timer for one second, then call noteoff to end the note (presuming it does not end itself), and noteon to start the second note. Then you sleep for another second, then call noteoff to end that.

In this case, the program has become its own sequencer. As long as you specify a 0 time, the notes, instrument changes, volume changes, etc, all happen as soon as sndlib gets your order. However, that's a lot of work. sndlib can do some or all of it for you, with its built in sequencer.

To start sndlib's sequencer, you call starttime, which starts a 100us counter running (it ticks every 10,000th of a second). Then, you specify time in terms of that counter. You can get the current time by curtime, so you can specify time relative to that. For example:

curtime+10000

means a time that is one second in the future.

What the sequencer allows you to do is "set and forget" what you are going to play. Need to play a fanfare ? Dump a few notes into the sequencer:

const second = 10000;           { one second }

      osec   = second div 8;    { 1/8 second }

 

begin

 

   noteon(synth_out,  0,               1, note_c+octave_6, maxint);

   noteoff(synth_out, curtime+osec*2,  1, note_c+octave_6, maxint);

   noteon(synth_out,  curtime+osec*3,  1, note_d+octave_6, maxint);

   noteoff(synth_out, curtime+osec*4,  1, note_d+octave_6, maxint);

   noteon(synth_out,  curtime+osec*5,  1, note_e+octave_6, maxint);

   noteoff(synth_out, curtime+osec*6,  1, note_e+octave_6, maxint);

   noteon(synth_out,  curtime+osec*7,  1, note_f+octave_6, maxint);

   noteoff(synth_out, curtime+osec*8,  1, note_f+octave_6, maxint);

   noteon(synth_out,  curtime+osec*9,  1, note_e+octave_6, maxint);

   noteoff(synth_out, curtime+osec*10, 1, note_e+octave_6, maxint);

   noteon(synth_out,  curtime+osec*11, 1, note_d+octave_6, maxint);

   noteoff(synth_out, curtime+osec*13, 1, note_d+octave_6, maxint);

   ...

The fanfare plays, and your program goes on to other work, or you can wait for the sequenced time to pass by setting a timer to the time required to finish it.

If you output a note (or other action) with a 0 time while the sequencer is running, it will still occur immediately. Time 0 always means "now". The sequencer is a "flow through" model. You can time actions and notes with the sequencer, or do it yourself, or any combination thereof.

When the sequencer is no longer required, stoptime stops it. Doing that can save processor time, and probably free up system timers.

Effects

The good news is, there are lots of effects in MIDI that can be applied to output notes. The bad news is few of them are implemented on most computer sound cards or software synthesizers. Many of them may be implemented on external, professional equipment, so the calls for them appear here. But be warned.

attack(p, t, c, at) adjusts the "attack time" of each note in channel c, for the port p, at time t. The at time is a 0..maxint ratio, where 0 is no attack time, and maxint is the maximum, which is MIDI dependent.

release(p, t, c, rt) adjusts the release or "decay" time of the note in port p,  time t, channel c, with at the 0..maxint ratioed release time.

reverb(p, t, c, r) sets the amount of reverberation, port p, time t, channel c, with 0..maxint ratioed level of reverb r.

vibrato(p, t, c, tr) sets the tremulo, port p, time t, channel c, 0..maxint ratio of tremulo tr.

chorus(p, t, c, tr) sets the tremulo, port p, time t, channel c, 0..maxint ratio of tremulo tr.

phaser(p, t, c, tr) sets the tremulo, port p, time t, channel c, 0..maxint ratio of tremulo tr.

brightness(p, t, c, tr) sets the tremulo, port p, time t, channel c, 0..maxint ratio of tremulo tr.

timbre(p, t, c, tr) sets the tremulo, port p, time t, channel c, 0..maxint ratio of tremulo tr.

aftertouch(p, t, c, tr) sets the tremulo, port p, time t, channel c, 0..maxint ratio of tremulo tr.

pressure(p, t, c, tr) sets the tremulo, port p, time t, channel c, 0..maxint ratio of tremulo tr.

legato(p, t, c, tr) sets the tremulo, port p, time t, channel c, 0..maxint ratio of tremulo tr.

portamento(p, t, c, tr) sets the tremulo, port p, time t, channel c, 0..maxint ratio of tremulo tr.

The effects, if you need them, are best simulated by other means. As an example, you can simulate release control by putting the instrument to control in its own channel, sounding the note, then lowering the volume in steps until 0, then turning the note off.

Pitch Changes

If we need a frequency that is not exactly on a note, we can "bend" the pitch with pitch(p, t, c, pt), with port p, time t, channel c, and -maxint...maxint ratioed pt. The pitch change is none for 0, and by default, one note up or down. In other words, the default pitch change range is one note up or down. A D note can be bent downwards to C, or upwards to E. The term "bend" comes from bending a string to change the note.

The default range of pitch changes can also be changed, by pitchrange(p, t, c, r), port p, time t, channel c, and pitch range r. The range r is a ratioed 0..maxint. 0 means no pitch range at all (disabled), and maxint means the full 128 notes worth of pitch range. What you pick up with total range, you lose in fine control. If the pitch range is maxint, each step of pitch change is going to be very coarse.

Pitch changes are a musician's way to get special tunings (the musician actually tunes the entire instrument up or down from a standard note), or temporary effects. For synth players and computer game makers, they are the key to getting any frequency you want, and wild effects like flying saucers, laser zappers, etc.

Prerecorded MIDI

We don't have to make all our MIDI commands on the fly. In fact, we can forget doing any MIDI, and just play back prerecorded MIDI files with playsynth(p, t, sf). The port is p, time t, and the name of the file containing the midi sequence is in sf. The format is up to the system, but MIDI also has a standard file format, so it is usually in that. If its prerecorded MIDI, why does it have a time ? Because we can weave together prerecorded tracks with our own MIDI notes, in any combination. Or we can just specify 0 time, and bypass the sequencer.

Waveform Files

Waveform files are how we get arbitrary sounds into the computer. Anything, for any length, can be played via the waveform files. Waveform outputs go out via their own ports separate from MIDI ports. Like MIDI, however, they are selected via logical numbers from 1 to n, where n is the maximum number of waveform output devices on the computer. The total number of waveform devices present in the system can be found via waveout. By convention, the normal wave output device is 1.

Waveform devices must be opened and closed individually. They are opened with openwaveout(p), and closed with closewaveout(p).

A waveform file is played with playwave(p, t, sf). The port is the logical waveform port. The time is t. The name of the waveform file to read is the string in sf. Waveform files are usually very system dependent, so the exact format of the file will be different for different systems. Typically, it is best to specify the base name, leave off the extention, and let the operating system find the right file.

Ok, now why is there time on this, a very non-MIDI output type ? Because this allows you to use sndlib's sequencer to mix waveform files, together with MIDI prerecorded files, and even individual notes, all correctly timed. The sequencer is the workhorse of sndlib. Or, you can just set time 0 if preferred.

The playback volume for waveform files is adjusted separately from MIDI with the volwave(p, t, v) call. The port is p, time t, and v is a 0..maxint ratio volume level.

One of the unfortunate limitations with waveform files is that some systems cannot play more than one file at a time. This is a serious limitation for things like games, where you often need more than one sound occurring at once. Hopefully later versions of sndlib will have automatic mixer capabilities.

Functions

procedure starttime;

Start time for sequencer. Starts the sequencer running. If the sequencer is already running, it will be restarted at 0.

procedure stoptime;

Stop sequencer. Halts the sequencer timer, and releases it.

function curtime: integer;

Get current sequencer time. Returns the current sequencer time, in 100 Microsecond counts. The count is guaranteed not to wrap for 24 hours.

function synthout: integer;

Find number of output synthesizers. Returns the total output synthesizers in the system.

procedure opensynthout(p: integer);

Open output synthesizer. Opens the output synthesizer by the logical number p, where p is 1..synthout.

procedure closesynthout(p: integer);

Close output synthesizer. Closes the output synthesizer by the logical number p.

procedure noteon(p, t: integer; c: channel; n: note; v: integer);

Start note. Starts a note for synthesizer p, in channel c, with note, and 0..maxint ratioed volume v.

procedure noteoff(p, t: integer; c: channel; n: note; v: integer);

Stop note. Stops a note for synthesizer p, in channel c, with note, and 0..maxint ratioed volume v. v is usually ignored on a noteoff.

procedure instchange(p, t: integer; c: channel; i: instrument);

Change instrument. Changes the instrument assigned to a channel, for output port p, at time t, for channel c, to instrument i.

procedure attack(p, t: integer; c: channel; at: integer);

Set attack time. Sets the attack time for synthesizer output port p, at time t, for channel c, to 0..maxint ratioed time at.

procedure release(p, t: integer; c: channel; rt: integer);

Set release time. Sets the release time for synthesizer output port p, at time t, for channel c, to 0..maxint ratioed time at.

procedure legato(p, t: integer; c: channel; b: boolean);

Set legato. Sets legato mode on or off, for synthesizer output port p, at time t, for channel c, to on/off value b.

procedure portamento(p, t: integer; c: channel; b: boolean);

Set portamento. Sets portamento mode on or off, for synthesizer output port p, at time t, for channel c, to on/off value b.

procedure vibrato(p, t: integer; c: channel; v: integer);

Set vibrato. Sets vibrato amount, for synthesizer output port p, at time t, for channel c, to 0..maxint ratioed value v.

procedure volsynthchan(p, t: integer; c: channel; v: integer);

Set volume for channel. Sets volume for channel, for synthesizer output port p, at time t, for channel c, to 0..maxint ratioed value v.

procedure porttime(p, t: integer; c: channel; v: integer);

Set portamento time. Sets portamento time, for synthesizer output port p, at time t, for channel c, to 0..maxint ratioed value v.

procedure balance(p, t: integer; c: channel; b: integer);

Set channel balance. Sets the right left balance for synthesizer output port p, at time t, for channel c, to -maxint..maxint ratioed value. -maxint is full left, maxint is full right, and 0 is centered.

procedure pan(p, t: integer; c: channel; b: integer);

Set channel pan. Sets the right left pan for synthesizer output port p, at time t, for channel c, to -maxint..maxint ratioed value. -maxint is full left, maxint is full right, and 0 is centered.

procedure timbre(p, t: integer; c: channel; tb: integer);

Set timbre. Sets timbre amount, for synthesizer output port p, at time t, for channel c, to 0..maxint ratioed value tb.

procedure brightness(p, t: integer; c: channel; b: integer);

Set brightness. Sets brightness amount, for synthesizer output port p, at time t, for channel c, to 0..maxint ratioed value b.

procedure reverb(p, t: integer; c: channel; r: integer);

Set reverb. Sets reverb amount, for synthesizer output port p, at time t, for channel c, to 0..maxint ratioed value r.

procedure tremulo(p, t: integer; c: channel; tr: integer);

Set tremulo. Sets tremulo amount, for synthesizer output port p, at time t, for channel c, to 0..maxint ratioed value tr.

procedure chorus(p, t: integer; c: channel; cr: integer);

Set chorus. Sets chorus amount, for synthesizer output port p, at time t, for channel c, to 0..maxint ratioed value cr.

procedure celeste(p, t: integer; c: channel; ce: integer);

Set celeste. Sets celeste amount, for synthesizer output port p, at time t, for channel c, to 0..maxint ratioed value ce.

procedure phaser(p, t: integer; c: channel; ph: integer);

Set phaser. Sets phaser amount, for synthesizer output port p, at time t, for channel c, to 0..maxint ratioed value ph.

procedure aftertouch(p, t: integer; c: channel; n: note; at: integer);

Set aftertouch. Sets aftertouch amount, for synthesizer output port p, at time t, for channel c, to 0..maxint ratioed value at.

procedure pressure(p, t: integer; c: channel; n: note; pr: integer);

Set pressure. Sets pressure amount, for synthesizer output port p, at time t, for channel c, to 0..maxint ratioed value pr.

procedure pitch(p, t: integer; c: channel; pt: integer);

Set pitch bend. Sets the pitch "bend", or change amount, for synthesizer output port p, at time t, for channel c, to -maxint..maxint ratioed value pt. pt value is -maxint for full down range, maxint for full up range, and 0 for neutral (on note) pitch. The amount of pitch range is set by the ptichrange procedure, and defaults to one note down and one note up.

procedure pitchrange(p, t: integer; c: channel; v: integer);

Set pitch bend range. Sets the total amount of pitch change that can be reached by the pitch command, for synthesizer output port p, at time t, for channel c, to 0..maxint ratioed value v. 0 disables the pitch command, and maxint allows it to reach all 128 notes of MIDI. Note that increasing the range of the pitch command decreases its resolution.

procedure mono(p, t: integer; c: channel; ch: integer);

Set mono mode. Sets mono mode for synthesizer output port p, at time t, for channel c, for the number of channels ch. See MIDI specification for details.

procedure poly(p, t: integer; c: channel);

Set polyphonic mode. Sets polyphonic mode for synthesizer output port p, at time t, for channel c. Reverses the effect of a mono operation.

procedure playsynth(p, t: integer; view sf: string);

Play MIDI synthesizer file. Plays the MIDI instructions from the file by the name in sf, for output synthesizer p, at time t.

function waveout: integer;

Find number of waveform output files. Returns the total number of waveform files in the system.

procedure openwaveout(p: integer);

Open waveform device. Opens the logical waveform device p, where p is 1..waveout.

procedure closewaveout(p: integer);

Close waveform device. Closes the logical waveform device p.

procedure playwave(p, t: integer; view sf: string);

Play waveform file. Plays the waveform file by the name sf, for output waveform device p, at time t.

procedure volwave(p, t, v: integer);

 Set waveform volume. Sets the output waveform device volume for logical device p, at time t, to 0..maxint ratioed value v.


trmlib - Terminal Mode Output Library


Standard ISO 7185 Pascal uses an I/O paradigm that is serial, or more correctly called "line oriented". Each line is built up in sections, then output to the I/O device with an appended "end of line". The end of line causes the current line to be completed, and the next line begins.

The line oriented protocol worked well for the output devices up to the 1960's, which were teletypes, line printers, card punches and readers, and paper tape punches and readers. By the 1960's, a new paradigm began to replace the line oriented I/O methods, the "glass teletype", or CRT terminal. The CRT terminal was designed to emulate the device it replaced, namely the teletype, and had a keyboard, and introduced a method to emulate the look of paper being printed, called a "scrolling display".

In the simple emulation of the first CRT terminals, the display scrolled up when an end of line was output at the last line of the terminal. It also became common to automatically wrap the line if printed past the right end, so that the text would not print off the right side of the page.

As useful as a CRT terminal was to replace a teletype, however, it soon became obvious that it had capabilities that the teletype did not, such as the ability to place text anywhere on the screen, and the ability to overwrite old text. As commands to locate the "cursor" arbitrarily, and backspace over text were implemented, it became clear that CRT terminals that emulated teletypes had a problem in that they were not a good match for the paradigm of presenting an array of characters on an x-y grid. For example, the character on the bottom right side of the screen could not be written to, because writing it would trigger scrolling.

The terminal paradigm is still with us as a window that a console shell executes within, and it is still a very useful paradigm. trmlib implements that paradigm for IP Pascal programs. It intercepts the "input" and "output" files, and all routines that operate on them, and implements the terminal I/O surface for the program to write to. trmlib is completely upward compatible with the serial library, and ISO 7185 Pascal. Any program that runs under serial mode IP Pascal will also run under trmlib, without modification.

To access the greater capabilities of the terminal mode, advanced calls are used in conjunction with standard read and write calls. This allows access to the full x-y surface, attributes and colors, multiple text buffers, n-way scrolling, and advanced input devices such as the mouse or even a joystick.

This window is running a text editor under trmlib. It uses x-y positioning, and text colors to work.

Able to use timers, a mouse, a joystick, and even sound (via sndlib), even full interactive games can be programmed via trmlib.

Standard Output

To write data to the trmlib screen, ordinary Pascal write calls are used. A write statement places each character on the screen, then moves the cursor to the right, obeying any automatic line wrapping. If a writeln is called, then  The cursor is returned to the left side of the screen, one line down. If the end of the line is reached, and automatic scrolling is enabled, then the screen will scroll upwards.

The page procedure, which causes a printer to move to the next (blank) page, is emulated by blanking the screen, then placing the cursor at the upper left hand corner of the screen.

Basic Cursor Positioning

The cursor is the point where text is entered onto the screen. Its usually marked with a blinking block or underline. To move the cursor one character up, down, left or right, the up, down, left and right procedures are used. To move the cursor anywhere on the screen, the cursor call is used. To find out where the cursor currently is, the functions curx and cury return the current x and y coordinates of it.

The character cells on the screen are labeled from 1 to N, where in x it's 1 to the left side of the screen, and N is the right side of the screen. In y it's 1 at the top of the screen, and N at the bottom of the screen.

The actual size of the screen is system specific, and can be found by maxx and maxy, which return the maximum index in x and y for the screen. When a terminal is emulated in a windowing system, it usually by default 25 lines of 80 characters each, because that was a very common size in terminals.

Automatic Mode

Automatic mode is the mode that causes trmlib to scroll upwards when the bottom of the screen is reached, and an eoln is written. Also, line wrap, or the wrapping of characters back to the left at the next line if text is written off the right hand side, is an automatic mode. The automatic mode is useful for emulating serial mode programs, but rapidly gets in the way for advanced programs.

Automatic mode can be turned off with auto. Turning auto off converts (or actually, reveals) the screen as a character surface that goes from -maxint to +maxint in both x and y, and has its origin at 1,1, and the screen is a "viewport" on this surface that extends from 1,1 to maxx, maxy. Text can be drawn anywhere, including off screen, but the characters outside of the 1,1 to maxx, maxy box will not be seen, and are "clipped out" of view. It can be determined if the cursor lies within the screen's bounds by curbnd.

The "virtual screen" that appears with auto off may seem strange at first, but it's a good match for today's windowing environments. Text can be drawn without worrying if it will cause the line to wrap, or the screen to scroll. And if a line of text happens to extend off the screen, that does not cause an error.

Tabbing

trmlib will keep track of tabs set in x, for any character position. When trmlib starts, the tabs are set to every 8th position on the screen, i.e., positions x = 9, 17, etc.

Outputting a tab character ('\ht') will cause the cursor to move to the next tab position on the line. If the cursor is at a tab position, then it will move to the next one. Tab positions can be set by settab, and cleared by restab. clrtab clears all set tabs.

Scrolling

The screen can be scrolled by the scroll routine. Actually, it implements true arbitrary scrolling. If the x value given is positive, then the screen data scrolls up. If the x value is negative, then the screen data scrolls down. Likewise, if the y value is positive, the screen data scrolls left, and if it's negative, the screen data scrolls right. If either x or y is 0, then there is no movement in that direction.

Colors

There are two colors to set for text. One is the foreground, and the other is the background. The foreground is the color within the character itself. The background is the space behind the character. The possible colors are chosen from the two sets of primary colors:

type color = (black, white, red, green, blue, cyan, yellow, magenta);

Characters are written in the currently set foreground and background colors. The foreground color is set by fcolor, and the background color by bcolor.

The current background color is also used to set the color of any blanked out areas caused by other commands. For example, the page procedure clears the screen to the background color. Scrolls, either programmer selected or automatic, use the background color for any uncovered areas that are blanked out.

For a program to be flexible, it should not rely on the fact that any more than 2 colors, black and white, are available.

Attributes

trmlib provides many attribute controls, each of which can be switched on or off individually.

reverse

Enables reverse video.

underline

Underlines each character.

superscript

Gives a smaller and higher character for subscripting.

subscript

Gives a smaller and lower character for superscripting.

italic

Prints in italic or slanted characters.

bold

Prints in extra dark, or bold characters.

Strikeout

Prints characters with a horizontal bar through them.

blink

Blinks each character on and off.

For programs to maintain portability, it is recommended that the programmer assume two things. First, that only a single attribute at one time can be set. This would mean that setting a second attribute after a first one is set, would cause the first attribute to be unset.

Second, don't assume that any one particular attribute is available. Many older terminals did not have more than one or two attributes. Superscript, subscript, italic and strikeout were rare in standalone terminals. Superscript, subscript, italic and bold are rare today on graphical windowing systems because they alter the geometry of the characters such that it is difficult or impossible to emulate a character cell in such a system. blink is also rare in graphical systems because of the computational work required.

For this reason, there is a general mode, standout, that can be enabled or disabled just like an attribute. What standout does is enable an attribute the terminal does have, so that the program does not have to select a specific attribute.

Multiple Surface Buffering

trmlib implements up to 10 logical screens in buffers. This is a copy of the characters on the screen, saved in a buffer of the same size as the screen. Logical buffers have many uses. Multiple screens can be kept, and quickly switched to the user. A program, like an editor, that wants to save the screen as it was before it started, can switch away to a second screen to do its work, then switch back to the original screen to restore its contents. Buffers can also be used to accomplish animation.

The select procedure sets both the current screen to be displayed, and also the screen that updates are to go to. When trmlib starts, these are both set to the same screen, number 1. Any screen can be selected, from 1 to 10, and the display screen and the update screen can be different. If the update screen is not the one in display, then all of the write statements will place characters in the buffer, but not on screen. This "split mode" is typically used for animation.

Advanced Input

trmlib implements an advanced output model that is upward compatible with the old serial model. Similarly, an advanced input model is also implemented that meets the needs of newer systems.

Today's programs have to deal with multiple input devices, not just the keyboard. Devices include a mouse, a joystick, timers, and other future devices. The standard method on small computers was to poll for such devices, or accept interrupts from them. The difficulty with those solutions was that these methods did not fit well with modern multitasking systems.

One method would be to use a multitask thread per device. However, this complicates small programs unnecessarily. The common solution today is "events", and the "event loop". The event model takes advantage of the fact that user input devices don't generate a lot of high bandwidth data. Each of the devices attached to the input from the user have their data packaged as "messages", which are small records, which are the description of the event. These events are then placed in a queue, and the program pulls the events, one at a time, from the queue and acts on them.

The description of an event record is:

const maxtim = 10; { maximum number of timers available }

 

type joyhan = 1..4; { joystick handles }

     joynum = 0..4; { number of joysticks }

     joybut = 1..4; { joystick buttons }

     joybtn = 0..4; { joystick number of buttons }

     joyaxn = 0..3; { joystick axies }

     mounum = 0..4; { number of mice }

     mouhan = 1..4; { mouse handles }

     moubut = 1..4;  { mouse buttons }

     timhan = 1..maxtim; { timer handle }

     funky  = 1..100; { function keys }

     { events }

     evtcod = (etchar,    { ANSI character returned }

               etup,      { cursor up one line }

               etdown,    { down one line }

               etleft,    { left one character }

               etright,   { right one character }

               etleftw,   { left one word }

               etrightw,  { right one word }

               ethome,    { home of document }

               ethomes,   { home of screen }

               ethomel,   { home of line }

               etend,     { end of document }

               etends,    { end of screen }

               etendl,    { end of line }

               etscrl,    { scroll left one character }

               etscrr,    { scroll right one character }

               etscru,    { scroll up one line }

               etscrd,    { scroll down one line }

               etpagd,    { page down }

               etpagu,    { page up }

               ettab,     { tab }

               etenter,   { enter line }

               etinsert,  { insert block }

               etinsertl, { insert line }

               etinsertt, { insert toggle }

               etdel,     { delete block }

               etdell,    { delete line }

               etdelcf,   { delete character forward }

               etdelcb,   { delete character backward }

               etcopy,    { copy block }

               etcopyl,   { copy line }

               etcan,     { cancel current operation }

               etstop,    { stop current operation }

               etcont,    { continue current operation }

               etprint,   { print document }

               etprintb,  { print block }

               etprints,  { print screen }

               etfun,     { function key }

               etmenu,    { display menu }

               etmouba,   { mouse button assertion }

               etmoubd,   { mouse button deassertion }

               etmoumov,  { mouse move }

               ettim,     { timer matures }

               etjoyba,   { joystick button assertion }

               etjoybd,   { joystick button deassertion }

               etjoymov,  { joystick move }

               etterm);   { terminate program }

     { event record }

     evtrec = record

 

        case etype: evtcod of { event type }

 

           { ANSI character returned }

           etchar:   (char:                char);

           { timer handle that matured }

           ettim:    (timnum:              timhan);

           etmoumov: (mmoun:               mouhan;   { mouse number }

                      moupx, moupy:        integer); { mouse movement }

           etmouba:  (amoun:               mouhan;   { mouse handle }

                      amoubn:              moubut);  { button number }

           etmoubd:  (dmoun:               mouhan;   { mouse handle }

                      dmoubn:              moubut);  { button number }

           etjoyba:  (ajoyn:               joyhan;   { joystick number }

                      ajoybn:              joybut);  { button number }

           etjoybd:  (djoyn:               joyhan;   { joystick number }

                      djoybn:              joybut);  { button number }

           etjoymov: (mjoyn:               joyhan;   { joystick number }

                      joypx, joypy, joypz: integer); { joystick coordinates }

           etfun:    (fkey:                funky);   { function key }

           etup, etdown, etleft, etright, etleftw, etrightw, ethome, ethomes,

           ethomel, etend, etends, etendl, etscrl, etscrr, etscru, etscrd,    

           etpagd, etpagu, ettab, etenter, etinsert, etinsertl, etinsertt,

           etdel, etdell, etdelcf, etdelcb, etcopy, etcopyl, etcan, etstop,    

           etcont, etprint, etprintb, etprints, etmenu,

           etterm: (); { normal events }

 

        { end }

 

     end;

The next event for the program is retrieved via the event call. The basis of the event system is that events must be retrieved often enough that the input queue does not fill up. Thus, an "event model" program will be centered around the event loop that gets the next event, acts on it, and returns to the top for more events.

The event system is fully downward compatible with the read/readln Pascal methods for input. How can this be ? When read or readln is called for input, trmlib calls event on behalf of the program, discards any record that is not standard character input, and returns the characters read to the program. Your program can choose either the standard read/readln system, or move to the event system. The two can even be mixed, but you will find that it is easier to move to a completely event driven program.

Events are not a perfect paradigm, although they have enjoyed much hype. For example, events are a bad match for input from the high speed Internet. They also have serious limitations for handling real time events such as interrupts. As with any programming model, both the good and the bad must be considered.

Timers

Timers allow a trmlib program to perform periodic events, such as screen updates, and keeping track of time. From 1 to 10 timers are available, numbered 1..10. Each timer is given a time to measure, in 100 Microsecond counts (see extlib for more details). When the timer is done, it sends an event to the event queue.

Timers can either simply stop when their time is done, or they can automatically start timing their original set time again. This is the "recurrent" mode, and its useful when an activity needs to be performed periodically for the life of the program. For example, updating the screen on an active program, or checking when to update a clock display.

Timers are set by timer. A timer can be stopped or "killed" by killtimer. Killing a timer that is not active will not generate an error. This allows a single run timer to be killed without worrying if it will time out during the call.

It is important to understand that due to the queuing nature of the event system, there is no guarantee about the accuracy of a timer. It can arrive later than its set time. If the program is late getting back to the event queue, or is busy with other events that occur before the timer, receipt of the timer event can be very late. All that receiving a timer event does is tell you that the time it measures has passed.

An example of what this means is the display of a clock, using the extlib time call. If we set a recurrent timer to go off on every second, the program should NOT simply advance the second on each timer event. Instead, the timer event should tell the program to read time to determine if the second has changed, and what value it currently has.

Mouse

The mouse gives a position x,y on the screen, as well as from 1 to 4 buttons on it. A mouse actually gives its position as relative movements in x and y, but the system converts this to a screen position for you. Its possible to have from 0 to 4 mice, and the function mouse returns the number of mice attached to the system.

Mice generate two events. First, when the mouse moves, it generates position changes via the etmoumov event. The program does not have to worry about where it is going. Each time the position changes, a new x, y position is posted as an event.

The second event generated by a mouse is mouse button asserts and deasserts, etmouba and etmoubd. An "assert" means a press of the button, and a "deassert" is the release of the button. Note that instead of an on/off status check as polled by the program, the assert and deassert events give exact notice of when the button changes state, and what it is changing to.

When there is more than one mouse per system, the default behavior should be to treat them as separate, but equal controls on the screen from the same user. When a mouse moves or changes button state, it gets control of the program. This matches the common use of multiple pointing devices, where two devices are alternated by one user. For example, a trackball and a mouse may be just two different input methods from one user.

Alternately, a second mouse can actually be a remote mouse over a network. This "collaborative computing" model allows two users to look at the same document, with separate mice..

Joysticks

From 0 to 4 joysticks may be supported. Each joystick can have from 0 to 3 "axes" of directions of travel. In addition, each joystick can have 0 to 4 buttons. The function joystick returns the number of joysticks in the system. The function joyaxis gives the number of axes on a given joystick. The function joybutton gives the number of buttons on a given joystick.

The messages etjoyba or joystick button assert, and etjoybd or joystick button deassert, give events for the assertion and deassertion of the buttons on a joystick. When any axis on a joystick moves, it generates a etjoymov, or joystick movement, event. This event gives the relative setting of each axis of the joystick.

Unlike a mouse, a joystick is entirely relative. Each axis is represented by an integer. If the axis is not implemented it always reads 0. If the axis is deflected left, up or in, depending on the type of axis, then it is negative. If it is right, down, or out, it is positive. The axis is determined in its amount of deflection. If it is in the middle, it is 0. If it is deflected to the maximum, it is maxint, with the sign giving the direction.

By convention, the axes on a joystick are:

1. Left/right, or slider.

2. Up/down

3. In/out

Joysticks are "rate limited" devices. This means that no matter how fast the joystick is "slewed", it will only produce an event about every 10 milliseconds at maximum rate. The reason for this is that humans cannot generally perceive events like movement of a pointer across the screen at a faster rate than this, so there is no point in updating faster than that, and flooding the input event queue.

Function Keys

The system may have from 0 to 100 function keys, which are keys whose function are determined by the program. The number of function keys implemented are found with funkey. Function key messages are sent by the message etfun.

Functions

procedure cursor(var f: text; x, y: integer);

Set cursor location in x and y.

function maxx(var f: text): integer;

Find maximum screen location x.

function maxy(var f: text): integer;

Find maximum screen location y.

procedure home(var f: text);

Send cursor to 1,1 location (upper left of screen).

procedure del(var f: text);

Back up cursor by one character, and erase character at that location. If the cursor is at the left side of the screen, and automatic mode is on, the cursor will be moved up one line, and to the right of screen. If the cursor is at the top of the screen, extreme left, then the screen will be scrolled down one line, and the cursor moves to the right side of the screen.

procedure up(var f: text);

Move cursor up one line. If the cursor is already at the top line, and automatic mode is on, the screen will be scrolled down, and the cursor remains at the same position.

procedure down(var f: text);

Move cursor down one line. If the cursor is already at the bottom line, and automatic mode is on, the screen will be scrolled up, and the cursor remains at the same position.

procedure left(var f: text);

Back up cursor by one character. If the cursor is at the left side of the screen, and automatic mode is on, the cursor will be moved up one line, and to the right of screen. If the cursor is at the top of the screen, left, then the screen will be scrolled down one line, and the cursor moves to the right side of the screen.

procedure right(var f: text);

Move forward by one character. If the cursor is at the right side of the screen, and automatic mode is on, the cursor will be moved down one line, and to the left of screen. If the cursor is at the bottom of the screen, right, then the screen will be scrolled up one line, and the cursor moves to the left side of the screen.

procedure blink(var f: text; e: boolean);

Causes all further characters written to the screen to appear in blinking text.

procedure reverse(var f: text; e: boolean);

Causes all further characters written to the screen to appear in reverse text.

procedure underline(var f: text; e: boolean);

Causes all further characters written to the screen to appear in underlined text.

procedure superscript(var f: text; e: boolean);

Causes all further characters written to the screen to appear in superscript text.

procedure subscript(var f: text; e: boolean);

Causes all further characters written to the screen to appear in subscript text.

procedure italic(var f: text; e: boolean);

Causes all further characters written to the screen to appear in italic text.

procedure bold(var f: text; e: boolean);

Causes all further characters written to the screen to appear in bold text.

procedure strikeout(var f: text; e: boolean);

Causes all further characters written to the screen to appear in strikeout text.

procedure standout(var f: text; e: boolean);

Causes all further characters written to the screen to appear in standout text. Standout is assigned to the first mode possible from the following order:

1. Reverse.

2. Underline.

3. Bold

4. Italic.

5. Strikeout.

6. Blink.

If none of those modes are available, standout is a no-op.

procedure fcolor(var f: text; c: color);

Sets the foreground, or text color, to the color c.

procedure bcolor(var f: text; c: color);

Sets the background, or space color, to the color c.

procedure auto(var f: text; e: boolean);

Turns automatic mode on or off.

procedure curvis(var f: text; e: boolean);

Turns cursor visibility on or off.

procedure scroll(var f: text; x, y: integer);

Scroll in aribitrary directions. The screen is scrolled according to the differences in x and y. Uncovered areas on the screen appear in the current background color.

function curx(var f: text): integer;

Find the current x location of the cursor.

function cury(var f: text): integer;

Find the current y location of the cursor.

function curbnd(var f: text): boolean;

Check cursor in bounds. Returns true if the cursor is currently within the bounds of the screen.

procedure select(var f: text; u, d: integer);

Select buffer to update and display. Selects the active buffer for update u, and display d. The update buffer will receive the result of all writes. The display buffer will be shown on screen.

procedure event(var f: text; var er: evtrec);

Get next event. Retrieves the next event from the input queue. If there is no event ready, the program will wait.

procedure timer(var f: text; i: timhan; t: integer; r: boolean);

Set timer active. The timer i will be set to run for time t. If the repeat flag r is set, then the timer will automatically repeat when the time expires. If the timer is already in use, then it will cease its current timing, and perform the new time.

procedure killtimer(var f: text; i: timhan);

Stop timer. Stops the timer i. If the timer is not active, no error is reported.

function mouse(var f: text): mounum;

Returns the number of mice in the system.

function mousebutton(var f: text; m: mouhan): moubut;

Returns the number of buttons on a given mouse m.

function joystick(var f: text): joynum;

Returns the number of joysticks in the system.

function joybutton(var f: text; j: joyhan): joybtn;

Returns the number of buttons on the given joystick j.

function joyaxis(var f: text; j: joyhan): joyaxn;

Returns the number of axes on the given joystick j.

procedure settab(var f: text; t: integer);

Set new tab. Sets a new tab location at t.

procedure restab(var f: text; t: integer);

Reset tab. Removes the tab at location t. If there is not a tab set there, it is not an error.

procedure clrtab(var f: text);

Clear all tabs. All tabs are removed from the tabbing table.

function funkey(var f: text): funky;

 Returns the number of function keys available.


gralib - Graphical Mode Output Library


The graphical library gralib extends the trmlib model by adding graphical output procedures.

 

Since it is completely upward compatible with trmlib, and standard ISO 7185 serial output modes, any program from standard Pascal, or trmlib compliant IP Pascal will run under gralib. Even in the most advanced modes of gralib, ordinary Pascal write statements can still be used to output text, so all of the output formatting procedures you are used to will still work.

gralib will handle any graphics task. Besides drawing features, it supports double page animation. Combined with the sound library sndlib, full graphical games are possible.

Terminal model

gralib emulates trmlib by setting up a "character grid" across the pixel based screen. Each "cell" on the grid matches the pixel height and width of a character. The character font is set to a fixed font by default when gralib starts. This gives every character in the font the same height and width. Finally, the text drawing mode is set such that both the foreground (the inside of the characters) and the background (the space behind the characters) are drawn, overwriting any content of each character cell as it is written.

This mode can be kept, while drawing other figures on the same text surface. Alternately, the cursor can be set to any arbitrary pixel on the screen, and text written anywhere, down to the pixel. Also, the font can be changed to a proportional one for a more pleasing look. Because automatic mode relies on characters being neatly placed on the grid, it must be turned off before the cursor leaves the grid or becomes a non-fixed font.

Graphics Coordinates

The total size of the graphics screen is found by maxxg and maxyg, which return the maximum pixel index in x and y. The pixel coordinates on the screen are from 1,1 to maxxg(f), maxyg(f). The cursor can be set to any pixel position by cursorg(f, x, y). The current location of the cursor in pixel terms is found by curxg and curyg.

Character Drawing

There can be any number of fonts available on the system, including both fixed, and proportional fonts that vary in the width of characters. The number of fonts in the system can be found with the font function. Fonts are chosen by logical number with font(f, c), where c is the font code, 1 to font. Of course, you don't know what font each code standards for. There are two methods. The first is the standard font codes. Standard fonts are numbers for commonly used fonts in the system. These are just commonly available fonts the program may need.

Font 1: Terminal Font

This is the default font set up by gralib when it starts. It's a fixed font. It also cannot be superscripted, subscripted, bold or italic, because these modes change the size of the font.

Font 2: Book Font

This font has serifs, and is good for general purpose text such as what this paragraph is written in. This is the most common proportional font.

Font 3: Sign Font

This font has no serifs (sans serif), and is best for headings, titles and similar uses. It gets its name from the road and other signs it is used on. It's a proportional font.

Font 4: Technical Font

The technical font is a fixed font that is guaranteed to be able to scale to any arbitrary size. This font is used to label drawings and engineering documents. Before the technology existed to create arbitrary fonts in any point size, the technical font was done with stroked vector graphics. Now, it is more likely to be equivalent to the sign font.

Beyond the standard fonts, the name of an installed font can be found by fontnam(f, fc, fns), where fc is the font code, 1 to font, and fns returns the descriptive string for the font, like "Helvetica" or similar. It isn't really intended that the program try to match font name strings against font strings from this call. Rather, the application should use the standard fonts by default, then present the system fonts, by name to the user, and let them choose one of them.

The size of each character is set by fontsiz(f, n), where n is the height of the font in pixels. The reason character sizes are set by their height is because proportional fonts vary in the width of the character. The height never varies. The current height of the font is found by chrsizy. Its width is found with chrsizx. Of course, we just got though saying that fonts have no standard width! When a proportional font is active, chrsizx returns the width of a space in that font, which is always as wide or wider than the widest character in that font.

Besides the basic font, extra space can be added between lines (known as "leading" in typography, for the lead strips used between type lines) with chrspcy(f, n). n is the number of pixels of extra space to add between lines. Extra space between characters is added with chrspcx(f, n), where n is the number of pixels of extra space to add between characters.

The graphical cursor is placed at the upper left of the character box for the next character to be drawn. Often, we need to know exactly where the character will rest if drawn on a line. The offset from the top of the character box to the baseline of the text is found with the function baseline.

In typography, fonts and characters are measured by points, of which there are 28.35 points per centimeter. Point measurement implies that you know the exact size of objects drawn on the screen. This can be determined by the functions dpmx and dpmy, which return the "dots per meter" or pixels in one meter for both x and y. The reason it can be two different measures for the two different axes is that the display may not have square pixels or a 1:1 aspect ratio.

To find a given point size in terms of the height needed for the character, it is found by:

dpmy/2835*point size

The screen aspect ratio can also be found from these calls, it is:

dpmx/dpmy

String Sizes and Kerning

When writing text to the screen in proportional font, we often need to know exactly how much space the string will take up on the screen, down to the pixel. This amount of space can change not only because of the variable width of proportional fonts, but also because of an effect called "kerning". When a string of characters is draw together, the system can apply "kerning" to characters in the string. Kerning means to fit the individual letters together, like puzzle pieces, to come up with a tighter spacing than is normally possible. For example, "A" and "V" together as "AV" can typically be kerned together to take less space because they can overlap. To find the exact number of pixels in x that a string will occupy, the function strsiz(f, s) is used, where s is the string. The size of the string in y does not change, and can be found with chrsizy. To find the exact position, in pixels offset from the beginning of the string in x, of a given character, use chrpos(f, s, n), where s is the string, and n is the index of the character you want the x offset of.

Justification

Justification is the spreading of spacing through a string of characters to fit a given space. You can find out if the string will fit into the space with strsiz(f, s), and checking if the resulting pixels required are less than or equal to the space they will occupy as justified. The character string is written in justified mode with writejust(f, s, n), where s is the string to write, and n is the number of pixels to fit it in. If the number of pixels allowed for is not enough, the string will be larger than the requested number. To find the offset, in x, of a given justified character, use justpos(f, s, p, n), where s is the string, p is the offset of the character you are interested in, and n is the total number of pixels to fit the string in, as in writejust.

Effects

gralib expands the effects in trmlib a bit. For smaller character baselines, use condensed(f, b). For larger character baselines, use extended(f, b).

In addition to normal bold, there are also light(f, b), xlight(f, b), and xbold(f, b) effects. for lighter than normal, extra light, and extra bold modes.

Characters can have an embossed look with hollow(f, b) and raised(f, b). hollow makes the character look sunken, and raised makes it look as if coming off the page.

Tabs

gralib extends the character level of tabbing in trmlib with procedures that can set tabs on an individual pixel. settabg(f, x) sets a tab at the pixel x. restabg(f, x) resets the tab at pixel x. The trmlib procedure clrtab(f) clears all tabs, including pixel level tabs.

Colors

The simple eight colors from trmlib are still available, but there are two new calls that let you get access to the full range of colors an advanced graphic system provides. fcolorg(f, r, g, b) sets the foreground color from values of red r, green g, and blue b. Similarly, bcolorg(f, r, g, b) sets the background color from rgb values. The values of the colors are ratioed. This means that instead of an absolute number, the possible colors are ratioed from 0 to maxint, where 0 is dark, and maxint is saturated color. Color ratios allow the true color range implemented by the system to be hidden.

Drawing Modes

When colors, background or foreground, are drawn on the screen, they can be in a number of mixing modes. Mixing modes govern how the new color is laid over the old. The modes are mutually exclusive, so the setting of a new mix mode for a given color deactivates the old mode. If the old color is simply to be overwritten, then fover(f) or bover(f) is appropriate. If the new color is to be xor'ed with the old color, then fxor(f) or bxor(f) is used. If the new color is to be ignored, leaving the old color underneath intact, use finvis(f) or binvis(f).

There might not seem to be a use for an "invisible" color, but there are actually several uses. First, if the background is set invisible, the text can be overlaid on another pattern. In fact, this is the most common drawing mode, and most programmers will prefer to turn the background off and lay the backgrounds themselves. Similarly, leaving the background on, then setting the foreground invisible can be used to "stencil" letters with arbitrary patterns inside the letters.

Xor mode is good for several things. First, if a series of figures, say rectangles, are to be laid, but the intersections between them are to be left visible, xor is the right mode. Second, xor can be used to place, and then remove a pattern, even a complex one, easily. This is used to allow the user to place figures by dragging them with the mouse to a new location. It can be used to draw "rubber band" boxes around selections.

Xor mode has two rules of interest:

1. Any two colors, even the same colors, xored together, will give a third color, with the exception of black.

2. Having xored a drawing into the viewplane, xoring the same color and drawing into the viewplane again will restore the old drawing.

Those rules actually make xor good for a lot of tricks and effects. However, there is a warning to go with the fun of xor. Xor dosen't like inaccuracy. Xoring something back off the screen has to be done the same way it was put on, with the same parameters. Also, the mode of drawing in gralib is not very friendly to xor mode. Drawing a rectangle, for example, will result in "corner errors", because the rectangles are built from lines, and lines start and end at the coordinates of the box corners. Because one line starts at the same point another ends, they will xor mix together. The result is a recognizable point of off-color on each corner.

The best way to use xor is to xor a single figure onto the screen, then xor it back off. If complex figures are to be xored on and off the screen, be prepared to run the xor backwards, that is, from the last figure drawn back to the first.

Drawing Graphics

A graphics element that is not a character is referred to as a "figure". What we have tried to do is provide a small toolset, that does not include figures that you could reasonably construct from the lower level figures. For example, there is no circle figure in gralib, because that is simply a special case of ellipse.

A parameter that applies to almost all figures is the width of lines. The width of a line usually defaults to 1, but may be more if a single line is unusable on the current display. This can easily happen on a very high resolution display. The line width can be set by the procedure linewidth(f, n), where n is the number of pixels for the line to use. There is no limit on the width of a line, and in fact, lines are a defacto way to draw arbitrary angle filled rectangles.

When the line width is set to an even number of pixels, an effect called "even line uncertainty" exists. If you draw a line between to points, and the line width is say, 2, one of the 2 pixels is going to be on the line, and the other could be to either side of it. It literally depends on how the math happens to round off.

To prevent this, its recommended that the line width always be set to an odd number.

Figures

The fundamental figure in graphics is the line. A line is drawn, in the current linewidth, by line(f, x1, y1, x2, y2). A rectangle is drawn with rect(f, x1, y1, x2, y2), whose borders have the current linewidth. A filled rectange is drawn with frect(f, x1, y1, x2, y2), whose interior is the foreground color. An ellipse is drawn with ellipse(f, x1, y1, x2, y2). The x and y parameters define a rectangle that contains the figure. The procedure fellipse(f, x1, y1, x2, y2) draws a filled ellipse.

The procedure arc(f, x1, y1, x2, y2, rs, re) draws an arc line around the ellipse formed by the rectangle formed by the x and y parameters. The start and end points of the arc are described by a special ratio notation that gives the angle. The angles in a 360 degree circle are described by a number from 0 to maxint. 0 is the 0 degree, or top center, of the ellipse. The angles around the circle clockwise then go from 0 to maxint, at which time a full 360 degrees have been traversed. For example, maxint div 2 is 180 degrees, maxint div 4 is 90 degrees, etc. The parameter rs gives the angle where the arc starts. The parameter re gives the angle where the arc ends. Arcs can be specified to cross maxint back to zero, or use negative degrees, or any combination.

The procedure farc(f, x1, y1, x2, y2, rs, re) draws a filled arc. The procedure fcord(f, x1, y1, x2, y2, rs, re) draws a filled cord (a line bisecting the circle).

Rectangles with rounded corners can be drawn with rrect(f, x1, y1, x2, y2, xw, yw). The x and y parameters describe the bounding box, The xw and yw parameters describe the size of the ellipses that are placed in the corners to round the edges of the box. To draw a filled rounded rectangle, use frrect(f, x1, y1, x2, y2, xw, yw).

The general purpose shape ftriangle(f, x1, y2, x2, y2, x3, y3) draws a filled triangle. Parting with convention, gralib does not give complex polygon procedures. Rather, you can build up polygons from triangles, and in any case, a high speed drawing engine in hardware would accept triangles only, so the lower level software would have to break up the polygon for you.

Single pixels can be set with setpixel(f, x, y).

Predefined Pictures

A picture, or a bitmap, is defined outside the program by a drawing application. Its format is typically operating system specific. gralib considers pictures to be a cached resource. A picture is loaded from a file by loadpict(f, p, fs). The string fs indicates the file name for the picture file. p is a logical picture number, from 1 to n, and indicates how you want to refer to the picture while it is loaded into memory.

A logical picture is drawn onto the screen with picture(f, p, x1, y1, x2, y2). The parameter p indicates the logical picture to draw. The x and y parameters indicate the box that the picture is to be drawn into. gralib will scale or stretch the picture as needed to make the picture fit into the space given.

In order to determine the parameters of a picture, such as native size and aspect, the functions pictsizx(f, p) and pictsizy(f, p) are used. These give the native size of the picture in x and y, and the aspect ratio of the picture is then found with x/y.

Scrolling

Like trmlib, gralib can scroll in arbitrary directions. It can also scroll down to the pixel, using the call scrollg(f, x, y). The parameters work the same way as the character position parameters of scroll, except that pixels are specified instead of characters.

Clipping

Clipping is completely automatic in gralib. Any figure drawn is clipped to the edges of the screen. If a figure is drawn entirely outside the screen bounds, it is completely clipped out.

Mouse Graphical Position

A new event, etmoumovg, exists that gives mouse movements in pixels, not just characters. The old etmoumov still occurs, and carries the character grid message. The etmoumovg message happens when the mouse moves a pixel, and the etmoumov message happens when the mouse moves a whole character cell. If you don't need the etmoumov message, you simply ignore it.

Animation

Back in trmlib, we introduced the select(f, u, d) call, that switches between multiple screen buffers. This call is tailor made for double buffer animation, and yes, it works in gralib the same way. Double buffer animation works by having to screen buffers. You draw to one of them, and display the other one. When you are done drawing a "frame", i.e., have completed the picture in the buffer you are currently drawing, you swap the drawing and display buffers, and then you start drawing on what used to be the display buffer, clearing it if necessary.

What double buffering does is, remove any view of the ongoing drawing operations from the user. Although computers do this quite fast, seeing drawing operations in progress tends to produce annoying flashing effects, sparkles and other effects during the drawing. In addition, although the worst interactions with the painting of the graphics card memory to the CRT screen have disappeared, you still get odd effects from the fact that the CRT is being swept across the image you are drawing.

gralib supports more than double buffering (triple, quad or better). However, the advantages of this typically diminish as the amount of data being managed grows without compensating gain in drawing speeds.

To get a really first class, rock solid display, the best thing to do is flip your buffers when the CRT display enters its retrace period. Syncing with the CRT eliminates the effects that occur when you are trying to redraw the screen while the CRT beam is painting across it. The retrace period is when the beam has finished the last lines on the screen, and will head back to the top of the screen. This gives a short time while the CRT is not doing anything, so if the buffer swap can occur within the retrace, no effects will appear on the screen at all. Note that all of this applies to LCD screens as well, since they also scan.

The solution is the etframe message. etframe is sent to you when the CRT enters refresh. If the system does not allow notification for retrace, the etframe message is generated by a timer that keeps either the screen refresh rate, or 30 cycles per second if that cannot be determined.

Declarations

The declarations for gralib are very similar to trmlib, with the addition of the mouse move graphical event, and the standard font codes.

const maxtim = 10; { maximum number of timers available }

      maxbuf = 10; { maximum number of buffers available }

      font_term = 1; { terminal font }

      font_book = 2; { book font }

      font_sign = 3; { sign font }

      font_tech = 4; { technical font (vector font) }

 

type { colors displayable in text mode }

     color = (black, white, red, green, blue, cyan, yellow, magenta);

     joyhan = 1..4; { joystick handles }

     joynum = 0..4; { number of joysticks }

     joybut = 1..4; { joystick buttons }

     joybtn = 0..4; { joystick number of buttons }

     joyaxn = 0..3; { joystick axies }

     mounum = 0..4; { number of mice }

     mouhan = 1..4; { mouse handles }

     moubut = 1..4;  { mouse buttons }

     timhan = 1..maxtim; { timer handle }

     funky  = 1..100; { function keys }

     { events }

     evtcod = (etchar,     { ANSI character returned }

               etup,       { cursor up one line }

               etdown,     { down one line }

               etleft,     { left one character }

               etright,    { right one character }

               etleftw,    { left one word }

               etrightw,   { right one word }

               ethome,     { home of document }

               ethomes,    { home of screen }

               ethomel,    { home of line }

               etend,      { end of document }

               etends,     { end of screen }

               etendl,     { end of line }

               etscrl,     { scroll left one character }

               etscrr,     { scroll right one character }

               etscru,     { scroll up one line }

               etscrd,     { scroll down one line }

               etpagd,     { page down }

               etpagu,     { page up }

               ettab,      { tab }

               etenter,    { enter line }

               etinsert,   { insert block }

               etinsertl,  { insert line }

               etinsertt,  { insert toggle }

               etdel,      { delete block }

               etdell,     { delete line }

               etdelcf,    { delete character forward }

               etdelcb,    { delete character backward }

               etcopy,     { copy block }

               etcopyl,    { copy line }

               etcan,      { cancel current operation }

               etstop,     { stop current operation }

               etcont,     { continue current operation }

               etprint,    { print document }

               etprintb,   { print block }

               etprints,   { print screen }

               etfun,      { function key }

               etmenu,     { display menu }

               etmouba,    { mouse button assertion }

               etmoubd,    { mouse button deassertion }

               etmoumov,   { mouse move }

               ettim,      { timer matures }

               etjoyba,    { joystick button assertion }

               etjoybd,    { joystick button deassertion }

               etjoymov,   { joystick move }

               etterm,     { terminate program }

               etmoumovg,  { mouse move graphical }

               etframe);   { frame sync }

     { event record }

     evtrec = record

 

        case etype: evtcod of { event type }

 

           { ANSI character returned }

           etchar:   (char:                char);

           { timer handle that matured }

           ettim:     (timnum:              timhan);

           etmoumov:  (mmoun:               mouhan;   { mouse number }

                       moupx, moupy:        integer); { mouse movement }

           etmouba:   (amoun:               mouhan;   { mouse handle }

                       amoubn:              moubut);  { button number }

           etmoubd:   (dmoun:               mouhan;   { mouse handle }

                       dmoubn:              moubut);  { button number }

           etjoyba:   (ajoyn:               joyhan;   { joystick number }

                       ajoybn:              joybut);  { button number }

           etjoybd:   (djoyn:               joyhan;   { joystick number }

                       djoybn:              joybut);  { button number }

           etjoymov:  (mjoyn:               joyhan;   { joystick number }

                       joypx, joypy, joypz: integer); { joystick coordinates }

           etfun:     (fkey:                funky);   { function key }

           etmoumovg: (mmoung:              mouhan;   { mouse number }

                       moupxg, moupyg:      integer); { mouse movement }

           etup, etdown, etleft, etright, etleftw, etrightw, ethome, ethomes,

           ethomel, etend, etends, etendl, etscrl, etscrr, etscru, etscrd,    

           etpagd, etpagu, ettab, etenter, etinsert, etinsertl, etinsertt,

           etdel, etdell, etdelcf, etdelcb, etcopy, etcopyl, etcan, etstop,    

           etcont, etprint, etprintb, etprints, etmenu,

           etterm, etframe: (); { normal events }

 

        { end }

 

     end;

Functions

See trmlib for the basic text functions. These are all implemented in gralib.

function maxxg(var f: text): integer;

Returns the maximum pixel index x.

function maxyg(var f: text): integer;

Returns the maximum pixel index y.

function curxg(var f: text): integer;

Returns the current location of the cursor, in pixel units, for x.

function curyg(var f: text): integer;

Returns the current location of the cursor, in pixel units, for y.

procedure line(var f: text; x1, y1, x2, y2: integer);

Draw line. Draws a line between the point x1,y1 to x2,y2, using the current line width, color and mode.

procedure linewidth(var f: text; w: integer);

Set line width. Sets the line drawing width at w pixels wide. Use of an odd number of pixels is recommended.

procedure rect(var f: text; x1, y1, x2, y2: integer);

Draw rectangle. Draws a rectangle whose opposite corners are x1,y1 and x2,y2. Uses the current line width, color and mode.

procedure frect(var f: text; x1, y1, x2, y2: integer);

Draw filled rectangle. Draws a solid rectangle, whose opposite corners are x1,y1 and x2,y2. Uses the current color and mode.

procedure rrect(var f: text; x1, y1, x2, y2, xs, ys: integer);

Draw rounded rectangle. Draws a rectangle with rounded corners. The opposite corners of the bounding box are specified as x1,y1 to x2,y2. The ellipses that specify the rounded corners are xs and ys, which specify the width and height of the ellipse. Uses the current line width, color and mode.

procedure frrect(var f: text; x1, y1, x2, y2, xs, ys: integer);

Draw filled rounded rectangle. Draws a rectangle with rounded corners. The opposite corners of the bounding box are specified as x1,y1 to x2,y2. The ellipses that specify the rounded corners are xs and ys, which specify the width and height of the ellipse. Uses the current color and mode.

procedure ellipse(var f: text; x1, y1, x2, y2: integer);

Draw an ellipse. Draws an ellipse bonded by a box whose opposite corners are x1,y1 to x2,y2. Uses the current line width, color and mode.

procedure fellipse(var f: text; x1, y1, x2, y2: integer);

Draw a filled ellipse. Draws a solid ellipse bonded by a box whose opposite corners are x1,y1 to x2,y2. Uses the current color and mode.

procedure arc(var f: text; x1, y1, x2, y2, sa, ea: integer);

Draw an arc. Draws an arc on an ellipse, whose bounding box is x1,y1 to x2,y2. The arc starts on the ellipse from the angle sa, to the angle ea. The angles are given in 360 degree to maxint ratio form. Uses the current color and mode.

procedure farc(var f: text; x1, y1, x2, y2, sa, ea: integer);

Draw a filled arc. Draws a filled arc on an ellipse, whose bounding box is x1,y1 to x2,y2. The arc starts on the ellipse from the angle sa, to the angle ea. The angles are given in 360 degree to maxint ratio form. Uses the current color and mode.

procedure fchord(var f: text; x1, y1, x2, y2, sa, ea: integer);

Draw a filled cord. Draws a filled cord on an ellipse, whose bounding box is x1,y1 to x2,y2. The cord starts on the ellipse from the angle sa, to the angle ea. The angles are given in 360 degree to maxint ratio form. Uses the current color and mode.

procedure ftriangle(var f: text; x1, y1, x2, y2, x3, y3: integer);

Draw a filled triangle. Draws a filled triangle given the three points x1,y1, x2,y2, and x3,y3. Uses the current color and mode.

procedure cursorg(var f: text; x, y: integer);

Position the cursor graphically. Moves the cursor to the pixel position x and y.

function baseline(var f: text): integer;

Find baseline of current font. Finds the baseline, or line on which all characters of the current font sit upon, in terms of offset from the character bounding box origin in y.

procedure setpixel(var f: text; x, y: integer);

Set single pixel. Sets a single pixel at the point x,y, using the current color and mode.

procedure fover(var f: text);

Set overwrite mode foreground. Sets all new drawing to overwrite old colors on the foreground.

procedure bover(var f: text);

Set overwrite mode background. Sets all new drawing to overwrite old colors on the background.

procedure finvis(var f: text);

Set invisible mode foreground. Sets all new drawing to discard colors on the foreground.

procedure binvis(var f: text);

Set invisible mode background. Sets all new drawing to discard colors on the background.

procedure fxor(var f: text);

Set xor mode foreground. Sets all new drawing to xor old colors with new colors on the foreground.

procedure bxor(var f: text);

Set xor mode background. Sets all new drawing to xor old colors with new colors on the background.

function chrsizx(var f: text): integer;

Find character size in x. Returns the x size, in pixels, of the characters in the current font. If the font is proportional, its x size will vary per character. The size will then be the space character, which is guaranteed to be the widest character in the font.

function chrsizy(var f: text): integer;

Find character size in y. Returns the y size, in pixels, of the characters in the current font.

function fonts(var f: text): integer;

Find number of fonts. Returns the number of fonts installed on the system.

procedure font(var f: text; fc: integer);

Select logical font. Selects a font by logical number fc, where fc is 1..fonts.

procedure fontnam(var f: text; fc: integer; var fns: string);

Find name of logical font. Returns the name of the logical font fc in the string fns.

procedure fontsiz(var f: text; s: integer);

Set font size. Sets the height of the current font, in pixels.

procedure chrspcy(var f: text; s: integer);

Set character y spacing. Sets the line to line spacing to s, in pixels.

procedure chrspcx(var f: text; s: integer);

Set character x spacing. Sets the character to character spacing s, in pixels.

function dpmx(var f: text): integer;

Find dots per meter x. Finds the dots per meter in x of the current display device.

function dpmy(var f: text): integer;

Find dots per meter y. Finds the dots per meter in y of the current display device.

function strsiz(var f: text; view s: string): integer;

Find pixel size of string. Finds the total x size of the string s, in pixels. Accounts for all sizes and spacing.

function chrpos(var f: text; view s: string; p: integer): integer;

Find pixel offset of character. Finds the pixel offset from the start of a string s in terms of x pixels, to the character by the index p.

procedure writejust(var f: text; view s: string; n: integer);

Write string justified. Writes the given string s into the number of x pixels n. If the space is more than is required, the extra space will be distributed between the characters. If the space given is insufficient for the characters to be drawn, it will be drawn in the minimum amount of space for the entire string.

function justpos(var f: text; view s: string; p, n: integer): integer;

Find justified pixel off set of character. Finds the pixel offset from the start of a string s, in terms of x pixels, to the character by the index p, for a string justified to fit into n pixels. The rules of justification are the same as for writejust.

procedure condensed(var f: text; e: boolean);

Set condensed mode. Sets the current font to occupy a shorter baseline than normal. Note that this effect may not be implemented.

procedure extended(var f: text; e: boolean);

Set extended mode. Sets the current font to occupy a longer baseline than normal. Note that this effect may not be implemented.

procedure xlight(var f: text; e: boolean);

Set extra light mode. Sets the current font to extra light printing. Note that this effect may not be implemented.

procedure light(var f: text; e: boolean);

Set light mode. Sets the current font to light printing. Note that this effect may not be implemented.

procedure xbold(var f: text; e: boolean);

Set extra bold mode. Sets the current font to extra bold printing. Note that this effect may not be implemented.

procedure hollow(var f: text; e: boolean);

Set hollow mode. Sets the current font to hollow, or sunken look, printing. Note that this effect may not be implemented.

procedure raised(var f: text; e: boolean);.

Set raised mode. Sets the current font to raised, or relief look, printing. Note that this effect may not be implemented.

procedure settabg(var f: text; t: integer);

Set graphical tab. Sets a tab to the pixel t.

procedure restabg(var f: text; t: integer);

Reset graphical tab. The tab at pixel t is removed. If no tab is set at t, no error will result.

procedure fcolorg(var f: text; r, g, b: integer);

Set foreground color graphical. The foreground color is set to the red r, green g and blue b color. The colors are in 0..maxint ratios, with 0 = black, and maxint = saturated. The nearest color to the one given is found and set active as the foreground color. The exact color that results will depend on the total number of colors implemented in the system, and could well be black and white in a system so equipped.

procedure bcolorg(var f: text; r, g, b: integer);.

Set background color graphical. The background color is set to the red r, green g and blue b color. The colors are in 0..maxint ratios, with 0 = black, and maxint = saturated. The nearest color to the one given is found and set active as the foreground color. The exact color that results will depend on the total number of colors implemented in the system, and could well be black and white in a system so equipped.

procedure loadpict(var f: text; p: integer; view fn: string);

Load picture. Loads the picture from the filename fn to logical picture number p, which is 1..n. The file must be in a format that the system understands, and is converted to an in memory format that is optimal for the system, such as a direct match for the graphics device in use.

function pictsizx(var f: text; p: integer): integer;

Find picture size x. Returns the size, in pixels of x, of the logical picture p.

function pictsizy(var f: text; p: integer): integer;

Find picture size y. Returns the size, in pixels of y, of the logical picture p.

procedure picture(var f: text; p: integer; x1, y1, x2, y2: integer);

Draw picture. The logical picture p is drawn into the bounding box formed by x1,y1 to x2,y2. The picture is stretched or compressed as required to do this. The method used to stretch or compress may depend on how much computing power is available on the system.  There is no attention paid to aspect ratio.

procedure delpict(var f: text; p: integer);

Remove logical picture from use. The logical picture p is removed from the picture queue, and will no longer take up memory space.

procedure scrollg(var f: text; x, y: integer);

Scroll in arbitrary directions. The screen is scrolled according to the differences in x and y, which are in pixels. Uncovered areas on the screen appear in the current background color.


For more information contact: Scott A. Moore samiam@moorecad.com