Menu

using the p5x compiler

Trevor Blight

p5x Extensions

p5x is a version of the pascal compiler that allows extensions that are common in other pascal compilers, for example

  • under_scores in identifiers
  • constant expressions
  • relaxed declaration order
  • // style comments
  • case statement extensions
  • etc

This makes code easier to read and aids in porting other pascal programs to p5x.


Underscores are allowed in identifiers

eg

 function my_function( x: real ): real;

It is an error if an identifier begins with an underscore because all identifiers starting with an underscore are reserved for system variables. so this, for example, is not allowed

    _bad : integer;   { error, name starts with '_' }

Support for underscores is very common, so code that uses this feature is likely to work on other pascal compilers.

Note, however, that some pascal compilers do not allow an identifier to end with an underscore, or do not allow adjacent underscores.
p5x fobids only an initial underscore.


dollar char is allowed in identifiers

eg

    my$speed: real;

This feature is not common, so identifiers with dollar characters may fail on other pascal compilers.

Note that identifiers must not begin with a dollar because hexadecimal numbers start with a dollar.
so this, for example, is not allowed

        $bad : integer;   { error, this is a hex number }

// style comments

comments starting with // are available, comment ends at the end of a line
eg

    var
    // this is a comment
          x : real;
          i : integer; // this is also a comment
          c : char;

This feature is available on many other pascal compilers, but not all.
Delphi, free pascal and gnu pascal all accept this style of comments. Code containing these comments might work on other compilers.


mixed declaration order

The order of declarations is relaxed, and there can be multiple sections of declarations, eg

    program x(output);

    const
        c1 = 1.e-5;

    type colour = (red, orange, yellow, green, blue, indigo, violet);
    const 
                          firstColour = red;
                          lastColour = violet;

    const
                msgLen = 10;
    type
                 messageString = packed array[1..msgLen] of char;
    var
                  messages = array[1..6] of messageString;

    var
                 v1 : real;
    const
                tol = 1.0e-5;
    function solve( var x: real; function f(x:real):real ): boolean;
       ...

    begin{x}
          ...
    end. {x}

This allows related declarations to be grouped together.

Everything must be defined before it is used - as expected.

Also, all pointers must be resolved before the end of any type declaration, as shown in this example:

    type
       prec1 = ^rec1;
       rec1 = record   { this is OK - rec1 is resolved here }
                 s    : packed array[1..8] of char;
                 link : ^rec2;
              end;
    { !!! ERROR - rec2 is not resolved before the end of the type declaration }

    var
       pr : prec1;

    type
       rec2 = record  { rec2 is resolved here, but too late }
                 s2    : packed array[1..8] of char;
                 link2 : prec1;
              end;

Mixed declaration order is very common amongst pascal compilers, so using this feature is unlikely to create portability problems.


constant expressions

Constant expressions for ordinal types can be used wherever a constant is expected in the source code.
Ordinal types are integers, booleans chars and enumerated types, but not reals or strings or pointers.

Constant expressions can include any of these operators or functions:

  • +, -, or
  • \, *, mod, div, and
  • abs(), sqr()
  • ord(), odd(), chr()
  • pred(), succ()
  • not, ()

This is useful when declaring types, constants and arrays, eg

    const
       first = 0;
       last = 20;
       size = last-first+1;

If you need the maximum of 2 integer constants, a & b, try this:

       const
         a = something;
         b = otherthing;
         maxab = (a+b + abs(a-b)) div 2;

and the minimum is

   minab = (a+b - abs(a-b)) div 2;

case statements expect constants for the case values, so constant expressions can be used here as well, eg

       case e of
         base .. base+offset:  do_something;
         base+offset+1 .. last: do_anotherthing;
         otherwise: warning(bad_value);
       end; {case}

A compile time error is issued when the constant expression overflows or underflows, eg

  const rubbish = succ(maxint);  {!!! ERROR, this overflows}

The ability to use constant expressions is very common amongst pascal compilers, but different compilers allow different operators and functions in the expressions.


command line parameters

The standard procedure

    argv( i, s )

copies the ith command line parameter to the string variable s.

The command line parameter is padded with null characters or truncated to fit the string variable s if necessary.

The standard function argc returns the number of command line arguments available.

parameter 0 is the name of the program, so the maximum value of the parameter number in argv is argc-1.
Any attempt to access parameters outside the range 0..argc-1 is an error.

