• Ingen resultater fundet

Interpreter for Building Extensible Applications

4.2 The Central Idea

4.2.1 Types of Extensions Desired

Say we have an application structured as follows:

GraphicalEditor:

(# Display :

(# screen : @screenDesc; (* instance variables *)

Window : (* nested patterns *)

(# contents : @bitmap; (* instance variables *) refresh :< (# do ... contents ...

screen ...; INNER #) (* methods *) GraphicalObject : (# ... #) (* nested patterns *) Circle : GraphicalObject (# ... #)

do ... contents ... screen ...; INNER

#)

cw : ^Window;

do ... new(Window) -> cw[]; cw; ...

#)

d : ^Display;

do ... new(Display) -> d[]; d; ...

#)

Here the patternGraphicalEditoris meant to be an editor for editing gures. The pattern

Display is nested within it. It has instance variables: screen which describes the screen it should display on, and cw which stores a reference to the current window. The pattern

Window which describes a typical window is nested within Display. It has an instance variable: contents. It also has a virtual pattern: refresh. The patternGraphicalObject

is an abstract description of all graphical objects to be handled by this application;Circle is a concrete sub-pattern of it. Both their declarations are nested within Window.

The GraphicalEditorpattern has a do-part; this implies that it may be executed. Exe-cuting it will create an instance of it and run the do-part. The do-part will typically setup the overall user interface. During this process it will create an instance of the Display pattern and execute it; this is shown in the listing. The Display will create one or more

Windowobjects and manage them; it will provide the main interaction with the user. The listing shows it creating and executing an instance of Window. The Window pattern has been designed so that executing it will initialize it. Its do-part is shown to have references to variables: contents and screen. At times, the Display may also call refresh on the

Window (not shown in the listing). The INNER imperative appearing in the do-parts of

Windowand refresh is explained later.

The above code is block structured and lexically scoped. It is impossible to create, for example, a Window from outside a Display. This adds to the clarity of the code by expressing existential dependencies: a window cannot exist without a display. The many other benets of block structure and lexical scoping, which are not the focus of this paper, are also enjoyed by this application.

There are a number of ways to extend this application; for example, one may want to introduce colored windows, or change the way circles are highlighted when they are dragged. One way to introduce colored windows, is to dene a new pattern describing colored windows, say ColorWindow, as a sub-pattern of Window. Place this denition in the same block asWindow. Replace all applied occurrences ofWindowbyColorWindow, and then recompile the resulting source.1 The following listing shows this extended program:

GraphicalEditor:

(# Display :

(# screen : @screenDesc; (* instance variables *)

Window : (* nested patterns *)

(# contents : @bitmap; (* instance variables *) refresh :< (# do ... contents ...

screen ...; INNER #) (* methods *) GraphicalObject : (# ... #) (* nested patterns *) Circle : GraphicalObject (# ... #)

do ... contents ... screen ...; INNER

#)

ColorWindow : Window (# color : @colorDesc;

refresh ::< (# do ...contents... screen ... color; INNER #) do ... contents ... screen ... color ...; INNER

#)

cw : ^Window;

do ... new(ColorWindow) -> cw[]; cw; ...

#)

do ... new(Display) -> d[]; d; ...

#)

1One could also have declaredWindowandDisplayas virtuals in the original implementation, thus allowing the window extension to be installed without modifyingthe originaldenition. I have deliberately not chosen this approach so that I can demonstrate the use of the interpreter.

In this case, the Display pattern is shown creating and executing an instance of a

ColorWindow. When an instance of ColorWindow is executed, the do-part of Window is executed until the INNER imperative is encountered. Then, control is transferred to the do-part of ColorWindow. The INNER here is like a no-operation, as there are no further specializations. Similar comments apply to the do-part of therefresh pattern.

The goal of this section has been to demonstrate the types of extensions (e.g.ColorWindow) the interpreter has been designed to support.

4.2.2 Using the Interpreter

The interpreter has been designed as a tool to support the above types of extensions. It is meant to be used to write an application, such as the editor above, with a well-dened set of open points | points that are open for extension. Such an open or tailorable application has built into it, the ability to load the source code for extensions such as

ColorWindow and extend itself accordingly. Furthermore, these extensions may happen dynamically and without any recompilation or relinking of the original application.

In order to enable the editor to accept extensions such asColorWindow, it must be rewritten as follows:

GraphicalEditor:

(# Display :

(# screen : @screenDesc; (* instance variables *)

Window : (* nested patterns *)

(# contents : @bitmap; (* instance variables *) refresh :< (# do ... contents ...

screen ...; INNER #) (* methods *) GraphicalObject : (# ... #) (* nested patterns *) Circle : GraphicalObject (# ... #)

do ... contents ... screen ...; INNER

#)

cw : ^Window;

windowExtension: ^text;

WindowX : ##Window;

extendWindow : (#

do inputFromUser -> windowExtension; (* textual input *) (Window##, windowExtension) -> interpret -> WindowX##;

#)

do ... new(WindowX) -> cw[]; cw; ...

#)

d : ^Display;

... new(Display) -> d[]; ... d.extendWindow ...

#)

This listing diers from the original in that it declares a pattern variable2 WindowX (a variable which holds pattern closures3, as opposed to instances of patterns). The pattern variableWindowXis declared of typeWindow. This means that it can hold theWindowpattern or any sub-pattern of it. Also, references to the pattern Window have been replaced by references to the pattern variableWindowX | thus allowing the pattern denotable by these references to be dynamically changed.

A pattern called extendWindow has been declared within Display; calling it on a dis-play object extends the denition of Window. windowExtension denotes the source code for the new denition; its value can be specied dynamically, as is exemplied by the

inputFromUserimperative. Any source code which denes a sub pattern ofWindowcan be used here; as an example, consider ColorWindowfrom the previous listing:

ColorWindow : Window (# color : @colorDesc;

refresh ::< (# do ...contents... screen ... color; INNER #) do ... contents ... screen ... color ...; INNER

#)

Within extendWindow, the interpreter is invoked with this source code and a closure for the Window pattern (i.e. Window##). By passing the pattern closure for Window, the user indicates that she/he would like the extension to be processed in the same lexical scope as Window. Thus, the interpreter processes the extension as if it appeared withinDisplay. This scope information is used by the interpreter to determine which name references (e.g.Window,contents,screen) in the interpreted code are legal, as well as how to resolve them (i.e. map a reference to a memory address).

An intuitive way to explain the arguments with which the interpreter should be invoked is: call the interpreter with the source code of the extension pattern and the pattern closure of the pattern to be extended. The interpreter returns the pattern closure of the extension pattern.4

2Pattern variables are a standard feature of Beta

3These are called structure values in Beta; pattern closures have a more intuitive connotation for this discussion and hence are used here

4This is a natural extension of the standard way in which pattern closures are created and assigned in Beta: the statementWindow## -> WindowX##, constructs a pattern closure for a pre-dened Window

Returning to the editor example, when this application creates a new display object and calls extendWindow on it, the display gets extended with a new user-dened denition of window, such as ColorWindow. In other words, the denition of an extension to Window gets loaded into the application without any need for recompilation, relinking, or a restart of the original application.

4.2.3 The contribution

This paper discusses the implementation of an interpreter designed to accomplish the above-described tasks. What makes this interesting are a number of things:

1. The ability to handle extensions of patterns dened in any lexical scope | even nested ones. Lexical scoping rules are not violated by this; the extension gets access to an environment as per the lexical scoping rules. Lexical scope is provided as an argument to the interpreter. The interpreter can be used to handle extensions of any pattern in any scope e.g.Circlein Window.

2. Type checking of the extensions and preservation of the type soundness of the orig-inal program.

3. The packaging and return of the interpreted pattern as a pattern closure. This results in a seamless interface between the interpreter and the compiled application invoking the interpreter.

4. An interesting and unusual blend of compiled and interpreted code.