IVI: Functions (1)

IVI

Functions, Procedures and Driver Programs

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 Pascal, and compiled 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.

Functions

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>
/<DEL>
(left brace: {
)right brace: }
!vertical bar: |
` (i.e. backquote) tilde: ~
@ or SPACEnull byte

Example:

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

Notes:

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 {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. The function:

    FU 6|๊tes

however, 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

Examples

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:

xyzxyzxyzxyzxyzxyzxyzxyzxyzxyz

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,

    {?}then-part{:}else-part{.}
    {?}then-part{.}

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 in Functions (2).

{<}, {^} 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.

Warnings:

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.

Examples:

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^>}
BE
FI 0|SUE

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

Looking at the function in more detail:

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

Procedure 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:

AT fr.at
TM fr.tm
LE fr.lex
MO fr.mor
TB fr.tb
SY fr.sy

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 execute 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

If a file named "ivi.start" is present in the current directory when IVI is started up, it is processed as a procedure file. It is anticipated that this will contain any KEy, FOnt, MArgin, EDitor option and other commands to tailor IVI for a particular user.

The procedure is executed before any file mentioned on the shell/DOS 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! (Let the user beware!)


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 in Functions (2).


Driver Programs

As we have already noted, a driver program is a program written in your favorite language and compiled. The compiled 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 fucntions 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.


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 are as follows.

0, 1 - these send no reply, but set a certain switch on or off. When it is off, the secondary output is suppressed. There was once (is still?) a valid reason for this, but it is lost in the mists of time.

2 - the reply is the number already on top of the result stack. So the driver can discover the results of FInds, FEtches, Number operations, and so on.

3 - the cursor line number.

4 - the cursor column number.

5 - how many lines are in the flagged block. (The driver can go to the first starred line using PO *, and then perform some task on every line of the block.)

6 - the column number of the first non-blank on the cursor line; 0 if the line is empty.

7 - the character under the cursor.

8 - the number in the FP register.

9 - whether the filename appearing in the status line is present in the directory and readable (i.e. whether it exists and you have permission to read it).

10 - whether the filename is present and writeable (i.e. whether it exists and you can overwrite it).

11 - whether the filename is absent and writeable (i.e. whether it doesn't exist and you have permission to save to it).

In cases 9, 10 and 11, the result is 1 or 0, representing YES or NO.

12 - the word at the cursor (if the cursor is midword, I think you get the last part)

13 - the rest of the line from the cursor onwards.

14 - this is tailored to the driver acquiring the report produced by the VINCI error comparison process, so that it can analyze it and conduct adaptive processing. (The report is placed in corefile 15.) If corefile 15 contains p lines, the ASk command delivers (p + 1) lines, the first containing p by itself, the rest the actual lines. Only the first 1000 lines are sent. Trailing blanks have been removed. Nothing about the command requires the contents of the corefile actually to be a VINCI error report, but if it is, it was produced by a computer, so the format is precise. This alternative should be generalized for other corefiles.

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 ###

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:

       the corefile number
       the first line
       the last line
       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/Vinci output stream.

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

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, EMit carries out these actions if the fourth parameter is 0, or if there is an installed driver-program which is currently in the ACTIVE state, or both. The reason why EMit can send information to the ivi/Vinci output stream in this case is because this output will pass through the intermediary program, stdnt, which will recognize and intercept it. Indeed, the main purpose of EMit is to allow instr to send information to stdnt (by putting it into a corefile and executing EMit), or vice versa. This, for example, allows instr, having triggered a VinciLingua error analysis, to pass the error report back to stdnt and thus to the webpage.

Keyboard/Driver Interaction

Six keys provide keyboard/driver interaction, and these are summarized in the following table:

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.


Another Perspective

In the preceding description, we have presented a driver program as a helper to the editor, supplying keys to carry out complicated editing task and saving the user from work. An alternative perspective sees IVI as a helper to a driver program.

Suppose some desired program needs to extract information from, or alter, or create, one or more text files or simple textual databases. Then we can write it using IVI to provide the file manipulation facilities. The program is simply run as a driver program from within IVI. To manipulate files and locate information, it outputs IVI operations and commands. To obtain information, it makes use of the ASk command. The actual output of the program is merely written to an IVI corefile and saved. In effect, IVI acts as a very powerful input/output package which combines both activities, and can indeed edit the output after it has been produced.


Return to IVI Reference Manual


Last revision: February 11, 2002.