Draft PhD Submission

Tony Pritchett thought that the concepts used in the GRAM system and the various implementations and usage could be the basis for a PhD Submission. For reasons unknown, the project was not completed. The handwritten notes were scanned by Kate Sullivan from the Tony Pritchett Archives in November 2020

1. STATEMENT OF PROBLEM

1.1 HISTORICAL CONTEXT

The Open University has for some time made use of animated graphics, generated by computer, in its television programmes. The justification for using computer graphics, instead of artist-drawn and animated diagrams, lies with subjects which would be tedious or impossible to animate by normal techniques, for example those with a mathematical content or involving perspective projections of three-dimensional space.

In the wider context, computer animation, as the technique has become known, has been used for some years in the teaching of mathematics and other sciences, especially Physics, and mainly in the United States. There have also been attempts to apply it to animated films generally with the aim of it being quicker and cheaper than doing it by hand. But this has not yet caught on, probably because most of the systems devised turned out to be neither quicker nor cheaper than conventional methods; and even if they were, could not compete in quality.

1.2 THE PROBLEM IN THE OPEN UNIVERSITY CONTEXT

In order to produce animation by computer, one needs access to specialised hardware for plotting computer graphics on to cine film. In Britain, this was available only on the Atlas Computer Laboratory, and more recently in the University of London Computer Centre (see section ...), in the form of microfilm recorders. Also it was necessary to use the software provided by these establishments for driving their microfilm recorders. This software is by use of a Fortran Package, and although very general-purpose, is not ideal for the rapid production of animated films (this is discussed in more detail in the next section. Add to this the fact that the computers at both the establishments mentioned are only usable in batch mode, and one has a rather long-winded method of producing computer animation which frequently misses television studio deadlines.

The principle aim of this project was to provide the Open university with a more ergonomic method of producing computer-animated films. Other fairly advanced computer-animated films do exist (which are discussed in section ...) but they are not available in Britain, and are mostly not portable.

1.3 BROADER ISSUES

The Open University also has a considerable requirement for the production of still graphics for the course units (books). There is thought to be a role that computer graphics could play in this context, particularly if interactive, in providing an electronic drawing board facility. It was therefore deemed that the proposed system should be sufficiently general-purpose to serve this application as well as animation.

The requirement envisaged that a new facility should be usable not only by computer programmers but by academics from all parts of the University.

1.4 ORDEAL BY COMPUTER PACKAGE: THE TRADITIONAL WAY OF DOING COMPUTER ANIMATION

All computer animation produced for the Open University has been done using software of the subroutine package type (eg GROATS, DIMFILM) as described in more detail in section ... These packages normally provide a basic set of subroutines to drive a graphic output device (such as drawing lines and and advancing the film) within the framework of a popular scientific programming language, usually FORTRAN. Despit the fact that this method is extremely flexible, giving the user all the power of a high-level programming language, it has the following disadvantages for the ergonomic production of animation.

  1. It presupposes that the user is able to program a computer in the conventional way and in particular with the language in question (eg FORTRAN, ALGOL or BASIC). Thus in order tp produce a piece of animation which does not even contain any advance mathematics, like simply moving objects around the screen, requires the user to think in terms of algorithms.
  2. A one-off program must be written for each new film sequence, which must be debugged and finally given just one (successful) production run. High level languages such as FORTRAN are just not intended to be used in this way. The long-winded cycle of debugging (connecting the source text, compiling, loading, running the whole program and trying to work out what went wrong, particularly on a batch computer) is only tolerable when the finished program is intended to be run an indefinite number of times with different input data.
  3. Then there is the sequencing problem. This is best explained by a FORTRAN example, let us take the very simple case of a circle expanding from radius SMALL to radius LARGE over NF frames. Suppose also we already have a subroutine for drawing a circle:

          SUBROUTINE CIRCLE(XCENT,YCENT,RADIUS)
    

    Given that SUBROUTINE ADVFLM advances the film to a new frame, then the animation could be set up as a DO loop as follows:

          DR=(LARGE-SMALL)/(NF-1)
          DO 10 I=1,NF
          RADIUS=SMALL+DR*(I-1)
          CALL CIRCLE(X,Y,RADIUS)
      10  CALL ADVFLM
    

    This is a very simple example of an animation loop. More complex pictures require several animation loops. Also, film sequences of the type required by the Open University normally contain many sub-sequences within them,each one requiring a separate DO loop, which must contain everything that appears on the screen whether actively being animated or not.

    Consider also the situation, which often occurs, of animation movements which take place concurrently and independently of each other, such as a movement starting before another has finished. the simple FORTRAN loop structure clearly cannot cope with this situation, such overlapping (as opposed to nested) loops are impossible. Finally, there is the time it takes to produce a full working animation sequence.

  4. The longer and more complex a sequence is, the longer and more complex is the monolithic program necessary to generate it, requiring a large number of loops as described above. The time necessary to debug a program tends to increase in proportion to the square of its size. The reason for this can be deduced from the following intuitive observation:

    The potential number of errors (e) is proportional to the size of the program (s). Also the time taken to locate a particular error (t) is also approximately proportional to s. Therefore time T to locate all the errors in the program must be proportional to s2.

2. REVIEW OF OTHER SYSTEMS (in preparation)

3. GENERAL APPROACH, ESSENTIAL FEATURES, ADOPTED SOFTWARE STRATEGY (22 pages)

3.1 GENERAL APPROACH

3.1.1 Interactivity

Since the main aim of this project was to make the production of computer animation faster and easier, as well as making computer graphics more accessible for graphic designers and academics, the most important design constraint which was understood right from the start was that the proposed system should be interactive. That is to say, a user should be able to interact directly with the system through a graphic display terminal.

3.1.2 Direct Visual Output

Another aim was to achieve output from the system direct into video format instead of on film. This would not only bring the process closer to the television studio (if not actually into the studio), but also enable one to view results immediately without having to wait for film processing. However, this turned out to be a technically difficult thing to achieve, and financially outside the scope of this project. Therefore, it was decided to opt for film output via microfilm recorders for the moment, independent as possible, to allow for changes in the output medium.

3.1.3 Difference of Requirements between Animation and CAD

At first glance, computer animation appears to be an application of Computer-Aided Design (CAD), and therefore it ought to be possible to apply standard CAD techniques to it, or even adopt existing CAD packages. This is not the case, due to the following fundamental differences:

1. Although both animation and CAD start with the process of designing static images, the principal process of animation is the dynamic changing of these images with time, whereas CAD begins and ends with a static design. CAD packages do not normally include facilities for incrementally changing images in order to create an illusion of movement. A possible exception to that is the facility sometimes included in the more sophisticated packages for real-time rotation of three-dimensional images on a refresh display (see refs). However, the object in this case is merely to enable the designer to see his design more clearly; it is not possible that the rotation itself can be remembered by the system and become part of the design. In animation, on the other hand, the composing and storage of sometimes quite complex sequences of dynamic changes to images are an important part of the design process, and must therefore be catered for in the data structure.

2. In most applications of CAD the data structure must be capable of representing more than just images composed of sets of coordinates, especially where any mathematical modelling takes place as part of the continuous evaluation of a design. For example, in the case of computer-aided electronic circuit design, the input/output relationship of individual components and their basic connectivity must be represented somehow by the data structure, in order that mathematical simulation can take place. In fact the underlying semantics of a circuit is the object of the design process, usually employing quite complex data structures, (e.g. ring structures ref ?). The images representing these structures on a graphic display are a secondary matter.

With animation, however, the images themselves are the end product of the design process, and do not require any structure other than that which is visible. A data structure suitable for images by themselves can be much simpler than those necessary for most CAD applications.

The objection can immediately be raised that this last statement certainly holds for entertainment and simple diagrammatic animation, but what about the bulk of computer animation required by the Open University, does this not involve mathematical modelling? The answer to this is: yes of course, but quite apart from attempting to provide a clever data structure which is flexible enough to model virtually anything, experience has shown up a subtle difference between the animation and CAD situations. In the latter, one uses an interactive systems to manipulate the structure of a model and view the result, since one is seeking a structure which was unknown at the outset, this is the design process applied to the model. In producing an animated film sequence of a model, however, one is merely trying to show how a particular model behaves. In most cases the model itself has already been designed, and one is seeking the best visual representation of it; this is the design process applied to the visual image of the model. Thus an animation system needs a data structure which is appropriate for composing and animating images, even if some of the data describing the images and their movement are derived from a modelling program, which could be an entirely separate entity.

A fair example of the above situation can be seen in a piece of animation commissioned quite early in the project. The film sequence involved a diagram of satellites orbiting the earth, and how to position a satellite above a position on the earth's surface (ie a synchronous satellite) and keep it there (see ..). Simulating the motion of the satellite (which could be described as modelling Keppler's laws of motion) proved to be a comparatively simple matter. The parameters needed to give various orbital radii, etc were easily determinable by hand. A small piece of special-purpose program was written which simply handed over a pair of coordinates of the satellite's position for each successive frame. What consumed far more time and trouble was getting the actual pictures right e.g. the images representing the earth and the satellite, the right relative sizes to give the most pleasing effect, zooms, etc happening in the right places and many other graphic and filmic details.

