Pascal Extensions and Enhancements

Alice supports a superset of ``standard'' Pascal (i.e. Pascal as described in The Pascal User Manual and Report by Kathleen Jensen and Niklaus Wirth). Much of Alice's extensions are based on WATCOM Pascal and are described in the WATCOM Pascal Tutorial and Reference Manual by F.D.Boswell, T.R.Grove, and J.W.Welch. However, to make our manual complete, this chapter will describe all deviations from standard Pascal, including those derived from WATCOM Pascal.

The Program Statement

The contents of the program statement are entirely ignored. This means that the names of the program and of the files on the program statement may be the same as names in the body of the program.

Names

The underscore ``_'' is a valid character in the middle of names, but may not be used as the first character. Thus ``input_line'' is a valid name, but ``_hello'' is not.

Alice Pascal is case insensitive -- the letters used to enter a symbol may be upper or lower case. It will recognize NAME , name , Name , nAmE , etc. as equivalent names. However, Alice always regards the declaration version of a name as the ``authoritative'' version. Thus if you enter NAME the first time you mention the name in a program, all subsequent references will be converted to this form. Even if you enter name , Alice will display it as NAME once the symbol is recognized.

Strings

In standard Pascal, strings have the type

packed array [1..N] of char

where N is the length of the string. A strict interpretation of types would therefore prevent the assignment of a string constant to a string variable if the lengths are different. This makes life very difficult for people who want to write string manipulation subroutines.

To get around such problems, Alice Pascal supports the following extensions.

String Constants

String constants can be enclosed in either single quotes or double quotes.

A double-quoted string of characters always has the type packed array of char , regardless of how many characters are inside the quotes. This means that you may have null strings ("" , zero characters), and single character strings (e.g. a ). According to the strict definition of Pascal, a single character in single quotes always has the type char instead of packed array of char . Thus you could not assign 'a' to a packed array of char variable but you could assign "a" .

String Compatibility

In Alice Pascal, all string types are assignment compatible. By a string type, we mean anything with a type of the form

packed array [1..N] of char

Furthermore, all string constants are assignment compatible to string types.

If you assign a longer string to a shorter string, the assigned string will be truncated to the appropriate length. For example, suppose str is a string type with length 3.

str := "abcde";

stores the string "abc" in str .

If you assign a shorter string to a longer string, the characters of the shorter string are placed in the longer string and then a special ``end of string'' marker named StrEnd is added to show the end of the string. For example, suppose str6 is a string type variable of length 6. If we assign

str6 := "lgs";

str6 will have the following elements.

str6[1] = 'l'
str6[2] = 'g'
str6[3] = 's'
str6[4] = StrEnd
str6[5] is undefined
str6[6] is undefined

The StrEnd character has the type char . It is the ASCII null, so its ordinal value ord(StrEnd) is zero. If we assign

str6 := "abcdef";

there is no StrEnd character because all six elements of str6 are filled by characters in the string.

Strings of different lengths are comparison compatible as well as being assignment compatible. For example, one might say

if str1 > str2 ...

Comparisons are made character by character using the ASCII collating sequence. Comparisons will stop at the end of the shorter string or when StrEnd is encountered in either string.

String constants are compatible with the Pointer type. This is discussed in the explanation of the Pointer type, later in this chapter.

String I/O

If a string type variable is specified in a read or readln instruction, Alice will place one character at a time into the variable until the variable is full or end-of-line occurs. If the reading stops because of end-of-line, a StrEnd marker will be placed in the string after the last character read.

Declarations

The following extensions are related to declarations.

Multiple Declaration Blocks

Alice Pascal lets you have several blocks of the same kinds of declaration in a single subprogram or the program mainline. For example, you can have several separate var blocks if you wish. This can happen if you bring in a lot of declaration blocks with the MERGE command or if you type a declaration keyword like var on a Declarations placeholder. The basic provision is that you may not have forward references (except when declaring a pointer type to a type that has not yet been declared).

Forward Declarations

Alice does not work with forward declarations in the same way that standard Pascal does. See Chapter 5 for more details.

Passing Subprograms as Parameters

Alice lets you pass functions and procedures as arguments to other subprograms. To define a subprogram that takes such an argument, enter the keyword function or procedure in an empty Parameter placeholder in the heading of the subprogram. Alice will lay out a formal function or procedure parameter declaration. The formal declaration requires a full list of the parameters that the function or procedure takes. Thus you might see

