Supporting control definition in programming languages

Supporting control definition in programming languages

Comyut Lan,g Vol 9 No I. pp 1-23. 1994 0096-055l b4 S300~-I) 00 Cop.'mght c [9S4 Pergamon Press lad Pergamon Pre,s k:d Printed m Great Bntmn SUPPOR...

1MB Sizes 0 Downloads 25 Views

Comyut Lan,g Vol 9 No I. pp 1-23. 1994

0096-055l b4 S300~-I) 00 Cop.'mght c [9S4 Pergamon Press lad

Pergamon Pre,s k:d Printed m Great Bntmn

SUPPORTING CONTROL DEFINITION IN PROGRAMMING LANGUAGES MARCO COLOMBETTI and GIOVANNI GUIDA Dlparumento di Elenronica. Politecnico di Milano, Progetto di [ntelligenza Artificiale, Milano. Ital? (Receiced 4 October

1982; revision

receiced 2 June

1983)

Abstract--The paper is devoted to illustrate a new approach to control definition in programming languages. The DIL systemdeveloped by the authors to allow a user to define and utilize his own control mechamsms in program design and construction is presented. The motivations of the proposal are d~scussed :n the frame of software engineering and abstraction programming. The main features of DIL are introduced in an informal way through a number of examples of increasing complexity. The basic mode of operation of the system is described and an architecture for the implementation of DIL is given. Meaningful sample applications are shown to validate the main features of the language. Promising directions for future research are presented as well. A run-time model of DIL is sketched in the Appendix. Control structures Macroexpanders

Extensible languages Abstractionprogramming Programmingmethodologies

1. I N T R O D U C T I O N

AND

BACKGROUND

The recent development of programming languages and systems has largely been influenced by basic methodologies newly proposed for design, construction, verification, and maintenance of large, reliable software systems. A number of such methodologies constitute the discipline of structured programming, which is characterized by the use of abstraction reasoning in the design and construction of programs [1-3]. In particular, the role of abstraction programming in software development is clearly exemplified by the stepwise refinement [4] and information hiding [5] approaches. Within such methodologies, the basic concept of abstract data type has been defined and thoroughly investigated. The importance of programming languages supporting data abstraction has been pointed out, and several programming systems facing this important issue have been proposed [6-10]. However, only minor attention has so far been devoted to the concept of control in programm ing languages, although control structures play a key role within any programming methodology [11]. As remarked in [12], the set of control structures at the p r o g r a m m e r ' s disposal not only determines the class of expressible algorithms, but also strongly influences the programming style and discipline. This means that the availability of flexible control primitives, or better the possibility of user defined control, could be of the greatest importance for program development. The importance of "control abstraction" is acknowledged by the designers of C L U [8] .and A L P H A R D [13], and the iterator/generator construct is available in such languages for dealing with simple cases of control specialization. Such a construct makes it possible to specialize a general iterative structure according to the number and type of the indexes. In such a way, however, only a family of iterative constructs is taken into account, and no attention is paid to different control mechanisms. In fact, we think that the abstract data type paradigm is too specific to be directly applied to control structures in general. Nevertheless, we expect that several basic ideas still preserve their validity in the field of control structures and, also, may be helpful in developing programming tools able to support and enhance a flexible and disciplined use of control mechanisms. Significant steps in this direction have been made by the designers of "extensible langua~,ges" [9, 14, 15] like, for instance, F O R M A L [16] and S T A G E [17]. However, the extension mechanisms so far proposed seem to be too weak to allow one to introduce sufficiently general and powe, rful control structures.

2

MARCO COLOMBETTI and GIOVANNI GUIDA

Before defining precisely the aim and scope of our work, we shall now briefly illustrate the concept of control structure as it will be used throughout the paper. By the term control structure we mean a family of partial flowcharts which share some common properties and represent a usual and repeating configuration or behaviour of the control flow in classes of algorithms. Note that in some cases, all flowcharts belonging to a control structure show the same topology: think for example of the classical if-then-else and while-do structures. In other cases the topology may change, like with case, f~, and RE n structures [12]. If we put no restriction on this definition a crucial problem arises since any procedure could be considered just a special case of control structure, thus allowing an undesidered ambiguity between control abstraction and procedural abstraction [8]. Moreover, it would be difficult to distinguish between control structures and procedures if we allow procedures to be passed as parameters to other procedures. To avoid such critical confusions, we shall assume that: (i) procedures cannot in general be passed as parameters to other procedures; (ii) the specification of a control structure must always contain at least one variable object of type (statement) in its definition, as in the examples: while

(condition)

dg-° (statement) od -

-

i_f fi

(condition) then (statement) else (statement)

This requirement, which is shown here in two simple particular cases, will be more evident in the sequel of the paper after we shall have presented a general formalism for specifying control structures. This definition seems to be wide enough to embed very high level control mechanisms as control structures (think, for example, of nondeterministic and parallel constructs), thus allowing a quite general and nontrivial discussion about the use of control mechanisms in programming languages to be engaged. Let us now come to the specific aim of the present work. The purpose of this paper is to exploit the implications of the abstraction programming approach on the definition and implementation of control mechanisms in programming languages. Our aim is to propose a new programming language (called DIL) supporting the syntactic definition and the implementation of control structures by the user. In our opinion, such a system will constitute a very interesting programming tool. In fact, the possibility of programming (defining and implementing) control structures suggests a new discipline in problem solving and program construction [2]. At each step of the refinement process [1,4] the appropriate data types are first chosen and implemented, and the required processing algorithms invented; the adequate control structures are then introduced and programmed, which are needed for implementing in the most natural way the program being designed. The advantages of this discipline are manyfold. First, a more balanced and integrated role of the two basic components of programming, namely data types and control, is ensured. Second, the greater freedom left to the user in defining data and control according to the particular needs of the different problems he is facing will result in a more clear and straightforward evidence in the program of the designer's choices and reasoning. Third, the correct and skilful use of the features available in a programming language is highly promoted by the fact that they have themselves been constructed (not only chosen) by the user. These features are very important in supporting a flexible and sound discipline in programming and in promoting the production of good quality software. From this standpoint, the first basic characteristic of control in programming languages is that it should be user-programmable. One reason for this is that several control regimes which are formally very similar should be clearly distinguished in different applications and therefore cannot be a priori built in within a programming language. For instance, a caseqike structure may be used to branch on a number of conditions which can be verified during normal execution of a program, or to capture exceptional events which require special handling. For example, consider this loop to get a command string which is numeric and not longer than three characters:

Control definition

3

repeat write ("command?"): read (command-string): if length (command-string) > 3 then write ("too long") fi until command-string is numeric; process command-string; It is straightforward that the use of a specific exception handling control structure in the latter case would result in more readable and selfdocumenting programs. The previous example could, for instance, be better defined as: do write ('~command?"): read (command-string); except l: length (command-string) > 3; except 2: command-string is not numeric; process command-string exceptions 1: write (~'too long"); restart 2: write ("not numeric"); restart , od User programmable control structures allow the designer to write more concise and clearer programs in the case a class of algorithms repeatedly contains a specific combination of elementary structures like the ones normally available in programming languages. Besides, the processing of complex data types is made easier and more transparent by the use of specially designed control structures. Any language allowing the user to define new data types should therefore also support control definition. The possibility of defining highly specialized and concise control structures might also be of great help in program correctness proofs [18]. This topic will however receive no attention in the present paper. A further fundamental characteristic of control is that a specific structure can generally be implemented in different ways. This is of particular interest with non-deterministic control like Dijkstra's do-od structure [2], which can (and should) be used by a programmer ignoring the particular algorithm for the selection of the statements employed in the actual implementation. In this case, the analogy with abstract data types is evident. However, the basic differences between data types and control structures should not be overlooked. To use a data type one only needs to know the effects of the admissible operations defined on it. Nothing similar happens with control structures, which can only be specified (and used) describing their effect on the structure of computation. To be more precise, the specification of a control structure requires: (i) a syntactic definition, i.e. the description of the syntactic form assumed by legal instances of the structure; (ii) a semantic specification, which unambiguously describes the function of the structure. The paper is organized in the following way. Section 2 introduces the basic features of D1L in an informal way, through a number of examples of increasing complexity. Particular attention is devoted to the syntactic aspects of the language. Section 3 is devoted to outline some relevant aspects of the system implementation. In Section 4 a few significant applications of D I k are discussed to support the general contribution of our proposal. Section 5 presents some concluding remarks and several topics and hints for future work. Finally, in the Appendix we illustrate the run-time semantics of DIL.

4

MARCO COLOMBE'I-FIand GIOVANNI GUIDA

2. D E F I N I T I O N

OF C O N T R O L

MECHANISMS

2. l Preliminary concepts This section is devoted to the presentation of the fundamental aspects of DIL [19]. Both the syntax of the language and its naive semantics are illustrated on the base of selected examples of increasing complexity. A run-time model of DIL will be sketched in the next section. Particular attention is devoted to show the expressive power and the flexibility of DIL, which constitute the most valuable aspects of our proposal. From the user's point of view DIL appears as a meta-language, which makes it possible to design wide classes of control mechanisms and to enlarge the expressive power of the programming language currently utilized. The language to which DIL is applied is called base language L and is assumed to be a Pascal-like language. The extended language which is obtained by enlarging the set of control structures available in L is denoted by L*, regardless of the particular structures which are actually available in it. The term " D I L " denotes three different objects at a time: - - a formal language for defining control mechanisms (the DIL language); - - a n extensible macroexpander which translates L* programs into equivalent L program modules (the L* compiler); - - a translator which compiles DIL programs (i.e. definitions of control mechanisms) into extensions of the DIL macroexpander (the DIL compiler). This allows the user to dynamically define his own control regime according to his particular design exigencies. Details on the implementation of DIL and on its run-time behaviour will be presented in the following sections. A DIL program (not to be confused with an L* program!) is constituted by a sequence of definitions of control mechanisms called DIL definitions. A DIL definition must supply two types of information: the syntactic pattern of the new construct, to be used for syntactic analysis, and the implementation of the same structure in L, to be used for macroexpansion. Moreover, in each definition an explicit declaration of the variables, constants, and identifiers involved is needed. The following BNF formula illustrates the syntax of a DIL definition: (definition):: = D E F I N E (identifier) D E C L A R E (declaration) PATTERN (pattern) I M P L E M E N T A T I O N (implementation) ENDDEF The identifier following the terminal word D E F I N E denotes the name of the structure and, therefore, cannot be used elsewhere to denote a different object. The declaration, the pattern, and the implementation are all built up of three different types of objects: identifiers (of DIL and L variables, separators, etc.), DIL constructs (iterator, enrichment, where-clause, etc.), and L statements (in the implementation only).

2.2 Some simple examples Let us consider a very simple example to introduce the use of the basic features of DIL. Suppose the while-loop structure [12] is not available in L; by means of the following definition we can introduce it in L*: DEFINE while-loop DECLARE S E P A R A T O R S while: RESERVED, do, od VARS p: B O O L E A N EXPRESSION, a: S T A T E M E N T GCONSTS START: LABEL PAT:rERN while p do a od

Control definition

5

IMPLEMENTATION START: if p then a: goto S T A R T fi ENDDEF We nov, illustrate the use of D I L in the above example. Let us begin by some lexical features. D I L terminal symbols are uppercase underlined, new terminal symbols to be introduced in L* (e.g. while, do, etc.) are lowercase underlined (unless special symbols or punctuation marks); D I L variables and compiler generated objects (variable or constant) are lowercase or uppercase not underlined; terminal symbols of L are lowercase underlined (unless special symbols or punctuation marks). In the above definition while, do, od, are new terminal symbols of L* (SEPARATORS), one of which, namely while, is reserved ( R E S E R V E D ) in the sense that it cannot be used elsewhere in L* with a different meaning. This is a general feature of DIL: the first separator in the pat~.ern is reserved in order to speed up the parsing. The declaration must provide a type specification for any symbol used in the definition which does not belong to the set of D I L and L terminal symbols. Punctuation marks are implicitly declared as separators. The definition of the pattern is subject to the following rules: (i) the pattern must begin with a reserved separator and end with a separator; (ii) at least one separator must occur between any two variables. The implementation is here just an L program module in which D I L variables and compiler generated objects are used. A DIL definition is supposed to extend the syntactic type ( s t a t e m e n t ) of L* with the new control structure being defined (in our example while-loop). D I L definitions are cumulative, i.e. a DIL variable of type S T A T E M E N T stands for a generic L* statement, which may contain structure instances at any level of nesting (unless differently specified by means of where-clause; see Subsection 2.5). 2.3 Repeating a piece of text; the iterator In this subsection we are going to introduce the first typical construct of DIL: the iterator. Let us consider a simple example: suppose we want to define a new control structure for case analysis with exception handling [12] of the following type: case p~: ai, Pz: a~,

p~,: an

end case where p~, p2 . . . . . p, are predicates (conditions) and a~, a, . . . . . an are L* statements. The following D I L program provides a possible definition of this construct. D E F I N E select-case DECLARE S E P A R A T O R S case: RESERVED,endcase MVARS p: B O O L E A N E X P R E S S I O N , a: S T A T E M E N T GCONSTS END: L A B E L PATTERN case ~p: a H, ~ endcase

6

MARCO COLOMBETTIand GIOVANNI GUIDA

IMPLEMENTATION Iifp then a: goto END write ("exception in case analysis"); END: dummy ENDDEF The iterator allows denoting the repetition of pieces of text for a number of times unknown at the moment of definition. The general form of the iterator is the following: [ ( t e x t ) [t (separator)[I (tag)~ where ( t e x t ) is a piece of a pattern or of an implementation which generally contains multiple DIL variables (MVARS) or compiler generated objects; (separator) is a separator, ( t a g ) is an identifier, both chosen by the user. The meaning of the above formalism is that the actual text denoted by the iterator is composed of the repetition of (text), for a number of times defined at E* compile time only, any two adjacent repetitions being separated by (separator). When ( t e x t ) is a piece of implementation, (separator) may be omitted, and is taken to be ";" by defaul:. The iterator
Control definition

loop L 1: read (A): if A < 0 then exit k l fi: loop L2: read (B): if B < 0 then exit L1 else if B > 100 then exit L2 fi fi; insert B in array BETA endloop process A and BETA endloop write ("error in data") Note that the body of this construct is composed of an L* statement within which simple control statements of the form exit (label) can appear. These statements are not themselves instances of some structure and, also, are meaningful only within the body of a loop-exit structure. We denote this kind of situation, which is of general concern in defining control mechanisms, by' stating that the body of a loop structure can be built by L* statements locally enriched by simple control statements of the type exit (label). Such an enrichment is made possible by a new D I L construct. The D I L definition of this kind of loop-exit structure is given below: D E F I N E loop-exit DECLARE SI~PARATORS loop: R E S E R V E D , exit: R E S E R V E D , endloop VARS a: E N R I C H E D S T A T E M E N T , L: T A G GCONSTS START, END: LABEL PATTERN loop L: a [exit L] endloop IMPLEMENTATION START: a [goto END]; goto S T A R T END: dummy ENDDEF Let us add a few comments about this example. The variable being enriched (a, in this case) is declared E N R I C H E D in the declaration part. The variable L is used to individualize the particular instance of loop-exit control structure to which an exit L statement refers (note in the sample program above presented the use of exit LI within loop L2). To this purpose L is declared as "FAG, a new syntactic type not to be confused with LABEL. The variable declared as T A G must appear in the pattern just after the first reserved separator and after the reserved separator used in the enrichment body. The variable of type T A G may be omitted whenever each new statement introduced through the enrichment is assumed to refer to the most recent instance of the structure. This construct denotes that, within a given environment which defines the scope of the enrichment (in our case the statement a), the new control statement introduced may occur zero or more times, yielding a correct text for an L* program. During the parsing all the occurrences of the new statement within the legal scope are individuated thanks to the declaration of exit as R E S E R V E D separator; later, in the macro expansion step, they are substituted by the actual L text which is specified in the implementation (goto L in our example), Let us note that, being the labels S T A R T and E N D compiler generated constants (which are always different for any different instance of the loop-exit control structure), this very simple D I L definition is sufficiently powerful to represent any combination of correctly nested loop-exit structures. In the most general case, an enrichment has the form: (scope> [(body>]

8

.'~[ARCO COLOMBETTI and GIOVANNI GLID~,

where (scope) denotes the DIL variable (declared E N R I C H E D ) to which the enrichment applies (only variables of type S T A T E M E N T can be enriched), and ( b o d y ) specifies, in the pattern, the new control statements which can be used as legal L* statements within the (scope} and, in the implementation, the way in which the new control statements introduced must be translated into L program modules. Within the pattern, the ( b o d y ) of an enrichment may be constituted of: (i) a simple component not containing multiple variables and just satis~'ing the general rules for a pattern illustrated in Subsection 2.2; (ii) a complex component containing multiple variables: (iii) a finite number of simple or complex components separated from each other by a stroke "" [1 "'. The body of an enrichment cannot contain iterators or enrichments. Let us illustrate some more features of the new construct introduced by a further example. Suppose we want to define a control mechanism which may serve as a tool for promoting the use of a top-down programming methodology [1, 2, 4]. The idea here is that this control structure (we call it top-down-section) should force the programmer to proceed in the design by strict step-wise discipline, postponing the expansion of code until this may seem suitable. At any point of a statement an exec (label) statement can refer to a subsequent program segment introduced by the statement step (label), which will be expanded and actually programmed in a further refinement only. The following DIL definition fulfils our aim and also provides a clear illustration of the iterator and enrichment constructs in a nontrivial case. D E F I N E top-down-section DECLARE S E P A R A T O R S section: RESERVED, exec: R E S E R V E D step, endsec, [] L,M: LABEL, b: S T A T E M E N T , a: E N R I C H E D S T A T E M E N T MVARS GVARS JUM: LABEL MGCONSTS RET: LABEL GCONSTS END: LABEL PATTERN section a [exec L]; ~step M: bll C]]] endsec IMPLEMENTATION a [JUM: = RET; goto L; RET: dummy]; goto END; ~M: b; goto JUM~ END: dummy ENDDEF In this definition two new types of declared objects are introduced: GVARS and MGCONSTS. A GVARS object is used when the L* compiler is expected to generate a new L* variable; the L* variable type is also indicated in the declaration. M G C O N S T S objects are multiple objects of type GCONSTS. A complete definition of all types of DIL objects will be given in the next section. 2.5 Controlling the nesting of control structures: the where-clause In this subsection we shall introduce the last construct of DIL, aimed to restrict the nesting of control structures in L*. Up to now control mechanisms defined in DIL have been supposed to be used in constructing L* programs without any constraint. This directly follows from DIL's power of extending the type (statement) of the base language L. Nevertheless, the need often arises, when defining complex control environments, of putting some constraints to the free nesting of control mechanisms within particular constructs. As an example, let us consider again the last example presented in Subsection 2.4, namely the top-down-section. According to the given definition, top-down-section structures can be freely nested both in the statement a and in the statement b in the step parts. This does not ensure a sound discipline in the use of the top-down-section since in a correct step-wise refinement top-down-section structures should be

Control defimtion

9

nested only in the step parts of the construct [2]. This can be specified by forcing the statement a to embed no top-down-section through the following where-clause inserted in the declaration: VARS a: E N R I C H E D S T A T E M E N T W H E R E NOT top-do~vn-section In the general case the where-clause allows the programmer to constrain the nesting of control structures by specifying that a given statement either can only embed a specified set of legal control structures or cannot contain some illegal ones. The where-clause may apply only to DIL variables of type S T A T E M E N T and is specified, within the declaration, in the following way: {i) W H E R E followed by a list of names of DIL defined structures: (ii) W H E R E NOT followed by a list of names of DIL defined structures; (iii) W H E R E NIL. The meaning of the above clauses is straightforward: case (i) denotes that only the specified structures may be freely used and nested within the statement to which the where-clause refers; case (ii) denotes that any DIL defined structures which do not appear in the specified list may be utilized; and case (iii) denotes that only the concatenation of assignment statements may be used in the constrained statement,

3. DIL IN O P E R A T I O N 3.1 Preliminary concepts The activity of the L* compiler can be considered, from a merely conceptual point of view, as composed of two sequential steps [20, 21]: (i) parsing of the input L* text (taking mostly into account the information supplied in the pattern definition) in order to individualize the user defined control structures contained in it, to control their correct nesting, and to generate the appropriate environment (managing multiple variables and objects, assignment of values to DIL variables--see later) which is needed for code generation: (ii) macroexpansion, i.e. generation of the equivalent L program module by expanding the text supplied in the implementation of the involved control structures in their appropriate environment (i.e. with each DIL variable substituted by its value and with the correct compiler generated objects--see later). Parsing and macroexpansion steps are activated, during the translation of an L* program, in such a way that the correct nesting of the involved control structures is assured. More precisely, if we supose that any L* program is defined according to the following DIL definition: D E F I N E DIL-program DECLARE S E P A R A T O R S program: RESERVED, endprogram VARS a: S T A T E M E N T W H E R E N O T DIL-program PATTERN program a endprogram IMPLEMENTATION a ENDDEF the basic activity of the L* compiler (regardless of the problem of error detecting and diagnostic generation) is illustrated in Fig. 1 through a self-explanatory flowchart. In order to go further in our discussion let us now introduce some necessary details about the different types of identifiers which may appear within a DIL definition. An identifier appearing in the body of a definition may denote: (i) a new terminal symbol of L* to be used as a separator (SEPARATORS); (ii) a DIL variable which has value in L* and is typed by a syntactic type of L*: this variable is global both to the pattern and the implementation (VARS); the actual value of a variable is assigned at L* compile-time through the parsing of the input text:

10

MaRco COCOMBE'r-rland Giova.,,xz GUIDA

I., sc'n 'hOOt t"' ] IS scanning

term,hated?~

"

~

+, IS d

new OIL control structure recognized? /

~Y curmnt I struc ure ( I a?y)

suspend parsing of the

L

I

+

I beg=nparsing of the new control I structure

J

+ ~s parsing of the current

terminated? )

structure

I +,.

I +

I resumeparsing of the 9revlous controt slructure (if any)

J Fig. I. Basic activity of the L* compiler.

(iii) an object (constant or variable) which may assume any type allowed in L for variables and constants and which is generated at L* compile-time during macroexpansion (GCONSTS, GVARS); such compiler generated objects are local to the implementation. DIL variables and compiler generated objects may be of two different kinds, namely simple and multiple. A simple variable or object refers to a single (i.e. exactly to one) value or object. A multiple one (MVARS, MGVARS, M G C O N S T S ) denotes a possibly multidimensional array of variables or objects of the declared type, whose actual extent is not known at DIL compile-time when the control mechanism is defined. Multiple variables can be used only in connection with an iterator or an enrichment construct. The actual value of the extent of multiple variables or objects is defined at L* compile-time according to the particular rules which will be illustrated below. Let us then conclude this subsection by revisiting the rules which govern the use and operation of the iterator and enrichment constructs with particular attention to the management of multiple variables and compiler generated objects.

Iterator Multiple variables appearing in the pattern within an iterator are unfolded by the L* compiler during the parsing: each time a new iteration of ( t e x t ) is recognized, a new element of each multiple variable involved in it is generated and the appropriate value is assigned to it. (In this way the actual extent of multiple variables is also determined.) The iterator is then expanded in the implementation according to the following steps (note that each iterator in the pattern has one or more corresponding iterator in the implementation): - - ( t e x t ) is repeated in the object code exactly the same number of times determined during the parsing for the corresponding iterator appearing in the pattern; - - at each repetition multiple variables are substituted by their appropriate values in the same order determined during the parsing;

Control definition

1',

- - multiple compiler generated objects are unfolded by generating a new element of them for any new repetition of (text). Note that multiple variables and objects may appear more than once within (text), but, in this case, they all refer to the same elements, since new elements are generated only in correspondence to a new iteration. Note also that multidimensional multiple variable and objects are involved whenever nested iterators are considered. Let us outline that compiler generated objects (constants and variables) are different for each new instance of control structure (also for nested ones) in an L* program and are also protected against any possible accidental alteration or confusion with user defined objects,

Enrichment Let us distinguish three cases: (i) the ( b o d y ) of the enrichment contains only one simple component: (ii) the ( b o d y ) is composed of a simple component containing multiple variables: (iii) the ( b o d y ) contains a finite number of simple or complex components. The meaning of the enrichment in case (i) is straightforward: the simple component defined in the ( b o d y ) may be freely used within the (scope) as a legal L* statement, thus appearing there zero or more times. Case (ii) deserves a little more attention: the multiple DIL variables appearing in the complex component are unfolded at L* compile-time during the parsing according to the same rule above illustrated for the iterator: each time a new occurrence of the complex component is recognized in the (scope) a new element is generated for each multiple variable and the appropriate values are assigned to them. Case (iii) simply denotes that each one of the components defined in the ( b o d y ) may occur within the (scope) of the enrichment: simple and complex components are treated independently of each other as above illustrated. Each enrichment in the pattern is always coupled with a corresponding one in the implementation, which specifies how the new L* control statements recognized in the (scope) daring the parsing must be translated into an L program module. The linking between two enrichments is generally defined by the (scope) to which the enrichment is referred to. Corresponding enrichments must be composed of the same number of components, which are supposed to appear in the same order both in the pattern and in the implementation, in such a way that corresponding components are easily identified on the base of their displacement in the ( b o d y ) of the enrichment. Complex components may contain, in the implementation, multiple compiler generated objects, in addition to multiple DIL variables already appearing in the pattern. The enrichments are expanded at L* compile-time according to the following rule: each occurrence of a component recognized during the parsing is substituted by the corresponding component which is specified in the implementation. In the case of simple components this operation is quite straightforward, while for complex components multiple variables are substituted by their values and multiple compiler generated objects are unfolded with the same rule already illustrated for the iterator (a new element for any new occurrence of the component). A run-time model of the L* compiler is sketched in the Appendix.

3.2 An architecture for the DIL system In this subsection we shall illustrate the main design criteria of an experimental project, which we are developing to support the DIL system, This system is conceived as a prototype tool for high level software design and construction and is organized as illustrated in Fig. 2. The main requirements which have been considered in the system architecture are: --to

--to --

allow DIL control structure definitions and L* program modules to be independently compiled and managed; provide a complete diagnostic system for DIL and L* programs at compile-time; to provide a flexible and effective connection with a module for abstract data types definition.

12

MARCO COLOMBETTI and GIov~,.~'Xl GL'XDA O lL source

data type definitions

programs

OIL COMPL IER

DATATYPE 1 COMPL IER

L~' source

programs

-

I

L*

- 01k dfagnoshcs

L•

COMPILER

diagnostvcs

symbohc L programs

1 L COMPILER

L dlagnoshcs

~

I L/L~"OIAfiROSTIC TRANS~CER

relocatable elements Fig. 2. Overall architecture for a software development tool supporting the DIL system.

The system is comprised of two basic modules, the DIL compiler and the data type compiler, which are aimed to supply the user with both control structure and abstract data type definition facilities. The data type compiler is intended as a CLU-like system [8] and is aimed at providing a workable tool for defining and managing new data types to be used in program design as well as in the definition of new control mechanisms. We shall focus our attention in this section on the DIL compiler only, which is devoted to provide, together with the L* compiler, the control definition facilities which are the main subject of our work. As a base language a version of Pascal language (with external procedures and goto) has been adopted as the most appropriate to our application. The DIL compiler above introduced is devoted to translate source DIL programs into internal DIL tables which contain all the syntactic and semantic information supplied in the DIL definition and necessary for the L* compiler to perform the three activities of parsing, diagnostic, and code generation. For each new control structure a new table is defined and the DIL symbol table containing DIL reserved words is updated. The DIL compiler also provides a diagnostic facility which informs the user of possible incorrect structuring of DIL programs and of illegal uses of DIL syntax. The output of the DIL compiler is organized in the controlstructure library which is utilized by the L* compiler in translating L* source programs into symbolic L modules. The overall organization of the L* compiler is not far from the one described in [14, 16]. Several parallel stacks are used to speed up the parsing and macroexpansion activities and the dynamic memory management, which is required both in stacks and multiple variables management, is substituted by a static memory allocation controlled by the user through the choice of appropriate parameters (maximum level of nesting, maximum multiple variables dimensions and extent, etc.). A diagnostic module supplies the user with any information concerning the possibly incorrect use of the new control mechanisms introduced in L* through DIL definitions (not in the use of basic L language features). The output of the L* compiler is constituted by symbolic L modules which are afterwards processed by an L compiler and translated

Control d e f i n m o n

[3

into relocatable elements. An L,'L* diagnostic transducer provides an effective back reference for L diagnostic to L* source code in order to allow the user to completely ignore the intermediate L code in the activity of L* programming. 4. SOME A P P L I C A T I O N S

OF DIL

In this section we shall describe some applications of DIL in order to illustrate the most relevant features of the language. The examples are also aimed at showing the flexibility and power of the system in facing problems of actual interest. In particular, the first subsection presents the general control mechanism proposed by J. Mack Adams [22]; the second subsection is focused on nondeterministic and quasi-parallel programming. Other relevant applications such as exception handling, event driven programming, variant record processing, etc. are neglected in this section since the), do not contribute to a deeper understanding of the general framework.

4.1 A cornprehensice example Let us consider as a comprehensive example the sample program on lens design proposed, by J. Mack Adams in [22]. This is in fact a very typical example which shows how control structures should be designed to meet the particular characteristics of different data structures, what is indeed a very important feature in constructing sound programs. We show here how the iterative control structure proposed in [22] to solve in the best way the lens design problem can be simply defined using DIL. This structure (we call it MA-loop) has the following general syntactic form: MA-loop when al/'P~ or a2/P2 or . . . or an/Pn" until b~/Q~ oj b:/Q: or . . . or bm/Qm; S when-actions a t ~ S i [] a,-~ S~ []

an :::::~S n

until-actions

bl:=:"RL D b~:=*"R2 ~]

