p5x is a version of the pascal compiler that allows extensions that are common in other pascal compilers, for example
This makes code easier to read and aids in porting other pascal programs to p5x.
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.
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 }
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.
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 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.
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 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.
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:
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))
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.
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.
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().
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.
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:
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.
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:
p5x myprog.pas myprog.c > myprog.lst cc -I . -lm -o myprog myprog.c ext.c
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:
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.
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.
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;
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:
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;
the pascal language
using the p5c compiler
p5x - pascal extensions
implementation notes
Wiki: -quick summary
Wiki: Home
Wiki: The Pascal Language
Wiki: implementation notes
Wiki: using the p5c compiler
Wiki: using the p5x compiler