IMP80 Language Manual To use the Index, type "index," followed by the entry you require followed by '?'. E.g. index,record? Type C to get the full contents list. Abridged contents: PREFACE TO MANUAL /2 HISTORICAL INTRODUCTION /5 ELEMENTS OF THE LANGUAGE 1 TYPES, VARIABLES, CONSTANTS AND EXPRESSIONS 2 BLOCKS AND PROCEDURES 3 EXECUTABLE STATEMENTS 4 INPUT/OUTPUT FACILITIES 5 STORE MAPPING 6 STANDARD PROCEDURES 7 EXAMPLES (TO FOLLOW) 8 APPENDIX A: IMP80 SYNTAX 9 APPENDIX B: IMPLEMENTATION-SPECIFIC INFORMATION 10 APPENDIX C: EMAS - IMP80 CONVERSION 11 INDEX 12 .... Preface to manual follows .... PREFACE This manual describes the programming language IMP80, which is a common subset of several extant versions of IMP. The reason for the existence of these different versions is explained in the "Historical Introduction" (below). It is intended that for the foreseeable future implementations of IMP will follow IMP80 as far as possible, and in particular will not depart from it for frivolous or cosmetic reasons; however, this does not preclude implementation-specific extensions. Separate sections of Appendix B will be provided for each major implementation of IMP80. These detail departures from or extensions to IMP80, and include relevant system-dependent information. Such system-dependent information is also included occasionally within the main body of the manual, where it was felt that to have excluded it would have been inconvenient or even misleading. Such material is clearly flagged as not describing IMP80 itself, and is always repeated in the relevant section(s) of Appendix B. There are two implementations of IMP80 at the time of writing: EMAS IMP80, implemented on the ICL 2900 range by Peter Stephens, ERCC; and IMP77, implemented on several different machines by Peter Robertson, Lattice Logic Ltd., Edinburgh. This is primarily a reference manual, and it is hoped that the contents list and index are detailed enough to enable it to be used as such. In addition, however, it is felt that guidance on the use of language features should be given, as their utility might not be apparent from a statement of their syntax and semantics alone. The programming examples included for illustrative purposes are also intended to indicate good programming practice. Furthermore, Section 8 includes advice on writing IMP80 programs as well as some larger examples. A Backus-Naur Form (simplified) specification of the complete syntax of IMP80 is given in Appendix A. An explanation of the conventions used in the syntax definition is given at the end of Appendix A. This manual was written by Felicity Stephens and John Murison, apart from the Historical Introduction by Peter Stephens. Much of the text is based on earlier documents, in particular: "The Edinburgh IMP Language Manual" (second edition) edited by Roderick McLeod, Edinburgh Regional Computing Centre (1974), and "The IMP-77 Language" (third edition) by Peter Robertson, Department of Computer Science, University of Edinburgh (1980). Please report any suspected errors or omissions in this manual to the ERCC Advisory Service, James Clerk Maxwell Building, The King's Buildings, Mayfield Road, Edinburgh EH9 3JZ. John M. Murison Editor October 1981 HISTORICAL INTRODUCTION Evolution The Imp language is evolved from Atlas Autocode, which itself is a direct descendant of Algol 60. Although Algol 60 had only moderate success as a programming language - it was hardly used in the United States - no other language before or since has achieved more than a fraction of its influence on programming language design. At the same time as Algol was being devised and revised, in Manchester another event was taking place which was also to have wide influence. The University was building its fourth machine (the Atlas) - which true to tradition was at the very limit of the technology of the time. This machine was to introduce paging to the world; the idea being that memory management, provided by the operating system with hardware support, was cheaper and more efficient than allowing each programmer to overlay or shoehorn his program into the space available. After a slow start, this idea was to change the appearance of computing. Since Atlas was a revolutionary machine, Manchester had to write their own software, as they had done for the Manchester Mark 1 and Mercury. This led them to consider the attractions and disadvantages of Algol. Algol's principal attraction is its block and stack structure: by collecting space together on a stack and re-using it for successive procedure calls, an Algol program causes much less paging than the same program written in (say) Fortran. The disadvantages of Algol are the lack of standard input/output and the difficulties that some features of the language present to the compiler writer. The tragedy of Algol was that so little was gained from the features which presented most of the problems. Almost nothing of real power was gained either from call by substitution or failure to specify formal procedures adequately, and little was gained by the enormous generality of the %for statements. Yet the problems these areas posed caused all early Algol compilers to produce comparatively low quality object code. All this and more was obvious to the Manchester team and although Algol was to be implemented on Atlas, the prime language was a new one - Atlas Autocode. This was a simplified Algol with changes to the block, loop and procedure structures to remove the worst problem areas. It contrived to deliver 90% of the power of Algol to the programmer while only requiring 25% as much effort from the compiler writer. (Further details can be obtained from [1]The Computer Journal, Vol 8, pp 303-310 (1965/66)). In retrospect, the name was unfortunate since autocodes were normally low level languages, and "Atlas" indicated quite wrongly a degree of machine dependence. Edinburgh University started its computing with a data link to the Manchester Atlas and this happy accident began the long association between the University and the language. When Glasgow and later Edinburgh obtained KDF9 computers it was necessary to write a compiler for Atlas Autocode; this was carried out in a short time by Mr (now Professor) H. Whitfield and his associates. This compiler was in advance of its time in that it was written entirely in Atlas Autocode and developed on Atlas. It was transferred to KDF9 by the elegant technique of self-compilation. The compiler thus produced compared exceedingly well with the manufacturer's Algol compiler, both in compilation time and in object code efficiency. This project also confirmed that Atlas Autocode was free of implementation trouble spots and very suitable for large scale system programming. In 1966 there began a large scale project with a joint University/Manufacturer team to write a time-sharing operating system for the ICL 4-75 computer. The project was based in Edinburgh and the final system would have to support Atlas Autocode among other languages. The recent success of the Atlas Autocode compiler project led to the decision to implement the time-sharing system (later called EMAS) in a high level language called IMP. IMP was to be a superset of Atlas Autocode, containing additional features for system programming. It was at this point that almost all the main language changes were made and the distinctive philosophy of IMP originated. Philosophy and Style IMP was to be primarily a system programming language; in 1966 that was perceived to require: * efficient object code. System programs are liable to be executed millions of times. Thus features that could not be implemented efficiently should be omitted. * early compiler availability. The compiler should be available as soon as the hardware, otherwise programmers would program in something else. Consequently features that would or might cause implementation trouble spots must not creep into the language. * minimum run-time support. Some system programs like supervisors and loaders have to run in an environment almost devoid of support software. The language should be free of features requiring run-time support. (The Atlas Autocode fault statement conflicted with this aim and had to be banned from system programs, although retained in the language for user programs.) * readability. System programs have a long life and require maintenance. It is more important that the program be easy to understand than quick to write. Optional keyword omission or abbreviations should be banned. (The language should be "verbose rather than obscure".) * access to bizarre hardware features. System programs require access to funny features - to blow the hooter or ring the gong on hardware failure, for example. However the language would not be compromised here. Instead machine coding would be allowed at any point in the program, with access to the IMP variables and arrays. Even with fifteen years of hindsight this list still seems very relevant, although the need for efficient object code was more pressing then than now. The real key to the long life of IMP is the last item in the list. By allowing machine code in extremis almost no machine dependent features were included except the underlying one of a byte address structure. Consequently IMP has been successful on a dozen or so machines, unlike the main competitors of the era (PL360, Burroughs Extended Algol). In accordance with the language philosophy, the following changes were made to Atlas Autocode to produce IMP: * The internal character code was changed to ISO. * Logical operations were added to the language. * The additional declarators %byte %integer and %short %integer were introduced. * Structured data objects - %records - were introduced. * Text handling features were added specifically to aid writing of command interpreters. * Pointer variables and additional features were added to enable programmers to operate on storage areas outwith the compiler controlled stack. This language was used in the writing of EMAS and it proved possible to write a high performance multi-access operating system almost entirely in IMP. (Those requiring further information should read [2]The Computer Journal, Vol 17, pp 216-223 (1974).) Recent Developments After the successful completion of EMAS, responsibility for maintenance of the system and the IMP compiler passed from the department of Computer Science to the Edinburgh Regional Computing Centre. Over the next ten years the language evolved very slowly - even a move of EMAS from ICL 4-75 to ICL 2900 hardware scarcely disturbed the stability of the language. However, the Computer Science department, in pursuing its diverse research interests, encountered a variety of machines and wrote IMP compilers for a substantial proportion of them. As befits an academic department these compilers contained novel features and gradually diverged from EMAS IMP and from each other. In 1980 a stock-taking was instituted, from which there gradually emerged the common core of features described in this manual. Most compilers will have some additional features but will support this common base, and it should be possible to write most programs in the common subset. Such programs should be readily portable in the Edinburgh environment. P.D. Stephens ___________________________________________________________________________ Elements of the language IMP80 is a high-level block-structured language. Relative to Algol 60, it adds program structuring, data structuring, event signalling and text handling facilities, but removes (or retains in a modified form) intrinsically inefficient features such as the Algol 60 %name-type parameter. Character set An IMP80 program is a sequence of statements constructed using the ISO seven bit character set extended with a special alphabet. The special alphabet is used to construct keywords. The ISO seven bit character set is as follows: _______________________________________________________________________ | | | | | | 0 NUL | 32 SPACE | 64 @ | 96 ` | | 1 SOH | 33 ! | 65 A | 97 a | | 2 STX | 34 " | 66 B | 98 b | | 3 ETX | 35 # | 67 C | 99 c | | 4 EOT | 36 $ | 68 D | 100 d | | 5 ENQ | 37 % | 69 E | 101 e | | 6 ACK | 38 & | 70 F | 102 f | | 7 BEL | 39 ' | 71 G | 103 g | | 8 BS | 40 ( | 72 H | 104 h | | 9 HT | 41 ) | 73 I | 105 i | | 10 LF(NL) | 42 * | 74 J | 106 j | | 11 VT | 43 + | 75 K | 107 k | | 12 FF | 44 , | 76 L | 108 l | | 13 CR | 45 - | 77 M | 109 m | | 14 SO | 46 . | 78 N | 110 n | | 15 SI | 47 / | 79 O | 111 o | | 16 DLE | 48 0 | 80 P | 112 p | | 17 DC1 | 49 1 | 81 Q | 113 q | | 18 DC2 | 50 2 | 82 R | 114 r | | 19 DC3 | 51 3 | 83 S | 115 s | | 20 DC4 | 52 4 | 84 T | 116 t | | 21 NAK | 53 5 | 85 U | 117 u | | 22 SYN | 54 6 | 86 V | 118 v | | 23 ETB | 55 7 | 87 W | 119 w | | 24 CAN | 56 8 | 88 X | 120 x | | 25 EM | 57 9 | 89 Y | 121 y | | 26 SUB | 58 : | 90 Z | 122 z | | 27 ESC | 59 ; | 91 [ | 123 { | | 28 FS | 60 < | 92 \ | 124 | | | 29 GS | 61 = | 93 ] | 125 } | | 30 RS | 62 > | 94 ^ | 126 ~ | | 31 US | 63 ? | 95 _ | 127 DEL| |_______________|____________________|_________________|______________| The use of the character set is summarised in the sections below and described in detail in the rest of the manual. However, some general points about certain characters should be noted at the outset: a) Quotes Several language constructions call for one or more characters to be enclosed in quotes. Within quotes all characters are significant and stand for themselves; thus, for example, space, newline, and percent characters may appear between quotes and stand for space, newline, and percent. Two quote characters are used: ' - character quote " - string quote Examples: 'A', ';', '"', '!' "String", "sealing wax", "!" If it is required to include the delimiting quote within the text it must be represented by two consecutive quotes. Examples: '''' - the character quote "A ""big"" dog" - a string of eleven characters However, note '"' and "it's mine". b) Spaces Except when used to terminate keywords or when between quotes, space characters are ignored and may be used to improve the legibility of the program. c) Lower case letters Except when enclosed in quotes, lower case letters are equivalent to the corresponding upper case letters. d) Non-graphic characters The ISO characters with no graphic representation have code values less than 32 or greater than 126. Any non-graphic characters can be included within quotes in an IMP80 program, but only one, the newline character (NL), is included in the syntactic definition of the language (see Appendix A). This has the ISO code value 10. {EMAS IMP80: all characters with code values less than 32 are treated as spaces, apart from NL (ISO code 10) EM (ISO code 25) - terminates program SUB(ISO code 26) - causes a syntax fault It ignores DEL (ISO code 127) and maps all codes greater than 127 according to the extended Regional Network Code (which is detailed in Regional Communications Memo JID/78/M20.4). } {IMP77 Except for NL (see above) all characters not enclosed in quotes and with ISO codes outwith the range 32 to 126 inclusive are treated as spaces, but will be sent to the listing unaltered. The character FF (form feed) may be used to cause a new page to be taken in program listing files.} Statements An IMP80 program typically consists of an ordered set of statements, as follows: %begin %on %event .... %start %finish %end %of %program Only the delimiting keywords %begin and %end %of %program must be present. However, assuming that all the types of statement above are represented in an IMP80 program, the effect of running the program on a computer is as follows: First, storage space for the declarations is obtained. Conceptually, all the space is obtained at once - there is no defined order of execution of declaration statements. The procedure descriptions are skipped, as are the statements in the %on %event block, and the first of the executable statements is obeyed. Thereafter, the statements themselves define the sequence of execution ("flow of control"). Control passes to the %on %event block if one of the events defined in the %on %event statement occurs whilst executing a statement. The %on %event block is optional and often omitted. Here is an IMP80 program: %begin %integer A, B, SUM, DIFFERENCE, MAX, MIN MAX = -1; MIN = 5000 %cycle READ(A) %exit %if A=-1; ! End of input. READ(B) PRINTSTRING("Input values:") WRITE(A,3); PRINTSYMBOL(',') WRITE(B,3) SUM = A+B; DIFFERENCE = A-B PRINTSTRING(" ... Sum is") WRITE(SUM,3) PRINTSTRING(", Difference is") WRITE(DIFFERENCE,3); NEWLINE %if SUM>MAX %start MAX = SUM %finish %else %if SUM)| | | | ## (or \==) & ( ) * | | | | \ \\ + , - | | | | -> . / // : | | | | ; < <- << <= | | | | = == > >= >> | | | | @ \ (or ~) _ { } | | | | newline space | |_________________________________________________________________| {IMP77: \ and \\ may be replaced by ^ and ^^ respectively.} {EMAS IMP80: \ and \\ may be replaced by ** and **** respectively.} The special symbols are used in IMP80 in various ways: * as operators (arithmetic, string, record, relational, logical) * as separators, e.g. (..) {..} : ; , newline space * in constants, e.g. 3_201, +27@4, -17.6, X'8F', "Constant" * in record subfield references, e.g. TAX(MONTH)_OVERTIME * ! as an alternative to the keyword %comment Keywords A keyword is a sequence of letters in a special alphabet. In programs stored in a computer in text form ("source" programs), representation of this alphabet is achieved by using the character percent (%), which is defined as causing the subsequent letters (upper or lower case) to be shifted into the special alphabet. The effect of the % character is terminated by any non-alphabetic character, including space and newline. Hence the following statements are equivalent: %string(7) %array %name P %STRING (7) %ARRAYNAME P In this manual keywords are given in lower case. The following is a list of all the IMP80 keywords: ____________________________________________________________ | | | %alias %and %array %begin %byte | | %c %comment %const %constant %continue | | %cycle %dynamic %end %event %exit | | %external %file %finish %fn %for | | %format %function %half %if %include | | %integer %list %long %map %monitor | | %name %not %on %of %or | | %own %program %real %record %repeat | | %result %return %routine %short %signal | | %spec %start %stop %string %switch | | %system %then %unless %until %while | |____________________________________________________________| Miscellaneous statements Certain statements are described here rather than in subsequent sections, as they are different in kind from the other statements of the language, and do not readily fit into any of the categories used in the rest of the manual. Comments Textual comments can be included in an IMP80 program. They have no effect on the program when it runs, but may be used to render it meaningful both to the originator and to others. A comment is a statement, and must be separated from the preceding statement by a newline or semi-colon and from the succeeding statement by a newline - not by a semi-colon. Note that the continuation rules described above apply to comment statements. The keyword %comment is used to introduce the text of the comment, which may contain any character except newline. %comment may be replaced by the symbol '!'. {IMP77: comments cannot be continued onto subsequent lines by any of the methods described.} Example: %begin %comment This program sorts a list of names %comment into alphabetical order : : %end %of %program The IMP80 language also allows comments to be embedded within other statements. An embedded comment is any sequence of characters, excluding '}' and newline, enclosed in a pair of braces, '{' and '}'. This form of comment may appear between any two atoms, but may not occur within an atom. For convenience the closing brace may be replaced by a newline. Example: ! This is a portion of a test program COUNT = 0; ! Note the semi-colon before the '!'; note thenext one ! next one (in the text of the comment). LIMIT = 100 {Only 100 cases} MINIMUM = 0 {all positive PROCESS(X {cases}, Y {total cost}) This fragment of program is exactly equivalent to COUNT = 0 LIMIT = 100 MINIMUM = 0 PROCESS(X,Y) Note that this type of comment never counts as a statement in its own right, even when given on a line by itself. Thus it can be interposed between lines of a continued statement. %include statement A file of IMP80 statements (terminated by the statement %end %of %file) may be included in a source program by giving a statement of the form %include where is a string constant representing a system-dependent file name. Refer to the relevant section of Appendix B for details of any implementation-dependent limitations on the use of %include. Example: %begin %include "WXYZ83.EXTSPECS" %integer TIME, DISPLACEMENT, ... : : Note that the %include statement appears in the program listing generated by the compiler, followed by the contents of the file referred to (including the %end %of %file). The %include statement should not be followed on the same line of the source program by other statements, since the complete source line will appear in the program listing before the statements included, while in the actual program the statements following the %include follow the statements included. %list and %end %of %list Normally the compiler generates a listing of a program as it compiles it. The precise layout of this listing is implementation-dependent. The production of the listing can normally by controlled by setting a system option prior to the compilation. In addition, however, the programmer can control the generation of the listing by use of the statement %end %of %list, which inhibits the listing until the end of the program or the statement %list is encountered. The default is for the listing to be generated. The statements %end %of %list and %list may appear anywhere within an IMP80 program. Note that when listing is inhibited, incorrect statements and the accompanying fault messages are still listed. {IMP77: %end %of %list and %list are nested, so that two %end %of %list statements require two matching %list statements to switch on the listing again. This means that it is possible within an included file (see above) to control the listing of the contents of the file, without changing the listing status of the rest of the program.} Variables, constants, expressions Types Each data item (i.e. each constant or variable) used by an IMP80 program has a type associated with it which determines what sort of item it is or can have as a value. There are five categories of type: Arithmetic types These comprise integer and real types, as follows: %byte %integer %short %integer %half %integer %integer %long %integer %real %long %real %long %long %real Some implementations of IMP80 might not provide all of these types; see Appendix B for details. The modifiers %byte, %short, etc. relate to the size or precision of items of the appropriate type. For byte-addressed machines, the ranges and (where appropriate) precisions associated with arithmetic types are as follows: ______________________________________________________________________ | | | type normal range of values | | storage (inclusive) | | allocation | |______________________________________________________________________| |%byte %integer 8 bits 0 to 255 | |(or just %byte) | |%short %integer 16 bits -32767 to 32767 | |(or just %short) | |______________________________________________________________________| ______________________________________________________________________ | type normal range of values precision| | storage (inclusive) (decimal | | allocation digits) | |______________________________________________________________________| |%half %integer 16 bits 0 to 65535 | |(or just %half) | |%integer 16 bits or -32767 to 32767 or | | 32 bits -2147483647 to 2147483647 | |%long %integer 64 bits -9223372036854775807 to | | 9223372036854775807 | | Integer arithmetic involving values outside the range | | which a variable of type %integer can hold may be invalid. | |______________________________________________________________________| |%real 32 bits \ 7 | | | -7.2x10@75 to -1.2x10@(-77), | |%long %real 64 bits |- 0, 16 | | | 1.2x10@(-77) to 7.2x10@75 | |%long %long %real 128 bits / 36 | |______________________________________________________________________| String type The type %string relates to sequences of characters. An item of this type has a length associated with it, the actual or maximum number of characters which the item comprises or can have as a value. Record types An item of type %record is a composite of several sub-items, each of which has an associated type. When a variable of type %record is declared, its precise composition must be specified. Array types An item of array type is a composite of (in general) several sub-items, each with the same associated type, which must be one of those listed above. The name of the array type is obtained by appending the keyword %array to the name of the type in question. For example: type corresponding array type %byte %integer %byte %integer %array %record () %record () %array %string() %string()%array Reference types An item of this type is, or has as its value, a reference to a variable of a specified type. For each of the foregoing types there is a corresponding reference type, the name of which is obtained by appending the keyword %name to the name of the type in question. For example: type corresponding reference type %byte %integer %byte %integer %name %record () %record () %name %string() %string()%name %real %array %real %array %name Variables of this type are known as reference variables or pointer variables. Variables Variables are named store locations used to hold numeric or textual information. Each variable must be defined in a declaration statement which specifies its type and an identifier to name it. The amount of storage allocated to a variable depends on its type. All variables must be declared at the head of the block in which they are to be used, or in an outer block. The initial value of a variable, i.e. the value assigned to it when it is created, is implementation-dependent; in most cases it is undefined (see Appendix B for details). Any attempt to use a variable whose value is undefined will cause an event to be signalled. {IMP77: all variables can be assigned initial values. The syntax is as for %own variable initialisation, described below. Stack variables are re-initialised whenever they are re-created.} Variables can be divided into four categories: Arithmetic String Record Reference The following standard integer function is provided (a 'standard' procedure is one which is predefined; see Section 7): %integer %function SIZE OF(%name A) The number of storage units occupied by the given variable is returned. The unit is implementation-dependent but commonly is a byte. Arithmetic variables Arithmetic variables may be of two types: %integer and %real. The first holds whole numbers and the second holds numbers with fractional parts. For more efficient use of store, %short, %half, %byte and %long integer types may be provided, whilst greater precision will be obtained when %long %real and %long %long %real are available. However, the provision of different lengths is necessarily hardware dependent, though type %integer will always be available; it corresponds to the word length of the machine. Furthermore, it is possible that on some restricted implementations for small machines, type %real will not be supported. Where implementation is on a byte-addressed machine (e.g. IBM 370, ICL 2900 ranges) %short %integer and %half %integer would be 16 bits in length, %integer 16 or 32 bits, %long %integer 64 bits, and %real types would be 32 bits, 64 bits and 128 bits in length. On such machines, %real variables can only hold values to a precision of 7.2 significant decimal digits whereas %long %real variables are precise to 16.8 decimal digits and %long %long %real variables (if available) to 36 decimal digits. Further details of the representation of variables can be found in the relevant hardware manuals. The following example illustrates the declaration of various arithmetic variables. Example: %begin %integer I, J %real P, Q %begin %byte %integer Z : : P = I+J+Q Z = 0 : : %end : : %end %of %program Arithmetic variables can be grouped into arrays. The array bounds separated by the symbol ':' are given in brackets after the array identifier when the array is declared. Example: %integer %array IN(1:10), OUT(1:20) Multi-dimensional arrays can be used. The maximum number of dimensions is implementation dependent. Example: %integer %array BITLIST(-4:4, 1:2, 10:100, 1:2) When an individual array element is accessed, the array identifier is followed by an ordered list of integer expressions (one for each dimension) enclosed in brackets. These integer expressions are called subscripts. Examples: BITLIST(3, 1, 54, J) BITLIST(I+K, I, 10, 1) Each of these integer expressions must evaluate to an integer which lies within the range described by the bounds for the relevant dimension. A run time fault may occur if the array bounds are exceeded. An array can be declared with integer variables instead of constants for the array bounds. Obviously, the variables used in such a declaration must be declared and given a value before the array declaration occurs. Examples: %begin %integer TOP READ(TOP); ! TOP is given a value from the input data. %begin %integer %array TABLE(1:TOP) : : %end : %end String variables A string variable is one which holds textual information. The maximum length of a string is implementation dependent but is not normally less than 255 characters. When the string variable is declared, the maximum number of characters which the string may hold is specified, in parentheses, after the keyword %string. Example: %string(24) S : S = "Results of last test" As the string of characters may vary in length within the given location while the program executes, an indication of the current length is stored along with the current contents. In all implementations on byte-oriented machines to date, the current length is held in an extra byte at the front of the string location. Thus the string S declared in the above example would be allocated 25 bytes of storage. As can be seen from the above example, the character " (double quote) is used to delimit textual information to be stored in a string location. Where one double quote character is part of the text, two consecutive double quote characters should occur in the text, to distinguish it from the terminating delimiter. Example: MESSAGE = "Peter says ""No"" " Multi-dimensional string arrays can be used. Note that the maximum number of dimensions is implementation dependent. Each element of a string array must have the same maximum length, which is specified when the array is declared. Example: %string(63)%array FIELDS (1:5) FIELDS consists of five strings, each of maximum length 63 characters. The occurrence in an IMP program of a semi-colon or newline character normally terminates a statement. However, both are permitted in string constants. Example: S = "A; B; C" SNL = " " A string may be regarded as having a value based on the ISO code values of the characters of the string. Thus the relational operators >, <, =, #, <=, >= may be used to compare strings. In particular, strings composed entirely of alphabetic characters can be regarded as having a dictionary ordering for the purposes of comparison. Example: "AB" < "C" is TRUE "AB" < "ABC" is TRUE "IMP" < "FORTRAN" is FALSE Record variables A record is a variable comprising a collection of entities which may be of different types. It has an identifier which refers to the whole collection and each entity has an identifier; an entity (or "sub-field") can be referenced by using the record identifier together with the required sub-field identifier. The collection of sub-fields which makes up the record is described in a record format statement, which specifies the identifier and type of each sub-field. Arrays with constant bounds may be used as sub-fields. Example: %constant %integer FROM=3, TO=5 %record %format F(%byte %integer A, %string(8) S, %integer %array M, F(FROM:TO, 1:400), %real Y) %record (F) R : Record format statements are placed with other declarations at the head of a block or procedure. They do not cause allocation of storage. Note that the sub-field identifiers need not be distinct from other identifiers in use in the program, as they are always associated with a specific record identifier. A record format may include alternatives. Example: %record %format AS(%byte %integer %array CHAR(0:12) %or %c %string(12) TEXT) The space allocated to a record of this format can be regarded as holding a byte integer array of 13 elements or a string of maximum length 12 characters. Alternatives provide a means of imposing different interpretations on all or part of a record. Where only part of a record is to have an alternative format, brackets must be used within the record format statement to enclose the alternatives. Example: %record %format AT(%real X, (%byte %integer A, B, C %or %real R %c %or %integer E), %string(10) F) Every sub-field in the record format must be distinct. Each alternative will start at the same address within the record and will be padded out to the size of the largest. (In the example above padding will be added, as the three alternatives are not all of the same size). The amount of padding required depends on the amount of store allocated to each sub-field, which is implementation-dependent (as is the relation between elements in different alternatives). A record format may contain several sets of alternatives, and alternatives may be nested to any depth. Note, however, that redundant brackets within a record format statement are not allowed. Records are declared at the head of a block or procedure. Space is allocated according to the associated record format statement whose identifier occurs in parentheses in the record declaration. The amount of space occupied by a record of given format is the total number of bytes occupied by all the sub-fields plus the minimum number of 'padding' bytes required to achieve alignment of sub-fields appropriate to their types. This alignment is implementation-dependent. Example: %record %format F(%integer A, %string(8) S) %record (F) R %record (F) PP, QQ, RR Note that, as in the example above, more than one record may be declared having the same record format. {EMAS IMP80: in a record declaration it is permissible to give the actual record format instead of giving the name of a record format. Example: %record (%integer I, J, %string(7) S) A, B, C It is also permissible to give the name of a record already declared in place of a record format name. The format of the specified record is then taken as the required format for the record being declared. Example: %record (A) D ! A is the record declared in the previous example. Each sub-field of a record can be referenced as a location of the appropriate variable type by subscripting the record identifier with the sub-field identifier, the two being separated by the underline character '_'. Examples: %record %format F(%integer A, %string(8) S) %record (F) R : %if R_S="INC" %then R_A = R_A+1 : %record %format PE(%integer I, %real %array X(0:10)) %record (PE) P %integer J : P_X(J+1) = P_X(J)*2 : : Arrays of records are analogous to the arithmetic and string types of arrays already described. Each element of a record array is a record of format specified in the record array declaration. Example: %record %format F(%integer A, %real %array X(1:5)) %record (F) %array RA(1:100) The fifth element of sub-field X in the 76th record array element may be referenced as follows: RA(76)_X(5) As with other types of array, several record arrays having the same bounds and format may be defined in a single declaration. Example: %record (F) %array RR1, RR2(1:100) A sub-field may be of type %record. In this case its format must have been already declared. In particular, its format may not be the record format being described (c.f. record name sub-fields). Example: %record %format P(%integer %array X(0:4), %integer I) %record %format F1(%integer A, B, %record (P) D) %record %format F2(%record (P) J, K) %record (F1) ENT %record (F2) JAK An arbitrary depth of subscription can thus obtain. Using the above declarations, the following are valid references to record elements: ENT_D_X(1) JAK_J_I A sub-field of type %record is word-aligned in most implementations, irrespective of the format of the sub-field. Whilst sub-fields of records may be used exactly like IMP80 entities of corresponding type, it is also possible to assign a whole record from one location to another. Two assignment operators are permitted: '=' and '<-'. Both operators require that operands on each side of the assignment refer to %record locations, except in the case where zero occurs on the right-hand side of the '=' operator: this results in the space allocated to the record referenced by the left-hand operand being set to binary zeros. When the '=' operator is used, the record formats associated with the left-hand and right-hand operands must be the same. {EMAS IMP80: the formats need not be the same, but they must have the same length.} The '<-' assignment operator transfers as many bytes from the record referenced by the right-hand operand as will fit into the record referenced by the left-hand operand. The '<-' operator takes no account of record formats. Example: %record %format F(%integer X, Y, Z, A) %record %format Q(%byte %integer %array B(0:15)) %record (F) J %record (Q) %array K(0:100) : : J = K(1) K(1) = 0; ! K(1)_B(0), K(1)_B(1), ..., K(1)_B(15) ! all set to binary zeros. {IMP77: A %record %format %spec statement is provided to enable a record format to be referred to before it has been declared. A statement of the form %record %format %spec specifies a record format identifier. Until the format is declared fully in a %record %format statement the identifier may only be used in the declaration of record reference variables (described below). Example: %record %format %spec Y %record %format X(%record (Y) %name P, %real VALUE) %record %format Y(%record (X) %name Q, %integer VALUE) Record formats of this sort are useful in list processing when the items in the list are records of alternating format X, then Y, then X, etc.} Reference variables A reference variable (or "pointer variable") is one which has as its value, not a constant, but a reference to a variable of a specified type. Reference variables are declared in the same way as the variables to which they can refer, but with the suffix %name added. Example: %integer A %integer %name AREF When a reference variable is declared, space is allocated for a reference to a variable of the corresponding type and precision. The operator "==" is used to establish the reference. Once a reference is established, all references to the reference variable will be redirected to the variable which it references. Note that the reference can be established before the referenced variable has been assigned a value. Reference variables are often used in conjunction with store mapping facilities (see Section 6). Example: %integer %name N %integer B N == B; ! Reference established : : N = 10; ! Assigns 10 to B : In exactly the same way, a reference to an array can be set up in an %array %name variable of the appropriate type. Example: %real %array %name P %real %array Q(0:27) %real %name Z P == Q; ! Reference established. : : P(25) = 10.3; ! This puts 10.3 into Q(25). Z == P(27) : : Z = 0; ! This sets the 27th element of Q to zero. : The examples above have been of reference variables of arithmetic types. However, string and record reference variables may also be used. Example: %string(20)%name SREF %string(20) S : SREF == S : A maximum size must be specified for a string reference variable, as for a string variable. A string reference variable can only refer to a string variable whose maximum size is equal to that of its own. However, where a string reference variable is required to refer to several strings of different maximum sizes, the form %string(*)%name may be used. {EMAS IMP80: the string variable maximum size may be omitted. Whether it is or not, the EMAS IMP80 compiler treats the size specification as '(*)'.} In the case of record reference variables, the format of the record to be referenced must be specified in the reference variable declaration. Example: %record %format RECFORMR(.....) %record (RECFORMR) REPORT %record (RECFORMR) %name REP2 A reference variable of type %record %name is assigned to by using the '==' operator (as before), where the right-hand operand is a reference to a record location with the same format as that specified for the reference variable. Example: %record %format F(.....) %record (F) %name F1 %record (F) Q, R %record (F) %array A(1:10) %record (F) %array %name Z, W F1 == Q; ! Makes F1 a synonym for record Q F1 == A(10); ! Makes F1 a synonym for 10th element of A Z == A; ! Makes Z a synonym for A {EMAS IMP80: the record location referenced by the right-hand operand does not have to have the same format as the record reference variable.} {IMP77: when a record reference variable is declared, the format can be specified as (*), meaning that the variable can refer to a record of any format. The reference variable can have no associated sub-fields; it is only of use when it is passed as a parameter to a procedure, to be pointed at subsequently within the procedure by a reference variable with a specific format.} Note that records may contain sub-fields of type %record %name. In this case the format of the record name sub-field may be the record format being described (c.f. sub-fields of type %record). The following example illustrates how the recursive nature of the format and sub-field format definitions facilitates the creation of a list structure: Example: %record %format F(%integer DATA, %record (F) %name LINK) %record (F) %array P(1:1000) The structure may be initialised as follows so that the 'link' field of each element of the record array P 'points' to the subsequent element: %record %format F(%integer DATA, %record (F) %name LINK) %record (F) %array P(1:1000) %record (F) END %integer J P(J)_LINK == P(J+1) %for J=1,1,999 P(1000)_LINK == END : : %if P(J)_LINK == END %then .... Note how the link field of the last record in the chain is set to point to the record END. %own, %constant and %external Additional properties can be given to variables by means of the prefixes %own, %constant (which can be abbreviated to %const) or %external added to the type in their declarations. An %own variable is allocated storage in such a way that it preserves its value between successive entries to the block or procedure in which it is declared. It can be initialised in its declaration statement. An %own variable can be used in any circumstances in which a normal variable of the corresponding type can be used. A %constant variable is declared in a similar manner to an %own variable, but it cannot be changed from its initial value. Constant variables are also known as "named constants", which better describes them, in that they have all the attributes of constants. Note that they do not have addresses (see ADDR, described in Section 6). Wherever a constant is permitted in an IMP80 program, a "constant expression" can be used instead. A constant expression is one which can be evaluated at compile-time, i.e. its operands are constants or named constants. Example: %string (73) DELIVERY can be replaced by %constant %integer MAXNAME=20, MAXADDRESS=52 %string (MAXNAME+1{for the newline}+MAXADDRESS) DELIVERY The constant integer NL is predefined: it contains the code value for the newline character. The constant long real PI is predefined. It is the value of pi to the long real precision of the implementation; where 64 bits are used to hold a long real, this is 3.141592653589793. An %external variable is a special form of %own variable which is used to provide communication between sections of program compiled separately (see "External linkage", 3.3). An %external variable can be used in any circumstances in which a normal variable of the corresponding type can be used. %own, %constant and %external arrays are normally one-dimensional, but need not be. Examples: %constant %byte %integer NUL=0, CR=13, DEL=127, FF=12 %own %long %real RMIN = -3.5@-4, RMAX = 17.23614@10 The initial values to be assigned to %own, %constant and %external variables are specified when the variables are declared. A variable can be followed by = , where is a constant or constant expression of the appropriate type. (A constant expression is one which can be evaluated at compile-time, i.e. made up of constants or variables of type %constant.) The variable is initialised to the value of . If no initial value is specified, the value assigned by default is implementation-dependent. {EMAS IMP80: %own, %constant and %external variables which are not assigned initial values are set to binary zeros.} {IMP77: %own, %constant and %external variables which are not assigned initial values are undefined, and any attempt to use them before they are assigned will cause an event ("unassigned variable") to be signalled.} {IMP77: the '<-' assignment operator can be used in %own, %constant and %external variable initialisation.} Any identifier being declared as %external may be given an "alias". The details of this facility are described in Section 3.3. An %own, %constant or %external array is initialised by appending a list of values to its declaration. Only one array may be declared per statement. Each element of the array must have a corresponding value with which it is to be initialised. In order to simplify this, each value may be followed by a repetition count in parentheses, and an asterisk, (*), may be used to represent the number of remaining elements of the array. If the array is multi-dimensional, the order in which the array elements are assigned the initial values is implementation-dependent. {EMAS IMP80 and IMP77: in a two-dimensional array whose first element was (1,1), the order would be (1,1), (2,1), (3,1), i.e. first subscript changing fastest.} Examples: %external %integer %array VALUES(-3:7) = %c 17, 4, 23, -2, 3(4), 7, 1(2) %constant %integer RED=1, ORANGE=2, YELLOW=4, GREEN=8, BLUE=16, INDIGO=32, VIOLET=64, WHITE=127 %own %byte %integer %array COLOUR(1:22) = %c RED, VIOLET(3), BLUE+GREEN, VIOLET, INDIGO+ORANGE+BLUE, YELLOW(2), WHITE(*) The {...} form of comment is useful for commenting array initialisation. Example: %own %integer %array OPCODE(0:20) = %c {opcode values} 16_5800, 16_4800, 16_5000, 16_4000, { L LH ST STH 16_5A00, 16_5B00, 16_5C00, 16_5D00, { A S M D 16_1A00, 16_1B00, 16_1C00, 16_1D00, { AR SR MR DR -1(*) {all the rest} %own, %constant and %external strings can be likewise initialised, with string constants or constant expressions at the time of their declaration. Example: %own %string(19) FILENAME = "ERCC00.TEST" %constant %string(6)%array F(0:4) = "Peter", "Mac", ""(3) Records may also be declared as %own, %constant or %external, but these may not be initialised at the time of declaration. {EMAS IMP80: the space allocated to such records is filled with binary zeros.} {IMP77: the values of the sub-fields of such records are undefined.} Variables of type %constant %record cannot be assigned at all, unless some implementation-dependent method is provided; see Appendix B. Reference variables may also be declared as %own, %external or %constant. If initialisation is permitted in the implementation, then the initialisation establishes the initial reference in an implementation-dependent manner. This facility is only useful for unusual system programming features. {Example: EMAS IMP80: the following declaration is valid: %constant %integer %name K INST PER SECOND = X'80C000C0' IMP77: the following declaration is valid: %constant %integer %name K INST PER SECOND == X'80C000C0' The value to which the integer name variable is being initialised specifies a particular storage location, and is therefore system dependent.} The initialisation of constant array name variables, if available, is described in Appendix B. Constants Constant values can be assigned to variables. In general, the type of the constant must be the same as the type of the variable, although an automatic type conversion is carried out on a constant of integer type before assignment to a variable of real type. Decimal constants Decimal Constants are written in a straightforward notation: 2.538 1 .25 The exponent, where present, consists of the symbol @ followed by an optional sign and decimal digits: -17.28@-1 1@7 The type of a decimal constant depends on its value. It is of integer type if it has no fractional part, i.e. no decimal point in its specification and the exponent (if present) is non-negative; otherwise it is of real type. The particular real or integer type depends upon the magnitude or precision of the constant. Base constants A base constant may be constructed by using the prefix _ to specify the base (up to a maximum of 36) of the subsequent constant. The letters A, B, ..., Y, Z are used to represent the digits 10, 11, ..., 34, 35. Example: 2_1010 ten in Binary 8_12 ten in Octal 9_11 ten in base nine 16_A ten in Hexadecimal An alternative form is provided for constants to bases 2, 8 and 16. The constant is written with the digits enclosed in single quotes and preceded by a code letter for the base, the codes being B for base 2 (Binary), K for base 8 (Octal) and X for base 16 (Hexadecimal). Example: B'1010' ten in Binary K'12' ten in Octal X'A' ten in Hexadecimal Either upper or lower case letters may be used in this form of constant, but spaces may not occur. When a program is to be used on other machines, care should be taken in the use of constants as the values of the constants may vary, particularly in a transfer from a machine using ones-complement arithmetic to one using twos-complement arithmetic, and vice-versa. Base constants are of type %integer. {IMP77: the base can have any positive integer value. However, if it is greater than 36 then not all the digits will be representable by 0...9 and A...Z. Example: I = 256_1234 where I is a four-byte integer, assigns the values 1, 2, 3 and 4 respectively to the four bytes of I.} {IMP77: a base constant can include a decimal part, in which case it is of type %real. Examples: 3_0.1 {= 1/3} 16_3.102A9 } Character constants The ASCII code value of any character may be obtained as an integer value by enclosing the character in single quotes. When the required character is a single quote, it must be represented by two consecutive single quotes. Examples: 'A', 'a', '+', 'o', '"', '''', ' ', ' ' Note the last three examples which represent the code values for single quote, space and newline, respectively. The predefined named constant NL may be used in place of the rather cumbersome form of a newline character enclosed in quotes. The code values for several characters may be packed together to form a single integer constant by enclosing the characters in single quotes and giving the prefix M. Examples: M'over', M'MAX', M'1+2', M'*@@#' The value of the constant is calculated by evaluating the expression: (..(c1< characters is of type %string(). Named constants These are treated in this manual as variables of type %constant (see Section 2.2.5). Arithmetic operators & expressions Arithmetic operators There are two assignment operators for use with arithmetic expressions: = equals <- jam transfer Where the = operator is used, the expression on the right-hand side is evaluated and the value obtained is assigned to the destination indicated by the left-hand side, provided that the lengths and types are compatible. A fault occurs if an attempt is made to assign too large a value to a variable using this operator. Where the <- is used, only as many bits as will fit the location designated by the left hand side are assigned, starting with the least significant bits. In general the arithmetic assignment instruction assigns the result of evaluating an arithmetic expression to a variable. Only the result of an integer expression may be assigned to an integer variable, but the result of an integer or real expression may be assigned to a real variable. The following operators may be applied to real and integer variables in arithmetic expressions: _______________________________________ | | | + addition | | - subtraction | | * multiplication | | / real division | | // integer division | | \ real exponentiation | | (e.g. Y\3 = Y cubed) | | \\ integer exponentiation | |_______________________________________| The established order of precedence for the arithmetic operators is given in the following table, starting with the highest. Operators on the same horizontal line of the table have equal precedence. _________________________ | | | \ \\ | | * / // | | * - | |_________________________| Parentheses may be used to override the natural order of evaluation of an expression or to remove ambiguity. Where operators are of equal precedence, left-hand precedence pertains as in normal mathematical usage. Examples: A-B+C is equivalent to (A-B)+C A-(B+C) (A)-(B+C) A/B*C (A/B)*C A/(B*C) (A)/(B*C) A**B*C (A**B)*C A**(B*C) (A)**(B*C) The one exception to the left-hand precedence rule is that consecutive exponentiations are evaluated from right to left; thus A**B**C is evaluated as A**(B**C), not as (A**B)**C. Arithmetic expressions An arithmetic expression is a sequence of operators and integer or real operands obeying the elementary rules of algebra. Expressions may be real or integer according to context. Apart from the rules for operator precedence given above, no assumptions may be made about the order of evaluation of expressions. Integer expressions An expression is evaluated as integer if it is being assigned to an integer variable, or passed as an integer value parameter, or occurs in a position where an integer expression is mandatory. All the operands and operators in an integer expression must yield integer values. The operators available for use in integer expressions are: + addition - subtraction * multiplication // integer division. This operator always yields an integer result. The result consists of a quotient whose sign is determined algebraically and a remainder which is ignored. Note that dividend and divisor must both be integer expressions. \\ integer exponentiation. This operator only operates on integer variables and always yields an integer result which is obtained by repeated multiplication. The exponent must be an integer expression with a value in the range 0 < exp < 63. The precision used in evaluating integer expressions depends on the operands. Variables of type %byte %integer, %short %integer and %half %integer are expanded to normal integer precision before the operation is carried out. An operation between an integer variable and a long integer variable will be carried out by long integer arithmetic. However, **** is anomalous in that long integer **** integer is carried out by repeated long integer multiplication but integer **** long integer is carried out by repeated integer multiplication. The following standard integer functions are provided (a 'standard' procedure is one which is predefined; see Section 7): %integer %function IMOD(%integer I) This function returns the modulus (absolute value) of the parameter. {IMP77: the modulus of an integer expression can also be obtained by enclosing the expression between vertical bars; e.g. |I-J| .} %integer %function INT PT(%long %real L) This function returns the integer part of L, any truncation being towards zero. Hence INT PT(-1.5)=-1. An event is signalled if the result cannot be held in an integer variable. %integer %function INT(%long %real L) This function returns the nearest integer to the parameter, truncation being towards zero. It is thus equivalent to: %integer %function INT(%long %real L) %result = INT PT(L+0.5) %end Real expressions All the operands and operators in a real expression must yield real or integer values, and assignment can only be made to a real variable. Integer values will automatically be converted into their real equivalents before being used. The operators available for use in real expressions are: + addition - subtraction * multiplication / division ** real exponentiation. This always yields a real result. Where the exponent is an integer expression, the operation is carried out by repeated multiplication. A negative exponent, e.g. X**(-4), is evaluated as 1/X**4. {EMAS IMP80: where the exponent is a real expression, the result is obtained by using the standard functions LOG and EXP, and events relating to these functions may be signalled.} A real expression is evaluated to single precision until a long real variable is encountered. Thereafter the expression is evaluated to double precision. Double precision work is time and space consuming and should only be used when strictly necessary to preserve accuracy. However, it is often required with floating-point arithmetic where loss of accuracy may occur in addition and subtraction due to cancellation of significant figures. The following standard real functions are provided (a 'standard' procedure is one which is predefined; see Section 7): %long %real %function FRAC PT(%long %real L) The fractional part of the parameter L is returned as the result. Note that the fractional part is always treated as being greater than or equal to zero; e.g. FRAC PT(-4.6) is 0.4 . %long %real %function MOD(%long %real I) This function returns the modulus (absolute value) of the parameter. {IMP77: the modulus of an real expression can also be obtained by enclosing the expression between vertical bars; e.g. |X-Y| .} %long %real %function FLOAT(%integer N) The floating-point equivalent of the integer parameter is calculated and returned as the result. Logical ops and expressions Logical operations are performed on bit patterns stored in integer variables, which may be of any of the permitted lengths. Before the operation is carried out, byte, short, and half integer variables are made up to full integer length in one of two ways, according to the length of the initial variable: a) byte and half integers are made up by filling the left hand bits with zeros; b) short integers are made up by sign extension, i.e. the leftmost bit of the variable - the sign bit - is propagated leftwards until the necessary number of bits have been obtained. Where necessary, integers are made up to long integer precision by sign extension. There are two assignment operators available for logical expressions. = equals treats the result of the logical operation as a signed integer and attempts to perform an arithmetic assignment to the designated variable. Hence it is not always possible to put the result back into a variable of the same precision as that in which an operand of the logical expression was originally held. <- jam transfer copies the bit pattern of the expression indicated by the right hand side into the variable indicated by the left hand side, starting with the least significant bits and stopping when the variable has been filled. The choice of assignment operator depends on the context of the program. The following is a list of the logical operators available, excluding the assignment operators discussed above. _________________________ | | | << left shift | | | | >> right shift | | | | & and | | | | ! or | | | | !! exclusive or | | | | \ (or ~) not | |_________________________| The shift operators allow the programmer to move the bit pattern of an integer of any length to the left or right by a number of places less than the number of bits in the specified integer. Example: J = I>>N This causes integer I to be shifted to the right the number of places specified by N and the result stored in integer J. If I or N are of less than integer precision they will be made up to integer precision, as described above, before the operation takes place. In a left shift, bit positions vacated at the right hand end are filled with zeros and bits shifted off the left hand end are lost. In a right shift, bit positions vacated at the left hand end are filled with zeros and bits shifted off the right hand end are lost. Note that a shift of more than N-1 characters will result in an integer whose value is implementation- dependent, not necessarily zero-filled. The operators &, !, !! are carried out on a bit-by-bit basis between the patterns stored in two integer variables. Where one operand is a long integer, the other will be made up to long integer by sign extension. 'and' (&) produces a pattern containing a 1-bit where the two source patterns both have 1-bits and containing 0-bits elsewhere. 'inclusive or' (!) produces a pattern containing a 0-bit where the two source patterns both have 0-bits and containing 1-bits elsewhere. 'exclusive or' (!!) produces a pattern containing a 1-bit where the bits in the source patterns are different and contains 0-bits elsewhere. These rules are summarised in the following table: ______________________________ | | | | Operands | & ! !! | |____________|_________________| | | | | 0 0 | 0 0 0 | | | | | 0 1 | 0 1 1 | | | | | 1 0 | 0 1 1 | | | | | 1 1 | 1 1 0 | |____________|_________________| The \ operator operates on a single operand to invert the value of each bit; that is, 0-bits become 1-bits and vice versa. Example: If X contains the bit pattern 01....0100110011 then \X is 10....1011001100 thus \X + X is 11....1111111111 Arithmetic and logical operators may occur in the same arithmetic expression. The established order of precedence, starting with the highest, is: ________________________ | | | \ | | | | ** >> << | | | | * / // & | | | | + - ! !! | |________________________| Operators given on the same line in the above table have equal precedence. Example: %integer I, J ! Variables of type %integer are assumed to have 32 bits ! in this example. %byte %integer %array B(0:3) : I = .... : B(J//8) = I>>(24-J) & X'FF' %for J = 0,8,24 : In this example, a 32-bit integer I is copied, in groups of 8 bits, into the byte integer array B. Note that X'FF' represents a bit pattern of eight 1s in the least significant end of the specified location and zeros elsewhere. String ops and expressions There are three assignment operators available for use with strings: "=", "<-" and "->". Where "=" is used, the string expression on the right-hand side is evaluated and assigned to the string location specified on the left-hand side. The string expression must evaluate to a string constant which is no larger than the maximum which the left-hand string location can hold. A run-time error will occur if the left-hand string location overflows. "<-" is known as the "jam transfer" operator. It will assign to the location specified on the left-hand side only as many characters of the right-hand string as will fit. Any remaining characters from the right-hand end of the string being assigned will simply be omitted and no error will occur. "->" is known as the "string resolution" operator. It is used exclusively for the manipulation of strings. Its effect is described below. Another operation exclusive to strings is "concatenation". This allows them to be joined in a prescribed order. The strings to be concatenated are listed in the required order and separated by the symbol '.' . Example: %begin %string(65) S %string(15) NAME1, NAME2, NAME3 %string(20) ADDRESS NAME1 = "Peter " NAME2 = "John " NAME3 = "Smith " ADDRESS = "12 Bothwell Drive" S = NAME1.NAME2.NAME3.ADDRESS PRINTSTRING(S) NEWLINE %end %of %program String S is assigned the concatenated characters of the four strings NAME1, NAME2, NAME3 and ADDRESS. Thus the printed output would read Peter John Smith 12 Bothwell Drive Note that the string NAME3 has a newline character in the text after the name Smith. The only type of string expression in IMP80 is that produced by concatenation, for which bracketed expressions are neither required nor permitted. String resolution allows a string to be searched for a specified substring of characters. If this substring does not occur in the string being resolved, an error condition occurs. If, however, the substring is found on searching the string from left to right, then all characters to the left and right of the substring will be stored respectively in two strings specified to the left and right of the substring in the resolution instruction. The substring, which may be a string expression (a constant, a variable or a concatenation), is enclosed in parentheses with a string identifier on either side, each of these three elements being separated by '.' . Example: S = "ERCC00.FLAG" S -> A.(".").B ! A now contains "ERCC00" and B contains "FLAG". Thus string S has been split into two smaller strings neither of which include '.' . The same exercise would be accomplished by the following: S = "ERCC00.FLAG" X = "." S -> A.(X).B The substring may be a concatenation: Example: FIRST NAME = "John" SURNAME = "Smith" NAME AND ADDRESS = "Peter John Smith, 12 Bothwell Drive" NAME AND ADDRESS -> A.(FIRST NAME." ".SURNAME).B {EMAS IMP80: multiple resolution is permitted; it is treated as a series of simple resolutions. Example: S = "WINSTON SPENCER CHURCHILL" S -> A.(" ").B.(" ").C The above resolution will be treated as S -> A.(" ").PRIV %and PRIV -> B.(" ").C} The strings used to store the characters which occur before and after the specified substring may be omitted, in which case the characters are discarded. Examples: S = "ERCC00.FLAG" S -> (".").B ! B now contains "FLAG". S = "ERCC00.FLAG" S -> A.(".") ! A now contains "ERCC00". S = "ERCC00.FLAG" S -> (".") ! Resolution would fail if "." did not occur in S. ! There are no other products of this resolution. Since a resolution must either succeed or fail, it may be used as a simple condition. Examples: S = A.B %while S -> A.(" ").B ! This statement removes all spaces from string S. %if S -> A.("/").B %then T = A." or ".B Note that, although the string resolution is used here as a condition, it is nonetheless carried out if it can be (i.e. if the condition is true). There are four procedures provided for the manipulation of strings. %byte %integer %map CHAR NO(%string(*)%name S, %integer N) This map returns a reference to the Nth character of string S. An event is signalled if N is negative or greater than the current length of S. %byte %integer %map LENGTH(%string(*)%name S) The result is a reference to a variable containing the current length of the string S. %string(*)%function SUBSTRING(%string(*)%name S, %integer I, J) The result is the substring of S comprising the Ith to Jth (inclusive) characters of S. An event is signalled unless 1 <= I <= LENGTH(S) and I-1 <= J <= LENGTH(S). If J=I-1 then a null string is returned. %string(1)%function TO STRING(%integer I) The result is a string of length 1 comprising the character defined by the least significant byte of integer I. Example: %string(80) S %integer %name L : : L == LENGTH(S) L = L-1 %while L>0 %and CHARNO(S,L) = ' ' ! This example shows how to delete trailing spaces ! from a string. Record operators The only record operators taking complete records, rather than record sub-fields, as their operands are the assignment operators '=', '<-' and '=='. These are described above, in 2.2.3/9 . Blocks and Procedures Block structure and storage IMP is a block-structured language. A block is a sequence of statements of which the first is %begin and the last is %end. The program itself is regarded as a block, and the first %begin encountered is interpreted as the start of the program. The statement %end %of %program, rather than %end, is used to indicate that the end of the program has been reached. Blocks may be nested to a depth determined by the particular implementation. Within each block, variables and constants to be used must be declared at the head of the block (and before any %on %event statement), unless they have already been declared at the head of an outer block. Labels and switch labels are always local to a block; thus it is impossible to jump from one block to another. Keywords which occur in pairs such as %cycle ... %repeat must have both elements within the same block. Example: %begin %integer I, J %real %array A(1:10) : %real X, Y : : %begin %real Z, P I = 4 J = 10 : : %end : : %end %of %program I and J have been declared at the head of the outermost block and may thus be referred to from any inner blocks of the program. If, however, a variable is declared which has the same identifier as a variable already declared in an outer block of the program, then use of that identifier will refer to the variable most recently declared. Example: %begin %integer I, J, K %real X, Y I = I+1 : : %begin %real I I = 4.106 : : %begin X = I : : %end %end %end %of %program I is first declared as an integer type and will be allocated storage accordingly. The first use of I refers to this integer location. Within the next block, however, I is redeclared as a real variable. Now space is allocated for a real variable and within this block (and any deeper blocks of the program - unless I is again redeclared) use of I refers to the real location and the integer location of the outer block remains untouched. When a variable is accessible it is said to be in scope. A variable is global to a block within the block in which it is declared, if it is accessible there. In the example above, X, Y, J and K are global to both inner blocks. The redeclaration of variables is permissible because of the way in which storage space is allocated to a program. Each program can be considered to have space allocated to it on what is called the stack. The stack is an area of store and the stack space given to each program has a prescribed layout, as shown in the following diagram. stack pointer-----------\ | | _____________________________________________________________v________ | Program | Constants, |%own variables, | Cells in | Free \ | Code | %constant variables,|%own arrays | use | cells | | %constant arrays | | | ... |__________|______________________|________________|__________|________ \________________________________/ \___________________________________/ | | read only read and write The "read only" area contains the object code of the program as produced by the compiler, and also any of the constant variables declared by the program. This area cannot be altered during execution of the program. The "read and write" area has own variables stored first and thereafter space is allocated according to the requirements of the program. Each cell will be as big as is required by the entity stored in it: thus an integer variable occupies only a small cell, whilst a complex record variable may require a very large cell. The stack pointer holds the address of the next free location in store. Information stored on the stack is kept as compact as possible though with due regard for alignment considerations. This is of particular significance on paging machines, as it allows the program to run with a minimum number of page faults. The following example illustrates the stack mechanism. Example: %begin %real A,B,C %integer I,MAX %real %array X(1:3), Y(1:4) As a result of the above declaration, the stack might look like this: ST1 ST2---\ | | v________________________________________________________________v_ | | | | | | | | | | | | | | | | A| B| C| I| MAX|###|X(1)| X(2)| X(3)|###|Y(1)| Y(2)| Y(3)| Y(4)| .. |__|__|__|__|____|___|____|_____|_____|___|____|_____|_____|_____|___ ST1 is the position of the stack pointer before the %begin, and ST2 is its position after the declarations. The shaded areas indicate portions of store which contain information essential to the program (such as array dimensions) but which cannot be accessed by the program. The stack pointer may be advanced by any further declarations or by activity initiated by the instructions of the program. On entry to a new block or procedure, the stack pointer is advanced as necessary to cope with new declarations, etc. On exit from the block (or procedure) it returns to its last position prior to entry to that block (or procedure). Storage space for fixed variables such as %real or %integer types is determined at compile time, but arrays with dynamic bounds cannot be allocated space until the values of the bounds are determined at run time. Since the block structure of a program is so closely related to the allocation of store, skilful use of blocks can lead to economical use of store. Consider the following examples. Example: %begin %integer N %cycle READ(N) %exit %if N=0 %begin %integer I %integer %array A(1:N) READ(A(I)) %for I=1,1,N : : %end %repeat %end %of %program The required size of the array is read in the outer block and the array itself declared in the inner block. Thus the space used by any one set of data will be recovered when the inner block is left, so allowing one to repeat the process without incurring successively increasing demands for storage space. Example: %begin : : %begin %real %array XYZ(1:5000) : : %end : : %begin %integer %array IJK(1:20, 1:250) : : %end : : %end Since the declarations at the head of a block are cancelled on executing the %end of the block, it is often possible to economise on storage space if a program consists of several distinct tasks, each requiring large amounts of space. The above example illustrates the point. Procedures can be used in a similar way to economise on store. Events During the execution of a program, events may occur which normally cause the program to terminate with an error message. However, there is a mechanism which allows events to be intercepted and used to control the subsequent execution of the program. This mechanism is activated by the use of the %on %event statement. The %on %event statement (which may occur only once in any block) is used to introduce a block of statements which is only executed if one of the specified events occurs. The form of the %on %event block is: %on %event %start %finish is a list of integers in the range 1 to 14 inclusive, where each number refers to a specified class of error as follows: 1 Overflow 2 Excess resource 3 Data error 4 Invalid data 5 Invalid arguments 6 Out of range 7 Resolution failure 8 Undefined value 9 I/O error 10 Library procedure error (e.g. SQRT negative) 11-14 Available for user definition {EMAS IMP80: event 11 is used by the ERCC Graphics Package.} {IMP77: there are two extensions to the range of event numbers: Event 0 is defined - Termination Event 15 is available for user definition} {IMP77: the form %on %event * %start is permitted. It is a shorthand way of specifying every event number; i.e. 0,1,...,14,15.} Up to 255 sub-events may be defined for each event, but these cannot be specifically intercepted and are necessarily implementation-dependent. For example, not all machines distinguish integer overflow from real overflow. Sub-events defined for some implementations are listed in Appendix B. The %on %event block must follow the declarations at the head of a block and may be regarded as the last declaration of the block. The code within the %start ... %finish is not executed by entry through the head of the block, but is jumped to on the occurrence, during the execution of the block, of an event referenced by the event list. Following such a jump, the flow of control is determined by the contents of the %on %event block; the program does not resume at the point of the failure. An event may be forced to occur by the instruction %signal %event , where specifies the event required and is an optional integer expression (evaluating to an integer within the range 0-255) which may be used to specify sub-event information. The use of the %signal %event statement is the only way of causing a user-defined event (i.e. one which is not pre-defined by the implementation) to occur, although it can also be used with the predefined events (1-10). If an event is forced by a %signal %event statement in an %on %event block which includes the occurring event in its event list, a branch is not made to the head of that block, since such a branch would probably cause looping. Instead, the event is traced up the stack through each superior block until either an %on %event statement including the occurring event in its list is found, or the user environment is left. If a suitable %on %event statement is found, control is transferred to its %start ... %finish block. In parallel with these language statements, two standard integer functions are provided which enable the programmer to determine further information when an event occurs. They may only be meaningfully called in a block which has an %on %event statement within it. %integer %function EVENT INF This function returns (event no<<8) ! sub-event no for the last event which has occurred. An error occurs at compile time if the function is called in a block with no %on %event statement, and an undefined value will result at run time if no event has in fact occurred when the function is called. %integer %function EVENT LINE This function returns the program line number at which the last event occurred during execution of the block in question (provided the program was compiled with line number updating; otherwise 0 will be returned). If no event has occurred, an undefined value will result. If an event is not intercepted in the block in which it occurs, then it is traced up the stack through each superior block until either a suitable %on %event statement is encountered or the user environment is left, the diagnostic package being entered in the latter case. When a suitable %on %event statement is encountered in an outer block, program control is transferred to its %start ... %finish block. As a result of these facilities it follows that, for example "input ended" may be detected and dealt with from within an external routine or a routine within a main program. Examples: %on %event 10 %start %if EVENT INF&255#8 %start %signal %event 10, EVENT INF&255 ! All subevents except 8 are passed to outer block %finish : : ! Code to deal with subevent 8 of event 10 : %finish System defined events %integer SUBCLASS, EVENTNO %constant %string(21)%array MESSAGE(1:2) = %c "Capacity exceeded", "Array bounds exceeded" %on %event 6 %start SUBCLASS = EVENT INF&X'FF' EVENTNO = EVENT INF>>8 & 15 %if 1ERROR EXIT %finish : : ERROR EXIT: .... User defined events %integer SUBEVENT %on %event 12 %start PRINTSTRING("Event 12 has been intercepted") NEWLINE ->EVENT 12 %finish : : SUBEVENT = 2 : : %signal %event 12, SUBEVENT : : EVENT 12: ..... {IMP77: a standard record map is provided to enable the programmer to determine further information when an event occurs. %record %format EVENT FM(%integer EVENT, SUB, EXTRA) %record(EVENT FM)%map EVENT EVENT returns a reference to a system-provided record which contains the parameters of the last event to have been signalled. If no event has been signalled all the fields of the record are set to zero. Consult Appendix B and documents relating to specific implementations of IMP77 for details of the information returned.} Note that the event mechanism is designed for intercepting the occasional event only - overheads are high if the %on %event statement is used frequently. Procedures A procedure takes the form of a block in which the first %begin is replaced by the procedure heading. However, a procedure can only be entered by execution of a procedure call statement, whereas a block is entered when the %begin statement at the start of the block is executed. There are three forms of procedure - %routine, %function (or %fn) and %map. Example: %begin %integer %array A(1:40) : : %routine CLEARA %integer I A(I) = 0 %for I=1,1,40 %end {Of %routine CLEARA.} : : CLEARA : : %end %of %program In this example, a procedure - routine CLEARA - is described and then used by means of the procedure call statement 'CLEARA'. CLEARA is effectively a named block: if the procedure heading were replaced by %begin and the procedure description moved down to take the place of the procedure call statement, the effect would be exactly the same. However, by making the block a routine - and thus giving it a name - it is possible to call it at different places in the program without repeating the description each time. In addition, procedures can have parameters, passed to them via a parameter list enclosed in brackets in the procedure call statement. Example: %begin %integer F, T %string(31) %array A(0:99) %routine STRINGSORT(%string(*)%array %name X, %integer FROM, TO) %integer L, U %string(255) D : : %end {Of %routine STRINGSORT.} : : STRINGSORT(A, F, T) : : %end %of %program In the above example, the procedure heading specifies the form of procedure (%routine) being described, gives the procedure the identifier STRINGSORT and describes the number, order and types of variables passed as parameters, using dummy names. The procedure is entered when the call statement STRINGSORT(A, F, T) is executed. The first line of a procedure has one of the following forms: %routine () %function () %map () where is one of the arithmetic types, or %string() or %record(), and () is optional. {EMAS IMP80: record functions and record maps are not available.} A procedure must be declared before it is called. If the procedure itself is placed at the head of a block (as above) no further declaration is needed. Otherwise a specification (%spec) statement must be placed amongst the other declarations at the head of a block, with the procedure included later at the same textual level. The %spec statement is exactly like the procedure heading with %spec inserted after the keyword %routine, %function or %map. Example: %routine %spec STRINGSORT(%string(*)%array %name X, %integer FROM, TO) It is most important to give the specification accurately: in particular, the number and types of the parameters must be correct. However, the names of the parameters in a specification statement are not significant. Variables used in procedures may be declared locally (as is the case for L, U and D in the above example), but any information stored in them becomes inaccessible on exit from the routine. If the information calculated by the procedure is to be preserved for use on subsequent entries, it must be stored in global variables or %own variables declared locally. Global variables should be used with care in procedures. Note that such variables must be declared globally to the procedure itself: it is not sufficient that they be declared globally to the procedure call. Example: %begin %integer X, Y %routine CONVERT X = .... Y = .... %end CONVERT : : %begin %integer X : : CONVERT : %end : : %end %of %program The routine CONVERT, which has no parameters, operates on variables X and Y. The first call of the routine uses X and Y as declared at the head of the program. The second call occurs from within an inner block in which X has been redefined. However, the procedure again uses the X declared at the head of the program, ignoring the redefined X of the inner block. Information calculated by a procedure and stored in global variables is, of course, accessible on exit from the procedure. There are three categories of procedure which may be called by a program: 1) standard procedures, which are automatically available to all programs (see Section 7); for example, READ, INT PT; 2) procedures described within the program; 3) external procedures, which are compiled separately from the program (see Section 3.3). Procedures can be nested: that is, a procedure can be defined inside another procedure. The scope rules apply as before. Procedures can be used recursively: that is, a procedure can be called from within itself. The example in 3.2.1 of a sorting program demonstrated the recursive use of routine STRINGSORT. Obviously, some criterion within the body of the procedure must eventually prevent the procedure calling itself endlessly. In the example, the recursive calls of STRINGSORT are embedded in conditional instructions, thus providing the necessary opportunity to stop the recursion process. Parameters When a procedure with parameters is described, the heading statement contains dummy names for the variables passed as parameters. These names are used in the description of the procedure and are referred to as "formal" names. When the procedure is executed, however, "actual" parameters take the place of the formal parameters, and are specified in the parameter list of the particular call. The parameters given in the procedure call must correspond in number, order and type with those specified by the procedure heading. Local storage is allocated within the procedure for each formal parameter. Parameters fall roughly into two categories - those passed "by value" and those passed "by name". When a procedure call is executed, parameters passed by value have their values assigned to the corresponding local variables. An arithmetic expression may thus be passed as a parameter: it will be evaluated before entry to the procedure and its value assigned to the appropriate formal parameter. On exit, it will become inaccessible and any information stored in it will be lost. Example: %begin %real Z, Y %real %function AUX(%real X) : : %end : : Y = AUX(Z) : : Y = AUX(4.5*Z) : : %end %of %program Function AUX has formal parameter X of type %real. Execution of the first call of the function will assign to X the value to be found in Z. On executing the second call, the expression 4.5*Z will be evaluated and the result assigned to formal parameter X. Parameters passed "by name", however, are treated differently. The local storage allocated for a parameter passed by name is a reference variable of the appropriate type. In this case, when a procedure call is executed, the effect is that the local variable is "pointed at" the actual parameter, which must therefore be the name of a variable, and not an expression. Thus, every reference in the procedure to the formal parameter is treated as if it were a reference to the actual parameter. Parameters passed by name can be used to preserve information calculated by the procedure, for use on exit. Example: Given a procedure with heading %routine ALPHA(%integer BETA, %real %name GAMMA) the call ALPHA(J*K+4, R(M)) has the effect of causing the following assignments to be carried out on entry to the procedure: BETA = J*K + 4 GAMMA == R(M) (Note however that these two assignments as given may violate the normal rules for scope of names, because they use variables in scope at the point of the call to assign to variables local to the routine body.) Example: %begin %constant %string (1) SNL = " " %string(31)%array NAMES(1:99) %routine %spec STRINGSORT(%string(*)%array %name X, %integer F, T) %integer I, N READ(N) %until 1 <= N <= 99; ! Reject if not in range. READSTRING(NAMES(I)) %for I=1,1,N STRINGSORT(NAMES,1,N) PRINTSTRING(NAMES(I).SNL) %for I=1,1,N %routine STRINGSORT(%string(*)%array %name X, %integer FROM, TO) %integer L, U %string(255) D %return %if FROM >= TO L = FROM; U = TO D = X(U) %cycle L = L+1 %while L < U %and X(L) <= D %exit %if L = U X(U) = X(L) U = U-1 %while U > L %and X(U) >= D %exit %if U = L X(L) = X(U) %repeat ! Now L = U. X(U) = D L = L-1; U = U+1 STRINGSORT(X,FROM,L) %if FROM < L STRINGSORT(X,U,TO) %if U < TO %end %end %of %program This program sorts a set of strings held in array NAMES. Note the %routine %spec statement at the head of the block, with the routine description occurring later at the same textual level. There are three parameters passed to the %routine STRINGSORT; the two integers are passed by value, and the string array is passed by name. Arrays can only be passed by name, to prevent unnecessary allocation of storage space and time-consuming copying. When the routine is called the first time the parameters are treated as follows. The string array NAMES is passed by name and thus all references to the formal parameter X within the body of the routine become references to NAMES; the actual value 1 will be assigned to formal parameter F; and the value stored in N will be assigned to formal parameter T. On exit from the routine, NAMES will have its elements sorted, but the value of N will be unchanged. Strings may be passed as parameters to procedures. Where a string name parameter is used, the length specified in the procedure heading can be replaced by * so that strings of any length (up to the allowed maximum) can be passed to that procedure. In this situation run-time overflow checking is applied to the actual string passed to the procedure. In IMP, parameters called by name are assigned at the time of call. Thus if a routine with parameter list (%realname X, %integername I, ...) were called with parameters (A(J), J, ...) where A is the name of a previously declared real array, then on execution of the procedure every reference to X will refer to the element of A determined by the value of J on entry, no matter how J varies during execution of the procedure. Procedures, too, may be passed as parameters: Example: %begin %routine ONE(%routine PARAM(%integer X) ) : : %end %routine TWO(%integer P) : : %end %routine THREE(%real X) : : %end ONE(TWO) %end Routine ONE has a single parameter, a routine with a single parameter of type %integer. Note that routine THREE cannot be passed as parameter to routine ONE because THREE has a parameter of type %real. That is, the parameter list of the actual procedure passed must correspond with the parameter list of the corresponding formal procedure parameter. To summarise: a formal parameter can be any of the following types: 1) any arithmetic type (e.g. %long %real, %short %integer) 2) %string(n) 3) %record(format) 4) any of the above followed by %array %name 5) any type of procedure (i.e %routine, %function of any type, %map of any type) Items (1) - (3) correspond to call by value; items (4) and (5) to call by name. The actual parameter in a call by value must be an expression of the appropriate type; in a call by name it must be a "reference" to an entity of the appropriate type. Routines A routine call may be used exactly like an instruction. When the call is executed, control is transferred to the routine, which executes until either the %end statement is reached or a %return statement is encountered. Flow of control is resumed at the statement after the routine call. Example: : : %integer X,Y %routine CONVERT %if X < Y %start X = X+Y %finish %else %start X = X-Y %finish %end : : CONVERT : : CONVERT %unless X = 0 : : Note that CONVERT uses global variables X and Y and that the result is stored in X on exit from the routine. Note also the use of CONVERT in a conditional statement. Functions A function calculates a value of the specified type (integer, real, string or record), and may be used in an expression exactly like an operand of that type. The function terminates when an instruction of the form %result = is executed, and the value of the expression, which must be of the same type as that of the function, is returned to the statement making the call. {EMAS IMP80: record functions are not available.} Example: %integer %function SUMSQ(%integer A, B) %result = A**2 + B**2 %end %integer X, Y, Z : : Z = SUMSQ(X,Y) - 3 : The use of global variables in functions may have unusual side effects. Such side effects can be difficult to detect and should be avoided. Example: %begin %integer I, J, K %integer %function SIDE I = I+1 %result = J %end : K = I+SIDE : %end In the statement K = I+SIDE, there is no defined order of evaluation of operands in the expression on the right hand side of the assignment, but the value of I used in the expression depends upon the order of evaluation. Thus the value of the expression is indeterminate. The actual value computed depends on the implementation of IMP80 used. Functions may be of %string type; in this case the maximum length of the string which may be returned by the function is included in the specification and heading of the function. Example: %string(20)%function FIELD(%integer I) Functions may be of %record type; in this case the format of the record returned must be included in the specification and heading of the function. Example: %record %format RFA(%integer ONE, TWO, THREE) %record (RFA) %function TRIPLES(%integer ITEM) : {EMAS IMP80: record functions are not available.} Maps A map (or "mapping function") calculates a reference to a variable of the specified type (integer, real, string or record) and may be used exactly like a variable of that type. The map terminates when an instruction of the form %result == is executed and the address of the given variable is returned to the calling instruction. The "reference to a variable" on the right hand side of the %result statement can be a normal variable, a reference variable or a mapping function call. {EMAS IMP80: programmer-written record maps are not available.} {EMAS IMP80: a %result statement in a map can be of the form %result = integer expression where the integer expression gives the address of some location. It usually includes a call of the function ADDR, described in Section 6.} {IMP77: the keyword %map may be replaced by %name %function or %name %fn.} Example: %integer X, Y, K %integer %map MIN %if X < Y %then %result == X %else %result == Y %end MIN = 0 ! This statement is exactly equivalent to ! %if X < Y %then X = 0 %else Y = 0 : K = MIN : Note the use of a map on the left-hand side or right-hand side of an assignment statement. Where a map is of %string type, the specification and heading must include a length - the precise maximum length of any string to which the map may refer. Example: %string(3)%map XA(%integer I) If a string map might refer to strings of varying length, then the procedure heading and declaration may have the symbol '*' in place of a specific length. The map may then reference strings of any length up to the allowed maximum. Example: %string(*)%map XA(%integer I) : Where a map is of %record type, the specification and heading must include a record format. The right hand side of a %result statement within a record map must refer to a record of the same type as the map, or to the standard record map RECORD (described in Section 6). Example: %record %format RF(%integer X, %string(10) TITLE) %record (RF) %map RM(%integer I, J) %record (RF) %name CURRENT : CURRENT == ... : : %result == CURRENT %if ... : %end {Of %record %map RM %string(15) HEAD %integer P, Q : HEAD = RM(P,Q+1)_TITLE : RM(17,3)_X = 24.6 : {EMAS IMP80: programmer-written record maps are not available. However, a standard record map RECORD is provided; it is described in Section 6.} {IMP77: the record format given in the specification or heading can be replaced by (*), meaning that the reference returned by the map may be to a record of any format; the actual record format used depends on the context. Example: %record(*)%map SURVEY(%integer I, %real X) The standard map RECORD (described in Section 6) is of this sort.} A number of standard maps are provided. These are described in Section 6. External linkage A complete program may be divided into separately compiled modules which are linked before (or possibly while) the program is executed. This section describes the language facilities provided for setting up or accessing a separately compiled module. A procedure compiled separately from a program which uses it is called an external procedure. If a program uses an external procedure it must contain an external specification statement. This is of the same form as the %spec statement described in Section 3.2, but with the prefix %external. Example: %external %string(*) %function %spec I TO S(%integer A) An %external...%spec statement has the same effect as a normal %spec, except that there is no description of the procedure later in the program. %external ... %spec statements may be given wherever other %spec statements would be valid. The keywords %system and %dynamic may be used in place of %external; refer to the relevant system documentation for details of the effects of these keywords. External variables are also available. If a program uses an external variable it must include an external specification. This is of the same form as the variable declaration statement but with the keywords %external and %spec added. Examples: %external %integer %spec WAIT, CHOICE %external %real %array %spec MEAN(-6:6) External files A file of external procedures and variables may be compiled. Such a file differs from the structure of a program file (described in Section 1.2) in several respects: * There is no initial %begin. * %end %of %program is replaced by %end %of %file. * Variables declared outside any procedures must be %own, %constant or %external (described below). * The first statement of any procedure description can be preceded by the keyword %external; such a procedure can then be made accessible to other programs, as explained above. If a procedure in the file is not %external then it is accessible only within the file, in accordance with the normal scope rules. * %begin/%end blocks are not allowed, except within procedures. {IMP77: a single %begin/%end block at the outer level is allowed, so long as it immediately precedes the %end %of %file statement. The block's %end statement and the %end %of %file statement can then be replaced by the single statement %end %of %program.} * Where an %external ... %spec statement in the file specifies an external procedure or variable described later in the same file, the keyword %external may be omitted. An %external variable has all the properties of an %own variable, but is declared with the keyword %own replaced by %external. Examples: %external %integer CHOICE = 6, WAIT = -5 %external %real %array MEAN(-6:6) = 2.7(5),0.3,1.5(*) External variables can be declared in an external file or in a normal program file, wherever other declarations are valid. They are normally declared in the outer block of an external file. Note that external variables may be initialised, like %own variables, when they are declared, but not when they are specified in an %external ... %spec statement. Example of an external file %external %integer IN=0, OUT=0 %routine GET(%integername SYM) READSYMBOL(SYM) IN = IN+1 %end %routine PUT(%integer SYM) PRINTSYMBOL(SYM) OUT = OUT+1 %end %external %routine PROCESS %integer CH : GET(CH) %until CH='*' : PUT(CH) : %end {Of PROCESS} %end %of %file A program making use of the external file: %begin %external %routine %spec PROCESS %external %integer %spec IN, OUT %integer DATA, DMAX : %for DATA = 1,1,DMAX %cycle IN = 0; OUT = 0 PROCESS PRINTSTRING("Calculation no.") WRITE(IN,1) PRINTSTRING(" characters in;") WRITE(OUT,1) PRINTSTRING(" characters out.") NEWLINE %repeat : : %end %of %program %alias Any identifier given in an %external declaration or specification may be followed by %alias string constant, where the string constant specifies the string to be used for external linkage. This has no effect on the use of the identifier within the program. Examples: %external %long %real %fn %spec EIGENVALUE %alias %c "D#REFEIGEN$" (%long %real %array %name MAT) %external %integer RESULT %alias "ICL9CERETURN" = 4 Executable Statements In IMP80 a condition can form part of any executable statement. Conditions are therefore described before the various types of executable statement. Conditions The commonest form of simple condition in IMP80 is made up of two expressions separated by a comparator. The expressions are evaluated and compared. The condition is true if the relation specified by the comparator holds. The expressions must yield values of the same type. Complete records cannot be compared. Examples: J = 0 A = "END" X+23.7 <= F(J+2,Y) - 2*Z/P REF == K The comparators are: = is equal to # (or \= or <>) is not equal to < is less than <= is less than or equal to > is greater than >= is greater than or equal to == refers to the same variable as ## (or \==) does not refer to the same variable as In the case of the last two comparators, == and ##, the items being compared are references to variables, which must be of identical type. The condition is true if the addresses of the variables referred to are equal (==) or not equal (##). (Note that the address of a reference variable is the address of the variable to which it refers: see Section 6.1.2) {EMAS IMP80: the == and ## comparators cannot be used to compare references to arrays.} The other forms of simple condition are as follows: a) This is known as a double-sided condition. Example: A+B <= C+D < E+F This condition is true if A+B <= C+D and C+D < E+F . The third expression is only evaluated if the condition between the first two expressions is true. The comparators == and ## (or \==) may not be used in double-sided conditions. b) String resolution Example: A -> B.(C).D The resolution is attempted. If it succeeds the condition is true AND the resolution is performed. If it fails the condition is false, but no event is signalled. c) Compound condition (see below) enclosed in brackets Example: (A>0 %or B<=0) d) Any of the above forms preceded by the keyword %not The effect of preceding a simple condition with %not is to reverse the truth value of the condition. Examples: %not A <= 0 (equivalent to A > 0) %not 23 <= I+J <= 99 Compound conditions can be produced by combining simple conditions using the keywords %and and %or: %and %and .... %or %or ...... Examples: A+B <= C+D %and C+D < E+F I = 20 %or A -> B.("..").C %or X > Y >= Z+3.47 It is not valid to combine %and and %or, as in X < Y %and B = C %or D = 24 However a compound condition enclosed in brackets is treated as a form of simple condition (see above). Thus (X < Y %and B = C) %or D = 24 is valid. Note that the form %not (X < Y %and B = C) is also permitted. By combining %and and %or and brackets, conditions of arbitrary complexity can be produced: Example: (A <= B %or C == D) %and S -> ("Jim").T %and %c (X <= Y <= Z %or (P_K = 23 %and Q < 0)) The testing of conditions proceeds from left to right, simple condition by simple condition, terminating any clause as soon as an inevitable outcome for the clause has been established. Thus, in the example above, if A <= B were true then C == D would not be evaluated; if A <= B were false and C == D were false then the remainder of the condition would not be evaluated. Example: A = 0 %or B/A = C If the variable A has the value 0 the whole condition will be true without B/A = C being tested. B/A = C %or A = 0 In this case an event will be signalled ("overflow") if variable A has the value 0. Instructions An is an imperative statement which may be made conditional. The following IMP80 statement types are instructions: statement described in assignment Section 2 routine call Section 3.2 %monitor Section 4.2.4 %signal %event Section 3.1 %return Section 3.2 %result= Section 3.2 jump Section 4.2.1 %stop Section 4.2.3 %exit Section 4.4.3 %continue Section 4.4.3 In addition, a can be formed by use of the keyword %and: %and %and ...... Example: X = 23 %and %continue The last of the series of instructions in a compound instruction can be any of those listed above; the other component instructions can only be assignments, routine calls or %monitor statements. Conditional instructions Instructions without conditions are called unconditional. An unconditional instruction can be made conditional as follows: %if %then or %unless %then In the first form, the instruction is executed if the condition is true; in the second, the instruction is not executed if the condition is true. If the instruction is not executed, nothing is done. Examples: %if 0 < I <= 9 %and K > 0 %then B(I) = K %unless J = I %then A(I,J) = 0 %if and %unless statements can be elaborated to allow specification of an alternative instruction, to be executed if the first one is not: %if %then %else %unless %then %else Example: %if X < 47.2 %then Y = Z+3 %else Y = 0.0 Note that the first instruction in these elaborated %if and %unless statements must be unconditional but the second instruction can be conditional. Example: %if STATE < 0 %then ERROR = IN %else %c %if STATE = 0 %then ERROR = CALCULATION %else ERROR = OUT The simple forms of %if and %unless statements (i.e. those without the %else) can be made simpler still: %if %unless Examples: B(I) = K %if 0 <= I <= 9 %and K>0 A(I,J) = 0 %unless J = I Labels and jumps Any statement, excluding declarations, may be given one or more labels. A label is an identifier but it is not declared; however, it does have to be distinct from other local identifiers. {EMAS IMP80: labels do not have to be distinct from other identifiers.} Each label is located immediately to the left of the statement to which it refers, followed by a colon. Examples: NEXT: P = P+1 %if P < 0 ERR1: ERR2: FAULTS = FAULTS+1 Control is passed to a labelled statement when a jump instruction of the form ->