3.2 ESSENTIAL FEATURES

3.2.1 Interactive

This is most important for speed and ease of use (see criticism of both systems). One ought to be able to issue commands at a keyboard, and see their effect immediately on a graphical VDU. There should also be some means of graphical communication for the user via an input device such as a tablet, light-pen or joy stick.

3.2.2 Easy To Use

The potential users of the system are academics and television production staff who may be unfamiliar with the usual computer programming languages.Therefore it should have a command language with a very simple syntax, and wherever appropriate, a menu type of command system (best for graphic editing etc).

3.2.3 Multi-Stage Process

The traditional way of producing computer animation using a subroutine package on a batch system is based on the principle that a complete film sequence is generated (just once) by a single, monolithic program, which does everything in one run on the computer. The difficulties experienced with this method are explained in detail later. It is maintained that a more sensible approach would be to construct an animated sequence in a number of simpler, separate steps.

  1. Defining the images which are to be animated, ie graphic input and editing
  2. Defining the relationship between the images to form complete pictures. The end result is a hierarchical structure of sub-pictures.
  3. Scripting, ie composing a sequence of movements to all or the sub-picture structure.
  4. Outputting the finalised sequence onto the appropriate recording medium (film or video tape).

The first three steps should be interactive, the result of any modification being immediately checkable for errors. By the end of step(iii), the user would be confident that the sequence is correct (by interactively inspecting sample frames if real-time playback is available. Step (iv) can then be an automatic batch-type process.

3.2.4 General Algorithmic Ability

The steps in the process described above seem to assume that all images are hand-constructed, movements belonging to a standard set (eg zoom, rotate, lateral movement etc) and sequencing follows a user=composed script. This is usually sufficient for many kinds of animation but in the Open University context it must be possible for the user to incorporate mathematical generative components which are peculiar to the subject in hand (eg mathematical modelling, moving graphs etc). So either the system must have a general scientific language built into it and accessible at user level, or there must be a practical way of interfacing programs written in other languages, possible as a separate step in the multi-stage process.

3.2.5 Open Ended

There are dangers in designing an ideal animation system which has a fixed set of built-in understandable features. In practice, one is continually coming across the need for new facilities, which are not in the subject specific category covered by 3.2.4. The underlying structure of the system should therefore be designed in an open-ended manner so as to be easily extendable, ideally even by a knowledgeable user.

3.2.6 Good for All Graphic Data Types

The commonest way of representing graphic data digitally is as strings of number pairs representing the x and y Cartesian coordinates of the end points of straight lines. But it is not the only form of graphic data one is likely to want to handle; for instance coordinate triplets for three-dimensional figures or run-encoded scanned images (see section ?). Therefore it is unwise to design a system of graphical data storage assuming only one kind of representation.

3.2.7 General Graphics Base

The lower levels of the system should not be specific to animation, and should form a basic graphics utility with which to construct other kinds of graphic systems, CAD for instance (see Section on Broader Issues), without having to start from scratch.

3.2.8 Portable

The system should not be designed specifically for any particular hardware configuration, as far as either the processor or graphic input and output devices are concerned.

At the start of this project it was not certain what hardware would be ultimately available or how soon existing hardware would be replaced by something better (eg the SD4020 microfilm recorder at the Atlas Computing Laboratory, which was the best available film plotter at the time, was nearing the end of its life). There was also problems problems with regard to processor hardware (HP2000 versus Nova).

For portability, as much of the system as possible should be written either in a common high-level language, or possibly one of a class of systems languages which are designed to be easily implemented on other machines.

3.3 ADOPTED SOFTWARE STRATEGY

Having opted for a multi-stage process, it seems that one could simply write separate special-purpose programs to handle each of the four stages. This concept can be represented diagrammatically by the following system charts:

# STAGE I DEFINING FIGURES GRAPHIC EDITING PROGRAM Terminal X-Y Display Graphic Input Device STAGE II DEFINING PICTURES PICTURE COMPOSING PROGRAM Terminal X-Y Display Graphic Input Device STAGE III SCRIPTING SCRIPT COMPOSING PROGRAM Terminal X-Y Display Graphic Input Device STAGE IV PRODUCTION PRODUCTION PROGRAM Film or TV Output Hardware FIGURE LIBRARY PICTURE DEFINITIONS SCRIPTS

Four Stages

However, it appeared on further analysis that the four stages just described shared quite a few common elements with each other, which could be classified into the following six main functions:

  1. Alphanumeric Input from terminal, in the form of a free format command analyser
  2. Alphanumeric Output to terminal, for error and other messages
  3. Graphic Input using lightpen, joy stick or tablet cursor. This is the more important form of input for interactive graphics, particularly if a menu system of control is preferred to keyboard commands.
  4. Graphic Processing: performing the coordinate transformations on the raw images. This is the guts of the graphics system, where most of the numerical computing happens.
  5. Graphic Output in the form required for interactive checking, ie storage tube or refresh X-Y graphics.
  6. Data Management to handle dynamic storage of scripts, hierarchical lists of pictures and figures, etc.

It turns out that all stages except the fourth require all six functions to some extent. Alphanumeric input/output would be the basic method of communicating with the system. Graphic input is most important for stages I and II, for defining figures and their spatial relationships with each other in composite pictures.

Stage III could probably make do with a purely alphanumeric method of scripting, but graphic cursor input provides the opportunity to design a highly ergonomic graphical scripting technique (see ...).

Graphical output is required by the first three interactive stages even if it is in a simple non-colour and not scanned-in form. Real-time movement would be desirable for the scripting stage but may be prevented by the available hardware (eg storage tube displays). Stage IV graphic output to film or video is likely to be driving quite different and more sophisticated hardware, therefore must be regarded as a separate entity.

Graphic processing is required by all four stages, although for digitising and editing figures (stage I) only a subset of the graphic processes are likely to be useful (for example, for re-positioning or re-scaling a figure).

Storage management is also required by all stages, since they all have to access the figure, picture and script data.

Stage IV, Production, which does not need any user interaction and therefore could take place in batch mode, does not need any user interaction and therefore could take place in batch mode, does not have much use for alphanumeric input/output except to initiate the process, report a malfunction and perhaps allow the user to monitor progress. Graphic input is not needed and graphic output is likely to be different from that of the other stages. Graphic processing and storage management are important for the production stage, and therefore ought to be as efficient as possible, since most of the bulk computing is done at that stage.

ALPHANUMERIC INPUT ALPHANUMERIC OUTPUT STORAGE MANAGER CENTRAL CORE PROGRAMS GRAPHIC INPUT GRAPHIC PROCESSOR GRAPHIC OUTPUT 1 FIGURE DIGITISER & MONITOR 2 PICTURE COMPUTER 3 SCRIPT COMPOSER 4 PRODUCER

Architecture

So we can plan to have six program modules which are common to most stages of the animation process. Each stage conducts the particular business of that stage using the other modules in its own way. The most obvious way of structuring the system is to have that special module as the main program of each stage, calling the other modules as suites of subroutines. In diagram form, the system structure as conceived so far could be expressed as shown above.

STAGE I
FIGURE DIGITISING
AND EDITING
STAGE II
DEFINING
PICTURES
STAGE III
SCRIPTING
STAGE IV
PRODUCTION
ALPHANUMERIC
INPUT
YES YESYESNOT IMPORTANT
ALPHANUMERIC
OUTPUT
YES YESYESNOT IMPORTANT
GRAPHIC
INPUT
YES YESYESNO
GRAPHIC
PROCESSING
SUBSET ONLY
NEEDED
YESYESYES
GRAPHIC
OUTPUT
YES YESYESSPECIAL VERSION
FOR FINAL OUTPUT
STORAGE
MANAGEMENT
YES YESYESYES BUT
RETRIEVAL ONLY
NEEDED

3.4 THE INTERPRETER/MACRO-PROCESSOR SOLUTION

An alternative approach to the conventional one of implementing each central core module as a separate program written in a high-level compiled language is to have an interpretive language as the basis of the control core, with the functions particular to each stage being written in terms of this language. The immediate advantages of this approach are:

  1. An interpreter requires very little compilation and no separate load phase, in contrast to the compiler high level languages; it therefore speeds up program development. One of the original criteria was that the system should be open-ended and easy to expand.
  2. The stored pseudo-code of an interpreter is normally much more compact than the equivalent compiled machine code, provided it is the sort of interpreter that obeys no economical pseudo-code (consisting mainly of pointers) and does not interpret directly from source text. Of course, this advantage is lost if the interpreting program itself is so long that it uses up the space so saved.
  3. An interpreter is an aid to portability, provided its primitive functions are very well defined. Even the most standardised of existing high-level languages, ANSI FORTRAN IV, is not so standard that a program written in it can be implemented anywhere without some annoying alterations. By having only the interpreter itself written in FORTRAN, the quantity of conversion work can be considerably reduced.

On the debit side, interpreters are slow in execution commonly more than nten times slower than the equivalent compiled language. But in stages I to III of the animation process which are interactive, and because of the small amount of processing needed per user command, this slowness is not normally noticeable compared with the reaction time of the user. It is at the production stage (IV) where speed is important, because very large amounts of coordinate data are being processed without any user-interaction. At this stage, the bulk of the computing (probably 95%) will be handled by the graphic processor and graphic output modules. These two modules should therefore not be implemented in the interpretive language, but in the most efficient compiled code possible, while being controlled by the interpreter from a higher level.

There are certain features which can be included in an interpretive language which could make it a more attractive solution for this application:

3.4.1 Interpreter as Command Analyser

The interpretive languages APL, POP-2 and some implementations of BASIC have an interesting feature. Any valid statement or expression in the language when typed on the input terminal by itself, is executed immediately. (This is not strictly true of BASIC as some of its statements are meaningless in isolation, so the number of statements which can be used in this way is limited). In APL and BASIC, if whatever is typed is something like an arithmetic expression or a function call, the result is printed at the terminal; (with POP-2 one also has to terminate the call or expression with the print arrow ⇒ to print anything, but this is unimportant as far as the principle is concerned).

The principle involved is the following. Programs in most high-level languages have to have a main program segment which is entered first when a program is run, the root of the tree from which all other program segments are called as subroutines. The keyboard command to run a program is often something like RUN followed by the program name, which is an operating system command to load the binary version of the program from long-term storage and start executing it from the beginning of the main segment.

But languages like APL and POP-2 are different: there is no explicitly main stored segment of program. All segments of program currently loaded or present in store, are subroutines or functions, any of which may be called by the user typing a line of program at the terminal. The input line itself performs the function of the main program segment, even though it may consist only of a call to a single subroutine. Of course, typing a line of program and having it execute immediately is only really feasible with an interpreted language. (Also, the interpreter must first have been entered from the operating system by a RUN command or similar).

Another feature of the APL/POP-2 type of language is the use of a push-down stack from which subroutines and functions obtain their arguments and on to which they put their results. Whatever remains on the stack after the input line has been completely executed is what is printed out on the terminal (automatically with APL; by adding a ⇒ at the end of the input line with POP-2).

APL Example

Input:  (2+3) × 4
Output: 20

POP-2 Example
Input:  (2+3) × 4;1024;-14/7 ⇒
Output: **20,1024,-2

The point of all this that with this kind of interpretive language, the interpreter itseld can be used as a command analyser, provided the syntax is suitable for simple keyboard commands. Also, provided that the interpreter can handle alphanumeric string data (as well as numbers) and output strings of alphanumeric text, then alphanumeric output for messages etc can be handled by the interpreter. Hence we can absorb two of the input/output modules of the overall system into a suitable designed interpreter.

3.4.2 Interpreter as Data Manager

Subroutines which call each other in a hierarchical fashion are similar in concept to hierarchies of pictures and sub-pictures. Therefore, if one can use the same mechanism to look after both subroutines and sub-pictures, yet another module can be brought within the structure of the interpreter. The detail of how this can be done will be left to a later section.

3.4.3 The Question of the Graphics Modules

Having brought three of the six peripheral modules within the unifying concept of an interpretive language, what about the other three? These are all concerned with graphics:

  1. Graphic Input is very hard to standardise in view of the widely different modes of operation of graphic input hardware, ranging from joy-sticks to lightpens. It is therefore best regarded as a separate piece of program tailored to the particular hardware, but nevertheless providing the interpreter with an auxiliary input including a number-pair representing the cursor coordinate.
  2. Graphic Processing could be implemented entirely in terms of an interpretive language, as long as the interpreter could handle floating point numbers, but it would be very slow in execution. As already pointed out, graphic processing involves quite complex floating point arithmetic on a large number (often several thousands) of coordinates, and the difference of whether it takes only seconds or the same number of minutes to generate a frame is important. Most of the simpler graphic functions (eg Move, Scael, Rotate) involve matrix multiplication with arrays of N coordinate pairs being treated as 2 by N matrices. APL is an interpretive language which is designed to handle matrices efficiently since all its operators are potentially matrix operators and all operands are potentially matrices.

    APL itself is not suitable for use in the animation context (for other reasons), but the principle whereby a single interpreted instruction causes a fast operation on a whole set of coordinates is a good solution. So the graphic processing could be brought within the scope of the interpreter by the addition of a set of powerful interpreter instructions which perform graphic operations on whole arrays of coordinates at a time.

    The above concept was implemented in an early version of the system, but ran into serious space difficulties on the minicomputer available. It turned out that the memory occupied by the compiled code of the special graphics instructions increased the size of the interpreter to such an extent that the remaining memory space available for interpreter pseudo-code and coordinate arrays became far too restricted. The solutions of getting more memory or moving to a larger machine were not available, and anyway would have gone against one of the project's aims of an animation system implementable on an average-sized mini computer. However, the machine's operating system permitted overlaying (or paging) parts of a program from disc, thereby achieving a larger apparent amount of program for a given memory area. This required the program to be divided into two (or more) overlay segments which could not communicate directly with each other. So the compiled subroutines which execute the special graphic instructions of the interpreter were put into an overlay segment which overlays most of the rest of the interpreter. The consequence of that was that each time the interpreter came to execute a graphic instruction it had to page the graphic overlay in from disc and page back the rest of the interpreter afterwards, which was time-consuming and frustrated the speed gained by having special graphic instructions.

    The solution to this problem was to contrive to do as much picture-processing work as possible, such as a complete frame, entirely within the graphic overlay segment without returning tp the main interpreter. This amounts to the graphic processor overlay becoming a separate entity containing its own simple interpreter for obeying only graphic instructions, and would look to the main interpreter like a black box to which it handed over a set of graphic instructions and coordinate data suitably transformed. Hence the graphic processor came to be separated from the interpreter.

  3. Graphic Output, like graphic input, is hardware dependent. Also, most of the coordinate data it receives is likely to have just been transformed by the graphic processor, therefore it makes sense if it is driven directly from the graphic processor. However, if the production-stage version of the Graphic Output module includes some shading, it is likely to be quite large and to require its own overlay. or even a totally separate program fed from the graphic processor via a disc or magnetic tape buffer.

GRAPHIC INPUT Input Stream CONTROLLING ALPHANUMERIC TERMINAL MAIN INTERPRETER (GRAM) Output Stream GRAPHIC PROCESSOR Processed pictures fed back to Interpreter GRAPHIC OUTPUT

Schematic of System Showing Principle Program Modules

The diagram above attempts to convey the concept of the system consisting of two main program modules (and two peripheral ones) which are as much as possible independent of each other, communicating by passing data to each other in a predefined format. It has already been decided how the different modules can execute sequentially by paging in overlays from disc. Another, more interesting idea, is to have a pipeline of physical processors, one for each module, operating in parallel. It would be fast, and is unfortunately outside the physical resources of the present project, but the suggested system design allows for it at as a future development.

Having established the need for an interpretive language, the next question was whether to design one specially for the purpose, or to use an existing language. The matter was settled quite easily in favour of a specially designed one for the following reasons:

  1. At the time, no suitable language of the category needed (eg APL, POP-2) had been implemented on the computer available to the project (a Data General Nova). It possessed a BASIC interpreter, but this could not be used as a command language (see ..).
  2. Any existing interpretive language would have had to have been so thoroughly modified and extended in order to handle the linkage with the graphic processor and other peripheral modules efficiently, that one might as well have started from scratch.

The interpretive language is called GRAM, which stands for GRAphic Macroprocessor. This needs a little explaining. The acronym was aimed very early in the project when it was thought that the interpreter would also encompass the graphic processing. The term Macroprocessor is due to the fact that the interpreter was based on macroprocessor concepts (Strachey's GPM in particular). The distinction between a macroprocessor and an interpretive language is fairly vague in any case. It could be said that a macroprocessor is a special case of an interpretive language which is designed for pre-processing the source text of another language. In that sense GRAM is not strictly a macroprocessor, but the name stuck.

4. MACRO-PROCESSORS

4.1 Introduction

The concept of the macro-processor originated in the context of machine-code assemblers, as a means of allowing a programmer to write a single pseudo-instruction with arguments in place of an often-used sequence of machine instructions. The macro-processor expands the pseudo-instructions into the corresponding actual machine instruction sequence, incorporating the pseudo-instruction arguments, prior to entering the assembler proper. However, such a macro-processor is so closely associated with an assembler that the whole is usually termed a macro-assembler.

Several macro-processors, though, have been designed outside any association with an assembler, and can therefore be termed general-purpose. Essentially they are symbol-stream processors, taking an input stream of symbols and converting them to an output stream of symbols by a process of substitution. But when designed with a sufficiently general and recursive substitution process, a macro-processor becomes exceedingly powerful and can be made to behave like a general-purpose algorithmic language, although since the symbol streams are normally strings of characters, processes such as arithmetic can only be done very inefficiently by macro-processors.

4.2 Strachey's GPM

The General Purpose Macrogenerator designed by C Strachey is a very pure and simple form of macro-processor. It is ideals for describing the principles which it holds in common with other designs. It also provided the original inspiration for the design of the GRAM language.

GPM has an input stream and an output stream of symbols. The unitary symbol is a single alphanumeric character. The input stream is merely copied directly to the output stream, except where a macro-call appears in the input stream, which causes a pre-defined substitution to be passed to the output stream.

Macro-calls have the general form:

[macro-name,argument,argument,....]

Example:
[ABC,148,++,ZD-V] is the macro ABC
                  called with three arguments
                  148,  ++, ZD-V
[WXYZ] is the macro WXYZ called with no arguments

Square brackets and commas are special symbols which delimit macro calls and separate arguments respectively. The original version of GPM, for local character-code reasons, used š instead of [ and ; instead of ]. But Strachey himself suggested square brackets as a suitable alternative.

Evaluation of a macro-call means its substitution in the output stream by a symbol string which has been pre-defined to be associated with the macro-name, and termed a macro-definition. This string may contain instances of the special symbol ~ followed by a numerical digit 0-9 which represent dummy arguments. During evaluation, ~1, ~2, ~3 etc are replaced by the first second and third etc actual arguments in the macro-call (~0 refers to the macro-name itself).

For example, if the macro-name ABC has been associated with the macro-definition [ABC~2FH~1*~3B then the macro-call [ABC,**K,/V/,123] in the input stream will generate in the output stream the string XY/V/FH**K*123B.

It is convenient to refer to the symbol string generated by evaluating a macro-call as its value.

The process of evaluating a macro-call to a value is completely general and recursive, in that macro-calls can be embedded in macro definitions and in the argument lists of other calls.

For example, suppose, in addition to ABC defined above, we have XYZ defined to be GM[ABC,J,P,4]~1 then calling [XYZ,[XYZ,#]] will generate the value GMXYFHJ*4BGMXYPFHJ*4B#.

In the case of macro-calls nested within argument strings of macro-calls, the order of evaluation is from the inner most call outwards.

Creating a Macro-Definition is achieved by calling the special macro DEF, which is termed a primitive macro, since instead of causing a substitution, it causes a special process to occur which is built-in to the system.

The format of a call to DEF is as follows:

[DEF, macro-name, macro-definition]

Example
[DEF,THING,**!-BOOF**]
would associate the macro-definition
**!-BOOF**
with the macro-name THING

String Quotes in GPM refer to the special symbols < and >. These have the effect of suppressing the action of any special symbols (including other nested, string quotes) between matching pairs of them; eg they prevent the evaluation of macro calls. All symbols (including special symbols) are simply copied over to the output stream, except that the outermost pair of string quotes are removed.

Example:
AB<;[VT,0,[DEF<~1[PN,2]>],OZ>NK
gets passed over as
AB0[VT,0,[DEF<~1[PN,2]>],OZNK

An example of the importance of string quotes is in the deficiency of macros which contain macro calls within them. The macro-definition in the call to DEF must be enclosed in string quotes in order to prevent its evaluation during the process of definition.

Example:
[DEF,FRED,<BOB~2[BILL,2]~1>]

4.3 Mechanism of Evaluation

In order to understand fully how evaluation takes place and copes with nesting and recursion, it is best to describe the mechanism in more detail with the aid of a conceptual diagram.

INPUT DEVICE Destination Switch Source Switch Input Stream Output Stream MILL OUTPUT DEVICE STACK MACRO STORE

The Mill

The Mill can be thought of as a control box which intercepts special symbols and takes appropriate action.

Initially the switches are set to pass all symbols straight though from the input device to the output device.

Destination Switch Source Switch 2 G N A * O B 3

But when the mill encounters the first [ symbol, it switches the destination from the output device to the stack.

2 G N A * O B 3 [ F R E D , [ J O E B

A begin current call pointer is set to the position of the [.

From henceforth all output from the mill accumulates on the stack. But whenever another [ symbol is encountered, it is replaced on the stack by the current contents of the pointer B, and B is updated to point at this item, thus creating a chain of pointers linking the starts of nested cells.

[ F R E D , . J O E , . A L F , 4 5 ] B

However, when a ] is encountered by the mill, the following actions occur:

[ F R E D , . J O E , . A L F , 4 5 ] B E ~ 1 [ T O M ] A L F
  1. The macro-name defined by the symbols between the pointer B and the first comma , is matched with a name labelling a macro in the macro store
  2. The source of the input stream is switched from the input device to the macro in the macro store.
  3. An end current call pointer E is set to the position of the ] on the stack.

On encountering the dummy argument symbol ~, both it and the following digit symbol n are replaced by the symbols following the nth Comma after pointer B up to the next comma.

[ F R E D , . J O E , . A L F , 4 5 ] 4 5 B E [ T O M ] 9 9 ~1

Any further [ coming from the macro store has exactly the same effect as before, in adding another link to the chain of pointers headed by B. Also, another ] will start a similar chain of pointers headed by E as well as switching the input stream to another stored macro.

[ F R E D , . J O E , . A L F , 4 5 ] 4 5 T O M B E 9 9 4 , 2 * T O M

Please note that the symbology of the diagram does not imply that macros are deleted from the macro store as they are fed to the mill, but are merely copied.

When a macro in the store is exhausted, another set of actions take place:

  1. The portion of the stack from pointer B to pointer E (ie the name and arguments of the call just finished) is deleted, and the stack above it pushed down to close up the space.
  2. The pointers B and C are modified to point to the next link down each chain.
  3. The source of the input stream is switched back to the previous uncompleted macro in store.
[ F R E D , . J O E , . A L F , 4 5 ] 4 5 4 , 2 * B E 9 9

Continuing the process:

[ F R E D , . J O E , 4 5 4 , 2 * 9 9 , A ] ] B E ?

At this stage, E has come to the end of the chain, and there are no more unfinished macros in the store, so the source switches back to the input device

[ F R E D , . J O E , 4 5 4 , 2 * 9 9 , A ] ] B E 1 2 ~ 3 ~ 2 ~ 1 J O E

but switches back to the macro store on encountering another ].

The process continues until B is once again pointing at the first [ at the end of the stack, whereupon the destination is switched back to the output device.

[ F R E D , 1 2 A 2 * 9 9 4 5 4 ] B E A B ~ 1 Y Z F R E D
A B 1 2 A 2 * 9 9 4 5 4 Y Z Deleted B ? E ?

4.4 Stack Languages

Most high-level programming languages achieve nesting of procedures and expressions by means of a stack mechanism.

Algol 60 was one of the first languages to permit n-level nesting of functions within argument lists of functions. Functions are easier than procedures for purposes of comparison with macro evaluation, since they pass on values to the outer argument list.

Consider the following valid Algol 60 expression:

a(b(c,d(e,f),g(h),i),j)

a, b, d, g are assumed to be functions; e, f, i, j are assumed to be variables.

The expression is essentially identical in structure to the following in GPM:

[a,[b,c,[d,e,f],[g,h],i],j]

The only significant syntactic difference between them is the position of the left-hand bracket with respect to the function or macro name of each call.

Compare    d(e,f)
with      [d,e,f]

{The above section attempts to point out the similarities between macrogenerators and high-level stack-based languages (especially POP-2 and APL).}

The following section lists what I consider to be the essential differences which distinguish macrogenerators from interpreted stack languages.

4.5 Features which Distinguish Macrogenerators from Stack Languages

Thus one can say that there is a basic similarity between Algol function calls and GPM macro calls, in that both use prefix notation, ie the function/macro name prefixes the argument string, even though there is a difference in the position of one delimiter (bracket).

5. DESCRIPTION OF GRAM

5.1 The GRAM Item

By virtue of the original purpose for which they were invented, most macro-processors are only designed to process character strings. GRAM, being primarily concerned with graphics, must be able to handle numbers reasonably efficiently. Therefore, whilst retaining the basic macro-processor concept of symbol replacement, the unitary symbol in GRAM is not a single character, but a whole integer computer word which may be represented externally in text form by more than one character. This is termed an item rather than a symbol, in order to avoid any semantic association between symbol and >character.

1 Numbers

External syntax: A string of up to four decimal digits (0-9) optionally preceded by a minus sign.

Example:
1234   -99   0   430   -8

Internal form: An integer in the range -8191 to +8191. The limitation on range is in order to distinguish numbers from the other three types internally, which are represented by integers outside this range.

2. Alpha Pairs

External syntax: One or two characters out of the whole ASCII graphic character set (apart from ", but including space) surrounded by string quotes ".

Example:
"AZ" "5" "**" "!?" "." ",H"

Internal form: Two characters packed into an integer word left to right. In the case of only one character externally, the least significant byte is a blank (all zeros). Provided the characters are held externally as two 8-bit ASCII bytes out of the graphic range (ie not control characters), they will always form an integer greater than 8191.

Pairs of string quotes between contiguous alpha-pair items may be omitted in input, and are always omitted in output:

"ABCDEFG" is equivalent to "AB""CD""EF""G"

Note that if the string quotes contain an odd number of characters, the last item always contains the odd integer single character.

3.Names

External syntax:

(a) A string of characters from the range of letters A-Z and numerals 0-9 but always starting with a letter

Example:
ABC THING M45 X ZOOLOGICAL

The number of characters in a name is only limited by the maximum length of the input line (typically 72).

(b) Any other single character out of the ASCII graphic subset which is not a Control Symbol or syntactic device.

* + - / ? % & are all valid Names

Internal form: A pointer to an entry in a table, added to a negative constant to keep it less than -8191 in order to distinguish it from other types.

The table contains (a) the characters defining the external form of the name, and (b) a pointer to a corresponding Macro Body, if one has been defined for it.

4 Control Symbols

External syntax: One of the following characters:

< > ( ) # !

Internal form: A large negative integer outside the range occupied by Names.

These items have special control functions, eg delimiting a macro call: (NAME ARG1 ARG2).

5.2 Syntactic Separation of Items

The character space acts as a separator between items. But it may be omitted where there is no ambiguity, as in the case of a Name surrounded by Control Symbols, eg (WXYZ), since ( and ) only occur as Control Symbols and cannot form part of a Name. In other words, multi-character items need not be separated by spaces if the first character of the second item cannot be construed to be part of the first item.

Example:
123ABC

represents two items, 123 (number) and ABC (name), whereas ABC123 would be a valid Name as a single item, and would need to be separated by a space, ABC 123, to be recognised as two items.

5.3 Macro Execution

The principle process of the GRAM macro language is the replacement of a name in the input stream by a macro definition in the output stream. The macro definition is a string of items which has previously been defined to correspond with the name. The process is termed the execution of a macro.

In order to execute a macro, the input stream must confirm the corresponding name item surrounded by the control symbols, ( and ). This termed a macro call.

For example, suppose the name ABC has been defined to correspond with the macro definition 12 XYZ 999. If the following is a portion of the input stream:

ZZ 456 (ABC) AA

then it will be expanded and appear, in the output stream, as

ZZ 456 12 XYZ 999 AA

The execution of (ABC) can be regarded as the temporary diversion of the input to the processor from the input stream to a stored macro definition, and anything that can appear in the input stream can also be in a macro definition, including further macro calls.

If the definition of ABC is modified slightly to 12(XYZ)999, and XYZ is defined to be 13 14 15 16, then ZZ 456 (ABC) AA will be expanded to ZZ 456 12 13 14 15 16 999 AA.

This is equivalent to functions calling other functions in any high-level language.

5.4 Passing Arguments

Any item appearing between the macro name and the closing bracket ) of a macro call is an argument.

Example: (FRED 20 BILL 5)

is a call of the macro FRED with the tree arguments 20 BILL 5.

However,for arguments to have any effect on the execution of a call, the control suymbol # must appear in the corresponding macro definition. The pair of items #n, where n is a number, appearing in a macro definition means:

Replace #n by the nth argument in the call.

For example, suppose the definition of the macro FRED is:

1 #2 222 JOE #3 #1 SAM
then calling
(FRED ALF JOHN BILL)
will give
1 JOHN 222 JOE BILL ALF SAM
in the output stream

Note that in GRAM, one argument means one item. They cannot be grouped in the same way that in GPM, for instance, a group of symbols between commas constitute an argument.

In this respect, note also the effect of alpha pairs in an argument list: calling (FRED "LOADED") will generate 1 "AD" 222 JOE "EDLO" SAM.

For further rules pertaining to the parsing of arguments see the Appendix.

5.5 Macro Calls Within Argument Lists

In accordance with the complete generality of macro generation and stack languages, macro calls may appear within argument lists. For example:

JIM ABCD(FRED 1 NN X) BOB)

The inner call of FRED is executed first, generating a section of the sequential list of the call of JIM. Using the last definition of FRED, it is as if JIM is called as follows:

(JIM ABCD 1 NN 222 JOE X 1 SAM BOB)

There is no theoretical restriction on the depth to which calls may be nested. For example:

(JIM(FRED(BOB(TOM 40(DICK)180(HARRY)))))

The inner most calls are executed first, passing on items to the argument list of the next one out, and so on.

There is also no reason why the name of a call (ie the first item following a ( cannot be generated by another call. For example, if FN is defined to be FRED then ((FN) 1 2 3) will effectively be equivalent to calling (FRED 1 2 3).

5.6 Primitives

GRAM has a set of functions, termed primitives, for performing a range of essential or useful operations (such as defining macros). Each primitive has a name, and is called in an identical manner to a macro, ie (name argument list). But instead of switching the input stream to a macro definition, it executes the appropriate special operation which is built into the GRAM interpreter.

There are, in the present state of GRAM, approaching 100 primitives, some of which extend its power from that of a macrogenerator to a more general-purpose high-level language (eg there are primitives for performing arithmetic and logical operations).

5.7 Defining Macros

One very important primitive is that for defining macros. It has the single character name : (colon). It could have been DEF or any other multi-character name, but it was considered appropriate for such an important and often-used primitive to be identified by a single graphic symbol.

The first argument in a call to : is the name of the macro being defined, and the rest of the argument list is the string of items comprising the definition. For example:

(: FRED  ABC 123 "ZZ")

would define FRED to be ABC 123 "ZZ"

Note that : in common with quite a lot of other primitives, does not return any items to the output stream. In this respect it is more like a procedure or subroutine than a function, in high-level language terms.

For example, if a definition call is nested within another argument list:

(TOM DICK (: JOE FRED 80) HARRY)

it will effectively disappear from the call to TOM, ie

(TOM DICK HARRY)

after having defined JOE to be FRED 80.

If a macro is called before being defined, the call is effectively ignored (and if originating from a terminal input stream, prints the message WHAT? on the terminal).

A macro can be defined any number of times, each new definition erasing the old one. However, another primitive, NEW, can be used to temporarily redefine a macro without losing the old version. The primitive OLD erases the existing definition to reveal the previous definition. Repeated calls of NEW create a stack of definitions, equivalent to stack push and pop instructions. For details of the rules governing NEW and OLD, see Appendix ?.

5.8 Protecting Brackets

At this point it is necessary to introduce another pair of Control Symbols, < and >. They are used as a form of bracketing rather than a less than and greater than signs, and are termed Protecting Brackets. Their function is to prevent any other Control Symbols between pairs of them from having any effect, but do not get passed on themselves. For example:

<(FRED 1 2 3)>

In the input stream would getb passed over to the output stream as

(FRED 1 2 3)

without any execution of FRED.

They are essential to be able to define macros which contain other macro calls within them. For example, suppose we want to define JOE to be

(FRED (AB)(CD)(EF))

The definition itself must be surrounded by protecting brackets, as follows:

(:JOE<(FRED(AB)(CD)(EF))>)

in order to prevent the premature execution of FRED prior to definition.

Protecting brackets may be nested within protecting brackets to any depth, and only the outermost pair are stripped off when passing through the processor. For example, consider:

(:SAM<(:JIM<(FRED 1 2 3)>)>)

in which SAM is defined to be a macro which when called defines JIM to be (FRED 1 2 3).

5.9 Expressions

GRAM possesses a set of primitives for performing arithmetic, relational and logical operations on numbers. These can be written as dyadic operators to make expressions of the form:

operand operator operand operator ......  operand

The primitives concerned are:

Arithmetic: EXP * / + 1
Relational: LT LE EQ NE GE GT
Logical:    NOT AND OR

They have the same meanings (except EXP means to the power of), and follow the same precedence rules of execution order within an expression, as in FORTRAN. This section only attempts to explain the concept, for details see Appendix ..

In order to fit diadic expressions into the syntax of GRAM without having to use another form of bracketing, the following expedient was devised: Any apparent macro call with a number instead of a name immediately following the opening bracket ( is interpreted as an expression, which generates a single numerical result. For example:

(3*4/2+1)

generates 7

Logical values true and false are represented by -1 and 0 respectively, therefore:

(135 GT 100) generates -1
(0 AND -44 LT 9*8) generates 0

An operand may also be a name, which is treated without an expression as a variable whose value is the first item in the definition associated with the name. For example:

FRED is defined to be 22 33 44
then (100+FRED) would generate 122

This depends on the first item in FRED being of type number. For details of other situations see Appendix ...

Note that (FRED+100) on the other hand will not be interpreted as an expression, but as a call to the macro FRED with arguments + and 100. It is frequently necessary to have a name as the first operand in an expression. The way round this difficulty is to precede the name by an operator. For example:

(+FRED+100)

Even though + is not a number, expression evaluation is entered by virtue of it being an expression primitive. The expression is evaluated as if there were a zero preceding the initial operator, so that + and - are the only sensible operators to use in this position. For example:

(-FRED) generates -22

There is a syntactic problem concerning the minus sign -. When it is immediately followed by a string of numerical digits (without an intervening space), the whole is translated by the input syntax analyser into a negative number. Everywhere else, the minus sign is translated into the name of the subtraction operator. For example:

ABC-345    becomes the 2 items    ABC   -345
ABC- 345   becomes the 3 items    ABC   -      345
ABC-FRED   becomes the 3 items    ABC   -      FRED

The expression evaluator gets round this problem by always treating a negative number as if it were split into two items, subtraction operator and positive number, so that ABC-345 and ABC - 345 are evaluated in exactly the same way.

5.10 Expressions within Expressions

As with macro calls, expressions may have other macro calls or expressions nested within them. For example:

( 2 - ( M A X A B 6 4 ) * 3 ) ( 1 0 0 * ( 1 2 - F R E D / 2 ) ) ( + A G T B * ( - C + D / ( + E + F ) ) A N D G N E H ) 1 2 3 4 5 6 7

Since the innermost calls or expressions are always executed first, generating operands for the next expression out, it is as if the brackets play their traditional role in an expression of modifying execution order. The numbers against the last example indicate the order in which it would be executed (depending partly on operator precedence and partly on nesting.

There is no reason why operators, as well as operands, should not be generated by macro calls. For example, consider (+A(OPR)B). If the definition is then made (:OPR+), the above example will be evaluated as (+A+B).

Argument references, of course, may also be incorporated into expressions. For example, consider the definition:

(:FNA<(25 #1 X * #2)>)

then calling (FNA+50) would generate the result of evaluating (25+X*50).

5.11 Subscripts

There is a method of subscripting names within expressions in order to be able to access items in a macro other than the first, and thus treat a macro as an array. In the absence of any further suitable brackets in the character set (square brackets [ and ] are already in use, see..), a different syntactically device is used: the juxtaposition of two operands without an intervening operator. The first operand is the subscript and the second must be the macro name. For example, 3 FREDmeans that the third item in FRED so that if (:FRED 10 20 30 40 50 60 70) then (3 FRED + 5 FRED) generates the result 80.

The subscript itself may also be a name, ed if :JOE 3 2 1) then (3 FRED + 5 FRED) generates the result 80.

The subscript itself may also be a name, eg if :JOE 3 2 1) then (+JOE FRED) generates 30.

In fact a subscript which is a name may itself be qualified by a subscript, and so on.

For example, the construction 2 A B C D E would be the equivalent of E[D[C[B[A[2]]]]] in Algol.

Note that only the first subscript of such a string can be a number. If two or more number - operands are juxtaposed, all except the last one are ignored. This has been found to be surprisingly useful (see ...).

5.12 Assignments

There is a primitive named = which acts as an assignment operator, and converts an expression into an assignment statement. The item in a macro defined by a subscripted (or unsubscripted) name to the left of = is changed to the result of the expression to the right of =. For example, if (:JIM 100 200 300 400 500) then (3 JIM = 2 JIM + 45) would change the contents of JIM to 100 200 245 400 500 and (+JIM = 2 JIM -50) would change it to 150 200 300 400 500) and (+JIM = 2 JIM - 50) would change it to 150 200 300 400 500.

However, as well as changing the appropriate item in a macro, the result is also passed on, as in a normal expression.

For example: (108 + (4 JIM = 20)) would generate 128 as well as changing the 4th item in JIM to 20. This effect is frequently useful, but if the passing on is not required, it can be suppressed by inserting the special operator ' at the start of the assignment as follows:

'4 JIM = 20) which would change item 4 in JIM but not pass on any value (as with a definition call, see ...

5.13 Item Order and Decompilation

Although the GRAM input stream is compiled from its external character-string form into the internal one-item-per-word form, no Reverse Polish style re-ordering of items occurs. This complicates the interpreter mechanism a certain amount, but it has two main advantages:

  1. A stored macro can be edited, using the editing primitives (see ...). But it would be difficult and confusing to do if re-ordering of items were to take place. This facility is made use of extensively in animation graphics.
  2. An edited macro can be easily decompiled back into its external form by means of the CY primitive, for purposes of listing and permanent storage.

5.14 The Copy Primitive CY

Consider the definition (:SQUARE<(#1*#1)>) which will be stored as the string of items:

  (  #  1  *  #  1  )

Calling (SQUARE 4) will give 16.

But supposing we want to use the string of items exactly as they are stored, without being executed, as if they were surrounded by protecting brackets < and >? It would be possible to edit the brackets into the macro by means of the editing primitives, but it is tedious and they would remain there.

Calling (CY macro-name) produces an exact copy of the contents of a macro without executing it. For example (CY SQUARE) would give (#1 * #1).

5.15 Particular Features of External Syntax

5.15.1 Implicit Brackets

Since GRAM is not intended to be used in a classical macroprocessor context, ie preprocessing text written in another language, the ability to parse item strings from input stream to output stream without modification is practically useless. As a command language for a graphics system, all input is likely to be in the form of one or macro calls. For example:

(AFTER 10 SEC)(ANIM LIN 15 SEC)(CH SIZE 50 50)

which is a typical section of animation script, or

(FILM SEQ1)

which is a typical keyboard command to initiate execution of a script called SEQ1.

It is assumed that the majority of users of macro systems written in GRAM, say for animation, using only simple un-nested commands, would be dismayed at the necessity to type in so many brackets. It seems desirable from an ergonomic point of view to enable the user to type in a command in the form:

FILM SEQ1 (followed by "return")

in order to have it executed.

Therefore a standard feature of input syntax is that a pair of brackets is automatically added to the beginning and end of each line by the lexical analyser, without them having to be typed by the user.

Thus, returning to the first example, the section of script could be written externally as:

   ...
   ...
AFTER 10 SEC
ANIM LIN 15 SEC
CH SIZE 50 50
   ...
   ...

This does not of course preclude the use of nested brackets where these are desired, For example CH SIZE (+XSIZE*(100-GROW)) 100 which would appear internally as (CH SIZE (+XSIZE*(100-GROW)) 100).

Conversely, there are situations in which implicit brackets need to be suppressed, as in the case of a macro call with too many arguments to fit on one line. Implicit brackets can therefore be cancelled by typing the opposite bracket: ) at the beginning of a line or ( at the end of a line. For example:

MANYARGFUNC ARG1 ARG2 ARG3 ARG4 (
)ARG5 ARG6
which could be treated internally as
(MANYARGFUNC ARG1 ARG2 ARG3 ARG4 ARG5 ARG6)

5.15.2 Line-by-Line Input

So far the input and output streams have been regarded in an abstract way as if they were inputting and outputting simultaneously and continuously. Normally, however, both the input and output streams are represented by a single keyboard terminal, which clearly cannot handle input and output simultaneously without making the result totally incomprehensible. Therefore input processing and output takes place consecutively for each line of input, in the following cycle:

  1. Wait for one line of input text, terminated by a return character.
  2. Process the input line.
  3. Print output stream resulting from the input, which may be one line, many lines, or none at all.
  4. Output prompt character, repeat from start

The prompt character, normally (back arrow) or _ (underline), is useful for

  1. Distinguishing between typed input and the output response.
  2. Announcing the end of the processing phase when there is no resultant output.

Example of a (quite trivial) session at a terminal (notice how compressions can be used in a pocket-calculator manner):

_123*2-1
245
_:X 100
_X
100
_5*(1+X)+30
535
_:ARRAY 10 20 30 40 50 60 70 80 90 100 110 (
_) 120 130 140 150 160 170 180 190 200
_16 ARRA160
_5 ARRAY=3 ARRAY*X
3000
_ARRAY
10 20 30 40 3000 60 70 80 90 100 110 120 130 140 150 160 170 180 190 200

5.15.3 Commas and Square Brackets

There are three characters which, when used externally, get translated into pairs of control symbols internally, thereby improving the readability of GRAM text. Consider:

_CALL1 ARG1)(CALL2 ARG2)(CALL3 ARG3

which is a perfectly legal way of writing three consecutive calls on one line, but due to the absence of the implicit brackets at either end, somewhat inelegant.

However, the pair of items )( can be represented by a comma, as follows:

_CALL1 ARG1,CALL2 ARG2,CALL3 ARG3

The processor will then see the line as:

(CALL1 ARG1)(CALL2 ARG2)(CALL3 ARG3)

In addition, the square brackets [ and ] may be used to represent combinations <( and )> respectively.

Example: suppose we need to define a macro AMACRO to be

(A 40)(B)(C(2*X))(D)

the processor need to see in the input stream:

(:AMACRO<(A 40)(B)(C(2*X))(D)>)

which is a bit of a mouthful of brackets. But we may write:

:AMACRO[A 40,B,C(2*X),D]

which is a considerable improvement in readability.

5.15.4 The Newline Item

At the end of each actual text line of the input stream is an extra control symbol (inserted after the implicit closing bracket) which means new line, but which normally does not get passed on because its only processor action is to disappear. However, it does not get passed on if it occurs within protecting brackets, and if it survives to appear in the output stream will cause either a new line, or the character ^ (↑ on some terminals), depending on the output option chosen (see ...). For example, typing in:

_:GRAPH1 [FIG AXES
_         FIG SHADE
_         PIC CURVE]

will appear to the processor as:

(:GRAPH1<(FIG AXES)^(FIG SHADE)^(PIC CURVE)>)^

and the stored version of the macro GRAPH1 will be

(FIG AXES)^(FIG SHADE)^(PIC CURVE)

The main purpose of the newline item is to preserve the original line structure of macros for the sake of readability of listings. It has no effect on the execution of a macro other than a very small time overhead.

5.15.5 Output Options

Using the last example, typing in

_CY GRAPH1

will result in the following line actually being printed:

(FIG AXES)^(FIG SHADE)^(PIC CURVE)

There is, however, a way of producing output which restores the input format, ie it removes brackets at beginnings and ends of lines, does an actual newline instead of ^, replaces )( by ,, etc. and in addition inserts line numbers.

The primitive OP effectively caused forced output of the existing contents of the output stream whenever it is called, and if it carries a positive numeric argument n, will convert to the special input format with line numbers starting at n.

The largest permissible line number is 999, therefore n longer than 999 suppresses line numbering. Fir example:

_CY GRAPH1,OP1000,CY GRAPH1,OP20
FIG AXES
FIG SHADE
PIC CURVE
 20 FIG AXES
 21 FIG SHADE
 22 PIC CURVE
_

See section ... for more details of the OP primitive.

6. THE GRAPHIC PROCESSOR

7. THE ANIMATION SYSTEM (MACRO SUPERSTRUCTURE) (to be written)

8. APPLICATION - EXAMPLES (to be written)

9. DISCUSSION and CONCLUSIONS (to be written)

This is where the archive stops. It is unclear whether a fuller version of the PhD submission was ever completed.