For example a small segment of code to write out the command line parameters of a program might look like this:

    var str : packed array[1..20] of char
       ...
       writeln( 'there are ', argc:1, ' command line parameters' );
       argv( 0, str );
       writeln( 'program name is ', str );
       if argc > 1 then begin
         writeln( 'the command line parameters are:' );
         for i := 1 to argc-1 do begin
           argv(i, str);
           writeln( 'argv[', i:1, '] is ''', str, '''' );
         end; {for}
       end; {if}

Note:

Attempts to access command line parameters with index outside the range 0 .. argc-1 produce a fatal error.


case statement

Case statements accept a range for case values, as in the example:

    case e of
         0:        writeln('empty');
         1..12:    writeln('less than a dozen');
       end;

The keyword 'otherwise' is allowed in a case statement to handle case values not handled by the case constants, eg

       myChar := 'q';
       case myChar of
         'a','e','i','o','u': writeln('myChar is a vowel');
         '0'..'9'           : writeln('myChar is a digit');
         otherwise
           otherLetter := true;
           writeln( 'myChar is not a vowel or a digit' );
       end;

Any number of statements may appear between the otherwise and end reserved words, there is no need for a separate begin..end.

If the case selector (myChar in the above example) does not correspond to one of the case constants, and there is no otherwise part of the case statement then a runtime error will occur.

Note that one purpose of runtime errors is to highlight programming errors, so when the case statement has an
otherwise part that does very little, these programming errors may remain unnoticed. (In case you're wondering - another purpose of runtime errors is to prevent the program behaving unpredictablly and damaging its environment.)

Case statements with ranges and an otherwise part are a very widely supported by most pascal compilers, so using these features is likely to be portable.


succ() & pred() take an optional second argument

If succ(e) returns the value that follows e, succ(e,n) returns the nth value after e, providing such a value exists
Similarly, pred(e,n) returns the nth value before e.

eg

       type
          shape = (triangle, rectangle, circle);

       var
          colour : (white, red, orange, yellow, green, blue);
       ...

       succ(triangle, 0) { yields triangle}
       succ(yellow)      { yields green}
       succ(yellow, 2)   { yields blue}
       pred(red, -1)     { yields yellow}
       pred(triangle, 0) { yields triangle}
       pred(green)       { yields yellow}
       pred(blue, 2)     { yields yellow}

These are as defined for iso-1026 extended pascal.

Notes:

  • the first argument must be an ordinal value (just like the old pred/succ)
  • the second argument must be an integer. It may be negative or zero, and pred( e, -n ) = succ( e, n )
  • the result may not exist (eg succ(green,2) above). If run time checking is enabled, then a fatal error is produced.
  • these variants of succ and pred can be used as an inverse ord function.
    In the above example, ord(orange) is 2. To go the other way, use succ (or pred) like this:

     succ(white, 2) { colour that corresponds to the integer 2 }
    

    If you can't guarantee that white always will be the first colour, use this:

     succ(white, 2-ord(white))
    

predefined constants

in addition to the standard predefined constant maxint, the following are available:

  maxreal   // the largest real number
  minreal   // the smallest positive real number (that has full precision)
  epsreal   // the smallest number greater than 1.0 is (epsreal + 1)
  maxchar   // the larget value of type char

these are as defined for iso-1026 extended pascal.

Note that many systems allow numbers smaller than minreal, but with a loss of precision. Eg, for 64-bit ieee systems, minreal is 2.225074e-308, and a smaller number such as 0.005074e-308 is still valid, but has fewer significant digits.


file Assign()

The assign(f,s) procedure connects a named file to a file variable, where f is a file variable and s is a pascal string variable or constant.
After calling assign, when calling either reset(f) or rewrite(f) the external file is opened.

If assign() is not used, reset(f) and rewrite(f) open temorary files that are not accessible to the world outside your pascal program.

For example to read data from the text file 'mydata':

            var f: text;
            begin
              assign( f, 'mydata' );
              reset(f);
              ....

Any trailing blanks are stripped from from the name string. This means that trailing blanks in file names obtained from the command line using argv() do not need to be processed.

If the string is empty (or all blanks), the file variable is not assigned to any file, and reset or rewrite will open a temporary file (just like standard pascal does).

Many pascal compilers offer this procedure, so code that uses this function is very likely to be portable to other pascal compilers.


file extend

It is possible to append new data to an existing file with the extend procedure, eg

           var f: text;
           ...
           rewrite(f);
           writeln(f, 'first line');
           reset(f);
           ...
           extend(f);
           writeln(f, 'another line');

It is an error to call extend with a file that does not exist.

This feature is defined in iso-1026 extended pascal. Many other versions of pascal have a similar procedure, although sometimes it is called append().


the exponent operator

This raises a real number to a real or integer power.
eg, for x raised to the power of 2.3,

    r := x**2.3;

The left factor (ie x in this example) is real, or is converted to real, and the result is always real.

If the power (ie the factor on the right) is an integer, more efficient code is produced but the result may be less accurate for very large powers( say x**(100 million) ).

A negative number raised to a non integer power produces a fatal error unless debugging is disabled - in which case the results are not defined.

By the rules of pascal, -2**2 is interpreted as -(2**2), ie the ** is evaluated before the negation. If you want (-2)**2, use parentheses.

Two or more exponents in a row must have parentheses, eg

           r := (x**2)**3; { OK, this uses parentheses }
           r := x**(2**3); { OK, this uses parentheses }
           r := x**2**3;   { error }

Note that exponents were left out of the original pascal because they encouraged lazy programming. For example it is easy to evaluate a polynomial like this:

        x := 12*x**3 + 8*x**2 + 4*x + 1;

but it can be coded much more eficiently as

        x := (((12*x) + 8)*x + 4)*x + 1;

Note:

a negative number raised to a non-integer power preduces a fatal error
eg

      x := -1.2**0.8; // error, only integer powers of negative numbers are allowed

Many pascal compilers provide a similar ** operator, but vary slightly in the way they handle integer powers.


relaxed constant string assignment & compare

a constant string does not need to be as long as the string it is assigned to or compared with.
The string is padded with as many null characters as necessary.

for example,

    type str = packed array[1..9] of char;
    var
        myStr : str;
    begin
      myStr := '123         ';  {standard pascal}
      myStr := '123';        // p5x extension
      if myStr > '122' then
         ...

Similarly, a parameter may be assigned a shorter constant string:

    procedure myProc(s:str);
    ...
    myProc('abc');  // works for parameters as well

Notes:

  • many pascal versions provide similar extensions, but may pad with spaces
  • it is an error to attempt assignment of longer strings - only constant strings may be shorter, you cannot have, say,

     myShortStr := myLongString;  // assign string variables
    
  • the string library functions in string.inc.pas and the code in tstring.pas show how strings can be used flexibly and efficently in p5x.


external variables

the external directive can be used with variables in much the same way as it can be used with functions and procedures.

eg the following accesses a real variable called myvar in an external c source file:

    var myvar: real; external;         // case must match
        ...
        writeln( 'myvar is ', MYVAR:1:2 ); // pascal rules - any case is OK

Notes:

  • the case of myvar in the declaration must exactly match the case of myvar in the external file.
  • when accessing myvar in other parts of the code, normal pascal rules apply and the case is not important (as shown in the above example).
  • the external c file must be linked with your pascal program. For example if you had a c file called ext.c with external definitions in it, you could link it with your pascal program with something like this:
      p5x myprog.pas myprog.c > myprog.lst
      cc -I . -lm -o myprog myprog.c ext.c
    

function and procedure inlining

the inline directive allows functions and procedures to be inlined, for example

   function getChar(pos:integer): char; inline;
   begin
      nextChar := buffer[pos];
   end;

So now all calls to getChar are (probably) replaced by the code inside getChar, and we can expect more efficient code - not least by saving the overhead of the function calls.

Notes:

  • forward declared functions can be inlined, but only after they have been defined. The inline directive must appear where the function is defined, like this:
        procedure forwardProc(var x: real); forward; // declared forward here
        ...
        procedure someProc
        begin
           forwardProc(r);           // cannot be inlined here
        end;
        ...
        procedure forwardProc; inline;               // declared inline here
  • any problems calling inlined procedures recursively are dealt with by gcc

  • the inline directive does not force inlining, it just provides a hint to the compiler. gcc inlines simple functions anyway, whether or not they are declared with the inline directive.


hexadecimal numbers

hexdecimal numbers start with a $, eg

    const mask = $000f;

hexadecimal constants can be used anywhere a constant can.
This is a very common feature, so code that uses hex numbers like this is likely to work on other pascal compilers.


bit operations

the normal bit operations are provided by predeclared functions:

bitand(a,b)   // return bitwise and of a and b
bitor(a,b)    // return bitwise or of a and b
bitxor(a,b)   // return bitwise xor of a and b
bitnot(a)     // return bitwise complement of a
rshift(a,b)   // return bitwise right signed shift of a by b places. The
                        newly vacant bits on the left are filled with the sign
                         bit, ie ones for negative numbers, zeroes for positive
                          numbers or zero.
rshiftu(a,b)  // return bitwise right unsigned shift of a by b places. The
                          newly vacant bits on the left are always filled with zeroes.

A right shift by a negative amount is interpreted as a left shift, and the newly vacant bits on the right are filled with zeroes.

as an example, for

       a := $ff00; b := $f0f0;

then

   bitand(a,b) returns $f000
   bitor(a,b)  returns $fff0
   bitxor(a,b) returns $0ff0
   bitnot(a)   returns $00ff

   rshift(a,4)   returns $0ff0
   rshift(a,-4)  returns $ff000
   rshiftu(a,4)  returns $0ff0
   rshiftu(a,-4) returns $ff000

So, if a is a positive number, then rshift & rshiftu produce the same result. Also, they produce the same result for a left shift, ie when b is negative.

On the other hand, if a is negative, and b is positive the most significant bits of the result will be 0 for rshiftu and 1 for rshift.
eg, assuming 32 bit integers

  rshift( $ffffffff,4 )  returns $ffffffff
  rshiftu( $ffffffff,4 ) returns $0fffffff

Most pascal compilers that allow bitwise arithmetic on integers allow the 'and' and 'or' to operate on integers, and may also introduce new operators such as xor and shl. This can create precedence confusion, which do not occur when the bit operations are implemented as functions.
eg what does this mean:

 a or b xor c

is it

 (a or b) xor c

or maybe

 a or (b xor c) ?

To create portable programs, you may need to write your own bitxxx functions that use the corresponding bit operators.
eg

 { my bitand function }
 function bitand(a,b: integer;
 begin
    bitand := a and b;
 end; {bitand}

If you're thinking of using the bit operators to extract bit fields from an integer, you might like to consider using a packed record instead.
eg, consider assigning digits of a bcd encoded integer:

      var bcdValue: integer;
      ...
      tensDigit:= 3;
      bcdValue := bitor( bitand(bitnot($000000f0), bcdValue), {clear tens digit}
                         rshift(tensDigit, -4) );             {assign new value}

or, alternatively:

      var bcdValue: packed record
                       units    : 0..9;  { bits 0..3, ie ls 4 bits }
                       tens     : 0..9;  { bits 4..7 }
                       hundreds : 0..9;  { bits 8..11 }
                       thousands: 0..9;  { bits 12..15 }
                    end;
      ...
      tensDigit:= 3;
      bcdValue.tens := tensDigit;

GetTimeStamp()

There is a predefined type called timeStamp that is defined as follows:

    type timeStamp = packed record
                        year: integer;
                        dateValid,
                        timeValid: boolean;
                        month: 1..12;
                        day:  1..31;
                        hour: 0..23;
                        minute: 0..59;
                        second: 0..60;  {allow leap second!}
                        day_of_week: 1..7;
                        isdst: boolean;
                        dstValid: boolean;
                     end;

There is also a predefined procedure called getTimeStamp that finds the current date and time. eg

        var t: timeStamp;

        begin
            getTimeStamp( t );
            writeln( 'the year is ' t.year );
            ....

This is almost identical to the terms described in the extended pascal standard (iso 10206). Differences include:

  • dateValid and timeValid are both true or both false (in fact they are different names for each other)
  • if time and date are invalid, the other fields are undefined
  • the seconds field accounts for leap seconds
  • day_of_week is an extra field, 1 = Sunday, ... 7 = Saturday
  • dstValid is true if the daylight saving information is known, otherwise it is false.
  • isdst is a daylight saving flag, true if current time is daylight saving time. Otherwise it is false.
    If dstValid is false, idst will also be false.

halt

This procedure does what it says - it halts the program with exit code 0.
Optionally, it may take an integer argument for a specified error code.

halt;    // exit code 0
halt(99) // exit code 99

Use it when something has gone wrong, and there is no hope of recovery, for example:

       if eof(myFile) then begin
          writeln('unexpected end if input, quitting');
          halt;   // what else can we do?
       end;

Contents

the pascal language
using the p5c compiler
p5x - pascal extensions
implementation notes


Related

Wiki: -quick summary
Wiki: Home
Wiki: The Pascal Language
Wiki: implementation notes
Wiki: using the p5c compiler
Wiki: using the p5x compiler

Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.