Rules of ISO 7185 Pascal


This file contains an overview of the basic rules of ISO 7185 Pascal. See also the books on the subject. For serious users, I recommend:

Standard Pascal: Users Reference Manual, Doug Cooper

Oh ! Pascal !, Doug Cooper

Both available from Amazon.com.

Note that the following description could be wrong or incomplete.


Index

Lexography

Program structure

Label declaration

Constant declaration

Types

Variable declaration

Block declaration

Declaration order

Predefined types

Basic types

Integer types

Enumerated types

Boolean types

Character types

Subrange types

Real types

Structured types

Packing

Set types

Array types

Record types

File types

Pointer types

Type compatibility

Expressions

Predefined functions

Statements

Assignment

If statement

While statement

Repeat statement

For statement

Case statement

Goto statement

Compound statement

Procedures and functions

Predefined procedures and file operations

Predefined procedures and text files

Header files

Packing procedures

Dynamic allocation


Lexography

Pascal source consists of identifiers, keywords, numbers and special  character sequences. A Pascal identifier must begin with 'a' to 'z', but may continue with 'a' to 'z' and '0' to '9'. There is no length limit on labels, but there may be a practical limit. If the compiler cannot process a source line longer than N, you cannot have a label longer than N, since labels may not cross lines.

Keywords (or reserved words) appear just as labels, but have special meaning wherever they appear, and may never be used as identifiers:

and      array     begin     case      const     div       do
downto   else      end       file      for       function  goto
if       in        label     mod       nil       not       of
or       packed    procedure program   record    repeat    set
then     to        type      until     var       while     with

A number can appear in both integer and real form. Integers will appear as a sequence of digits:

83
00004

Are valid integer numbers. For a number to be taken as "real" (or "floating point") format, it must either have a decimal point, or use scientific notation:

1.0
1e-12
0.000000001

Are all valid reals. At least one digit must exist on either side of a decimal point. Strings are made up of a sequence of characters between single quotes:

'string'

The single quote itself can appear as two single quotes back to back in a string:

'isn''t'

Finally, special character sequences are one of the following:

+        -         *         /         =         <         >
[        ]         .         ,         :         ;         ^
(        )         <>        <=        >=        ..        @
{        }         (*        *)        (.        .)

Note that these are just aliases for the same character sequence:

@  and ^ (or the "up arrow" if allowed in the typeface)
(. and [
.) and ]
(* and {
*) and }

Spaces and line endings in the source are ignored except that they may act as "separators". No identifier, keyword, special character sequence or number may be broken by a separator or other object. No two identifiers, keywords or numbers may appear in sequence without an intervening separator:

MyLabel         - Valid
My Label        - Invalid
begin farg := 1 - Valid
beginfarg := 1  - Invalid
1.0e-12         - Valid
1.e-122e-3      - Invalid

Program structure

A Pascal program appears as a nested set of "blocks", each of which has the following form:

block_type name(parameter [, parameter]...);
label x[, y]...
 
const x = y; 
      [q = r;]...
 
type x = y;
     [q = r;]...
 
var  x[,y]...: z;
     [x[,y]...: z;]...
 
[block]...
 
begin
 
   statement[; statement]
 
end[. | ;]

Note that:

[option]    means optional.
[repeat]... means can appear 0 or more times.
[x | y]     means one or the other.

There are three types of blocks, program, procedure and function. Every program must contain a program block, and exactly one program block exists in the source file.

Each block has two distinct sections, the declaration and statements sections. The declarations immediately before a statement section are considered "local" to that section.

The declaration section builds a description of the data used by the coming statement section in a logical order. For example, constants are usually used to build type declarations, and type declarations are used to build variables, and all of these may be used by nested blocks.

Label declaration

The first declaration, labels, are numeric sequences that denote the target of any goto's appearing in the block:

label 99,
      1234;

Are valid labels. Labels "appear" to be numbers, and must be in the range 0 to 9999. The "appearance" of a number means that:

label 1,
      01,

Are the same label.

Constant declaration

Constant declarations introduce fixed valued data as a specified identifier:

const x = 10;
      q= -1;
      y = 'hi there';
      r = 1.0e-12;
      z = x;

Are all valid constant declarations. Only integer, real and character constants may be so defined (no sets may appear).

Types

The type declaration allows types to be given names, and are used to create variables later:

type x = array [1..10] of integer;
     i = integer;
     z = x;

Types can be new types, aliases of old types, etc.

Variable declaration

Variables set aside computer storage for a element of the given type:

var x, y: integer;
    z:    array [1..10] of char;

Block declaration