procedure findroot(function f(x:real):real);

at the beginning of a procedure definition. The findroot procedure takes one argument. This argument happens to be a function. Inside the code of findroot , the argument function will be called f . The argument function takes one real argument and returns a real value.

You must type in a function or procedure keyword for every formal parameter. This means that you can't say something like

function f(procedure a,b) ...

Instead, you have to use

function f(procedure a; procedure b) ...

Set Types

The base type of a set may contain a maximum of 256 elements. Moreover, the maximum ordinal value (from the ord function) in the base type must be in the range 0..255. None of the elements of the base type can be negative. This means that set of char is all right, but set of integer is invalid, as well as sets of subranges like 0..1000 (upper limit is too high) and -2..2 (can't have negatives). You can create a set of any enumerated type.

Multi-Dimensional Arrays

Alice supports multi-dimensional arrays but uses a syntax that is slightly more restricted than standard Pascal.

Pascal permits declarations like

array [1..10,1..20] of char;

Alice does not allow multiple ranges inside a single set of braces. If you enter 1..10 followed by a comma inside the braces, Alice will automatically expand this to

array [1..10] of array [Range] of Type;

You can then fill 1..20 into the second Range placeholder.

Similarly, Alice will automatically convert subscripting operations like

arr[M,N]

into

arr[M][N]

Note that you may still enter your subscripts using the first format (and APIN can translate the format properly). Alice will just display the subscripts using the second format.

Statements

The following extensions are related to statements.

ELSE Clause in CASE Statements

case statements may have an else clause. This clause is executed if none of the other case instances match the value of the case selection expression. For example, you might have

case power of
  1: begin
      writeln(x);
      end;
  2: begin
      writeln(x*x);
      end;
  3: begin
      writeln(x*x*x);
  else begin
      writeln("power has unacceptable value");
      end;
  end;

Unlike the other case labels, there is no colon after the else. To get an else clause in Alice, just enter the keyword else on a Case-Instance placeholder.

If a case statement has two else clauses, you will be given an error message, but not until run-time.

WITH Statements

Alice does not support multiple record variables in a single with statement. Standard Pascal accepts

with a, b do ...

but Alice requires the equivalent form

with a do begin
   with b do begin ...

GOTO Statements

You may not use a goto to jump into the middle of a begin block from outside the block. This applies to all begin blocks, including the ones inside other structures (e.g. for or while statements).

You can use goto to jump out of such a block, provided that you are not using EXECUTE or superstepping at the time.

Calling Subprograms

As in regular Pascal, you may not define a function that returns a structured type (i.e. an array, a record, a set, or a file).

If you are passing a record or array as a value argument, it is not necessary to initialize every field in the record or every element in the array. You will, however, receive an error if the called routine tries to use an uninitialized record field or array element.

A subprogram may declare parameters with subrange types. However, Alice will not check that arguments passed to the subprogram fall inside the declared subrange -- Alice only checks that the arguments have the proper base type.

Debugging Features

Pressing the [CTRL-BREAK] key will stop an executing program. The program will be placed in a suspended state, allowing you to examine it using debugging facilities like immediate mode. Execution can usually be continued via the CONTINUE or STEP commands (see Chapter 4).

Alice performs a number of error-checks that are not usually found in other versions of Pascal.

  1. At run-time, Alice detects the use of uninitialized variables, i.e. variables that have not yet been assigned a value.
  2. Alice checks subrange bounds in scalar assignments.
  3. Alice detects array subscripts that have gone out of bounds.
  4. Alice checks for argument values that are out of bounds when calling built-in routines.
  5. Alice also detects the following:

-- indirecting through a nil pointer

-- set elements out of bounds

-- division by zero

-- modulus values out of bounds

-- case values that match no labels

-- succ or prec out of bounds

-- goto an invalid label

-- attempts to read beyond end-of-file

and a number of other erroneous conditions.

On the IBM PC, Alice does not detect arithmetic overflow or underflow yet.

Standard Library Routines

The following extensions are related to standard library routines. Non-standard library routines are described in separate sections later in this chapter.

The readln Procedure

readln takes an optional string constant that will be used as a prompt for input. For example,

readln("Enter a value for x: ",x);

would print out

Enter a value for x:

and then wait for the user to enter an appropriate value.

Terminal Input for Read and Readln

Input is only read from the terminal when the program calls for it, and then Alice reads an entire line. This is slightly different than standard Pascal, which is always one character ahead of the user. When you are entering an input line, you may correct typing mistakes with backspacing. Nothing from the input line is passed to the program until you press [ENTER].

Note that calls to the eoln and eof functions actually perform a read operation on the terminal to see if there is a character waiting to be read. This can cause some problems. For example, suppose you have

while not eof do begin
    writeln("Enter integer");
    readln(i);
    ...
    end;

The call to eof causes Alice to read an entire line of input from the terminal. This happens before the writeln instruction writes out the prompt for input. You may have to rewrite programs slightly to get the prompt in the right place.

You can create an explicit ``end-of-file'' condition by pressing F6 as the first character of an input line. You do not have to press [ENTER].

If you ask read or readln to read a number from the terminal, but they find something non-numeric, they throw away the rest of the input line and keep looking for a number. For example, suppose you say

read(i,j,k);

and enter the input line

15 b 68

the variable i gets the 15, then a warning is printed indicating that Alice expected numeric input but found a b . Alice will read in another line of input to give values to j and k .

Reset and Rewrite

The standard procedures reset and rewrite have been extended. They now have the form

reset(filevar,filename);
rewrite(filevar,filename);

In both cases, filevar is a normal file variable. The filename argument is a string giving the name of the file that should be associated with the file variable. For example,

reset(indata,'myfile');

associates the file named ``myfile'' with the file variable indata and opens ``myfile'' for input.

If the file name argument is omitted, Alice will use the name of the file variable as the file name. For example, if you say

rewrite(info);

Alice will write output to a file named ``info''. If the file variable is a field in a record, Alice will use the name of the record variable, not the field name.

The special file format

?:file

may be used in reset , rewrite , and append calls, as in

reset(filevar,"?:mydata");

As noted in Chapter 5, this format tells Alice to search a number of devices for the desired file.

Files are closed automatically upon completion of the subprogram in which they are opened. If they are opened in the program mainline, they are closed upon program termination.

Writeln

If the user doesn't specify a field width or a precision when writing out a real number, writeln chooses an ``optimal'' format for the output. This may or may not be exponential format (standard Pascal always uses exponential format). If a field width is specified but not a precision, Alice will use exponential notation. If both a field width and a precision are specified, Alice will write the number in the format requested.

If no field width is specified when writing an integer, Alice will print the integer with a single leading blank. If you do not want this leading blank, specify a field width of zero.

You may use writeln to write enumerated type values. Unlike many other versions of Pascal, Alice will write out the actual name of the value. For example, if you have

type
    colour = (red,blue,green);
    ...
writeln(red);

Alice will write out the word red .

Similarly, Alice will write out sets as a list of values surrounded by square brackets. You may thus use writeln to write out sets.

The new Procedure

If new is being used to allocate space for a record that has a variant part, standard Pascal lets you specify values for the tag field(s) of the variant part. By plugging in the tag field value(s), the new procedure can calculate the minimal amount of memory required to store the new record.

In the current implementation of Alice, tag field arguments can be passed to new , but the procedure does not use them. It always allocates enough space to store the maximum possible length of the record.

Unimplemented Routines

Several standard subprograms have not been implemented in the current version of Alice. Alice recognizes their names and programs can contain calls to these routines, but the routines do nothing. The unimplemented routines are

pack
unpack

In addition, the dispose routine only does half of what it's supposed to -- it deletes something allocated with new but does not make that memory available for a new use.

New Built-In Symbols

The built-in subprograms, types, etc. discussed in this section are not found in ``standard'' Pascal.

String Manipulation Routines

String manipulation routines manipulate string constants and variables with string types. Our descriptions of these routines will give examples of source code using each routine, followed by output from this source code.

StrConcat(str1,str2);

This procedure concatenates the contents of str2 on the end of str1 .

str1 := "hello";
str2 := " there";
StrConcat(str1,str2);
writeln(str1);
- - - - -
hello there

StrDelete(string,N,position);

This procedure deletes N characters from string . Position is an integer giving the subscript of the first character to be deleted.

str := "The quick brown fox";
StrDelete(str,2,5);
writeln(str);
- - - - -
The ick brown fox

StrInsert(str1,str2,N);

This procedure inserts the contents of str2 into str1 . The first character of str2 will become character N of str1 .

str := "The quick fox";
StrInsert(str1,"brown",5);
writeln(str);
- - - - -
The brownquick fox

i := StrLen(string);

This function returns an integer giving the length of a string. For a string constant, this is the number of characters in the constant. For a string variable, it is the number of characters currently stored in the variable. If the variable is not full, it will be the number of characters up to but not including the StrLen that follows the last character in the string.

str := "abcdef";
writeln(StrLen(str));
str := "abc";
writeln(StrLen(str));
writeln(StrLen("xyz");
- - - - -
6
3
3

i := StrScan(str1,str2);

This function looks for the string str2 inside str1 . If it is found, StrScan returns the subscript where str2 begins in str1 . If the string is not found, StrScan returns 0.

i := StrScan("hello there","the");
writeln(i);
- - - - -
7

i := StrSize(string);

This function returns an integer giving the maximum length of a string. If the string is a string constant, this is the number of characters in the string. If the string is a string variable, this is the maximum number of characters that the string can hold, regardless of how many it holds at the moment.

var
    str : packed array [1..10] of char;
  ...
writeln(StrSize("abc"));
str := "abc";
writeln(StrSize(str));
- - - - -
3
10

SubStr(str1,N,position,str2);

This procedure obtains a copy of part of str1 . N is the length of the substring and position is the subscript where the substring starts. The substring that is obtained is copied into str2 .

str1 := "Hello there";
SubStr(str1,2,3,str2);
writeln(str2);
- - - - -
ll

File Routines

The following routines deal with I/O to and from files.

append(filevar,filename);

This procedure is much like rewrite in that it opens a file for writing. The file is associated with the file variable filevar . The name of the file is given by the string filename . If the filename argument is omitted, Alice will use the file variable name as the file name.


The difference between append and rewrite is that append sets up for writing at the end of the current contents of the file. Thus, new output is added to what the file already contains. With rewrite , output is written over the file's current contents, and those previous contents are lost.

update(filevar,filename);

This procedure opens a file for direct access reading and writing. The filevar argument is the file variable that will be associated with the file. The filename argument is a string that gives the name of the file. If this string is omitted, Alice will use the name of the file variable as the file name.


Opening a file for direct access I/O does not let you add new elements to the end of the file, but it lets you read and write existing elements in any order. When update is issued, the file is positioned at the beginning and an initial get(filevar) is performed. This means that the buffer variable filevar^ will contain the value of the first element of the file. You may move to any other element of the file with the setnext procedure.

setnext(filevar,record);

This procedure moves to an arbitrary record in a direct access file. The filevar argument must be a file variable that has been opened with the update procedure. The record argument is an integer indicating the number of the file element to which you wish to move. The first element in the file is numbered 0.

Terminal I/O

The following routines deal with I/O to and from the terminal.

bool := Char_Waiting;

This function returns a Boolean value indicating whether or not there is a character waiting to be read from the keyboard. It returns ``true'' if there is a character waiting and ``false'' otherwise.

Cursor_To(Row,Column);

This procedure moves the cursor to a particular position on the terminal screen. Row is an integer giving the row to which the cursor should be moved. The top row is numbered 0. Column is an integer giving the column to which the cursor should be moved. The leftmost column is numbered 0. Thus Cursor_To(0,0) puts the cursor in the upper left hand corner while Cursor_To(13,40) moves the cursor to about the middle of the screen.

c := Get_Char;

This function gets a single character from the keyboard. If there isn't an input character already waiting, Get_Char waits for one to be entered. Unlike read and readln , Get_Char obtains a character immediately -- it does not wait for an entire line to be entered. Get_Char cannot detect end-of-file.

You should avoid mixing calls to Get_Char and Char_Waiting with calls to standard I/O functions like readln , get , writeln , etc., since they perform I/O in entirely different ways. (Standard I/O functions buffer I/O, while Get_Char does not.)

i := ScrXY(code);

This function returns integer values that provide information about the terminal screen. The code argument is an integer indicating what kind of information you want.

ScrXY(1) -- tells how many rows the screen has.

ScrXY(2) -- tells how many columns the screen has.

ScrXY(-1) -- row of current cursor position (top=0).

ScrXY(-2) -- column of current cursor position (left=0).

Set_Attr(code);

This procedure is used to set terminal attributes when your program is writing output. The argument of Set_Attr is an integer indicating the attribute you want to set (colour, bold face, reverse video, etc.). The integer is interpreted as a bit pattern, according to the following key:

Bits 0-2 (values 1-7)

colour; on a monochrome screen, 1 means underlining; colour codes are

0 -- black

1 -- blue

2 -- green

3 -- cyan

4 -- red

5 -- magenta

6 -- brown

7 -- white

Bit 3 (value 8)

bold face (brighter for colour)

Bit 5 (value 32)

"reverse video" (black on white background)

Bit 6 (value 64)

black on coloured background; colour given by bits 0-2

Bit 7 (value 128)

blinking output

For example, a code of octal 054 would give reverse video bold red.

Pointers

Alice supports a number of built-in routines for obtaining and using pointers into memory. A number of these make use of the new built-in named type Pointer .

The Pointer type is assignment compatible with all other Pascal pointers. The nil pointer is a good example of the Pointer type; it may be assigned to any other pointer.

Alice lets you assign string constants to Pointer variables. In this case, the string is created in memory and the Pointer variable receives a pointer to where that string is stored. For example, you might have

type
    str10 = packed array [1..10] of char;
var
    p : Pointer;
    sp : ^str10;
begin
p := "Hello";
sp := p;
writeln(sp^[1]);
- - - - -
H

i := address(variable);

This function obtains the actual memory address of its argument. The argument must be a variable name, a subscripted variable, a field inside a record variable, or an indirectly-referenced variable. address returns an integer that gives the machine address that has been allocated to that variable. For example,

i := address(v);

obtains the address of the variable v .

You can also specify a second argument for address , indicating whether you want the segment containing the variable or the offset of the variable within the segment. If the argument is the integer 0, you get the offset; if it is any other integer, you get the segment. If this second argument is omitted, address returns the offset. For example,

i := address(v,1);

obtains the segment that contains the variable v .

p := MakePointer(variable);

This function is much like address except that it returns a Pointer type value instead of an integer. This can be assigned to any valid Pascal pointer type. Unlike address , MakePointer does not take an optional second argument. Alice Pascal pointers are constructed to give both the segment and offset of a particular memory location.

i := peek(address);

This function returns the value that is at the given memory address. The address argument is given as an integer. The value that is returned is a single byte and will therefore be in the range 0..255. On the IBM PC, only the first 64K may be accessed this way. To access other addresses, use RawPointer (described below).

poke(address,value);

This procedure stores a value in the given address. The address is given as an integer. Since poke only stores a single byte, the value argument should be in the range 0..255. If it is not, poke will use value mod 256 . On the IBM PC, only the first 64K may be accessed this way. To access other addresses, use RawPointer (described below).

p := RawPointer(offset,segment);

This function converts an integer address into a Pointer type. The offset argument is an integer giving an offset within a segment, and the segment argument is an integer giving a segment number. If the segment is omitted, 0 is assumed. The value returned by RawPointer has the Pointer type.

Random Number Generation

initrandom(seed,upper);

This procedure initializes a pseudo-random number generator. Both seed and upper are integers. Different values of seed will give different number sequences; identical values of seed will give identical sequences. Random numbers will be generated in the range 0..(upper-1) .

i := random

This function returns a pseudo-random integer. You may not call random until you have initialized the random number generator with initrandom .

System Calls

i := SysFunc(p,arg,arg,arg,...);

This function calls a system function. The p argument is a Pointer type pointing to the memory location of the function to be called. The function is passed an argument count and a pointer to a vector of arguments. The value returned by SysFunc is the value returned by the system function. This routine is not intended for use by normal users.

SysProc(p,arg,arg,arg,...);

This procedure works the same way as SysFunc . It is used to call system routines that do not return a value.

Miscellaneous Symbols

Alice_Version

This is a built-in constant giving the version number of the Alice you are using.

pause;

When Alice has ``Debug On'' and it encounters a call to the pause procedure, the effect is the same as if a breakpoint was triggered while running the program. The program will stop and wait for run-time commands (e.g. going into immediate mode). When Alice has ``Debug Off'', the pause procedure has no effect.

i := SizeOf(variable);

This function returns the number of bytes of storage that are allocated to a particular variable. With string variables, SizeOf is equivalent to StrSize .