IVI: Functions (2)


Functions, Procedures and Driver Programs

Part 2

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:

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? Yes

Can you call a driver program from within an IVI procedure? 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? 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 ... ABORT KEY!!!

      `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 are described in Functions (1). 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 * symbols denoting "wildcards". 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:

c1,s1 EXACT
c3,s2 verts/vertes MORPH masc/fém
c4,s4 EXACT
c5,s5 achetterez/acheterées PHON tt/t,ez/ées
c6,s6 EXACT
c7,s8 EXACT
c8,s9 EXACT
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 feminine.
   {:-}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 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:
Keep it within the house,
No need to learn a programming language
For VINCI error reports, the unify operation

Awkward layout (must find convenient places to park LFs)
Loops and conditionals very rudimentary
Calculations cumbersome (but not impossible: use of corefile as scratchpad.

Simplify C programming with a package