bmz=~ Rna

pool-AM The structure is composed of three parts: a prologue containing when-conditions (a,/P,) and until-conditions (bj/Qj), a body (S), and an epilogue containing when-actions ( a ~ S , ) and until-actions (bj~R~). Each a, or bj is an identifier which is the name of the corresponding boolean expression P, or Q . Each Si or Rj is a block of code and the block S, which forms the main body of the loop, must have the form: begin Co; ?f~; CI; ?f2; C_,; . . . ; ?f~; C~ end where each fk is the name of a when-condition or of an until-condition. Each statement of the form ?fk is called a test statement. Each Ck is a possibly null sequence of statements not containing a test statement. It should be noted that not all the names a,, bj need appear in the body, although this should probably elicit a warning from the compiler. Also, any a,(bj) may appear more than once.

14

MARCO COLOMBETTI and GIOVANNI GUIDa

As given in [22], the semantics of this construct is the following. The block S forms the main body of the loop and the elements of C0: ?fL; CE: ?f.,: C:: . . . ; ?ft; C~ that comprise S are performed in the fashion that we now describe. The statements comprising Co are executed first. Then, assuming that f~ is the same as some name a,(bj) declared in the prologue of the loop, the "test statement" ?fz causes the Boolean expression P,(Q) to be evaluated. If the resulting value is true, an exit (return) to the beginning of S is to be performed. However, before the exit or return the action S,(Rj) is taken. If, on the other hand, the value of P,(Q) is false, the execution sequence continues with Ct. This process is continued and if the statements comprising Ct are ever executed, a return to the beginning of S is performed without further action. DEFINE Mack-Adams-loop DECLARE SEPARATORS MA-loop: RESERVED, when, or, until, begin, ? : RESERVED, end, when-actions, until-actions, pool-AM, D , ~ , / MVARS a, b, d, g, h: LABEL, P, Q: BOOLEAN EXPRESSION, S, R: STATEMENT VARS body: E N R I C H E D STATEMENT W H E R E NIL GCONSTS START, EXIT: LABEL GVARS JUM: LABEL MGCONSTS RET, WHEN-LABEL, UNTIL-LABEL: LABEL PATTERN MA-loop when ~a/P [l or [l ITI~ until ~b/Q 1[ or [[ IT2[[ begin body [?d] end when-actions [ g ~ S II [] II IZl~ until-actions ~h~R It [] 111T2~ pool-AM IMPLEMENTATION START: S[JUM: = RET; goto d; RET: dummy] goto START; ~a: if P then goto W H E N - L A B E L else goto JUM fill ITI'~ ~b: if Q then goto UNTIL-LABEL else goto JUM _fi [I IT2[ ~WHEN-LABEL: S; goto START[ ITI~ ~UNTIL-LABEL: R; goto EXIT[ IT2~ EXIT: dummy ENDDEF 4.2 Nondeterministic and quasi-parallel programming In this subsection we are going to show how our approach allows one to treat control structures in nondeterministic programming, provided that enough powerful primitives can be used in DIL implementations. We shall feel free to add primitives to DIE on the basis of intuitive descriptions only. The term nondeterminism refers to different orders of concepts i think, for instance, of Dijkstra's nondeterministic if and do structures, of heuristic search in AI programming, and of parallel programming. In t-his secti--on we shall be concerned in particular with search and quasi-parallel (monoprocessor) programming. In fact, we believe that the meaning of Dijkstra's structures can only be captured at the abstract level of denotational specification, since any implementation would transform them in fully deterministic structures. On the other hand, some general concepts of parallel programming--like monitors--lie far beyond the notion of control structure. Let us now introduce a number of primitives for nondeterministic searching and quasi-parallel processes. We do not claim that our primitives are really adequate for practical purposes; they will rather be used as an example of how searching can be dealt with in DIL. The fundamental tool for seaching is the context. A context is a data structure containing a value for each variable used in a program. There is exactly one initial context, in which all variables are

Control definition

15

initialized to a standard value depending on their type (e.g. 0 for integers, false for booleans, e~:c.). When the execution starts, the initial context becomes active. The execution process transforms the active context by updating the values of variables according to the program instructions. At any time, the primitive current refers to the presently active context. A new context can be createcl at any point of execution by the primitive newcxt. The context created and referred to by newcxt is a copy of current, but is not active. This means that the new context will not be influenced by further execution unless explicitly activated. As we are dealing with monoprocessor programming. only one context at a time can be active. Therefore the inactive contexts must be inserted tn a sequencing structure allowing one to activate them when necessary. In our examples we shall make use of a stack and a queue for context sequencing. We have the following primitives: push (c,1) :where c is a context and 1 is a D I L implementation label. Its execution pushes the couple (c,l) on top of the stack. top :it gives the couple (c,l) on top of the stack, popping the stack at the same time. If the stack is empty, it results in an abort. insq (c,l) :it inserts the couple (c,1) at the end of the queue. firstq :it gives the couple (c,l) at the beginning of the queue, erasing it from the queue at the same time. If the queue is empty, it has no effect. act (c,lt :it causes execution to proceed from l within the context c. The current context is cancelled, unless previously inserted in the queue or pushed on the stack. killq :it cancels all contexts from the queue. Our first example shows a nondeterministic or structure with failures and backtracking [23, 24]. All branches of the or structure are guarded by boolean expression. The search process tries the first branch whose guarding expression is true and backs up in case of failure to try next branch. If all branches have already been tried, execution backs up to a previous or structure. If there is no such structure, the program results in an abort. It is essential for such a process that only one global stack is used to handle all contexts; this allows an or structure to be exited and a previous one to be resumed from the point it had been left. Here is the D I L definition: D E F I N E backtracking DECLARE S E P A R A T O R S or: R E S E R V E D , fail: R E S E R V E D , D, ro MVARS a: E N R I C H E D S T A T E M E N T , p: B O O L E A N E X P R E S S I O N MGCONSTS L: L A B E L GCONSTS END: L A B E L PATTERN or {p:a[fai_~l] I[ f--l~ r£ IMPLEMENTATION if p then push (newcxt, L); a[act (top)]; goto E N D fi; L: d u m m y 1 act (top); END: d u m m y ENDDEF Let us now' consider as a second example a breadth-first search process [25, 26]. The first branch is tried first; when a suspend c o m m a n d is encountered, control is passed to the next branch. After the last branch, the first one is resumed from the point of suspension, and so on. The structure is exited when a branch is completed for the first time. Below we give the D I L definition of breadthfirst search:

[P

MARCO COLOMBETTI and GIO'~ANNI GLIDA

DEFINE breadth-first DECLARE SEPARATORS do: RESERVED, suspend: RESERVED, [-], od MVARS a: E N R I C H E D STATEMENT MGCONSTS L,M: LABEL GCONSTS END: LABEL PATTERN do :~a[suspend] II [-]1 o d IMPLEMENTATION insq(newcxt, L) l'~; act (firstq); EL: a [insq(current,M); act(firstq); M: dummy]; killq; goto END~, END: dummy ENDDEF The examples given show that DIL supplies indeed a flexible and general tool for Artificial Intelligence programming [26] which seems to be a promising domain of application. We conclude this section by presenting a last application of DIL in the field of quasi-parallel programming [27-29]. Let us consider a kind of scheduler which allows a quasi-parallel (monoprocessor) execution of different tasks. The tasks are stored in a queue; whenever a wait p command is encountered in a task, if the boolean expression p is true the task is deactivated and control is transferred to the following task. A task is completed, and then deleted from the queue, when all its instructions have been executed; the scheduler terminates if all the tasks in it are completed. Below we have the DIL definition: DEFINE scheduler DECLARE SEPARATORS schedule: RESERVED, task, endtask, wait: RESERVED, endsch MVARS a: E N R I C H E D STATEMENT, p: BOOLEAN EXPRESSION MGCONSTS L,M: LABEL PATTERN schedule ~task a[wait p] endtask~ endsch IMPLEMENTATION [insq (newcxt,L)~ ~act (firstq); L: a[{i_f p then insq (current, M); act (firstq) fi; M: dummy}]~ ENDDEF 5. C O N C L U S I O N In the paper the DIL language for defining control mechanisms has been presented. Its application to the implementation of user definable control regimes in programming languages has been illustrated and several examples have been discussed. The attention has been focused both on the syntax and on the run-time model of DIL, and an architecture for a programming system to support DIL, mainly based on a macroexpansion technique, has been outlined. The research work reported in the paper brings up several contributions in different areas. In the field of programming languages DIL represents the first global and comprehensive proposal of a system for control definition and implementation. In fact it allows one to deal in a flexible and very powerful way with a wide class of control regimes freely programmable by the user. The theoretical evaluation of the richness of the class of control structures which can be defined through DIL will be considered in a future work. From the point of view of programming methodology,

Control defimtion

17

t h e d e v e l o p m e n t o f D I L is i n t e n d e d to s u p p o r t a n e w p r o g r a m c o n s t r u c t i o n t e c h n i q u e , in w h i c h t h e role o f d a t a t y p e s a n d c o n t r o l s t r u c t u r e s d e s i g n is c o n s i d e r e d in a b a l a n c e d a n d u n i t a r y way. In fact, D I L a i m s a t p r o p o s i n g i t s e l f as a s t e p t o w a r d t h e c o n c e p t i o n o f a p r o g r a m m i n g too'~ in w h i c h t h e u s e r is a l l o w e d to d e s i g n a t a v e r y h i g h level its o w n d a t a t y p e s a n d c o n t r o l m e c h a n i s m s , t h u s d e f i n i n g t h e m o s t a p p r o p r i a t e p r o g r a m m i n g e n v i r o n m e n t w h i c h is r e q u i r e d f o r a d i s c i p l i n e d a n d effective p r o g r a m m i n g a c t i v i t y . In t h i s w a v D I L is i n t e n d e d to b r i d g e t h e g a p in t h e area. o f c o n t r o l s t r u c t u r e s d e f i n i t i o n a n d use, w h i c h r e s u l t s f r o m t h e r e c e n t s t u d i e s o n a b s t r a c t d a t a t y p e s a n d p r o g r a m m i n g t o o l s [3, 8, 31]. T h e r e s e a r c h d o n e w i t h D I E h a s d i s c l o s e d s e v e r a l t o p i c s f o r f u t u r e w o r k . A first t a s k c o n c e r n s t h e i m p l e m e n t a t i o n o f t h e s y s t e m s k e t c h e d in S u b s e c t i o n 3, w h i c h i m p l i e s t h e e x p e r i m e n t a t i o n o f D I L a n d L* c o m p i l e r s a n d , t h e n , t h e d e s i g n o f a C L U - l i k e d a t a t y p e s d e f i n i t i o n s u b s y s t e m . F u r t h e r t o p i c s , like t h e f o r m a l e v a l u a t i o n o f t h e e x p r e s s i v e p o w e r o f D I L , s e m a n t i c s a n d p r o g r a m v e r i f i c a t i o n , a n d c o m p u t a t i o n s v i e w e d as a b s t r a c t d a t a t y p e s h a v e n o t r e c e i v e d a t t e n t i o n so far a n d will c o n s t i t u t e t h e s u b j e c t s o f f u t u r e r e s e a r c h . APPENDIX A run-time model of the L* compiler

We shall sketch here a run-time model describing the way in which the L* compiler translates L* programs into L programs making use of DIL definitions. Such a run-time model will be defined by use of an abstract stack, whose implementation detads will be neglected here. They are in fact quite standard topics of compiler construction, which are freely left to the system designer's individual taste. The operation mode of the L* compiler depends to some extent on the output of the DIL compiler, i.e. on the internal representation of DIL definitions. In the following we shall avoid such a problem by treating DIL definition as if they were interpreted by the L* compiler, i.e. as if their internal representation were essentially identical to the external one (as described in Subsection 3. I ). We shall only suppose that the DIL compiler, in addition to storing the DIL definitions, builds up and incrementally updates a symbol table containing as items couples of the type ((reserved separator), (pointer to the corresponding DIL definition)). The run time model will be described through examples to avoid a too heavy and inessential formal definition. Let us suppose that the L* compiler is given as input the following piece of program: ~hile L> 0 ~)- CaSe

x > 0: a[i]: = x, x < O: a[i]:= - x endcase i:=i-1 od The s~tuation is described in Fig. 3. In such a case the L* compiler goes through the following steps: ( 1) (2)

(3~

(4.1) (4.2)

(4.3)

(4.4) (4,5) (4.6)

CL~);

the word while is recognized as a reserved separator; it is pushed onto the stack, and its corresponding DIL definition is retrieved']-~-pointer to the separator while in the pattern is pushed onto the stack (see Fig. 4); the declaration in the while-loop definition is examined, and one position on the stack is reserved for each variable and the compiler g e n e ~ constant: this is also assigned an arbitrary (but reserved) value (see Fig. 5); the task of the L* compiler is now to explore the input text in order to assign the proper values to the variables on the stack: such values will be pieces of L text; the pattern of the while-loop is scanned by moving its pointer one position ahead; the variable p is then associated with the string "'i ~ e input text, the end of such string being marked by the separator do, which is lbund by moving the pattern pointer one more position ahead; the string "i > 0"" is then stored in a locat~-n accessible from the stack (see Fig. 6): (see step 1) while scanning the input text, the word case is recognized as a reserved separator; it is pushed onto the stack, and its corresponding DIL definition is retrievedTa pointer to the separator case in the pattern ~s pushed onto the stack (see Fig. 7); Isee step 2) the declaration in the select-case definition is examined, and one position on the stack is reserved for each multiple variable and for the comDD[~- generated constant (see Fig. 8); as already explained, the values of multiple variables will be arrays of pieces of L text; since the bounds of such arrays are not known at this phase of L* compiling, dynamic array must be allocated and managed; the value of the constant is assigned arbitrarily, but in a unique way; (see step 3) the pattern of the select-case definition is scanned by moving the pointer one position ahead; as the iterator symbol { is found, space is a l l ~ one-dimensional arrays corresponding to p and a; an iteration index is pushed onto the stack, and initialized to 1; the pointer is then moved ahead, and the text "x > 0"" is associated with p, and stored in the corresponding array (see Fig. 9); by moving the pattern pointer ahead, the text corresponding to a and the interator separator are found; the text is stored in the array corresponding to a, and the pattern pointer is moved back to the beNnning of the iterator, adding 1 to the ~teration index (see Fig. 10); step 4.4 is repeated; as the iterator separator is not found in the input text, the pattern pointer is moved on through t s e e F~g. IlL the end separator endcase is found; this means that the environment of the select-case structure has been completed, and the c o r r e s p o n - d i ~ code can be generated: B

IS

MARCOCOLOMBE3-1"I and GIOVANNIGUIDA while i • 0

D~FINE

I

do

while-loop

i od

ENDDEF

{

while

DEFINE

COMPILER

select-case

stack ENDDEF

Fig. 3. Compiling a sample program segment; initial configuration.

DEFINE while-loop

PATTERN while p do a od

while

+

stack Fig. 4. Step I.

START a DEFINE while-loop P PATTERN while p do a od

while

stack Fig. 5. Step 2.

START

]

a

DEFINE while-loop

P PATTERN while p do a od while

stack Fig. 6. Step 3.

Control definition

DEFINE select-case pATTERS

case I P:a; I ,3 endcas%

I-

START

DEFINE while-loop

PATTERN while p do a od

wh~le

stack Fig. 7. Step 4.1.

_q-72

END

DEFINE select-case

a(MVAR) p(MVAR)

PATTERN

. . . . [p:all ,] end . . . . 5ase

I__

START

t _ _

J

a DEFINE while-loop P

PATTERN while while p do a od

sl~ack Fig. 8. Step 4.2.

interator index END

_U -I

p(MVAR)

case

DEFINE select-case

PATTERN

stack

case ~p:all ,~ endcase

Fig. 9. Step 4.3•

MARCO COLOMBE'~I and Gfov~N',,I GLIDa

20

iterator index END a(MVAR) p(MVAR)

I

X > 0

DEFINE select-case

case PATTERN

stack

case ~p:all ,] endcase

Fig. i0. Step 4.4.

(4.7) the L codefor the select-case structure is generated following the implementation part of the select-case definition; a temporary p o i n t e r ~ o i n t s to the generated code, and the stack is popped (see Fig. 12); d u ~ g e n e r a t i o n , variables and constants m the implementation are replaced by their values; the number of iterations of the implementation iterator is given by the iteration index; (5) the L* compiler returns to the analysis of the while structure; the pattern pointer shows that the L code pointed by temp has to be appended to the value of variable a; after this is done, the compiler goes on in the analysis of a; (6) et ~ i e c e of text "i: = i - 1" is recognized, and the end separator od is encountered; this means that the while-loop structure has been completely analyzed, and the code generation p~aase can start (Fig. 13); (7) the L code for the while-loop structure is generated following the implementation part of the while-loop definition; a temporary pointert e ~ o i n t s to the generated code, and the stack is popped (see Fig. 14); (8) as the input program h a s t e n completely analyzed and the stack is empty, temp points to the complete L translation, and the L* compilation ends. To complete our sketch of DIL run-time model we shall now deal with an example of enrichment. Let us consider the nested loop program presented in Subsection 2.4. The problem here is building the correct L code every time an exit L statement is found in the input program. The control statements exit LI and exit L2 are recognized by the L* compiler since exit is contained in the table of reserved separators. We assume tha--{-the table--gfso contains an indication of the control str-~ture in which the control statement is legal (the loop-exit structure in our case). If the r i g h ~ e of the control structure containing the control statements (i.e. the right environment on the stack) can be found, then the translation is straightforward. This requires only the expansion of the corresponding implementation in the environment of the instance of control structure corresponding to the control statement. Such environment is in general not on top of the stack, as the control statement can be nested in further instances of control structures. To retrieve the proper environment, we have introduced the enrichment tag. When an instance of the loop-exit structure is encountered by the L* compiler, and the corresponding DIL declarations examined, a variable L of t ~ is found. In this case one token after the separator loop is examined, and assigned as value to L. Such a value is then used to mark the environment presently being constructed on the stack (see Fig. 15). When a control statement exit L1 or exit L2 is then encountered in the L* program, the corresponding environment is retrieved through the enrichment tag and used to assign values to the variables and constants in the implementation of the enrichment statement.

i~erator index END a(MVAR) p(MVAR)

DEFINE select-case case

PATTERN

stack

case [p:a~ ,] endcase

--F Fig. 1 l. Step 4.5.

2

Control definmon

t f x < 0 :hen

~[:]:=-x;

~

DEFINE

3 T .~RT

E}~D:

f_.!:;

~nzle-looa

PATTERN white

~ ao

a od

~h~le

slack Fig. 12. Step 4.7.

START

{

if x > O then a[il:=x; if x < O then a[il:=-×; END[

P

l:=

5oto $oto

ENDI

f i;

ENDI fi;

: dummy; i-i;

while

stack DEFINE

while-loop

PATTERN while

p do a od

Fig. 13. Step 6.

STARTI : i f

i > 0 chen

temp

if

x > O then

a[il:=x;

~oto

ENDI f ~ ;

i f x < 0 then a [ i l : = - x ; ~oto ENDI f_~_i; ENDI

: dummy;

i := i - I;

~oto fi

stack Fig. 14. Step 7.

ST.:u-RT[

22

MaRCo COLOMBE'i-TIand GIOVANNI GUIDA

stack Fig. 15. The use of enrichment tags.

In the case where no enrichment TAG is provided, the system retrieves as default the most recent environment corresponding to the control structure associated to the control statement being examined. The last feature of DIL, the where-clause, needs no particular attention, except a straightforward legality control.

REFERENCES 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18, 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31.

Dahl O. J., Dijkstra E. W. and Hoare C. A. R., Structured Programming. Academic Press, New York (1972). Dijkstra E. W., A Discipline o f Programming. Prentice-Hail, Englewood Cliffs, NJ (1976). Guttag J., Abstract data types and the development of data structures. Commun. A C M 20, 396---404 (1977). Wirth N., On the composition of well-structured programs. A C M Comput. Surv. 6, 247-259 (1974). Pamas D. L., On the criteria to be used in decomposing system into modules. Commun. A C M 15, 1053-1058 (1977). Birtwistle G., Dah[ O.-J., Myhrhang B. and Nygaard K., Simula Begin. Student Litteratur, Auerbach (1973). Lampson B., Homing J., London R., Mictchell J. and Papek G., Report on the programming language EUCLID. A C M Sigplan Notices 12(2), 1-79 (1977). Liskov B., Snyder A., Atkinson R. and Schaffert C , Abstraction mechanisms in CLU. Commun. ACM, 20, 564-576 (1977). Solutseff N. and Yezerski A., A survey of extensible programming languages. A. Rev. autom. Pragm. 7, 267-307 (1974). Weigbreit B., The treatment of data types in ELI. Commun. ACM, 17, 251-264 (1974). Fridman F. J., Ho[Ioway G. H., Minsky N. H. and Stein J., Abstract FOR-loops over several aggregates. Information Processing Lett. 8, 204-206 (1979). Ledgard H. F. and Marcotty M., A genealogy of control structures. Commun. ACM, 18, 629-639 (1975). Shaw M., Wulf W. and London R., Abstraction and verification in Alphard: defining and specifying iteration and generators. Commun. ACM, 20, 553-564 (1977). Leavenworth B. M , Syntax macros and extended translation. Commun. ACM, 9, 790-793 (1966). Standish T. A., Extensibility in programming language design. Proc. AFIPS 44, 287-290 (1975). Nagata H., FORMAL: a language with a macro-oriented extension facility. Comput. Lang. 5, 65-76 (1980). Waite W. M., The mobile programming system: STAGE 2. Commun. A C M 13, 415-421 (1970). Hoare C. A. R., An axiomatic basis for computer programming. Commun. A C M 12, 576--580 (1969). Colombetti M. and Guida G., A framework for control abstraction in programming languages. Proc. Informatica 79, Ljubljana, Yugoslavia, 1/107/1-1/107/4 (October 1979). Feldman J. and Giles D., Translator writing systems. Commun. A C M 11, 77-113 (1968). McKeeman W. M., Homing J. J. and Worthman D. B., A Compiler Generator. Prentice Hall, Englewood-Cliffs, NJ (1970). Mack Adams J., A general, verificable, iterative control structure. IEEE Trans. Software Engng SE-3, 144-149 (1977). Dijkstra E. W., Guarded commands, nondeterminacy and formal derivation of programs. Commun. A C M 18, 453--457 (1975). Lindstrom G., Backtracking in a generalized control setting. A CM Trans. Programming Languages and Systems 1, 8-26 (1979). Golomb J. W. and Baumert L. D., Backtrack programming. J. A C M 12, 516-524 (1965). Montangero C., Pacini G. and Turini F., Two-level control structure for nondeterministic programming. Commun. A CM 20, 725-730 (1977). Brinch Hansen P., The programming language concurrent Pascal. IEEE Trans. Software Engng SE-I, 199-207 (1975). Wirth N., Modula: a language for modular multiprogramming. Software-Practice and Experience 7, 3-35 (1977). Wirth N., Toward a discipline of real-time programming. Commun. A C M 20, 577-583 (1977). Cohen J., Non-deterministic algorithms. A C M Comput. Surv. 11, 79-94 (1979). Pratt T. W., Control computations and the design of loop control structures. IEEE Trans. Software Engng SE-4, 81-89 (1978).

Control defimtlon About the Author--MARco COLOMBE'vrl was born m Milano, Italy, on April 16, 1951. He attended the Politecnico di Milano from 1970 to 1976, receiving the degree in Electronics Engineering. From 1977 Mr Colombetti has been working at the Department of Electronics and the Artificial Intelligence Project of the Politecnico di Milano as a -esearcher. His research interests span from the design of tools for advanced structured programming to the development of artificial intelligence systems for natural language understanding and knowledge representation. Recently he became also involved in the study of human cognitive processes. He is the author of various scientific publications. About the Author--G~ovh,-,.~I GU1DA was born in Novara, Italy, on December 10th, 1951. He received the Dr Ing degree in Electronic Engineering (major in Computer Science) from the Politecnico di Milano in 1975. Dr Guida has been researcher at the Politecnico di Milano, Department of Electronics, Artificial Intelligence Project, since 1976. Since 1979 he has been associate professor of computer science at the University of Udine. He has devoted most of his research activity to artificial intelligence and, in particular, to problem solving, program synthesis, natural language processing, and expert systems. His research interests also include formal languages, programming methodologies, and computer languages. He is the author of several scientific publications.

23