A block can be declared within a block, and that block can declare blocks within it, etc. There is no defined limit as to the nesting level. Because only one program block may exist, by definition all "sub blocks" must be either procedure or function blocks. Once defined, a block may be accessed by the block it was declared in. But the "surrounding" block cannot access blocks that are declared within such blocks:

program test;
 
procedure junk;
 
procedure trash;
 
begin { trash }
 
   ...
 
end;  { trash }
 
begin { junk }
 
   trash;
   ...
 
end;  { junk }
 
begin { test }
 
   junk;
   ...
 
end.  { test }

Here test can call junk, but only junk can call trash. Trash is "hidden" from the view of test. Similarly, a subblock can access any of the variables or other blocks that are defined in surrounding blocks:

program test;
 
var x;
 
procedure q;
 
begin
 
end;
 
procedure y;
 
begin
 
   q;
   x := 1
 
end;
 
begin
 
   y;
   writeln('x')
 
end.

The variable "x" can be accessed from all blocks declared within the same block. It is also possible for a block to call itself, or another block that calls it. This means that recursion is allowed in Pascal.

Declaration order

Every identifier must be declared before it is used, with only one exception, pointers, which are discussed later. But there is a way to declare procedures and functions before they are fully defined to get around problems this may cause.

Predefined types

Several types are predeclared in Pascal. These include integer, boolean, char, real and text. predeclared types, just as predeclared functions and procedures, exist in a conceptual "outer block" around the program, and can be replaced by other objects in the program.

Basic types

Types in Pascal can be classed as ordinal, real and structured. The ordinal and real types are referred to as the "basic" types, because they have no complex internal structure.  Ordinal types are types whose elements can be numbered, and there are a  finite number of such elements.

Integer types

The basic ordinal type is "integer", and typically it represents the accuracy of a single word on the target machine:

var i: integer;

A predefined constant exists, "maxint", which tells you what the maximum integral value of an integer is. So:

type integer = -maxint..maxint;

Would be identical to the predefined type "integer". Specifically, the results of any operation involving ordinals will only be error free if they lie within -maxint to +maxint. Although other ordinal types exist in Pascal, all such types have a mapping into the type "integer", and are bounded by the same rules. The "ord" function can be used on any ordinal to find the corresponding integer.

Enumerated types

Enumerated types allow you to specify an identifier for each and every value of an ordinal:

type x = (one, two, three, four);

Introduces four new identifiers, each one having a constant value in sequence from the number 0. So for the above:

one   = 0
two   = 1
three = 2
four  = 3

Enumerated types may have no relationship to numbers whatever:

type y = (red, green, blue);

Or some relationship:

type day = (mon, tue, wed, thur, fri, sat, sun);

