Functions, Procedures and Drivers

We introduce here the concept of driving or programming the editor. The essential idea is that we want to carry out a whole series of editor actions (presumably constituting some processing task) by specifying them in a program and then triggering it. In effect, the program produces a sequence of keys, and uses these to control the editor (i.e. to drive it), temporarily replacing the user at the keyboard.

ivi offers three driving mechanisms of increasing complexity. These provide the user with powerful tools for text and record processing.

The simplest of these is the function, which might be better named the macro. This lets the user equate commonly used sequences of keys to a single key operation. The sequences may generate a simple word or phrase, or may contain any of ivi's operations or even commands. It may also contain rudimentary control structures, so that functions may perform fairly complex looping and conditional tasks.

Very similar is the procedure, which permits the user to store sequences of commands and operations in a text file, and to have these obeyed as though they had been entered at the keyboard. This mechanism can also be used to initialize ivi, to tailor it for a particular user or task.

Most complex of all is the driver program. This is a program written in any programming language, say, C or Python, and compiled (or not) as usual. It can then be connected to ivi in such a way that the output of the program is used as input to ivi. Initially a user calls a driver program from within the editor. Subsequently, control of ivi may pass back and forth between user and driver program. The latter may also seek information from the editor; for example, it may query the success or failure of a FInd command, ask for the character at the cursor, even request to be sent the contents of a corefile. In effect, the driver program has at its disposal all the capabilities of the editor as well as the facilities of the programming language -- a very powerful combination.

This page may be read in its entirety (recommended to see the interplay of the three mechanisms) or the following links may be used:


A function is a short set of keystrokes which may be associated with a function key. The function is defined with the FUnction command, and triggered by entering the key.

Command: FUnction

Purpose: Define a function

Format: FU <digit>|<function string>

Parameters: <digit> is the number of the function being defined; the second parameter specifies the sequence of keys which the function is to be equivalent to.

A user may define up to 10 functions numbered 0 to 9. In its simplest form, a function may be just a sequence of character keys. Entering the corresponding function key is then equivalent to typing the character keys one by one.

So if we define

    FU 6|xyzzy

xyzzy will be entered as a sequence of keys whenever function 6 is triggered, even in COMMAND mode, where it might become a filename.

In order for them to carry out editing operations, function strings must also contain control characters. For this purpose, ivi arranges for characters inside brace brackets {} to be ascribed special meaning. Generally speaking, {X} denotes <CTRL X> where X is any upper- or lower-case letter or one of the symbols [ \ ] ^ or _ . These translations are summarized in the following table:

CharacterTranslated to
X or x<CTRL X>
I or i<CTRL I>, i.e. <TAB>
M or m<CTRL M>, i.e. <RETURN>
[<CTRL [ >, i.e. <ESC>
(left brace: {
)right brace: }
!vertical bar: |
` (i.e. backquote) tilde: ~
@ or SPACEnull byte


FU 2|{(}abc{!}def{)} defines a function which enters the sequence {abc|def}.


1. Several letters/symbols may appear in a single set of braces.


2. A function may call other functions, and may even to call itself recursively, but only to a maximum depth of 10. (A function call at a greater depth than 10 is discarded.)

3. The maximum length of a (translated) function is about 47 bytes (characters or operations). It is also restricted to about the same length by the limit on command parameters.

4. A null byte acts as a function string terminator, so that the inclusion of {@} or { } (a space) in a function string merely stops the function at that point.

5. In function strings, the tilde may be used outside braces to represent itself. This, however, is not true for procedure strings, where the tilde has a special meaning, and {`} must be used to represent it.

Warning. It is important to note that a function string specifies keys, not characters. The difference may be seen by considering the function defined above:

    FU 6|xyzzy

This enters the keys xyzzy, and therefore types the string xyzzy.

On the other hand, the function:

    FU 6|êtes

does not type the string êtes. The problem is ê. In the case of English letters, you type the x-key (ivi key number 120) to store the letter x (character value 120); key number and character value are the same. This is not true for the accented characters. The character value of ê is 234, which is the key number for <ESC> j, the Sleep operation. (Character values are determined by the ISO 8859-1 or Latin-1 standard. ivi key numbers are discussed elsewhere.) The keys required for ê are <ESC> <ESC> e, so the function to types êtes should be:

    FU 6|{[}Etes

Operation: Execute function

Purpose: Invoke a previously defined function

Default Keys: <ESC> 0, <ESC> 1, ... , <ESC> 9

Operation Numbers: 118, 119, ... , 127


If we define FU 4|{C}ov*{M}, the key sequence <ESC> 4 causes entry to command mode and executes OVerwrite * .

The function FU 3|{[}2{[}2{[}2{[}2 calls function 2 four times.

The function FU 5|xyz{[}5 types xyz and then calls itself to repeat this action. In principle, this should loop indefinitely. In practice, because of the depth restriction mentioned earlier, the 11th call will be discarded, bringing the process to an end. So <ESC> 5 produces:


It is perhaps worth noting here that when a function is specified by a FUnction command, a null byte (a byte of value 0) is placed at the end of the function string to act as terminator. When a function is called, the call does not end when its final key is executed, but only when ivi tries to execute the terminator. So each call to function 5 in this example is still in place until its successor call is terminated. This is why we eventually reach the maximum depth of 10.

Loops and Conditionals

Functions may also be made to execute simple loops and conditionals. For this purpose, several further characters occurring in brace brackets are interpreted as control components:

Character Interpretation
? if top of result stack > 0 continue, otherwise skip past "else" {:}
: else
. end-if
$ push the number which follows onto the result stack
% pop result stack
< begin-loop
^ exit loop if top of result stack <= 0, otherwise decrement it
> end-loop

These abbreviated interpretations require some amplification.

We have already noted that there is a result stack whose top element is set by various commands and operations; for example, FInd and Find restart set it to 1 or 0, indicating whether or not the search succeeded, Number sets it to the result of the count, and so on.

In fact, most other commands also set it to indicate their success or failure; for example, FEtch sets it to 1 if the fetch succeeded, 0 if an error arose. (See also the Check and Extended check operations and the ASk command.)

{?} is essentially an if-clause, which tests the success of the last such command/operation, and it combines with {:} and {.} to form conditionals. In effect,


simulate if-then-else and if-then statements, respectively. If the test indicates success, the then-part is carried out; if it indicates failure, the else-part (or nothing) is done.

Actually, these controls are more general than this, and a more precise discussion can be found below.

{<}, {^} and {>} combine to form a loop.

{<} simply marks the start of a loop, and causes no other action.

{>} marks the end of a loop, and causes the function to return to the key following the matching {<}.

{^} tests the top of the result stack. If result < = 0, the function proceeds to the item following {>} at the current level of nesting, and thus exits the current loop. If result > 0, the top element of the result stack is decreased by 1.

If the loop contains, say, a Find restart, which sets the top element of the result stack, we can create a function to repeat some task until the search fails.

Alternatively we can use the top element as a loop counter, to perform a task a certain number of times.

To allow for this, {$}, which must be followed by a number, can be used to push a loop-counter onto the stack, and so specify the number of times the loop is to be repeated. Note that the digits which form the number are outside the brace brackets.

If {$} is used, then {%} should follow the loop to clean up the result stack.

The nesting of loops within loops, conditionals within conditionals, and either within the other, is legal to a maximum depth of about 20. Presumably, the user will push the result stack before an inner loop and pop it afterwards, so that the loops have different loop-counters.


1. Suppose a loop is making use of a loop-counter and contains commands and operations which affect the result stack. Then these alter the top element and destroy the count. In such a case, it is necessary to push a spurious value onto the stack at the start of the loop to receive such results. The stack should be popped before testing and re-pushed if necessary. (So why don't these operations push their result onto the stack? Because this would have repercussions when the editor is under keyboard control.)

2. It is obviously possible to initiate infinite loops. If the editor becomes locked in a loop, the Abort key, usually <CTRL _ >, erases all pending function and keyboard input, restoring ivi to a usable state (see System Stuff). It may be necessary to enter the GEnerate display command to obtain a correct display.


FU 1|{$}10{<^/////M>%} deletes five characters from each of 10 successive lines. It starts at the cursor on the first line and at the left margin on the others. Note that the test is positioned at the start of the loop. If it moved to the end, the action will be carried out 11 times.

FU 2|{<N^/>} finds all occurrences of the current search string and deletes its first character. The loop terminates when Find restart fails.

FU 7|{N?///.} searches for the next occurrence of the current search string; if successful, it deletes three characters, otherwise it does nothing.

Let's write a function for global search and replace based on the FInd command. Obviously in this simple case we could use CHange, but CHange's search is less general than FInd's. Suppose we wish to replace all occurrences of SUE by PETER. Then we enter:

    FU 5|{<}PET{W}ER{WN^>}
    FI 0|SUE

and after ensuring that the editor is not in INSERT submode, enter <ESC>5.

Looking at the function in more detail:

{<} - begin loop
PET - overtype SUE with PET
{W} - enter INSERT submode
ER - insert the rest of the word PETER
{W} - leave INSERT submode
{N} - find next SUE (setting result)
{^} - exit loop if result < = 0 (not found)
{>} - end loop

Well, nearly but not quite!!! If, by some remote chance, the text happens to contain SUESUE, the second SUE will not be found. The reason, as we have noted elsewhere, is that <CTRL N> is subtly different from FInd; it shifts the scan position before its first match attempt, to avoid finding the same copy of the string again. This is what the user most often wants, but in this case, it causes the second SUE to be missed. The cure: if the remote chance might actually occur, we must move the cursor up a line before obeying <CTRL N>. (Left would also work in this case, but not if we wished to locate SUE and delete it, and SUESUE happened to be at the start of a line.) What we really need is a second Find restart which does not shift position before attempting to match.


Procedures are very similar to functions, except that

Command: PRocedure

Purpose: Read and execute a series of commands and/or operations from a file.

Format: PR <filename>

The execution of procedures and functions is carried out by a single mechanism; so all function facilities are available to procedures too.

A significant difference between functions and procedures occurs when a procedure file is being read. Procedures were originally intended to execute a series of commands to set ivi up for some application. For example, a typical procedure to install a set of French language files for vinci is:

LE fr.lex
MO fr.mor
TB fr.tb

Now suppose we want a procedure to define a function such as:

FU 4|{C}ov*{M}

Then the translation of characters in brace brackets would cause a problem. In this case, we actually want the sequences {C} and {M}, not <CTRL C> and <RETURN>, to appear in the function string. We could, of course, achieve this by way of:

FU 4|{(}C{)}ov*{(}M{)}

but this is cumbersome.

Instead, we allow translation to be turned on and off while the procedure is being read. Initially, characters in braces are not translated like function characters, but simply represent themselves. The tilde character ~ is used as a toggle. If it appears in a file, subsequent material in braces is translated until a further tilde occurs. Of course, if a tilde is itself required in the file, it is entered as {`} when translation is on, or as ~{`}~ when it is off!

Note that at the start of a procedure, the editor is always in COMMAND mode. (You have just executed a PRocedure command.) Also, the end of every line supplies a <LINEFEED>, or <CTRL J>, character. It is this which actually triggers the commands in the vinci example.

Unfortunately, the text layout of a procedure requires picky care. Procedures, especially in the absence of loops and conditionals, are merely a list of keys to be sent to ivi. Each of these keys, including the <LINEFEED>, causes some action. Suppose that a blank line precedes the list of commands in the vinci example. Its <LINEFEED> returns ivi to TYPING/INSERT mode, and the commands simply appear to ivi as text.

As in the case of functions, a procedure can become locked in an endless loop. Again the Abort key can be used to restore ivi to a usable state.

Initializing ivi

A procedure file may be called when ivi is started. To do this, the following syntax is used:

    ivi -p <procedure file>

It is also possible to call both a procedure file AND a file to be edited, by typing:

    ivi -p <procedure file> <file>

As noted earlier, the file called by <file> exists, then it is FEtched. Otherwise, it is created and NAmed with the name <file>. It is important to note, however, that the procedure is executed before any file mentioned on the command line is FEtched or NAmed.

Interactive Processing

Within the vinci environment, procedures are often used to test some aspect of a student's language understanding. Typically the procedure drives ivi/vinci to generate some question or stimulus, and passes control to the keyboard to allow the student to reply. On completing the response, the student returns control to the procedure, allowing it to continue.

The following operations permit such interaction to take place.

Operation: Suspend procedure

Purpose: Cause a procedure to be suspended and control to be passed back to the keyboard

Default Keys: <ESC> <CTRL y>

Operation Number: 14

This key sequence is produced by a procedure or function, where it presumably appears in the form {[Y}. When ivi carries out this operation, it leaves the procedure pending and resumes reading from the keyboard.

Note. Both for this and the next operation, the same keys perform similar functions for a driver program. There are, however, other considerations which will be discussed in that context.

Warning. Bad things may happen if this operation is entered at the keyboard, or if the next is produced by a procedure. Caveat utilitor!

Operation: Resume procedure

Purpose: Cause a suspended procedure to resume where it left off

Default Keys: <CTRL @> or <ESC> <CTRL w>

Operation Number: 13

This is entered by the user.

Operation: Sleep

Purpose: Cause ivi to do nothing for 3 seconds

Default Keys: <ESC> j

Operation Number: 101

We may want to display a question or stimulus for a few seconds, and then clear it before returning control to the keyboard. This operation causes ivi to "sleep" for 3 seconds before reading the next input.

Presumably this operation serves no purpose except within a procedure or function. In a driver program, the writer can call the system sleep procedure directly.

Some extra conditionals primarily designed to analyze vinci's error comparison reports are described below.

Driver Programs

As we have already noted, a driver program is a program written in some language like C or Python, and compiled, or not, depending on the language. The program is started up by the RUn command as a subprocess, its output being connected to ivi's input to drive it. As in the case of functions and procedures, the output may contain both displayable and control characters.

Several facilities provide for interaction between driver and editor. The program can obtain information from ivi; the user can interrupt or kill the program; and so on.

Command: RUn

Purpose: Initiate an executable program to drive ivi

Format: RU <filename>

The standard output path of the driver program is connected to the standard input of ivi. The standard output of ivi remains connected to the display. A secondary output path of ivi is connected to the standard input of the driver, allowing ivi to send data to the driver program in response to the ASk command.

Note also that, just as in the case of procedures, a driver program may be specified when ivi is started from the command line, as in:

    ivi -d <driver file>

It is also possible to call both a driver file AND a file to be edited, by typing:

    ivi -d <driver file> <file>

As in the case of procedure files, it is important to note that any operations specified in the driver file will be done before any file mentioned on the command line is FEtched or NAmed.

Command: ASk

Purpose: Ask the editor to send information to a driver program and, where appropriate, to the top of the result stack

Format: AS <number>

Parameter: The parameter is a number from 0 through 14 selecting one of 15 alternatives. See below. If a parameter above 14 is entered, the command is ignored.

When a user is typing at the keyboard, certain information is available (i.e. can be seen on the display) which may affect further actions; for example, the number of lines in a corefile, the word at the cursor, the contents of the FP register, whether a FEtch command just failed, and so on. For a driver program, this is the role of the ASk command. When ivi is sent an ASk command, it sends a response by way of its secondary output path. When the driver outputs an ASk, it presumably executes an input procedure to read the reply. Where the result is a simple integer it is also placed in the top element of the result stack, which may make it useful to a procedure. If no driver program is running, the secondary output is not produced.

The alternatives for the AS command are as follows.

Note: every reply, including alternative 7, is terminated with a <LINEFEED> character.

In cases 3-7, 9, 10 and 11, the top element of the result stack is set to the result value. In case 7, this is the ASCII (or ISO-8859-1) value of the character.

It may be useful to recall that other operations/commands, such as Check, Extended check, FInd, Find restart, Number, ... , set the top element of the result stack, which can be acquired using AS 2.)

Command: EMit

Purpose: Causes the editor to send part or all of a corefile to a driver program or, in the xv-version, to the editor output stream.

Format: EM <digit|number or *|number or *|digit>

This command is related to the driver-program feature. Its purpose is similar to that of the ASk command, in that it can send information (in this case, part of a corefile) to the driver, either by driver calling for it by executing the command or by the user entering it at the keyboard. In the xv-version, it can also send the data to the ivi output stream for display.

Its actions are closely similar to those of the SAve command, which also transmits some of the corefile, in this case, to disk. Indeed, the two commands share the principal section of the code.

Its original purpose was in connection with the VinciLingua language-learning webpage, where a driver-program, instr, supervises the whole process, while a program, stdnt, serves as an intermediary between ivi/Vinci and the webpage. EMit can be used by instr to send information via ivi/Vinci to stdnt (and thus, to the webpage), and by stdnt to send information derived from the webpage, via ivi/Vinci, to instr.

Parameters: There are four parameters:

  1. the corefile number to which output should be emitted
  2. the first line to emit
  3. the last line to emit
  4. an integer which is 0 or 1

If the first parameter is left blank, the default is the current corefile. The second and third parameters are analogous to the corresponding line number parameters in SAve, etc., but with ONE EXCEPTION: the use of * to denote one of the starred lines of the corefile works ONLY for the current corefile!

If the fourth parameter is 1, the data transmitted by EMit is placed in input stream of the driver-program. If it is 0, it is sent to the ivi output stream.

In both cases, it is terminated by a final line composed of : $$$

Nota bene:

In the standard version of ivi, EMit carries out these actions ONLY if the fourth parameter is 1 and there is an installed driver-program which is currently in the ACTIVE state (i.e. not suspended); in other words, if the standard ivi is running under the control of a driver-program, it can send the output to the driver-program. There is no reason for EMit to send part of a corefile to ivi's display, since the corefile can already be displayed by many other commands.

In the xv version of ivi, which is used in the webpage environment, EM, if the fourth parameter is 0, output is sent to the ivi editor. This can be done from the keyboard, whether or not a driver program is running. On the other hand, if there is an installed driver-program which is currently in the ACTIVE state, setting the fourth parameter to 1 sends can sends information to the driver program, which can then recognize and intercept it. Indeed, the main purpose of EMit is to allow the driver program to process information and send it on to some other destination, such as a web page.

Keyboard/Driver Interaction

The primary mechanism for setting interaction between ivi or xvivi and a driver program is through the SWitch command:

Command: SWitch

Purpose: Pass control back and forth between a driver program and ivi

Format: SW <0|1|2>



  1. if no driver program is active and SW 0 is entered, ivi itself is suspended
  2. if no driver program is active and SW 1 is entered, the error message No suspended program! is shown
  3. if no driver program is active and SW 2 is entered, the command is ignored

In addition to the SWitch command, six key sequences provide keyboard/driver interaction, as in the case of procedures. These are summarized in the following table:

OperationDefault KeysSent ByOp Num
Suspend driverSuspend keyKeyboardN/A
Abort driverAbort keyKeyboardN/A
Resume driver<CTRL @>
or <ESC> <CTRL W>
Suspend voluntary<ESC> <CTRL Y>Driver14
Abort voluntary<ESC> <CTRL V>
or byte -1
Request ackn<ESC> <CTRL X>Driver16

The Suspend and Abort keys have been mentioned elsewhere (see System Stuff). As noted there, these keys are set up at entry to ivi, and cannot be changed by the user. They generate signals which interrupt the editor and cause it to take special actions.

The Suspend key is <CTRL A>. When a driver program is running, ivi simply passes the signal to the driver, causing it, rather than ivi, to be suspended. In a typical application, ivi produces a series of individual salary letters from a database of records. If a problem is noted as one of the letters is being created, the user can suspend the driver and make a correction, before causing the driver to resume. (But see the note on synchronization below.)

The Abort key is <CTRL _ > (or <CTRL DEL>, depending on your keyboard). Again, ivi passes this to the driver, killing it; it also continues to clear the input buffer and the function-stack, since any pending functions have almost certainly been initiated by the driver. The Abort key allows ivi to resume if the driver is in an endless loop or if it appears to have gone astray.

These two keys cause "involuntary" suspension/abortion (involuntary from the viewpoint of the driver program), triggered by the user from the keyboard. Two other operations are triggered by the driver to cause voluntary suspension/abortion.

The Suspend voluntary operation is very similar to the Suspend procedure described earlier, and serves the same purpose. Sent by the driver, it requests ivi to suspend the program and pass control back to the keyboard. The keyboard can subsequently cause resumption. One small difference: because of synchronization problems (see below), the driver must attempt to read a single character immediately after sending the Suspend voluntary key. The actual character can be discarded.

The Abort voluntary operation requests ivi to kill the driver program. The driver will presumably send it if it encounters some situation it can't cope with; say, some critical file not present, or inability to SAve some data. On seeing this key, ivi removes the driver program and seals off the connections which link it to the program's input/output.

This operation must also be sent by the driver when it terminates. At one time this was done automatically without the programmer taking any action. One of the "keys" associated with the operation is the one represented by the byte -1 (or 255), and this byte used to be placed in a connecting "pipe" by UNIX whenever the sending end of the pipe went away, triggering the necessary clean-up. Unfortunately this no longer seems to be the case. If a driver terminates without requesting this Abort explicitly, a further RUn command raises an error message "Program already running". This can be put right by entering Abort voluntary from the keyboard.

By the way, the corresponding Abort voluntary for a procedure is merely the null byte (the byte of value 0), which causes procedures and functions to terminate.

If a driver program is suspended in either manner, it can be caused to resume where it left off with the Resume driver key. If suspension was voluntary, ivi sends a single character (a <LINEFEED>) to the driver immediately after restarting it. This supplies the character which the driver is waiting to read.

Synchronization. A procedure is basically a sequence of keys. ivi reads one of these, carries out some task, and then goes back for another. A driver program, in contrast, generates a sequence of keys, running as an independent subprocess. It is not synchronized with ivi, and may get well ahead of it, generating keys long before ivi uses them. This can cause problems. In the salary letter application, for example, by the time the user notices a problem with a letter, the driver may have generated keys to create several more. So the suspension may take place long after the user thinks he/she is requesting it. Similarly, when the driver requests a voluntary suspension, it may be some time before ivi sees and acts upon the request. In fact, it is even possible that the driver may have terminated before ivi catches up.

Catch-up, of course, occurs whenever the driver sends an ASk command and waits for some input, since ivi can't reply until it sees the request. It also occurs when the driver sends a Suspend voluntary provided that it attempts to read a character immediately afterwards. The driver waits for the character, and is eventually suspended by ivi while it is in the waiting-loop. When the program resumes, it continues waiting, but then receives the <LINEFEED> character and the wait ends.

The two processes (ivi and driver) can also be synchronized using the Request acknowledgement operation. The driver sends this and attempts to read a single character. This causes it to wait. When ivi sees the request, it sends a <LINEFEED>, bringing the wait to an end.

The reverse situation, by the way, is not a problem. If ivi gets ahead of the driver, and needs a key when none has been generated, it simply waits, as it always does when a user is typing at the keyboard.

Issues of Interaction

We deal here with some rather complex issues related to functions, procedures and driver programs, specifically:

Who can call whom?

This section deals with the interaction between functions, procedures and driver programs, and between these and user activity at the keyboard.

It all began with the question:

Can you call an ivi procedure from within an ivi procedure?

In fact, the answer is No, but if you try it, it often works. This is the most complicated of the questions of this type, and we will discuss it in detail because the answer provides insight to many of the other questions.

First, it is helpful to know the following:

  1. ivi functions and procedures are sequences of keys (bytes) stored in the main memory of the computer. In the case of procedures, this memory space is requested from the operating system and freed when it is no longer needed. All of these sequences are terminated with a null byte (a byte of value 0).
  2. The process which implements both functions and procedures makes use of a function stack. The elements on this stack are pointers to the next key in a function or procedure string. The deepest element is always a special pointer denoting the keyboard. ivi obtains its next key by referring to the top element on the stack. If the top element is the special pointer, input is taken from the keyboard. When a procedure or function is called, a pointer to it is pushed on top of the stack, and the corresponding string is therefore read before input from any other source. When ivi finds that the top element of the function stack is pointing at a null byte, it removes that element and reverts to the source next below it.

Now let's return to the question.

When a new procedure is initiated, it frees the previous procedure (if any), and starts over. On the face of it, therefore, it seems that we can call procedure C from within B as long as the call is the very last item in B. We have finished with B, and discarding it does not appear to be a problem. Unfortunately, it may be.

During the execution of B, the function stack contains:

and at the moment when ivi has just read and executed PR C, it contains:

As mentioned earlier, the pointer to the end of B is still present, and should be pointing at the null byte which terminates B. In due course, when C has been completed and its pointer removed, ivi should see the null byte and remove the B-pointer.

Unhappily, however, the string pointed at by B has been freed, and the pointer may no longer be pointing at a null byte.

What are the possibilities? There is a good chance that the space freed from B will be reissued for C. In this case, if C is shorter than B, the B-pointer may still indicate a null; but if C is longer than B, the B-pointer will now indicate an arbitrary place in C, and part of C will be re-executed when C is completed.

It is also possible that C has been allocated a quite different area of memory. In this case, whether the B-pointer is still pointing at its former string depends on other memory requests and freeings which have taken place.

This is why the expected action often works in spite of the negative answer to the question.

Can you call an ivi procedure from within a driver program?

The answer is Yes

Can you call a driver program from within an ivi procedure?

Again, the anser is Yes, but it may not do exactly what you are expecting.

In contrast to functions and procedures, a driver program is not implemented by pushing an element onto the function stack. Instead, it takes the place of the keyboard; in other words, when ivi sees the special pointer it takes its input from the output of the driver program. This implies that the ivi procedure, and any outstanding functions, will run to their conclusion before the driver program begins.

Can you call a driver program from within a driver program?

The answer is No. In fact, the RUn command gives an error "Program already running".

What about functions?

A function may not, however, call a FUnction command which changes the function being executed, nor call a procedure which does so. And so on.

How can a procedure suspend itself and pass back control to the keyboard while still maintaining its position?

By pushing a second keyboard pointer on top of the stack. When this is removed by the Resume procedure operation, the stack returns to its former state and the procedure continues.

Can a user call a function while a procedure is suspended?

Yes, but not another procedure, for the same reason which began this discussion.

Can such a function contain <ESC> <CTRL w> to cause the procedure to resume?

No, for the technical reason that procedure resumption is triggered only if the second keyboard pointer is on top of the function stack. (By the way, the alternative Resume key, <CTRL @>, is itself the null byte and acts as a function or procedure terminator.)

If a driver calls a procedure, and the procedure contains <ESC> <CTRL X>, which is suspended?

From the code, I believe the procedure is suspended, but this situation was not foreseen by the programmer and the stack is in an unusual state. Caveat utilitor!

What if the user cannot resist the temptation to fiddle with the keyboard (other than the Suspend and Abort keys) while a procedure or driver program is controlling the editor?

The keys are queued in the input buffer, and read by ivi when control passes back to the keyboard. Of course, if this happens through an (involuntary) Abort, the input buffer is cleared.

What if ...

`I have answered ten questions, and that is enough,'
Said his father; `don't give yourself airs!
Do you think I can listen all day to such stuff?
Be off, or I'll kick you down stairs!'

with apologies to Lewis Carroll

Conditionals within Procedures

Basic loops and conditionals for functions and procedures were described earlier. This section presents further conditionals for use in procedures. Though designed primarily for analyzing the results of the vinci error comparison process, nothing precludes their wider use. We also define the components of the basic conditionals more precisely.

Once again the controls are represented by characters in brace brackets. The various components are:

{+}<pattern> <end of line>

This is a conditional clause somewhat similar to {?}. The parameter <pattern> is a character string which may contain * wildcard symbols. The operation attempts to unify the pattern with a complete line in corefile 15, beginning with the cursor line. (Unification requires the pattern and the line to match, with each wildcard corresponding to zero or more line characters. So abc*def*gh can be unified with abcxyzdefgh.) If any line can be unified, the strings which matched the pattern wildcards are stored in numbered buffers, and the procedure continues. If no line can be unified with the pattern, the procedure skips past the next {:} or {.} on the current nesting level.

It is possible that a line can be unified with a pattern in more than one way. (Consider, for example, the pattern a*b*c and the line axbybzc.) {+} chooses leftmost alternatives. (So the wildcards in the example match x and ybz, not xby and z.)

{-}<number> <non-digit> <string> <end of line>

Another conditional clause. This compares <string> to the one in the numbered buffer. If they match, the procedure continues. If they don't, The procedure skips past the next {:} or {.} on the current nesting level. <non-digit> marks the end of the number. If it is a space, it is discarded.


In effect, this is begin-if and matches end-if, i.e. {.}, for (inner) nested conditionals. It triggers no action itself. It is simply a marker causing the nesting level to be increased during a skip.


This causes a skip past the next {.} on the current nesting level. It is also a marker terminating a skip.


This takes no action itself. It is simply a marker terminating a skip or causing the nesting level to be decreased during a skip.


As we have seen, this tests whether the number at the top of the result stack > 0. If so, it continues to execute the procedure. If not, it causes a skip past the next {:} or {.} on the current nesting level. It also acts as a marker causing the nesting level to be increased during a skip. In effect, it implicitly includes {&}.

So what do these achieve? Here are some examples, ignoring matters of layout:

(if-then-else statement)


(if-then statement)


(if-then-else statement based on unification)


(guarded statement)

The patterns are tested in turn and the action taken is the one corresponding to the first pattern which can be unified.


Note that any these may be nested inside one another or within loops, and that loops may be nested within these, BUT ... if a guarded rule beginning with {+} or {-} is nested inside any other guarded rule or conditional, it MUST be preceded by {&} in order to increase the level of nesting and "hide" the inner {:}s and {.}s. (At the outermost level of nesting, the presence or absence of {&} is irrelevant.)

Note a subtle difference between {?} and {+}, in that {?} incorporates an implicit {&}. Unfortunately this means that every {?} must be matched by a {.}, which is not the case for {+}. Of course, every {&} must be matched by a {.} too.

The {-} operator is closely analogous to {+}, but can only be used after a successful {+}, which will have placed strings in the numbered buffers. An example is shown below.

Conditionals or loops may call functions, but skips do NOT follow down through them; furthermore, all skips terminate at the end of the current function or procedure. The implication is that all of the control symbols of a guarded rule, conditional or loop should be in the original procedure or function, not in a higher or lower level; otherwise the effect may be very unhappy :-(

Another new operation for use in a procedure is:

{#}<number> <non-digit>

This behaves like a function, inserting the contents of the numbered buffer into the ivi input stream. <non-digit> is any single non-digit character; it is not discarded. There must, of course, have been an earlier {+} operation to set the buffers.


Suppose that, with the cursor on the first line, corefile 15 contains the vinci error report, where Cx stands for the computer grammar output and Sx for the student input. The third column contains either the keyworkd EXACT where there is no error, or x/y, where x stands for the computer's expected answer and y for the student's answer. If there is more analysis, such as the identification of a MORPHological error (feminine for masculine) or a PHONOgraphic error (the spelling represents the sound, but with the wrong letters) or a LEXical issue, such as use of a SYNonym), the nature of the error is further described.

C3,S2 verts/vertes MORPH masc/fém
C5,S5 achetterez/acheterées PHON tt/t,ez/ées
C9,S7 gros/grand LEX 14:syn

A typical procedure might be the following (again ignoring layout):

{+}*,* */* LEX *:*
Dear student, 
    one of your words differs from 
    the expected one.
   {&-}6 syn
   You have used the synonym {#}3 
   where others would have used {#}4.
   This is, of course, a syn, not a sin.
   {:-}6 mal
   You have used an improper word {#}3 
   in place of {#}4.
{:+}*,* */* MORPH */*
Wretched student, 
   you have a morphological error.
   {&-}5 masc
   You have failed to make the word {#}3 
   {:-}5 fém
   You seem to believe that {#}4 ought 
   to be feminine.
Congratulations, you have avoided lexical 
or morphological errors. There may be other 
horrors which I have not looked for.

This simplified procedure attempts to detect only for one error in the report. It looks for the first line containing LEX (along with a few other symbols), or failing that, the first line containing MORPH. If it finds a LEX line, it looks at the string matching the 6th wildcard. (In our sample grammar, this must be syn or mal, indicating a synonym or bad word.) It then addresses the student accordingly. In a LEX line, the 3rd wildcard matches the word used by the student; the 4th matches the expected word.

The other branches are analogous.

Procedures versus Drivers

The previous section invites the question as to whether procedures or driver programs should be used for complicated programs.

Advantages of procedures:


If a driver program is chosen, it provides significantly more computational power, but must be written. In our experience, this difficulty may be reduced by producting of a 'library' of runnable programs.