Here the fact that "day"s are numbers (say, isn't that a lyric ?) is useful because the ordering has real world applications:

if mon < fri then writeln('yes');

And of course, subranges of enumerated types are quite possible:

type workday = (mon..fri);

Enumerated types are fundamentally different from integer and subrange types in the fact that they cannot be freely converted to and from each other. There is only one conversion direction defined, to integer, and that must be done by special predefined function:

var i: integer;
    d: day;
 
...
 
i := ord(d); { find integral value of d }

Boolean types

The only predefined enumerated type is "boolean", which could be declared:

type boolean = (false, true);

However, booleans cannot be cross converted (being enumerated types), this user created type could not in fact be used just as the predeclared one. Booleans are special in that several predefined procedures, and all of the Comparison operators ("=", ">", etc.) give boolean results. In addition, several special operators are defined just for booleans, such as "and", "or" etc.

Character types

Character types in Pascal hold the values of the underlying character set, usually ISO single byte encoded (including ASCII). The Pascal standard makes no requirements as to what characters will be present or what order they will appear in. However, as a practical matter, most Pascal programs rely on the characters of the alphabet and the digits '0'-'9' being present, and that these are numbered sequentially (which leaves out EBCDIC, for example). A character declaration appears as:

var c: char;

Character values can also be converted to and from integers at will, but only by using the special functions to do so:

ord(c); { find integer value of character }
chr(i); { find character value of integer }

Subrange types

Subrange types are simply a voluntary programmer restriction of the values an ordinal type may hold:

type constrained = -10..50;

(the notation x..y means all values from x to y inclusive.)

It is an error to assign a value outside of the corresponding range to a variable of that type:

var x: constrained
 
...
 
x := 100; { invalid! }

But note that there are no restrictions on the USE of such a type:

writeln('The sum is: ', x+100);

Here, even though the result of x+100 is greater than the type of x, it is not an error. When used in an expression, a subrange is directly equivalent to the type "integer".

Subranges can be declared of any ordinal type:

type enum = (one, two, three, four, five, six, seven, eight, nine, ten);
 
var e: three..seven;
 
var c: 'a'..'z';

Etc.

Real types

Real types, or "floating point", allow approximations of a large range of numbers to be stored. The tradeoff is that reals have no direct ordinality (cannot be counted), and so have no direct relationship with integers. Real types are the only basic type which is not ordinal.

var r: real;

Integers are considered "promotable" to reals. That is, is is assumed that an integer can always be represented as a real. However, there may be a loss of precision when this is done (because the mantissa of a real may not be as large as an integer). Reals are never automatically promoted to integer, however, and the programmer must choose between finding the nearest whole number to the real, or simply discarding the fraction. This choice must be made explicitly by predefined function.

Structured types

A structured type is a type with a complex internal structure. In fact, the structured types all have one thing in common: they can hold more than one basic type object at one time. They are structured because they are "built up" from basic types, and from other structured types.

Packing

Structured types can also be "packed", which is indicated by the keyword "packed" before the type declaration. Packing isn't supposed to change the function of the program at all. Stripping the "packed" keywords out of a program will not change the way it works (with the exception of "strings", below). Packing means that (if implemented: its optional) the program should conserve space by placing the values in as few bits as possible, even if this takes more code (and time) to perform.

Packing is better understood if you understand the state of computers before Microprocessors (the Jurassic age of computers ?). Most mainframe computers access memory as a single word size only, and not even a neat multiple of 8 bits either (for example, 36 bit computer; the CDC 6000 has 60 bit words). The machine reads or writes in words only. There is no byte access, no even/odd addressing, etc. Because storage on such a machine of small items could be wasteful (especially characters), programs often pack many single data items into a single word.

The advent of the Minicomputer changed that. DEC started with an 8 bit machine (just as microprocessors did), and when they changed to 16, then 32 bits the  ability to address single bytes was maintained.

For this reason, many people refer to such a machine as "automatically packed", or that Pascal's packing feature is unnecessary on such machines. However,  quantizing data by 8 bit bytes is not necessarily the most extreme packing method available. For example, a structure of boolean values, which take up only 1 bit per element, left to byte packing would waste 7/8s of the storage allocated.

Set types

Set types are perhaps the most radical feature of Pascal. A set type can be thought of as an array of bits indicating the presence or absence of each value in the base type:

var s: set of char;

Would declare a set containing a yes/present or no/not present indicator for each character in the computer's character set. The base type of a set must be ordinal.

Array types

The most basic structured type is the array. Pascal is unusual in that both the upper and lower bounds of arrays are declared (instead of just the upper bound or length), and that the index type can be any ordinal type:

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

Would declare an array of 10 integers with indexes from 1 to 10. You may recognize the index declaration as a subrange, and indeed any subrange type can be used as an index type:

type sub = 0..99;
var a: array [sub] of integer;

Arrays can also be declared as multidimensional:

var a: array [1..10] of array [1..10] of char;

There is also a shorthand form for array declarations:

var a: array [1..10, 1..10] of char;

Is equivalent to the last declaration.

A special type of array definition is a "string". Strings are arrays of packed characters, with integer indexes, whose lower bound is 1:

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

String types are special in that any two strings with the same number of components are compatible with each other, including constant strings.

Record types

Records give the ability to store completely different component types together as a unit. There they can be manipulated, copied and passed as a unit. It is also possible to create different typed objects that occupy the same storage space.

var r: record
 
          a: integer;
          b: char
 
       end;

Gives a single variable with two completely different components, which can be accessed independently, or used as a unit.

var vr: record
 
           a: integer;
           case b: boolean of { variant }
 
              true: (c: integer; d: char);
              false: (e: real)
 
           { end }
 
        end;

Variant records allow the same "collection of types", but introduce the idea that not all of the components are in use at the same time, and thus can occupy the same storage area. In the above definition, a, b, c, d, and e are all elements of the record, and can be addressed individually. However, there are three basic "types" of record elements in play:

1. "base" or normal fixed record elements, such as a.

2. The "tagfield" element. Such as b.

3. The "variants", such as c, d, and e.

All the elements before the case variant are normal record elements and are always present in the record. The tagfield is also always present, but has special function with regards to the variant. It must be an ordinal type, and ALL of it's possible values must be accounted for by a corresponding variant. The tagfield gives both the program and the compiler the chance to tell what the rest of the record holds (ie., what case variant is "active"). The tagfield can also be omitted optionally:

var vr: record
 
           a: integer;
           case boolean of { variant }
 
              true: (c: integer; d: char);
              false: (e: real)
 
           { end }
 
        end;

In this case, the variant can be anything the program says it is, without  checking. The variants introduce what essentially is a "sub record" definition that gives the record elements that are only present if the selecting variant is "active". A variant can hold any number of such elements. If the compiler chooses to implement variants, the total size of the resulting record will be no larger than the fixed record parts plus the size of the  largest variant. It is possible for the compiler to treat the variant as a normal record,  allocating each record element normally, in which case the variant record would be no different from a normal record.

File types

Files are identical to arrays in that they store a number of identical components. Files are different from arrays in that the number of components they may store is not limited or fixed beforehand. The number of components in a file can change during the run of a program. A file can have any type as a component type, with the exception of other file types. This rule is strict: you may not even have structures which contain files as components. A typical file declaration is:

var f: file of integer;

Would declare a file with standard integer components. A special predefined file type exists:

var f: text;

Text files are supposedly equivalent to:

type text = file of char;

But there are special procedures and functions that apply to text files only.

Pointer types

Pointers are indirect references to variables that are created at runtime:

var ip: ^integer;

Pointers are neither basic or structured types (they are not structured because they do not have multiple components). Any type can be pointed to. In practice, pointers allow you to create a series of unnamed components which can be arranged in various ways. The type declaration for pointers is special in that the type  specified to the right of "^" must be a type name, not a full type specification. Pointer declarations are also special in that a pointer type can be declared using base types that have not been declared yet:

type rp: ^rec;
     rec: record
 
             next: rp;
             val:  integer
 
          end;

The declaration for rp contains a reference to an undeclared type, rec. This "forward referencing" of pointers allows recursive definition of pointer types, essential in list processing.

Type compatibility

Type compatibility (ability to use two different objects in relation to each other), occurs on three different levels:

1. Two types are identical.

2. Two types are compatible.

3. Two types are assignment compatible.

Two types are identical if the exact same type definition was used to create the objects in question. This can happen in several different ways. Two objects can be declared in the same way:

var a, b: array [1..10] of record a, b: integer end;

Here a and b are the same (unnamed) type. They can also be declared using the same type name:

type mytype = record a, b: integer end;
 
var a: mytype;
    b: mytype;

Finally, an "alias" can be used to create types:

type mytype = array [1..10] of integer;
     myother = mytype;
 
var a: mytype;
    b: myother;

Even though an alias is used, these objects till have the same type. Two types are considered compatible if:

1. They are identical types (as described above).

2. Both are ordinal types, and one or both are subranges of an identical type.

3. Both are sets with compatible base types and "packed" status.

4. Both are string types with the same number of components.

Finally, two types are assignment compatible if:

1. The types are compatible, as described above.

2. Neither is a file, or has components of file type.

3. The destination is real, and the source is integer (because integers can always be promoted to real, as above).

4. The source "fits" within the destination. If the types are subranges of the same base type, the source must fall within the destination's range:

var x: 1..10;
 
...
 
x := 1; { legal }
x := 20; { not legal }

5. Both are sets, and the source "fits" within the destination. If the base types of the sets are subranges, all the source elements must also exist in the destination:

var s1: set of 1..10;
 
...
 
s1 := [1, 2, 3]; { legal }
s1 := [1, 15]; { not legal } 

Expressions

The basic operands in Pascal are:

xxx        - Integer constant. A string of digits, without sign, whose
             value is bounded by -maxint..maxint.
x.xex      - Real constant.
'string'   - String constant.
[set]      - Set constant. A set constant consists of zero or more elements
             separated by ",":
 
                [1, 2, 3]
 
             A range of elements can also appear:
 
                [1, 2..5, 10]
 
             The elements of a set must be of the same type, and the 
             "apparent" base type of the set is the type of the elements.
             The packed or unpacked status of the set is whatever is
             required for the context where it appears.
ident      - Identifier. Can be a variable or constant from a const 
             declaration.
func(x, y) - A function call. Each parameter is evaluated, and the
             function called. The result of the function is then used
             in the encompassing expression.

The basic construct built on these operands is a "variable access", where "a" is any variable access.

ident    - A variable indentifier.
a[index] - Array access. It is also possible to access any number of
           dimensions by listing multiple indexes separated by ",":
 
              [x, y, z, ...]
 
a.off    - Record access. The "off" will be the element identifier as
           used in the record declaration.
 
a^       - Pointer reference. The resulting reference will be of the
           variable that the pointer indexes. If the variable reference
           is a file, the result is a reference to the "buffer variable"
           for the file.

Note that a VAR parameter only allows a variable reference, not a full  expression. For the rest of the expression operators, here they are in precedence, with the operators appearing in groups according to priority (highest first). "a" and "b" are operands.

(a)      - A subexpresion.
not      - The boolean "not" of the operand, which must be boolean.
 
a*b      - Multiplication/set intersection. If the operands are real or
           integer, the multiplication is found. If either operand is
           real, the result is real. If the operands are sets, the 
           intersection is found, or a new set with elements that exist
           in both sets.
a/b      - Divide. The operands are real or integer. The result is a real
           representing a divided by b.
a div b  - Integer divide. The operands must be integer. The result is an
           integer giving a divided by b with no fractional part.
a mod b  - Integer modulo. The operands must be integer. The result is an
           integer giving the modulo of a divided by b.
a and b  - Boolean "and". Both operands must be boolean. The result is a
           boolean, giving the "and" of the operands.
 
+a       - Identity. The operand is real or integer. The result is the
           same type as the operand, and gives the same sign result as the
           operand (essentially a no-op).
-a       - Negation. The operand is real or integer. The result is the
           same type as the operand, and gives the negation of the
           operand.
a+b      - Add/set union. If the operands are real or integer, finds the
           sum of the operands. If either operand is real, the result is
           real. If both operands are sets, finds a new set which contains
           the elements of both.
a-b      - Subtract/set difference. If the operands are real or integer,
           finds a minus b. If either operand is real, the result is
           real. If both operands are sets, finds a new set which contains
           the elements of a that are not also elements of b.
a or b   - Boolean "or". Both operands must be boolean. The result is
           boolean, giving the boolean "or" of the operands.
 
a < b    - Finds if a is less than b, and returns a boolean result.
           The operands can be basic or string types.
a > b    - Finds if a is greater than b, and returns a boolean result.
           The operands can be basic or string types.
a <= b   - Finds if a is less than or equal to b, and returns a boolean
           result. The operands can be basic, string, set or pointer
           types.
a >= b   - Finds if a is greater than or equal to b, and returns a boolean
           result. The operands can be basic, string, set or pointer
           types.
a = b    - Finds if a is equal to b, and returns a boolean result.
           The operands can be basic, string, set or pointer types.
a <> b   - Finds if a is not equal to b, and returns a boolean result.
           The operands can be basic, string, set or pointer types.
a in b   - Set inclusion. A is an ordinal, b is a set with the same base
           type as a. Returns true if there is an element matching a in
           the set.

Predefined functions

The following predefined functions exist:
 
sqr(x)    - Finds the square of x, which can be real or integer. The
            result is the same type as x.
sqrt(x)   - Finds the square root of x, which can be real or integer. The
            result is always real.
abs(x)    - Finds the absolute value of x, which can be real or integer.
            The result is the same type as x.
sin(x)    - Finds the sine of x,which can be real or integer. x is
            expressed in radians. The result is always real.
cos(x)    - Finds the cosine of x,which can be real or integer. x is 
            expressed in radians. The result is always real.
arctan(x) - Finds the arctangent of x, which can be real or integer. The
            result is always real, and is expressed in radians.
exp(x)    - Finds the exponential of x, which can be real or integer. The
            result is always real.
ln(x)     - Finds the natural logarithm of x, which can be real or
            integer. The result is always real.
 
ord(x)    - Finds the integer equivalent of any ordinal type x.
succ(x)   - Finds the next value of any ordinal type x.
pred(x)   - Finds the last value of any ordinal type x.
chr(x)    - Finds the char type equivalent of any integer x.
trunc(x)  - Finds the nearest integer below the given real x (converts a
            real to an integer).
round(x)  - Finds the nearest integer to the given real x.

Statements

Pascal uses "structured statements". This means you are given a few standard control flow methods to build a program with.

Assignment

The fundamental statement is the assignment statement:

v := x;

There is a special operator for assignment, ":=" (or "becomes"). Only a single variable reference may appear to the right, and any expression may appear to the left. The operands must be assignment compatible, as defined above.

If statement

The if statement is the fundamental flow of control structure:

if cond then statement [else statement]

In Pascal, only boolean type expressions may appear for the condition (not integers). The if statement specifies a single statement to be executed if the condition is true, and an optional statement if the condition is false. You must beware of the "bonding problem" if you create multiple nested if statements:

if a = 1 then if b = 2 then writeln('a = 1, b = 2')
else writeln('a <> 1');

Here the else clause is attached to the very last statement that appeared, which may not be the one we want.

While statement

Just as if is the fundamental flow of control statement, while is the fundamental loop statement:

while cond do statement

The while statement continually executes it's single statement as long as the condition is true. It may not execute the statement at all if the condition is never true.

Repeat statement

A repeat statement executes a block of statements one or more times:

repeat statement [; statement] until cond

It will execute the block of statements as long as the condition is false. The statement block will always be executed at least once.

For statement

The for statement executes a statement a fixed number of times:

for i := lower to upper do statement
for i := upper downto lower do statement

The for statement executes the target statement as long as the "control variable" lies within the set range of lower..upper. It may not execute at all if lower > upper. The control variable in a for is special, and it must obey several rules:

1. It must be ordinal.

2. It must be local to the present block (declared in the present block).

3. It must not be "threatened" in the executed statement. To threaten means to modify, or give the potential to modify, as in passing as a VAR parameter to a procedure or function (see below).

Case statement

The case statement defines an action to be executed on each of the values of an ordinal:

case x of
 
  c1: statement;
  c2: statement;
  ...
 
end;

The "selector" is an expression that must result in an ordinal type. Each of the "case labels" must be type compatible with the selector. The case  statement will execute one, and only one, statement that matches the current selector value. If the selector matches none of the cases, then an error results. It is NOT possible to assume that execution simply continues if none of the cases are matched. A case label MUST match the value of the selector.

Goto statement

The goto statement directly branches to a given labeled statement:
goto 123
 
...
 
123:

Several requirements exist for gotos:

1. The goto label must have been declared in a label declaration.

2. A goto cannot jump into any one of the structured statements above (if, while, repeat, for or case statements).

3. If the target of the goto is in another procedure or function, that target label must be in the "outer level" of the procedure or function. That means that it may not appear inside any structured statement at all.

Compound statement

A statement block gives the ability to make any number of statements appear as one:

begin statement [; statement]... end

All of the above statements control only one statement at a time, with the exception of repeat. The compound statement allows the inclusion of a whole substructure to be controlled by those statements.

Procedures and functions

When you need to use a block of the same statements several times, a compound block can be turned into a procedure or function and given a name:

procedure x;
 
begin
 
   ...
 
end;
 
function y: integer;
 
begin
 
   ...
 
end;

Then, the block of statements can be called from anywhere:

var i: integer;
 
x; { calls the procedure }
 
i := y; { calls the function }

The difference between a procedure and a function is that a function returns a result, which can only be a basic or pointer type (not structured). This makes it possible to use a function in an expression. In a function, the result is returned by a special form of the assign statement:

function y: integer;
 
begin
 
   ...
   y := 1 { set function return }
 
end;

The assignment is special because only the name of the function appears on the left hand side of ":=". It does not matter where the function return assignment appears in the function, and it is even possible to have multiple assignments to the function, but AT LEAST one such assignment must be executed before the function ends. If the procedure or function uses parameters, they are declared as:

procedure x(one: integer; two, three: char);
 
begin
 
   ...
 
end;

The declaration of a parameter is special in that only a type name may be specified, not a full type specification. Once appearing in the procedure or function header, parameters can be treated as variables that just happen to have been initialized to the value passed to the procedure or function. The modification of parameters has no effect on the original parameters themselves. Any expression that is assignment compatible with the parameter declaration can be used in place of the parameter during it's call:

x(x*3, 'a', succ('a'));

If it is desired that the original parameter be modified, then a special form of parameter declaration is used:

procedure x(var y: integer);
 
begin
 
   y := 1
 
end;

Declaring y as a VAR parameter means that y will stand for the original parameter, including taking on any values given it:

var q: integer;
 
...
 
   x(q);

Would change q to have the value 1. In order to be compatible with a VAR the passed parameter must be of identical type as the parameter declaration, and be a variable reference. Finally, Pascal provides a special mode of parameter known as a procedure or function parameter which passes a reference to a given procedure or function:

procedure x(procedure y(x, q: integer));
 
...
 
procedure z(function y: integer);
 
...

To declare a procedure or function parameter, you must give it's full parameter list, including a function result if it is a function. A procedure or function is passed to a procedure or function by just it's name:

procedure r(a, b: integer);
 
begin
 
   ...
 
end;
 
begin
 
   x(r); { pass procedure r to procedure x }
 
   ...

The parameter list for the procedure or function passed must be "congruent" with the declared procedure or function parameter declaration. This means that all it's parameters, and all of the parameters of it's procedure or function parameters, etc., must match the declared parameter. Once the procedure or function has been passed, it is then ok for the procedure or function that accepts it to use it:

procedure x(procedure y(x, q: integer));

 
begin
 
   y(1, 2);
   ...

Would call r with parameters 1 and 2.

Procedures and functions can be declared in advance of the actual appearance of the procedure or function block using the forward keyword:

procedure x(a, b: integer); forward;
 
procedure y;
 
begin
 
   x(1, 2)
   ...
 
end;
 
procedure x;
 
begin
 
   ...

The forward keyword replaces the appearance of the block in the first  appearance of the declaration. In the second appearance, only the name of the procedure appears, not it's header parameters. Then the block appears as normal. The advance declaration allows recursive structuring of procedure and function calls that would be otherwise not be possible.

Predefined procedures and file operations

A file is not accessed directly (as an array is). Instead, Pascal automatically declares one component of the files base type which is accessed by special syntax:

f^

So that:

f^ := 1;

Assigns to the file "buffer" component, and:

v := f^;

Reads the file buffer. Unless the file is empty or you are at the end of the file, the file buffer component will contain the contents of the component at the file location you are currently reading or writing. Other than that, the file buffer behaves as an ordinary variable, and can even be passed as a parameter to routines. The way to actually read or write through a file is by using the predeclared procedures:

get(f);

Loads the buffer variable with the next element in the file, and advances the file position by one element, and:

put(f);

Outputs the contents of the buffer variable to the file and advances the file position by one. These two procedures are really all you need to implement full reading and writing on a file. It also has the advantage of keeping the next component in the file as a "lookahead" mechanism. However, it is much more common to access files via the predefined procedures read and write:

read(f, x);

Is equivalent to:

x := f^; get(f);

And:

write(f, x);

Is equivalent to:

f^ := x; put(f);

Read and write are special in that any number of parameters can appear:

read(f, x, y, z, ...);
write(f, x, y, z, ...);

The parameters to read must be variable references. The parameters to write can be expressions of matching type, except for the file parameter (files must always be VAR references). Writing to a file is special in that you cannot write to a file unless you are at the end of the file. That is, you may only append new elements to the end of the file, not modify existing components of the file.

Files are said to exist in three "states":

1. Inactive.

2. Read.

3. Write.

All files begin life in the inactive state. For a file to be read from, it must be placed into the read state. For a file to be written, it must be placed in the write state. The reset and rewrite procedures do this:

reset(f);

Places the buffer variable at the 1st element of the file (if it exists), and sets the file mode to "read".

rewrite(f);

Clears any previous contents of the file, and places the buffer variable at the start of the file. The file mode is set to "write". A file can be tested for only one kind of position, that is if it has reached the end:

eof(f);

Is a function that returns true if the end of the file has been reached. eof must be true before the file can be written.

Predefined procedures and text files

As alluded to before, text files are treated specially under Pascal. First, The ends of lines are treated specially. If the end of a line is reached, a read call will just return a space. A special function is required to determine if the end of the line has been reached:

eoln(f);

Returns true if the current file position is at the end of a line. Pascal strictly enforces the following structure to text files:

line 1<eoln>
line 2<eoln>
...
line N<eoln>
<eof>

There will always be an eoln terminating each line. If the file being read does not have an eoln on the last line, it will be added automatically. Besides the standard read and write calls, two procedures are special to text files:

readln(f...);
writeln(f...);

Readln behaves as a normal read, but after all the items in the list are read, The rest of the line is skipped until eoln is encountered. Writeln behaves as a normal write, but after all the items in the list are written, an eoln is appended to the output. Text files can be treated as simple files of characters, but it is also possible to read and write other types to a text file. Integers and reals can be read from a text file, and integers, reals, booleans, and strings can be written to text files. These types are written or read from the file by converting them to or from a character based format. The format for integers on read must be:

[+/-]digit[digit]...

Examples:

 9
+56
-19384

The format for reals on read is:

[+/-]digit[digit]...[.digit[digit]...][e[+/-]digit[digit]...]

Examples:

-1
-356.44
7e9
+22.343e-22

All blanks are skipped before reading the number. Since eolns are defined as blanks, this means that even eoln is skipped to find the number. This can lead to an interesting situation when a number is read from the console. If the user presses return without entering a number (on most systems), nothing will happen until a number is entered, no matter how many times return is hit!

Write parameters to textfiles are of the format:

write(x[:field[:fraction]]);

The field states the number of character positions that you expect the object to occupy. The fraction is special to reals. The output format that occurs in each case are:

integer: The default field for integers is implementation defined, but is usually the number of digits in maxint, plus a position for the sign. If a field is specified, and is larger than the number of positions required to output the number and sign, then blanks are added to the left side of the output until the total size equals the field width. If the field width is less than the required positions, the field width is ignored.

real: The default field for reals is implementation defined. There are two different format modes depending on whether the fraction parameter appears.

If there is no fraction, the format is:

-0.0000000e+000

Starting from the left, the sign is either a "-" sign if the number is negative, or blank if the number is positive or zero. Then the first digit of the number, then the decimal point, then the fraction of the number, then either 'e' or 'E' (the case is implementation defined), then the sign of the exponent, then the digits of the exponent. The number of digits in the exponent are implementation defined, as are the number of digits in a fraction if no field width is defined. If the field width appears, and it is larger than the total number of required positions in the number (all the characters in the above format without the fraction digits), then the fraction is expanded until the entire number fills the specified field, using right hand zeros if required. Otherwise, the minimum required positions are always printed.

If a fraction appears (which means the field must also appear), the format used is:

[-]00...00.000..00

The number is converted to it's whole number equivalent, and all the of whole number portion of the number printed, regardless of the field size, proceeded by "-" if the number is negative. Then, a decimal point appears, followed by the number of fractional digits specified in the fraction parameter. If the field is greater then the number of required positions and specified fraction digits, then leading spaces are appended until the total size equals the field width. The minimum positions and the specified fractional digits are always printed.

Header files

The header files feature was originally designed to be the interface of Pascal to the external files system, and as such is implementation by definition. It is also (unfortunately) ignored in most implementations. The header files appear as a simple list of identifiers in the program header:

program test(input, output, source, object);

Each header file automatically assumes the type text. If the file needs to be another type, it should be declared again in the variables section of the program block:

program test(intlist);
 
var intlist: file of integer;

Two files are special, and should not be redeclared. These are input and output. The input files are understood to represent the main input and main output from the program, and are present in all Pascal programs. In addition, they are the default files is special forms of these  procedures and functions:

This form      is equivalent to      This form
--------------------------------------------------------------
write(...)                           write(output, ...)
writeln(...)                         writeln(output, ...)
writeln                              writeln(output)
read(...)                            read(input, ...)
readln(...)                          readln(input, ...)
readln                               readln(input)
eof                                  eof(input)
eoln                                 eoln(input)
page                                 page(output)

Packing procedures

Because arrays are incompatible with each other even when they are of the same type if their packing status differs, two procedures allow a packed array to be copied to a non-packed array and vice versa:

unpack(PackedArray, UnpackedArray, Index);

Unpacks the packed array and places the contents into the unpacked array. The index gives the starting index of the unpacked array where the data is to be placed. Interestingly, the two arrays need not have the same index type or even be the same size ! The unpacked array must simply have enough elements after the specified starting index to hold the number of elements in the packed array.

pack(UnpackedArray, Index, PackedArray);

Packs part of the unpacked array into the packed array. The index again gives the starting position to copy data from in the unpacked array. Again, the arrays need not be of the same index type or size. The unpacked array simply need enough elements after the index to provide all the values in the packed array.

Dynamic allocation

In Pascal, pointer variables are limited to the mode of variable they can index. The objects indexed by pointer types are anonymous, and created or destroyed by the programmer at will. A pointer variable is undefined when it is first used, and it is an error to access the variable it points to unless that variable has been created:

var p: ^integer;
 
...
 
   new(p); { create a new integer type }
   p^ := 1; { place value }

Would create a new variable. Variables can also be destroyed:

dispose(p);

Would release the storage allocated to the variable. It is an error (a very serious one) to access the contents of a variable that has been disposed. A special syntax exists for the allocation of variant records:

var r: record
 
          a: integer;
          case b: boolean 
of
 
             true: 
(c: integer);
             false: 
(d: char)
 
          { end }
 
       end;
 
...
 
new(p, true);
 
...
 
dispose(p, true);

For each of new and dispose, each of the tagfields we want to discriminate are parameters to the procedure. The appearance of the tagfield values allow the compiler to allocate a variable with only the amount of space required for the record with that variant. This can allow considerable storage savings if used correctly. The appearance of a discriminant in a new procedure does not also  automatically SET the value of the tagfield. You must do that yourself. For the entire life of the variable, you must not set the tagfield to any other value than the value used in the new procedure, nor access any of the  variants in the record that are not active. The dispose statement should be called with the exact same tagfield values and number. Note that ALL the tagfields in a variable need not appear, just all the ones, in order, that we wish to allocate as fixed.


For more information contact: Scott A. Moore samiam@moorecad.com